音视频入门基础:AAC专题(7)——FFmpeg源码中计算AAC裸流每个packet的size值的实现

=================================================================

音视频入门基础:AAC专题系列文章:

音视频入门基础:AAC专题(1)——AAC官方文档下载

音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件

音视频入门基础:AAC专题(3)——AAC的ADTS格式简介

音视频入门基础:AAC专题(4)——ADTS格式的AAC裸流实例分析

音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现

音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现

音视频入门基础:AAC专题(7)——FFmpeg源码中计算AAC裸流每个packet的size值的实现

音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础:AAC专题(9)——FFmpeg源码中计算AAC裸流每个packet的duration和duration_time的实现

音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现

=================================================================

一、引言

通过FFprobe命令:

ffprobe -of json -show_packets XXX.aac

可以显示AAC裸流每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的size:

这个“size”实际是AVPacket结构体中的成员变量size,为AVPacket的成员变量data指向的缓冲区的大小,也就是AAC裸流中某个packet的大小(单位为字节)。该值通过fftools/ffprobe.c中的show_packet函数打印出来:

static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...print_val("size",             pkt->size, unit_byte_str);
//...
}

本文讲述这个“size”值是怎样被计算出来的。如果想直接看结论,可以跳到本文的最后,直接看“总结”。对于AAC裸流,size值是通过adts_aac_read_packet函数计算出来的。

二、adts_aac_read_packet函数的定义

adts_aac_read_packet函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/aacdec.c中:

static int adts_aac_read_packet(AVFormatContext *s, AVPacket *pkt)
{int ret, fsize;retry:ret = av_get_packet(s->pb, pkt, ADTS_HEADER_SIZE);if (ret < 0)return ret;if (ret < ADTS_HEADER_SIZE) {return AVERROR(EIO);}if ((AV_RB16(pkt->data) >> 4) != 0xfff) {// Parse all the ID3 headers between framesint append = ID3v2_HEADER_SIZE - ADTS_HEADER_SIZE;av_assert2(append > 0);ret = av_append_packet(s->pb, pkt, append);if (ret != append) {return AVERROR(EIO);}if (!ff_id3v2_match(pkt->data, ID3v2_DEFAULT_MAGIC)) {av_packet_unref(pkt);ret = adts_aac_resync(s);} elseret = handle_id3(s, pkt);if (ret < 0)return ret;goto retry;}fsize = (AV_RB32(pkt->data + 3) >> 13) & 0x1FFF;if (fsize < ADTS_HEADER_SIZE) {return AVERROR_INVALIDDATA;}ret = av_append_packet(s->pb, pkt, fsize - pkt->size);return ret;
}

该函数的作用是:获取一个ADTS音频帧的数据,赋值给形参pkt指向的packet。

形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext对象。

形参pkt:输出型参数,指向一个AVPacket对象。执行adts_aac_read_packet函数后,pkt->data指向的的缓冲区会得到整个ADTS音频帧的数据,pkt->size会增至该ADTS音频帧的大小。

三、adts_aac_read_packet函数的内部实现分析

宏定义ADTS_HEADER_SIZE的值为7,表示不包含CRC校验的ADTS Header的长度:

#define ADTS_HEADER_SIZE 7

adts_aac_read_packet函数内部,首先通过av_get_packet函数从AVIOContext输入缓冲区或文件描述符中总共读取ADTS_HEADER_SIZE(7)个字节数据,也就是读取该音频帧的Header,追加到原来pkt->data指向的的缓冲区的尾部。关于av_get_packet函数的用法可以参考 《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》。如果实际读取到的大小小于7个字节,返回AVERROR(EIO)表示IO错误:

    ret = av_get_packet(s->pb, pkt, ADTS_HEADER_SIZE);if (ret < 0)return ret;if (ret < ADTS_HEADER_SIZE) {return AVERROR(EIO);}

由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS音频帧的adts_fixed_header中的syncword属性占12位,每个位都必须被设置为1。通过下面的if语句判断syncword属性的值是否正确,如果不正确,执行大括号内解析id3v2 header的流程。ID3v2是一系列元数据,里面存储了一些跟歌曲相关的信息(比如:演唱者、歌曲名、备注等)。关于AV_RB16宏定义的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:

    if ((AV_RB16(pkt->data) >> 4) != 0xfff) {// Parse all the ID3 headers between frames//...}

获取adts_variable_header中的aac_frame_length属性,即该ADTS音频帧的总长度(包含ADTS Header、错误校验和AAC原始数据块,单位为字节),赋值给变量fsize。由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),所以如果从上面得到的该ADTS音频帧的总长度小于7,表示ADTS Header格式不正确,返回AVERROR_INVALIDDATA:

    fsize = (AV_RB32(pkt->data + 3) >> 13) & 0x1FFF;if (fsize < ADTS_HEADER_SIZE) {return AVERROR_INVALIDDATA;}

通过av_append_packet函数,增加形参pkt指向的packet的大小至aac_frame_length个字节,让pkt->data指向的的缓冲区得到整个ADTS音频帧的数据。关于av_append_packet函数的用法可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》:

ret = av_append_packet(s->pb, pkt, fsize - pkt->size);

四、总结

1.AAC裸流每个packet的size值(该packet的大小,单位为字节)实际上是通过ADTS音频帧(adts音频压缩数据包)的adts_fixed_header中的aac_frame_length属性获取的。

2.如果仔细地观察,会发现AAC裸流每个packet的size值都不一样,这是因为ADTS音频帧存贮的是压缩后的音频数据。而WAV音频文件一般存贮的是PCM,也就是无压缩的原始音频数据,所以在存贮的是PCM数据的情况下,WAV音频文件每个packet的size值都是一样的。各位同学可以把本文跟《音视频入门基础:WAV专题(7)——FFmpeg源码中计算WAV音频文件每个packet的size值的实现》进行对比,以加深对音频帧size值的理解。

3.FFmpeg源码内部得到AAC裸流每个packet的size值是通过adts_aac_read_packet函数,但是解码ADTS Header获取里面信息(音频采样频率、声道数、采样位数等)是通过ff_adts_header_parse函数,关于ff_adts_header_parse函数可以参考:《音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现》。可以看到这两个函数有部分功能是重复了,重复获取了ADTS音频帧的大小。FFmpeg对AAC裸流解封装时,会重复解码ADTS Header导致性能损失。也就是说:FFmpeg为了架构的通用性、扩展性、兼容性和代码的可读性造成了代码的冗余和重复,所以为了极致的性能,比如更快的解封装速度,很多开发者会选择不依赖任何第三方库自己写解析代码,或者改FFmpeg的源码实现优化。各位同学可以尝试改FFmpeg源码,使得对AAC裸流进行解复用时,只解码一次ADTS Header,从而提高解复用速度。

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

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

相关文章

移动应用开发实验室web组js第一次考核

请简述var,let,const的区别 var var存在变量提升、暂时性死区可以重复赋值 let let不存在变量提升、暂时性死区块级作用域可以重复赋值 const const不存在变量提升、暂时性死区声明时必须定义值块级作用域 解释垃圾回收机制&#xff0c;垃圾回收的方式 垃圾回收机制 如…

【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL77

编写乘法器求解算法表达式 描述 编写一个4bit乘法器模块&#xff0c;并例化该乘法器求解c12*a5*b&#xff0c;其中输入信号a,b为4bit无符号数&#xff0c;c为输出。注意请不要直接使用*符号实现乘法功能。 模块的信号接口图如下&#xff1a; 要求使用Verilog HDL语言实现以上…

水下目标检测数据集 urpc2021

项目背景&#xff1a; 水下目标检测在海洋科学研究、水下考古、海洋资源勘探等多个领域具有重要的应用价值。由于水下环境的复杂性和多变性&#xff0c;传统的人工检测方法存在诸多限制&#xff0c;自动化检测技术的需求日益增加。URPC2021数据集旨在为水下目标检测提供高质量…

蔚来是如何算加电网络的「大账」的?

作者 | 张马也 编辑 | 德新 李斌很忙&#xff0c;连中秋假期也没休息&#xff0c;开着ES8在新疆喀什周边的县区考察。 这次考察的目的&#xff0c;是为了推进「加电县县通」计划的落地。蔚来在一个月前的加电日发布会&#xff0c;推出了这个大胆的计划&#xff0c;要实现全国县…

如何在webots中搭建一个履带机器人

前期准备 下载webotswebots基本知识 a. 官方文档:Webots documentation: Track b. B站教程:webots-超详细入门教程(2020)_哔哩哔哩_bilibili搭建流程 搭建履带机器人主要使用到了webots中的track节点,这个节点是专门用来定义履带的相关属性,模拟履带运动的 首先,创建一个…

C一语言—动态内存管理

目录 一、为什么要有动态内存管理 二、malloc和free &#xff08;2.1&#xff09;malloc &#xff08;2.2&#xff09;free 三、calloc和realloc &#xff08;3.1&#xff09;calloc &#xff08;3.2&#xff09;realloc 四、常见的动态内存的错误&#xff08;举例均为错…

深度学习实战93-基于BiLSTM-CRF模型的网络安全知识图谱实体识别应用

大家好,我是微学AI,今天给大家介绍一下深度学习实战93-基于BiLSTM-CRF模型的网络安全知识图谱实体识别应用。本文介绍了基于深度学习 BiLSTM-CRF 模型的网络安全知识图谱实体识别方法。首先阐述项目背景,强调其在网络安全领域的重要性。接着详细介绍 BiLSTM-CRF 模型原理,包…

sqli-lab靶场学习(四)——Less11-14(post方法)

前言 第1-10关都是get方法&#xff0c;本关开始进入post方法。其实post也好get也好&#xff0c;本质都差不多&#xff0c;使用的技巧也基本相同。 Less11 第11关打开是一个输入用户名密码的界面 显然登陆对话框会使用post方式提交&#xff0c;这里我们尝试在Username一栏通过…

软件工程专业未来发展方向

1. 前端开发&#xff08;Front-end Development&#xff09; 简介&#xff1a; 前端开发者专注于网站和应用程序的用户界面和用户体验设计。他们使用HTML、CSS、JavaScript等基本技术&#xff0c;以及React、Angular、Vue.js等前端框架&#xff0c;来创建互动性强、响应迅速的…

Scrapy爬虫实战——某瓣250

# 按照我个人的习惯&#xff0c;在一些需要较多的包作为基础支撑的项目里&#xff0c;习惯使用虚拟环境&#xff0c;因为这样能极大程度的减少出现依赖冲突的问题。依赖冲突就比如A、B、C三个库&#xff0c;A和B同时依赖于C&#xff0c;但是A需要的C库版本大于N&#xff0c;而B…

Python中lambda表达式的使用——完整通透版

文章目录 一、前言二、 基本语法三、举个简单的例子&#xff1a;四、常见应用场景1. 用于排序函数sort() 方法简介lambda 表达式的作用详细解释进一步扩展总结 2、与 map、filter、reduce 等函数结合1、 map() 函数示例&#xff1a;将列表中的每个数字平方 2、 filter() 函数示…

音视频直播应用场景探讨之RTMP推流还是GB28181接入?

技术背景 好多开发者跟我们沟通音视频解决方案的时候&#xff0c;不清楚什么时候用RTMP推送模块&#xff0c;什么时候用GB28181设备接入模块&#xff0c;也不清楚二者差异化。实际上&#xff0c;RTMP推流和GB28181接入模块&#xff0c;在很多方面存在差异&#xff0c;如应用领…

centos 安装VNC,实现远程连接

centos 安装VNC&#xff0c;实现远程连接 VNC(Virtual Network Computing)是一种远程控制软件&#xff0c;可以实现通过网络远程连接计算机的图形界面。 服务器安装VNC服务 yum install -y tigervnc-server*启动VNC服务&#xff0c;过程中需要输入连接密码 vncserver :1查看…

SQL常用语法详解

SQL 常用语法详解&#xff1a;数据库开发者的基础指南 Structured Query Language&#xff08;SQL&#xff09;是管理和操作关系型数据库的标准语言&#xff0c;广泛应用于数据查询、数据操控和数据库管理。无论是构建数据库、查询数据&#xff0c;还是更新表格&#xff0c;SQ…

云栖大会Day1:云应用开发平台 CAP 来了

2024 云栖大会开幕&#xff0c;在大会第一天&#xff0c;阿里云正式发布全新产品——云应用开发平台 CAP。CAP 拥有丰富的场景化应用模板&#xff0c;可以极速体验&#xff0c;并且具备更低的成本优势以及灵活组装等特点&#xff0c;成为广大开发者与企业必备的一站式应用开发平…

Stable Diffusion绘画 | ControlNet应用-instant-ID控制器:快速生成人物多角度图片

使用 instant-ID 控制器&#xff0c;用户只需要提供一张正脸图片&#xff0c;就可以快速地给人物生成多角度图片的&#xff0c;从而很好的保持了人物的一致性。 对于要制作小说推文、创建人物故事情节的创作&#xff0c;是一个非常好用且高效的功能。 准备工作 使用该控制类型&…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第一篇-原理】

如果想直接制作&#xff0c;请看【第二篇】内容 这次做一个这样的东西&#xff0c;通过在2DRT上实时绘制&#xff0c;生成动态的体积纹理&#xff0c;也就是可以runtime的VDB 设想的文章流程: 对原理进行学习制作体积渲染制作实时绘制 第一篇&#xff08;本篇&#xff09;是对“…

vue3+element-plus icons图标选择组件封装

一、最终效果 二、参数配置 1、代码示例 <t-select-icon v-model"selectVlaue" />2、配置参数&#xff08;Attributes&#xff09;继承 el-input Attributes 参数说明类型默认值v-model绑定值string-prefixIcon输入框前缀iconstringSearchisShowSearch是否显…

机器翻译之创建Seq2Seq的编码器、解码器

1.创建编码器、解码器的基类 1.1创建编码器的基类 from torch import nn#构建编码器的基类 class Encoder(nn.Module): #继承父类nn.Moduledef __init__(self, **kwargs): #**kwargs&#xff1a;不定常的关键字参数super().__init__(**kwargs)def forward(self, X, *args…

python的基础语法

Python 的基础语法非常简洁明了&#xff0c;适合初学者快速上手。下面我将为你总结几个最重要的基础语法点&#xff0c;帮你快速掌握 Python 的核心概念。让我们从基础开始逐步深入&#xff0c;像刷副本一样一关一关地攻克它们&#xff01; 1. Hello, World! 每一种编程语言的…