avformat_open_input()用于打开输入媒体流与读取头部信息,包括本地文件、网络流、自定义缓冲区。关键流程:打开avio、探测输入流的封装格式。对应的释放方法为avformat_close_input()。

1、打开输入媒体流

avformat_open_input方法位于libavformat/utils.c,流程包括分配AVFormatContext、设置options、初始化输入流、拷贝白名单与黑名单协议、读取ID3V2参数。具体方法如下:

int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;int i, ret = 0;AVDictionary *tmp = NULL;ID3v2ExtraMeta *id3v2_extra_meta = NULL;// 分配AVFormatContextif (!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\n");return AVERROR(EINVAL);}if (fmt)s->iformat = fmt;// 拷贝optionsif (options)av_dict_copy(&tmp, *options, 0);// 如果已经设置AVIOContext,那么flag设为自定义IOif (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;// 设置optionsif ((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);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;}}if (s->pb)ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);#if FF_API_DEMUXER_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#elseif (s->iformat->read_header)
#endifif ((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) {av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");av_dict_free(&s->internal->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 FF_API_DEMUXER_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#elseif (s->pb && !s->internal->data_offset)
#endifs->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;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;
}

2、初始化输入媒体流

核心方法是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.\n");return 0;}// 探测输入格式if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;// 打开avioif ((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);
}

3、探测输入格式

av_probe_input_format2()位于libavformat/format.c,内部调用av_probe_input_format3(),而av_probe_input_format()会内部调用av_probe_input_format2()。具体调用关系如下:

ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{int score_ret;ff_const59 AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}ff_const59 AVInputFormat *av_probe_input_format(ff_const59 AVProbeData *pd, int is_opened)
{int score = 0;return av_probe_input_format2(pd, is_opened, &score);
}

3.1 探测过程

我们主要看下av_probe_input_format3方法,探测过程分为三步:read_probe、av_match_ext、av_match_name。每一步匹配结果有个score得分,取最高分数的作为format。具体探测匹配过程如下:

ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened,int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;ff_const59 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;// 判断是否匹配到ID3V2,存在于mp3、aac、wav、ttaif (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 (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;score = 0;if (fmt1->read_probe) {// 匹配格式的特定标识符,比如flv格式头部为"FLV"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;}// 匹配mimetypeif (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) { // 取最高得分作为formatscore_max = score;fmt       = (AVInputFormat*)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;
}

3.2 匹配分数

每步匹配有不同的分数,最高分数为100,retry为25,extension为50,mimetype为75.具体的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;#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)#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

3.3 探测mp4格式

mp4/mov/3gp格式都在libavformat/mov.c中,read_probe指向mov_probe方法。首先探测ftyp、moov、mdat等关键box的tag,然后是hdlr、mhlr、MPEG的tag,具体的探测方法如下:

static int mov_probe(const AVProbeData *p)
{int64_t offset = 0;uint32_t tag;int score = 0;int moov_offset = -1;for (;;) {tag = AV_RL32(p->buf + offset + 4);switch(tag) {case MKTAG('m','o','o','v'):moov_offset = offset + 4;case MKTAG('m','d','a','t'):case MKTAG('p','n','o','t'):case MKTAG('u','d','t','a'):case MKTAG('f','t','y','p'):if (tag == MKTAG('f','t','y','p') &&(   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')|| AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' '))) {score = FFMAX(score, 5);} else {score = AVPROBE_SCORE_MAX;}break;case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */case MKTAG('w','i','d','e'):case MKTAG('f','r','e','e'):case MKTAG('j','u','n','k'):case MKTAG('p','i','c','t'):score  = FFMAX(score, AVPROBE_SCORE_MAX - 5);break;case MKTAG(0x82,0x82,0x7f,0x7d):case MKTAG('s','k','i','p'):case MKTAG('u','u','i','d'):case MKTAG('p','r','f','l'):score  = FFMAX(score, AVPROBE_SCORE_EXTENSION);break;}if (size > INT64_MAX - offset)break;offset += size;}if (score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {offset = moov_offset;while (offset < (p->buf_size - 16)) {if (AV_RL32(p->buf + offset     ) == MKTAG('h','d','l','r') &&AV_RL32(p->buf + offset +  8) == MKTAG('m','h','l','r') &&AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')) {return 5;} else {/* Keep looking */offset += 2;}}}return score;
}

4、打开avio

avio_open2方法会打开ffurl_open_whitelist()和fifo_fdopen()。具体调用链如下图所示:

4.1 open_whiltelist

其中位于avio.c的ffurl_open_whitelist()进行两步操作:ffurl_alloc和ffurl_connect,具体代码如下:

int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char* blacklist,URLContext *parent)
{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_closep(puc);return ret;
}

ffurl_alloc主要实现2个功能:查找协议和分配协议,查找过程是遍历协议数组根据scheme进行匹配。代码如下:

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;return AVERROR_PROTOCOL_NOT_FOUND;
}

ffurl_connect主要是判断用url_oepn2还是url_open来打开:

