ffmpeg源码分析(九)解协议

news/2024/12/26 18:07:18/

本文将聚焦于FFmpeg协议处理模块,以avformat_open_input函数为核心,详细剖析其在最新FFmpeg源码中的实现。

音视频处理流程简介

在这里插入图片描述

avformat_open_input概述

avformat_open_input是FFmpeg用于打开输入多媒体数据的关键函数。它通过统一的接口处理多种协议,使用户无需关心底层实现。该函数的声明如下,位于libavformat/avformat.h

int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
  • ps:指向AVFormatContext结构体的指针,用于存储打开的多媒体数据上下文。

  • url:要打开的资源URL。

  • fmt:指定输入格式,如果为NULL,FFmpeg会自动探测。

  • options:用于传递其他选项(字典形式)。

这个函数主要完成的功能就是探测输入的格式,然后配置格式的上下文,

比如输入一个mp4文件的地址,它会判断出这是mp4文件,然后AVFormatContext 就知道该怎么处理这个文件了。

avformat_open_input 打开数据流

/*** @brief 打开一个输入媒体文件。* * @param ps 用户提供的 AVFormatContext 的指针,可能是一个指向 NULL 的指针来分配一个新的上下文。* @param filename 要打开的文件名。* @param fmt 如果非 NULL,此参数强制使用特定的输入格式。* @param options 填充 AVFormatContext 和解复用器私有选项的字典。返回时,该参数将被销毁并替换为包含未找到选项的字典。* * @return 成功返回0,失败返回负的 AVERROR。*/
int avformat_open_input(AVFormatContext **ps, const char *filename,const AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;  // 获取提供的格式上下文FFFormatContext *si;       // 内部格式上下文AVDictionary *tmp = NULL;  // 用于选项的临时字典int ret = 0;               // 返回值// 如果没有提供格式上下文,则分配一个新的,也就是说这个格式上下文是可以由外部分配创建的if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM); // 获取内部格式上下文,这相当于给AVFormatContext补充额外的上下文,这在ffmepg中是比较常见的。注意补充字段的内存是在avformat_alloc_context中分配的,所以不要自己直接malloc AVFormatContextsi = ffformatcontext(s);   if (!s->av_class) {av_log(NULL, AV_LOG_ERROR, "输入上下文未通过 avformat_alloc_context() 正确分配\n");return AVERROR(EINVAL); }if (fmt)s->iformat = fmt; // 设置输入格式if (options)av_dict_copy(&tmp, *options, 0); // 如果设置了IO上下文,则设定定制的解复用标志if (s->pb)s->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, "格式不在白名单 '%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; // 分配私有数据if (ffifmt(s->iformat)->priv_data_size > 0) {if (!(s->priv_data = av_mallocz(ffifmt(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;}}// mp3,读取 ID3v2 元数据if (s->pb)ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, NULL);// 【重要】调用iformat格式的 read_header 函数if (ffifmt(s->iformat)->read_header)if ((ret = ffifmt(s->iformat)->read_header(s)) < 0) {if (ffifmt(s->iformat)->flags_internal & FF_INFMT_FLAG_INIT_CLEANUP)goto close;goto fail;}// 处理 ID3 标签//...// 分析 ID3v2 额外数据,如果是特殊格式if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac")) {// 处理 ID3v2 的 APIC、章节和隐私数据// ...}// 队列附加的图片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://【重要】调用iformat格式的 read_close 函数if (ffifmt(s->iformat)->read_close)ffifmt(s->iformat)->read_close(s);
fail: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 初始化媒体输入流

该函数的主要功能是初始化输入媒体文件的处理环境,包括格式探测及文件打开操作。同时,代码设计中考虑了多种情况,包括用户提供的自定义 IO 和文件格式和网络格式,确保了高灵活性和广泛的适用性。


static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
{int ret;AVProbeData pd = { filename, NULL, 0 };int score = AVPROBE_SCORE_RETRY;//处理自定义ioif (s->pb) {s->flags |= AVFMT_FLAG_CUSTOM_IO;// 如果输入格式未指定if (!s->iformat)// 从自定义 IO 缓冲区中探测输入格式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);
}

av_probe_input_buffer2 探测输入格式

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;int eof = 0; // 文件结束标志// 检查并设置最大探测大小,以及偏移量...// 获取 MIME 类型, MIME 类型信息可以被用来指示文件的媒体类型,使ffmpeg快的处理流信息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; // 截断 MIME 类型if (semi) {*semi = '\0';}}// 探测循环for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt && !eof;probe_size = FFMIN(probe_size << 1, FFMAX(max_probe_size, probe_size + 1))) {score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;// 分配和读取探测数据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) {// 处理读取错误if (ret != AVERROR_EOF)goto fail;score = 0; // 到达文件末尾ret = 0;eof = 1;}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); // 填充缓冲区末尾// 猜测文件格式*fmt = av_probe_input_format2(&pd, 1, &score);if (*fmt) {// 检测到格式,记录日志if (score <= AVPROBE_SCORE_RETRY) {av_log(logctx, AV_LOG_WARNING, "格式 %s 仅以低得分 %d 检测到,可能存在错误识别。\n", (*fmt)->name, score);} else {av_log(logctx, AV_LOG_DEBUG, "格式 %s 已通过 size=%d 和 score=%d 探测到\n", (*fmt)->name, probe_size, score);}}}if (!*fmt)ret = AVERROR_INVALIDDATA; // 如果未找到格式,则返回格式无效错误fail:// 回退并重用探测缓冲区ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);if (ret >= 0)ret = ret2;av_freep(&pd.mime_type); // 释放 MIME 类型内存return ret < 0 ? ret : score; // 返回探测得分或错误
}

