zoukankan      html  css  js  c++  java
  • FFmpeg内存模型与API介绍(notes 2)

    从上图中可以看出 AVPacket 和 AVFrame 是存储音视频解码前后数据的重要结构体,我们使用 av_read_frame 将解封装后的数据存入 AVPacket,将 avcodec_receive_frame() 函数将解码后的数据存入AVFrame,这部分必定会涉及到内存的分配和释放问题。在 FFMpeg 中,内存 IO 叫做 buffered IO ,是指将一块内存缓冲区用作 FFmpeg 的输入或者输出,与内存 IO 操作对应的是指定 URL 作为 FFmpeg 的输入或输出。

    假如现在需要将一个 Packet1 的数据拷贝到一个新的 Packet2 里面的,可以有两种方式:

    • (1)两个 Packet 的buf引用的是同一数据缓存空间。这时需要注意的是数据缓存空间的释放问题,(浅拷贝)

    • (2)两个Packetbuf引用不同的数据缓存空间。每个Packet都有数据缓存空间的copy(深拷贝)

    我们主要是基于第一种方式进行介绍。

    对于多个AVPacket共享同一个缓存空间,FFmpeg使用的引用计数的机制来管理。

    • AVBufferFFmpeg中的缓冲区,一开始时AVBuffer的引用计数(refcount)初始化为 0
    • 当有新的Packet引用共享的缓存空间时,就将引用计数再 +1
    • Packet释放掉对AVBuffer这块共享缓存空间的引用时,将引用计数 -1
    • 只有当refcount为 0 的时候,才会释放掉缓存空间AVBuffer

    AVBuffer 和 AVBufferRef

    我们首先来看以下这两个的源码:

    • (1)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;  //引用此缓冲区的现有AVBufferRef实例的数目
    
        /**
         * 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;
    };
    • (2)AVBufferRef
    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.
         */
         //数据缓冲区。当且仅当这是对缓冲区的唯一引用时,才认为它是可写的,在这种情况下,av_buffer_is_writable()返回1。
        uint8_t *data;
        /**
         * Size of data in bytes.
         */
        int      size;
    } AVBufferRef;

    • 这里的两个核心对象——AVBufferAVBufferRefVBuffer 表示数据缓冲区本身;它是不透明的(非常类似与private),不应被访问或由调用方(AVPacket/AVFrame)直接调用,只能通过AVBufferRef访问。
    • 但是,调用者可以通过比较两个AVBuffer指针,检查两个不同引用是否指向同一数据缓冲区。
    • AVBufferRef表示单个对AVBuffer的引用,它是一个可以由调用者直接调用的对象。

    AVFrame也是采用同样的机制。

    AVPacket 常用 API

    • AVPacket *av_packet_alloc(void);: 分配一个AVPacket,并将其字段设置为默认值。通过这个API分类的Packet必须由av_packet_free释放
    • void av_packet_free(AVPacket **pkt);: 释放掉Packet,如果这个Packet有引用的AVBuffer,将会先释放引用。
    • void av_init_packet(AVPacket *pkt);: 初始化Packet,注意,这并不涉及datasize成员,它们必须分别初始化。
    • int av_new_packet(AVPacket *pkt, int size);: 给AVPacket分配内存,这里引用计数将会+1
    • int av_packet_ref(AVPacket *dst, const AVPacket *src);:增加引用计数
    • void av_packet_unref(AVPacket *pkt);: 减少引用计数
    • void av_packet_move_ref(AVPacket *dst, AVPacket *src);: 将src中的每个字段移动到dst,并重置src
    • AVPacket *av_packet_clone(const AVPacket *src);: 克隆一个与src相同数据的Packet,等于av_packet_alloc()+av_packet_ref()

    AVFrame 常用的 API

    • AVFrame *av_frame_alloc(void);: 分配一个AVFrame,并将其字段设置为默认值。通过这个API分类的Packet必须由av_frame_free()释放
    • void av_frame_free(AVFrame **frame);: 释放掉AVFrame,如果这个Frame有引用的AVBuffer,将会先释放引用。
    • int av_frame_ref(AVFrame *dst, const AVFrame *src): 增加引用计数
    • void av_frame_unref(AVFrame *frame);: 减少引用计数
    • void av_frame_move_ref(AVFrame *dst, AVFrame *src);: 将src中的每个字段移动到dst,并重置src
    • int av_frame_get_buffer(AVFrame *frame, int align);: 为音频或视频数据分配新的缓冲区。在调用这个字段之前必须在音视频帧上设置下面的字段
    • format (pixel[像素] format for video, sample format[采样格式]for audio)
    • width and height for video
    • nb_samples and channel_layout for audio

    代码示例

    示例 1

    void av_packet_test1()
    {
        AVPacket * pkt = NULL;
        int ret = 0;
    
        //分配一个AVPacket
        pkt = av_packet_alloc();
        //给AVPacket分配内存,这里引用计数将会+1
    //    int av_new_packet(AVPacket *pkt, int size);
        ret = av_new_packet(pkt,MEM_ITEM_SIZE);  //始化字段,还为data分配了存储空间 如果成功就返回0
        /*
             void * memccpy(void *dest, const void * src, int c, size_t n);
            函数说明:memccpy()用来拷贝src 所指的内存内容前n 个字节到dest 所指的地址上。
         * */
        memccpy(pkt->data,(void *)&av_packet_test1,1,MEM_ITEM_SIZE);
    
        //减少引用计数,只有当引用计数为0时,才调用av_packet_free()时释放data的缓存。
        av_packet_unref(pkt);
        //释放掉packet,如果packet被还引用计数,它将首先被取消引用。
        av_packet_free(&pkt);
    }

    通过调试可以发现, 在新建packet的时候buf是空的,只有当计数器置为1的时候才会给buf分配内存。当执行av_packet_unrefpktbuf就会置空,此时计数器 -1.

    此外。通过查看源码可以发现其实av_packet_free内部已经调用了av_packet_unref,所以程序中第 28 行可以不调用,但是如果重复调用av_packet_unref也并不会出问题,av_packet_unref内部的av_buffer_unref函数中对buf进行了判断,如果buf已经为空就会直接return回去。

    示例 2

    void av_packet_test2()
    {
        AVPacket *pkt = NULL;
        int ret = 0;
    
        pkt = av_packet_alloc();
        ret = av_new_packet(pkt,MEM_ITEM_SIZE);
        memccpy(pkt->data,(void *)&av_packet_test2,1,MEM_ITEM_SIZE);
    //    av_init_packet(pkt);  //这个时候init就会导致内存无法释放
        av_packet_free(&pkt);
    }

    如果free之前调用了initinit会把pktbuf置空,free中也会调用init。(void av_init_packet仅仅是把pkt的参数设为默认值,要求pkt的内存已经分配好了,如果为NULL,则此处会崩溃)。

    示例3

    void av_frame_test1()
    {
        AVFrame *frame = NULL;
        int ret = 0;
    
        //分配一个AVFrame
        frame = av_frame_alloc();
    
        /*在调用av_frame_get_buffer这个字段之前必须在音视频帧上设置下面的字段*/
        frame->format = AV_SAMPLE_FMT_S16;//      //AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
        frame->channel_layout = AV_CH_LAYOUT_STEREO;//单声道    //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO(立体声 双声道)
        frame->nb_samples = 1024;    //1024 * 2 * (16/8)
    
    
        ret = av_frame_get_buffer(frame, 0);    // 为音频或视频数据分配新的缓冲区
        if(frame->buf && frame->buf[0])
            printf("%s(%d) 1 frame->buf[0]->size = %d
    ", __FUNCTION__, __LINE__, frame->buf[0]->size);    //受frame->format等参数影响
    
        //AV_SAMPLE_FMT_S16P格式 buf[1]才不为空
        if(frame->buf && frame->buf[1])
            printf("%s(%d) 1 frame->buf[1]->size = %d
    ", __FUNCTION__, __LINE__, frame->buf[1]->size);    //受frame->format等参数影响
    
        if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
            printf("%s(%d) ref_count1(frame) = %d
    ", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
    
        ret = av_frame_make_writable(frame);    // 当frame本身为空时不能make writable
        printf("av_frame_make_writable ret = %d
    ", ret);
    
        if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
            printf("%s(%d) ref_count2(frame) = %d
    ", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
    
        av_frame_unref(frame);
        if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
            printf("%s(%d) ref_count3(frame) = %d
    ", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
    
        av_frame_free(&frame);
    }

    运行结果:

    自己也在学习阶段,以上仅是自己的理解和总结,有不对的地方欢迎指正
    from:https://zhuanlan.zhihu.com/p/349149227
  • 相关阅读:
    Mybatis 原始dao CRUD方法
    JQuery的焦点事件focus() 与按键事件keydown() 及js判断当前页面是否为顶级页面 子页面刷新将顶级页面刷新 window.top.location
    使用actionerror做失败登录验证
    Java项目中的下载 与 上传
    shiro框架 4种授权方式 说明
    javascript 中数组的创建 添加 与将数组转换成字符串 页面三种提交请求的方式
    序列化表单为json对象,datagrid带额外参提交一次查询 后台用Spring data JPA 实现带条件的分页查询 多表关联查询
    Spring data JPA 理解(默认查询 自定义查询 分页查询)及no session 三种处理方法
    orcal 数据库 maven架构 ssh框架 的全注解环境模版 maven中央仓库批量删除lastupdated文件后依然是lastupdated解决方法 mirror aliyun中央仓库
    EasyUI加zTree使用解析 easyui修改操作的表单回显方法 验证框提交表单前验证 datagrid的load方法
  • 原文地址:https://www.cnblogs.com/lidabo/p/15030934.html
Copyright © 2011-2022 走看看