今天看一下ffmpeg裏面avformat_open_input這個函數,我個人認爲這個函數算是在ffmpeg中最重要的函數了,因爲其實在執行了這個函數之後,基本上所有的文件信息都就出來了。比如這一段流裏面有幾股流,每一股流都是什麼信息基本都可以看到了。尤其當你是從內存裏面讀取數據的時候,這個函數就更加的重要了。因爲這個函數裏面有好幾重嵌套,所以我在函數上有一些有加了註釋,我會重點講講重要的函數。PS:當前我使用的ffmpeg的版本是3.4.7,另外我是以從內存讀取數據來重點看的,而不是讀取文件來看。
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, 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\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 fail
s->flags |= AVFMT_FLAG_CUSTOM_IO;
//設置所有的選項到一個對象中。暫時不知道作用,但是這塊可以先跳過
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
//把文件名稱寫入
av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
//非常重要的函數,獲取了很多信息,需要進一步分析
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
//得到了預測到的的格式的分數。內容主要還是再init_input中
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;
}
}
//這個就是使用剛剛在判斷s->iformat = fmt;這個設置的時候可以用
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 a AVIOContext */
if (s->pb)
ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
//讀一下媒體的頭部,在read_header()中主要是做某種格式的初始化工作,比如填充自己的
//私有結構,根據流的數量分配流結構並初始化,把文件指針指向數據區開始處等.
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
//暫時不知道作用,但是這塊可以先跳過
if (!s->metadata) {
s->metadata = s->internal->id3v2_meta;
s->internal->id3v2_meta = NULL;
} else if (s->internal->id3v2_meta) {
int level = AV_LOG_WARNING;
if (s->error_recognition & AV_EF_COMPLIANT)
level = AV_LOG_ERROR;
av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
av_dict_free(&s->internal->id3v2_meta);
if (s->error_recognition & AV_EF_EXPLODE)
return AVERROR_INVALIDDATA;
}
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;
if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
goto fail;
} else
av_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 fail;
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
s->internal->data_offset = avio_tell(s->pb);
//原始數據包緩衝區可用的剩餘大小(字節)。
//#define RAW_PACKET_BUFFER_SIZE 2500000
s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
update_stream_avctx(s);
//在這裏就認爲剛剛已經在init_input這個函數裏面把數據信息已經全部獲得了
for (i = 0; i < s->nb_streams; i++)
s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;
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_closep(&s->pb);
avformat_free_context(s);
*ps = NULL;
return ret;
}
這裏面最重要的函數就是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;
//剛剛在內存在avformat_open_input裏面的時候修改的,如果是從內存讀取碼流,就需要直接探測他的格式。
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);
//如果已指定pb也指定了iformat,但是又指定了不需要文件(也包括URL指定的地址),這就矛盾了,
//此時應是不需要pb的,因爲不需操作文件,提示一下吧,也不算錯.
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;
}
//如果已指定了iformat並且不需要文件,也就不需要pb了,可以直接返回
//如果沒指定iformat,但是可以從文件名中猜出iformat,也成功.
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);
}
這裏面最重要的就是av_probe_input_buffer2,通過這個函數來探測流的類型。再繼續看:
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\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';
}
}
#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!\n", (*fmt)->name, score);
} else
av_log(logctx, AV_LOG_DEBUG,
"Format %s probed with size=%d and score=%d\n",
(*fmt)->name, probe_size, score);
#if 0
FILE *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_format2這個函數,講述瞭如何取探測的。裏面又調用了av_probe_input_format3這個函數。再往下看:
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)
{
int score_ret;
//直接調用av_probe_input_format3
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;
}
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;
AVInputFormat *fmt1 = NULL, *fmt;
int score, score_max = 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;
} else
nodat = ID3_GREATER_PROBE;
}
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 (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;
}
看到最後基本就可以明白了,其實就是通過while之後執行av_iformat_next不斷的找format,然後去read_probe探測分數,最終取到最高的分數的format。然後返回。這裏的read_probe應該就是各種各樣的格式了。比如flv、mp4、ts、ps等等。這個因爲執行不下去了,所以就看到這裏吧。
好了,今天就到這裏吧!
參考資料:
https://www.iteye.com/blog/wodamazi-1293994
https://blog.csdn.net/leixiaohua1020/article/details/12678577