当前位置: 首页 > news >正文

avformat_open_input() 代码分析

----------------------------------------
avformat_open_input() 代码分析
author:hjjdebug
date: 2022年 10月 05日 星期三
----------------------------------------
我不想把代码抄下来一行行分析,有人已经这么做了.
而是想就关键问题进行分析.
文件被成功打开后,会返回一个 AVFormatContext 结构.
这是一个复杂的结构,构建也需要分步进行.

下面从3个方面来分析

说明: 我调试的时候是用ffmpeg 库打开了一个ts 流, 测试代码见后.

就从ts流格式入手吧.

----------------------------------------
甲. 输入文件格式是如何确定的? avf->iformat
----------------------------------------
    init_input->av_probe_input_buffer2->av_probe_input_format2 来决定的.
    其输入参是是pd, 输出参数是score得分和AVInputFormat *
    score 满分100, 得分越高可信度越高.

1.    先解释一下输入参数 pd 是什么吧,
    它是探测数据结构.
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;

意义很明确,能看得懂,就是检测缓冲区中的一段数据,下面给个pd 实例
(gdb) p pd
  $3 = {
    filename = 0x7fffffffe22f "/opt/test/test1.ts",
    buf = 0x555555562360 "G@\021\020",
    buf_size = 2048,
    mime_type = 0x0
  }

2. av_probe_input_format2 是如何探测的.

    while ((fmt1 = av_demuxer_iterate(&i))) { //这下会枚举几百种格式
            score = fmt1->read_probe(&lpd);
    }
    原来它是枚举每一种输入格式,调用输入格式的探测函数,返回得分,得分高者即为探测的格式.

----------------------------------------
乙: avf->nb_streams 是如何确定的?
----------------------------------------

