FFmpeg内存模型与API介绍(notes 2)

从上图中可以看出 AVPacket 和 AVFrame 是存储音视频解码前后数据的重要结构体,我们使用 av_read_frame 将解封装后的数据存入 AVPacket,将 avcodec_receive_frame() 函数将解码后的数据存入AVFrame,这部分必定会涉及到内存的分配和释放问题。在 FFMpeg 中,内存 IO 叫做 buffered IO ,是指将一块内存缓冲区用作 FFmpeg 的输入或者输出,与内存 IO 操作对应的是指定 URL 作为 FFmpeg 的输入或输出。

假如现在需要将一个 Packet1 的数据拷贝到一个新的 Packet2 里面的,可以有两种方式:

  • (1)两个 Packet 的buf引用的是同一数据缓存空间。这时需要注意的是数据缓存空间的释放问题,(浅拷贝)

  • (2)两个Packetbuf引用不同的数据缓存空间。每个Packet都有数据缓存空间的copy(深拷贝)

我们主要是基于第一种方式进行介绍。

对于多个AVPacket共享同一个缓存空间,FFmpeg使用的引用计数的机制来管理。

  • AVBufferFFmpeg中的缓冲区,一开始时AVBuffer的引用计数(refcount)初始化为 0
  • 当有新的Packet引用共享的缓存空间时,就将引用计数再 +1
  • Packet释放掉对AVBuffer这块共享缓存空间的引用时,将引用计数 -1
  • 只有当refcount为 0 的时候,才会释放掉缓存空间AVBuffer

AVBuffer 和 AVBufferRef

我们首先来看以下这两个的源码:

  • (1)AVBuffer
struct AVBuffer {
    uint8_t *data; /**< data described by this buffer */ int size; /**< size of data in bytes */ /**  * number of existing AVBufferRef instances referring to this buffer  */ atomic_uint refcount; //引用此缓冲区的现有AVBufferRef实例的数目  /**  * a callback for freeing the data  */ void (*free)(void *opaque, uint8_t *data); /**  * an opaque pointer, to be used by the freeing callback  */ void *opaque; //一个不透明的指针,由释放回调函数使用  /**  * A combination of BUFFER_FLAG_*  */ int flags; };
  • (2)AVBufferRef
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.  */ //数据缓冲区。当且仅当这是对缓冲区的唯一引用时,才认为它是可写的,在这种情况下,av_buffer_is_writable()返回1。  uint8_t *data; /**  * Size of data in bytes.  */ int size; } AVBufferRef;

  • 这里的两个核心对象——AVBufferAVBufferRefVBuffer 表示数据缓冲区本身;它是不透明的(非常类似与private),不应被访问或由调用方(AVPacket/AVFrame)直接调用,只能通过AVBufferRef访问。
  • 但是,调用者可以通过比较两个AVBuffer指针,检查两个不同引用是否指向同一数据缓冲区。
  • AVBufferRef表示单个对AVBuffer的引用,它是一个可以由调用者直接调用的对象。

AVFrame也是采用同样的机制。

AVPacket 常用 API

  • AVPacket *av_packet_alloc(void);: 分配一个AVPacket,并将其字段设置为默认值。通过这个API分类的Packet必须由av_packet_free释放
  • void av_packet_free(AVPacket **pkt);: 释放掉Packet,如果这个Packet有引用的AVBuffer,将会先释放引用。
  • void av_init_packet(AVPacket *pkt);: 初始化Packet,注意,这并不涉及datasize成员,它们必须分别初始化。
  • int av_new_packet(AVPacket *pkt, int size);: 给AVPacket分配内存,这里引用计数将会+1
  • int av_packet_ref(AVPacket *dst, const AVPacket *src);:增加引用计数
  • void av_packet_unref(AVPacket *pkt);: 减少引用计数
  • void av_packet_move_ref(AVPacket *dst, AVPacket *src);: 将src中的每个字段移动到dst,并重置src
  • AVPacket *av_packet_clone(const AVPacket *src);: 克隆一个与src相同数据的Packet,等于av_packet_alloc()+av_packet_ref()

AVFrame 常用的 API

