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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章