多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > php教程 > FFmpeg源代碼簡單分析:avformat_open_input()

FFmpeg源代碼簡單分析:avformat_open_input()

來源:程序員人生   發布時間:2015-03-23 08:09:56 閱讀次數:8484次

本文簡單分析FFmpeg中1個經常使用的函數:avformat_open_input()。該函數用于打開多媒體數據并且取得1些相干的信息。它的聲明位于libavformatavformat.h,以下所示。

/** * Open an input stream and read the header. The codecs are not opened. * The stream must be closed with avformat_close_input(). * * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context). * May be a pointer to NULL, in which case an AVFormatContext is allocated by this * function and written into ps. * Note that a user-supplied AVFormatContext will be freed on failure. * @param filename Name of the stream to open. * @param fmt If non-NULL, this parameter forces a specific input format. * Otherwise the format is autodetected. * @param options A dictionary filled with AVFormatContext and demuxer-private options. * On return this parameter will be destroyed and replaced with a dict containing * options that were not found. May be NULL. * * @return 0 on success, a negative AVERROR on failure. * * @note If you want to use custom IO, preallocate the format context and set its pb field. */ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);

代碼中的英文注釋寫的已比較詳細了,在這里拿中文簡單敘述1下。

ps:函數調用成功以后處理過的AVFormatContext結構體。
file:打開的視音頻流的URL。
fmt:強迫指定AVFormatContext中AVInputFormat的。這個參數1般情況下可以設置為NULL,這樣FFmpeg可以自動檢測AVInputFormat。

dictionay:附加的1些選項,1般情況下可以設置為NULL。

函數履行成功的話,其返回值大于等于0。

該函數最典型的例子可以參考: 最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采取SDL2.0)

此前已粗略寫了1篇關于avformat_open_input()的文章《 圖解FFMPEG打開媒體的函數avformat_open_input》,還轉載了1篇注釋比較詳細的文章《FFMPEG源碼分析:avformat_open_input()(媒體打開函數)》。但是個人感覺這個函數確切太重要了,可以算作FFmpeg的“靈魂”,所以打算再寫1篇文章分析1下它的結構。


函數調用關系圖

函數調用結構圖以下所示。


單擊查看更清晰的圖片

avformat_open_input()

下面看1下的定義,位于libavformatutils.c中,以下所示。

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) { AVFormatContext *s = *ps; int ret = 0; AVDictionary *tmp = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL; if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); 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 "); return AVERROR(EINVAL); } if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, *options, 0); if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail; if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; s->probe_score = ret; if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) { av_log(s, AV_LOG_ERROR, "Format not on 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; av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename)); /* 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 a AVIOContext */ if (s->pb) ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0); if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((ret = s->iformat->read_header(s)) < 0) goto fail; if (id3v2_extra_meta) { if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") || !strcmp(s->iformat->name, "tta")) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) goto fail; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping "); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset) s->data_offset = avio_tell(s->pb); s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; if (options) { av_dict_free(options); *options = tmp; } *ps = s; return 0; fail: ff_id3v2_free_extra_meta(&id3v2_extra_meta); av_dict_free(&tmp); if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) avio_close(s->pb); avformat_free_context(s); *ps = NULL; return ret; }

avformat_open_input()源代碼比較長,1部份是1些容錯代碼,比如說如果發現傳入的AVFormatContext指針沒有初始化過,就調用avformat_alloc_context()初始化該結構體;還有1部份是針對1些格式做的特殊處理,比如id3v2信息的處理等等。有關上述兩種信息不再詳細分析,在這里只選擇它關鍵的兩個函數進行分析:
init_input():絕大部份初始化工作都是在這里做的。

s->iformat->read_header():讀取多媒體數據文件頭,根據視音頻流創建相應的AVStream。

下面我們逐1看看上述函數。


init_input()

init_input()作為1個內部函數,居然包括了1行注釋(1般內部函數都沒有注釋),足可以看出它的重要性。它的主要工作就是打開輸入的視頻數據并且探測視頻的格式。該函數的定義位于libavformatutils.c,以下所示。

/* Open input file and probe the format if necessary. */ 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. "); 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 = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags, &s->interrupt_callback, 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,因此邏輯還是有點復雜的,我們可以梳理1下:
在函數的開頭的score變量是1個判決AVInputFormat的分數的門限值,如果最后得到的AVInputFormat的分數低于該門限值,就認為沒有找到適合的AVInputFormat。FFmpeg內部判斷封裝格式的原理實際上是對每種AVInputFormat給出1個分數,滿分是100分,越有可能正確的AVInputFormat給出的分數就越高。最后選擇分數最高的AVInputFormat作為推測結果。score的值是1個宏定義AVPROBE_SCORE_RETRY,我們可以看1下它的定義:
#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。這1情況出現的不算很多,但是當我們從內存中讀取數據的時候(需要初始化自定義的AVIOContext),就會履行這1步驟。
(2)在更1般的情況下,如果已指定了AVInputFormat,就直接返回;如果沒有指定AVInputFormat,就調用av_probe_input_format(NULL,…)根據文件路徑判斷文件格式。這里特地把av_probe_input_format()的第1個參數寫成“NULL”,是為了強調這個時候實際上并沒有給函數提供輸入數據,此時僅僅通過文件路徑推測AVInputFormat。

