前言:
本文介绍FFMPEG的AVPacket相关内容:
1. AVPacket在FFMPEG中用来保存Demux分离出来的压缩的ES流信息。
2. AVBufferRef表示AVBuffer的引用,和AVBuffer的关系是多个AVBufferRef对一个AVBuffer。
3. AVBuffer 实现引用计数,其参数refcount表示正在使用这个AVBuffer的AVBufferRef个数。
使用AVPacket时,通过调用av_buffer_alloc获得表示AVBufferRef的buf和AVBuffer,数据操作可在获得buf后读写pkt->data结构体实现,本文FFMPEG使用版本ffmpeg-4.2.2。
AVPacket,AVBufferRef,AVBuffer关系总结
关系图
引用层次关系可以简述为:AVPacket ->AVBufferRef->AVBuffer->refcount
总结
API解读
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = av_mallocz(sizeof(AVPacket));
if (!pkt)
return pkt;
/*
*1. 释放side_data,side_data类似extradata,比如保存ADTS的采样率,声道等信息。
在FFMPEG中用AVPacketSideDataType这个枚举类型来描述,Android的MetaData就是一种side_data。
封装在MP4,FLV,MKV中的H264,H265的SPS,PPS信息也存在于extradata中,push给解码器的时候,需要事先把这些Header加上。
此处av_freep(&pkt->side_data)时,用free或者_aligned_free函数释放了0地址。
GCC手册中描述,free的参数可以是0,也就是说free 0地址多次是可以的,什么动作也不会发生。但如果free的参数非calloc,malloc,realloc,则结果未定义。
*2. 如果AVBufferRef存在,则消灭他。
*3. 给AVPacket的成员赋初值
*/
av_packet_unref(pkt);
return pkt;
}
/* 带了size参数意味着可以生成AVBufferRef和AVBuffer */
int av_new_packet(AVPacket *pkt, int size)
{
AVBufferRef *buf = NULL;
/*
* 1. AVBufferRef为空时,流程同av_buffer_alloc,先创建data空间,再创建AVBuffer,最后创建AVBufferRef并返回。此时设置BUFFER_FLAG_REALLOCATABLE标记。
* 2. AVBufferRef不空时,调用av_buffer_realloc:
a.AVBuffer的flag不带有BUFFER_FLAG_REALLOCATABLE标记,或不能写(READONLY或者引用计数不是1),或者data指向与AVBufferRef不一致,则重新分配AVBufferRef并释放之前的AVBufferRef。
b.否则不用重新分配AVBufferRef,直接av_realloc(buf->buffer->data, size)。
*/
int ret = packet_alloc(&buf, size);
if (ret < 0)
return ret;
av_init_packet(pkt);
pkt->buf = buf;
pkt->data = buf->data;
pkt->size = size;
return 0;
}
/* 两个packet共享AVBuffer,但side data不共享 */
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
int ret;
/* 复制src的内容到dst,整这一出是为了深拷贝。
* 把src中side data全部拷贝到dst中去,而不是仅仅传个指针到dst。
*/
ret = av_packet_copy_props(dst, src);
if (ret < 0)
return ret;
if (!src->buf) {
/* 根据情况使用现存的AVBufferRef, 亦或使用新分配的AVBufferRef,此时无AVBufferRef,就会生成新的AVBufferRef */
ret = packet_alloc(&dst->buf, src->size);
……
} else {
/* src指向的数据引用计数加1。为dst产生一个新的AVBufferRef,然后AVBufferRef指向与src同一个AVBuffer。
* 注意dst和src的 AVBufferRef是不相同的,但AVBuffer是相同的。真正计数是在 AVBuffer中进行
*/
dst->buf = av_buffer_ref(src->buf);
if (!dst->buf) {
ret = AVERROR(ENOMEM);
goto fail;
}
/* src指向的数据使用了引用计数, dst->data数据此时不需要复制,只需要把数据指针指向src->data即可 */
dst->data = src->data;
}
dst->size = src->size;
return 0;
fail:
av_packet_free_side_data(dst);
return ret;
}
void av_packet_unref(AVPacket *pkt)
{
/* 如果side data 存在,就释放掉。*/
av_packet_free_side_data(pkt);
/* 如果AVBufferRef存在,就将它释放掉,并且,将它指向的AVBuffer的引用计数减1。如果减1之后,AVBuffer无人引用,那么需要将AVBuffer也释放掉*/
av_buffer_unref(&pkt->buf);
/* 初始化 */
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
}
/* 实际上是使用引用计数功能将新生成的packet指向同一个AVBuffer */
AVPacket *av_packet_clone(const AVPacket *src)
{
AVPacket *ret = av_packet_alloc();
if (!ret)
return ret;
/* 将ret的data指向src的data,并且使用AVBuffer引用计数。*/
if (av_packet_ref(ret, src))
av_packet_free(&ret);
/* AVBufferRef分配不成功可能返回有问题的pkt */
return ret;
}
void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
/* 将src的全部信息放到dst中,并且将 src中信息擦除 */
*dst = *src;
av_init_packet(src);
src->data = NULL;
src->size = 0;
}
/* 让pkt具有计数功能 */
int av_packet_make_refcounted(AVPacket *pkt)
{
int ret;
/* 如果pkt已经使用引用计数,则返回 */
if (pkt->buf)
return 0;
ret = packet_alloc(&pkt->buf, pkt->size);
if (ret < 0)
return ret;
av_assert1(!pkt->size || pkt->data);
if (pkt->size) /* 将原始数据复制到新生成的data buffer中 */
memcpy(pkt->buf->data, pkt->data, pkt->size);
/* data指向新位置,如果需要释放data,此处未进行释放,注意风险 */
pkt->data = pkt->buf->data;
return 0;
}
/* 如果pkt指向的data部分不可以写,那么就新生成一个AVBufferRef和AVBuffer,复制之前data指向的数据,让新生成的data默认可写。 */
int av_packet_make_writable(AVPacket *pkt)
{
AVBufferRef *buf = NULL;
int ret;
if (pkt->buf && av_buffer_is_writable(pkt->buf))
return 0;
ret = packet_alloc(&buf, pkt->size);
if (ret < 0)
return ret;
av_assert1(!pkt->size || pkt->data);
if (pkt->size)
memcpy(buf->data, pkt->data, pkt->size);
av_buffer_unref(&pkt->buf);
pkt->buf = buf;
pkt->data = buf->data;
return 0;
}
int av_get_packet(AVIOContext *s, AVPacket *pkt, int size)
{
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
pkt->pos = avio_tell(s);
/* 1. 用av_grow_packet来给pkt分配AVBufferRef,如果AVBufferRef存在,调用av_buffer_realloc;否则调用av_buffer_alloc
* 2. 用AVIO从流中读size个数据到pkt指向的data中,pkt有数据存在时,从有效data的末尾开始写数据。
*/
return append_packet_chunked(s, pkt, size);
}
/* 增加av pkt的size,增加grow_by字节,注意返回的pkt一定是使用AVBufferRef引用计数的 */
int av_grow_packet(AVPacket *pkt, int grow_by)
{
/* 检查增加量,与pkt->size+grow_by+AV_INPUT_BUFFER_PADDING_SIZE这种写法比,可以避免溢出。 */
if ((unsigned)grow_by >
INT_MAX - (pkt->size + AV_INPUT_BUFFER_PADDING_SIZE))
return AVERROR(ENOMEM);
/* 整块新的buf size */
new_size = pkt->size + grow_by + AV_INPUT_BUFFER_PADDING_SIZE;
/* 如果pkt使用AVBufferRef */
if (pkt->buf) {
size_t data_offset;
uint8_t *old_data = pkt->data;
if (pkt->data == NULL) {
data_offset = 0;
pkt->data = pkt->buf->data;
} else {
/* 已经使用的数据长度,目前也只有在此函数中发现,pkt->data与pkt->buf->data指向可能不同
* 可以看出,完整的pkt data和size应该是要从AVBufferRef中拿
* pkt结构体中指向的data和size是当前可以使用的。像av_shrink_packet这个函数,也是减少了pkt的size。
*/
data_offset = pkt->data - pkt->buf->data;
if (data_offset > INT_MAX - new_size)
return AVERROR(ENOMEM);
}
if (new_size + data_offset > pkt->buf->size) {
int ret = av_buffer_realloc(&pkt->buf, new_size + data_offset);
if (ret < 0) {
pkt->data = old_data;
return ret;
}
/* pkt的data仍然需要比AVBufferRef的data超前data_offset个。*/
pkt->data = pkt->buf->data + data_offset;
}
} else {
/* 没有使用AVBufferRef,则创建一个,所以,av_grow_packet成功之后,pkt的data一定使用了引用计数 */
pkt->buf = av_buffer_alloc(new_size);
if (!pkt->buf)
return AVERROR(ENOMEM);
if (pkt->size > 0)
memcpy(pkt->buf->data, pkt->data, pkt->size);
pkt->data = pkt->buf->data;
}
pkt->size += grow_by;
memset(pkt->data + pkt->size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
return 0;
}
AVPacket中的data不总是等于其成员buf中data成员
先说明一下情况,如果数据包可能包含多个Frame帧,每个Frame帧都有自己的数据和大小,那么AVPacket中的data就可能和成员buf中data成员不一致。总的说来,pkt.buf->data指向整个数据包,而pkt->data指向的是当前正在使用的数据部分。
A. av_grow_packet的时候,pkt->data = pkt->buf->data + data_offset; 如果data_offset不为0,这个时候,二者不一致。
B. mkv demux中,pkt->buf->data指向block,pkt->data指向lace.
static int matroska_parse_frame(...)
{
MatroskaTrackEncoding *encodings = track->encodings.elem;
uint8_t *pkt_data = data;
......处理pkt_data
av_init_packet(pkt);
if (pkt_data != data)
pkt->buf = av_buffer_create(pkt_data, pkt_size + AV_INPUT_BUFFER_PADDING_SIZE,
NULL, NULL, 0);
else
pkt->buf = av_buffer_ref(buf);
if (!pkt->buf) {
res = AVERROR(ENOMEM);
goto fail;
}
pkt->data = pkt_data;
pkt->size = pkt_size;
pkt->flags = is_keyframe;
pkt->stream_index = st->index;
......
res = ff_packet_list_put(&matroska->queue, &matroska->queue_end, pkt, 0);
}
static int matroska_parse_block(AVBufferRef *buf, uint8_t *data...)
{
block_time = sign_extend(AV_RB16(data), 16);
data += 2;
res = matroska_parse_laces(matroska, &data, &size, (flags & 0x06) >> 1,
&lace_size, &laces);
......
for (n = 0; n < laces; n++) {
......
res = matroska_parse_frame(matroska, track, st, buf, data, lace_size[n],
timecode, lace_duration, pos,
!n ? is_keyframe : 0,
additional, additional_id, additional_size,
discard_padding);
......
/* buf不动,data继续向前推进 */
data += lace_size[n];
size -= lace_size[n];
}
}
static int matroska_parse_cluster(MatroskaDemuxContext *matroska)
{
MatroskaBlock *block = &cluster->block;
......
res = matroska_parse_block(matroska, block->bin.buf, block->bin.data,
block->bin.size, block->bin.pos,
cluster->timecode, block->duration,
is_keyframe, additional, block->additional_id,
block->additional.size, cluster->pos,
block->discard_padding);
......
}
说明
1. 关于side data
ffmpeg中mpegts.c的side data比较特别,调用顺序是:
handle_packets->handle_packet->mpegts_push_data->new_pes_packet->av_packet_new_side_data->av_packet_add_side_data
也就是说每一个pes包都要生成side data,这个side data用于保存当前pkt的ID信息:
sd = av_packet_new_side_data(pkt, AV_PKT_DATA_MPEGTS_STREAM_ID, 1);
if (!sd)
return AVERROR(ENOMEM);
*sd = pes->stream_id;
ts demux里面填入了当前pes的stream_id,比如0xe0这种。
2. side data不单单可以是codec data,还可以是AVPacketSideDataType的各种类型,比如DRM系统需要的初始化信息,存储IV信息等等。