在FFmpeg中,AVPacket主要存储编码数据,例如:H264、H265、AAC等。对于视频流,它通常应该包含一个编码帧;对于音频流,则可能包含多个音频帧。编码器可能输出空AVPacket,不包含编码数据,只包含边side data,例如:在编码结束时更新一些流参数。
AVPacket结构体如下所示:
typedef struct AVPacket {
/**
* 若为空,则表示AVPacket未使用引用计数管理负载数据,否则指向存储负载数据的引用计数AVBufferRef -> AVBuffer
* A reference to the reference-counted buffer where the packet data is
* stored.
* May be NULL, then the packet data is not reference-counted.
*/
AVBufferRef *buf;
/**
* 基于AVStream->time_base的pts,若文件中没有,则为AV_NOPTS_VALUE。
* pts必须大于等于dts,因为显示不能早于解码
*/
int64_t pts;
/**
* 基于AVStream->time_base的dts,若文件中没有,则为AV_NOPTS_VALUE。
*/
int64_t dts;
// 负载数据
uint8_t *data;
// 负载数据的长度
int size;
// 属于AVFormatContext中的哪个AVStream
int stream_index;
/**
* A combination of AV_PKT_FLAG values,AV_PKT_FLAG_KEY表示关键帧
*/
int flags;
/**
* Additional packet data that can be provided by the container.
* Packet can contain several types of side information.
* 携带的不同类型的side data
*/
AVPacketSideData *side_data;
int side_data_elems;
/**
* Duration of this packet in AVStream->time_base units, 0 if unknown.
* Equals next_pts - this_pts in presentation order.
* 当前帧的持续时间,基于AVStream->time_base
*/
int64_t duration;
// byte position in stream, -1 if unknown
int64_t pos;
} AVPacket;
几个关键点:
- AVPacket的时间戳都是基于AVStream->time_base时间基准。
- dts是解码时间,pts是显示时间,pts必须大于等于dts,因为只有先解码,才能显示。
- AVPacket->buf决定了负载数据的管理方式,若为空,则表示AVPacket未使用引用计数管理负载数据,AVPacket->data就是负载数据;否则,则使用引用计数管理负载数据,AVPacket->buf引用AVBufferRef,AVBufferRef引用AVBuffer,AVBuffer存储了负载数据,关于引用计数下面 详细介绍。
side_data和side_data_elems
AVPacket->side_data是AVPacket携带的side数据数组,AVPacket->side_data_elems是数组的长度。av_packet_new_side_data
和av_packet_add_side_data
函数都提供了向AVPacket添加指定类型side data的能力,只是参数略有差异,每次都会把新side data添加到数组的尾部。av_packet_get_side_data
函数提供了从AVPacket获取指定类型side data的能力。
typedef struct AVPacketSideData {
uint8_t *data;
int size;
// side data的类型
enum AVPacketSideDataType type;
} AVPacketSideData;
针对AVPacket的函数
FFmpeg提供了很多函数操作AVPacket,其中主要需要关注的是不同函数对AVPacket引用计数的影响。首先看下与引用计数相关的AVBufferRef和AVBuffer结构体:
typedef struct AVBufferRef {
//
AVBuffer *buffer;
/**
* The data buffer. It is considered writable if and only if
* this is the only reference to the buffer, in which case
* av_buffer_is_writable() returns 1.
*/
uint8_t *data;
/**
* Size of data in bytes.
*/
int size;
} AVBufferRef;
struct AVBuffer {
// 具体数据和Size
uint8_t *data; /**
AVBufferRef引用AVBuffer,每增加一个指向同一个AVBuffer的AVBufferRef,AVBuffer中的引用计数计数就加1;相反,每减少一个指向AVBuffer的AVBufferRef,AVBuffer中的引用计数就减1,当引用计数等于0时,就会释放AVBuffer.data以及AVBuffer本身,类似于C++的智能指针。
av_packet_alloc
分配并返回AVPacket,所有参数都是默认值,此时并没有分配AVPacket->buf,因为AVPacket还没有包含有效负载数据。
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = av_mallocz(sizeof(AVPacket));
if (!pkt)
return pkt;
// 减少pkt指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。同时,释放side_data,其他参数则reset默认值。
av_packet_unref(pkt);
return pkt;
}
【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~
av_packet_free
释放AVPacket自身内存和AVBufferRef自身内存,并且减少AVBufferRef指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。
void av_packet_free(AVPacket **pkt)
{
if (!pkt || !*pkt)
return;
// 减少pkt指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。同时,释放side_data,其他参数则reset默认值。
av_packet_unref(*pkt);
av_freep(pkt);
}
av_new_packet
为已有的AVPacket分配负载数据,设置引用计数,并且通过av_init_packet
函数把其他参数设置为默认值。
int av_new_packet(AVPacket *pkt, int size)
{
AVBufferRef *buf = NULL;
// 分配负载数据,设置引用计数
int ret = packet_alloc(&buf, size);
if (ret buf = buf;
pkt->data = buf->data;
pkt->size = size;
return 0;
}
av_init_packet
把AVPacket的字段设置初始化为默认值,但是并不会为AVPacket->data和AVPacket->size设置值,因为此时还没有包含负载数据。
void av_init_packet(AVPacket *pkt)
{
pkt->pts = AV_NOPTS_VALUE;
pkt->dts = AV_NOPTS_VALUE;
pkt->pos = -1;
pkt->duration = 0;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
pkt->convergence_duration = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
pkt->flags = 0;
pkt->stream_index = 0;
pkt->buf = NULL;
pkt->side_data = NULL;
pkt->side_data_elems = 0;
}
av_packet_ref
把src的数据copy到dst,包含两部分:
- 普通字段直接通过
av_packet_copy_props
函数copy,例如:side data、pts等 - 负载数据:为dst AVPacket分配AVBufferRef,指向src的AVBuffer,两个AVPacket共享负载数据AVBuffer,增加了AVBuffer的引用计数。
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
int ret;
// 把src的参数copy到dst,不包含负载数据
ret = av_packet_copy_props(dst, src);
if (ret buf) { // 若src不是引用计数,则为dst创建AVBufferRef,初始化引用计数为1,并且把src的负载数据(AVPacket->data)copy到AVBuffer中
ret = packet_alloc(&dst->buf, src->size);
if (ret size)
memcpy(dst->buf->data, src->data, src->size);
dst->data = dst->buf->data;
} else { // 若src已经是引用计数,则基于src->buf创建并返回AVBufferRef,并增加AVBuffer的引用计数
dst->buf = av_buffer_ref(src->buf);
if (!dst->buf) {
ret = AVERROR(ENOMEM);
goto fail;
}
dst->data = src->data;
}
dst->size = src->size;
return 0;
fail:
av_packet_free_side_data(dst);
return ret;
}
av_packet_unref
把AVPacket的普通变量设置为默认值,释放所有的side data,并且,释放AVBufferRef自身内存,减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBuffer
void av_packet_unref(AVPacket *pkt)
{
// 删除所有的side data
av_packet_free_side_data(pkt);
// 释放AVBufferRef自身内存,并且减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBuffer
av_buffer_unref(&pkt->服务器托管网buf);
// 把AVPacket的其他变量设置为默认值
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
}
av_packet_move_ref
把scr的所有字段move到dst,并把src的字段重置为默认值,此时AVPacket对AVBuffer的引用计数并不会变。
void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
*dst = *src;
// 重置src AVPacket的所有字段
av_init_packet(sr服务器托管网c);
src->data = NULL;
src->size = 0;
}
av_packet_clone
首先通过av_packet_alloc创建一个AVPacket,然后使用av_packet_ref把src AVPacket copy到新创建的AVPacket。
AVPacket *av_packet_clone(const AVPacket *src)
{
AVPacket *ret = av_packet_alloc();
if (!ret)
return ret;
if (av_packet_ref(ret, src))
av_packet_free(&ret);
return ret;
}
总结
AVPacket的字段包含两部分:
- 普通字段:除AVPacket->data、AVPacket->size和AVPacket->buf之外的所有字段,这些字段依附于AVPacket自身,通过av_init_packet重置为默认值。
- 负载数据:AVPacket->data、AVPacket->size和AVPacket->buf,负载数据需要单独创建(av_new_packet),通过引用计数进行管理。
所有关于AVPacket的函数都是针对这两部分操作:
- av_packet_alloc与av_packet_free相对应:前者分配AVPacket自身内存,后者释放AVPacket自身内存,并且通过av_packet_unref释放AVBufferRef自身内存,减少AVBuffer引用计数。
- av_packet_ref:把src AVPacket的所有字段copy到dst AVPacket,并且为dst AVPacket创建AVBufferRef,指向同一个AVBuffer。最终,两个AVPacket指向同一个AVBuffer负载数据,引用计数也会加1。
- av_packet_unref:释放AVPacket的AVBufferRef,并且减少AVBuffer的引用计数,同时把其他字段设置为默认值。
- av_packet_move_ref:av_packet_ref是copy操作,最终两个AVPacket都有效,引用计数增1;而av_packet_move_ref是move操作,最终src AVPacket废弃,dst AVPacket有效,引用计数不变。
- av_packet_clone:相当于av_packet_alloc + av_packet_ref
作者:ltlovezh
原文链接FFmpeg之AVPacket – 掘金
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试,已经让我变得不思进取,谈了2年的女朋友也因为我的心态和工资和我分手…