FFmpeg源代碼簡單分析-解碼-打開媒體的函數avformat_open_input

參考鏈接

  • 圖解FFMPEG打開媒體的函數avformat_open_input_雷霄驊的博客-CSDN博客_avformat_open_input 使用
  • FFmpeg源代碼簡單分析:avformat_open_input()_雷霄驊的博客-CSDN博客_avformat_open_input()

avformat_open_input

  • FFmpeg打開媒體的的過程開始于avformat_open_input,因此該函數的重要性不可忽視。
int avformat_open_input(AVFormatContext **ps, const char *filename,const AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;FFFormatContext *si;AVDictionary *tmp = NULL;ID3v2ExtraMeta *id3v2_extra_meta = NULL;int ret = 0;if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM);si = ffformatcontext(s);if (!s->av_class) {av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");return AVERROR(EINVAL);}if (fmt)s->iformat = fmt;if (options)av_dict_copy(&tmp, *options, 0);if (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;if ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;if (!(s->url = av_strdup(filename ? filename : ""))) {ret = AVERROR(ENOMEM);goto fail;}if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;s->probe_score = ret;if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);if (!s->protocol_whitelist) {ret = AVERROR(ENOMEM);goto fail;}}if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);if (!s->protocol_blacklist) {ret = AVERROR(ENOMEM);goto fail;}}if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);ret = AVERROR(EINVAL);goto fail;}avio_skip(s->pb, s->skip_initial_bytes);/* Check filename in case an image number is expected. */if (s->iformat->flags & AVFMT_NEEDNUMBER) {if (!av_filename_number_test(filename)) {ret = AVERROR(EINVAL);goto fail;}}s->duration = s->start_time = AV_NOPTS_VALUE;/* Allocate private data. */if (s->iformat->priv_data_size > 0) {if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {ret = AVERROR(ENOMEM);goto fail;}if (s->iformat->priv_class) {*(const AVClass **) s->priv_data = s->iformat->priv_class;av_opt_set_defaults(s->priv_data);if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)goto fail;}}/* e.g. AVFMT_NOFILE formats will not have an AVIOContext */if (s->pb)ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);if (s->iformat->read_header)if ((ret = s->iformat->read_header(s)) < 0) {if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)goto close;goto fail;}if (!s->metadata) {s->metadata    = si->id3v2_meta;si->id3v2_meta = NULL;} else if (si->id3v2_meta) {av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");av_dict_free(&si->id3v2_meta);}if (id3v2_extra_meta) {if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||!strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)goto close;if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)goto close;if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)goto close;} elseav_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");ff_id3v2_free_extra_meta(&id3v2_extra_meta);}if ((ret = avformat_queue_attached_pictures(s)) < 0)goto close;if (s->pb && !si->data_offset)si->data_offset = avio_tell(s->pb);si->raw_packet_buffer_size = 0;update_stream_avctx(s);if (options) {av_dict_free(options);*options = tmp;}*ps = s;return 0;close:if (s->iformat->read_close)s->iformat->read_close(s);
fail:ff_id3v2_free_extra_meta(&id3v2_extra_meta);av_dict_free(&tmp);if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))avio_closep(&s->pb);avformat_free_context(s);*ps = NULL;return ret;
}
  • 在該函數中,FFMPEG完成了:
    • 輸入輸出結構體AVIOContext的初始化;
    • 輸入數據的協議(例如RTMP,或者file)的識別(通過一套評分機制):
      • 1判斷文件名的后綴
      • 2讀取文件頭的數據進行比對;
    • 使用獲得最高分的文件協議對應的URLProtocol,通過函數指針的方式,與FFMPEG連接(非專業用詞);
    • 剩下的就是調用該URLProtocol的函數進行open,read等操作了? 沒看到相關的代碼
  • 函數的形參
    • ps:函數調用成功之后處理過的AVFormatContext結構體。
    • file:打開的視音頻流的URL。
    • fmt:強制指定AVFormatContext中AVInputFormat的。這個參數一般情況下可以設置為NULL,這樣FFmpeg可以自動檢測AVInputFormat。
    • options:附加的一些選項,一般情況下可以設置為NULL。
  • ?函數執行成功的話,其返回值大于等于0。
  • 以下是函數調用關系圖(有存在差異的地方)
    • Avio_open源代碼里面已經不用了,而是使用io_open,只是打開了,但是沒看到open,read等操作的代碼

  • ?avformat_open_input()源代碼比較長,一部分是一些容錯代碼,比如說如果發現傳入的AVFormatContext指針沒有初始化過,就調用avformat_alloc_context()初始化該結構體;
  • 還有一部分是針對一些格式做的特殊處理,比如id3v2信息的處理等等。
  • 有關上述兩種信息不再詳細分析,在這里只選擇它關鍵的兩個函數進行分析:
  • init_input():絕大部分初始化工作都是在這里做的。
  • s->iformat->read_header():讀取多媒體數據文件頭,根據視音頻流創建相應的AVStream。 ?
  • 下面我們逐一看看上述函數。

init_input()

  • 它的主要工作就是打開輸入的視頻數據并且探測視頻的格式
