深入理解FFMPEG-AVBuffer/AVBufferRef/AVBufferPool

[toc]
想学习音视频以及服务器开发的朋友可以去看看 腾讯课堂 动脑学院的免费公开课
C/C++ linux 大型互联网架构师课程【动脑学院】 https://ke.qq.com/course/131973

FFMPEG中众多数据的存储都是以AVBuffer/AVBufferRef为基础,深入理解其运行机制对后续源码的研读非常有帮助,比如非常关键的AVPacket和AVFrame结构体。

结构体分析

在FFMPEG的位置libavutil\buffer_internal.h(对于调用者是隐藏的)和 libavutil\buffer.h(对于调用者是公开的)

AVBuffer是一个具有引用计数的数据缓存,FFMPEG提供了一套完善的API对AVBuffer进行操作。

AVBuffer和AVBufferRef是API的两个核心结构体。

  • AVBuffer代表数据缓存本身,但一般情况下调用者不应该直接调用它,而是需要通过AVBufferRef的调用,让API内部对AVBuffer进行操作。当然了,调用者可以检测两个AVBuffer的指针,对比其是否指向同一个AVBuffer对象。
  • AVBufferRef 则是直接面对调用者,调用者可以直接操作它。

有两种方法来分配AVBuffer,一种是av_buffer_alloc(),另一种是av_buffer_create(),av_buffer_create()可以自定义内存的释放方法,具体见API分析。
AVBuffer在创建的时候引用计数为1,当调用av_buffer_ref()对其进行操作时,引用计数+1,当av_buffer_unref() 对其操作时则引用计数-1(当-1后引用计数为0时,av_buffer_unref()将自动释放分配的数据缓存)。

一个AVBuffer是否可写,通过av_buffer_is_writable()来判断。只有引用计数为1的情况下,AVBuffer才处于可写状态。
当一个AVBuffer为只读时(不可写),你可以通过av_buffer_make_writable()来让其可写,该函数内部内部自动创建一个新的数据缓冲区,并且原来的AVBuffer引用计数-1.


- AVBuffer引用计数+1和-1的操作是线程安全的,调用者不需要额外的加锁机制。
- 不同的AVBufferRef对同一AVBuffer的操作是平等的,不会因为谁先谁后产生而有差异。

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;

    // a callback for freeing the data 释放数据的回调函数
    void (*free)(void *opaque, uint8_t *data);

    /* an opaque pointer, to be used by the freeing callback 隐性指针, 供释放数据的回调函数使用,pool_alloc_buffer就使用了该参数 */
    void *opaque;

    // A combination of BUFFER_FLAG_ 标记该buffer是否为总是只读
    int flags;
};

AVBuffer是FFMPEG内存管理的基石,深入理解AVBuffer的实现机制有助于后续FFMPEG源码的研读。

AVBuffer使用了引用计数来管理数据缓存

  • 什么是引用计数
    引用计数是内存管理的一个技巧,可以看做是一种简单的垃圾回收机制,它允许多个拥有共同值的对象共享同一个对象。
  • 为什么要使用引用计数
    • 对于没有垃圾自动回收机制的语言(如C/C++)和技术(如COM)来说,引用计数提供了一种简洁高效的内存管理方法。
      它简化了跟踪处理堆中对象的过程。
    • 一个对象被从堆中分配出来之后,我们需要明确的知道是谁拥有了这个对象,因为只有拥有这个对象的所有者能够销毁它。但我们在实际使用过程中, 这个对象可能被传递给另一个对象(例如通过传递指针参数),一旦这个过程复杂,我们很难确定谁最后拥有了这个对象。
    • 使用引用计数就可以抛开这个问题,我们不需要再去关心谁拥有了这个对象,因为我们把管理权交割给了对象自己(这有点类似__stdcall和__cdecl两种函数调用约定)。当这个对象不再被任何人使用时,它自己负责销毁自己。
    • 简单并且高效。简单是特定时候增减一下对象的引用计数就可以了;高效是让多个具有相同值的对象共享同一个对象,省却了拷贝复值的过程,而且更加节省内存和宝贵的CPU时间。

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() * 判断为可读是才能通过该data指针来写入数据 */
    uint8_t *data;        // 和 buffer->data指向同样的内存,

    // Size of data in bytes.
    int      size;            // 数据长度 buffer->size一致
} AVBufferRef;

