FFmpeg 会将读到的码流封装成一个一个的 AVPacket,AVPacket 是解码器的输入,是编码器的输出。
AVPacket 可以分为两个部分,一个部分我们可以称之为 prop,这部分主要存放了 AVPacket 的一些属性,例如,pts,dts,还有 AVPacket 所属流的 index;另一个部分,我们可以称之为 data,这部分则存放了 AVPacket 中的核心数据,例如 H.264 的原始码流等。
typedef struct AVPacket {
AVBufferRef *buf;
int64_t pts;
int64_t dts;
uint8_t *data;
int size;
int stream_index;
int flags;
...
}AVPacket;
int64_t pts;
播放时间戳,pts。
int64_t dts;
解码时间戳,dts。
int stream_index;
所属 stream 的 index。
uint8_t *data;
存放数据的指针。
int size;
存放数据的大小。
AVPacket *av_packet_alloc(void);
用来分配一个 AVPacket,返回值是一个指针。使用该函数分配的 AVPacket 必须使用 av_packet_free() 来释放掉。该函数分配的时候,只会分配 AVPacket 本身的内容,而不会分配 data 数据。
AVPacket * packet = av_packet_alloc();
AVPacket *av_packet_clone(const AVPacket *src);
复制一个 AVPacket。这个函数会复制 AVPacket 的基本属性,而对于 AVPacket 中的 data 数据,这里只是复制了其 data 的一个引用。
distPacket = av_packet_clone(srcPacket);
void av_packet_free(AVPacket **pkt);
销毁一个 AVPacket,注意这里的参数是一个二级指针。在释放调用之后,传入的指针会变成 NULL。
av_packet_free(&packet);
void av_init_packet(AVPacket *pkt);
初始化一个 AVPacket,但是需要注意的是,这个函数不会动 data 指针和 size 这两个属性。
av_init_packet(packet);
int av_new_packet(AVPacket *pkt, int size);
这个函数可以为一个 AVPacket 分为 data 部分,默认来说,我们 alloc 出来的 AVPacket 是没有 data 部分的。如果我们需要让 AVPacket 分配一段数据的话,这里建议使用这个函数。size表示想要申请的大小。
av_new_packet(packet, size);
void av_shrink_packet(AVPacket *pkt, int size);
这个函数用来减少 AvPacket 中 data 数据大小。
av_shrink_packet(packet, size);
int av_grow_packet(AVPacket *pkt, int grow_by);
这个函数用来增加 AvPacket 中 data 数据大小。
av_grow_packet(packet, grow_by);
int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size)
把data指针指向数据交给 Packet 的 buffer 进行管理,也就是把 Packet 的 data 赋值为传入的 data,指向新的数据区域。
av_packet_from_data(packet, data, size);
int av_packet_ref(AVPacket *dst, const AVPacket *src)
与 av_packet_clone 类似,把 src 中的所有数据赋值到新的 dst 中,对于引用数据,data 数据会创建一块新的缓冲区进行赋值。
av_packet_ref(dst, packet);
void av_packet_unref(AVPacket *pkt);
把 AvPacket 各个数据进行解引用和释放对应的空间,释放 buffer 缓冲区,data 重新置为 NULL,size 置为0。
av_packet_unref(packet);
void av_packet_move_ref(AVPacket *dst, AVPacket *src);
把 dst 中的 data 指向 src 中的 data,把 src 进行初始化,src 的 data 置 NULL,size置0。
av_packet_move_ref(dst, packet);
AVpacket 本质也是一个容器,它本身不包含 data 数据, 而是有一个指向 data 数据区域的指针(地址)。
存在2个 packet ,但2个 packet 指向不同的 data 区域, 但 data 数据一样。
2个 packet 不会相互干扰,数据各自储存在各自的空间当中,但是显而易见的就是会造成更多空间内存上的浪费。
当多个 packet 同时指向一个 data 数据缓存空间,FFmpeg 采用引用计数的方式来管理 data 数据区。
那么 AVpacket 中引用的数量记录在哪里呢?
引用数量就记录在 AVBufferRef *buf 这个结构体当中。
struct AVBuffer {
atomic_uint refcount;
......
};
typedef struct AVBufferRef {
AVBuffer *buffer;
uint8_t *data;
int size;
} AVBufferRef;
在 int av_packet_ref(AVPacket *dst, const AVPacket *src) 函数中:
如果 src 里的 AVBufferRef *buf 为空,dst 分配一个新的缓冲区并复制 src 的数据,同时 dst 的引用计数记为 1。
if (!src->buf) {
ret = packet_alloc(&dst->buf, src->size);
av_assert1(!src->size || src->data);
if (src->size)
memcpy(dst->buf->data, src->data, src->size);
dst->data = dst->buf->data;
}
如果 dat 里的 AVBufferRef *buf 不为空,dst 的 buf 作为新的对 src 中 buf 的引用, buf 引用计数加一。
else {
dst->buf = av_buffer_ref(src->buf);
dst->data = src->data;
}
在 void av_packet_unref(AVPacket *pkt) 函数中,
把 pkt 中 AVBufferRef *buf 引用计数 -1,如果引用次数为 0 ,销毁数据缓存空间。