  • AVFrame *av_frame_alloc(void);: 分配一个AVFrame,并将其字段设置为默认值。通过这个API分类的Packet必须由av_frame_free()释放
  • void av_frame_free(AVFrame **frame);: 释放掉AVFrame,如果这个Frame有引用的AVBuffer,将会先释放引用。
  • int av_frame_ref(AVFrame *dst, const AVFrame *src): 增加引用计数
  • void av_frame_unref(AVFrame *frame);: 减少引用计数
  • void av_frame_move_ref(AVFrame *dst, AVFrame *src);: 将src中的每个字段移动到dst,并重置src
  • int av_frame_get_buffer(AVFrame *frame, int align);: 为音频或视频数据分配新的缓冲区。在调用这个字段之前必须在音视频帧上设置下面的字段
  • format (pixel[像素] format for video, sample format[采样格式]for audio)
  • width and height for video
  • nb_samples and channel_layout for audio

代码示例

示例 1

void av_packet_test1()
{
    AVPacket * pkt = NULL; int ret = 0; //分配一个AVPacket  pkt = av_packet_alloc(); //给AVPacket分配内存,这里引用计数将会+1 // int av_new_packet(AVPacket *pkt, int size);  ret = av_new_packet(pkt,MEM_ITEM_SIZE); //始化字段,还为data分配了存储空间 如果成功就返回0  /*  void * memccpy(void *dest, const void * src, int c, size_t n);  函数说明:memccpy()用来拷贝src 所指的内存内容前n 个字节到dest 所指的地址上。  * */ memccpy(pkt->data,(void *)&av_packet_test1,1,MEM_ITEM_SIZE); //减少引用计数,只有当引用计数为0时,才调用av_packet_free()时释放data的缓存。  av_packet_unref(pkt); //释放掉packet,如果packet被还引用计数,它将首先被取消引用。  av_packet_free(&pkt); }

通过调试可以发现, 在新建packet的时候buf是空的,只有当计数器置为1的时候才会给buf分配内存。当执行av_packet_unrefpktbuf就会置空,此时计数器 -1.

此外。通过查看源码可以发现其实av_packet_free内部已经调用了av_packet_unref,所以程序中第 28 行可以不调用,但是如果重复调用av_packet_unref也并不会出问题,av_packet_unref内部的av_buffer_unref函数中对buf进行了判断,如果buf已经为空就会直接return回去。

示例 2

void av_packet_test2()
{
    AVPacket *pkt = NULL; int ret = 0; pkt = av_packet_alloc(); ret = av_new_packet(pkt,MEM_ITEM_SIZE); memccpy(pkt->data,(void *)&av_packet_test2,1,MEM_ITEM_SIZE); // av_init_packet(pkt); //这个时候init就会导致内存无法释放  av_packet_free(&pkt); }

如果free之前调用了initinit会把pktbuf置空,free中也会调用init。(void av_init_packet仅仅是把pkt的参数设为默认值,要求pkt的内存已经分配好了,如果为NULL,则此处会崩溃)。

示例3

void av_frame_test1()
{
    AVFrame *frame = NULL; int ret = 0; //分配一个AVFrame  frame = av_frame_alloc(); /*在调用av_frame_get_buffer这个字段之前必须在音视频帧上设置下面的字段*/ frame->format = AV_SAMPLE_FMT_S16;// //AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16  frame->channel_layout = AV_CH_LAYOUT_STEREO;//单声道 //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO(立体声 双声道)  frame->nb_samples = 1024; //1024 * 2 * (16/8)  ret = av_frame_get_buffer(frame, 0); // 为音频或视频数据分配新的缓冲区  if(frame->buf && frame->buf[0]) printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size); //受frame->format等参数影响  //AV_SAMPLE_FMT_S16P格式 buf[1]才不为空  if(frame->buf && frame->buf[1]) printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size); //受frame->format等参数影响  if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针  printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0])); ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writable  printf("av_frame_make_writable ret = %d\n", ret); if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针  printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0])); av_frame_unref(frame); if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针  printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0])); av_frame_free(&frame); }

运行结果:

自己也在学习阶段,以上仅是自己的理解和总结,有不对的地方欢迎指正
from: https://zhuanlan.zhihu.com/p/349149227
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值