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

news2024/12/23 13:43:24

一、引言

通过FFmpeg命令:

./ffmpeg -i XXX.aac

可以获取到ADTS格式的AAC裸流的音频采样频率、声道数、采样位数、码率等信息:

在vlc中也可以获取到这些信息(vlc底层也使用了FFmpeg进行解码):

所以FFmpeg和vlc是怎样获取到这些信息的呢?它们其实是通过解码ADTS格式的AAC的Header(adts_fixed_header + adts_variable_header)获取的。执行FFmpeg命令:./ffmpeg -i XXX.aac时,FFmpeg源码内部会调用adts_aac_probe函数检测该文件是否为ADTS格式的AAC裸流(具体可以参考:《音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现》)。然后如果检测出该文件为ADTS格式的AAC裸流,会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

二、ff_adts_header_parse函数的声明

ff_adts_header_parse函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavcodec/adts_header.h中:

/**
 * Parse the ADTS frame header to the end of the variable header, which is
 * the first 54 bits.
 * @param[in]  gbc BitContext containing the first 54 bits of the frame.
 * @param[out] hdr Pointer to struct where header info is written.
 * @return Returns 0 on success, -1 if there is a sync word mismatch,
 * -2 if the version element is invalid, -3 if the sample rate
 * element is invalid, or -4 if the bit rate element is invalid.
 */
int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr);

FFmpeg对媒体文件/流进行解复用时,会调用avformat_open_input函数,通过avformat_open_input函数内部的av_probe_input_format3函数来检测该文件是否为ADTS格式的AAC裸流。如果是,FFmpeg源码会继续执行avformat_find_stream_info函数读取部分packet(数据包)以获取码流信息。在avformat_find_stream_info函数内会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

所以ff_adts_header_parse函数的作用就是解码ADTS格式的AAC的Header。

形参gbc:既是输入型参数也是输出型参数。指向已经被初始化的GetBitContext对象。关于GetBitContext结构体可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》。

形参hdr:输出型参数,指向一个AACADTSHeaderInfo对象。AACADTSHeaderInfo结构体声明在libavcodec/adts_header.h中:

typedef struct AACADTSHeaderInfo {
    uint32_t sample_rate;
    uint32_t samples;
    uint32_t bit_rate;
    uint8_t  crc_absent;
    uint8_t  object_type;
    uint8_t  sampling_index;
    uint8_t  chan_config;
    uint8_t  num_aac_frames;
    uint32_t frame_length;
} AACADTSHeaderInfo;

执行ff_adts_header_parse函数后,形参hdr指向的对象的成员变量会得到从AAC Header中解码出来的信息。

返回值:解码成功返回包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。解码失败返回一个负数。

三、ff_adts_header_parse函数的定义

ff_adts_header_parse函数定义在libavcodec/adts_header.c中:

int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr)
{
    int size, rdb, ch, sr;
    int aot, crc_abs;

    memset(hdr, 0, sizeof(*hdr));

    if (get_bits(gbc, 12) != 0xfff)
        return AAC_AC3_PARSE_ERROR_SYNC;

    skip_bits1(gbc);             /* id */
    skip_bits(gbc, 2);           /* layer */
    crc_abs = get_bits1(gbc);    /* protection_absent */
    aot     = get_bits(gbc, 2);  /* profile_objecttype */
    sr      = get_bits(gbc, 4);  /* sample_frequency_index */
    if (!ff_mpeg4audio_sample_rates[sr])
        return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;
    skip_bits1(gbc);             /* private_bit */
    ch = get_bits(gbc, 3);       /* channel_configuration */

    skip_bits1(gbc);             /* original/copy */
    skip_bits1(gbc);             /* home */

    /* adts_variable_header */
    skip_bits1(gbc);             /* copyright_identification_bit */
    skip_bits1(gbc);             /* copyright_identification_start */
    size = get_bits(gbc, 13);    /* aac_frame_length */
    if (size < AV_AAC_ADTS_HEADER_SIZE)
        return AAC_AC3_PARSE_ERROR_FRAME_SIZE;

    skip_bits(gbc, 11);          /* adts_buffer_fullness */
    rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */

    hdr->object_type    = aot + 1;
    hdr->chan_config    = ch;
    hdr->crc_absent     = crc_abs;
    hdr->num_aac_frames = rdb + 1;
    hdr->sampling_index = sr;
    hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];
    hdr->samples        = (rdb + 1) * 1024;
    hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;
    hdr->frame_length   = size;

    return size;
}