此时的调用栈为:
  #0  0x00007ffff6654284 in avformat_new_stream (s=0x555555559040, c=0x0) at libavformat/utils.c:4596
  #1  0x00007ffff657d285 in pmt_cb (filter=0x55555555ee80, section=0x55555555de00 "\002\260\035", section_len=32) at libavformat/mpegts.c:2418
  #2  0x00007ffff6576fcc in write_section_data (ts=0x55555556a3c0, tss1=0x55555555ee80, buf=0x5555555624dd "\002\260\035", buf_size=183, is_start=1) at libavformat/mpegts.c:466
  #3  0x00007ffff657e816 in handle_packet (ts=0x55555556a3c0, packet=0x5555555624d8 "GP", pos=564) at libavformat/mpegts.c:2810
  #4  0x00007ffff657f020 in handle_packets (ts=0x55555556a3c0, nb_packets=26595) at libavformat/mpegts.c:2975
  #5  0x00007ffff657f694 in mpegts_read_header (s=0x555555559040) at libavformat/mpegts.c:3093
  #6  0x00007ffff6644e51 in avformat_open_input (ps=0x555555558018 <avf>, filename=0x7fffffffe22f "/opt/test/test1.ts", fmt=0x0, options=0x0) at libavformat/utils.c:609
  大致意思为,
  1. 调用iformat->read_header(s) 此例就调到了mpegts_read_header(s), 因为ts流的read_header 指针就指向这里
      此处曾把AVFormatContext s 放到 ts->stream
      ts->stream     = s;
    此处 ts 是私有类 MpegTSContext, 后面avformat_new_stream 时会用到它

  2. 调用 handle_packets(ts, probesize / ts->raw_packet_size);
    此处 probesize=5000,000 tw->raw_packet_size 是188字节,传进的时包的个数26595,  ts 是个什么变量呢?

    MpegTSContext *ts = s->priv_data;  // ts 是AVFormatContext 的私有数据,在init_input()之后,获得了inputformat.,然后分配私有数据.并初始化这个私有类.

    /* 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;
        }
    }
    此处:
    s 是AVFormatContext,
    ts 是私有类 MpegTSContext
    ts 结构有一个8K 个 mpeg ts 过滤器 MpegTSFilter *pids[8192];     
    这些过滤器的指针用到的会被填充. 例如pat 过滤器, pmt 过滤器,及其它过滤器.
        mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
        mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1);
        ...
    
    该函数的原型如下,ts 是上下文,pid,cb 必须, 后面ts 是作为opaque 传入的,是cb 的参数, 1是是否检查crc。
    MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts,
                                                unsigned int pid,
                                                SectionCallback *section_cb,
                                                void *opaque,
                                                int check_crc)

  struct MpegTSFilter {
      int pid;
      int es_id;
      ...
      enum MpegTSFilterType type;
      union {
          MpegTSPESFilter pes_filter;
          MpegTSSectionFilter section_filter;
      } u;
  }

  struct MpegTSSectionFilter {
      int section_index;    //灵活使用
      int section_h_size;  // 由数据流中读取填充
      ...
      uint8_t *section_buf;    // 数据流中的地址
      SectionCallback *section_cb; //回调函数
      void *opaque; //回调参数
  }

    后面就是一个包一个包的扫描了,读一个包read_packet(s, packet, ts->raw_packet_size, &data); 这里就不分析了,

3. 调用handle_packet
    处理一个包. ret = handle_packet(ts, data, avio_tell(s->pb)); //从调用可见data 正指向一个包的开头,pos 是文件的数据偏移
  再调用 write_section_data, 其参数 tss1 是MpegTSFilter指针,这个指针来于包的pid, tss = ts->pids[pid];
    此时的包ID肯定是pmt的包ID(pmt的包id 是解析pat包得到的), 因而会调用pmt_cb
     tss->section_cb(tss1, cur_section_buf, tss->section_h_size);
     此处的tss 是 MpegTSSectionFilter
    MpegTSSectionFilter *tss = &tss1->u.section_filter;
    uint 8 *cur_section_buf = tss->section_buf;
    前面注册的回调就是pmt_cb, 因而 tss->section_cb(函数指针) == pmt_cb

4. 调用 pmt_cb
    它传入的第一个参数是tss1,     MpegTSFilter *, 第二个参数是section 数据指针,第三个是大小
    pmt 分析会读取pmt包头, 然后进行读取pmt包体找到所有stream. 当找到新的stream 后,要创建一个stream, 先分析一下此时的传入参数

      pes = add_pes_stream(ts, pid, pcr_pid);  //添加一个pes 流 ,    其中, pes->stream  = ts->stream;
        if (pes && !pes->st) {
            st = avformat_new_stream(pes->stream, NULL);  // 传进去的参数就是 AVFormatContext * ,见前述
            st->id = pes->pid;
        }
5. 调用avformat_new_stream , 然后avf->nb_streams 也会➕加1, 即扫描pmt表,发现一个流就会创建一个流,流个数加1

----------------------------------------
丙  AVFormatContext 中av_class 是何时赋值的 ?
----------------------------------------
    这个简单,在内存分配时就已经赋值了. 而且固定的付给av_format_context_class, 对于该类的av_option也付给了初始值
    avformat_alloc_context() -> avformat_get_context_defaults(AVFormatContext *s)
    s->av_class = &av_format_context_class;
    av_opt_set_defaults(s);

如此程序也就分析完毕了. 是的,这是对庞杂代码的简要概括. 保留了骨干,抛弃一些简易的东西.

测试代码:

#include "libavformat/avformat.h"
#include "libavutil/timestamp.h"

AVFormatContext *avf = NULL;
int main(int argc, char **argv)
{
	if(argc==1)
	{
		printf("usage:%s <file>\n",argv[0]);
		return 1;
	}
	const char *filename=argv[1];
	avf = avformat_alloc_context();
	int ret;
	
	if ((ret = avformat_open_input(&avf, filename, NULL, NULL)) < 0) {
		fprintf(stderr, "%s: %s\n", filename, av_err2str(ret));
		return 1;
	}
	if ((ret = avformat_find_stream_info(avf, NULL)) < 0) {
		fprintf(stderr, "%s: could not find codec parameters: %s\n", filename,
				av_err2str(ret));
		return 1;
	}
	//read packet
	AVPacket packet;
	ret = av_read_frame(avf, &packet);
	if (ret < 0) {
		printf("read: %d (%s)\n", ret, av_err2str(ret));
	} else {
		AVRational *tb = &avf->streams[packet.stream_index]->time_base;
		printf("read: size=%d stream=%d dts=%s(%s) pts=%s(%s)\n",
				packet.size, packet.stream_index,
				av_ts2str(packet.dts), av_ts2timestr(packet.dts, tb),
				av_ts2str(packet.pts), av_ts2timestr(packet.pts, tb));
		av_packet_unref(&packet);
	}

	avformat_close_input(&avf);

	return 0;
}

运行结果:

./test_open /opt/test/test1.ts
read: size=25709 stream=0 dts=126000(1.4) pts=133200(1.48)

参考1: ffmpeg4.4 源代码

相关文章:

  • Spring Bean的生命周期、Java配置BeanFactoryPostProcessor失效与解决
  • 大模型系统和应用——高效训练模型压缩
  • “华为杯”第十八届中国研究生数学建模竞赛一等奖经验分享
  • C#的StreamReader类使用说明
  • 基于图搜索的规划算法之 A* 家族(九):Hybrid A* 算法
  • 2022年Webpack 5初学者完整指南
  • 【MATLAB教程案例22】基于MATLAB图像去噪算法仿真——中值滤波、高斯滤波以及频域滤波等
  • 浙江大学软件学院2022保研经历分享
  • 表的自然连接(数据结构链表链接)
  • 在Vue脚手架实现登录页面及跳转
  • 新旧电脑间文件互传(通过网络)
  • Python3,仅仅2段代码,就实现项目代码自动上传及部署,再也不需要Jenkins了。
  • 贪心c++(结合LeetCode例题)
  • MATLAB-多项式曲线回归拟合
  • 第五章-Python数据处理工具--Pandas
  • Android 架构优化~MVP 架构改造
  • Angular 4.x 动态创建组件
  • gops —— Go 程序诊断分析工具
  • javascript数组去重/查找/插入/删除
  • Java比较器对数组,集合排序
  • PHP 的 SAPI 是个什么东西
  • RxJS: 简单入门
  • SQLServer之创建显式事务
  • tensorflow学习笔记3——MNIST应用篇
  • unity如何实现一个固定宽度的orthagraphic相机
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 聊聊redis的数据结构的应用
  • 如何优雅地使用 Sublime Text
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 我这样减少了26.5M Java内存!
  • nb
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # 安徽锐锋科技IDMS系统简介
  • #define、const、typedef的差别
  • (11)MATLAB PCA+SVM 人脸识别
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (8)STL算法之替换
  • (C++17) optional的使用
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • .NET Core中的去虚
  • .Net Winform开发笔记(一)
  • .NET 表达式计算:Expression Evaluator
  • .NET学习全景图
  • .so文件(linux系统)
  • /etc/shadow字段详解
  • [ C++ ] 继承