FFmpeg学习笔记 - AVFrame/AVBuffer/引用计数

203 阅读3分钟

AVBuffer

AVBuffer是FFmpeg内存管理的基础。

AVBuffer表示数据缓冲区本身,不能直接访问。我们可以通过AVFrame提供的接口来操作AVBuffer中的数据。

AVBuffer结构体

struct AVBuffer {
    uint8_t *data; /**< data described by this buffer */
    int      size; /**< size of data in bytes */

    /**
     引用此缓冲区的现有AVBufferRef实例的数量
     */
    atomic_uint refcount;

    /**
     * 释放数据的回调
     */
    void (*free)(void *opaque, uint8_t *data);

    /**
     * 一个不透明的指针,供释放回调函数使用
     */
    void *opaque;

    /**
     * A combination of AV_BUFFER_FLAG_*
     */
    int flags;

    /**
     * A combination of BUFFER_FLAG_*
     */
    int flags_internal;
};

引用计数refcount

AVBuffer结构体中的refcount表示引用计数,当引用计数为0时,AVBuffer占据的内存空间会被自动释放

每当新增一个AVFrame指向AVBuffer时,refcount数量加一。

可以通过int av_frame_ref(AVFrame *dst, const AVFrame *src);增加对AVBuffer的指向。

对于refcount的操作是线程安全的操作,这里使用了全局锁,参考源码如下

static inline intptr_t atomic_fetch_ ## opname(intptr_t *object, intptr_t operand) 
{        
    intptr_t ret;
    avpriv_atomic_lock();                
    ret = *object;
    *object = *object op operand;
    avpriv_atomic_unlock();
    return ret;
}

avpriv_atomic_lock获取一个全局锁,锁持有期间,当前线程是唯一能访问共享资源的线程,防止多线程争抢

avpriv_atomic_unlock释放全局锁

AVFrame

下面这段代码展示了,如何通过AVFrame操作AVBuffer

//初始化AVFrame
static AVFrame* frame = nullptr;

// 打开YUV文件
yuv_file.open("400_300_25.yuv", ios::binary);
if (!yuv_file)
{
    QMessageBox::information(this, "Error", "open yuv failed");
    return;
}

sdl_width = 400;
sdl_height = 300;


//生成frame对象空间
frame = av_frame_alloc();
frame->width = sdl_width;
frame->height = sdl_height;
frame->format = AV_PIX_FMT_YUV420P;

//手动指定buffer大小,防止32Byte自动对齐导致的花屏
frame->linesize[0] = sdl_width; //Y
frame->linesize[1] = sdl_width/2; //U
frame->linesize[2] = sdl_width/2; //V

//生成图像空间 默认32Byte对齐
auto re = av_frame_get_buffer(frame, 0);

if (re != 0)
{
    char buf[1024] = { 0 };
    av_strerror(re, buf, sizeof(buf));
    cerr << buf << endl;
}

//将yuv_file文件数据写入AVBuffer
//istream& read(char* buffer, streamsize count);
//buffer目标内存地址,count读取的字节数
yuv_file.read((char*)frame->data[0], sdl_width * sdl_height);//Y
yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4);//U
yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4);//V

AVFrame相关的函数解析

AVFrame *av_frame_alloc(void);

返回一个AVFrame指针,申请了对象的空间,存储的空间并没有申请


int av_frame_get_buffer(AVFrame *frame, int align);

传入AVFrame结构指针,int align用于设置对齐方式 int align传入0时,采用默认对齐方式32Byte


int av_buffer_get_ref_count(const AVBufferRef *buf);

返回AVBuffer结构体中的refcount,引用计数


int av_frame_ref(AVFrame *dst, const AVFrame *src);

设置一个新的AVFrame结构体dst,指向已存在AVFrame结构体src


void av_frame_unref(AVFrame *frame);

解除AVFrame指针对buffer空间的引用,此时函数内部会读取AVBuffer内部的引用计数refcount,如果refcount为0,那么释放AVBuffer


void av_frame_free(AVFrame **frame); 释放AVFrame以及其中自动分配的对象,如果AVFrame指向某个AVBuffer,那么先解除引用,将AVBuffer引用计数refcount减一,再释放AVFrame

AVFrame **frame是一个双指针,因为我们这里要修改AVFrame *frame,AVFrame *frame只能修改所指向的AVBuffer,无法修改AVFrame *frame本身,所以这里使用AVFrame **frame

结尾

感谢阅读!本文主要解析了AVFrame, AVBuffer 的概念及联系,AVBuffer中引用计数的概念和原理,分析了AVFrame常用的API。