int ffurl_connect(URLContext *uc, AVDictionary **options)
{......int err =uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) :uc->prot->url_open(uc, uc->filename, uc->flags);......return 0;
}

4.2 打开fifo

调用aviobuf.c的fifo_fdopen方法打开fifo,主要是分配buffer缓冲区、分配AVIOContext:

int ffio_fdopen(AVIOContext **s, URLContext *h)
{// malloc bufferbuffer = av_malloc(buffer_size);if (!buffer)return AVERROR(ENOMEM);// malloc context*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,(int (*)(void *, uint8_t *, int))  ffurl_read,(int (*)(void *, uint8_t *, int))  ffurl_write,(int64_t (*)(void *, int64_t, int))ffurl_seek);fail:av_freep(&buffer);return AVERROR(ENOMEM);
}

FFmpeg源码分析:avformat_open_input()打开媒体流相关推荐

  1. FFmpeg源码分析:写媒体文件头avformat_write_header()

    FFmpeg在libavformat模块提供mux封装视频的API,包括avformat_write_header()写文件头.av_write_frame()写音视频帧.av_write_trail ...

  2. FFMPEG源码分析(二)

    ffmpeg源码分析之数据流 本文主要介绍ffmpeg的数据流,在ffmpeg中主要分有三个主要用途用于媒体流的解码播放,媒体流的转换(解码之后再编码)和媒体流录制. 媒体流的解码播放 在ffmpeg ...

  3. FFMPEG 源码分析

    FFMPEG基本概念: ffmpeg是一个开源的编解码框架,它提供了一个音视频录制,解码和编码库.FFMPEG是在linux下开发的,但也有windows下的编译版本. ffmpeg项目由以下几部分组 ...

  4. ffmpeg源码分析-parse_optgroup

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  5. FFMPEG源码分析(一)

    FFMPEG源码分析(一) ffmpeg之前公司项目中就使用过,但是多停留于应用层面,实现某个功能时,需要哪些结构体以及调用哪些函数.最近想系统的学习一下ffmpeg,于是开始看雷霄骅https:// ...

  6. FFmpeg源码分析-直播延迟-内存泄漏

    FFmpeg源码分析-直播延迟-内存泄漏|FFmpeg源码分析方法|ffmpeg播放为什么容易产生延迟|解复用.解码内存泄漏分析 专注后台服务器开发,包括C/C++,Linux,Nginx,ZeroM ...

  7. ffmpeg源码分析-ffmpeg_parse_options

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  8. ffmpeg源码分析-transcode_step

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  9. ffmpeg源码分析与应用示例(一)——H.264解码与QP提取

    本文包含以下内容 1.H.264解码流程详述与对应ffmpeg源码解析 2.针对一个应用实例介绍通过修改ffmpeg源码解决问题的方案 具有较强的综合性. 先介绍一下在第二部分中将要解决的实际问题:自 ...

  10. FFplay源码分析-avformat_open_input

    本系列 以 ffmpeg4.4 源码为准,主要讲解 ffplay 的 RTMP 协议解析,播放.本文使用的命令如下: ffplay -i rtmp://192.168.0.122/live/lives ...

最新文章

  1. MySQL · 捉虫动态 · 并行复制外键约束问题二
  2. 搭建TXManager分布式事务协调者
  3. Page_Load的问题
  4. [ECMAScript] module、export、import分别有什么作用?
  5. Socket常用语法与socketserver实例
  6. python 验证数据类型函数
  7. 小程序统一服务消息_微信团队发布小程序模板消息能力调整通知:小程序订阅消息接口正式上线...
  8. Python 爬虫 ——html 页面的认识
  9. struts2路径配置_Struts 2结果路径配置示例
  10. 从信号转换角度研究血压(波形)预测的相关论文
  11. 常用数学符号大学(包含罗马字符)
  12. 增强型绿植植被指数_MODIS增强型植被指数EVI与NDVI初步比较-中国科学院.PDF
  13. Windows驱动之电源管理
  14. 【用法总结】C++中常用的大小写转换(4种常用方法)
  15. python第三方库 invalid requirement_Python - 生成 requirement.txt 文件
  16. 计算机卡住了怎样恢复,电脑死机按什么键恢复
  17. [转]跨站漏洞解析及预防
  18. Nginx安装配置报错详解
  19. 【机器学习】浅谈 归纳偏置 (Inductive Bias)
  20. 企业微信客户端开启调试模式

热门文章

  1. Expires / Cache-Control / Last-Modified / If-Modified-Since / ETag / If-None-Match 区别使用
  2. English语法_并列连词-or
  3. 电子科技大学计算机专业分班,电子科技大学实验中学2020年分班题难不难?
  4. Vim 清除上次搜索突出显示
  5. 从NCE loss到InfoNCE loss
  6. ubuntu 安装 opencv 的 c++版本
  7. 想学SEM培训就来找网优谷
  8. GNN博客-A Gentle Introduction to Graph Neural Networks
  9. 揭秘地球十大地貌奇观(组图)。
  10. 初识JAVA07:自定义类、构造方法、this关键字、static关键字、block关键字、Debug调试工具