FFmpeg 内存模型分析

news2024/11/25 10:44:41

标题

  • 1. 内存模型图
  • 2. 分析流程
  • 3.追溯本源————源码分析
    • 3.1 AVPacket队列 什么时候生成的?
  • 4 .AVPacket和AVFrame相关操作API
  • 5. av_read_frame源码分析

1. 内存模型图

在这里插入图片描述

2. 分析流程

我们解复用后,媒体流数据就会被分离开来,分别生成对应AVPacketList,然后通过av_read_frame读取一个AVPacket来读取,然而如果有一个新的AVPacket也想要指向之前的AVPacket,那么内存模型应该是什么样子的呢?

通过思考应该有2种模型:
1.两个AVPacket同时指向同一块数据区域
2.先拷贝之前的数据区域,然后再给给新的AVPacket使用

在这里插入图片描述
由于我们数据区域是只读的,因此我们可以采用第一种方案比较稳妥,但是又迎来的新的问题,如果我们avpacket释放了数据,那么数据就会消失,那么avpacket2就会失效,那么该如何解决呢?
如果有朋友了解过C++的智能指针设计的话,那么就知道这种情况改如何设计了.
我们通过引入一个变量来标记该数据被多少AVPacket所使用,当该变量为1时释放就会真正的释放掉改数据.
在这里插入图片描述

3.追溯本源————源码分析

3.1 AVPacket队列 什么时候生成的?

在这里插入图片描述
我们先看一下上面这个流程.
我们仔细阅读一下avformat_open_input这个函数.

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    if (!s->av_class) {
        av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
        return AVERROR(EINVAL);
    }
    if (fmt)
        s->iformat = fmt;

    if (options)
        av_dict_copy(&tmp, *options, 0);

    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;

    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;

    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;

    if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
        s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
        if (!s->protocol_whitelist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
        s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
        if (!s->protocol_blacklist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
        av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
        ret = AVERROR(EINVAL);
        goto fail;
    }

    avio_skip(s->pb, s->skip_initial_bytes);

    /* Check filename in case an image number is expected. */
    if (s->iformat->flags & AVFMT_NEEDNUMBER) {
        if (!av_filename_number_test(filename)) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
    }

    s->duration = s->start_time = AV_NOPTS_VALUE;

    /* Allocate private data. */
    if (s->iformat->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (s->iformat->priv_class) {
            *(const AVClass **) s->priv_data = s->iformat->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

    /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);


    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;

    if (!s->metadata) {
        s->metadata = s->internal->id3v2_meta;
        s->internal->id3v2_meta = NULL;
    } else if (s->internal->id3v2_meta) {
        int level = AV_LOG_WARNING;
        if (s->error_recognition & AV_EF_COMPLIANT)
            level = AV_LOG_ERROR;
        av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&s->internal->id3v2_meta);
        if (s->error_recognition & AV_EF_EXPLODE) {
            ret = AVERROR_INVALIDDATA;
            goto close;
        }
    }

    if (id3v2_extra_meta) {
        if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
            !strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
            if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_priv(s, &id3v2_extra_meta)) < 0)
                goto close;
        } else
            av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
    }
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);

    if ((ret = avformat_queue_attached_pictures(s)) < 0)
        goto close;

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
        s->internal->data_offset = avio_tell(s->pb);

    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    update_stream_avctx(s);

    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

close:
    if (s->iformat->read_close)
        s->iformat->read_close(s);
