av_packet_unref()代码分析

------------------------------------------------------------
author:hjjdebug
date: 2024年 07月 25日 星期四 15:00:34 CST
description: av_packet_unref()代码分析
------------------------------------------------------------
av_frame_unref() 也有类似的架构, 分析一个就可以了.
av_packet_unref() 至少有5次动态内存释放!

void av_packet_unref(AVPacket *pkt)
{
    av_packet_free_side_data(pkt);  //释放包的附加数据,有2次
    av_buffer_unref(&pkt->buf); // 减少包所指向的缓存的引用,当缓存引用减到0时会释放缓存,有3次
    get_packet_defaults(pkt); //重新初始化为原始数据
}
关键就2句话, 释放附加数据,减少缓冲引用计数.
----------------------------------------
甲: 释放包的附加数据
----------------------------------------
void av_packet_free_side_data(AVPacket *pkt)
{
    int i;
    for (i = 0; i < pkt->side_data_elems; i++)
        av_freep(&pkt->side_data[i].data); //释放指针指向的内存并修改指针为0, 释放的是数组
    av_freep(&pkt->side_data); // 释放指针指向的内存并且修改指针为0, 释放的是指针数组本身
    pkt->side_data_elems = 0;
}


//前方高能, 理解指针的概念. 
//简单说,指针解引用就是取指针处内容,写指针处内容.
//复杂在于,指针可能指向字符,整数,长整数,数组,结构,甚至指针.修改的数据是任意的.

// 用memcpy 来给val 赋值,形式比较少见.
// 用memcpy 来作为源,访问arg所指的数据,是对arg 指针的一种解引用
// 效果相当于val=*(long *)arg
// 让arg 作为memcpy的目的地址,也是对arg指针的解引用.
// 修改了arg指针处内容为0,  相当于 *(long *)arg = 0;
// 由于传来的是地址,相当于直接修改了调用的参数
void av_freep(void *arg)
{
    void *val;
    memcpy(&val, arg, sizeof(val));  //用memcpy来解引用,保留指针到val
    memcpy(arg, &(void *){ NULL }, sizeof(val));  //用memcpy解引用,把0付给指针内容
    av_free(val);  // 把原来arg所指向的内容释放掉
}
以上两个memcpy的用法相当的使人容易范迷糊,是指针解引用的另一种形式.
解引用是指访问指针所指内容的方式.
我这里重写改写一下成容易理解的形式, 专门写了代码测试,实测效果一致!

//可能是有强制转换原作者才没有采用吧,强制转换是为了方便解引用
//因为void* 是不能直接解引用的
//说实话看起来比memcpy清爽多了!

void av_freep(void *arg)
{
    long * p_arg = (long *)arg; //强制类型转换
    free((void *)(*p_arg)); //解引用获取指针,把指针内存释放掉
    *p_arg=0; //解引用把0付给指针内容,这里指针指向的还是指针,改的就是指针的内容!

}

我见到的pkt的side_data 就是ts流的stream_id
 p *pkt
$82 = {
  buf = 0x55555559d880,
  pts = 132720,
  dts = 132720,
  data = 0x555555684280 "\vw\220\257\024@C..."
  size = 768,
  stream_index = 1,
  flags = 1,
  side_data = 0x5555556728e0,   // 指针数组
  side_data_elems = 1,            // 指针数组有一个元素
  duration = 2880,
  pos = 78772,
  convergence_duration = 0
}
(gdb) p pkt->side_data[0]   // 打印这个side_data
$78 = {  // 数据类型stream_id, 大小1个
  data = 0x5555555c2500 "\275",
  size = 1,
  type = AV_PKT_DATA_MPEGTS_STREAM_ID
}
(gdb) p pkt->side_data[0].data  // 指针类型 char*
$79 = (uint8_t *) 0x5555555c2500 "\275"
(gdb) p/x *pkt->side_data[0].data // 数值是0xbd 的ts流id
$81 = 0xbd

----------------------------------------
乙: 解引用AVBuffer
----------------------------------------
pkt->buf 是AVBufferRef *
所以&pkt->buf 是AVBufferRef **
void av_buffer_unref(AVBufferRef **buf)
{
    if (!buf || !*buf) //如果AVBufferRef** 为空或AVBufferRef* 为空,则直接返回
        return;

    buffer_replace(buf, NULL); //用空来替换buf
}

//src==NULL 时的代码简化
static void buffer_replace(AVBufferRef **dst, AVBufferRef **src==NULL)
{
    AVBuffer *b = (*dst)->buffer; //保留AVBuffer 指针
    av_freep(dst); //释放掉AVBufferRef

    //AVBuffer 的参考引用减1,当减到0时
    if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) {
//调用free (b->free往往就是free()) 释放掉AVBuffer所指的数据,b->data才是真正的数据指针
        b->free(b->opaque, b->data); 
        av_freep(&b); //释放掉AVBuffer(头)
    }
}

所以说pkt->buf->buffer->data 才是真正的数据.
第一层是AVBufferRef
第二层是AVBuffer
第三层是data

内存泄漏往往是AVBuffer的引用次数与解引用次数不平衡,使得不能释放包指向的内存
例如: av_packet_clone()就会使引用计数增加,这就需要2次av_packet_unref


补充: 
---------------------------------------------------
av_packet_free 与av_packet_unref 是什么关系.
---------------------------------------------------
void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt) return;
    av_packet_unref(*pkt);
    av_freep(pkt);
}
可见,av_packet_free 不仅unref了pkt, 把pkt 本身也释放了.

小结:
分析了一个复杂对象AVPacket 释放内存的过程. av_packet_unref(AVPacket *pkt);
讲述了av_freep(void *arg)如何实现即释放内存,又将指针赋值为空的过程.
因为它传递的是指针的地址. 
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值