ffmpeg源码分析(九)解协议

server/2024/12/27 6:01:56/

本文将聚焦于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/server/153544.html

相关文章

各种网站(学习资源及其他)

欢迎围观笔者的个人博客~ 也欢迎通过RSS网址https://kangaroogao.github.io/atom.xml进行订阅~ 大学指南 上海交通大学生存手册中国科学技术大学人工智能与数据科学学院本科进阶指南USTC不完全入学指南大学生活质量指北科研论 信息搜集 AI信息搜集USTC飞跃网站计算机保研 技…

产品初探Devops!以及AI如何赋能Devops?

DevOps源自Development&#xff08;开发&#xff09;和Operations&#xff08;运维&#xff09;的组合&#xff0c;是一种新的软件工程理念&#xff0c;旨在打破传统软件工程方法中“开发->测试->运维”的割裂模式&#xff0c;强调端到端高效一致的交付流程&#xff0c;实…

使用ID3算法根据信息增益构建决策树

题目1 题目2 根据ID3&#xff08;信息增益&#xff09;方法利用以下数据集构建决策树。 electionseasonoil pricestock pricenowinterriserisenosummerfallfallnosummerriseriseyeswinterrisefallnowinterfallfallyessummerriseriseyessummerfallfallyeswinterfallfall 设数…

VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比

VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比 目录 VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比; 2.单变量时间序列预测 就是先vmd把变…

js控制文字溢出显示省略号

.text{display: -webkit-box;overflow: hidden;white-space: normal;text-overflow: ellipsis;word-wrap: break-word;-webkit-line-clamp: 2;-webkit-box-orient: vertical; }本人有个需求就是在一个盒子内有一段文本&#xff0c;然后控制文本显示两行&#xff0c;第二行要显示…

使用FFmpeg进行拉流和推流操作

FFmpeg是一款强大的多媒体处理工具&#xff0c;可以用于视频的录制、转换、推流和拉流等操作。下面将详细介绍如何使用FFmpeg进行拉流和推流操作。 1. FFmpeg推流操作 推流是将本地的音视频流推送到流媒体服务器上&#xff0c;例如主播将本地电脑上的画面推流到直播平台的流媒…

微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局

近日&#xff0c;微软 CEO 萨提亚・纳德拉与著名投资人比尔・格里和布拉德・格斯特纳进行了一场深度对话&#xff0c;回顾了过去十年微软的转型历程&#xff0c;并展望了 AI 时代的战略布局。在这次访谈中&#xff0c;纳德拉分享了他在微软的早期经历&#xff0c;包括他加入微软…

feign验签不通过,但是postman没问题

测试一个外部api的时候发现&#xff0c;同样的签名方法和payload&#xff0c;放在postman请求完全没问题&#xff0c;curl也能通过&#xff0c;但是到了feign就签名错误。 百思不得其解&#xff0c;后来发现了问题。 计算签名的时候&#xff0c;payload使用的格式是 {"…