fail:
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    av_dict_free(&tmp);
    if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
        avio_closep(&s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

其中关键的是:

    if ((ret = avformat_queue_attached_pictures(s)) < 0)
        goto close;

avformat_open_input调用了avformat_queue_attached_pictures这个函数,那么我看一下这个函数是干嘛的?

int avformat_queue_attached_pictures(AVFormatContext *s)
{
    int i, ret;
    for (i = 0; i < s->nb_streams; i++)
        if (s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC &&
            s->streams[i]->discard < AVDISCARD_ALL) {
            if (s->streams[i]->attached_pic.size <= 0) {
                av_log(s, AV_LOG_WARNING,
                    "Attached picture on stream %d has invalid size, "
                    "ignoring\n", i);
                continue;
            }

            ret = ff_packet_list_put(&s->internal->raw_packet_buffer,
                                     &s->internal->raw_packet_buffer_end,
                                     &s->streams[i]->attached_pic,
                                     FF_PACKETLIST_FLAG_REF_PACKET);
            if (ret < 0)
                return ret;
        }
    return 0;
}

根据函数很明显可以看出这个函数是对每一个流进行调用ff_packet_list_put构建AVPacketList链表.
我们仔细看一下ff_packet_list_put这个函数:

int ff_packet_list_put(AVPacketList **packet_buffer,
                       AVPacketList **plast_pktl,
                       AVPacket      *pkt, int flags)
{
    AVPacketList *pktl = av_mallocz(sizeof(AVPacketList));
    int ret;

    if (!pktl)
        return AVERROR(ENOMEM);

    if (flags & FF_PACKETLIST_FLAG_REF_PACKET) {
        if ((ret = av_packet_ref(&pktl->pkt, pkt)) < 0) {
            av_free(pktl);
            return ret;
        }
    } else {
        // TODO: Adapt callers in this file so the line below can use
        //       av_packet_move_ref() to effectively move the reference
        //       to the list.
        pktl->pkt = *pkt;
    }

    if (*packet_buffer)
        (*plast_pktl)->next = pktl;
    else
        *packet_buffer = pktl;

    /* Add the packet in the buffered packet list. */
    *plast_pktl = pktl;
    return 0;
}

这个函数其实就是一个链表的尾插函数,每次都创建一个AVPacketList,而AVPacketList结构体中封装的是:AVPacket

typedef struct AVPacketList {
    AVPacket pkt;
    struct AVPacketList *next;
} AVPacketList;

那么到这里我们就回答了一开始标题的问题了.

4 .AVPacket和AVFrame相关操作API

在这里插入图片描述
在这里插入图片描述

5. av_read_frame源码分析

int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{
    const int genpts = s->flags & AVFMT_FLAG_GENPTS;
    int eof = 0;
    int ret;
    AVStream *st;

    if (!genpts) {
        ret = s->internal->packet_buffer
              ? ff_packet_list_get(&s->internal->packet_buffer,
                                        &s->internal->packet_buffer_end, pkt)
              : read_frame_internal(s, pkt);
        if (ret < 0)
            return ret;
        goto return_packet;
    }

    for (;;) {
        AVPacketList *pktl = s->internal->packet_buffer;

        if (pktl) {
            AVPacket *next_pkt = &pktl->pkt;

            if (next_pkt->dts != AV_NOPTS_VALUE) {
                int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;
                // last dts seen for this stream. if any of packets following
                // current one had no dts, we will set this to AV_NOPTS_VALUE.
                int64_t last_dts = next_pkt->dts;
                av_assert2(wrap_bits <= 64);
                while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {
                    if (pktl->pkt.stream_index == next_pkt->stream_index &&
                        av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2ULL << (wrap_bits - 1)) < 0) {
                        if (av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2ULL << (wrap_bits - 1))) {
                            // not B-frame
                            next_pkt->pts = pktl->pkt.dts;
                        }
                        if (last_dts != AV_NOPTS_VALUE) {
                            // Once last dts was set to AV_NOPTS_VALUE, we don't change it.
                            last_dts = pktl->pkt.dts;
                        }
                    }
                    pktl = pktl->next;
                }
                if (eof && next_pkt->pts == AV_NOPTS_VALUE && last_dts != AV_NOPTS_VALUE) {
                    // Fixing the last reference frame had none pts issue (For MXF etc).
                    // We only do this when
                    // 1. eof.
                    // 2. we are not able to resolve a pts value for current packet.
                    // 3. the packets for this stream at the end of the files had valid dts.
                    next_pkt->pts = last_dts + next_pkt->duration;
                }
                pktl = s->internal->packet_buffer;
            }

            /* read packet from packet buffer, if there is data */
            st = s->streams[next_pkt->stream_index];
            if (!(next_pkt->pts == AV_NOPTS_VALUE && st->discard < AVDISCARD_ALL &&
                  next_pkt->dts != AV_NOPTS_VALUE && !eof)) {
                ret = ff_packet_list_get(&s->internal->packet_buffer,
                                               &s->internal->packet_buffer_end, pkt);
                goto return_packet;
            }
        }

        ret = read_frame_internal(s, pkt);
        if (ret < 0) {
            if (pktl && ret != AVERROR(EAGAIN)) {
                eof = 1;
                continue;
            } else
                return ret;
        }

        ret = ff_packet_list_put(&s->internal->packet_buffer,
                                 &s->internal->packet_buffer_end,
                                 pkt, FF_PACKETLIST_FLAG_REF_PACKET);
        av_packet_unref(pkt);
        if (ret < 0)
            return ret;
    }

