av_read_frame 代码研究

------------------------------------------------------------
author: hjjdebug
date: 2024年 07月 05日 星期五 11:02:51 CST
av_read_frame 代码研究
------------------------------------------------------------

有人只标注一层,标注一层太肤浅了.不能了解底层之精妙.
有人给出调用框图, 框图又太笼统了,还是了解不了细节.
如果来一个完全的标注,那也有两个毛病.
1. 代码太多,篇幅太大.
2. 重点不突出, 一叶障目,窥不见森林.
那怎么做呢? 
捡一个最深的调用为纲,搞清其工作原理,再辅以上下的各个问题解释清楚。
做到纲举目张.

想读一个frame, 以ts文件为例, 它必需要读文件, 并且它要在
合适的位置停止读,因为数据已经构成了一个frame.

mpegts.c 就是ts 文件执行读写和分析的基础文件
我在mpegts.c 中设下了一个断点.
0 in mpegts_push_data of libavformat/mpegts.c:1377
1 in handle_packet of libavformat/mpegts.c:2846
2 in handle_packets of libavformat/mpegts.c:2975
3 in mpegts_read_packet of libavformat/mpegts.c:3219
4 in ff_read_packet of libavformat/utils.c:843
5 in read_frame_internal of libavformat/utils.c:1546
6 in av_read_frame of libavformat/utils.c:1750
7 in main of main.c:304

上层框架简单,就是接口,调用别人干活,主要看下层,是具体的操作:
handel_packes:
    它循环调用读包, 每次读188字节,处理包 handle_packet, 满足条件才会退出.
handle_packet:
    根据包的pid 可以拿到它的过滤器
    MpegTsFilter   tss = ts->pids[pid];
    过滤器的类型也是在分析section 数据时建立的,例如pes 的过滤器是分析pmt表时建立的.
    pmt表中记录了每个节目包含哪些流(最常见的是一个音频,一个视频), 这些流的ID 是多少
    如果过滤器的类型是PES, 就是说该小包数据是pes,就会回调pes_cb, 这就是mpegts_push_data
    if (tss->type == MPEGTS_PES) 
    {
        if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
                                            pos - ts->raw_packet_size)) < 0)
        return ret;
    }
    就是说从id拿到对应的id对象(tss),然后调用对应的回调函数tss->u.pes_filter.pes_cb = mpegts_push_data
    可见pes_cb并不是tss的直接成员,而是其下成员u.pes_filter的一个成员. 又多了一个中间管理者pes_filter
    可以认为pes_filter和section_filter 是并列的,是一个联合.
        union {
        MpegTSPESFilter pes_filter;
        MpegTSSectionFilter section_filter;
    } u;

    而tss是它们的上一级.
    struct MpegTSFilter {
    int pid;
    int es_id;
    int last_cc; /* last cc code (-1 if first packet) */
    int64_t last_pcr;
    int discard;
    enum MpegTSFilterType type;
    union {
        MpegTSPESFilter pes_filter;
        MpegTSSectionFilter section_filter;
    } u;
};

mpegts_push_data:
    它大部分时间是把负载直接copy到pes 数据区,但当数据copy够了时,就设置退出条件.
    所谓copy够就是拷贝的pes数据pes->data_index 加头部大小pes->header_size等于
    pes包总大小加上pes包起始大小
    switch(pes->state)
    {
    case MPEGTS_PAYLOAD
       memcpy(pes->buffer->data + pes->data_index, p, buf_size);
        pes->data_index += buf_size;
        if (!ts->stop_parse && pes->total_size < MAX_PES_PAYLOAD &&
            pes->pes_header_size + pes->data_index == pes->total_size + PES_START_SIZE) 
        { //数据copy够了,退出
            ts->stop_parse = 1;
            ret = new_pes_packet(pes, ts->pkt); //后面如果还有数,那就属于新包数据了
            if (ret < 0)
                return ret;
        }
    }
//下面是 gdb 打印的某一次结果
(gdb) p pes->pes_header_size
$20 = 14
(gdb) p pes->data_index //此处是负载的位置索引
$21 = 2304
(gdb) p pes->total_size
$22 = 2312

14+2304 = 2312 + 6

下面我们来分析一个这几个概念.
1. #define PES_START_SIZE  6
pes包时由固定的3个字节00 00 01 及后面3个字节stream_id(1)+包长度(packet_length)
2. pes->total_size
这就是上面pes 头部中的packet_length. 从码流中得到的大小
它在代码中什么位置赋值的?

    pes->total_size = AV_RB16(pes->header + 4);
    if (!pes->total_size)
        pes->total_size = MAX_PES_PAYLOAD; //200*1024
