ffmpeg中AVBuffer的实现分析

Posted Tocy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg中AVBuffer的实现分析相关的知识,希望对你有一定的参考价值。

[时间:2017-10] [状态:Open]
[关键词:ffmpeg,avutil,avbuffer, 引用计数]

0 引言

AVBuffer是ffmpeg提供的基于引用计数的智能指针的一个实现版本。
FFmpeg中很多结构体是基于AVBuffer实现的,比如AVFrame、AVPacket。

AVBuffer实现

主要实现文件位于libavutil中的buffer.h、buffer_internal.h、buffer.c三个文件中。其中最主要的是两个结构体AVBufferRefAVBuffer。其实AVBufferRef作为AVBuffer的wrapper,也只能通过其访问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 */
    void *opaque;

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

AVBufferRef定义如下:

typedef struct AVBufferRef {
    AVBuffer *buffer;

    uint8_t *data;
    int      size;
} AVBufferRef;

avutil中主要提供了以下几个创建AVBuffer的接口:

AVBufferRef *av_buffer_alloc(int size);
AVBufferRef *av_buffer_allocz(int size);
AVBufferRef *av_buffer_create(uint8_t *data, int size,
                              void (*free)(void *opaque, uint8_t *data),
                              void *opaque, int flags);
int av_buffer_realloc(AVBufferRef **buf, int size);

1 创建AVBuffer

主要逻辑位于av_buffer_create中,其他接口都是调用该接口实现的。先看看其他几个函数的实现代码:

AVBufferRef *av_buffer_alloc(int size)
{
    AVBufferRef *ret = NULL;
    uint8_t    *data = NULL;
    // 这里创建内存区域的指针
    data = av_malloc(size);
    if (!data)
        return NULL;
    // 这里创建AVBuffer对象
    ret = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
    if (!ret)
        av_freep(&data);

    return ret;
}

AVBufferRef *av_buffer_allocz(int size)
{
    AVBufferRef *ret = av_buffer_alloc(size);
    if (!ret)
        return NULL;
    // 相比av_buffer_alloc添加了重置内存的操作
    memset(ret->data, 0, size);
    return ret;
}

int av_buffer_realloc(AVBufferRef **pbuf, int size)
{
    AVBufferRef *buf = *pbuf;
    uint8_t *tmp;
    // 如果未分配内存,则分配一个
    if (!buf) {
        /* allocate a new buffer with av_realloc(), so it will be reallocatable
         * later */
        uint8_t *data = av_realloc(NULL, size);
        if (!data)
            return AVERROR(ENOMEM);

        buf = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
        if (!buf) {
            av_freep(&data);
            return AVERROR(ENOMEM);
        }

        buf->buffer->flags |= BUFFER_FLAG_REALLOCATABLE;
        *pbuf = buf;

        return 0;
    } else if (buf->size == size)// 长度够用,不用重新分配
        return 0;

    // 需要重新分配,检查下内存实际情况
    if (!(buf->buffer->flags & BUFFER_FLAG_REALLOCATABLE) ||
        !av_buffer_is_writable(buf) || buf->data != buf->buffer->data) {
        /* cannot realloc, allocate a new reallocable buffer and copy data */
        AVBufferRef *new = NULL;

        av_buffer_realloc(&new, size);
        if (!new)
            return AVERROR(ENOMEM);

        memcpy(new->data, buf->data, FFMIN(size, buf->size));

        buffer_replace(pbuf, &new);
        return 0;
    }

    tmp = av_realloc(buf->buffer->data, size);
    if (!tmp)
        return AVERROR(ENOMEM);

    buf->buffer->data = buf->data = tmp;
    buf->buffer->size = buf->size = size;
    return 0;
}

下面是最主要的逻辑,关于av_buffer_create实现。

AVBufferRef *av_buffer_create(uint8_t *data, int size,
    void (*free)(void *opaque, uint8_t *data), void *opaque, int flags)
{
    AVBufferRef *ref = NULL;
    AVBuffer    *buf = NULL;
    // 创建AVBuffer,并初始化之
    buf = av_mallocz(sizeof(*buf));
    if (!buf)
        return NULL;

    buf->data     = data;
    buf->size     = size;
    buf->free     = free ? free : av_buffer_default_free;
    buf->opaque   = opaque;
    // ffmpeg提供的原子初始化函数
    atomic_init(&buf->refcount, 1);

    if (flags & AV_BUFFER_FLAG_READONLY)
        buf->flags |= BUFFER_FLAG_READONLY;

    // 创建AVBufferRef的内存区域,并初始化
    ref = av_mallocz(sizeof(*ref));
    if (!ref) {
        av_freep(&buf);
        return NULL;
    }

    ref->buffer = buf;
    ref->data   = data;
    ref->size   = size;

    return ref;
}

这里提供了一个默认的内存区域释放函数,实际上就是调用av_free,如下:

void av_buffer_default_free(void *opaque, uint8_t *data)
{
    av_free(data);
}

其他主要的函数就是访问AVBuffer的一些成员和添加、删除引用计数。

AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{
    AVBufferRef *ret = av_mallocz(sizeof(*ret));

    if (!ret)
        return NULL;

    *ret = *buf;
    // 引用计数+1
    atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);

    return ret;
}

static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    AVBuffer *b;

    b = (*dst)->buffer;

    if (src) {
        **dst = **src;
        av_freep(src);
    } else
        av_freep(dst);
    // 引用计数-1,如果是最后一个引用计数,则释放内存区域
    if (atomic_fetch_add_explicit(&b->refcount, -1, memory_order_acq_rel) == 1) {
        b->free(b->opaque, b->data);
        av_freep(&b);
    }
}

void av_buffer_unref(AVBufferRef **buf)
{
    if (!buf || !*buf)
        return;

    // 引用计数和NULL交换
    buffer_replace(buf, NULL);
}

int av_buffer_get_ref_count(const AVBufferRef *buf)
{
    // 获取AVBuffer的引用计数
    return atomic_load(&buf->buffer->refcount);
}

整体来说实现相对简单,主要依赖ffmpeg中所提供的诸多原子操作机制。AVBuffer的基本用法代码如下:

AVBufferRef * br = av_buffer_alloc(100);
printf("ref count %d\n", av_buffer_get_ref_count(br));
av_buffer_ref(br);
printf("ref count %d\n", av_buffer_get_ref_count(br));
av_buffer_unref(br);
printf("ref count %d\n", av_buffer_get_ref_count(br));
av_buffer_unref(br);
printf("ref count %d\n", av_buffer_get_ref_count(br));

2 小结

ffmpeg中的AVBuffer实现逻辑较简单,其核心就是一个代理模式的实现。你可以绕过ffmpeg提供的接口直接访问内存指针,但是这样就无法做到安全的访问控制。

以上是关于ffmpeg中AVBuffer的实现分析的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg中AVPacket与AVFrame中数据的传递与释放

ffmpeg中AVPacket与AVFrame中数据的传递与释放

ffmpeg中AVPacket与AVFrame中数据的传递与释放

ffmpeg中AVPacket与AVFrame中数据的传递与释放

如何利用ffmpeg将一小段视频截取成图片

FFmpeg实现音视频同步的精准片段拼接