------------------------------------------------------------
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)如何实现即释放内存,又将指针赋值为空的过程.
因为它传递的是指针的地址.