四、ff_adts_header_parse函数的内部实现分析

ff_adts_header_parse函数中,首先通过memset让形参hdr指向的对象的成员变量清0:

memset(hdr, 0, sizeof(*hdr));

从《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,syncword为嵌入在ADTS流中的一种编码,用于标识ADTS帧的起始位,其占12位,每个位都必须被设置为1,也就是0b111111111111。所以通过下面的if语句来判断是否读取到了syncword,如果没有读取到,返回宏定义AAC_AC3_PARSE_ERROR_SYNC(-0x3030c0a)。关于get_bits函数的用法可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》:

    if (get_bits(gbc, 12) != 0xfff)
        return AAC_AC3_PARSE_ERROR_SYNC;

如果读取到了syncword,继续往下执行,跳过adts_fixed_header的ID和layer属性(因为跳过了ID属性,所以FFmpeg根本不会区分到底是MPEG-2还是MPEG-4的AAC),关于skip_bits1和skip_bits函数的用法可以参考:《FFmpeg源码:skip_bits、skip_bits1、show_bits函数分析》:

    skip_bits1(gbc);             /* id */
    skip_bits(gbc, 2);           /* layer */

读取adts_fixed_header的protection_absent、profile_ObjectType、samplingFrequencyIndex属性:

    crc_abs = get_bits1(gbc);    /* protection_absent */
    aot     = get_bits(gbc, 2);  /* profile_objecttype */
    sr      = get_bits(gbc, 4);  /* sample_frequency_index */

全局数组ff_mpeg4audio_sample_rates定义在libavcodec/mpeg4audio_sample_rates.h中:

const int ff_mpeg4audio_sample_rates[16] = {
    96000, 88200, 64000, 48000, 44100, 32000,
    24000, 22050, 16000, 12000, 11025, 8000, 7350
};

通过samplingFrequencyIndex属性得到音频采样频率,单位为Hz:

    if (!ff_mpeg4audio_sample_rates[sr])
        return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;

跳过private_bit属性。读取channel_configuration属性,也就是音频声道数:

    skip_bits1(gbc);             /* private_bit */
    ch = get_bits(gbc, 3);       /* channel_configuration */

跳过original_copy、home、copyright_identification_bit、copyright_identification_start属性:

    skip_bits1(gbc);             /* original/copy */
    skip_bits1(gbc);             /* home */

    /* adts_variable_header */
    skip_bits1(gbc);             /* copyright_identification_bit */
    skip_bits1(gbc);             /* copyright_identification_start */

读取aac_frame_length属性,即包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。宏定义AV_AAC_ADTS_HEADER_SIZE的值为7,判断aac_frame_length属性的值是否小于7。ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),所以如果aac_frame_length属性的值小于7,表示ADTS Header格式不正确,返回宏定义AAC_AC3_PARSE_ERROR_FRAME_SIZE(-0x4030c0a):

    size = get_bits(gbc, 13);    /* aac_frame_length */
    if (size < AV_AAC_ADTS_HEADER_SIZE)
        return AAC_AC3_PARSE_ERROR_FRAME_SIZE;

跳过adts_buffer_fullness属性,读取number_of_raw_data_blocks_in_frame属性:

    skip_bits(gbc, 11);          /* adts_buffer_fullness */
    rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */

将profile_ObjectType属性的值加1赋值给hdr->object_type。所以MPEG版本为MPEG-4时,如果hdr->object_type为1,表示AAC的规格为AAC Main;hdr->object_type为2,表示规格为AAC LC;hdr->object_type为3,表示规格为AAC SSR;hdr->object_type为4,表示规格为AAC LTP:

hdr->object_type    = aot + 1;

