音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
=================================================================
音视频入门基础: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的实现
音视频入门基础:AAC专题(11)——AudioSpecificConfig简介
音视频入门基础:AAC专题(12)——FFmpeg源码中,解码AudioSpecificConfig的实现
音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
=================================================================
一、引言
对于携带Audio Specific Config的媒体文件,比如音频压缩编码格式为AAC的FLV文件,FFmpeg一般通过解码其Audio Tag中Audio Specific Config获取其音频信息。而通过《音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件》生成的AAC裸流文件和TS流中的AAC是没有Audio Specific Config的,只有ADTS Header,这时就得通过解码ADTS Header获取其音频信息(音频压缩编码格式的profile、音频采样率、音频声道数、码率等):

本文讲述FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现。
二、音频压缩编码格式
具体获取方法可以参考:《音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现》
三、音频压缩编码格式的profile
音频压缩编码格式还有附带的profile(规格)。比如,如果音频压缩编码格式为AAC,根据《ISO14496-3-2009.pdf》第124页,还有AAC Main、AAC LC、AAC SSR、AAC LTP这几种规格:

FFmpeg获取AAC裸流的音频压缩编码格式的profile,是根据ADTS Header中的profile_ObjectType属性获取的。由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占2位的profile_ObjectType属性,表示AAC的规格。
由《音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现》可以知道,FFmpeg源码中通过ff_adts_header_parse函数解码ADTS格式的AAC的Header。而ff_adts_header_parse函数中,通过下面语句,将profile_ObjectType属性的值加1赋值给hdr->object_type:
hdr->object_type = aot + 1;
然后在parse_adts_frame_header函数中,将hdr->object_type赋值给ac->oc[1].m4ac.object_type:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...size = ff_adts_header_parse(gb, &hdr_info);if (size > 0) {//...ac->oc[1].m4ac.object_type = hdr_info.object_type;//...}
//...
}
之后,通过aac_decode_frame_int函数将ac->oc[1].m4ac.object_type的值减1赋值给AVCodecContext的profile,这样AVCodecContext的profile就会得到原本的profile_ObjectType属性:
static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,int *got_frame_ptr, GetBitContext *gb,const AVPacket *avpkt)
{
//...// The AV_PROFILE_AAC_* defines are all object_type - 1// This may lead to an undefined profile being signaledac->avctx->profile = ac->oc[1].m4ac.object_type - 1;
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:profile = avcodec_profile_name(enc->codec_id, enc->profile)拿到上一步中得到的AVCodecContext的profile。最后再在dump_stream_format函数中将profile打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...profile = avcodec_profile_name(enc->codec_id, enc->profile);
//...
}
所以FFmpeg获取AAC裸流文件的音频压缩编码格式的profile,获取的是ADTS Header中的profile_ObjectType属性:

四、音频采样率
FFmpeg获取AAC裸流的音频采样频率,是根据ADTS Header中的samplingFrequencyIndex属性获取的。 由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占4位的samplingFrequencyIndex属性,表示音频采样频率:

ff_adts_header_parse函数中,通过下面语句,将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:
hdr->sampling_index = sr;hdr->sample_rate = ff_mpeg4audio_sample_rates[sr];
然后在parse_adts_frame_header函数中,将hdr->sample_rate赋值给ac->oc[1].m4ac.sample_rate:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...size = ff_adts_header_parse(gb, &hdr_info);if (size > 0) {//...ac->oc[1].m4ac.sample_rate = hdr_info.sample_rate;//...}
//...
}
之后,通过aac_decode_frame_int函数将ac->oc[1].m4ac.sample_rate赋值给AVCodecContext的sample_rate:
static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,int *got_frame_ptr, GetBitContext *gb,const AVPacket *avpkt)
{
//...if (ac->oc[1].status && audio_found) {avctx->sample_rate = ac->oc[1].m4ac.sample_rate << multiplier;avctx->frame_size = samples;ac->oc[1].status = OC_LOCKED;}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:av_bprintf(&bprint, "%d Hz, ", enc->sample_rate)拿到上一步中得到的AVCodecContext的sample_rate。最后再在dump_stream_format函数中将其打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...switch (enc->codec_type) {case AVMEDIA_TYPE_AUDIO:av_bprintf(&bprint, "%s", separator);if (enc->sample_rate) {av_bprintf(&bprint, "%d Hz, ", enc->sample_rate);}
//...}
//...
}
所以FFmpeg获取AAC裸流文件的音频采样率,获取的是ADTS Header中的samplingFrequencyIndex属性:

五、音频声道数
FFmpeg获取AAC裸流的音频声道数,是根据ADTS Header中的channel_configuration属性获取的。 由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占3位的channel_configuration属性,表示音频声道数:

ff_adts_header_parse函数中,通过下面语句,将音频声道数赋值给hdr->chan_config:
hdr->chan_config = ch;
然后在parse_adts_frame_header函数中,将hdr->chan_config赋值给AVCodecContext的ch_layout:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...size = ff_adts_header_parse(gb, &hdr_info);if (size > 0) {//...if (hdr_info.chan_config) {ac->oc[1].m4ac.chan_config = hdr_info.chan_config;if ((ret = set_default_channel_config(ac, ac->avctx,layout_map,&layout_map_tags,hdr_info.chan_config)) < 0)return ret;if ((ret = output_configure(ac, layout_map, layout_map_tags,FFMAX(ac->oc[1].status,OC_TRIAL_FRAME), 0)) < 0)return ret;}//...}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:av_channel_layout_describe_bprint(&enc->ch_layout, &bprint)拿到AVCodecContext的ch_layout对应的音频声道数目。最后再在dump_stream_format函数中将音频声道数目打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...switch (enc->codec_type) {case AVMEDIA_TYPE_AUDIO:av_channel_layout_describe_bprint(&enc->ch_layout, &bprint);//...break;}
//...
}
所以FFmpeg获取AAC裸流文件的音频声道数,获取的是ADTS Header中的channel_configuration属性:

六、Bit depth
FFmpeg获取AAC裸流的Bit depth(又叫位深度、位元深度、采样深度、采样位数、采样格式),获取到的值是没有意义的。当音频压缩编码格式为AAC时,FFmpeg会强制把Bit depth设置为fltp。这是因为对于有损压缩编解码器(如MP3和AAC),Bit depth是在编码期间计算的,并且可以因采样而异,Bit depth只对PCM数字信号有意义。具体可以参考:《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》。
可以看到在aac_decode_init函数中(该函数定义在libavcodec/aacdec_template.c),强制把音频采样格式设置成了AV_SAMPLE_FMT_FLTP:
static av_cold int aac_decode_init(AVCodecContext *avctx)
{
//...avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//...
}
所以如果音频压缩编码格式为AAC,通过FFmpeg获取到的音频采样格式固定为fltp,该值没有意义:

七、音频码率
通过解码ADTS Header无法直接获得音频码率,但是可以通过里面的属性间接计算出音频码率。
ff_adts_header_parse函数中,将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples。FFmpeg源码内部强制默认AAC(AAC Main、AAC LC、AAC SSR、AAC LTP)的samples是1024。hdr->samples为该ADTS音频帧中采样的次数:
hdr->samples = (rdb + 1) * 1024;
通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:
hdr->bit_rate = size * 8 * hdr->sample_rate / hdr->samples;
然后ff_aac_ac3_parse函数中,通过下面代码得到实际的以bps为单位的音频码率,赋值给AVCodecContext的bit_rate:
int ff_aac_ac3_parse(AVCodecParserContext *s1,AVCodecContext *avctx,const uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size)
{
//...if (got_frame) {//...int bit_rate;if (avctx->codec_id != AV_CODEC_ID_AAC) {//...}else{AACADTSHeaderInfo hdr, *phrd = &hdr;int ret = avpriv_adts_header_parse(&phrd, buf, buf_size);if (ret < 0)return i;bit_rate = hdr.bit_rate;}/* Calculate the average bit rate */s->frame_number++;if (!CONFIG_EAC3_DECODER || avctx->codec_id != AV_CODEC_ID_EAC3) {avctx->bit_rate +=(bit_rate - avctx->bit_rate) / s->frame_number;}}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:bitrate = get_bit_rate(enc)拿到AVCodecContext的bit_rate。最后再把它除以1000,得到以kb/s为单位的音频码率,打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...bitrate = get_bit_rate(enc);if (bitrate != 0) {av_bprintf(&bprint, ", %"PRId64" kb/s", bitrate / 1000);
//...
}
所以FFmpeg获取AAC裸流文件的音频码率,是根据ADTS Header中的属性计算出来的:
相关文章:
音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
音视频入门基础:AAC专题系列文章: 音视频入门基础:AAC专题(1)——AAC官方文档下载 音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件 音视频入门基础:AAC…...
【C++】B2069 求分数序列和题目解析与优化详解
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯题目描述输入格式输出格式输入输出样例输入:输出: 💯解题思路分析题目解题步骤 💯代码实现我的代码实现实现特点 老师的代码…...
4.FPGA如何实现设计
在前面分别引入了,LUT的知识,全局时钟网络,以及FPGA内部的资源。 LUT的知识: 在FPGA设计中实现的逻辑运算在不借用其他的硬核的基础上都是在LUT中通过查表的方式进行完成的,比如实现的c a & b;就是将a&b的所…...
SO-CNN-LSTM-MATT蛇群算法优化注意力机制深度学习多特征分类预测
SO-CNN-LSTM-MATT蛇群算法优化注意力机制深度学习多特征分类预测(多输入单输出) 目录 SO-CNN-LSTM-MATT蛇群算法优化注意力机制深度学习多特征分类预测(多输入单输出)分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matl…...
大模型-Ollama使用相关的笔记
大模型-Ollama使用相关的笔记 解决Ollama外网访问问题(配置ollama跨域访问)Postman请求样例 解决Ollama外网访问问题(配置ollama跨域访问) 安装Ollama完毕后, /etc/systemd/system/ollama.service进行如下修改&#…...
OpenCV计算机视觉 02 图片修改 图像运算 边缘填充 阈值处理
目录 图片修改(打码、组合、缩放) 图像运算 边缘填充 阈值处理 上一篇文章: OpenCV计算机视觉 01 图像与视频的读取操作&颜色通道 图片修改(打码、组合、缩放) # 图片打码 import numpy as np a cv2.imre…...
langchain使用FewShotPromptTemplate出现KeyError的解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...
tryhackme-Cyber Security 101-Linux Shells(linux命令框)
目的:了解脚本和不同类型的 Linux shell。 任务1:Introduction to Linux Shells(Linux Shell 简介) 作为操作系统的常规用户,我们都广泛使用图形用户界面 (GUI) 来执行大多数操作。只需点击几…...
亚远景-ISO 21434标准涵盖了哪些方面?
ISO 21434标准《道路车辆—网络安全工程》全面涵盖了汽车网络安全领域,其目的是确保汽车电子系统在整个产品生命周期中的网络安全性能。具体来说,该标准包括以下几个方面: 1. 术语和定义 :提供汽车网络安全相关的术语、概念和定义…...
第3章 集合与关系
2024年12月24日一稿 2024年12月26日二稿 🐰3.1 集合的概念和表示法 🦘3.1.1 集合的表示 🦘3.1.2 基本概念 🐰3.2 集合的运算 🦘3.2.1 集合的基本运算 🦘3.2.2 有穷计数集 🦘3.2.3 广义交和广义…...
【vmware】|设置共享文件夹
目的: 虚拟机中设置共享文件夹,本地物理机中可以搜到该共享文件夹 1、虚拟机: 设置共享文件夹 右键属性-共享页码进行下列设置 点击网络和共享中心,检查下列选项 二、在本地物理机中启用网络发现: 此时,刷新网络…...
Log4j1.27配置日志输出级别不起效
起因:构建独立版本debezuim使用时,日志一直打印debug信息。 原因:包冲突问题,进行排包操作。 参考log4j日志级别配置完成后不生效 系统一直打印debug日志_log4j不起作用-CSDN博客 1、application.properties logging.configc…...
计算机图形学知识点汇总
一、计算机图形学定义与内容 1.图形 图形分为“图”和“形”两部分。 其中,“形”指形体或形状,存在于客观世界和虚拟世界,它的本质是“表示”;而图则是包含几何信息与属性信息的点、线等基本图元构成的画面,用于表达…...
详解下c语言中struct和union的对齐规则
接触过c语言的同学应该都知道字节对齐。有些时候我们很容易弄错字节对齐的方式,特别是涉及到struct(结构体)和union(联合体)时。今天我们通过详细例子来说明下struct和union的对齐规则,以便了解各种struct和…...
ubuntu安装sublime安装与免费使用
1. ubuntu安装sublime 参考官网: Linux Package Manager Repositories 2. 破解过程 打开如下网址,打开/opt/sublime_text/sublime_text https://hexed.it/ 3. 替换在hexed打开的文件中查找并替换: 4180激活方法 使用二进制编辑器 8079 0500 0f94 c2替换为 c641 05…...
攻防世界 cookie
开启场景 Cookie(HTTP cookie)是一种存储在用户计算机上的小型文本文件。它由网站通过用户的浏览器在用户访问网站时创建,并存储一些用于跟踪和识别用户的信息。Cookie 主要用于在网站和浏览器之间传递数据,以便网站可以根据用户的…...
深度学习笔记1:神经网络与模型训练过程
参考博客:PyTorch深度学习实战(1)——神经网络与模型训练过程详解_pytorch 实战-CSDN博客 人工神经网络 ANN:张量及数学运算的集合,排列方式近似于松散的人脑神经元排列 组成 1)输入层 2)隐…...
什么是 DevOps 自动化?
DevOps 自动化是一种现代软件开发方法,它使用工具和流程来自动化任务并简化工作流程。它将开发人员、IT 运营和安全团队聚集在一起,帮助他们有效协作并交付可靠的软件。借助 DevOps 自动化,组织能够处理重复性任务、优化流程并更快地将应用程…...
使用 Python 操作 MySQL 数据库的实用工具类:MySQLHandler
操作数据库是非常常见的需求,使用 Python 和 pymysql 库封装一个通用的 MySQL 数据库操作工具类,并通过示例演示如何使用这个工具类高效地管理数据库。 工具类的核心代码解析 MySQLHandler 类简介 MySQLHandler 是一个 Python 类,用于简化…...
DB-GPT V0.6.3 版本更新:支持 SiliconCloud 模型、新增知识处理工作流等
DB-GPT V0.6.3版本现已上线,快速预览新特性: 新特性 1. 支持 SiliconCloud 模型,让用户体验多模型的管理能力 如何使用: 修改环境变量文件.env,配置SiliconCloud模型 # 使用 SiliconCloud 的代理模型 LLM_MODELsiliconflow_p…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