static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
{int ret;AVProbeData pd = { filename, NULL, 0 };int score = AVPROBE_SCORE_RETRY;if (s->pb) {s->flags |= AVFMT_FLAG_CUSTOM_IO;if (!s->iformat)return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);else if (s->iformat->flags & AVFMT_NOFILE)av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and ""will be ignored with AVFMT_NOFILE format.\n");return 0;}if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;if (s->iformat)return 0;return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}
  • 這個函數在短短的幾行代碼中包含了好幾個return,因此邏輯還是有點復雜的,我們可以梳理一下: ?
  • 在函數的開頭的score變量是一個判決AVInputFormat的分數的門限值,如果最后得到的AVInputFormat的分數低于該門限值,就認為沒有找到合適的AVInputFormat。
  • FFmpeg內部判斷封裝格式的原理實際上是對每種AVInputFormat給出一個分數,滿分是100分,越有可能正確的AVInputFormat給出的分數就越高。最后選擇分數最高的AVInputFormat作為推測結果。
  • score的值是一個宏定義AVPROBE_SCORE_RETRY,我們可以看一下它的定義:
    • #define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4) ?
  • 其中AVPROBE_SCORE_MAX是score的最大值,取值是100:
    • #define AVPROBE_SCORE_MAX ? ? ? 100 ///< maximum score ?
  • 由此我們可以得出score取值是25,即如果推測后得到的最佳AVInputFormat的分值低于25,就認為沒有找到合適的AVInputFormat。
  • ?整個函數的邏輯大體如下:
    • (1)當使用了自定義的AVIOContext的時候(AVFormatContext中的AVIOContext不為空,即s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果沒有指定就調用av_probe_input_buffer2()推測AVInputFormat。這一情況出現的不算很多,但是當我們從內存中讀取數據的時候(需要初始化自定義的AVIOContext),就會執行這一步驟。
    • (2)在更一般的情況下,如果已經指定了AVInputFormat,就直接返回;如果沒有指定AVInputFormat,就調用av_probe_input_format(NULL,…)根據文件路徑判斷文件格式。這里特意把av_probe_input_format()的第1個參數寫成“NULL”,是為了強調這個時候實際上并沒有給函數提供輸入數據,此時僅僅通過文件路徑推測AVInputFormat。
    • (3)如果發現通過文件路徑判斷不出來文件格式,那么就需要打開文件探測文件格式了,這個時候會首先調用io_open()打開文件,然后調用av_probe_input_buffer2()推測AVInputFormat。 ?

av_probe_input_format2()

  • av_probe_input_format2()是一個API函數。
const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,int is_opened, int *score_max)
{int score_ret;const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}
  • 該函數用于根據輸入數據查找合適的AVInputFormat。參數含義如下所示:
    • pd:存儲輸入數據信息的AVProbeData結構體。
    • is_opened:文件是否打開。
    • score_max:判決AVInputFormat的門限值。只有某格式判決分數大于該門限值的時候,函數才會返回該封裝格式,否則返回NULL。
  • 從函數中可以看出,av_probe_input_format2()調用了av_probe_input_format3(),并且增加了一個判斷,當av_probe_input_format3()返回的分數大于score_max的時候,才會返回AVInputFormat,否則返回NULL。
  • 下面我們看一下av_probe_input_format3()
  • 從函數聲明中可以看出,av_probe_input_format3()和av_probe_input_format2()的區別是函數的第3個參數不同:av_probe_input_format2()是一個分數的門限值,而av_probe_input_format3()是一個探測后的最匹配的格式的分數值。
  • av_probe_input_format3函數最主要的部分是循環。該循環調用av_iformat_next()遍歷(av_demuxer_iterate函數替代了av_iformat_next)FFmpeg中所有的AVInputFormat,并根據以下規則確定AVInputFormat和輸入媒體數據的匹配分數(score,反應匹配程度):
  • (1)如果AVInputFormat中包含read_probe(),就調用read_probe()函數獲取匹配分數(這一方法如果結果匹配的話,一般會獲得AVPROBE_SCORE_MAX的分值,即100分)。如果不包含該函數,就使用av_match_ext()函數比較輸入媒體的擴展名和AVInputFormat的擴展名是否匹配,如果匹配的話,設定匹配分數為AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值為50,即50分)
  • (2)使用av_match_name()比較輸入媒體的mime_type和AVInputFormat的mime_type,如果匹配的話,設定匹配分數為AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值為75,即75分)
  • (3)如果該AVInputFormat的匹配分數大于此前的最大匹配分數,則記錄當前的匹配分數為最大匹配分數,并且記錄當前的AVInputFormat為最佳匹配的AVInputFormat。
#define AVPROBE_PADDING_SIZE 32             ///< extra allocated bytes at the end of the probe buffer
#define AVPROBE_SCORE_EXTENSION  50 ///< score for file extension
#define AVPROBE_SCORE_MIME       75 ///< score for file mime type
#define AVPROBE_SCORE_MAX       100 ///< maximum scoreconst AVInputFormat *av_probe_input_format3(const AVProbeData *pd,int is_opened, int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;const AVInputFormat *fmt = NULL;int score, score_max = 0;void *i = 0;const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];enum nodat {NO_ID3,ID3_ALMOST_GREATER_PROBE,ID3_GREATER_PROBE,ID3_GREATER_MAX_PROBE,} nodat = NO_ID3;if (!lpd.buf)lpd.buf = (unsigned char *) zerobuffer;if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {int id3len = ff_id3v2_tag_len(lpd.buf);if (lpd.buf_size > id3len + 16) {if (lpd.buf_size < 2LL*id3len + 16)nodat = ID3_ALMOST_GREATER_PROBE;lpd.buf      += id3len;lpd.buf_size -= id3len;} else if (id3len >= PROBE_BUF_MAX) {nodat = ID3_GREATER_MAX_PROBE;} elsenodat = ID3_GREATER_PROBE;}while ((fmt1 = av_demuxer_iterate(&i))) {if (fmt1->flags & AVFMT_EXPERIMENTAL)continue;if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;score = 0;if (fmt1->read_probe) {score = fmt1->read_probe(&lpd);if (score)av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {switch (nodat) {case NO_ID3:score = FFMAX(score, 1);break;case ID3_GREATER_PROBE:case ID3_ALMOST_GREATER_PROBE:score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);break;case ID3_GREATER_MAX_PROBE:score = FFMAX(score, AVPROBE_SCORE_EXTENSION);break;}}} else if (fmt1->extensions) {if (av_match_ext(lpd.filename, fmt1->extensions))score = AVPROBE_SCORE_EXTENSION;}if (av_match_name(lpd.mime_type, fmt1->mime_type)) {if (AVPROBE_SCORE_MIME > score) {av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);score = AVPROBE_SCORE_MIME;}}if (score > score_max) {score_max = score;fmt       = fmt1;} else if (score == score_max)fmt = NULL;}if (nodat == ID3_GREATER_PROBE)score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);*score_ret = score_max;return fmt;
}
const AVInputFormat *av_demuxer_iterate(void **opaque)
{static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1;uintptr_t i = (uintptr_t)*opaque;const AVInputFormat *f = NULL;uintptr_t tmp;if (i < size) {f = demuxer_list[i];} else if (tmp = atomic_load_explicit(&indev_list_intptr, memory_order_relaxed)) {const AVInputFormat *const *indev_list = (const AVInputFormat *const *)tmp;f = indev_list[i - size];}if (f)*opaque = (void*)(i + 1);return f;
}