(3)如果發現通過文件路徑判斷不出來文件格式,那末就需要打開文件探測文件格式了,這個時候會首先調用avio_open2()打開文件,然后調用av_probe_input_buffer2()推測AVInputFormat。


下面分析1下av_probe_input_format(),avio_open2(),av_probe_input_buffer2()這幾個函數。

av_probe_input_format2()

av_probe_input_format2()是1個API函數,聲明位于libavformatavformat.h,以下所示。

/** * Guess the file format. * * @param pd data to be probed * @param is_opened Whether the file is already opened; determines whether * demuxers with or without AVFMT_NOFILE are probed. * @param score_max A probe score larger that this is required to accept a * detection, the variable is set to the actual detection * score afterwards. * If the score is <= AVPROBE_SCORE_MAX / 4 it is recommended * to retry with a larger probe buffer. */ AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max);

該函數用于根據輸入數據查找適合的AVInputFormat。參數含義以下所示:

pd:存儲輸入數據信息的AVProbeData結構體。
is_opened:文件是不是打開。

score_max:判決AVInputFormat的門限值。只有某格式判決分數大于該門限值的時候,函數才會返回該封裝格式,否則返回NULL。

該函數中觸及到1個結構體AVProbeData,用于存儲輸入文件的1些信息,它的定義以下所示。

/** * 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;

av_probe_input_format2()函數的定義位于libavformatformat.c,以下所示。
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) { int score_ret; AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret); if (score_ret > *score_max) { *score_max = score_ret; return fmt; } else return NULL; }

從函數中可以看出,av_probe_input_format2()調用了av_probe_input_format3(),并且增加了1個判斷,當av_probe_input_format3()返回的分數大于score_max的時候,才會返回AVInputFormat,否則返回NULL。

下面我們看1下av_probe_input_format3()。


av_probe_input_format3()

av_probe_input_format3()是1個API函數,聲明位于libavformatavformat.h,以下所示。

/** * Guess the file format. * * @param is_opened Whether the file is already opened; determines whether * demuxers with or without AVFMT_NOFILE are probed. * @param score_ret The score of the best detection. */ AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);

從函數聲明中可以看出,av_probe_input_format3()和av_probe_input_format2()的區分是函數的第3個參數不同:av_probe_input_format2()是1個分數的門限值,而av_probe_input_format3()是1個探測后的最匹配的格式的分數值。

av_probe_input_format3()的定義位于libavformatformat.c,以下所示。
#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 score AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret) { AVProbeData lpd = *pd; AVInputFormat *fmt1 = NULL, *fmt; int score, nodat = 0, score_max = 0; const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE]; if (!lpd.buf) lpd.buf = 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) { lpd.buf += id3len; lpd.buf_size -= id3len; } else if (id3len >= PROBE_BUF_MAX) { nodat = 2; } else nodat = 1; } fmt = NULL; while ((fmt1 = av_iformat_next(fmt1))) { if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2")) continue; score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(&lpd); if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { if (nodat == 0) score = FFMAX(score, 1); else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1); else score = FFMAX(score, AVPROBE_SCORE_EXTENSION); } } 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)) score = FFMAX(score, AVPROBE_SCORE_MIME); if (score > score_max) { score_max = score; fmt = fmt1; } else if (score == score_max) fmt = NULL; } if (nodat == 1) score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max); *score_ret = score_max; return fmt; }

