0
点赞
收藏
分享

微信扫一扫

【FFmpeg】FFmpeg 内存结构 ① ( AVPacket 队列 和 AVFrame 队列 | AVPacket 数据的 深拷贝 和 浅拷贝 | AVPacket 的 引用计数器机制 )



文章目录

  • 一、FFmpeg 内存模型
  • 1、AVPacket 队列 和 AVFrame 队列
  • 2、解码操作涉及的函数
  • 3、AVPacket 数据的 深拷贝 和 浅拷贝
  • 4、AVPacket 数据的 浅拷贝 引用计数器机制
  • 5、AVPacket 中 存储 音视频数据 细节




FFmpeg 4.0 版本源码地址 :

  • GitHub : https://github.com/FFmpeg/FFmpeg/tree/release/4.0
  • GitCode : https://gitcode.com/gh_mirrors/ff/FFmpeg/tree/release/4.0





一、FFmpeg 内存模型



1、AVPacket 队列 和 AVFrame 队列



FFmpeg 打开媒体文件后 , 调用 av_read_frame 函数 从 解复用器 中 获取到 音频包 / 视频包 AVPacket , 然后将读取到的数据包放入 AVPacket 队列中 ;

将 AVPacket 队列中的元素 通过 调用 avcodec_send_packet 和 avcodec_receive_frame 函数 , 获取到 AVFrame 数据帧 , 然后将 AVFrame 数据帧放入到 AVFrame 队列中 ,

如下图所示 :

【FFmpeg】FFmpeg 内存结构 ① ( AVPacket 队列 和 AVFrame 队列 | AVPacket 数据的 深拷贝 和 浅拷贝 | AVPacket 的 引用计数器机制 )_多媒体

这里涉及到两个队列 , AVPacket 队列 和 AVFrame 队列 , 这是 复用 / 解复用器 , 编码器 / 解码器 的 核心操作数据 ,



AVPacket 队列 的 入队操作 和 出队操作 :

  • 入队 : 调用 av_read_frame 函数
  • 出队 : 调用 avcodec_send_packet 函数


AVFrame 队列 的 入队操作 和 出队操作 :

  • 入队 : 调用 avcodec_receive_frame 函数
  • 出队 : 从 AVFrame 队列中取出数据进行播放 ;


2、解码操作涉及的函数



上述 AVPacket 队列 和 AVFrame 队列

首先 , 调用 avcodec_send_packet 函数

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *packet);

然后 , 调用 avcodec_receive_frame 函数

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

得到 AVFrame 数据后 , 可以直接用于播放 ;



3、AVPacket 数据的 深拷贝 和 浅拷贝



在上述 复用 / 解复用 和 编码 / 解码 操作中 , 大量使用到了 AVPacket 和 AVFrame 的数据拷贝操作 , 本章节以 AVPacket 为例进行解析 :

AVPacket 中 的 AVBufferRef *buf 就是 实际的 压缩编码后的 数据帧 , 其指向堆内存中的数据空间 , 音视频数据就存储在该数据空间中 ;



AVPacket 数据 拷贝 时 分为 深拷贝 和 浅拷贝 ,

  • 浅拷贝 : 拷贝后的 AVPacket 的 AVBufferRef *buf 字段 和 原来的 AVPacket 的该字段 指向 堆内存 中的 同一个数据空间 ;
  • 【FFmpeg】FFmpeg 内存结构 ① ( AVPacket 队列 和 AVFrame 队列 | AVPacket 数据的 深拷贝 和 浅拷贝 | AVPacket 的 引用计数器机制 )_ffmpeg_02

  • 深拷贝 : 拷贝后的 AVPacket 的 AVBufferRef *buf 字段 和 原来的 AVPacket 的该字段 指向 堆内存 中的 不同个数据空间 ; 拷贝前 , 先在堆内存申请内存空间 , 然后将 音视频数据 拷贝过去 , 这样拷贝前后的 AVPacket 各自持有一份不同的数据 ;
  • 【FFmpeg】FFmpeg 内存结构 ① ( AVPacket 队列 和 AVFrame 队列 | AVPacket 数据的 深拷贝 和 浅拷贝 | AVPacket 的 引用计数器机制 )_FFmpeg_03