AVInputFormat->read_probe()

  • ?AVInputFormat中包含read_probe()是用于獲得匹配函數的函數指針,不同的封裝格式包含不同的實現函數。
  • 例如,FLV封裝格式的AVInputFormat模塊定義如下所示。??
const AVInputFormat ff_flv_demuxer = {.name           = "flv",.long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.priv_data_size = sizeof(FLVContext),.read_probe     = flv_probe,.read_header    = flv_read_header,.read_packet    = flv_read_packet,.read_seek      = flv_read_seek,.read_close     = flv_read_close,.extensions     = "flv",.priv_class     = &flv_kux_class,
};
  • 其中,read_probe()函數對應的是flv_probe()函數
  • 我們可以看一下flv_probe()函數的定義:
static int flv_probe(const AVProbeData *p)
{return probe(p, 0);
}
  • 可見flv_probe()調用了一個probe()函數。probe()函數的定義如下。
static int probe(const AVProbeData *p, int live)
{const uint8_t *d = p->buf;unsigned offset = AV_RB32(d + 5);if (d[0] == 'F' &&d[1] == 'L' &&d[2] == 'V' &&d[3] < 5 && d[5] == 0 &&offset + 100 < p->buf_size &&offset > 8) {int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);if (live == is_live)return AVPROBE_SCORE_MAX;}return 0;
}
  • ?從probe()函數我們可以看出,該函數做了如下工作:
  • (1)獲得第6至第9字節的數據(對應Headersize字段)并且做大小端轉換,然后存入offset變量。之所以要進行大小端轉換是因為FLV是以“大端”方式存儲數據,而操作系統是以“小端”方式存儲數據,這一轉換主要通過AV_RB32()函數實現。AV_RB32()是一個宏定義,其對應的函數是av_bswap32()。
  • (2)檢查開頭3個字符(Signature)是否為“FLV”
  • (3)第4個字節(Version)小于5
  • (4)第6個字節(Headersize的第1個字節?)為0
  • (5)offset取值大于8。 ?
  • 參照FLV文件頭的格式可以對上述判斷有一個更清晰的認識:?

  • ??此外代碼中還包含了有關live方式的FLV格式的判斷,在這里我們不加探討。對于我們打開FLV文件來說,live和is_live兩個變量取值都為0。也就是說滿足上述5個條件的話,就可以認為輸入媒體數據是FLV封裝格式了。
  • 滿足上述條件,probe()函數返回AVPROBE_SCORE_MAX(AVPROBE_SCORE_MAX取值為100,即100分),否則返回0(0分)

av_match_name()

  • av_match_name()是一個API函數,聲明位于libavutil\avstring.h,如下所示
  • av_match_name()用于比較兩個格式的名稱。簡單地說就是比較字符串。注意該函數的字符串是不區分大小寫的:字符都轉換為小寫進行比較。
int av_match_name(const char *name, const char *names)
{const char *p;int len, namelen;if (!name || !names)return 0;namelen = strlen(name);while (*names) {int negate = '-' == *names;p = strchr(names, ',');if (!p)p = names + strlen(names);names += negate;len = FFMAX(p - names, namelen);if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))return !negate;names = p + (*p == ',');}return 0;
}
  • 上述函數還有一點需要注意,其中使用了一個while()循環,用于搜索“,”。這是因為FFmpeg中有些格式是對應多種格式名稱的,例如MKV格式的解復用器(Demuxer)的定義如下
const AVInputFormat ff_matroska_demuxer = {.name           = "matroska,webm",.long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),.extensions     = "mkv,mk3d,mka,mks,webm",.priv_data_size = sizeof(MatroskaDemuxContext),.flags_internal = FF_FMT_INIT_CLEANUP,.read_probe     = matroska_probe,.read_header    = matroska_read_header,.read_packet    = matroska_read_packet,.read_close     = matroska_read_close,.read_seek      = matroska_read_seek,.mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
};
  • ?從代碼可以看出,ff_matroska_demuxer中的name字段對應“matroska,webm”,mime_type字段對應“audio/webm,audio/x-matroska,video/webm,video/x-matroska”。
  • av_match_name()函數對于這樣的字符串,會把它按照“,”截斷成一個個的名稱,然后一一進行比較。

av_match_ext()

  • av_match_ext()是一個API函數,聲明位于libavformat\avformat.h(注意位置和av_match_name()不一樣),如下所示
  • av_match_ext()用于比較文件的后綴。該函數首先通過反向查找的方式找到輸入文件名中的“.”,就可以通過獲取“.”后面的字符串來得到該文件的后綴。
  • 然后調用av_match_name(),采用和比較格式名稱的方法比較兩個后綴。
/*** @file* Format register and lookup*/int av_match_ext(const char *filename, const char *extensions)
{const char *ext;if (!filename)return 0;ext = strrchr(filename, '.');if (ext)return av_match_name(ext + 1, extensions);return 0;
}

AVProbeData

  • av_probe_input_format3()根據輸入數據查找合適的AVInputFormat。輸入的數據位于AVProbeData中?
  • 該函數中涉及到一個結構體AVProbeData,用于存儲輸入文件的一些信息,它的定義如下所示。
  • 其中filename是文件路徑
  • buf存儲用于推測AVInputFormat的媒體數據,其中buf可以為空,但是其后面無論如何都需要填充AVPROBE_PADDING_SIZE個0(AVPROBE_PADDING_SIZE取值為32,即32個0)。
  • 最后還有個mime_type保存媒體的類型。
/*** This structure contains the data a format has to probe a file.*/
typedef struct AVProbeData {const char *filename;unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */int buf_size;       /**< Size of buf except extra allocated bytes */const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
    /*** A callback for opening new IO streams.** Whenever a muxer or a demuxer needs to open an IO stream (typically from* avformat_open_input() for demuxers, but for certain formats can happen at* other times as well), it will call this callback to obtain an IO context.** @param s the format context* @param pb on success, the newly opened IO context should be returned here* @param url the url to open* @param flags a combination of AVIO_FLAG_** @param options a dictionary of additional options, with the same*                semantics as in avio_open2()* @return 0 on success, a negative AVERROR code on failure** @note Certain muxers and demuxers do nesting, i.e. they open one or more* additional internal format contexts. Thus the AVFormatContext pointer* passed to this callback may be different from the one facing the caller.* It will, however, have the same 'opaque' field.*/int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,int flags, AVDictionary **options);

av_probe_input_buffer2()

  • av_probe_input_buffer2()是一個API函數,它根據輸入的媒體數據推測該媒體數據的AVInputFormat,聲明位于libavformat\avformat.h,如下所示。
/*** Probe a bytestream to determine the input format. Each time a probe returns* with a score that is too low, the probe buffer size is increased and another* attempt is made. When the maximum probe size is reached, the input format* with the highest score is returned.** @param pb the bytestream to probe* @param fmt the input format is put here* @param url the url of the stream* @param logctx the log context* @param offset the offset within the bytestream to probe from* @param max_probe_size the maximum probe buffer size (zero for default)* @return the score in case of success, a negative value corresponding to an*         the maximal score is AVPROBE_SCORE_MAX* AVERROR code otherwise*/
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,const char *url, void *logctx,unsigned int offset, unsigned int max_probe_size);
  • av_probe_input_buffer2()參數的含義如下所示:
    • pb:用于讀取數據的AVIOContext。
    • fmt:輸出推測出來的AVInputFormat。
    • url:輸入媒體的路徑。
    • logctx:日志(沒有研究過)。
    • offset:開始推測AVInputFormat的偏移量。
    • max_probe_size:用于推測格式的媒體數據的最大值。
    • ?返回推測后的得到的AVInputFormat的匹配分數。
  • ?av_probe_input_buffer2()的定義位于libavformat\format.c,如下所示。
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,const char *filename, void *logctx,unsigned int offset, unsigned int max_probe_size)
{AVProbeData pd = { filename ? filename : "" };uint8_t *buf = NULL;int ret = 0, probe_size, buf_offset = 0;int score = 0;int ret2;if (!max_probe_size)max_probe_size = PROBE_BUF_MAX;else if (max_probe_size < PROBE_BUF_MIN) {av_log(logctx, AV_LOG_ERROR,"Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);return AVERROR(EINVAL);}if (offset >= max_probe_size)return AVERROR(EINVAL);if (pb->av_class) {uint8_t *mime_type_opt = NULL;char *semi;av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);pd.mime_type = (const char *)mime_type_opt;semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;if (semi) {*semi = '\0';}}for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;probe_size = FFMIN(probe_size << 1,FFMAX(max_probe_size, probe_size + 1))) {score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;/* Read probe data. */if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)goto fail;if ((ret = avio_read(pb, buf + buf_offset,probe_size - buf_offset)) < 0) {/* Fail if error was not end of file, otherwise, lower score. */if (ret != AVERROR_EOF)goto fail;score = 0;ret   = 0;          /* error was end of file, nothing read */}buf_offset += ret;if (buf_offset < offset)continue;pd.buf_size = buf_offset - offset;pd.buf = &buf[offset];memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);/* Guess file format. */*fmt = av_probe_input_format2(&pd, 1, &score);if (*fmt) {/* This can only be true in the last iteration. */if (score <= AVPROBE_SCORE_RETRY) {av_log(logctx, AV_LOG_WARNING,"Format %s detected only with low score of %d, ""misdetection possible!\n", (*fmt)->name, score);} elseav_log(logctx, AV_LOG_DEBUG,"Format %s probed with size=%d and score=%d\n",(*fmt)->name, probe_size, score);
#if 0FILE *f = fopen("probestat.tmp", "ab");fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);fclose(f);
#endif}}if (!*fmt)ret = AVERROR_INVALIDDATA;fail:/* Rewind. Reuse probe buffer to avoid seeking. */ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);if (ret >= 0)ret = ret2;av_freep(&pd.mime_type);return ret < 0 ? ret : score;
}
  • av_probe_input_buffer2()首先需要確定用于推測格式的媒體數據的最大值max_probe_size
  • max_probe_size默認為PROBE_BUF_MAX(PROBE_BUF_MAX取值為1 << 20,即1048576Byte,大約1MB)。
  • ?在確定了max_probe_size之后,函數就會進入到一個循環中,調用avio_read()讀取數據并且使用av_probe_input_format2()(該函數前文已經記錄過)推測文件格式。
  • ?肯定有人會奇怪這里為什么要使用一個循環,而不是只運行一次?其實這個循環是一個逐漸增加輸入媒體數據量的過程。av_probe_input_buffer2()并不是一次性讀取max_probe_size字節的媒體數據,我個人感覺可能是因為這樣做不是很經濟,因為推測大部分媒體格式根本用不到1MB這么多的媒體數據。因此函數中使用一個probe_size存儲需要讀取的字節數,并且隨著循環次數的增加逐漸增加這個值。函數首先從PROBE_BUF_MIN(取值為2048)個字節開始讀取,如果通過這些數據已經可以推測出AVInputFormat,那么就可以直接退出循環了(參考for循環的判斷條件“!*fmt”);如果沒有推測出來,就增加probe_size的量為過去的2倍(參考for循環的表達式“probe_size << 1”),繼續推測AVInputFormat;如果一直讀取到max_probe_size字節的數據依然沒能確定AVInputFormat,則會退出循環并且返回錯誤信息。

AVInputFormat-> read_header()

  • ?在調用完init_input()完成基本的初始化并且推測得到相應的AVInputFormat之后,avformat_open_input()會調用AVInputFormat的read_header()方法讀取媒體文件的文件頭并且完成相關的初始化工作。
  • read_header()是一個位于AVInputFormat結構體中的一個函數指針,對于不同的封裝格式,會調用不同的read_header()的實現函數。
  • 舉個例子
    • 當輸入視頻的封裝格式為FLV的時候,會調用FLV的AVInputFormat中的read_header()
  • FLV的AVInputFormat定義位于libavformat\flvdec.c文件中,如下所示。
const AVInputFormat ff_flv_demuxer = {.name           = "flv",.long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.priv_data_size = sizeof(FLVContext),.read_probe     = flv_probe,.read_header    = flv_read_header,.read_packet    = flv_read_packet,.read_seek      = flv_read_seek,.read_close     = flv_read_close,.extensions     = "flv",.priv_class     = &flv_kux_class,
};
  • 可以看出read_header()指向了flv_read_header()函數
  • flv_read_header()的實現同樣位于libavformat\flvdec.c文件中,如下所示。?
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{FLVContext *flv = s->priv_data;int ret, i, size, flags;enum FlvTagType type;int stream_type=-1;int64_t next, pos, meta_pos;int64_t dts, pts = AV_NOPTS_VALUE;int av_uninit(channels);int av_uninit(sample_rate);AVStream *st    = NULL;int last = -1;int orig_size;retry:/* pkt size is repeated at end. skip it */pos  = avio_tell(s->pb);type = (avio_r8(s->pb) & 0x1F);orig_size =size = avio_rb24(s->pb);flv->sum_flv_tag_size += size + 11;dts  = avio_rb24(s->pb);dts |= (unsigned)avio_r8(s->pb) << 24;av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb));if (avio_feof(s->pb))return AVERROR_EOF;avio_skip(s->pb, 3); /* stream id, always 0 */flags = 0;if (flv->validate_next < flv->validate_count) {int64_t validate_pos = flv->validate_index[flv->validate_next].pos;if (pos == validate_pos) {if (FFABS(dts - flv->validate_index[flv->validate_next].dts) <=VALIDATE_INDEX_TS_THRESH) {flv->validate_next++;} else {clear_index_entries(s, validate_pos);flv->validate_count = 0;}} else if (pos > validate_pos) {clear_index_entries(s, validate_pos);flv->validate_count = 0;}}if (size == 0) {ret = FFERROR_REDO;goto leave;}next = size + avio_tell(s->pb);if (type == FLV_TAG_TYPE_AUDIO) {stream_type = FLV_STREAM_TYPE_AUDIO;flags    = avio_r8(s->pb);size--;} else if (type == FLV_TAG_TYPE_VIDEO) {stream_type = FLV_STREAM_TYPE_VIDEO;flags    = avio_r8(s->pb);size--;if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD)goto skip;} else if (type == FLV_TAG_TYPE_META) {stream_type=FLV_STREAM_TYPE_SUBTITLE;if (size > 13 + 1 + 4) { // Header-type metadata stuffint type;meta_pos = avio_tell(s->pb);type = flv_read_metabody(s, next);if (type == 0 && dts == 0 || type < 0) {if (type < 0 && flv->validate_count &&flv->validate_index[0].pos     > next &&flv->validate_index[0].pos - 4 < next) {av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n");next = flv->validate_index[0].pos - 4;}goto skip;} else if (type == TYPE_ONTEXTDATA) {avpriv_request_sample(s, "OnTextData packet");return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_ONCAPTION) {return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_UNKNOWN) {stream_type = FLV_STREAM_TYPE_DATA;}avio_seek(s->pb, meta_pos, SEEK_SET);}} else {av_log(s, AV_LOG_DEBUG,"Skipping flv packet: type %d, size %d, flags %d.\n",type, size, flags);
skip:if (avio_seek(s->pb, next, SEEK_SET) != next) {// This can happen if flv_read_metabody above read past// next, on a non-seekable input, and the preceding data has// been flushed out from the IO buffer.av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n");return AVERROR_INVALIDDATA;}ret = FFERROR_REDO;goto leave;}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}/* now find stream */for (i = 0; i < s->nb_streams; i++) {st = s->streams[i];if (stream_type == FLV_STREAM_TYPE_AUDIO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&(s->audio_codec_id || flv_same_audio_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&(s->video_codec_id || flv_same_video_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)break;} else if (stream_type == FLV_STREAM_TYPE_DATA) {if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA)break;}}if (i == s->nb_streams) {static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_DATA};st = create_stream(s, stream_types[stream_type]);if (!st)return AVERROR(ENOMEM);}av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard);if (flv->time_pos <= pos) {dts += flv->time_offset;}if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_AUDIO))av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME);if ((st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || stream_type == FLV_STREAM_TYPE_AUDIO)) ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && stream_type == FLV_STREAM_TYPE_VIDEO)) ||st->discard >= AVDISCARD_ALL) {avio_seek(s->pb, next, SEEK_SET);ret = FFERROR_REDO;goto leave;}// if not streamed and no duration from metadata then seek to end to find// the duration from the timestampsif ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&(!s->duration || s->duration == AV_NOPTS_VALUE) &&!flv->searched_for_end) {int size;const int64_t pos   = avio_tell(s->pb);// Read the last 4 bytes of the file, this should be the size of the// previous FLV tag. Use the timestamp of its payload as duration.int64_t fsize       = avio_size(s->pb);
retry_duration:avio_seek(s->pb, fsize - 4, SEEK_SET);size = avio_rb32(s->pb);if (size > 0 && size < fsize) {// Seek to the start of the last FLV tag at position (fsize - 4 - size)// but skip the byte indicating the type.avio_seek(s->pb, fsize - 3 - size, SEEK_SET);if (size == avio_rb24(s->pb) + 11) {uint32_t ts = avio_rb24(s->pb);ts         |= (unsigned)avio_r8(s->pb) << 24;if (ts)s->duration = ts * (int64_t)AV_TIME_BASE / 1000;else if (fsize >= 8 && fsize - 8 >= size) {fsize -= size+4;goto retry_duration;}}}avio_seek(s->pb, pos, SEEK_SET);flv->searched_for_end = 1;}if (stream_type == FLV_STREAM_TYPE_AUDIO) {int bits_per_coded_sample;channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;bits_per_coded_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;if (!av_channel_layout_check(&st->codecpar->ch_layout) ||!st->codecpar->sample_rate ||!st->codecpar->bits_per_coded_sample) {av_channel_layout_default(&st->codecpar->ch_layout, channels);st->codecpar->sample_rate           = sample_rate;st->codecpar->bits_per_coded_sample = bits_per_coded_sample;}if (!st->codecpar->codec_id) {flv_set_audio_codec(s, st, st->codecpar,flags & FLV_AUDIO_CODECID_MASK);flv->last_sample_rate =sample_rate           = st->codecpar->sample_rate;flv->last_channels    =channels              = st->codecpar->ch_layout.nb_channels;} else {AVCodecParameters *par = avcodec_parameters_alloc();if (!par) {ret = AVERROR(ENOMEM);goto leave;}par->sample_rate = sample_rate;par->bits_per_coded_sample = bits_per_coded_sample;flv_set_audio_codec(s, st, par, flags & FLV_AUDIO_CODECID_MASK);sample_rate = par->sample_rate;avcodec_parameters_free(&par);}} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {int ret = flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK, 1);if (ret < 0)return ret;size -= ret;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {st->codecpar->codec_id = AV_CODEC_ID_TEXT;} else if (stream_type == FLV_STREAM_TYPE_DATA) {st->codecpar->codec_id = AV_CODEC_ID_NONE; // Opaque AMF data}if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 ||st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {int type = avio_r8(s->pb);size--;if (size < 0) {ret = AVERROR_INVALIDDATA;goto leave;}if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {// sign extensionint32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;pts = av_sat_add64(dts, cts);if (cts < 0) { // dts might be wrongif (!flv->wrong_dts)av_log(s, AV_LOG_WARNING,"Negative cts, previous timestamps might be wrong.\n");flv->wrong_dts = 1;} else if (FFABS(dts - pts) > 1000*60*15) {av_log(s, AV_LOG_WARNING,"invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);dts = pts = AV_NOPTS_VALUE;}}if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264)) {AVDictionaryEntry *t;if (st->codecpar->extradata) {if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)return ret;ret = FFERROR_REDO;goto leave;}if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;/* Workaround for buggy Omnia A/XE encoder */t = av_dict_get(s->metadata, "Encoder", NULL, 0);if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))st->codecpar->extradata_size = 2;ret = FFERROR_REDO;goto leave;}}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}ret = av_get_packet(s->pb, pkt, size);if (ret < 0)return ret;pkt->dts          = dts;pkt->pts          = pts == AV_NOPTS_VALUE ? dts : pts;pkt->stream_index = st->index;pkt->pos          = pos;if (flv->new_extradata[stream_type]) {int ret = av_packet_add_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,flv->new_extradata[stream_type],flv->new_extradata_size[stream_type]);if (ret >= 0) {flv->new_extradata[stream_type]      = NULL;flv->new_extradata_size[stream_type] = 0;}}if (stream_type == FLV_STREAM_TYPE_AUDIO &&(sample_rate != flv->last_sample_rate ||channels    != flv->last_channels)) {flv->last_sample_rate = sample_rate;flv->last_channels    = channels;ff_add_param_change(pkt, channels, 0, sample_rate, 0, 0);}if (stream_type == FLV_STREAM_TYPE_AUDIO ||(flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_SUBTITLE ||stream_type == FLV_STREAM_TYPE_DATA)pkt->flags |= AV_PKT_FLAG_KEY;leave:last = avio_rb32(s->pb);if (!flv->trust_datasize) {if (last != orig_size + 11 && last != orig_size + 10 &&!avio_feof(s->pb) &&(last != orig_size || !last) && last != flv->sum_flv_tag_size &&!flv->broken_sizes) {av_log(s, AV_LOG_ERROR, "Packet mismatch %d %d %d\n", last, orig_size + 11, flv->sum_flv_tag_size);avio_seek(s->pb, pos + 1, SEEK_SET);ret = resync(s);av_packet_unref(pkt);if (ret >= 0) {goto retry;}}}if (ret >= 0)flv->last_ts = pkt->dts;return ret;
}
  • 可以看出,函數讀取了FLV的文件頭并且判斷其中是否包含視頻流和音頻流。如果包含視頻流或者音頻流,就會調用create_stream()函數。

create_stream

  • create_stream()函數定義也位于libavformat\flvdec.c中,如下所示。?
static int create_stream(AVFormatContext *s)
{XCBGrabContext *c = s->priv_data;AVStream *st      = avformat_new_stream(s, NULL);xcb_get_geometry_cookie_t gc;xcb_get_geometry_reply_t *geo;int64_t frame_size_bits;int ret;if (!st)return AVERROR(ENOMEM);ret = av_parse_video_rate(&st->avg_frame_rate, c->framerate);if (ret < 0)return ret;avpriv_set_pts_info(st, 64, 1, 1000000);gc  = xcb_get_geometry(c->conn, c->window_id);geo = xcb_get_geometry_reply(c->conn, gc, NULL);if (!geo) {av_log(s, AV_LOG_ERROR, "Can't find window '0x%x', aborting.\n", c->window_id);return AVERROR_EXTERNAL;}if (!c->width || !c->height) {c->width = geo->width;c->height = geo->height;}if (c->x + c->width > geo->width ||c->y + c->height > geo->height) {av_log(s, AV_LOG_ERROR,"Capture area %dx%d at position %d.%d ""outside the screen size %dx%d\n",c->width, c->height,c->x, c->y,geo->width, geo->height);free(geo);return AVERROR(EINVAL);}c->time_base  = (AVRational){ st->avg_frame_rate.den,st->avg_frame_rate.num };c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);c->time_frame = av_gettime_relative();ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format, &c->bpp);free(geo);if (ret < 0)return ret;frame_size_bits = (int64_t)c->width * c->height * c->bpp;if (frame_size_bits / 8 + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) {av_log(s, AV_LOG_ERROR, "Captured area is too large\n");return AVERROR_PATCHWELCOME;}c->frame_size = frame_size_bits / 8;#if CONFIG_LIBXCB_SHMc->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,c->conn, allocate_shm_buffer, NULL);if (!c->shm_pool)return AVERROR(ENOMEM);
#endifst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;st->codecpar->codec_id   = AV_CODEC_ID_RAWVIDEO;st->codecpar->width      = c->width;st->codecpar->height     = c->height;st->codecpar->bit_rate   = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);return ret;
}
  • 從代碼中可以看出,create_stream()調用了API函數avformat_new_stream()創建相應的視頻流和音頻流。
  • 上面這段解析FLV頭的代碼可以參考一下FLV封裝格式的文件頭格式,如下圖所示。

  • ?經過上面的步驟AVInputFormat的read_header()完成了視音頻流對應的AVStream的創建。至此,avformat_open_input()中的主要代碼分析完畢。

補充?

  • ?可見最終都調用了URLProtocol結構體中的函數指針。? ?并未得出此結論
  • URLProtocol結構如下,是一大堆函數指針的集合(avio.h文件)
typedef struct URLProtocol {const char *name;int     (*url_open)( URLContext *h, const char *url, int flags);/*** This callback is to be used by protocols which open further nested* protocols. options are then to be passed to ffurl_open_whitelist()* or ffurl_connect() for those nested protocols.*/int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);int     (*url_accept)(URLContext *s, URLContext **c);int     (*url_handshake)(URLContext *c);/*** Read data from the protocol.* If data is immediately available (even less than size), EOF is* reached or an error occurs (including EINTR), return immediately.* Otherwise:* In non-blocking mode, return AVERROR(EAGAIN) immediately.* In blocking mode, wait for data/EOF/error with a short timeout (0.1s),* and return AVERROR(EAGAIN) on timeout.* Checking interrupt_callback, looping on EINTR and EAGAIN and until* enough data has been read is left to the calling function; see* retry_transfer_wrapper in avio.c.*/int     (*url_read)( URLContext *h, unsigned char *buf, int size);int     (*url_write)(URLContext *h, const unsigned char *buf, int size);int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);int     (*url_close)(URLContext *h);int (*url_read_pause)(URLContext *h, int pause);int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);int (*url_get_file_handle)(URLContext *h);int (*url_get_multi_file_handle)(URLContext *h, int **handles,int *numhandles);int (*url_get_short_seek)(URLContext *h);int (*url_shutdown)(URLContext *h, int flags);const AVClass *priv_data_class;int priv_data_size;int flags;int (*url_check)(URLContext *h, int mask);int (*url_open_dir)(URLContext *h);int (*url_read_dir)(URLContext *h, AVIODirEntry **next);int (*url_close_dir)(URLContext *h);int (*url_delete)(URLContext *h);int (*url_move)(URLContext *h_src, URLContext *h_dst);const char *default_whitelist;
} URLProtocol;
  • URLProtocol功能就是完成各種輸入協議的讀寫等操作
  • 但輸入協議種類繁多,它是怎樣做到“大一統”的呢?
  • 原來,每個具體的輸入協議都有自己對應的URLProtocol。
  • 比如file協議(FFMPEG把文件也當做一種特殊的協議)(*file.c文件)
  • 可見它們把各自的函數指針都賦值給了URLProtocol結構體的函數指針
  • 因此avformat_open_input只需調用url_open,url_read這些函數就可以完成各種具體輸入協議的open,read等操作了