av_probe_input_format3()根據輸入數據查找適合的AVInputFormat。輸入的數據位于AVProbeData中。前文已提到過,AVProbeData定義以下。
/** * 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;

其中filename是文件路徑, buf存儲用于推測AVInputFormat的媒體數據,最后還有個mime_type保存媒體的類型。其中buf可以為空,但是其后面不管如何都需要填充AVPROBE_PADDING_SIZE個0(AVPROBE_PADDING_SIZE取值為32,即32個0)。
該函數最主要的部份是1個循環。該循環調用av_iformat_next()遍歷FFmpeg中所有的AVInputFormat,并根據以下規則肯定AVInputFormat和輸入媒體數據的匹配分數(score,反應匹配程度):

(1)如果AVInputFormat中包括read_probe(),就調用read_probe()函數獲得匹配分數(這1方法如果結果匹配的話,1般會取得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。

上述進程中觸及到以下幾個知識點:


AVInputFormat->read_probe()

AVInputFormat中包括read_probe()是用于取得匹配函數的函數指針,不同的封裝格式包括不同的實現函數。例如,FLV封裝格式的AVInputFormat模塊定義(位于libavformatflvdec.c)以下所示。

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_class, };

其中,read_probe()函數對應的是flv_probe()函數。我們可以看1下flv_probe()函數的定義:
static int flv_probe(AVProbeData *p) { return probe(p, 0); }

可見flv_probe()調用了1個probe()函數。probe()函數的定義以下。
static int probe(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是以“大端”方式存儲數據,而操作系統是以“小端”方式存儲數據,這1轉換主要通過AV_RB32()函數實現。AV_RB32()是1個宏定義,其對應的函數是av_bswap32()。

(2)檢查開頭3個字符(Signature)是不是為“FLV”。

(3)第4個字節(Version)小于5。

(4)第6個字節(Headersize的第1個字節?)為0。

(5)offset取值大于8。

參照FLV文件頭的格式可以對上述判斷有1個更清晰的認識:


另外代碼中還包括了有關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()是1個API函數,聲明位于libavutilavstring.h,以下所示。
/** * Match instances of a name in a comma-separated list of names. * @param name Name to look for. * @param names List of names. * @return 1 on match, 0 otherwise. */ int av_match_name(const char *name, const char *names);

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 ((p = strchr(names, ','))) { len = FFMAX(p - names, namelen); if (!av_strncasecmp(name, names, len)) return 1; names = p + 1; } return !av_strcasecmp(name, names); }

上述函數還有1點需要注意,其中使用了1個while()循環,用于搜索“,”。這是由于FFmpeg中有些格式是對應多種格式名稱的,例如MKV格式的解復用器(Demuxer)的定義以下。
AVInputFormat ff_matroska_demuxer = { .name = "matroska,webm", .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"), .extensions = "mkv,mk3d,mka,mks", .priv_data_size = sizeof(MatroskaDemuxContext), .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()函數對這樣的字符串,會把它依照“,”截斷成1個個的名稱,然后逐一進行比較。


av_match_ext()

av_match_ext()是1個API函數,聲明位于libavformatavformat.h(注意位置和av_match_name()不1樣),以下所示。
/** * Return a positive value if the given filename has one of the given * extensions, 0 otherwise. * * @param filename file name to check against the given extensions * @param extensions a comma-separated list of filename extensions */ int av_match_ext(const char *filename, const char *extensions);

av_match_ext()用于比較文件的后綴。該函數首先通過反向查找的方式找到輸入文件名中的“.”,就能夠通過獲得“.”后面的字符串來得到該文件的后綴。然后調用av_match_name(),采取和比較格式名稱的方法比較兩個后綴。
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; }

avio_open2()

有關avio_open2()的分析可以參考文章:FFmpeg源代碼簡單分析:avio_open2()

av_probe_input_buffer2()

av_probe_input_buffer2()是1個API函數,它根據輸入的媒體數據推測該媒體數據的AVInputFormat,聲明位于libavformatavformat.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 filename the filename 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, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size);

av_probe_input_buffer2()參數的含義以下所示:

pb:用于讀取數據的AVIOContext。
fmt:輸出推測出來的AVInputFormat。
filename:輸入媒體的路徑。
logctx:日志(沒有研究過)。
offset:開始推測AVInputFormat的偏移量。

max_probe_size:用于推測格式的媒體數據的最大值。

返回推測后的得到的AVInputFormat的匹配分數。

av_probe_input_buffer2()的定義位于libavformatformat.c,以下所示。

int av_probe_input_buffer2(AVIOContext *pb, 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 ", 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; av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt); pd.mime_type = (const char *)mime_type_opt; } #if 0 if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) { if (!av_strcasecmp(mime_type, "audio/aacp")) { *fmt = av_find_input_format("aac"); } av_freep(&mime_type); } #endif 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! ", (*fmt)->name, score); } else av_log(logctx, AV_LOG_DEBUG, "Format %s probed with size=%d and score=%d ", (*fmt)->name, probe_size, score); #if 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%s ", 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以后,函數就會進入到1個循環中,調用avio_read()讀取數據并且使用av_probe_input_format2()(該函數前文已記錄過)推測文件格式。

肯定有人會奇怪這里為何要使用1個循環,而不是只運行1次?其實這個循環是1個逐步增加輸入媒體數據量的進程。av_probe_input_buffer2()其實不是1次性讀取max_probe_size字節的媒體數據,我個人感覺多是由于這樣做不是很經濟,由于推測大部份媒體魄式根本用不到1MB這么多的媒體數據。因此函數中使用1個probe_size存儲需要讀取的字節數,并且隨著循環次數的增加逐步增加這個值。函數首先從PROBE_BUF_MIN(取值為2048)個字節開始讀取,如果通過這些數據已可以推測出AVInputFormat,那末就能夠直接退出循環了(參考for循環的判斷條件“!*fmt”);如果沒有推測出來,就增加probe_size的量為過去的2倍(參考for循環的表達式“probe_size << 1”),繼續推測AVInputFormat;如果1直讀取到max_probe_size字節的數據仍然沒能肯定AVInputFormat,則會退出循環并且返回毛病信息。