return_packet:

    st = s->streams[pkt->stream_index];
    if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY) {
        ff_reduce_index(s, st->index);
        av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, AVINDEX_KEYFRAME);
    }

    if (is_relative(pkt->dts))
        pkt->dts -= RELATIVE_TS_BASE;
    if (is_relative(pkt->pts))
        pkt->pts -= RELATIVE_TS_BASE;

    return ret;
}

有了前面的知识铺垫,不难分析出read_frame_internal是调用ff_packet_list_put来获取AVPacket的,但是这里要注意ff_packet_list_put并不是简单的将AVPacket给read_frame_internal。

int ff_packet_list_get(AVPacketList **pkt_buffer,
                       AVPacketList **pkt_buffer_end,
                       AVPacket      *pkt)
{
    AVPacketList *pktl;
    av_assert0(*pkt_buffer);
    pktl        = *pkt_buffer;
    *pkt        = pktl->pkt;
    *pkt_buffer = pktl->next;
    if (!pktl->next)
        *pkt_buffer_end = NULL;
    av_freep(&pktl);
    return 0;
}

根据源码可以得出,ff_packet_list_get先通过 *pkt = pktl->pkt; 将原来的数据直接拷贝给pkt,然后就释放掉了pkt1,也就是AVPacketList链表上的一个节点。那么这时候链表上就会消失一个AVPacket,这样做的好出就是,会将AVPacketList链表中的AVPacket慢慢释放掉,从而减少内存占用!

对于AVFrame相关API详解,我留到介绍编解码API流程时详细介绍,敬请期待~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651957.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BART论文解读

1 概述 全称&#xff1a;Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension。BART来源于Bidirectional and Auto-Regressive Transformers发表时间: 2019.10.29团队&#xff1a;Facebook AI Paper地址​arxiv.o…

chatgpt赋能python:Python怎么求解方程

Python怎么求解方程 在数学中&#xff0c;求解方程是一种基本的技能。Python作为一种广泛应用于科学计算和数据分析领域的编程语言&#xff0c;可以帮助我们求解各种类型的方程。Python提供了多个库和函数&#xff0c;使得求解方程在Python中变得非常轻松。 一元方程求解 一…

Android Framework分析SystemServer进程

SystemServer进程是Android系统的核心进程&#xff0c;运行在Android系统启动后&#xff0c;负责管理和加载系统服务。本文将介绍SystemServer进程的详细结构和工作原理&#xff0c;并使用代码注释的方式阐述其关键部分代码。 结构&#xff1a; SystemServer进程的核心是Syste…

SSH基本概念,带你了解SSH

1、SSH基本概念 SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于在不安全的网络中安全地传输数据。它是一种加密协议&#xff0c;可以保护数据在传输过程中不被窃取、篡改或伪造。SSH协议最初是由芬兰的Tatu Ylonen开发的&#xff0c;现在已经成为了一…

Hadoop集群之模板虚拟机的安装

Hadoop集群之模板虚拟机的安装 文章目录 Hadoop集群之模板虚拟机的安装0. 写在前面1. CentOS的安装1.1 配置电脑1.1.1 进入VMware1.1.2 自定义新的虚拟机1.1.3 解决虚拟机的兼容性1.1.4 选择当前虚拟机的操作系统1.1.5 选择虚拟机将来需要安装的系统1.1.6 电脑的具体配置1.1.7 …

KUKA机器人通过示教器进行关机冷启动的具体方法演示

KUKA机器人通过示教器进行关机冷启动的具体方法演示 如下图所示,首先需要登录管理员权限,默认密码:KUKA,然后点击左上角的机器人图标进行菜单选项,找到并点击“关机”选项, 如下图所示,找到并点击“重新启动控制系统PC”, 如下图所示,此时系统提示:确实要重新启动…

chatgpt赋能python:Python生成序列的方法详解

Python生成序列的方法详解 在Python编程中&#xff0c;序列&#xff08;Sequence&#xff09;是常用的数据类型之一。序列是有序的&#xff0c;可以通过下标访问其中的元素。Python中有多种方法可以生成序列&#xff0c;下面将对常用的几种方法进行详细介绍。 利用range函数生…

2023-06-16 Android Studio 使用CMakeList编译JNI ,最简单的demo源码

一、代码结构图&#xff0c;代码路径https://download.csdn.net/download/qq_37858386/87913001 二、cmakedemo\app\build.gradle 加下面的代码 externalNativeBuild {cmake {cppFlags "-frtti -fexceptions"}}externalNativeBuild {cmake {path src/main/jni/CMakeL…