?

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445962.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445962.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445962.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

redis session java獲取attribute_面試題:給我說說你能想到幾種分布式session實現?...

作者&#xff1a;yanglbme 來源&#xff1a;https://github.com/doocs/advanced-java/blob/master/docs/distributed-system/distributed-session.md# 面試官心理分析面試官問了你一堆 dubbo 是怎么玩兒的&#xff0c;你會玩兒 dubbo 就可以把單塊系統弄成分布式系統&#xff0…

Projection投影

解釋一 Projection means choosing which columns (or expressions) the query shall return. Selection means which rows are to be returned. if the query is select a, b, c from foobar where x3;then “a, b, c” is the projection part, “where x3” the selecti…

FFmpeg源代碼簡單分析-解碼-avformat_find_stream_info()

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avformat_find_stream_info()_雷霄驊的博客-CSDN博客_avformat_find_stream_info avformat_find_stream_info() ?該函數可以讀取一部分視音頻數據并且獲得一些相關的信息avformat_find_stream_info()的聲明位于libavformat\avform…

Tail Recursion尾遞歸

什么是尾遞歸 Tail Recursion /te?l r??k??r?n/ In traditional recursion, the typical model is that you perform your recursive calls first, and then you take the return value of the recursive call and calculate the result. In this manner, you don’t g…

