在工作中用到ffmpeg推流,使用过程中发现AVPacket这个结构体比较特殊,现在记录下来,备忘录。
该结构体如下:
//该结构在libavcodec中
typedef struct AVPacket {/*** 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;/*** Presentation timestamp in AVStream->time_base units; the time at which* the decompressed packet will be presented to the user.* Can be AV_NOPTS_VALUE if it is not stored in the file.* pts MUST be larger or equal to dts as presentation cannot happen before* decompression, unless one wants to view hex dumps. Some formats misuse* the terms dts and pts/cts to mean something different. Such timestamps* must be converted to true pts/dts before they are stored in AVPacket.*/int64_t pts;/*** Decompression timestamp in AVStream->time_base units; the time at which* the packet is decompressed.* Can be AV_NOPTS_VALUE if it is not stored in the file.*/int64_t dts;uint8_t *data;int size;int stream_index;/*** A combination of AV_PKT_FLAG values*/int flags;/*** Additional packet data that can be provided by the container.* Packet can contain several types of side information.*/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.*/int64_t duration;int64_t pos; ///
#endif
} AVPacket;/*** A reference to a data buffer.** The size of this struct is not a part of the public ABI and it is not meant* to be allocated directly.*/
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;
其中,每个字段具体意思可以查看其它文章,本问主要介绍该结构体的其它几个函数:
AVPacket *av_packet_alloc(void);
void av_init_packet(AVPacket *pkt);
void av_packet_free(AVPacket **pkt)
一般来讲,该结构体我们都是定义为指针,然后通过如下定义:
AVPacket *pkt=av_packet_alloc();
av_init_packet(pkt);
//use pkt read(pkt)
av_read_frame(avformatCtx, pkt);
av_packet_free(&pkt);
以前一直以为av_packet_alloc()是为其内部的指针分配内存,不然直接用AVPacket pkt这样多好,后来看了源码发现自己错了,请看源码:
AVPacket *av_packet_alloc(void)
{//这里仅仅是给结构体本身分配了空间,并没有给其内部存储数据的指针分配空间AVPacket *pkt = av_mallocz(sizeof(AVPacket));if (!pkt)return pkt;//可以看到这里它也调用了初始化,所以我们调用了alloc就不要init了av_init_packet(pkt);return pkt;
}
//这个函数跟上面的区别就在这里要传入size大小,这个size就是pkt内部的size大小,
//这个函数可以用来深拷贝另外一个pkt,特别强调的一点,其实所有的包数据都是存储在
//buf中的,data指针指向了buff的data。
int av_new_packet(AVPacket *pkt, int size)
{AVBufferRef *buf &#61; NULL;int ret &#61; packet_alloc(&buf, size);if (ret < 0)return ret;av_init_packet(pkt);pkt->buf &#61; buf;pkt->data &#61; buf->data;//指向了bufpkt->size &#61; size;return 0;
}
void av_init_packet(AVPacket *pkt)
{pkt->pts &#61; AV_NOPTS_VALUE;pkt->dts &#61; AV_NOPTS_VALUE;pkt->pos &#61; -1;pkt->duration &#61; 0;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGSpkt->convergence_duration &#61; 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endifpkt->flags &#61; 0;pkt->stream_index &#61; 0;pkt->buf &#61; NULL;pkt->side_data &#61; NULL;pkt->side_data_elems &#61; 0;
}
然后我们来看av_packet_free&#xff1a;
void av_packet_free(AVPacket **pkt)
{if (!pkt || !*pkt)return;av_packet_unref(*pkt);av_freep(pkt);
}
源码如上&#xff0c;我们注意上面传入了二级指针&#xff0c;并且用到了
av_packet_unref(*pkt);
av_freep(pkt);
这里才是今天我们的重点&#xff0c;我们知道av_read_frame(avformatCtx, pkt)&#xff0c;读到数据包的时候就为其内部数据指针分配了空间并且存储了数据&#xff0c;我的问题是&#xff1a;我们如何拷贝一份avpacket&#xff0c;怎么样才算深拷贝&#xff1f;拷贝了怎么释放&#xff1f;
int av_copy_packet(AVPacket *dst, const AVPacket *src);
int av_packet_ref(AVPacket *dst, const AVPacket *src);
//void av_packet_unref(AVPacket *pkt);
AVPacket *av_packet_clone(const AVPacket *src);
这三个函数之间有上面区别&#xff0c;我们使用的时候到底用那个才合适&#xff0c;下面我们意义探讨&#xff1a;
int av_copy_packet(AVPacket *dst, const AVPacket *src)
{//这里我们注意&#xff0c;内容浅拷贝&#xff0c;dst在外部一定要分配好外壳内存*dst &#61; *src;return copy_packet_data(dst, src, 0);
}
/* Makes duplicates of data, side_data, but does not copy any other fields */
static int copy_packet_data(AVPacket *pkt, const AVPacket *src, int dup)
{pkt->data &#61; NULL;pkt->side_data &#61; NULL;pkt->side_data_elems &#61; 0;if (pkt->buf) {//仅仅是buf的引用&#xff0c;难道这里用到了类似智能指针的技术引用&#xff1f;AVBufferRef *ref &#61; av_buffer_ref(src->buf);if (!ref)return AVERROR(ENOMEM);pkt->buf &#61; ref;pkt->data &#61; ref->data;} else {DUP_DATA(pkt->data, src->data, pkt->size, 1, ALLOC_BUF);}if (src->side_data_elems && dup) {pkt->side_data &#61; src->side_data;pkt->side_data_elems &#61; src->side_data_elems;}if (src->side_data_elems && !dup) {return av_copy_packet_side_data(pkt, src);}return 0;failed_alloc:av_packet_unref(pkt);return AVERROR(ENOMEM);
}AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{AVBufferRef *ret &#61; av_mallocz(sizeof(*ret));if (!ret)return NULL;*ret &#61; *buf;//这里也是结构体//这里进行了原子操作&#43;1atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);return ret;
}
看到这里我们大致清楚了&#xff0c;av_copy_packet&#xff1a;
1、首先dst要有一个外壳内存&#xff08;这个指针一定调用了av_packet_alloc&#xff08;&#xff09;&#xff09;&#xff1b;
2、拷贝后&#xff0c;在堆区分配了AVBufferRef *ret &#61; av_mallocz(sizeof(*ret));字节大小的空间&#xff1b;
3、直接将src的ref浅拷贝给它 *ret &#61; *buf;
综上所述&#xff0c;也就是拷贝过程中我们并没有真真的区拷贝数据&#xff0c;而是只分配了一个AVBufferRef大小的结构体&#xff0c;然后让它去浅拷贝了
src的buf&#xff0c;然后将src->buf中的引用计数增加了1&#xff0c;妥妥的智能指针啊&#xff0c;与时俱进
关于怎么释放&#xff0c;我们最后看&#xff0c;接下来我们看下一个函数&#xff1a;int av_packet_ref(AVPacket *dst, const AVPacket *src);
//
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{int ret;dst->buf &#61; NULL;//该函数源码见下面&#xff0c;除了side_data分配了空间外&#xff0c;其余的全部是标准拷贝//从这里我们也可以看出dst一定的先调用av_packet_alloc&#xff08;&#xff09;分配外壳内存ret &#61; av_packet_copy_props(dst, src);if (ret < 0)goto fail;//这里比较特殊&#xff0c;一般来说我们av_read_frame(avformatCtx, pkt);后数据内存//看下面的这张图&#xff0c;一般这个用不到//只要读到包&#xff0c;里面的数据就存在buf中if (!src->buf) {ret &#61; packet_alloc(&dst->buf, src->size);if (ret < 0)goto fail;av_assert1(!src->size || src->data);if (src->size)memcpy(dst->buf->data, src->data, src->size);dst->data &#61; dst->buf->data;} //直接考虑这个//这里也仅仅是用了一次引用计数&#xff0c;具体看上面分析&#xff0c;看到这里恍然大悟&#xff0c;AVPacket的拷贝是通过类似智能指针的方式去//引用的。else {dst->buf &#61; av_buffer_ref(src->buf);if (!dst->buf) {ret &#61; AVERROR(ENOMEM);goto fail;}dst->data &#61; src->data;}dst->size &#61; src->size;return 0;
fail:av_packet_unref(dst);return ret;
}int av_packet_copy_props(AVPacket *dst, const AVPacket *src)
{int i;dst->pts &#61; src->pts;dst->dts &#61; src->dts;dst->pos &#61; src->pos;dst->duration &#61; src->duration;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGSdst->convergence_duration &#61; src->convergence_duration;
FF_ENABLE_DEPRECATION_WARNINGS
#endifdst->flags &#61; src->flags;dst->stream_index &#61; src->stream_index;dst->side_data &#61; NULL;dst->side_data_elems &#61; 0;for (i &#61; 0; i < src->side_data_elems; i&#43;&#43;) {enum AVPacketSideDataType type &#61; src->side_data[i].type;int size &#61; src->side_data[i].size;uint8_t *src_data &#61; src->side_data[i].data;uint8_t *dst_data &#61; av_packet_new_side_data(dst, type, size);if (!dst_data) {av_packet_free_side_data(dst);return AVERROR(ENOMEM);}memcpy(dst_data, src_data, size);}return 0;
}
av_read_frame(avformatCtx, pkt);后数据内存结构如下&#xff0c;data里面其实什么都没有&#xff0c;空空如也
关于AVPacket *av_packet_clone(const AVPacket *src)如下&#xff1a;
//与其余两个区别仅仅是dst不用分配外壳
AVPacket *av_packet_clone(const AVPacket *src)
{AVPacket *ret &#61; av_packet_alloc();if (!ret)return ret;if (av_packet_ref(ret, src))av_packet_free(&ret);return ret;
}
我们先总结下这三个区别&#xff1a;
int av_copy_packet(AVPacket *dst, const AVPacket *src);
//1、分配外壳
//2、不拷贝side_data数据&#xff0c;分配buf在堆区&#xff0c;具体内容引用
int av_packet_ref(AVPacket *dst, const AVPacket *src);
//1、分配外壳
//2、拷贝side_data数据&#xff0c;分配buf在堆区&#xff0c;具体内容引用
//void av_packet_unref(AVPacket *pkt);
AVPacket *av_packet_clone(const AVPacket *src);
//1、不分配外壳
//2、其余跟av_packet_ref一致。
最后我们来看void av_packet_unref(AVPacket *pkt)&#xff1a;
void av_packet_unref(AVPacket *pkt)
{//释放side_data内存av_packet_free_side_data(pkt);//释放前面讲的堆区buf&#xff0c;当计数为0的时候释放真正的数据内存av_buffer_unref(&pkt->buf);av_init_packet(pkt);pkt->data &#61; NULL;pkt->size &#61; 0;
}
void av_packet_free_side_data(AVPacket *pkt)
{int i;for (i &#61; 0; i < pkt->side_data_elems; i&#43;&#43;)av_freep(&pkt->side_data[i].data);av_freep(&pkt->side_data);pkt->side_data_elems &#61; 0;
}
void av_buffer_unref(AVBufferRef **buf)
{if (!buf || !*buf)return;buffer_replace(buf, NULL);
}
static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{AVBuffer *b;b &#61; (*dst)->buffer;if (src) {**dst &#61; **src;av_freep(src);} elseav_freep(dst);if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) &#61;&#61; 1) {b->free(b->opaque, b->data);av_freep(&b);}
}
讲到这里&#xff0c;就结束了&#xff0c;对AVPacket的拷贝赋值就不会一脸懵逼了吧。