调用者可以通过API来操作AVBufferRef 。

AVBufferPool

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.
    * 当调用者调用一次av_buffer_pool_get时,其引用计数+1,当调用者调用av_buffer_unref
    * 释放分配的AVBufferRef时,其引用计数-1
    * av_buffer_unref -> buffer_replace -> AVBuffer->free -> pool_release_buffer , 
    * pool_release_buffer 将其引用计数-1
    */
    atomic_uint refcount;         

    int size;                               // 每个Buffer的数据长度
    void *opaque;
    AVBufferRef* (*alloc)(int size); AVBufferRef* (*alloc2)(void *opaque, int size); void (*pool_free)(void *opaque); };

API分析

这里分为两部分进行分析,一个是对AVBufferRef的操作,一个是对AVBufferPool的操作。

AVBufferRef相关API分析

/** * 根据指定的数据大小使用av_malloc分配一个AVBuffer * * @成功时返回一个包含指定数据大小的AVBufferRef, 返回NULL则说明分配失败。 */
AVBufferRef *av_buffer_alloc(int size);

/** * 功能和av_buffer_alloc一样,但其在返回的时候将数据缓冲区初始化为0。 */
AVBufferRef *av_buffer_allocz(int size);

/** * 该标志将缓存数据强制标记为只读,即使它引用计数为1。 */
#define AV_BUFFER_FLAG_READONLY (1 << 0)

/** * 通过已存在的数据缓存区创建一个AVBuffer. * * 当该函数调用成功时,AVBuffer拥有传入的数据的操作所有权, 调用者只能通过AVBufferRef 对象对数据进行操作, * 不能直接再通过指针直接操作传入的数据。 * 如果函数调用失败,则不能使用AVBufferRef来操作,即是av_buffer_create与传入的数据没有产生关联。 * @param data 数据指针 * @param size 数据长度(字节为定位) * @param free 释放缓存数据的回调函数,当为NULL时,API内部默认使用av_buffer_default_free进行释放 * @param opaque 供释放数据的回调函数使用 * @param flags AV_BUFFER_FLAG_ 数据缓冲区的读写标志,当设置为AV_BUFFER_FLAG_READONLY时数据缓冲区 * 处于只读状态。 * * @成功时返回指向data的AVBufferRef,返回 NULL则失败. */
AVBufferRef *av_buffer_create(uint8_t *data, int size,
                              void (*free)(void *opaque, uint8_t *data),
                              void *opaque, int flags);

/** * 缺省的用于数据释放的回调函数, 其使用av_free() 释放数据. * 这是一个回调函数,不能直接调用。 */
void av_buffer_default_free(void *opaque, uint8_t *data);

/** * 新创建一个指向同一AVBuffer的引用参考. * * @成功时返回和buf指向同一AVBuffer 的AVBufferRef,返回NULL 则失败 */
AVBufferRef *av_buffer_ref(AVBufferRef *buf);

/** * 释放参考引用计数,且当AVBuffer没有其他AVBufferRef时,则释放自动 AVBuffer本身, * 即是其占用的内存将会被释放 * * @param buf 指向AVBuffer的参考对象,函数返回时其被置为NULL。 */
void av_buffer_unref(AVBufferRef **buf);

/** * 检测AVBuffer是否为可写 * @return 1 ,AVBuffer处于可写状态,说明只有一个AVBufferRef 引用了它 * Return 0 则说明不能写入数据. */
int av_buffer_is_writable(const AVBufferRef *buf);

/** * @return 返回被av_buffer_create设置的opaque(隐形指针)参数 . */
void *av_buffer_get_opaque(const AVBufferRef *buf);

/** * 返回buf指向的AVBuffer的引用计数 */
int av_buffer_get_ref_count(const AVBufferRef *buf);