//当packet_length==0 时, 我们给最大的尺寸200K , 此时真正的大小要等到再遇到一个pes包头来确定大小.

    /* allocate pes buffer */
    pes->buffer = buffer_pool_get(ts, pes->total_size);
    if (!pes->buffer)
        return AVERROR(ENOMEM);

3. pes->pes_header_size
      if (pes->data_index == PES_HEADER_SIZE)  //#define PES_HEADER_SIZE 9
      {
        pes->pes_header_size = pes->header[8] + 9; //9字节是固定头,前面6字节是start头,后面3字节是可选头部,
        pes->state           = MPEGTS_PESHEADER_FILL;
      }
      
  其中:
  pes->header[6], 加扰说明,版权说明等
  pes->header[7], 7个标志位,说明是否有option跟随,例如pts,dts等等
  pes->header[8], 可选头部的长度,1byte 不会超过255

  头部大小范围,最大 MAX_PES_HEADER_SIZE (9+255)

4. pes->data_index
 当然是pes数据的位置索引了. 不过它的身份是变的, 一会是数据的索引,一会是负载的索引, 要看它的时机.
 变量吗,就是变着来, 不过这样容易混淆,用是简单了,读却费劲了. 最好是分两个或多个变量.角色应该唯一.
 就像变量i, 一会是这个的索引,一会是那个的索引,只要分的清,也可以不换名,因为用着简单.

 小结:
 读一个frame, 就是从原始数据中不停的读, 读到一个合适的位置停止读,把有效的数据作为一个frame反馈给上层.
 当然,如果你不是从数据文件中读,而是内存中已经有了,例如以链表形式或者数组形式,那copy给你就可以了.
 这就是av_read_frame的核心工作.
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/775753.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

lua入门(2) - 数据类型

