FFmpeg 内存模型

FFmpeg 内存模型分析

在这里插入图片描述
通过前面的文章我们知道,AVPacket和AVFrame是储存着音视频解码前数据和解码后数据的重要结构体,我们使用av_read_frame()函数将解封装后的数据存入每个AVPacket,使用avcodec_receive_frame()函数将解码后的数据存入每个AVFrame,这时不可避免会出现储存空间的分配与释放问题,高明的FFmpeg是如何设计并解决这一问题的呢?
先让我们一下AVPacket和AVFrame:

一、struct AVPacket
typedef struct AVPacket {
    //指向引用计数缓冲区的引用
    AVBufferRef *buf;
    //显示时间戳
    int64_t pts;   
    //解码时间戳
    int64_t dts;
    //压缩编码的数据
    uint8_t *data;
    
    //data的大小
    int   size;
    //标识该AVPacket所属的视频/音频流。
    int   stream_index;
    /**
     * A combination of AV_PKT_FLAG values
     */
    int   flags;
    /**
     * Additional packet data that can be provided by the container.
     * Packet can contain several types of side information.
     */
    AVPacketSideData *side_data;
    int side_data_elems;

    /**
     * Duration of this packet in AVStream->time_base units, 0 if unknown.
     * Equals next_pts - this_pts in presentation order.
     */
    int64_t duration;

    int64_t pos;                            ///< byte position in stream, -1 if unknown

#if FF_API_CONVERGENCE_DURATION
    /**
     * @deprecated Same as the duration field, but as int64_t. This was required
     * for Matroska subtitles, whose duration values could overflow when the
     * duration field was still an int.
     */
    attribute_deprecated
    int64_t convergence_duration;
#endif
} AVPacket;

以上是FFmpeg avcodec.h文件中关于AVPacket的源码,我们可以提炼出以下关键:

  • 此结构存储被压缩的数据(关键数据见源码注释)。它通常由解复用器导出然后作为输入传递给解码器,或者从编码器输出并传入复用器。
  • 对于视频,它通常应该包含一个压缩帧。对于音频,它可以包含多个压缩帧。
  • 使用AVBufferRef指向一个引用计数缓冲区。
二、struct AVFrame

这篇文章中我们的任务是去探究FFmpeg的内存结构,所以我们不去深究AVFrame中究竟封装了哪些具体数据元素。
在AVFrame的源码中我们同样发现,他也使用AVBufferRef指向一个引用计数缓冲区。

三、struct 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是单个对数据缓冲区AVBuffer的引用,他是AVPacket、AVFrame和数据缓冲区AVBuffer之间的桥梁。下面我们就正式揭开上文提到的“引用数据缓冲区”的面纱。

四、struct AVBuffer

AVBuffer是FFmpeg中很常用的一种缓冲区,缓冲区使用引用计数(reference-counted)机制。

  • 这个API中有两个核心对象——AVBuffer和AVBufferRef。AVBuffer 表示数据缓冲区本身;它是不透明的(非常类似与private),不应被访问或由调用方直接调用,只能通过AVBufferRef访问

  • 但是,调用者可以通过比较两个AVBuffer指针,检查两个不同引用是否指向同一数据缓冲区。

  • AVBufferRef表示单个对AVBuffer的引用,它是一个可以由调用者直接调用的对象。

  • 有两个函数用于创建新的AVBuffer :

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

    data = av_malloc(size);
    if (!data)
        return NULL;

    ret = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
    if (!ret)
        av_freep(&data);

    return ret;
}

使用av_malloc分配一个新的缓冲区
调用av_buffer_create()创建 AVBufferRef::*buffer成员,用于管理AVBuffer缓冲区

  1. av_buffer_create()

函数主要功能就是初始化AVBuffer AVBufferRef::*buffer成员,即为ref->buffer各字段赋值,最终,AVBufferRef *ref全部构造完毕,将之返回。

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));
    if (!buf)
        return NULL;

    buf->data     = data;
    buf->size     = size;
    buf->free     = free ? free : av_buffer_default_free;
    buf->opaque   = opaque;

    atomic_init(&buf->refcount, 1);

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

    ref = av_mallocz(sizeof(*ref));
    if (!ref) {
        av_freep(&buf);
        return NULL;
    }

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

    return ref;
}
  • 可以使用 av_buffer_ref() 从现有引用,创建新引用。
AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{
    AVBufferRef *ret = av_mallocz(sizeof(*ret));

    if (!ret)
        return NULL;

    *ret = *buf;

    atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);

    return ret;
}

a) *ret = *buf; 这充分说明了新引用将与现有引用共用一份缓冲区。
b) atomic_fetch_add_explicit();当拷贝发生时,将AVBuffer缓冲区引用计数加1。

  • 使用av_buffer_unref() 释放引用(这将在释放所有引用的同时释放所有数据)。
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 (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;

    buffer_replace(buf, NULL);
}

a) 回收AVBufferRef **buf内存
b) AVBuffer的引用计数减1,若引用计数为0,则通过b->free(b->opaque, b->data);调用回调函数回收AVBuffer缓冲区内存。

  • 由此看出,等效在AVPacket中的操作就是:
    在这里插入图片描述
五、总结

1.浅拷贝模式
在这里插入图片描述
由图容易看出两个avpacket指向同一AVBuffer空间。

2.引用计数机制:一种垃圾回收机制,对于一个内存块,除内存块本身外增加一个引用计数,每当这个内存块被外部的指针或内存块所引用的时候,引用计数加1;当引用它的指针释放了对它的引用的时候,则引用计数减1,同时检查引用计数的值,当引用计数为0时,就销毁该内存块并释放其所占用的内存。引用计数机制需要2个函数,一个是增加引用计数,一个是减少引用计数并当计数减少到0时释放内存。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值