/** * 将给定的AVBufferRef变为可写,当其引用的AVBuffer本身可写时直接返回,如果引用的AVBuffer处于只读状态, * 则重新创建一个AVBuffer,并将原来AVBuffer的数据拷贝给新的AVBuffer,而且原来的AVBuffer引用计数-1, * 此时buf指向一个新的AVBuffer。 * * @param buf AVBufferRef对象. * @成功则返回0 , 返回负数 AVERROR则失败. */
int av_buffer_make_writable(AVBufferRef **buf);

/** * 给指定的AVBufferRef重新分配AVBuffer。 * * @param buf 指向重新分配AVBuffer的AVBufferRef. * @param size 新数据缓冲区的大小. * @成功则返回0 , 返回负数 AVERROR则失败. * * @注意 (1)当传入的数据长度和原来数据长度相同时,并不会重新分配AVBuffer,而是直接返回成功。 * (2)当传入的AVBufferRef处于非已重新分配、或只读、或AVBufferRef和AVBuffer的data指向不同时, * 函数内部重新分配一个AVBuffer对象。 * (3)其他情况则对原来的AVBuffer调用av_realloc重新分配内存。 */
int av_buffer_realloc(AVBufferRef **buf, int size);

AVBufferPool相关API分析

/** * 分配和初始化一个.AVBuffer池 * * @param size 该池内每个buffer的大小 * @param alloc 用于分配新buffers的回调函数,当其为NULL时,API内部默认使用 av_buffer_alloc() * @return 成功时返回AVBufferPool对象,返回NULL则说明失败。 */
AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size));

/** * 使用更复杂的allocator分配和初始化AVBuffer 池. * * @param size 该池内每个buffer的大小 * @param opaque allocator的自定义参数 * @param alloc 用于分配新buffers的回调函数. * @param pool_free a function that will be called immediately before the pool * is freed. I.e. after av_buffer_pool_uninit() is called * by the caller and all the frames are returned to the pool * and freed. It is intended to uninitialize the user opaque * data. * @return 成功时返回AVBufferPool对象,返回NULL则说明失败。 */
AVBufferPool *av_buffer_pool_init2(int size, void *opaque,
                                  AVBufferRef* (*alloc)(void *opaque, int size),
                                  void (*pool_free)(void *opaque));

/** * Mark the pool as being available for freeing. It will actually be freed only * once all the allocated buffers associated with the pool are released. Thus it * is safe to call this function while some of the allocated buffers are still * in use. * 只有在其分配的所有AVBuffer都被释放时,其才真正被释放。 * 即使存在AVBuffer没有被释放时调用该函数,也不影响AVBuffer的继续使用, * 其实现机制参考源码的pool_release_buffer,只有在引用计数为1的时候才会真正调用buffer_pool_free() * * @param pool pointer to the pool to be freed. It will be set to NULL. */
void av_buffer_pool_uninit(AVBufferPool **pool);

/** * 分配一个新的AVBuffer,如果池中有被释放的AVBuffer,则重新使用它。可以参考后续章节的测试范例. * 该函数可以在多线程环境使用. * * @成功时返回一个AVBufferRef,返回NULL 则失败 */
AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);

测试范例

测试范例分为3部分

  1. 简单测试二级指针
  2. 测试AVBufferRef相关的API
  3. 测试AVBufferPool相关的API

测试环境为

  • Windows7
  • QT 5.9.2 + Creator 4.4.1
  • FFMPEG库

简单测试二级指针

这里只是简单测试调用者传递的指针可以被调用的函数将其指针置为NULL

void testTwolevelPointer(char **pStr)
{

    std::cout << std::endl << "testTwolevelPointer function into " << std::endl;
    std::cout << "addr of pStr = " << pStr << std::endl;           // 指针在内存的地址
    std::cout << "addr of pStr = " << &(*pStr) << std::endl;       // 这里还是内存本身的地址
    std::cout <<"pStr pointer to " << hex << (uint64_t)( *pStr) <<std::endl;// 打印指向内容的地址
    std::cout << "pStr is " << *pStr << std::endl;                 // 获取到指针指向的内存
    std::cout << "testTwolevelPointer function leave " << std::endl << std::endl;
    *pStr  = nullptr;
}

void testPointer()
{
    char *pStrPtrArray[3];
    char str1[] = "Hello FFMPEG AVBuffer/AVBufferRef/AVBufferPool";

    std::cout << "\n--------------- Test Twolevel Pointer -------------" <<  std::endl;
    char *pStrPtr = str1;
    std::cout << "str1 is " <<  str1 << std::endl;
    std::cout << "str1 pointer to " << hex <<  (uint64_t)str1 << std::endl;
    std::cout << "addr of pStr = " <<  &pStrPtr << std::endl;

    testTwolevelPointer(&pStrPtr);
    std::cout << "&pStrPtr = " <<  &pStrPtr << std::endl;
    if(pStrPtr)
        std::cout << "pStrPtr = " <<  pStrPtr << std::endl;
    else
        std::cout << "pStrPtr = nullptr"  << std::endl;

    std::cout << "--------------- Test Pointer End -----------" <<  std::endl;
}

该测试很基础,只是为了演示下函数是怎么将外部指针置为NULL,因为FFMPEG的API有很多类似操作。

测试AVBufferRef相关的API

代码

/** * @brief 测试引用计数 */
void testReferenceCount()
{
    AVBufferRef *origBufferRef = NULL;
    AVBufferRef *tempBufferRef = NULL;

    // 测试引用计数
    std::cout << "\n--------------- Test testReferenceCount into -------------" <<  std::endl;
    tempBufferRef = av_buffer_alloc(200);
    std::cout << "\n1 tempBufferRef = av_buffer_alloc(200)"<<  std::endl;
    std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
                <<  av_buffer_get_ref_count(tempBufferRef) << std::endl;
    std::cout << "tempBufferRef size = "<< dec << tempBufferRef->size << std::endl;

    origBufferRef = av_buffer_ref(tempBufferRef);       // 增加引用计数
    std::cout << "\n2 origBufferRef = av_buffer_ref(tempBufferRef)"<<  std::endl;
    std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
            <<  av_buffer_get_ref_count(tempBufferRef) << std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
            <<  av_buffer_get_ref_count(origBufferRef) << std::endl;
    std::cout << "origBufferRef size = "<< origBufferRef->size << std::endl;
    std::cout << "tempBufferRef size = "<< tempBufferRef->size << std::endl;

    // 释放tempBufferRef
    //减少引用计数
    av_buffer_unref(&tempBufferRef);
    std::cout << "\n3 av_buffer_unref(&tempBufferRef);" << std::endl;
    if(tempBufferRef)
    {
        // tempBufferRef已经被置为NULL, 这里不会被打印
        std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
                <<  av_buffer_get_ref_count(tempBufferRef) << std::endl; 
    }
    else
    {
        std::cout << "tempBufferRef be free " << std::endl;
    }
    if(origBufferRef)
    {
         std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
                 <<  av_buffer_get_ref_count(origBufferRef) << std::endl;
    }
    else
    {
        std::cout << "origBufferRef be free " << std::endl;
    }
    //减少引用计数
    av_buffer_unref(&origBufferRef);
    std::cout << "\n4 av_buffer_unref(&tempBufferRef);" << std::endl;
    if(tempBufferRef)
    {
        // tempBufferRef已经被置为NULL, 这里不会被打印
        std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
                <<  av_buffer_get_ref_count(tempBufferRef) << std::endl;       
    }
    else
    {
        std::cout << "tempBufferRef be free " << std::endl;
    }
    if(origBufferRef)
    {
        std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
                <<  av_buffer_get_ref_count(origBufferRef) << std::endl;
    }
    else
    {
        std::cout << "origBufferRef be free " << std::endl;
    }
    std::cout << "\n--------------- Test testReferenceCount leave -------------" <<  std::endl;
}

