FFMpeg 源码分析 (3)avformat_open_input()
来源:互联网 发布:js判断有无滚动条 编辑:程序博客网 时间:2024/06/06 02:50
这个函数主要用来打开媒体资源。完成媒体格式的探测和获取相关的媒体信息的工作。
函数完成定义如下:
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; 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; 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\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); s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; update_stream_avctx(s); 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;}
我们一点点来分析,先分析下面这段代码。
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);}
主要做的就是初始化了一个AVFormatContext
结构体,也是最后的返回参数,通过第一个参数 AVFormatContext **ps
返回。那我们接下来具体看这个初始化时怎么做的吧,需要完成哪些工作。
AVFormatContext *avformat_alloc_context(void){ AVFormatContext *ic; ic = av_malloc(sizeof(AVFormatContext)); //申请内存空间 if (!ic) return ic; avformat_get_context_defaults(ic); //给ic 结构体成员赋默认值 ic->internal = av_mallocz(sizeof(*ic->internal)); if (!ic->internal) { avformat_free_context(ic); return NULL; } ic->internal->offset = AV_NOPTS_VALUE; ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; return ic;}
我们看一下avformat_get_context_defaults(ic)
这一句做了些什么
static void avformat_get_context_defaults(AVFormatContext *s){ memset(s, 0, sizeof(AVFormatContext)); s->av_class = &av_format_context_class; //指向一个定义好的全局 format context class s->io_open = io_open_default; // 默认以open 操作函数 s->io_close = io_close_default; //默认的 close 操作函数 av_opt_set_defaults(s); //所有的option 赋默认值}
av_format_context_class.option 会指向一个 avformat_options的结构体数组,里面存储了默认的options 的值。
接下来这一段
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;
就是如果avformat_open_input
这个函数调用的时候带了options参数,则把这些options 参数赋值到定义好的 AVFormatContext
结构体中。
接下来就是调用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);}
一般情况我们播放一个视频文件的话,我们在代码调用的时候不会指定好pb 所以一般就是直接执行 s->iformat = av_probe_input_format2(&pd, 0, &score)
这一句。看这个函数调用的结果。而在 av_probe_input_format2
里面又是直接调用 av_probe_input_format3
这个函数。函数调用语句为:AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
其中is_opened
的值为0。
那我们就一起来看看这个时候av_probe_input_format3(pd, 0, &score_ret)
这个函数调用里面做了些什么吧,先看这一段
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;}
我们回顾一下,我们的pd是怎么定义的:AVProbeData pd = { filename, NULL, 0 };
而AVProbeData
结构体的定义是:
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
就是我们调用 avformat_open_input
传入的第二个参数 filename
,buf
是 NULL
, buf_size
为 0, 那么mime_type
也为 NULL
。所以我们发现这段代码是不会执行的。那么就直接到了
while ((fmt1 = av_iformat_next(fmt1))) { ..............}
这个循环体里面。
我们先来看一下 av_iformat_next(fmt1)
这段代码的结果是什么吧,av_iformat_next
函数的源码如下:
AVInputFormat *av_iformat_next(const AVInputFormat *f){ if (f) return f->next; else return first_iformat;}
一目了然,就是取链表的下一个节点。那么这个链表是干嘛的呢,就是一个类型为AVInputFormat
的链表,链表里面存储的是FFMpeg所有支持的解复用器的引用。具体参见我的上一篇博文 FFMpeg 源码分析(1)av_register_all()。
那么下面的事情就是这个 while
循环到底在做什么呢?好的,我们一步步来看下去。我们先来看一个if ... else ...
语句。
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; }
如果有 read_probe
这个方法,就试着去读取探测一下。如果没有就走到else
部分,就比较 AVInputFormat
的解复用器里面定义的扩展名和我们的传进来的文件扩展名是否匹配,如果匹配,就得到一个文件扩展名匹配这个等级的分数。
好的,我们现在回过头来看一下read_probe
会做些什么吧。这个要到特定的协议格式实现里面去找。我们一 avi
格式为例吧。avi
格式对应的 AVInputFormat
类型结构定义如下:
AVInputFormat ff_avi_demuxer = { .name = "avi", .long_name = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"), .priv_data_size = sizeof(AVIContext), .extensions = "avi", .read_probe = avi_probe, .read_header = avi_read_header, .read_packet = avi_read_packet, .read_close = avi_read_close, .read_seek = avi_read_seek, .priv_class = &demuxer_class,};
read_probe
函数指向 avi_probe
, 所以avi
格式的read_probe
调用就是执行 avi_probe
。具体实现如下:
static int avi_probe(AVProbeData *p){ int i; /* check file header */ for (i = 0; avi_headers[i][0]; i++) if (AV_RL32(p->buf ) == AV_RL32(avi_headers[i] ) && AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4)) return AVPROBE_SCORE_MAX; return 0;}
那其实比较明显,就是对比文件头部信息是否匹配。如果完全匹配就直接返回 AVPROBE_SCORE_MAX
这个分数。不过这个时候 p->buf
里面应该是还没有东西的。所以应该是return 0
。那边接下来就是又去判断文件扩展名。如果匹配,那就根据 nodat
的值进入不同的分支,我们知道,这里我们的 nodat
的值应该是 NO_ID3
所以最后的 score
应该为 1。
接下来就是去判断mime_type
是否匹配。匹配则更新score
的值。
最后如果整个探测过程完成后如果score > 0
则返回我们探测到的 AVInputFormat
结构体。如果是 0 分,则认为探测失败。
那回到av_probe_input_format2
这个函数,我们在调用的时候,传入的 score
的阈值是 AVPROBE_SCORE_RETRY
即 (AVPROBE_SCORE_MAX/4)
AVPROBE_SCORE_MAX
为100 所以阈值就是25。
那我看下 av_probe_input_format2
里面的逻辑
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_format3
探测的分数大于25分,那么我们返回av_probe_input_format3
探测到的这个类型。如果小于等于,认为探测失败,返回 NULL
。这样 (s->iformat = av_probe_input_format2(&pd, 0, &score))
这次探测就失败了。函数继续往下执行。
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0) return ret;
尝试去打开这个媒体资源。那么 s->io_open
又是什么呢? 其实就是 io_open_default
这个函数。在前面说到的 avformat_get_context_defaults
函数中指定。好了,那么接下来就是要看 io_open_default
这个函数的功能是什么。
static int io_open_default(AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options){ #if FF_API_OLD_OPEN_CALLBACKS FF_DISABLE_DEPRECATION_WARNINGS if (s->open_cb) return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options); FF_ENABLE_DEPRECATION_WARNINGS #endif return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);}
代码很简单,刨除条件编译部分,就只剩下 return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
这一句了。
那么ffio_open_whitelist
函数的实现在哪里呢?在 libavformat/aviobuf.c里面。我们来分析这个函数的具体实现。
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist ){ URLContext *h; int err; err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist); if (err < 0) return err; err = ffio_fdopen(s, h); if (err < 0) { ffurl_close(h); return err; } return 0;}
那我们先看 ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist);
这句。先确定一下参数,filename
就是媒体资源的url
, flags
为 AVIO_FLAG_READ | s->avio_flags
我们知道 avio_flag
为空,所以就是 AVIO_FLAG_READ
。int_cb
也为NULL,options 为NULL, whitelist
为NULL, blacklist
也为NULL。
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char* blacklist){ AVDictionary *tmp_opts = NULL; AVDictionaryEntry *e; int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; if (options && (ret = av_opt_set_dict(*puc, options)) < 0) goto fail; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; if (!options) options = &tmp_opts; av_assert0(!whitelist || !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) || !strcmp(whitelist, e->value)); av_assert0(!blacklist || !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) || !strcmp(blacklist, e->value)); if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0) goto fail; if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0) goto fail; if ((ret = av_opt_set_dict(*puc, options)) < 0) goto fail; ret = ffurl_connect(*puc, options); if (!ret) return 0;fail: ffurl_close(*puc); *puc = NULL; return ret;}
这段代码我们简化一下来看就是这样
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char* blacklist){ AVDictionary *tmp_opts = NULL; AVDictionaryEntry *e; int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; ret = ffurl_connect(*puc, options); if (!ret) return 0;fail: ffurl_close(*puc); *puc = NULL; return ret;}
ffurl_alloc(puc, filename, AVIO_FLAG_READ , NULL);
然后是 ffurl_connect(*puc, NULL);
接下来我们一个个分析下去。ffurl_alloc()
的完整代码如下
int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb){ const URLProtocol *p = NULL; p = url_find_protocol(filename); if (p) return url_alloc_for_protocol(puc, p, filename, flags, int_cb); *puc = NULL; if (av_strstart(filename, "https:", NULL)) av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with " "openssl, gnutls " "or securetransport enabled.\n"); return AVERROR_PROTOCOL_NOT_FOUND;}
函数的目的是最终得到一个 URLContext
的结构体。那首先,根据filename
找到一个对应的 URLProtocol
结构体。
那我们就来看看 url_find_protocol
这个函数吧。这个函数比较简单,总结一下吧,就是截取filename
中的协议标识字符串,跟 url_protocols
这个全局数组中的每一个协议的 name
字段做比较。匹配则返回这个URLProtocol
结构体,举个例子。假如是 file
类型的。那么就会返回 ff_file_protocol
这个结构体指针。
完整定义如下:
const URLProtocol ff_file_protocol = { .name = "file", .url_open = file_open, .url_read = file_read, .url_write = file_write, .url_seek = file_seek, .url_close = file_close, .url_get_file_handle = file_get_handle, .url_check = file_check, .url_delete = file_delete, .url_move = file_move, .priv_data_size = sizeof(FileContext), .priv_data_class = &file_class, .url_open_dir = file_open_dir, .url_read_dir = file_read_dir, .url_close_dir = file_close_dir, .default_whitelist = "file,crypto"};
那如果拿到了 URLProtocol
, 就执行 url_alloc_for_protocol(puc, p, filename, flags, int_cb);
根据 URLProtocol
结构体的信息去完成一个URLContext
结构体的初始化工作。这样ffurl_alloc
的工作就完成了。
接下来就是ffurl_connect(*puc, options);
没有传options的话,代码就简化到
err = uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) : uc->prot->url_open(uc, uc->filename, uc->flags);
我们的例子里面就是调用url_open
函数。也就是file_open
就是打开文件,获得一个文件句柄,存放在 FileContext
结构体即 URLContext
结构体的 priv_data
里面。
至此 ffurl_open_whitelist
函数就结束了。
接下来就是 ffio_fdopen
函数了这个函数就通过刚刚得到了一个 URLContext
结构体得到一个 AVIOContext
结构体。
接下来就是通过 av_probe_input_buffer2
来探测具体的媒体资源格式了。这个函数里面重要的部分就是
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))) { ....... 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 */ } ....... *fmt = av_probe_input_format2(&pd, 1, &score); ........}
去读取媒体资源的内容。把读取到的内容放到 AVProbeData
的 buf
字段中。然后再调用 av_probe_input_format2
函数去探测媒体资源的格式。只不过这一次 pb.buf
不再是空,是有内容的。
而关于 av_probe_input_format2
的具体探测过程参考前面的分析就行。是一样的。
在init_imput(s, filename, &tmp)
这条语句执行完成之后,就会调用之前初始化好的AVInputFormat
结构体中的 read_header
方法。如果我们以FLV媒体封装格式为例的话,那么read_header
函数指针就指向的 flv_read_header
函数,函数定义在 libavformat/flvdec.c 中。完整代码如下:
static int flv_read_header(AVFormatContext *s){ FLVContext *flv = s->priv_data; int offset; avio_skip(s->pb, 4); avio_r8(s->pb); // flags s->ctx_flags |= AVFMTCTX_NOHEADER; offset = avio_rb32(s->pb); avio_seek(s->pb, offset, SEEK_SET); avio_skip(s->pb, 4); s->start_time = 0; flv->sum_flv_tag_size = 0; return 0;}
主要读取的头部信息。在现在的版本里面,read_header
并不会把媒体流给创建出来。而是要等到 read_packet
的时候。
- FFMpeg 源码分析 (3)avformat_open_input()
- FFMPEG源码分析:avformat_open_input()(媒体打开函数)
- FFMPEG源码分析:avformat_open_input()(媒体打开函数)
- ffmpeg学习五:avformat_open_input函数源码分析(以mp4文件为例)
- ffmpeg源码分析之三avformat_open_input()上
- ffmpeg源码分析之四-----avformat_open_input()下
- ffmpeg源码分析--3.avformat_alloc_context与avformat_open_input
- ffmpeg源码简析(七)解码-avformat_open_input,avformat_find_stream_info()
- avformat_open_input()源码分析
- ffmpeg源代码分析之avformat_open_input
- FFmpeg源代码简单分析:avformat_open_input()
- FFmpeg源代码简单分析:avformat_open_input()
- FFmpeg源代码简单分析:avformat_open_input
- FFMPEG 源码分析(-)
- FFMPEG 源码分析(-)
- ffmpeg源码跟踪笔记之avformat_open_input
- FFMpeg分析:第一个函数avformat_open_input
- FFMpeg分析:第一个函数avformat_open_input
- 算法题/求数组的逆序对
- Crawler4j快速入门实例
- 浅谈python中的字符编码与转码
- Spring如何加载XSD文件(org.xml.sax.SAXParseException: Failed to read schema document错误的解决方法)
- 极差归一化
- FFMpeg 源码分析 (3)avformat_open_input()
- 【学习笔记】jQuery库扩展
- UISegementControl切换简单应用
- json转list(谷歌的Gson.jar:)
- 对抗神经网络之对抗卷积神经网络[2]
- 设计模式之--单例模式
- C++自定义命名空间编写
- java框架Spring学习--Spring开发流程
- redis三种客户端:自带客户端、图形界面客户端、Java客户端