ffmpeg简单分析系列--内存管理(AVBuffer)

ffmpeg简单分析系列–内存管理(AVBuffer)

  • 本文基于ffmpeg4.2进行说明
  • libavutil/buffer.h此头文件主要是ffmpeg缓存数据的主要接口
  • ffmpeg的内存管理的核心主要包含以下几个数据结构:AVBuffer,AVBufferRef,BufferPoolEntry ,AVBufferPool
  • 其中AVBuffer是最基础也是最核心的,用于存放真正的数据以及释放数据
  • 但AVBuffer对外不公开,要通过AVBufferRef来间接使用AVBuffer
  • BufferPoolEntry可以说是一个链表,是配合缓冲池AVBufferPool 的一个重要的数据结构
  • 当完全弄懂AVBufferPool之后就会惊叹其设计的简约而不简单的巧妙构造

AVBuffer

  • AVBuffer位于libavutil\buffer_internal.h,是ffmpeg内部接口,非对外公开,其定义如下
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;

    /**
     * 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;
};
  • data和size就不用多说了,就是实际存放数据的指针和大小;
  • refcount是引用计数
  • 中间的回调函数是用于释放数据的,就是当refcount变为0时,就会回调这个函数去释放占用的内存,ffmepg默认使用av_buffer_default_free这个,其内部使用av_free来释放内存
  • opaque就是用户自定义的需要传递给回调函数的(对应回调函数的opaque参数);
  • flags标识一些属性,主要有BUFFER_FLAG_READONLY和BUFFER_FLAG_REALLOCATABLE两个,BUFFER_FLAG_READONLY表示只读,当引用计数为1时表示只有一个对象引用它,此时它是可写的,否则它就是只读的
  • AVBuffer是FFMPEG内存管理的基石,深入理解AVBuffer的实现机制有助于后续FFMPEG源码的研读
  • 既然AVBuffer是内部的,我们无法直接使用它,那么通过谁来间接使用它呢,答案就是AVBufferRef

AVBufferRef

/**
 * 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;
  • 以上的AVBufferRef.data和size与AVBuffer.data和size是一样的
  • 有两种方式创建AVBufferRef,一种是通过av_buffer_alloc,只要指定大小即可;另一种是通过av_buffer_create,这种主要是用于已经有了数据的情况,同时它也支持自定义释放此数据内存的方法
  • AVBuffer在创建的时候引用计数为1,当调用av_buffer_ref()对其进行操作时,引用计数+1,当av_buffer_unref对其操作时则引用计数-1(当-1后引用计数为0时,av_buffer_unref将自动释放分配的数据缓存
  • 当AVBufferRef指向的AVBuffer的引用计数为1时,它是可写的,否则就是只读的,然而我们无法访问AVBuffer,因此可以通过av_buffer_is_writable判断
    AVBufferRef指向的AVBuffer是否可写,通过av_buffer_get_ref_count知道AVBufferRef指向的AVBuffer的引用计数等等,更多的api都在libavutil/buffer.h中

AVBufferPool

typedef struct BufferPoolEntry {
    uint8_t *data;

    /*
     * Backups of the original opaque/free of the AVBuffer corresponding to
     * data. They will be used to free the buffer when the pool is freed.
     */
    void *opaque;
    void (*free)(void *opaque, uint8_t *data);

    AVBufferPool *pool;
    struct BufferPoolEntry *next;
} BufferPoolEntry;

struct AVBufferPool {
    AVMutex mutex;
    BufferPoolEntry *pool;

    /*
     * This is used to track when the pool is to be freed.
     * The pointer to the pool itself held by the caller is considered to
     * be one reference. Each buffer requested by the caller increases refcount
     * by one, returning the buffer to the pool decreases it by one.
     * refcount reaches zero when the buffer has been uninited AND all the
     * buffers have been released, then it's safe to free the pool and all
     * the buffers in it.
     */
    atomic_uint refcount;

