ffmpeg在使用avformat_open_input接口打开流时,就已经对流的格式做了探测,如果是输入的RTMP地址,它又是如何探测流的呢。
通过之前文档(FFMPEG-RTMP之抽取FLV),我们知道,ffmpeg在读取rtmp模块数据的时候,实际得到的是组装好的完整flv数据,首次读取flv头部数据的时间就发生在init_input函数中,它回调了一个io_open函数,此函数指针映射的正是rtmp模块的流打开函数,回调完成后,我们得到了flv的头部数据。
接下来,ffmpeg通过调用av_probe_input_buffer2匹配媒体数据格式。调用栈如下:
从调用栈可以看出,av_probe_input_buffer2 最终是会回调flv_probe来探测FLV数据的,当探测成功后,flv模块作为AVFormatContext的iformat成员变量保存了下来。至此,flv的探测工作圆满结束。
#0 probe (p=0x7fffffffdbf0, p=0x7fffffffdbf0, live=0) at libavformat/flvdec.c:77
#1 flv_probe (p=0x7fffffffdbf0) at libavformat/flvdec.c:93
#2 0x00005555557df619 in av_probe_input_format3 (pd=pd@entry=0x7fffffffdcd0, is_opened=is_opened@entry=1, score_ret=score_ret@entry=0x7fffffffdc64) at libavformat/format.c:209
#3 0x00005555557df872 in av_probe_input_format2 (pd=pd@entry=0x7fffffffdcd0, is_opened=is_opened@entry=1, score_max=score_max@entry=0x7fffffffdcbc) at libavformat/format.c:252
#4 0x00005555557dfa5b in av_probe_input_buffer2 (pb=0x555556e300a0, fmt=0x555556e2c608, filename=filename@entry=0x7fffffffe848 "rtmp://58.200.131.2:1935/livetv/hunantv", logctx=logctx@entry=0x555556e2c600, offset=offset@entry=0, max_probe_size=1048576)
at libavformat/format.c:332
#5 0x00005555558d1135 in init_input (options=0x7fffffffdd60, filename=0x7fffffffe848 "rtmp://58.200.131.2:1935/livetv/hunantv", s=0x555556e2c600) at libavformat/utils.c:420
#6 avformat_open_input (ps=ps@entry=0x7fffffffde30, filename=filename@entry=0x7fffffffe848 "rtmp://58.200.131.2:1935/livetv/hunantv", fmt=fmt@entry=0x0, options=0x555556e2c3e8) at libavformat/utils.c:536
#7 0x000055555568335a in open_input_file (o=o@entry=0x7fffffffdff0, filename=<optimized out>) at ffmpeg_opt.c:997
#8 0x0000555555684c52 in open_files (l=0x555556e2c058, l=0x555556e2c058, open_file=0x555555681940 <open_input_file>, inout=0x5555560de7e6 "input") at ffmpeg_opt.c:3135
#9 ffmpeg_parse_options (argc=<optimized out>, argv=<optimized out>) at ffmpeg_opt.c:3175
#10 0x0000555555677177 in main (argc=4, argv=0x7fffffffe5c8) at ffmpeg.c:4569
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;
s->probe_score = ret;
....
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
}
/* 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.\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);
}