@ltlovezh
        
        2020-09-20T10:23:05.000000Z
        字数 6257
        阅读 2588
    ffmpeg
在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中的哪个AVStreamint 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 unknownint64_t pos;} AVPacket;
几个关键点:
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 {// 具体数据和Sizeuint8_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;}
释放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_DURATIONFF_DISABLE_DEPRECATION_WARNINGSpkt->convergence_duration = 0;FF_ENABLE_DEPRECATION_WARNINGS#endifpkt->flags = 0;pkt->stream_index = 0;pkt->buf = NULL;pkt->side_data = NULL;pkt->side_data_elems = 0;}
把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;}
把AVPacket的普通变量设置为默认值,释放所有的side data,并且,释放AVBufferRef自身内存,减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBuffer
void av_packet_unref(AVPacket *pkt){// 删除所有的side dataav_packet_free_side_data(pkt);// 释放AVBufferRef自身内存,并且减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBufferav_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的函数都是针对这两部分操作: