[关闭]
@ltlovezh 2020-09-20T18:23:05.000000Z 字数 6257 阅读 1772

FFmpeg之AVPacket

ffmpeg


在FFmpeg中,AVPacket主要存储编码数据,例如:H264、H265、AAC等。对于视频流,它通常应该包含一个编码帧;对于音频流,则可能包含多个音频帧。编码器可能输出空AVPacket,不包含编码数据,只包含边side data,例如:在编码结束时更新一些流参数。

AVPacket结构体如下所示:

  1. typedef struct AVPacket {
  2. /**
  3. * 若为空,则表示AVPacket未使用引用计数管理负载数据,否则指向存储负载数据的引用计数AVBufferRef -> AVBuffer
  4. * A reference to the reference-counted buffer where the packet data is
  5. * stored.
  6. * May be NULL, then the packet data is not reference-counted.
  7. */
  8. AVBufferRef *buf;
  9. /**
  10. * 基于AVStream->time_base的pts,若文件中没有,则为AV_NOPTS_VALUE。
  11. * pts必须大于等于dts,因为显示不能早于解码
  12. */
  13. int64_t pts;
  14. /**
  15. * 基于AVStream->time_base的dts,若文件中没有,则为AV_NOPTS_VALUE。
  16. */
  17. int64_t dts;
  18. // 负载数据
  19. uint8_t *data;
  20. // 负载数据的长度
  21. int size;
  22. // 属于AVFormatContext中的哪个AVStream
  23. int stream_index;
  24. /**
  25. * A combination of AV_PKT_FLAG values,AV_PKT_FLAG_KEY表示关键帧
  26. */
  27. int flags;
  28. /**
  29. * Additional packet data that can be provided by the container.
  30. * Packet can contain several types of side information.
  31. * 携带的不同类型的side data
  32. */
  33. AVPacketSideData *side_data;
  34. int side_data_elems;
  35. /**
  36. * Duration of this packet in AVStream->time_base units, 0 if unknown.
  37. * Equals next_pts - this_pts in presentation order.
  38. * 当前帧的持续时间,基于AVStream->time_base
  39. */
  40. int64_t duration;
  41. // byte position in stream, -1 if unknown
  42. int64_t pos;
  43. } AVPacket;

几个关键点:

  1. AVPacket的时间戳都是基于AVStream->time_base时间基准。
  2. dts是解码时间,pts是显示时间,pts必须大于等于dts,因为只有先解码,才能显示。
  3. AVPacket->buf决定了负载数据的管理方式,若为空,则表示AVPacket未使用引用计数管理负载数据,AVPacket->data就是负载数据;否则,则使用引用计数管理负载数据,AVPacket->buf引用AVBufferRef,AVBufferRef引用AVBuffer,AVBuffer存储了负载数据,关于引用计数下面👇详细介绍。

side_data和side_data_elems

AVPacket->side_data是AVPacket携带的side数据数组,AVPacket->side_data_elems是数组的长度。av_packet_new_side_dataav_packet_add_side_data函数都提供了向AVPacket添加指定类型side data的能力,只是参数略有差异,每次都会把新side data添加到数组的尾部。
av_packet_get_side_data函数提供了从AVPacket获取指定类型side data的能力。

  1. typedef struct AVPacketSideData {
  2. uint8_t *data;
  3. int size;
  4. // side data的类型
  5. enum AVPacketSideDataType type;
  6. } AVPacketSideData;

针对AVPacket的函数

FFmpeg提供了很多函数操作AVPacket,其中主要需要关注的是不同函数对AVPacket引用计数的影响。首先看下与引用计数相关的AVBufferRef和AVBuffer结构体:

  1. typedef struct AVBufferRef {
  2. //
  3. AVBuffer *buffer;
  4. /**
  5. * The data buffer. It is considered writable if and only if
  6. * this is the only reference to the buffer, in which case
  7. * av_buffer_is_writable() returns 1.
  8. */
  9. uint8_t *data;
  10. /**
  11. * Size of data in bytes.
  12. */
  13. int size;
  14. } AVBufferRef;
  15. struct AVBuffer {
  16. // 具体数据和Size
  17. uint8_t *data; /**< data described by this buffer */
  18. int size; /**< size of data in bytes */
  19. /**
  20. * 引用当前AVBuffer的AVBufferRef实例数,即引用计数
  21. * number of existing AVBufferRef instances referring to this buffer
  22. */
  23. atomic_uint refcount;
  24. /**
  25. * a callback for freeing the data,释放AVBuffer时,会通过该接口释放data数据
  26. */
  27. void (*free)(void *opaque, uint8_t *data);
  28. /**
  29. * an opaque pointer, to be used by the freeing callback,调用free函数的参数
  30. */
  31. void *opaque;
  32. /**
  33. * A combination of BUFFER_FLAG_*
  34. */
  35. int flags;
  36. };

AVBufferRef引用AVBuffer,每增加一个指向同一个AVBuffer的AVBufferRef,AVBuffer中的引用计数计数就加1;相反,每减少一个指向AVBuffer的AVBufferRef,AVBuffer中的引用计数就减1,当引用计数等于0时,就是释放AVBuffer.data以及AVBuffer本身,类似于C++的智能指针。
AVPacket引用计数

av_packet_alloc

分配并返回AVPacket,所有参数都是默认值,此时并没有分配AVPacket->buf,因为AVPacket还没有包含有效负载数据。

  1. AVPacket *av_packet_alloc(void)
  2. {
  3. AVPacket *pkt = av_mallocz(sizeof(AVPacket));
  4. if (!pkt)
  5. return pkt;
  6. // 减少pkt指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。同时,释放side_data,其他参数则reset默认值。
  7. av_packet_unref(pkt);
  8. return pkt;
  9. }