国内大模型研究

自从chatgpt发布以来&#xff0c;国内大模型发展非常迅速。我对这项目技术也保持了非常多的持续关注&#xff0c;我一直认识&#xff0c;chatGPT以及其他GPT会给社会带来更大的变革。经过专业训练的大模型可以替代部分客服&#xff0c;部分程序员&#xff0c;部分美工&#xff…

Vue全家桶实战 从零独立开发企业级电商系统(免费升级Vue3.0)

Vue全家桶高仿小米商城–项目简介 文章目录 Vue全家桶高仿小米商城--项目简介电商项目选型--小米商城的页面流程&#xff1a;业务开发流程&#xff1a;项目内容&#xff1a;商城组件部分&#xff1a;课程所包含的知识图谱&#xff1a;章节介绍&#xff1a; 商城的界面展示&…

基于web漏洞扫描及分析系统设计_kaic

基于web漏洞扫描及分析系统设计 摘 要 随着信息技术的发展和网络应用在我国的普及&#xff0c;针对我国境内信息系统的恶意网络攻击也越来越多&#xff0c;并且随着黑客攻击技术的不断地更新&#xff0c;网络犯罪行为变得越来越难以应对&#xff0c;用户日常访问的网站是否安全…

【八大排序(六)】快排终极篇-快速排序非递归版

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:八大排序专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习排序知识   &#x1f51d;&#x1f51d; 快排非递归版 1. 前情回顾2. 快排非递归基…

A100 GPU服务器安装CUDNN教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

从Window中先多瞥几眼

JavaFx17官方文档中有如下的描述: Window类是一个顶层窗口类,在其中可以承载场景,并与用户交互。窗口可以是Stage、PopupWindow或其他类似的顶层窗口。 JavaFX Stage类是顶级的JavaFX容器。初级阶段由平台搭建。其他Stage对象可以由应用程序构造。 许多Stage属性是只读的…

chatgpt赋能python:Python入门:如何添加Seaborn库

Python入门&#xff1a;如何添加Seaborn库 Python是一种易于学习的、功能强大的编程语言&#xff0c;在数据分析和科学计算方面尤其闻名。Seaborn是一个建立在Matplotlib之上的Python可视化库&#xff0c;它提供了一组简单易用的界面&#xff0c;用于绘制优美的统计图形——包…

几个Caller-特性的妙用

System.Runtime.CompilerServices命名空间下有4个以“Caller”为前缀命名的Attribute&#xff0c;我们可以将它标注到方法参数上自动获取当前调用上下文的信息&#xff0c;比如当前的方法名、某个参数的表达式、当前源文件的路径&#xff0c;以及当前代码在源文件中的行号。 一…

RedisSon高并发分布式锁实战RedisSon源码解读

Redis高并发分布式锁实战 1.分布式场景下的synchronized失效的问题–用redis实现分布式锁 synchronized是通过monitor实现的jvm级别的锁&#xff0c;如果是分布式系统&#xff0c;跑在不同的虚拟机上的tomcat上&#xff0c;会导致synchronized无法锁住对象 ----------- 需要分…

读数据压缩入门笔记05_字典转换

1. 瓶颈 1.1. 在网络带宽有限、存储昂贵的时期 1.2. 移动设备正日益成为人们访问互联网的首选的今天 1.3. 数据压缩成了缓解这些瓶颈的关键 2. 字典转换 2.1. dictionary transforms 2.2. 完全改变了人们对数据压缩的认知 2.2.1. 压缩变成了一种对各种类型的数据都有用的…

《C和指针》读书笔记(第十一章 动态内存分配)

目录 0 简介1 为什么使用动态内存分配2 malloc和free3 calloc和realloc4 使用动态分配的内存5 常见的动态内存错误6 内存分配实例6.1 排序一列整型值6.2 复制字符串6.3 变体记录的创建与销毁 7 总结 0 简介 在实际开发中&#xff08;C语言&#xff09;&#xff0c;数组的元素存…

JDK,JRE,JVM有什么区别?跨平台?跨语言?

JDK Java Development Kit&#xff08;Java开发工具包&#xff09;&#xff0c;提供了Java的开发环境和运行环境。包含Java源文件的编译器Javac&#xff0c;还有调试和分析工具。 JRE Java Runtime Environment&#xff08;Java运行环境&#xff09;包含了Java虚拟机&#xff…