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
  • 相关阅读:
    python 进程通信,共享变量
    使用 curl 和 xargs 命令批量删除 ES索引,并将一些不想删除的索引过滤出来
    Spark Over Yarn 发现输出到kafka报错: topic not present on metadata after x
    ES 同台机器多实例报 master not discovered or elected yet
    ES数据写入时间格式问题
    Kibana 发现数据时间不对
    python包部署到服务器上报 cannot find module error
    好久没写博客了,。。。。。
    linux 修改 elf 文件的dynamic linker 和 rpath
    linux 进程 进程组 作业 会话 控制终端
  • 原文地址:https://www.cnblogs.com/lidabo/p/15030934.html
Copyright © 2011-2022 走看看