ts流音视频组包关键函数分析

------------------------------------------------------------
author: hjjdebug
date: 2024年 07月 30日 星期二 13:22:07 CST
description: ts流音视频组包关键函数分析
------------------------------------------------------------
在av_read_frame(AVFormatContext *s, AVPacket *pkt) 中
先贴上一个网上的贴图.


这个图就只是一个框架,本博想说的是其更底层的数据组包的过程。
可认为是ff_read_packet() 接口下面的东西以ts流为例,它必需要通过mpegts_push_data 来进行内存分配,数据组装

提纲:

分析1: buffer_pool_get()
分析2: MpegTSContext 对象是唯一的吗?
分析3: 内存池中的数据,怎样转移到packet 中?
分析4: 为什么要用内存池,内存池中申请的内存,何时释放?
 

一个TS流的例子:
内存上的考虑: 调用栈
 0 in posix_memalign of ld_preload_udpsend.c:268
 1 in av_malloc of libavutil/mem.c:86
 2 in av_mallocz of libavutil/mem.c:239
 3 in av_buffer_pool_init of libavutil/buffer.c:268
 4 in buffer_pool_get of libavformat/mpegts.c:1112
 5 in mpegts_push_data of libavformat/mpegts.c:1193
 6 in handle_packet of libavformat/mpegts.c:2846
 7 in handle_packets of libavformat/mpegts.c:2975
 8 in mpegts_read_packet of libavformat/mpegts.c:3219
 9 in ff_read_packet of libavformat/utils.c:843
10 in read_frame_internal of libavformat/utils.c:1546
11 in av_read_frame of libavformat/utils.c:1750
12 in main of demux-decoding.c:372

当是pes 包时,会调用到下面的函数:
static int mpegts_push_data(MpegTSFilter *filter,
                            const uint8_t *buf, int buf_size, int is_start,
                            int64_t pos)

