參考鏈接
- 圖解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等操作了
?
?
?