python遞歸算法案例教案_python教案

第五單元進階程序設計(總10課時)第一節選擇編程語言(1課時)一、教學目標1、了解程序設計語言和兩種翻譯方式&#xff1b;2、了解Python背景、功能、安裝&#xff0c;熟悉Python編程環境&#xff1b;3、編程初體驗。體驗一個小程序從建立、輸入、調試、運行、保存的全過程。掌握…

FFmpeg源代碼簡單分析-解碼-av_read_frame()

參考鏈接 ffmpeg 源代碼簡單分析 &#xff1a; av_read_frame()_雷霄驊的博客-CSDN博客_ffmpeg frame av_read_frame() ffmpeg中的av_read_frame()的作用是讀取碼流中的音頻若干幀或者視頻一幀。例如&#xff0c;解碼視頻的時候&#xff0c;每解碼一個視頻幀&#xff0c;需要…

數據庫 流量切分_私域流量之社群運營技巧,社群運營技巧解析

一、明白社群運營的目的1、社群的目的確立任何一個社群(組織)成立的時分&#xff0c;都是承載著一定的目的的&#xff0c;這個目的就像是北極星一樣&#xff0c;指引著我們的方向。確立運營目的的過程&#xff0c;也是在尋覓北極星的過程。社群運營屬于觸達用戶的一種方式&…

用Python在Tomcat成功啟動后自動打開瀏覽器訪問Web應用

前提條件 WindowsPython 2.7需設置CATALINA_HOME環境變量 放碼過來 # -*- coding: utf-8 -* import os import time import subprocesstomcatStartFilePath C:\\tomcat\\apache-tomcat-7.0.90-windows-x64\\apache-tomcat-7.0.90\\bin\\startup.bat browserPath C:\\Users…

FFmpeg源代碼簡單分析-解碼-avcodec_send_packet 和 avcodec_receive_frame 替代 avcodec_decode_video2

參考鏈接 ffmpeg 源代碼簡單分析 &#xff1a; avcodec_decode_video2()_雷霄驊的博客-CSDN博客_avcodec_decode_video2 avcodec_decode_video2 ffmpeg中的avcodec_decode_video2()的作用是解碼一幀視頻數據。輸入一個壓縮編碼的結構體AVPacket&#xff0c;輸出一個解碼后的結…

FFmpeg源代碼簡單分析-解碼-avformat_close_input()

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avformat_close_input()_雷霄驊的博客-CSDN博客_avformat_close_input avformat_close_input() 本文簡單分析FFmpeg的avformat_close_input()函數。該函數用于關閉一個AVFormatContext&#xff0c;一般情況下是和avformat_open_inp…