前言 本文参考自: Lua 数据类型 | 菜鸟教程 (runoob.com) 希望详细了解的小伙伴还请查看上方链接: 八个基本类型 type - 函数查看数据类型: 测试程序: print(type("Hello world")) --> string print(type(10.4*3)) --> number print(t…

pdf可以删除其中一页吗?6个软件教你快速进行pdf编辑

pdf可以删除其中一页吗&#xff1f;6个软件教你快速进行pdf编辑 编辑PDF文件并删除特定页面是处理文档时常见的需求&#xff0c;特别是在需要定制或精简文件内容时。以下是几款广受欢迎的PDF编辑软件&#xff0c;它们提供了强大的页面删除功能&#xff0c;帮助用户轻松管理和修…

Vue3学习笔记(n.0)

vue指令之v-for 首先创建自定义组件&#xff08;practice5.vue&#xff09;&#xff1a; <!--* Author: RealRoad1083425287qq.com* Date: 2024-07-05 21:28:45* LastEditors: Mei* LastEditTime: 2024-07-05 21:35:40* FilePath: \Fighting\new_project_0705\my-vue-app\…

安卓开发定时截屏

此处有两种方式&#xff1a;&#xff08;都是定时截屏&#xff0c;不需要定时功能可以剔除service&#xff09; 1.app内截屏 https://download.csdn.net/download/hdhhd/89517797 2.截取当前任意手机显示屏幕 https://download.csdn.net/download/hdhhd/89517800 第一种…

hitcontraining_uaf

BUUCTF[PWN][堆] 题目&#xff1a;BUUCTF在线评测 (buuoj.cn) 程序del是没有将申请的指针清零&#xff0c;导致可以再次调用输出print。 查看add_note函数&#xff1a;根据当前 notelist 是否为空&#xff0c;来申请了一个8字节的空间将地址(指针)放在notelist[i]中&#xff…

海尔智家:科技优秀是一种习惯

海尔智家&#xff1a;科技优秀是一种习惯 2024-06-28 15:19代锡海 6月24日&#xff0c;2023年度国家科学技术奖正式揭晓。海尔智家“温湿氧磁多维精准控制家用保鲜电器技术创新与产业化”项目荣获国家科学技术进步奖&#xff0c;成为家电行业唯一牵头获奖企业。 很多人说&…

RK3568平台(USB篇)TYPE-C接口与PD协议

一.TYPE-C接口简介 type-c 插座&#xff1a; type-c 插头&#xff1a; type-c 线缆&#xff1a; type-c 接口定义之插座&#xff1a; type-c 硬件原理图&#xff1a; VBUS&#xff1a;供电引脚&#xff0c;用于传输电源电压&#xff0c;一般为5V或12V。 GND&#xff1a;地引…

使用ChatGPT写论文,只需四步突破论文写作瓶颈!

欢迎关注&#xff0c;为大家带来最酷最有效的智能AI学术科研写作攻略。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥&#xff08;yida985&#xff09;交流 地表最强大的高级学术AI专业版已经开放&#xff0c;拥有全球领先的GPT学术科研应用&#xff0c;有兴趣的朋友可…

一键式创建GTest测试平台

适用于C GTest测试平台搭建。直接上python脚本。 #!/usr/bin/env python3 # -*- coding: utf-8 -*-import argparse import os import platform import subprocess from xml.etree import ElementTree as ETdefault_root_path "d:\\test\\UTtest"class DeveloperTe…

文件扫描pdf怎么弄?5个简易高效的文件扫描方法

在繁忙的工作中&#xff0c;我们常常需要将纸质文件快速转换为电子文档&#xff0c;以便于编辑、存储或分享。 无论是合同、报告还是笔记&#xff0c;将这些纸质文件转换为Word格式&#xff0c;不仅能提高工作效率&#xff0c;还能确保信息的安全备份。然而&#xff0c;面对市…

Web3 ETF的主要功能

Web3 ETF的主要功能可以概括为以下几点&#xff0c;Web3 ETF仍是一项新兴投资产品&#xff0c;其长期表现仍存在不确定性。投资者在投资Web3 ETF之前应仔细研究相关风险&#xff0c;并做好充分的风险评估。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xf…

如何爱上阅读及阅读的意义有哪些?

第一个是“情绪决定”&#xff0c;比如看到人家健身&#xff0c;摄影&#xff0c;画画时&#xff0c;自己的肾上腺素开始飙升&#xff0c;马上表示自己也想做&#xff1b; 第二个是“理智决定”&#xff0c;理智决定同样表示想要一样东西&#xff0c;但表示人必定已经想好了为…

[pwn]静态编译

静态编译 1. 栈足够大的情况下 程序在ida打开后&#xff0c;左侧的函数栏目没有红色&#xff08;系统调用的函数&#xff09;&#xff0c;而只有一些静态函数&#xff0c;通常这类文件的大小会必普通的pwn题程序要大得多。 这种静态编译的题没有调用库函数&#xff0c;也就没…

Prometheus + Grafana 监控系统-PrometheusAlert安装与配置指南

背景 Grafana 目前对国内主流的消息通知渠道兼容性一般&#xff0c;因此可以考虑结合使用国产的 PrometheusAlert PrometheusAlert是开源的运维告警中心消息转发系统&#xff0c;支持主流的监控系统Prometheus、Zabbix&#xff0c;日志系统Graylog2&#xff0c;Graylog3、数据…

华为云生态和快速入门

华为云生态 新技术催生新物种&#xff0c;新物种推动新生态 数字技术催生各类运营商去重塑并颠覆各行业的商业模式 从业务层面看&#xff0c;企业始终如一的目标是业务增长和持续盈利&#xff0c;围绕这些目标衍生出提质、增效、降本、安全、创新和合规的业务诉求&#xff0c…

树形结构C语言的实现

一.什么是树&#xff1a; 树形结构是一层次的嵌套结构。一个树形结构的外层和内层有相似的结构&#xff0c;所以这种结构多可以递归的表示。经典数据结构中的各种树状图是一种典型的树形结构&#xff1a;一棵树可以简单的表示为根&#xff0c;左子树&#xff0c;右子树。左子树…

Android HWASAN使用与实现原理

一、背景 为了提前检测出Android User Sapce的app或native进程的内存错误问题&#xff0c;帮助研发定位与分析这些问题&#xff0c;基于Android 14版本上对HWASAN做了调研分析。 二、ASAN介绍 HWASAN是在ASAN的基础上做了拓展&#xff0c;因此在介绍HWASAN之前先了解下ASAN.…

电源设计改进稳定度和误差放大器的解决方案

电池&#xff0c;变压器&#xff0c;电源和转换器会不断受到能量损失的影响。结果&#xff0c;负载上的输出电压会降低。温度是性能的另一个关键特征。通过创建误差放大系统&#xff0c;可以在任何类型的负载下稳定输出电压。 稳压二极管稳定器 使用功率晶体管以及电流放大器…

自己动手实现语音识别

声音的本质是震动,震动的本质是位移关于时间的函数,波形文件(.wav)中记录了不同采样时刻的位移。 通过傅里叶变换,可以将时间域的声音函数分解为一系列不同频率的正弦函数的叠加,通过频率谱线的特殊分布,建立音频内容和文本的对应关系,以此作为模型训练的基础。 语音mfc…

比赛获奖的武林秘籍:02 国奖秘籍-大学生电子计算机类竞赛快速上手的流程,小白必看

比赛获奖的武林秘籍&#xff1a;02 国奖秘籍-大学生电子计算机类竞赛快速上手的流程&#xff0c;小白必看 摘要 本文主要介绍了大学生参加电子计算机类比赛&#xff08;电赛、光电设计大赛、计算机设计大赛、嵌入式芯片与系统设计大赛等比赛&#xff09;的流程和涉及到的知识…