AAC音频格式详解
关于AAC音频格式基本情况,可参考维基百科http://en.wikipedia.org/wiki/Advanced_Audio_Coding
AAC音频格式分析
AAC音频格式有ADIF和ADTS:
ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。
简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。
语音系统对实时性要求较高,基本是这样一个流程,采集音频数据,本地编码,数据上传,服务器处理,数据下发,本地解码
ADTS是帧序列,本身具备流特征,在音频流的传输与处理方面更加合适。
ADTS帧结构:
header |
body |
ADTS帧首部结构:
序号 | 域 | 长度(bits) | 说明 |
1 | Syncword | 12 | all bits must be 1 |
2 | MPEG version | 1 | 0 for MPEG-4, 1 for MPEG-2 |
3 | Layer | 2 | always 0 |
4 | Protection Absent | 1 | et to 1 if there is no CRC and 0 if there is CRC |
5 | Profile | 2 | the MPEG-4 Audio Object Type minus 1 |
6 | MPEG-4 Sampling Frequency Index | 4 | MPEG-4 Sampling Frequency Index (15 is forbidden) |
7 | Private Stream | 1 | set to 0 when encoding, ignore when decoding |
8 | MPEG-4 Channel Configuration | 3 | MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE) |
9 | Originality | 1 | set to 0 when encoding, ignore when decoding |
10 | Home | 1 | set to 0 when encoding, ignore when decoding |
11 | Copyrighted Stream | 1 | set to 0 when encoding, ignore when decoding |
12 | Copyrighted Start | 1 | set to 0 when encoding, ignore when decoding |
13 | Frame Length | 13 | this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) |
14 | Buffer Fullness | 11 | buffer fullness |
15 | Number of AAC Frames | 2 | number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame |
16 | CRC | 16 | CRC if protection absent is 0 |
AAC解码
在解码方面,使用了开源的FAAD,http://www.audiocoding.com/faad2.html
sdk解压缩后,docs目录有详细的api说明文档,主要用到的有以下几个:
- NeAACDecHandle NEAACAPI NeAACDecOpen(void);
- 创建解码环境并返回一个句柄
- void NEAACAPI NeAACDecClose(NeAACDecHandle hDecoder);
- 关闭解码环境
- NeAACDecConfigurationPtr NEAACAPI NeAACDecGetCurrentConfiguration(NeAACDecHandle hDecoder);
- 获取当前解码器库的配置
- unsigned char NEAACAPI NeAACDecSetConfiguration(NeAACDecHandle hDecoder, NeAACDecConfigurationPtr config);
- 为解码器库设置一个配置结构
- long NEAACAPI NeAACDecInit(NeAACDecHandle hDecoder, unsigned char *buffer, unsigned long buffer_size, unsigned long *samplerate, unsigned char *channels);
- 初始化解码器库
- void* NEAACAPI NeAACDecDecode(NeAACDecHandle hDecoder, NeAACDecFrameInfo *hInfo, unsigned char *buffer, unsigned long buffer_size);
- 解码AAC数据
对以上api做了简单封装,写了一个解码类,涵盖了FAAD库的基本用法,感兴趣的朋友可以看看
MyAACDecoder.h:
- /**
- *
- * filename: MyAACDecoder.h
- * summary: convert aac to wave
- * author: caosiyang
- * email: csy3228@gmail.com
- *
- */
- #ifndef __MYAACDECODER_H__
- #define __MYAACDECODER_H__
- #include "Buffer.h"
- #include "mytools.h"
- #include "WaveFormat.h"
- #include "faad.h"
- #include <iostream>
- using namespace std;
- class MyAACDecoder {
- public:
- MyAACDecoder();
- ~MyAACDecoder();
- int32_t Decode(char *aacbuf, uint32_t aacbuflen);
- const char* WavBodyData() const {
- return _mybuffer.Data();
- }
- uint32_t WavBodyLength() const {
- return _mybuffer.Length();
- }
- const char* WavHeaderData() const {
- return _wave_format.getHeaderData();
- }
- uint32_t WavHeaderLength() const {
- return _wave_format.getHeaderLength();
- }
- private:
- MyAACDecoder(const MyAACDecoder &dec);
- MyAACDecoder& operator=(const MyAACDecoder &rhs);
- //init AAC decoder
- int32_t _init_aac_decoder(char *aacbuf, int32_t aacbuflen);
- //destroy aac decoder
- void _destroy_aac_decoder();
- //parse AAC ADTS header, get frame length
- uint32_t _get_frame_length(const char *aac_header) const;
- //AAC decoder properties
- NeAACDecHandle _handle;
- unsigned long _samplerate;
- unsigned char _channel;
- Buffer _mybuffer;
- WaveFormat _wave_format;
- };
- #endif /*__MYAACDECODER_H__*/
MyAACDecoder.cpp:
- #include "MyAACDecoder.h"
- MyAACDecoder::MyAACDecoder(): _handle(NULL), _samplerate(44100), _channel(2), _mybuffer(4096, 4096) {
- }
- MyAACDecoder::~MyAACDecoder() {
- _destroy_aac_decoder();
- }
- int32_t MyAACDecoder::Decode(char *aacbuf, uint32_t aacbuflen) {
- int32_t res = 0;
- if (!_handle) {
- if (_init_aac_decoder(aacbuf, aacbuflen) != 0) {
- ERR1(":::: init aac decoder failed ::::");
- return -1;
- }
- }
- //clean _mybuffer
- _mybuffer.Clean();
- uint32_t donelen = 0;
- uint32_t wav_data_len = 0;
- while (donelen < aacbuflen) {
- uint32_t framelen = _get_frame_length(aacbuf + donelen);
- if (donelen + framelen > aacbuflen) {
- break;
- }
- //decode
- NeAACDecFrameInfo info;
- void *buf = NeAACDecDecode(_handle, &info, (unsigned char*)aacbuf + donelen, framelen);
- if (buf && info.error == 0) {
- if (info.samplerate == 44100) {
- //44100Hz
- //src: 2048 samples, 4096 bytes
- //dst: 2048 samples, 4096 bytes
- uint32_t tmplen = info.samples * 16 / 8;
- _mybuffer.Fill((const char*)buf, tmplen);
- wav_data_len += tmplen;
- } else if (info.samplerate == 22050) {
- //22050Hz
- //src: 1024 samples, 2048 bytes
- //dst: 2048 samples, 4096 bytes
- short *ori = (short*)buf;
- short tmpbuf[info.samples * 2];
- uint32_t tmplen = info.samples * 16 / 8 * 2;
- for (int32_t i = 0, j = 0; i < info.samples; i += 2) {
- tmpbuf[j++] = ori[i];
- tmpbuf[j++] = ori[i + 1];
- tmpbuf[j++] = ori[i];
- tmpbuf[j++] = ori[i + 1];
- }
- _mybuffer.Fill((const char*)tmpbuf, tmplen);
- wav_data_len += tmplen;
- }
- } else {
- ERR1("NeAACDecDecode() failed");
- }
- donelen += framelen;
- }
- //generate Wave header
- _wave_format.setSampleRate(_samplerate);
- _wave_format.setChannel(_channel);
- _wave_format.setSampleBit(16);
- _wave_format.setBandWidth(_samplerate * 16 * _channel / 8);
- _wave_format.setDataLength(wav_data_len);
- _wave_format.setTotalLength(wav_data_len + 44);
- _wave_format.GenerateHeader();
- return 0;
- }
- uint32_t MyAACDecoder::_get_frame_length(const char *aac_header) const {
- uint32_t len = *(uint32_t *)(aac_header + 3);
- len = ntohl(len); //Little Endian
- len = len << 6;
- len = len >> 19;
- return len;
- }
- int32_t MyAACDecoder::_init_aac_decoder(char* aacbuf, int32_t aacbuflen) {
- unsigned long cap = NeAACDecGetCapabilities();
- _handle = NeAACDecOpen();
- if (!_handle) {
- ERR1("NeAACDecOpen() failed");
- _destroy_aac_decoder();
- return -1;
- }
- NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(_handle);
- if (!conf) {
- ERR1("NeAACDecGetCurrentConfiguration() failed");
- _destroy_aac_decoder();
- return -1;
- }
- NeAACDecSetConfiguration(_handle, conf);
- long res = NeAACDecInit(_handle, (unsigned char *)aacbuf, aacbuflen, &_samplerate, &_channel);
- if (res < 0) {
- ERR1("NeAACDecInit() failed");
- _destroy_aac_decoder();
- return -1;
- }
- //fprintf(stdout, "SampleRate = %d\n", _samplerate);
- //fprintf(stdout, "Channel = %d\n", _channel);
- //fprintf(stdout, ":::: init aac decoder done ::::\n");
- return 0;
- }
- void MyAACDecoder::_destroy_aac_decoder() {
- if (_handle) {
- NeAACDecClose(_handle);
- _handle = NULL;
- }
- }
1.ADTS是个啥
ADTS全称是(Audio Data Transport Stream),是AAC的一种十分常见的传输格式。
记得第一次做demux的时候,把AAC音频的ES流从FLV封装格式中抽出来送给硬件解码器时,不能播;保存到本地用pc的播放器播时,我靠也不能播。当时崩溃了,后来通过查找资料才知道。一般的AAC解码器都需要把AAC的ES流打包成ADTS的格式,一般是在AAC ES流前添加7个字节的ADTS header。也就是说你可以吧ADTS这个头看作是AAC的frameheader。
ADTS AAC
|
||||||
ADTS_header | AAC ES | ADTS_header | AAC ES |
...
|
ADTS_header | AAC ES |
2.ADTS内容及结构
ADTS 头中相对有用的信息 采样率、声道数、帧长度。想想也是,我要是解码器的话,你给我一堆得AAC音频ES流我也解不出来。每一个带ADTS头信息的AAC流会清晰的告送解码器他需要的这些信息。
一般情况下ADTS的头信息都是7个字节,分为2部分:
adts_fixed_header();
adts_variable_header();
syncword :同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始
ID:MPEG Version: 0 for MPEG-4, 1 for MPEG-2
Layer:always: '00'
profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种:
sampling_frequency_index:表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值。
There are 13 supported frequencies:
- 0: 96000 Hz
- 1: 88200 Hz
- 2: 64000 Hz
- 3: 48000 Hz
- 4: 44100 Hz
- 5: 32000 Hz
- 6: 24000 Hz
- 7: 22050 Hz
- 8: 16000 Hz
- 9: 12000 Hz
- 10: 11025 Hz
- 11: 8000 Hz
- 12: 7350 Hz
- 13: Reserved
- 14: Reserved
- 15: frequency is written explictly
channel_configuration: 表示声道数
- 0: Defined in AOT Specifc Config
- 1: 1 channel: front-center
- 2: 2 channels: front-left, front-right
- 3: 3 channels: front-center, front-left, front-right
- 4: 4 channels: front-center, front-left, front-right, back-center
- 5: 5 channels: front-center, front-left, front-right, back-left, back-right
- 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
- 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
- 8-15: Reserved
frame_length : 一个ADTS帧的长度包括ADTS头和AAC原始流.
adts_buffer_fullness:0x7FF 说明是码率可变的码流
3.将AAC打包成ADTS格式
如果是通过嵌入式高清解码芯片做产品的话,一般情况的解码工作都是由硬件来完成的。所以大部分的工作是把AAC原始流打包成ADTS的格式,然后丢给硬件就行了。
通过对ADTS格式的了解,很容易就能把AAC打包成ADTS。我们只需得到封装格式里面关于音频采样率、声道数、元数据长度、aac格式类型等信息。然后在每个AAC原始流前面加上个ADTS头就OK了。
贴上ffmpeg中添加ADTS头的代码,就可以很清晰的了解ADTS头的结构:
- int ff_adts_write_frame_header(ADTSContext *ctx,
- uint8_t *buf, int size, int pce_size)
- {
- PutBitContext pb;
- init_put_bits(&pb, buf, ADTS_HEADER_SIZE);
- /* adts_fixed_header */
- put_bits(&pb, 12, 0xfff); /* syncword */
- put_bits(&pb, 1, 0); /* ID */
- put_bits(&pb, 2, 0); /* layer */
- put_bits(&pb, 1, 1); /* protection_absent */
- put_bits(&pb, 2, ctx->objecttype); /* profile_objecttype */
- put_bits(&pb, 4, ctx->sample_rate_index);
- put_bits(&pb, 1, 0); /* private_bit */
- put_bits(&pb, 3, ctx->channel_conf); /* channel_configuration */
- put_bits(&pb, 1, 0); /* original_copy */
- put_bits(&pb, 1, 0); /* home */
- /* adts_variable_header */
- put_bits(&pb, 1, 0); /* copyright_identification_bit */
- put_bits(&pb, 1, 0); /* copyright_identification_start */
- put_bits(&pb, 13, ADTS_HEADER_SIZE + size + pce_size); /* aac_frame_length */
- put_bits(&pb, 11, 0x7ff); /* adts_buffer_fullness */
- put_bits(&pb, 2, 0); /* number_of_raw_data_blocks_in_frame */
- flush_put_bits(&pb);
- return 0;
- }
- 顶
- 0
- 踩
AAC音频格式详解的更多相关文章
- FLV视频封装格式详解
FLV视频封装格式详解 分类: FFMpeg编解码 2012-04-04 21:13 1378人阅读 评论(2) 收藏 举报 flvheaderaudiovideocodecfile 目录(?)[-] ...
- java分享第十五天(log4j 格式详解)
log4j 格式详解 log4j.rootLogger=日志级别,appender1, appender2, -. 日志级别:ALL<DEBUG<INFO<WARN<ERRO ...
- php 序列化(serialize)格式详解
1.前言 PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize.unserialize.不过在 PHP 手册中对这两个函数的说明仅限于如何使用,而对序列 ...
- Java字节码(.class文件)格式详解(一)
原文链接:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html 小介:去年在读<深入解析JVM>的时候写的,记得当时还 ...
- PNG,JPEG,BMP,JIF图片格式详解及其对比
原文地址:http://blog.csdn.net/u012611878/article/details/52215985 图片格式详解 不知道大家有没有注意过网页里,手机里,平板里的图片,事实上,图 ...
- binlog之四:mysql中binlog_format模式与配置详解,binlog的日志格式详解
mysql复制主要有三种方式:基于SQL语句的复制(statement-based replication, SBR),基于行的复制(row-based replication, RBR),混合模式复 ...
- 以太网帧格式、IP数据报格式、TCP段格式+UDP段格式 详解
转载:http://www.cnblogs.com/lifan3a/articles/6649970.html 以太网帧格式.IP数据报格式.TCP段格式+UDP段格式 详解 1.ISO开放系统有 ...
- Linux下 ps -ef 和 ps aux 的区别及格式详解
原文:https://www.cnblogs.com/5201351/p/4206461.html Linux下ps -ef和ps aux的区别及格式详解 Linux下显示系统进程的命令ps,最常用的 ...
- WAL日志文件名称格式详解
转自:http://blog.osdba.net/534.html WAL日志文件名称格式详解 PostgreSQL的WAL日志文件在pg_xlog目录下,一般情况下,每个文件为16M大小: osdb ...
随机推荐
- Linux环境下服务器环境搭建-mysql
下载对应版本的mysql.rpm(Linux 6 安装el6 Linux 7 安装el7) 安装环境 centos 7,安装版本mysql57-community-release-el7-9.noar ...
- Hibernate笔记②--hibernate类生成表、id生成策略、级联设置、继承映射
一.多表的一个关联关系 老师和学生是一对多的关系 student:tid属性 外键约束 对应teacher表中的id属性 teacher:id 在myeclipse的db窗口中选中两个表来生成类. ...
- 使用exe4j将jar包导出为exe
Exe4J使用方法 此工具是将Java程序包装成exe格式文件工具.(点击exe4j\bin\exe4j.exe文件)启动后如下图所示 如果未注册,则可使用这个注册码:A-XVK209982F-1y0 ...
- mybatis 原理
什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为 ...
- BeanCreationException报错启动不起来(jar包冲突)
一月 23, 2015 3:46:13 下午 org.apache.catalina.core.AprLifecycleListener init 信息: The APR based Apache T ...
- BETA-3
前言 我们居然又冲刺了·三 团队代码管理github 站立会议 队名:PMS 530雨勤(组长) 过去两天完成了哪些任务 一堆deadline截至前的两天,为了图形学和编译原理毅然决然地放弃冲刺 接下 ...
- Java面试& HashMap实现原理分析
1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O( ...
- C语言中Union类型的使用方法
转自:http://blog.csdn.net/feimor/article/details/6858103 使用C语言时,常常使用struct,对于union类型却几乎没有用过,只知道它是联合类型, ...
- git常用命令复习及其基本使用示例
年后回来新上到项目,对于git的一些操作命令记得有点混乱了,所以特整理笔记如下: 一.git常用命令复习 查看当前分支:git branch (显示结果中带有*号的是当前分支)查看所有分支: git ...
- ISCC2018(web)
ISCC2018 web writeup (部分) #web1:比较数字大小 只要比服务器上的数字大就好了 限制了输入长度,更改长度就好 #web2: 普通的代码审计,数组绕过 #web3:本地的诱惑 ...