FFMPEG智能缓冲实现及应用

我们知道,ffmpeg是用C语言开发的,C语言最棘手的一个问题就是对内存的管理。而对于作为专门进行媒体处理的ffmpeg来说,又需要大量地使用动态内存的分配和释放,因此,ffmpeg实现了一个智能缓冲来对动态内存进行管理。
ffmpeg的智能缓冲其原理与C++ 11的智能指针shared_ptr是相同的,即通过一个计数值纪录对当前缓冲区引用次数,当引用次数减少为0时,释放缓冲所占用的内存。其中,涉及到的关键数据结构有

libavutil/buffer.h

/**
 * 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为智能buffer对外的数据结构,相当于C++里的智能指针对象,该结构体只是一个外壳,实际的计数值和内存指针保存在AVBuffer中。为了使用方便,AVBufferRef中也保存了一份buffer内存的指针和大小,但是它们只不过是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
     */
    volatile int 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;
};

AVBuffer 保存了buffer内存的指针和大小,以及对buffer对象的引用计数值。

下面是对buffer进行操作的接口实现:
libavutil/buffer.c

AVBufferRef *av_buffer_alloc(int size)
{
    AVBufferRef *ret = NULL;
    uint8_t    *data = NULL;

    data = av_malloc(size); //分配buffer内存
    if (!data)
        return NULL;

    ret = av_buffer_create(data, size, av_buffer_default_free, NULL, 0); //创建buffer对象
    if (!ret)
        av_freep(&data);

    return ret;
}

av_buffer_alloc创建buffer对象,并分配buffer所使用的内存。

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;

    buf = av_mallocz(sizeof(*buf));//动态创建buffer对象
    if (!buf)
        return NULL;

    buf->data     = data;//buffer对象管理的实际内存
    buf->size     = size;//buffer对象管理的内存大小
    buf->free     = free ? free : av_buffer_default_free;//相当于析构函数,
                                                         //默认为直接释放data所指向的内存
    buf->opaque   = opaque;
    buf->refcount = 1;//初始化引用计数为1

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

    ref = av_mallocz(sizeof(*ref));//动态创建AVBufferRef 对象
    if (!ref) {
        av_freep(&buf);
        return NULL;
    }

    ref->buffer = buf;//AVBufferRef为AVBuffer的管理者
    ref->data   = data;//复制AVBuffer中的相关属性
    ref->size   = size;

    return ref;
}

从上面的代码可以看出,在创建buffer对象时,首先会创建一个AVBuffer对象,其中保存着buffer内存的指针,并将其引用计数初始化为1,然后,创建一个AVBufferRef对象对AVBuffer对象进行管理。注意,对于同一个buffer对象,AVBuffer对象只会创建一次,但是每个对该buffer引用都需要对应一个单独AVBufferRef对象,这些对象指向同一个AVBuffer对象,并且随着引用的创建和销毁相应地更新AVBuffer对象中的引用计数值。

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)) {
        /* 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_realloc中,如果pbuf指向一个空的AVBufferRef指针,则为其创建一个新的AVBufferRef对象,并分配buffer内存。如果pbuf已经指向了一个已存在的AVBufferRef对象,并且其buffer大小与输入的size不相等,则为其重新分配buffer内存。

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

    if (!ret)
        return NULL;

    *ret = *buf;

    avpriv_atomic_int_add_and_fetch(&buf->buffer->refcount, 1);

    return ret;
}

当需要对一个buffer对象进行引用时,调用av_buffer_ref接口,其输入是被引用的AVBufferRef指针,返回一个新的AVBufferRef,它们指向相同的AVBuffer对象,并且将引用计数加1。

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

    buffer_replace(buf, NULL);
}

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

    b = (*dst)->buffer;

    if (src) {
        **dst = **src;
        av_freep(src);
    } else
        av_freep(dst);

    if (!avpriv_atomic_int_add_and_fetch(&b->refcount, -1)) {
        b->free(b->opaque, b->data);
        av_freep(&b);
    }
}

当对一个buffer的引用结束时,需要调用av_buffer_unref接口。首先会将该引用的AVBufferRef对象释放,并且将引用计数减1,如果引用计数为0了,则调用buffer注册的析构函数(默认为直接释放buffer内存),并且释放AVBuffer所对应的内存。
下面以AVPacket为例看一下上面所讲的智能内存是如何使用的。

libavcodec/avpacket.c

int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

    av_init_packet(pkt);
    pkt->buf      = buf;
    pkt->data     = buf->data;
    pkt->size     = size;

    return 0;
}

static int packet_alloc(AVBufferRef **buf, int size)
{
    int ret;
    if (size < 0 || size >= INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
        return AVERROR(EINVAL);

    //对于第一次创建,buf为空
    ret = av_buffer_realloc(buf, size + AV_INPUT_BUFFER_PADDING_SIZE);
    if (ret < 0)
        return ret;

    memset((*buf)->data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    return 0;
}

int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
    int ret;

    //拷贝可选数据成员
    ret = av_packet_copy_props(dst, src);
    if (ret < 0)
        return ret;

    if (!src->buf) {//如果没有buffer对象,创建一个
        ret = packet_alloc(&dst->buf, src->size);
        if (ret < 0)
            goto fail;
        memcpy(dst->buf->data, src->data, src->size);
    } else {//已有buffer对象,创建一个新的引用
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    dst->size = src->size;
    dst->data = dst->buf->data;//packet的数据指针直接指向buffer的数据指针
    return 0;
fail:
    av_packet_free_side_data(dst);
    return ret;
}

void av_packet_unref(AVPacket *pkt)
{
    av_packet_free_side_data(pkt);//释放可选属性
    av_buffer_unref(&pkt->buf);//销毁该引用
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
}

总结:通过智能buffer的引入,免去了手动分配和释放内存的痛苦,可以有效减少bug的发生。ffmpeg的智能buffer的实现可以作为C语言下智能指针实现的一个示例。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值