av_packet_free

释放AVPacket自身内存和AVBufferRef自身内存,并且减少AVBufferRef指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。

  1. void av_packet_free(AVPacket **pkt)
  2. {
  3. if (!pkt || !*pkt)
  4. return;
  5. // 减少pkt指向的AVBuffer的引用计数,若计数等于0则释放AVBuffer。同时,释放side_data,其他参数则reset默认值。
  6. av_packet_unref(*pkt);
  7. av_freep(pkt);
  8. }

av_new_packet

为已有的AVPacket分配负载数据,设置引用计数,并且通过av_init_packet函数把其他参数设置为默认值。

  1. int av_new_packet(AVPacket *pkt, int size)
  2. {
  3. AVBufferRef *buf = NULL;
  4. // 分配负载数据,设置引用计数
  5. int ret = packet_alloc(&buf, size);
  6. if (ret < 0)
  7. return ret;
  8. // 设置默认值
  9. av_init_packet(pkt);
  10. // 赋值引用计数AVBufferRef和负载数据
  11. pkt->buf = buf;
  12. pkt->data = buf->data;
  13. pkt->size = size;
  14. return 0;
  15. }

av_init_packet

把AVPacket的字段设置初始化为默认值,但是并不会为AVPacket->data和AVPacket->size设置值,因为此时还没有包含负载数据。

  1. void av_init_packet(AVPacket *pkt)
  2. {
  3. pkt->pts = AV_NOPTS_VALUE;
  4. pkt->dts = AV_NOPTS_VALUE;
  5. pkt->pos = -1;
  6. pkt->duration = 0;
  7. #if FF_API_CONVERGENCE_DURATION
  8. FF_DISABLE_DEPRECATION_WARNINGS
  9. pkt->convergence_duration = 0;
  10. FF_ENABLE_DEPRECATION_WARNINGS
  11. #endif
  12. pkt->flags = 0;
  13. pkt->stream_index = 0;
  14. pkt->buf = NULL;
  15. pkt->side_data = NULL;
  16. pkt->side_data_elems = 0;
  17. }

av_packet_ref

把src的数据copy到dst,包含两部分:

  1. 普通字段直接通过av_packet_copy_props函数copy,例如:side data、pts等
  2. 负载数据:为dst AVPacket分配AVBufferRef,指向src的AVBuffer,两个AVPacket共享负载数据AVBuffer,增加了AVBuffer的引用计数。
  1. int av_packet_ref(AVPacket *dst, const AVPacket *src)
  2. {
  3. int ret;
  4. // 把src的参数copy到dst,不包含负载数据
  5. ret = av_packet_copy_props(dst, src);
  6. if (ret < 0)
  7. return ret;
  8. if (!src->buf) { // 若src不是引用计数,则为dst创建AVBufferRef,初始化引用计数为1,并且把src的负载数据(AVPacket->data)copy到AVBuffer中
  9. ret = packet_alloc(&dst->buf, src->size);
  10. if (ret < 0)
  11. goto fail;
  12. // copy有效负载数据
  13. if (src->size)
  14. memcpy(dst->buf->data, src->data, src->size);
  15. dst->data = dst->buf->data;
  16. } else { // 若src已经是引用计数,则基于src->buf创建并返回AVBufferRef,并增加AVBuffer的引用计数
  17. dst->buf = av_buffer_ref(src->buf);
  18. if (!dst->buf) {
  19. ret = AVERROR(ENOMEM);
  20. goto fail;
  21. }
  22. dst->data = src->data;
  23. }
  24. dst->size = src->size;
  25. return 0;
  26. fail:
  27. av_packet_free_side_data(dst);
  28. return ret;
  29. }

av_packet_unref

把AVPacket的普通变量设置为默认值,释放所有的side data,并且,释放AVBufferRef自身内存,减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBuffer

  1. void av_packet_unref(AVPacket *pkt)
  2. {
  3. // 删除所有的side data
  4. av_packet_free_side_data(pkt);
  5. // 释放AVBufferRef自身内存,并且减少AVBufferRef指向的AVBuffer的引用计数,若引用计数等于0,则释放AVBuffer
  6. av_buffer_unref(&pkt->buf);
  7. // 把AVPacket的其他变量设置为默认值
  8. av_init_packet(pkt);
  9. pkt->data = NULL;
  10. pkt->size = 0;
  11. }

av_packet_move_ref

把scr的所有字段move到dst,并把src的字段重置为默认值,此时AVPacket对AVBuffer的引用计数并不会变。

  1. void av_packet_move_ref(AVPacket *dst, AVPacket *src)
  2. {
  3. *dst = *src;
  4. // 重置src AVPacket的所有字段
  5. av_init_packet(src);
  6. src->data = NULL;
  7. src->size = 0;
  8. }

av_packet_clone

首先通过av_packet_alloc创建一个AVPacket,然后使用av_packet_ref把src AVPacket copy到新创建的AVPacket。

  1. AVPacket *av_packet_clone(const AVPacket *src)
  2. {
  3. AVPacket *ret = av_packet_alloc();
  4. if (!ret)
  5. return ret;
  6. if (av_packet_ref(ret, src))
  7. av_packet_free(&ret);
  8. return ret;
  9. }

总结

AVPacket的字段包含两部分:

  1. 普通字段:除AVPacket->data、AVPacket->size和AVPacket->buf之外的所有字段,这些字段依附于AVPacket自身,通过av_init_packet重置为默认值。
  2. 负载数据:AVPacket->data、AVPacket->size和AVPacket->buf,负载数据需要单独创建(av_new_packet),通过引用计数进行管理。

所有关于AVPacket的函数都是针对这两部分操作:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注