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

一、引言

通过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.rhkb.cn/news/425567.html

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

相关文章

C语言 | Leetcode C语言接雨水II

题目&#xff1a; 题解&#xff1a; typedef struct{int row;int column;int height; } Element;struct Pri_Queue; typedef struct Pri_Queue *P_Pri_Queue; typedef Element Datatype;struct Pri_Queue{int n;Datatype *pri_qu; };/*优先队列插入*/ P_Pri_Queue add_pri_que…

视频服务器:GB28181网络视频协议

一、前言 某项目中需要集成视频管理平台&#xff0c;实现分布在各省公司的摄像及接入&#xff0c;对视频进行统一管理。本项目中视频管理平台采用GB/T28181实现的监控设备接入管理平台&#xff0c;支持在开放互联网和局域网对监控设备进行远程接入、远程管理、远程调阅、录像回…

基于 PyQt5 和 OpenCV 进行图像处理操作的GUI工具初版

为了实现一个基于 PyQt5 和 OpenCV 的图形用户界面&#xff08;GUI&#xff09;&#xff0c;要求如下&#xff1a; 左边显示加载的图片。 中间提供各种对图片进行处理的操作方法&#xff08;如灰度化、模糊处理等&#xff09;。 右边显示处理后的效果图。 接下来我将详细讲解如…

PyQt5-loading-圆环加载效果

效果预览 代码实现 from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt, QThread, pyqtSignal from PyQt5.QtGui import QColor, QPainter from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QGridLayoutclass Cir…

Spring IOC的应用

目录 一、IOC基础 1、maven导入spring的 jar包 和 单测包 2、bean的配置 2.1 纯xml模式 2.1.1 xml文件头 2.1.2 实例化Bean的三种方式 2.1.3 Bean的生命周期 2.1.4 Bean标签属性 2.1.5 DI依赖注入的xml配置 2.1.5.1 构造函数注入 2.1.5.2 set方法注入 2.1.5.3 复杂数据类型注入…

【QT】常用控件-下

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;QT 目录 &#x1f449;&#x1f3fb;QComboBox&#x1f449;&#x1f3fb; QSpinBox&#x1f449;&#x1f3fb;QDateTimeEdit&#x1f449;&#x1f3fb;QD…

二叉树OJ题——二叉树的最大深度

文章目录 一、题目链接二、解题思路三、解题代码 一、题目链接 二叉树的最大深度 二、解题思路 三、解题代码

API - String 和 ArrayList

01 API是什么 答&#xff1a;API 全称 Application Programming Interfaace 应用程序编程接口。就是别人写好的一些程序&#xff0c;我们可以使用它们去解决相关问题。 02 为什么要学API 答&#xff1a;不要重复造轮子。Java已经有20多年的历史了&#xff0c;在这20多年里Ja…

【电路笔记】-差分运算放大器

差分运算放大器 文章目录 差分运算放大器1、概述2、差分运算放大器表示2.1 差分模式2.2 减法器模式3、差分放大器示例3.1 相关电阻3.2 惠斯通桥3.3 光/温度检测4、仪表放大器5、总结1、概述 在之前的文章中,我们讨论了反相运算放大器和同相运算放大器,我们考虑了在运算放大器…

android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。

1、先上一个图&#xff1a;这个是keystore无效的原因 之前在安装这个旧版本android studio的时候呢&#xff0c;安装过一版最新的android studio&#xff0c;然后通过模拟器跑过测试的demo。 2、运行旧的项目到模拟器的时候&#xff0c;就报错了&#xff1a; Execution failed…

初探全同态加密1 —— FHE的定义与历史回顾

文章目录 一、加密体系1、什么是加密体系2、加密体系的属性 Properties 二、同态加密&#xff1a;偶然的特殊性质三、同态加密体系的分类四、部分同态加密 Partially Homomorphic Encryption1、加法同态加密算法 —— ElGamal 加密算法1.1、ElGamal 的大致步骤1.2、ElGamal 的加…

7-ZIP工具的功能分享:合并分卷压缩文件

在日常工作中&#xff0c;有些大文件无法单独传输&#xff0c;我们通常会通过压缩拆分成多个分卷文件来完成传输。 当完成传输后&#xff0c;不想要这么多分卷文件的时候&#xff0c;就可以通过7-ZIP工具的合并功能来解决这个问题。下面一起来看看&#xff0c;具体如何操作。 …

Cortex-A7的GIC(通用中断控制器):边沿触发和电平触发中断处理流程

0 资料 ARM Generic Interrupt Controller Architecture version 2.0 Architecture Specification1 边沿触发和电平触发中断处理流程 1.0 边沿触发和电平触发的区别 边沿触发&#xff08;Edge-triggered&#xff09; This is an interrupt that is asserted on detection of…

学习笔记(一)

前言 一、对象 1、由类建模而成&#xff0c;是消息、数据和行为的组合 2、可以接收和发送消息&#xff0c;并利用消息进行彼此的交互。消息要包含传送给对象接收的信息 3、类的实例化&#xff1a;把类转换为对象的过程叫类的实例化。 4、对象的特性 (1) 对象有状态&#…

node.js+Koa框架+MySQL实现注册登录

完整视频展示&#xff1a;https://item.taobao.com/item.htm?ftt&id831092436619&spma21dvs.23580594.0.0.52de2c1bg9gTfM 效果展示&#xff1a; 一、项目介绍 本项目是基于node.jsKoamysql的注册登录的项目,主要是给才学习node.js和Koa框架的萌新才写的。 二、项目…

Datawhale------Tiny-universe学习笔记——Qwen(1)

1. Qwen整体介绍 对于一个完全没接触过大模型的小白来说&#xff0c;猛一听这个名字首先会一懵&#xff1a;Qwen是啥。这里首先解答一下这个问题。下面是官网给出介绍&#xff1a;Qwen是阿里巴巴集团Qwen团队研发的大语言模型和大型多模态模型系列。其实随着大模型领域的发展&a…

Pytorch详解-模型模块(RNN,CNN,FNN,LSTM,GRU,TCN,Transformer)

Pytorch详解-模型模块 Module & parameterModule初认识forward函数 ParameterPytorch中的权重、参数和超参数 Module容器-ContainersSequentialModuleListModuleDictParameterList & ParameterDict 常用网络层LSTM输入和输出 GRUConvolutional Layers卷积层的基本概念常…

第十七节:学习Hutool上传文件(自学Spring boot 3.x的第四天)

这节记录下如何使用Hutool库上传本地的文件到服务器端&#xff08;因为是练习&#xff0c;所以是本地端&#xff09;。 第一步&#xff1a;引入Hutool库最新版本&#xff0c;通过maven方式。&#xff08;最新版本需去maven仓库查询&#xff09; 第二步&#xff1a;编写一个post…

sqlgun新闻管理系统

一&#xff0c;打开主页 1.输入框测试回显点 -1union select 1,2,3# 出现回显点2 2.查看数据库表名 -1union select 1,database(),3# 3.查看表名 -1union select 1,2,group_concat(table_name) from information_schema.tables where table_schemasqlgunnews# 4.查看admin中…

【IP协议】解决 IP 地址不够用的问题(IP地址管理:动态分配、NAT、Ipv6)

文章目录 方案一、动态分配 IP 地址方案二、NATNAT 机制的缺点 方案三、IPv6 方案一、动态分配 IP 地址 一个设备上网就分配 IP&#xff0c;不上网就先不分配&#xff08;权宜之计&#xff09; 方案二、NAT 网络地址转换 以一当千&#xff0c;使用一个 IP&#xff0c;代表一大…