将音频声道数赋值给hdr->chan_config:

hdr->chan_config    = ch;

将protection_absent属性的值赋值给hdr->crc_absent。所以hdr->crc_absent为0时,表示CRC校验存在,当hdr->crc_absent为1时,CRC校验不存在:

hdr->crc_absent     = crc_abs;

将number_of_raw_data_blocks_in_frame属性的值赋值给hdr->num_aac_frames。所以该ADTS音频帧中有hdr->num_aac_frames个原始数据块:

hdr->num_aac_frames = rdb + 1;

将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:

    hdr->sampling_index = sr;
    hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];

将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples:

hdr->samples        = (rdb + 1) * 1024;

通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:

hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;

将整个ADTS音频帧的长度(包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节)赋值给hdr->frame_length:

hdr->frame_length   = size;

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

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

相关文章

【混淆矩阵】Confusion Matrix!定量评价的基础!如何计算全面、准确的定量指标去衡量模型分类的好坏??

【混淆矩阵】Confusion Matrix&#xff01;定量评价的基础&#xff01; 如何计算全面、准确的定量指标去衡量模型分类的好坏&#xff1f;&#xff1f; 文章目录 【混淆矩阵】Confusion Matrix&#xff01;定量评价的基础&#xff01;1. 混淆矩阵2.评价指标3.混淆矩阵及评价指标…

Redis基础数据结构之 ziplist 压缩列表 源码解读

目录标题 ziplist 是什么?ziplist 特点ziplist 数据结构ziplist 节点pre_entry_lengthencoding 和 lengthcontent ziplist 基本操作插入&#xff08;Insertion&#xff09;删除&#xff08;Deletion&#xff09;查找&#xff08;Search&#xff09;更新&#xff08;Update&…

Qt多元素控件——QTableWidget

文章目录 QTabWidget核心属性、方法和信号使用示例 QTabWidget核心属性、方法和信号 QTableWidget表示一个表格控件&#xff0c;一个表格中包含若干行&#xff0c;每一行包含若干列。 表格中的每一个单元格&#xff0c;是一个QTableWidgetItem对象。 QTableWidget核心方法&a…

Java 每日一刊(第9期):数组

文章目录 前言什么是数组初始化数组如何访问和操作数组遍历数组多维数组数组的常见操作复制数组排序数组搜索数组 数组的长度和异常处理Arrays 工具类本期小知识 “简单是效率的灵魂。” 前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内…

云计算和虚拟化技术 背诵

https://zhuanlan.zhihu.com/p/612215164 https://zhuanlan.zhihu.com/p/612215164 云计算是指把计算资源、存储资源、网络资源、应用软件等集合起来&#xff0c;采用虚拟化技术 &#xff0c;将这些资源池化&#xff0c;组成资源共享池&#xff0c;共享池即是“云”。 云计算…

从零开始学习Linux(12)---进程间通信(信号量与信号)

1.信号量 信号量是计算机科学中用于同步和互斥的一种抽象数据类型。在并发编程中&#xff0c;当多个进程或线程需要访问共享资源时&#xff0c;信号量用来确保资源在同一时刻只被一个进程或线程访问&#xff0c;从而避免竞争条件。 信号量通常具有以下特性&#xff1a; 整…

Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约

Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约 文章目录 Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约前言版本适配一、启动FIsco Bcos区块链网络二、获取控制台文件三、配置控制台3.1 执行download_console.sh脚本3.2 拷贝控制台配置文件3.3 修…

读构建可扩展分布式系统:方法与实践06异步消息传递

1. 异步消息传递 1.1. 通信是分布式系统的基础&#xff0c;也是架构师需要纳入其系统设计的主要问题 1.2. 客户端发送请求并等待服务器响应 1.2.1. 这就是大多数分布式通信的设计方式&#xff0c;因为客户端需要得到即时响应后才能继续 1.2.2. 并非所有系统都有这个要求 1…

数据时代,职场离不开的远程控制工具