    int size;
    void *opaque;
    AVBufferRef* (*alloc)(int size);
    AVBufferRef* (*alloc2)(void *opaque, int size);
    void         (*pool_free)(void *opaque);
};
  • 以上的BufferPoolEntry的opaque、data、free都是指向了AVBuffer最开始的opaque、data、free,然后AVBuffer自己的opaque和free则指向了BufferPoolEntry和pool_release_buffer
  • 为何这样设计呢,因为AVBuffer最终要进行释放的话,那还是得调用它自己最本来的free函数,但是此时由于要放到缓存池管理,因此在free时不能真正把AVBuffer释放了,因此用BufferPoolEntry来保存AVBuffer真正的释放内存的函数,然后再用缓存池释放函数pool_release_buffer来代替free函数,这样,当用户释放的时候,AVBuffer的free函数已经指向了pool_release_buffer函数,因此可以在pool_release_buffer里把AVBuffer返回给缓存池,等到缓存池自己想要被释放的时候,这个时候缓存池就从BufferPoolEntry把之前保存的真正释放AVBuffer的函数取出来,进行调用;不得不说,这个设计虽然非常绕,但确实精妙
  • AVBufferPool.size指的不是这个缓冲池有多少个AVBuffer,而是指AVBuffer.size
  • AVBufferPool在初始化时AVBufferPool.pool是空的
  • av_buffer_pool_get()会判断是否有AVBufferPool.pool,有则取出使用后AVBufferPool.pool指向下一个,无则创建一个新的BufferPoolEntry,BufferPoolEntry.pool都会指向创建他的AVBufferPool
  • 这里有点绕,其实就是AVBufferPool指向了BufferPoolEntry链表的头,这样AVBufferPool就能管理整条链了,而每个BufferPoolEntry都指向了AVBufferPool,也就是说每个BufferPoolEntry都能找到他的管理者AVBufferPool
  • pool_alloc_buffer() 就是创建一个BufferPoolEntry并指向AVBufferPool,并利用AVBufferPool创建一个AVBufferRef,然后BufferPoolEntry.data指向这个刚创建的AVBufferRef.buffer.data并返回这个AVBufferRef
  • av_buffer_pool_init()时会将AVBufferPool.refcount置为1
  • av_buffer_pool_uninit()会将AVBufferPool.refcount减1,减1后如果等于1,那就会调用buffer_pool_free()进行释放
  • buffer_pool_free()内部会找出AVBufferPool的整个链条,从链头开始进行逐一释放内存,最后将自身也释放并置null

在这里插入图片描述

在这里插入图片描述

  • 从图中可以看出,每个BufferPoolEntry都保存了指向AVBufferPool的指针
  • BufferPoolEntry又是一个链表,都保存了指向下一个BufferPoolEntry的地址
  • 而AVBufferPool则保存了指向BufferPoolEntry链表的表头
  • BufferPoolEntry虽然没有保存AVBufferRef,但通过AVBufferRef间接保存了AVBuffer的data,free(),opaque,然后AVBuffer自己的opaque转而指向BufferPoolEntry,free则转而指向pool_release_buffer()这个函数
  • 这里要理解的就是AVBuffer和BufferPoolEntry的数据其实各自做了一定程度的交换,这里的设计非常巧妙,一时搞不懂建议自己再仔细推敲一下
  • AVBuffer本来的默认释放函数是av_buffer_default_free(),这个函数是真正释放内存的
  • 通过AVBufferPool之后,AVBuffer本来的av_buffer_default_free()会被保存到BufferPoolEntry里,然后被替换为pool_release_buffer(),这样当我们调用AVBuffer的free()函数时候,实际上他调到的是pool_release_buffer(),pool_release_buffer()会重新利用AVBuffer,将其放入缓存池继续等待使用,只有当缓冲池要被释放的时候,缓存池AVBufferPool由于保存了BufferPoolEntry,这个BufferPoolEntry是整个链表的头,通过这个头,其他BufferPoolEntry也就能遍历出来了,而前面说过BufferPoolEntry保存了AVBuffer的真正释放内存的函数av_buffer_default_free()的指针,通过它真正释放内存
  • 普通的AVBuffer平时释放通过av_buffer_default_free即可,但是交给AVBufferPool后,释放就交给pool_release_buffer函数了
  • AVBufferPool存放了链表的头指针,比如链表为3->2->1,那么AVBufferPool就指向了表头3,当需要的时候就从中取出这个链表的头3,然后AVBufferPool指向2,此时AVBufferPool里的AVBufferPool链表就变成2->1了,当3使用完毕,进行释放的时候,此时并不会进行真正的内存释放,而是巧妙地利用了AVBuffer结构的释放函数,将3重新接回表头,变成3->2->1
  • 不得不说AVBuffer的释放函数设计得非常精巧,如果没有加入缓存池,AVBuffer的释放就是真正的内存释放,如果加入缓存池,释放后又可以回到缓存池继续等待他人使用(此时AVBuffer内存还在)

参考

  • 深入理解FFMPEG-AVBuffer/AVBufferRef/AVBufferPool_移动开发_muyuyuzhong的专栏-CSDN博客 https://blog.csdn.net/muyuyuzhong/article/details/79381152?utm_source=blogxgwz2
  • FFmpeg视频播放的内存管理 - 简书 https://www.jianshu.com/p/9f45d283d904
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值