void testBufferIsWritable()
{
    AVBufferRef *origBufferRef = NULL;
    AVBufferRef *tempBufferRef = NULL;

    std::cout << "\n\n--------------- Test testBufferIsWritable into -------------" <<  std::endl;
    // 测试引用计数
    tempBufferRef = av_buffer_alloc(200);
    std::cout << "1 tempBufferRef = av_buffer_alloc(200)"<<  std::endl;
    origBufferRef = av_buffer_ref(tempBufferRef);       // 增加引用计数
    std::cout << "origBufferRef = av_buffer_ref(tempBufferRef)"<<  std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " <<  av_buffer_get_ref_count(origBufferRef) << std::endl;       // 打印为为2
    std::cout << "av_buffer_is_writable(origBufferRef) = " <<  av_buffer_is_writable(origBufferRef) << std::endl;           // 不可写,应该打印为0

    av_buffer_unref(&tempBufferRef);                    // 减少引用计数
    std::cout << "\n2 av_buffer_unref(&tempBufferRef);" << std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
            <<  av_buffer_get_ref_count(origBufferRef) << std::endl;       // 打印为为1
    std::cout << "av_buffer_is_writable(origBufferRef) = " 
            <<  av_buffer_is_writable(origBufferRef) << std::endl;           // 可写,应该打印为1

    tempBufferRef = av_buffer_ref(origBufferRef);       // 增加引用计数
    std::cout << "\n3 tempBufferRef = av_buffer_ref(origBufferRef));" << std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
            <<  av_buffer_get_ref_count(origBufferRef) << std::endl;       // 打印为为2
    std::cout << "\n4 av_buffer_is_writable(origBufferRef) = " 
            <<  av_buffer_is_writable(origBufferRef) << std::endl;           // 不可写,应该打印为0
    if(0 == av_buffer_make_writable(&origBufferRef))
    {
        std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
                <<  av_buffer_get_ref_count(origBufferRef) << std::endl;       // 打印为为1
        std::cout << "av_buffer_is_writable(origBufferRef) = " 
                <<  av_buffer_is_writable(origBufferRef) << std::endl;           // 可写,应该打印为1
    }

    av_buffer_unref(&origBufferRef);        // 释放
    av_buffer_unref(&tempBufferRef);

    std::cout << "\n--------------- Test testBufferIsWritable leave -------------" <<  std::endl;
}

void testBufferRealloc()
{
    AVBufferRef *origBufferRef = NULL;
    AVBufferRef *tempBufferRef = NULL;

    std::cout << "\n\n--------------- Test testBufferRealloc into -------------" <<  std::endl;
    av_buffer_realloc(&origBufferRef, 200);  // 数据长度和原来一样,数据没有重新分配,所以两者的引用计数维持不变
    tempBufferRef = av_buffer_ref(origBufferRef);

    std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
            <<  av_buffer_get_ref_count(tempBufferRef) << std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
        <<  av_buffer_get_ref_count(origBufferRef) << std::endl;

    av_buffer_realloc(&origBufferRef, 100);         // 数据长度和原来的不一样,AVBuffer进行了重新分配
    std::cout << "origBufferRef->data = " <<  hex << (uint64_t)(origBufferRef->data) << std::endl;
    std::cout << "tempBufferRef->data = " <<  hex << (uint64_t)(tempBufferRef->data) << std::endl;
    av_buffer_realloc(&origBufferRef, 150);         // 数据长度和原来的不一样,AVBuffer进行了重新分配
    std::cout << "origBufferRef->data = " <<  hex << (uint64_t)(origBufferRef->data) << std::endl;
    std::cout << "tempBufferRef->data = " <<  hex << (uint64_t)(tempBufferRef->data) << std::endl;      
    // 其他case读者自行再测试下
    av_buffer_realloc(&origBufferRef, 200);
    // 长度没变,所以buffer地址也没有变
    std::cout << "tempBufferRef->data = " <<  hex << (uint64_t)(tempBufferRef->data) << std::endl;      
    std::cout << "av_buffer_get_ref_count(tempBufferRef) = " 
            <<  av_buffer_get_ref_count(tempBufferRef) << std::endl;
    std::cout << "av_buffer_get_ref_count(origBufferRef) = " 
            <<  av_buffer_get_ref_count(origBufferRef) << std::endl;
    std::cout << "av_buffer_is_writable(tempBufferRef) = " 
            <<  av_buffer_is_writable(tempBufferRef) << dec << ", size = "
            << tempBufferRef->size << std::endl;
    std::cout << "av_buffer_is_writable(origBufferRef) = " 
            <<  av_buffer_is_writable(origBufferRef) << dec << ", size = "
            << origBufferRef->size << std::endl;

    av_buffer_unref(&origBufferRef);        // 释放
    av_buffer_unref(&tempBufferRef);
    std::cout << "\n--------------- Test testBufferRealloc leave -------------" <<  std::endl;
}
/** * @brief testAVBuffer * 1.测试引用计数的问题 * 2.测试可写的问题 * 3.测试realloc的问题 */
void testAVBuffer()
{
    // 引用计数的测试
    testReferenceCount();

    // 测试可写
    testBufferIsWritable();

    // 测试realloc
    testBufferRealloc();
}

