• ffmpeg中AVPacket与AVFrame中数据的传递与释放


    总结了一下AVPacket与AVFrame中拷贝和释放相关操作。这里我理解在AVFrame与AVPacket传递流转的过程中并不会去每次创建和拷贝音视频数据,音视频数据被存储在AVBuffer中,而AVFrame与AVPacket在流转时进行浅拷贝,只有调用其对应unref时,会减少AVBuffer中的引用计数,最终释放内部存储音视频数据的buffer。

    目录

    1.av_freep释放并置空双重指针指向的那个指针

    2.av_packet_unref

    3.av_packet_ref

    4.av_packet_move_ref 

    5.av_packet_alloc与av_packet_free

    6.av_free

    7.av_frame_unref

    8.av_frame_ref

    9.av_frame_move_ref

    10.av_frame_alloc与av_frame_free

    1.av_freep释放并置空双重指针指向的那个指针
    释放内存并将指针置空

    void av_freep(void *arg)
    {
        void *val;
        //记录arg值
        memcpy(&val, arg, sizeof(val));
        //arg 指向的的首地址置空 
        memcpy(arg, &(void *){ NULL }, sizeof(val));
        //真正释放内存
        av_free(val);
    }
    示例

    static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
    {
        …
        if (src) {
            **dst = **src;
            //释放 AVBufferRef **src 中指针指向的内容并将这个指针置空
            //这里并没有释放AVBufferRef中的指针指向的内存
            av_freep(src);
        } 
        …
    }


    2.av_packet_unref
    释放pkt中在堆上的指针结构,复位内部指针,pkt中真正的音视频buffer没有被释放。结果相当于传入的这个AVPacket指针被掏空,音视频数据依然可能被其他AVPacket持有。真正释放AVPacket中音视频数据buffer的操作也应该是在这里。

    void av_packet_unref(AVPacket *pkt)
    {
        //置空packet中side_data相关指针,释放相关结构(不释放结构中指针指向的内容)
        av_packet_free_side_data(pkt);
        //释放pkt->buf结构,不释放这个结构指向的内容
        //buf的refcount原子减,如果AVBuffer引用计数为1则调用AVBuffer的free
        av_buffer_unref(&pkt->buf);
        //复位packet内部指针
        av_init_packet(pkt);
        pkt->data = NULL;
        pkt->size = 0;
    }
    示例:

    ffplay中释放AVPacket

    static void packet_queue_flush(PacketQueue *q)
    {
        MyAVPacketList *pkt, *pkt1;
        ...
        for (pkt = q->first_pkt; pkt; pkt = pkt1) {
            pkt1 = pkt->next;
            //减少引用计数,符合要求时删除buffer
            av_packet_unref(&pkt->pkt);
            av_freep(&pkt);
        }
        ...
    }


    3.av_packet_ref
    有了av_packet_unref的经验,理解av_packet_ref会相对容易一些。这个函数最终的效果是给dst的data浅拷贝一个值,还会给当前buffer增加引用计数。AVBuffer结构中存储了真的音视频数据,并且维护一个引用计数。

    struct AVBuffer {
        uint8_t *data; /**< data described by this buffer */
        int      size; /**< size of data in bytes */
        atomic_uint refcount;
        void (*free)(void *opaque, uint8_t *data);
        void *opaque;
        int flags;
    };
    下面看一下av_packet_ref的实现。

    int av_packet_ref(AVPacket *dst, const AVPacket *src)
    {
        int ret;
     
        //拷贝各种属性值,创建side_data指针并将src中的值赋值给它
        ret = av_packet_copy_props(dst, src);
        if (ret < 0)
            return ret;
     
        if (!src->buf) {
            //如果src的buf为空 des的buf创建一块空间 把src的data拷贝给这块空间
            //创建buf指针并拷贝数据内容
            ret = packet_alloc(&dst->buf, src->size);
            if (ret < 0)
                goto fail;
            if (src->size)
                memcpy(dst->buf->data, src->data, src->size);
            //数据浅拷贝给dst
            dst->data = dst->buf->data;
        } else {
            //否则 src的 buffer->refcount 原子+1
            dst->buf = av_buffer_ref(src->buf);
            if (!dst->buf) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            //数据浅拷贝给dst
            dst->data = src->data;
        }
     
        dst->size = src->size;
     
        return 0;
    fail:
        av_packet_free_side_data(dst);
        return ret;
    }


    4.av_packet_move_ref 
    浅拷贝一切src的一切数据,掏空src,操作不改变AVBuffer的引用计数。ffplay在AVPacket加入队列的时候使用=给结构体赋值。

    void av_packet_move_ref(AVPacket *dst, AVPacket *src)
    {
        *dst = *src;
        av_init_packet(src);
        src->data = NULL;
        src->size = 0;
    }


    5.av_packet_alloc与av_packet_free
    av_packet_free清空当前AVPacket结构体指针,并减少引用计数,代表当前这个AVPacket结构体被释放了,里面的音视频数据可能还在,需要等待最后一个引用这些音视频数据的AVPacket被销毁。如果直接在栈上定义AVPaceket结构体实例,使用av_packet_move_ref或者av_packet_ref赋值,用过之后av_packet_unref一下即可。

    AVPacket *av_packet_alloc(void)
    {
        AVPacket *pkt = av_mallocz(sizeof(AVPacket));
        if (!pkt)
            return pkt;
     
        av_packet_unref(pkt);
     
        return pkt;
    }
     
    void av_packet_free(AVPacket **pkt)
    {
        if (!pkt || !*pkt)
            return;
     
        av_packet_unref(*pkt);
        av_freep(pkt);
    }
    区别av_free_packet

    这个方法目前看已经废弃,ffplay里也没有搜到调用

    #if FF_API_AVPACKET_OLD_API
    FF_DISABLE_DEPRECATION_WARNINGS
    void av_free_packet(AVPacket *pkt)
    {
        if (pkt) {
            if (pkt->buf)
                av_buffer_unref(&pkt->buf);
            pkt->data            = NULL;
            pkt->size            = 0;
     
            av_packet_free_side_data(pkt);
        }
    }
    FF_ENABLE_DEPRECATION_WARNINGS
    #endif


    6.av_free
    对应malloc

    void av_free(void *ptr)
    {
    #if HAVE_ALIGNED_MALLOC
        _aligned_free(ptr);
    #else
        free(ptr);
    #endif
    }


    7.av_frame_unref
    这里减少了当前AVFrame中各种buffer的数据引用,同时将当前传入的frame掏空。这样看真正释放AVFrame音视频数据的地方也应该在这里。

    void av_frame_unref(AVFrame *frame)
    {
        int i;
     
        if (!frame)
            return;
     
        wipe_side_data(frame);
     
        for (i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++)
            av_buffer_unref(&frame->buf[i]);
        for (i = 0; i < frame->nb_extended_buf; i++)
            av_buffer_unref(&frame->extended_buf[i]);
        av_freep(&frame->extended_buf);
        av_dict_free(&frame->metadata);
    #if FF_API_FRAME_QP
        av_buffer_unref(&frame->qp_table_buf);
    #endif
     
        av_buffer_unref(&frame->hw_frames_ctx);
     
        av_buffer_unref(&frame->opaque_ref);
        //置空当前frame中的值
        get_frame_defaults(frame);
    }


    8.av_frame_ref
    主要是src各种浅拷贝加buffer赋值。要想把一个现有的AVFrame复制给新建的,可以使用这个方法,相当于直接浅拷贝并增加引用计数了。

    int av_frame_ref(AVFrame *dst, const AVFrame *src)
    {
        int i, ret = 0;
     
        av_assert1(dst->width == 0 && dst->height == 0);
        av_assert1(dst->channels == 0);
     
        dst->format         = src->format;
        dst->width          = src->width;
        dst->height         = src->height;
        dst->channels       = src->channels;
        dst->channel_layout = src->channel_layout;
        dst->nb_samples     = src->nb_samples;
     
        ret = frame_copy_props(dst, src, 0);
        if (ret < 0)
            return ret;
     
        /* duplicate the frame data if it's not refcounted */
        if (!src->buf[0]) {
            ret = av_frame_get_buffer(dst, 32);
            if (ret < 0)
                return ret;
     
            ret = av_frame_copy(dst, src);
            if (ret < 0)
                av_frame_unref(dst);
     
            return ret;
        }
     
        /* ref the buffers */
        for (i = 0; i < FF_ARRAY_ELEMS(src->buf); i++) {
            if (!src->buf[i])
                continue;
            dst->buf[i] = av_buffer_ref(src->buf[i]);
            if (!dst->buf[i]) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        }
     
        if (src->extended_buf) {
            dst->extended_buf = av_mallocz_array(sizeof(*dst->extended_buf),
                                           src->nb_extended_buf);
            if (!dst->extended_buf) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            dst->nb_extended_buf = src->nb_extended_buf;
     
            for (i = 0; i < src->nb_extended_buf; i++) {
                dst->extended_buf[i] = av_buffer_ref(src->extended_buf[i]);
                if (!dst->extended_buf[i]) {
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
            }
        }
     
        if (src->hw_frames_ctx) {
            dst->hw_frames_ctx = av_buffer_ref(src->hw_frames_ctx);
            if (!dst->hw_frames_ctx) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        }
     
        /* duplicate extended data */
        if (src->extended_data != src->data) {
            int ch = src->channels;
     
            if (!ch) {
                ret = AVERROR(EINVAL);
                goto fail;
            }
            CHECK_CHANNELS_CONSISTENCY(src);
     
            dst->extended_data = av_malloc_array(sizeof(*dst->extended_data), ch);
            if (!dst->extended_data) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            memcpy(dst->extended_data, src->extended_data, sizeof(*src->extended_data) * ch);
        } else
            dst->extended_data = dst->data;
     
        memcpy(dst->data,     src->data,     sizeof(src->data));
        memcpy(dst->linesize, src->linesize, sizeof(src->linesize));
     
        return 0;
     
    fail:
        av_frame_unref(dst);
        return ret;
    }


    9.av_frame_move_ref
    掏空一个AVFrame转移数据到另一个,不涉及引用计数变化。

    void av_frame_move_ref(AVFrame *dst, AVFrame *src)
    {
        av_assert1(dst->width == 0 && dst->height == 0);
        av_assert1(dst->channels == 0);
     
        *dst = *src;
        if (src->extended_data == src->data)
            dst->extended_data = dst->data;
        memset(src, 0, sizeof(*src));
        get_frame_defaults(src);
    }


    10.av_frame_alloc与av_frame_free
    av_frame_alloc与av_frame_free配合使用。av_frame_alloc之后需要av_frame_get_buffer真正开辟空间,av_frame_free会减少这些空间的引用计数。

    AVFrame *av_frame_alloc(void)
    {
        AVFrame *frame = av_mallocz(sizeof(*frame));
     
        if (!frame)
            return NULL;
     
        frame->extended_data = NULL;
        get_frame_defaults(frame);
     
        return frame;
    }
     
    void av_frame_free(AVFrame **frame)
    {
        if (!frame || !*frame)
            return;
     
        av_frame_unref(*frame);
        av_freep(frame);
    }
    上面av_frame_alloc并没有给AVFrame中buffer增加数据,需要进行如下操作申请buffer并为其增加音视频数据。而av_frame_free中av_frame_unref会去减少这些内存的引用并在最后释放这些内存。

        frame->format = c->pix_fmt;
        frame->width  = c->width;
        frame->height = c->height;
        //这里初始化了AVFrame中的buffer
        ret = av_frame_get_buffer(frame, 32);
     
        //确保当前frame可写 如果不可写的情况下会给frame搞一块新的buffer
        ret = av_frame_make_writable(frame);
        if (ret < 0)
            exit(1);
     
        //给AVFrame中buffer赋值
        for (y = 0; y < c->height; y++) {
            for (x = 0; x < c->width; x++) {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

  • 相关阅读:
    Office 2010激活 NO KMS products detected问题
    强制换行/不换行 (兼容IE)
    el-radio再次点击取消选中
    几个 JavaScript 实用小技巧
    微信小程序-点击事件传递参数
    微信小程序-存取本地缓存
    微信小程序-路由方式
    element-ui 表格排序失效
    watch监听(数组或者对象)
    vue 引入 base64或者md5对密码进行加密
  • 原文地址:https://www.cnblogs.com/lidabo/p/15897703.html
Copyright © 2020-2023  润新知