在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;
几个关键点:
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;
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; /**< data described by this buffer */
int size; /**< size of data in bytes */
/**
* 引用当前AVBuffer的AVBufferRef实例数,即引用计数
* number of existing AVBufferRef instances referring to this buffer
*/
atomic_uint refcount;
/**
* a callback for freeing the data,释放AVBuffer时,会通过该接口释放data数据
*/
void (*free)(void *opaque, uint8_t *data);
/**
* an opaque pointer, to be used by the freeing callback,调用free函数的参数
*/
void *opaque;
/**
* A combination of BUFFER_FLAG_*
*/
int flags;
};
AVBufferRef引用AVBuffer,每增加一个指向同一个AVBuffer的AVBufferRef,AVBuffer中的引用计数计数就加1;相反,每减少一个指向AVBuffer的AVBufferRef,AVBuffer中的引用计数就减1,当引用计数等于0时,就会释放AVBuffer.data以及AVBuffer本身,类似于C++的智能指针。
分配并返回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加群免费领取~
释放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);
}
为已有的AVPacket分配负载数据,设置引用计数,并且通过av_init_packet
函数把其他参数设置为默认值。
int av_new_packet(AVPacket *pkt, int size)
{
AVBufferRef *buf = NULL;
// 分配负载数据,设置引用计数
int ret = packet_alloc(&buf, size);
if (ret < 0)
return ret;
// 设置默认值
av_init_packet(pkt);
// 赋值引用计数AVBufferRef和负载数据
pkt->buf = buf;
pkt->data = buf->data;
pkt->size = size;
return 0;
}
把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等int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
int ret;
// 把src的参数copy到dst,不包含负载数据
ret = av_packet_copy_props(dst, src);
if (ret < 0)
return ret;
if (!src->buf) { // 若src不是引用计数,则为dst创建AVBufferRef,初始化引用计数为1,并且把src的负载数据(AVPacket->data)copy到AVBuffer中
ret = packet_alloc(&dst->buf, src->size);
if (ret < 0)
goto fail;
// copy有效负载数据
if (src->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;
}
把scr的所有字段move到dst,并把src的字段重置为默认值,此时AVPacket对AVBuffer的引用计数并不会变。
void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
*dst = *src;
// 重置src AVPacket的所有字段
av_init_packet(src);
src->data = NULL;
src->size = 0;
}
首先通过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的函数都是针对这两部分操作:
作者:ltlovezh
原文链接 FFmpeg之AVPacket - 掘金