测试结果


--------------- Test testReferenceCount into ------------- 1 tempBufferRef = av_buffer_alloc(200) av_buffer_get_ref_count(tempBufferRef) = 1 tempBufferRef size = 200 2 origBufferRef = av_buffer_ref(tempBufferRef) av_buffer_get_ref_count(tempBufferRef) = 2 av_buffer_get_ref_count(origBufferRef) = 2 origBufferRef size = 200 tempBufferRef size = 200 3 av_buffer_unref(&tempBufferRef); tempBufferRef be free av_buffer_get_ref_count(origBufferRef) = 1 4 av_buffer_unref(&tempBufferRef); tempBufferRef be free origBufferRef be free --------------- Test testReferenceCount leave ------------- --------------- Test testBufferIsWritable into ------------- 1 tempBufferRef = av_buffer_alloc(200) origBufferRef = av_buffer_ref(tempBufferRef) av_buffer_get_ref_count(origBufferRef) = 2 av_buffer_is_writable(origBufferRef) = 0 2 av_buffer_unref(&tempBufferRef); av_buffer_get_ref_count(origBufferRef) = 1 av_buffer_is_writable(origBufferRef) = 1 3 tempBufferRef = av_buffer_ref(origBufferRef)); av_buffer_get_ref_count(origBufferRef) = 2 4 av_buffer_is_writable(origBufferRef) = 0 av_buffer_get_ref_count(origBufferRef) = 1 av_buffer_is_writable(origBufferRef) = 1 --------------- Test testBufferIsWritable leave ------------- --------------- Test testBufferRealloc into ------------- av_buffer_get_ref_count(tempBufferRef) = 2 av_buffer_get_ref_count(origBufferRef) = 2 origBufferRef->data = 5ac160 tempBufferRef->data = 5abfc0 origBufferRef->data = 5ac240 tempBufferRef->data = 5abfc0 tempBufferRef->data = 5abfc0 av_buffer_get_ref_count(tempBufferRef) = 1 av_buffer_get_ref_count(origBufferRef) = 1 av_buffer_is_writable(tempBufferRef) = 1, size = 200 av_buffer_is_writable(origBufferRef) = 1, size = 200 --------------- Test testBufferRealloc leave ------------- 请按任意键继续. . .

测试AVBufferPool相关的API

代码