av_probe_input_format2

av_probe_input_format2实际的探测av_probe_input_format3里

const 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)) {...// 检查并处理 ID3v2 标签}// 迭代所有支持输入格式while ((fmt1 = av_demuxer_iterate(&i))) {if (fmt1->flags & AVFMT_EXPERIMENTAL)continue;// 跳过实验性格式// 特别处理 image2文件if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;score = 0;// 如果格式具有探测函数,则调用该函数进行文件格式检测if (ffifmt(fmt1)->read_probe) {score = ffifmt(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;
}

以HLS格式为例

m3u8列表打开流程

avformat_open_input→init_input-> 格式打开入口

av_probe_input_format2→av_probe_input_format3 :探测出是hls格式

io_open→io_open_default→io_open_default→ffio_open_whitelist→ffurl_open_whitelist→ffurl_connect→http_open->http_open_cnx->http_open_cnx_internal->http_connect :打开hls,开始下载


http://www.ppmy.cn/news/1558323.html

相关文章

python 随笔80%核心笔记(一)

目录 一、海龟 二、pygame 三、函数 四、类与对象 五、列表与元组 六、其他 1、格式化输出 2、最大公约数、最小公倍数 3、print、多变量一起定义赋值、end以及列表的方法 4、序列重复、字符串方法、其他列表方法、input 5、字典的方法、ASCII码转换、返回值、修改私人…

比特币市场震荡:回调背后的机遇与挑战

在刚刚过去的一周&#xff0c;比特币经历了一次显著的回调。从历史高点108,300美元一路下跌&#xff0c;跌幅一度接近15%&#xff0c;最低曾触及92,000美元附近。然而&#xff0c;随着市场情绪逐步趋于平稳&#xff0c;比特币价格已经回升至96,000美元&#xff0c;进入震荡整理…

01驱动钛丝(SMA)在汽车腰托支撑按摩气阀模块的应用

【前言】 形状记忆合金&#xff08;Shape memory alloy, SMA&#xff09;&#xff0c;也叫形态记忆合金、钛镍记忆合金&#xff0c;它是由Ti&#xff08;钛&#xff09;-Ni&#xff08;镍&#xff09;材料组成&#xff0c;经过多道工序制成的丝&#xff0c;我们简称钛丝&#…

Vue3:uv-upload图片上传

效果图&#xff1a; 参考文档&#xff1a; Upload 上传 | 我的资料管理-uv-ui 是全面兼容vue32、nvue、app、h5、小程序等多端的uni-app生态框架 (uvui.cn) 代码&#xff1a; <view class"greenBtn_zw2" click"handleAddGroup">添加班级群</vie…

Linux之ARM(MX6U)裸机篇----1.开发环境搭建

下载开启FTP服务 作用&#xff1a;用于电脑与linux系统之前文件传输 如上&#xff0c;编辑完成后重启 Window下FTP客户端安装使用http://www.filezilla.cn/download网址下载 新建网络连接站点 主机后写虚拟机的ip地址&#xff0c;用ifconfig查出ipv4的地址 笔记本电脑中虚拟…

oracle 加字段和字段注释 sql

在 Oracle 数据库中&#xff0c;你可以使用 ALTER TABLE 语句来添加字段&#xff0c;并使用 COMMENT ON COLUMN 语句来添加字段注释。以下是一个示例&#xff1a; 假设你有一个名为 employees 的表&#xff0c;你想要添加一个名为 email 的字段&#xff0c;并为其添加注释。 …

腾讯云云开发 Copilot具有以下优势

与其他代码生成工具相比&#xff0c;腾讯云云开发 Copilot具有以下优势&#xff1a; 功能特性方面 自然语言处理能力更强&#xff1a;许多代码生成工具仅能实现简单的代码补全或根据特定模板生成代码&#xff0c;而云开发 Copilot可直接通过自然语言生成完整的小程序/web全栈…

springboot实现图片上传、下载功能

写一下后端spring项目经常要做的功能&#xff0c;实现图片上传和下载&#xff0c;这里也把前端代码附上了。可能算是个简单版的&#xff0c;我这里图片上传都存在当前项目的根目录resource下了。 这里包含了&#xff0c;上传文件、下载文件&#xff08;下载文件流、获取base64&…