中秋了大概率是在正常放假了吧&#xff0c;如果突发遇到需要你处理的文件怎么办呢&#xff1f;其实有远程操作工具你就不用到办公室了。向日葵远程控制软件这些工具就可以帮我们远程实现控制电脑操作。如果你也有这方面需求就继续看吧&#xff0c;这次我将介绍几款我用过效果比…

Redis常见应用场景

目录 一、实现博客点赞功能 二、实现博客点赞用户列表功能 三、好友关注和取关以及求共同关注 四、实现关注推送 1、拉模式 2、推模式 3、推拉结合 四、三种模式对比 这里简单记录一下&#xff0c;没有实现方法&#xff0c;只是帮助记忆 一、实现博客点赞功能 可以通…

[NSSRound#4 SWPU]hide_and_seek-用gdb调试

看反汇编 ; __unwind { .text:0000000000001514 F3 0F 1E FA endbr64 .text:0000000000001518 55 push rbp .text:0000000000001519 48 89 E5 mov rbp, rsp .text:000000000000151C 53 …

python tkinter

基本使用 基于tkinter创建 GUI基本四步&#xff1a;窗口->组件->布局->事件 1.创建窗口对象 from tkinter import *root Tk() # 创建窗口root.mainloop() # 进入事件循环 2.创建组件 按钮文本等组件 btn Button(root) # 创建Button组件&#xff0c;使组件在…

re题(25)BUUFCTF-[GUET-CTF2019]re

BUUCTF在线评测 (buuoj.cn) 查下壳&#xff0c;是upx壳 脱一下 查看字符串&#xff0c;定位到主函数&#xff0c;也可以用ctrlE的方式找到主函数 明显&#xff0c;sub_4009AE是对flag加密的关键函数 进入sub_4009AE看一下 看到这儿有一堆大数和方程&#xff0c;我们知道要用z…

Transformer模型详细步骤

Transformer模型是nlp任务中不能绕开的学习任务&#xff0c;我将从数据开始&#xff0c;每一步骤都列举出来&#xff0c;然后对应重点的代码进行讲解 ------------------------------------------------------------------------------------------------------------- Trans…

Skytower

一、安装配置靶机 下载地址: SkyTower: 1 ~ VulnHub 下载之后解压发现是VirtualBox格式的 我们下载一个VirtualBox&#xff0c;这是官网 Downloads – Oracle VirtualBox 安装到默认路径就 打开后点击注册 选择解压后的vbox文件 然后点击左上角管理 点击导出虚拟电脑&…

PCIe进阶之TL:Request Handling Rules

1 Handling of Received TLPs 本节介绍接收到的 TLP 在数据链路层经过完整性验证之后,这些 TLP 在事务处理层时的处理方式。这些规则如下图所示: 接收侧会忽略保留字段。如果 Fmt 字段显示存在至少一个 TLP Prefix : (1)通过检查后续 DWORD 的第一个字节中的 Fmt 字段,…

两个人群填充参考(CHN100K和NARD)

分别是中国人群和东北亚人群的填充参考&#xff0c;测试了下&#xff0c;中国人群的参考注册还是相对友好的&#xff0c;没有像有些网站一样严格限制。东北亚的没有测试&#xff0c;两个数据库的特点都是包含了少数民族&#xff0c;研究朝鲜或蒙古族或其他民族的同学&#xff0…

Java 枚举 新特性

Java 枚举&#xff08;enum&#xff09;自JDK 1.5引入以来&#xff0c;随着版本的升级不断增强。本文将回顾枚举的演进&#xff0c;尤其是结合switch语句的应用&#xff0c;展示枚举如何在现代Java中变得更加灵活。 1. JDK 1.5&#xff1a;Java 枚举的诞生 在JDK 1.5之前&…

Dbt基本概念与快速入门

在过去的几年里&#xff0c;数据科学界已经慢慢地接受了以数据为中心的范式。我们不仅关注日益复杂的机器学习模型&#xff0c;还要更多地关注数据质量。这使得数据工程、分析工程领域技术和工具成为热点。dbt(数据构建工具)是一个显著改善数据工程师生活的工具。它的目的是向数…

【漏洞复现】金某云星空ERP GetImportOutData .net反序列化漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…