AVPacket
AVPacket 中存储的是经过编码的压缩数据。在解码中,AVPacket 由解复用器输出到解码器;在编码中,AVPacket 由编码器输出到复用器。
对于视频而言,一个 AVPacket 通常只包含一个压缩视频帧。而对于音频而言,一个 AVPacket 可能包含多个完整的音频压缩帧。AVPacket 也可以不包含压缩编码数据,而只包含 SideData,这种包可以称为空 Packet。例如,编码结束后只需要更新一些参数时就可以发空 Packet。
AVPacket 对象可以在栈上分配,注意此处指的是AVPacket对象本身。而 AVPacket 中包含的数据缓冲区是通过av_malloc() 在堆上分配的。
_______ ______________ | | | | | input | demuxer | encoded data | decoder | file | ---------> | packets | -----+ |_______| |______________| | v _________ | | | decoded | | frames | |_________| ________ ______________ | | | | | | | output | <-------- | encoded data | <----+ | file | muxer | packets | encoder |________| |______________|
剖析重要成员
buf、data、size
buf
为数据缓冲区引用,也可叫引用计数缓冲区
。对另一字段 uint8_t *data
指向的内存区域提供引用计数等管理机制。AVBufferRef 对数据缓冲区提供了管理机制,用户不应直接访问数据缓冲区。
- 如果 buf == NULL:则data指向的数据缓冲区不使用引用计数机制,av_packet_ref 执行数据缓冲区的拷贝,而非仅仅增加缓冲区引用计数;
- 如果 buf != NULL:则data指向的数据缓冲区使用引用计数机制,av_packet_ref 将不拷贝缓冲区,而仅增加缓冲区引用计数。av_packet_unref 将数据缓冲区引用计数减 1,当缓冲区引用计数为 0 时,缓冲区内存被ffmpeg回收;
如果 pkt.buf != NULL,则有 pkt.data == pkt.buf->data == pkt.buf->buffer.data
typedef struct AVPacket {
/**
* 对存储包数据的引用计数缓冲器的引用
* 可能为NULL,则该AVPacket包非引用计数模式
*/
AVBufferRef *buf;
/**
* 数据缓冲区地址与大小
* 音视频编码压缩数据存储于此片内存区域
*/
uint8_t *data;
int size;
} 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添加到数组的尾部。
typedef struct AVPacket {
// 辅助数据组,包含多种类型的辅助信息
AVPacketSideData *side_data;
// 数据组数量
int side_data_elems;
}
pts、dts
以AVStream->time_base为单位的时间戳
typedef struct AVPacket {
/**
* 以AVStream->time_base单位表示的时间戳
* pts必须大于或等于dts
*/
int64_t pts;
/**
* 以AVStream->time_base为单位的解压缩时间戳,即数据包解压缩的时间
*/
int64_t dts;
}
stream_index、flags
当前包(Packet)所有流(Stream)的索引(index)
typedef struct AVPacket {
// 当前包所有流的索引
int stream_index;
// packet标志位,比如是否关键帧等
int flags;
}
duration、pos
typedef struct AVPacket {
// 当前包解码后的帧播放持续的时长,单位timebase,值等于下一帧pts减当前帧pts
int64_t duration;
// 当前包在流中的位置,单位字节
int64_t pos;
}
缓存引用机制
typedef struct AVBufferRef {
AVBuffer *buffer;
uint8_t *data;
int size;
} AVBufferRef;
struct AVBuffer {
uint8_t *data; /**< data described by this buffer */
int size; /**< size of data in bytes */
atomic_uint refcount;
void (*free)(void *opaque, uint8_t *data);
void *opaque;
int flags;
};
AVBufferRef引用AVBuffer,每增加一个指向同一个AVBuffer的AVBufferRef,AVBuffer中的引用计数就加1;相反,每减少一个指向AVBuffer的AVBufferRef,AVBuffer中的引用计数就减1,当引用计数等于0时,就会释放AVBuffer.data以及AVBuffer本身,类似于C++的智能指针。
av_packet_ref
av_packet_ref() 作了处理如下:
- 如果 src->buf == NULL,则将 src 缓冲区拷贝到新创建的 dst 缓冲区,注意 src 缓冲区不支持引用计数,但新建的 dst 缓冲区是支持引用计数的,因为 dst->buf != NULL
- 如果 src->buf != NULL,则 dst 与 src 共用缓冲区,调用 av_buffer_ref() 增加缓冲区引用计数即可
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
// ....
if (!src->buf) {
// 为buf分配空间
ret = packet_alloc(&dst->buf, src->size);
if (ret < 0) goto fail;
av_assert1(!src->size || src->data);
// 数据从data拷贝到buf->data
if (src->size)
memcpy(dst->buf->data, src->data, src->size);
// data指针也指向buf->data
dst->data = dst->buf->data;
} else {
// 引用计数+1
dst->buf = av_buffer_ref(src->buf);
if (!dst->buf) {
ret = AVERROR(ENOMEM);
goto fail;
}
// data共用一个缓冲区
dst->data = src->data;
}
dst->size = src->size;
// ....
return ret;
}
av_packet_unref
注销 AVPacket *pkt 对象,并调用 av_buffer_unref(&pkt->buf) 将缓冲区引用计数减 1,将缓冲区引用计数减 1后,若缓冲区引用计数变成 0,则回收缓冲区内存。
void av_packet_unref(AVPacket *pkt)
{
// 释放 side_data
av_packet_free_side_data(pkt);
// 释放引用计数
av_buffer_unref(&pkt->buf);
// 初始化Packet属性
av_init_packet(pkt);
// 释放缓存区指针
pkt->data = NULL;
pkt->size = 0;
}
文章评论