4、AVPacket 数据的 浅拷贝 引用计数器机制



在 FFmpeg 中 , 使用 的 是 浅拷贝 , 因为 深拷贝 在实际操作中 要消耗两倍的内存空间 ;

使用 浅拷贝 就意味着 多个 AVPacket 引用相同的数据 , 如果要将 AVPacket 进行释放 , 是否需要将其指向的 音视频 数据进行释放 ;

在实际的 音视频数据 中 , 定义了引用计数器 , AVPacket 每次进行浅拷贝 , 引用计数器自增 1 , AVPacket 每次进行释放时 , 引用计数器自减 1 , 当 自减后 引用计数器 值为 0 时 , 将 实际的 音视频数据 堆内存进行释放 ;

typedef struct AVBuffer {
    /**
     * 数据指针,指向实际的内存缓冲区。
     */
    uint8_t *data;

    /**
     * 缓冲区的大小(以字节为单位)。
     */
    int size;

    /**
     * 引用计数器,指示有多少个 `AVBufferRef` 引用了此缓冲区。
     */
    int refcount;
} AVBuffer;



缓存空间 引用计数机制 :

  • 初始状态 , 计数值为 0 ;
  • 有新的 AVPacket 引用该 缓存空间 中的数据时 , 计数值 自增 1 ;
  • 有 AVPacket 释放时 , 计数值 自减 1 , 当 自减 后 计数值为 0 时 , 释放 缓存空间 的内存 ;


5、AVPacket 中 存储 音视频数据 细节



在 FFmpeg 源码中 , AVPacket 通常定义在 libavcodec/avcodec.h 文件 中 , 其中 buf 字段 用于引用计数的缓冲区封装 , 方便数据的共享和管理 ;

typedef struct AVPacket {
    /**
     * 包含指向数据缓冲区的指针和引用计数信息。
     */
    AVBufferRef *buf;
}

buf 是 AVBufferRef 类型的结构体指针

typedef struct AVBufferRef {
    /**
     * 指向实际缓冲区对象的指针。
     * 该缓冲区对象包含了数据缓冲区和引用计数管理信息。
     */
    AVBuffer *buffer;

    /**
     * 指向缓冲区数据的指针。
     * 这通常是 `buffer` 中数据缓冲区的开始地址,但可以指向 `buffer->data` 中的任意偏移。
     */
    uint8_t *data;

    /**
     * 缓冲区数据的大小(以字节为单位)。
     */
    int size;
} AVBufferRef;

上述结构体中的 AVBuffer *buffer 是真实的 数据结构 , AVBuffer 结构体 的原型如下 , 在该原型中 , 定义了数据实际的内存缓冲区 , 数据大小 , 以及 引用计数 ;

typedef struct AVBuffer {
    /**
     * 数据指针,指向实际的内存缓冲区。
     */
    uint8_t *data;

    /**
     * 缓冲区的大小(以字节为单位)。
     */
    int size;

    /**
     * 引用计数器,指示有多少个 `AVBufferRef` 引用了此缓冲区。
     */
    int refcount;

    /**
     * 用户自定义释放回调函数,在引用计数为 0 时调用。
     */
    void (*free)(void *opaque, uint8_t *data);

    /**
     * 自定义数据,传递给 `free` 回调函数的上下文。
     */
    void *opaque;
} AVBuffer;

参考下图进行理解 :

【FFmpeg】FFmpeg 内存结构 ① ( AVPacket 队列 和 AVFrame 队列 | AVPacket 数据的 深拷贝 和 浅拷贝 | AVPacket 的 引用计数器机制 )_多媒体_04

AVFrame 与 AVPacket 采用的是相同的 引用计数 机制 ;


举报

相关推荐

深拷贝和浅拷贝的区别

对象的深拷贝和浅拷贝

一. js的深拷贝和浅拷贝

0 条评论