android 使用shell模擬觸屏_[Android]通過adb shell input上報命令模擬屏幕點擊事件【轉】...

常用的 input上報命令&#xff1a;input text 1234 實際向界面注入1234文字&#xff0c;有輸入框&#xff0c;能明顯看到效果input keyevent 4 鍵盤事件&#xff0c;4 為返回input tap 100 300 單擊觸屏事件 &#xff0c;模擬點擊x100 y 300 位置input swipe 100 300 500 300 …

用Python連接MySQL并進行CRUD

Tag: MySQL, PyMySQL, Python 準備條件 Python 2.7MySQL 5.5安裝 PyMySQL pip install PyMySQL 放碼過來 創建一數據表 CREATE TABLE users (id int(11) NOT NULL AUTO_INCREMENT,email varchar(255) COLLATE utf8_bin NOT NULL,password varchar(255) COLLATE utf8_bin N…

python網絡爬蟲的方法有幾種_Python網絡爬蟲過程中5種網頁去重方法簡要介紹

一般的&#xff0c;我們想抓取一個網站所有的URL&#xff0c;首先通過起始URL&#xff0c;之后通過網絡爬蟲提取出該網頁中所有的URL鏈接&#xff0c;之后再對提取出來的每個URL進行爬取&#xff0c;提取出各個網頁中的新一輪URL&#xff0c;以此類推。整體的感覺就是自上而下進…

FFmpeg源代碼簡單分析-編碼-avformat_alloc_output_context2()

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avformat_alloc_output_context2()_雷霄驊的博客-CSDN博客_avformat_alloc_context avformat_alloc_output_context2() 在基于FFmpeg的視音頻編碼器程序中&#xff0c;該函數通常是第一個調用的函數&#xff08;除了組件注冊函數av…

《深入理解JVM.2nd》筆記(一):走進Java

概述 Java技術體系 Java程序設計語言各種硬件平臺上的Java虛擬機Class文件格式Java API類庫來自商業機構和開源社區的第三方Java類庫 Java發展史 Java虛擬機發展史 展望Java技術的未來 模塊化 混合語言 多核并行 進一步豐富語法 64位虛擬機 實戰&#xff1a;自己編譯…

js監聽只讀文本框_js 動態控制 input 框 的只讀屬性

input 框的只讀屬性&#xff1a; readonly在頁面中直接添加為只讀時&#xff0c;可在input中直接添加 readonly"readonly"&#xff0c;但是如果想通過點擊按鈕來改變的話&#xff0c;需要通過js(或jquery)來實現。最近一次使用這個&#xff0c;終于發現了以前寫這…

FFmpeg源代碼簡單分析-編碼-avformat_write_header()

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avformat_write_header()_雷霄驊的博客-CSDN博客_avformat_write_header avformat_write_header() FFmpeg寫文件用到的3個函數&#xff1a;avformat_write_header()&#xff0c;av_write_frame()以及av_write_trailer()其中av_writ…

《深入理解JVM.2nd》筆記(二):Java內存區域與內存溢出異常

文章目錄概述運行時數據區域程序計數器Java虛擬機棧本地方法棧Java堆方法區運行時常量池直接內存HotSpot虛擬機對象探秘對象的創建第一步第二步第三步第四步最后一腳對象的內存布局對象頭Header第一部分第二部分實例數據Instance對齊填充Padding對象的訪問定位句柄直接指針對象…

vue底部選擇器_Vue組件-極簡的地址選擇器

一、前言本文用Vue完成一個極簡的地點選擇器&#xff0c;我們接下來帶大家實現這個。當然其中也有一些值得學習與注意的地方。話不多說&#xff0c;我們先上demo圖。因為每個人的需要不一樣&#xff0c;我這邊就不在實現更多的功能&#xff0c;所以留有更大的空間供大家增刪改。…

FFmpeg源代碼簡單分析-編碼-avcodec_encode_video()已被send_frame 和 receive_packet替代

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avcodec_encode_video()_雷霄驊的博客-CSDN博客_avcodec_encode_video2 avcodec_encode_video() 該函數用于編碼一幀視頻數據。函數已被棄用參考鏈接&#xff1a;FFmpeg 新舊版本編碼 API 的區別_zouzhiheng的博客-CSDN博客 send_f…