当前位置: 首页 > news >正文

FFmpeg 中 AVPacket 与 AVFrame 中数据的传递与释放

总结了一下AVPacket与AVFrame中拷贝和释放相关操作。

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

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;
        }
    }

作者:vonchenchen1

来源:https://blog.csdn.net/lidec/article/details/118998366

11eff077973314163d96f02f0af00a7a.png

628125ec395774cc637be070653087d4.png

一个音视频领域专业问答的小圈子!

加我微信 ezglumes 拉你入技术交流群

推荐阅读:

音视频开发工作经验分享 || 视频版

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

Android NDK 免费视频在线学习!!!

你想要的音视频开发资料库来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

2d041efa05be1a2b142f4ec507ab1d8b.gif

相关文章:

  • 头条都在用的边下边播方案
  • YUV 与 颜色空间转换
  • 原创干货 | 入门或者转行音视频,应该要怎么做?
  • 上手 GAMES 104 课程 Pilot 游戏引擎~~
  • 关于音视频里面的 解码帧率 和 渲染帧率
  • 将音视频中的花屏、绿屏、黑屏问题一网打尽
  • 关于直播、WebRTC、FFmpeg 的那些事~~
  • 干货 | 快速抽取缩略图是怎么练成的?
  • 没有操作系统程序可以运行起来吗?
  • 重学音视频?认识 MP4 视频(上)
  • 重学音视频?认识 MP4 视频(下)
  • 关于 Android 渲染你应该了解的知识点
  • Android 视频编辑解析库 MP4Parser
  • 动态图片加两条白杠就能营造出 3D 效果 ?
  • FFmepg 中错误码的玄机!
  • [数据结构]链表的实现在PHP中
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • C++类的相互关联
  • CODING 缺陷管理功能正式开始公测
  • Java IO学习笔记一
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • LeetCode18.四数之和 JavaScript
  • Vue全家桶实现一个Web App
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 二维平面内的碰撞检测【一】
  • 翻译--Thinking in React
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 聚类分析——Kmeans
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • ​一些不规范的GTID使用场景
  • #预处理和函数的对比以及条件编译
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (Note)C++中的继承方式
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (五)网络优化与超参数选择--九五小庞
  • (一)RocketMQ初步认识
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET Core跨平台微服务学习资源
  • .NET 常见的偏门问题
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .NET简谈设计模式之(单件模式)
  • .NET开源项目介绍及资源推荐:数据持久层
  • .NET运行机制
  • @javax.ws.rs Webservice注解
  • []我的函数库
  • [Angular] 笔记 16:模板驱动表单 - 选择框与选项
  • [C#]C# winform部署yolov8目标检测的openvino模型
  • [CSAWQual 2019]Web_Unagi ---不会编程的崽