const int testAVBufferPoolLoopCount = 1;        // 通过修改循环次数来测试是否有内存的泄漏
void testAVBufferPool()
{
    std::cout << "\n\n--------------- Test testAVBufferPool into -------------" <<  std::endl;
    AVBufferPool *avBufferPool;
    AVBufferRef *avTempBuffferRef;
    AVBufferRef *avBuffferRef0;
    AVBufferRef *avBuffferRef1;
    AVBufferRef *avBuffferRef2;

    for(int i = 0; i < testAVBufferPoolLoopCount; i++)
    {
        avBufferPool = av_buffer_pool_init(1*1024*1024, nullptr);
        avBuffferRef0 = av_buffer_pool_get(avBufferPool);
        std::cout <<"avBuffferRef0-hex:" << hex << avBuffferRef0 <<std::endl;
        std::cout << "avBuffferRef0 size = " <<dec << avBuffferRef0->size << std::endl;

        //memcpy(avTempBuffferRef, avBuffferRef0, sizeof(*avTempBuffferRef));
        avTempBuffferRef = avBuffferRef0;
        av_buffer_unref(&avBuffferRef0);                    // 释放 avBuffferRef0


        std::cout << "avTempBuffferRef size = " <<dec << avTempBuffferRef->size << std::endl;
        avBuffferRef1 = av_buffer_pool_get(avBufferPool);
        avBuffferRef2 = av_buffer_pool_get(avBufferPool);

        std::cout <<"AVBufferPool" << std::endl;
        std::cout <<"avBuffferRef0-hex:" << hex << avBuffferRef0 <<std::endl;
        std::cout <<"avTempBuffferRef-hex:" << hex << avTempBuffferRef <<std::endl;
        // 这里可以看到打印的地址和avBuffferRef0未释放时是一样的,说明AVBufferPool内部有回收机制
        std::cout <<"avBuffferRef1-hex:" << hex << avBuffferRef1 <<std::endl;  
        std::cout <<"avBuffferRef2-hex:" << hex << avBuffferRef2 <<std::endl;
        if(avBuffferRef0)
            std::cout << "av_buffer_get_ref_count(avBuffferRef0) = " <<  av_buffer_get_ref_count(avBuffferRef0) << std::endl;
        if(avBuffferRef1)
            std::cout << "av_buffer_get_ref_count(avBuffferRef1) = " <<  av_buffer_get_ref_count(avBuffferRef1) << std::endl;

        avBuffferRef0 = av_buffer_pool_get(avBufferPool);
        av_buffer_pool_uninit(&avBufferPool);

        av_buffer_unref(&avBuffferRef0);        // 通过注释该部分来测试是否有内存的泄漏
        av_buffer_unref(&avBuffferRef1);
        av_buffer_unref(&avBuffferRef2);
    }

    std::cout << "\n--------------- Test testAVBufferPool leave -------------" <<  std::endl;

}

打印如下所示

--------------- Test testAVBufferPool  into -------------
avBuffferRef0-hex:0x5dc060
avBuffferRef0 size = 1048576
avTempBuffferRef size = 1048576
AVBufferPool
avBuffferRef0-hex:0
avTempBuffferRef-hex:0x5dc060
avBuffferRef1-hex:0x5dc060
avBuffferRef2-hex:0x5dc120
av_buffer_get_ref_count(avBuffferRef1) = 1

--------------- Test testAVBufferPool  leave -------------
请按任意键继续. . .

内存占用情况(未按任意键结束时占用的内存,并不是测试运行过程的内存)

这里主要是为了验证av_buffer_pool_uninit后,AVBuffer仍然是可以使用,并测试如果不进行av_buffer_unref,将导致内存泄漏。
比如修改如下

const int testAVBufferPoolLoopCount = 1000;        // 通过修改循环次数来测试是否有内存的泄漏

...
//        av_buffer_unref(&avBuffferRef0);        // 通过注释该部分来测试是否有内存的泄漏
//        av_buffer_unref(&avBuffferRef1);
//        av_buffer_unref(&avBuffferRef2);

则运行崩溃

完整的工程下载地址

下载地址:http://download.csdn.net/download/muyuyuzhong/10260119
将ffmpeg\ffmpeg-3.3.3-win32-shared\bin的动态文件拷贝到执行目录,否则不能正常执行。

结语

我们通过分析结构体和API,以及实验验证分析了AVBuffer/AVBufferRef/AVBufferPool的作用和使用方法。经过以上的分析后,知道了每个API的用途,相信读者自己在阅读相关的API源码时能够快速理解其代码的含义(libavutil\buffer.c,该代码在FFMPEG中属于容易理解的部分,但其对于理解FFMPEG的缓存机制有极大的帮助)。

参考

[1] AVBuffer http://ffmpeg.org/doxygen/trunk/structAVPacket.html
[2] 内存管理之引用计数 http://www.cppblog.com/smagle/archive/2010/07/23/120758.html

全部评论

相关推荐

AI牛可乐:哇塞,恭喜恭喜!48万的年薪,真是让人羡慕呀!看来你找到了一个超棒的工作,可以享受不卷的生活啦!🎉有没有什么求职秘诀想要分享给小牛牛呢?或者,想不想知道我是谁呢?😉(点击我的头像,我们可以私信聊聊哦~)
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务