AVInputFormat-> read_header()

在調用完init_input()完成基本的初始化并且推測得到相應的AVInputFormat以后,avformat_open_input()會調用AVInputFormat的read_header()方法讀取媒體文件的文件頭并且完成相干的初始化工作。read_header()是1個位于AVInputFormat結構體中的1個函數指針,對不同的封裝格式,會調用不同的read_header()的實現函數。舉個例子,當輸入視頻的封裝格式為FLV的時候,會調用FLV的AVInputFormat中的read_header()。FLV的AVInputFormat定義位于libavformatflvdec.c文件中,以下所示。

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_class, };

可以看出read_header()指向了flv_read_header()函數。flv_read_header()的實現一樣位于libavformatflvdec.c文件中,以下所示。
static int flv_read_header(AVFormatContext *s) { int offset, flags; avio_skip(s->pb, 4); flags = avio_r8(s->pb); s->ctx_flags |= AVFMTCTX_NOHEADER; if (flags & FLV_HEADER_FLAG_HASVIDEO) if (!create_stream(s, AVMEDIA_TYPE_VIDEO)) return AVERROR(ENOMEM); if (flags & FLV_HEADER_FLAG_HASAUDIO) if (!create_stream(s, AVMEDIA_TYPE_AUDIO)) return AVERROR(ENOMEM); // Flag doesn't indicate whether or not there is script-data present. Must // create that stream if it's encountered. offset = avio_rb32(s->pb); avio_seek(s->pb, offset, SEEK_SET); avio_skip(s->pb, 4); s->start_time = 0; return 0; }

可以看出,函數讀取了FLV的文件頭并且判斷其中是不是包括視頻流和音頻流。如果包括視頻流或音頻流,就會調用create_stream()函數。
create_stream()函數定義也位于libavformatflvdec.c中,以下所示。
static AVStream *create_stream(AVFormatContext *s, int codec_type) { AVStream *st = avformat_new_stream(s, NULL); if (!st) return NULL; st->codec->codec_type = codec_type; if (s->nb_streams>=3 ||( s->nb_streams==2 && s->streams[0]->codec->codec_type != AVMEDIA_TYPE_DATA && s->streams[1]->codec->codec_type != AVMEDIA_TYPE_DATA)) s->ctx_flags &= ~AVFMTCTX_NOHEADER; avpriv_set_pts_info(st, 32, 1, 1000); /* 32 bit pts in ms */ return st; }

從代碼中可以看出,create_stream()調用了API函數avformat_new_stream()創建相應的視頻流和音頻流。
上面這段解析FLV頭的代碼可以參考1下FLV封裝格式的文件頭格式,以下圖所示。
經過上面的步驟AVInputFormat的read_header()完成了視音頻流對應的AVStream的創建。至此,avformat_open_input()中的主要代碼分析終了。



雷霄驊
leixiaohua1020@126.com
http://blog.csdn.net/leixiaohua1020


生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国产色妇 | 波多野结衣 一区 | 好大好爽好舒服 | 久久久精品久久久久三级 | 亚洲最色| 亚洲综合日韩精品欧美综合区 | 久久片| 国产欧美另类久久久精品免费 | 免费一看一级毛片 | 亚洲小说图片 | 亚洲成年人影院 | 武则天一级淫片免费放 | yw视频在线观看 | 曰本一区二区三区 | www.福利| 一二三四视频在线观看免费高清 | 夜夜爽爽| 日本做爰免费大片视频 | 久草在线资源福利站 | 国产福利网 | 麻豆福利在线观看 | 亚洲欧洲精品视频在线观看 | 国产亚洲精品不卡在线 | 午夜视频在线观看免费观看在线观看 | 日本r级在线中文在线播放 日本vs黑人hd | 成人欧美一区二区三区黑人免费 | 黑人极品videos精品欧美裸 | 亚洲资源在线播放 | 性生一级欧美片 | 久久精品这里有 | 国产成人精品一区二区三在线观看 | 亚洲欧美综合一区二区三区四区 | 99久久久国产精品免费牛牛四川 | 国产农村1级毛片 | 国产成人精品aaaa视频一区 | a级成人毛片久久 | 欧美大片aaaa一级毛片 | 亚洲伊人久久网 | 成人国产综合 | 免费簧网站永久在线播放国产 | 亚洲 欧美 手机 在线观看 |