{

    PESContext *pes   = filter->u.pes_filter.opaque;
    MpegTSContext *ts = pes->ts;

    while (buf_size > 0) {
        switch (pes->state) {  //如果正在分析的包是pes 包头
        case MPEGTS_HEADER:
        ...
        pes->total_size = AV_RB16(pes->header + 4);  //读取包的长度
        /* NOTE: a zero total size means the PES size is unbounded */
        if (!pes->total_size)
            pes->total_size = MAX_PES_PAYLOAD;

        /* allocate pes buffer */
        pes->buffer = buffer_pool_get(ts, pes->total_size); //申请得到pes 缓冲位置
        ...
}
参数1: MpegTSFilter *filter, 对象
参数2: 输入,数据指针 buf.
参数3: 输入, 数据大小
参数4,bool 值,
参数5, 文件位置信息

------------------------------------------------------------
分析1: buffer_pool_get()
------------------------------------------------------------
static AVBufferRef *buffer_pool_get(MpegTSContext *ts, int size)
{
    int index = av_log2(size + AV_INPUT_BUFFER_PADDING_SIZE); // 根据大小计算它是2的多少次幂
    if (!ts->pools[index]) { //由多种pool, 如果该大小的pool 还没有创建
        int pool_size = FFMIN(MAX_PES_PAYLOAD + AV_INPUT_BUFFER_PADDING_SIZE, 2 << index); // pool 的大小是2的整数次幂
// 创建这个pool, 这里只分配内存88 字节,是pool的头部大小
// 结果保存在ts 对象中.(一个MpegTSContext)
        ts->pools[index] = av_buffer_pool_init(pool_size, NULL); 
        if (!ts->pools[index])
            return NULL;
    }
//这里当pool中没有空闲项时,会创建一个项,每项会通过av_malloc->posix_memalign向系统申请pool_size大小的数据
//当有空闲项时,直接返回就不用向系统申请了, pool 就是一个内存项的链表
    return av_buffer_pool_get(ts->pools[index]); 
}
内存池指针保存在MpegTSContext 对象中.

------------------------------------------------------------
分析2: MpegTSContext 对象是唯一的吗?
------------------------------------------------------------
先看一眼ts 吧, 它叫MpegTs 的上下文
(gdb) p ts
$13 = (MpegTSContext *) 0x55555556b3c0

    MpegTSContext *ts = pes->ts;
    它是从pes->ts 得来的.

   而pes 是PESContext 对象, 可叫做基础流包上下文
    PESContext *pes   = filter->u.pes_filter.opaque;

    而基础流包上下文又是从filter 得到的,filter 是MpegTSFilter对象
    面向对象的编程思想,不怕你复杂,就想看看你的真身是怎么来的.有是怎么没的!


从handle_packet 怎样调用到mpegts_push_data()
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{
    int pid = AV_RB16(packet + 1) & 0x1fff;
    int is_start = packet[1] & 0x40;
    MpegTSFilter *tss = ts->pids[pid];

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

tss->u.pes_filter.pes_cb 就是mpegts_push_data, 函数指针就构成了架构的软调用。
而tss 就是传递下去的第一个参数. 可见MpegTSFilter 就是数据的关键!
看代码, tss = ts->pids[pid]
可见又绕了回来, tss 由 MpegTSContext 对象决定. 由包的pid就能找到对应的filter
MpegTSContext 对象由哪里来?

static int mpegts_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    MpegTSContext *ts = s->priv_data;
    pkt->size = -1;
    ts->pkt = pkt;
    ret = handle_packets(ts, 0);
    ...
}
可见MpegTSContext 对象是AVFormatContext 对象的私有数据, 
而AVFormatContext 对象在整个数据处理期间是唯一的,所以 MpegTSContext 对象也是唯一的.
至于它在哪里创建的,就先不关心了.

------------------------------------------------------------
分析3: 内存池中的数据,怎样转移到packet 中?
------------------------------------------------------------
0 in new_pes_packet of libavformat/mpegts.c:1026
1 in mpegts_push_data of libavformat/mpegts.c:1134
2 in handle_packet of libavformat/mpegts.c:2846
3 in handle_packets of libavformat/mpegts.c:2975
4 in mpegts_read_packet of libavformat/mpegts.c:3219
5 in ff_read_packet of libavformat/utils.c:843
6 in read_frame_internal of libavformat/utils.c:1546
7 in av_read_frame of libavformat/utils.c:1750
8 in main of demux-decoding.c:372

static int new_pes_packet(PESContext *pes, AVPacket *pkt)
{
    av_packet_unref(pkt); //想往pkt中装东西,总是先去旧,再迎新.

    pkt->buf  = pes->buffer;      //原来pkt数据都是从pes中接手的
    pkt->data = pes->buffer->data;
    pkt->size = pes->data_index;
    memset(pkt->data + pkt->size, 0, AV_INPUT_BUFFER_PADDING_SIZE); //加上填充字节
    pkt->stream_index = pes->st->index;
    pkt->pts = pes->pts;
    pkt->dts = pes->dts;
    pkt->pos   = pes->ts_packet_pos;
    pkt->flags = pes->flags;
    uinr8_t *sd = av_packet_new_side_data(pkt, AV_PKT_DATA_MPEGTS_STREAM_ID, 1);
    *sd = pes->stream_id;

    pes->buffer = NULL;   // pes->buffer 变成了空,并被复位
    reset_pes_packet_state(pes);
    return 0;
}
虽然这里使用了pkt, 当要形成最终的pkt还要经过一断长长的路程,因为还要对pkt 进行parse_packet();
把pkt处理到s->internal->parse_pkt.释放掉pkt. 在这里申请的内存通过pool_release_buffer归还给了内存池.
还要把处理好的packet提交到链表中,再从链表中取回,这里限于篇幅就不展开了.

static int mpegts_push_data(MpegTSFilter *filter,
                            const uint8_t *buf, int buf_size, int is_start,
                            int64_t pos)

{
    PESContext *pes   = filter->u.pes_filter.opaque;
    MpegTSContext *ts = pes->ts;
    const uint8_t *p;
    int ret, len, code;

    if (is_start) { //正在分析的包是开始包
        if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) { //目前pes 正在填充es数据
//意味着上一个包已经结束,这是一个新的es包,立即把以前分析的pes包转移到pkt中,并创建新的pes包,
            ret = new_pes_packet(pes, ts->pkt); //ts->pkt,就是上层调用传的参数pkt
            if (ret < 0) return ret;
            ts->stop_parse = 1;// 就是说找到了包尾,上层调用要处理.
        } else {
            reset_pes_packet_state(pes);
        }
        pes->state         = MPEGTS_HEADER; //设置pes状态,处理完该包数据
        pes->ts_packet_pos = pos;
    }
    ....
}

------------------------------------------------------------
分析4: 为什么要用内存池,内存池中申请的内存,何时释放?
------------------------------------------------------------
内存池是一个内存项链表管理结构,程序向内存池申请内存项,如果没有空闲项,
内存池向系统申请内存。
当内存项使用完毕释放时(在read_frame中是在parse_packet函数中),
内存项通过 pool_release_buffer()归还给内存池而不是通过free()归还给系统
只有当当调用 av_buffer_pool_uninit(&pool); 并且pool的引用计数为0的时候,才会释放内存池,
把所有从系统申请的内存归还给系统.
实例可参考: FFMpeg AVBufferPool 的理解与掌握_ffmpeg 使用内存池-CSDN博客

小结: 着重分析了ff_read_packet中的一种实现,ts流的组包过程.
从基础流pes组包,内存分配的角度分析了mpegs_push_data()函数.重在理解而没有照抄代码.
重点是说内存池并没有随包的释放而释放给系统,其生命周期是整个数据分析周期.

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值