前言
aac的adts封装格式的音频文件是可以直接播放的,因为其内部的数据中每一帧都带有adts头部,头部包含了解码的必要信息。不像wav文件其头部的字段都是基于byte为单位,直接使用内存结构相同的实体即可直接读取,adts的头部字段是以bit为单位的,这就给解析其头部带来了一定的难度,几乎获取每个字段都需要进行位操作,一些跨byte的位还需要进行字节序的转换。本文将提供解析adts头的具体方法及实现。
一、原理说明
1.aac-adts的结构
adts的结构是每一帧都带adts头部,头部后面跟着是aac的原始流(aac es),结构如下:
2.adts的头结构
adts的头部一共有15个字段,共7bytes,如果有校验位则会在尾部增加2bytesCRC校验。具体如下:
序号 | 字段 | 长度 | 说明 |
---|---|---|---|
1 | synword | 12bit | 同步头,总是0xFFF,代表着⼀个ADTS帧的开始。 |
2 | id | 1bit | 设置MPEG标识符,1bit,0标识MPEG-4,1标识MPEG-2。 |
3 | layer | 2bit | 总是00。 |
4 | protection_absent | 1bit | 误码校验,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。为0时头部7bytes后面+2bytesCRC检验位。 |
5 | profile | 2bit | AAC级别,比如AAC LC=1。profile的值等于Audio Object Type的值减1。 |
6 | sampling_frequency_index | 4bit | 采样率下标,下标对应的采样率如下 。 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 |
7 | private_bit | 1bit | 私有位,编码时设置为0,解码时忽略。 |
8 | channel_configuration | 3bit | 声道数。 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 |
9 | orininal_copy | 1bit | 编码时设置为0,解码时忽略。 |
10 | home | 1bit | 编码时设置为0,解码时忽略。 |
11 | copyrigth_identification_bit | 1bit | 编码时设置为0,解码时忽略。 |
12 | copyrigth_identification_stat | 1bit | 编码时设置为0,解码时忽略。 |
13 | aac_frame_length | 13bit | 一个ADTS帧的⻓度,包括ADTS头和AAC原始流。 |
14 | adts_bufferfullness | 11bit | 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。具体查看附录。 |
15 | number_of_raw_data_blocks_in_frame | 2bit | 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧,为0表示说ADTS帧中只有一个AAC数据. |
一个具体的例子如下:
上面的数据对应的值如下
序号 | 字段 | 长度 | 值 |
---|---|---|---|
1 | synword | 12bit | 111111111111 |
2 | id | 1bit | 0 |
3 | layer | 2bit | 00 |
4 | protection_absent | 1bit | 1 |
5 | profile | 2bit | 01 |
6 | sampling_frequency_index | 4bit | 0100(十进制4) |
7 | private_bit | 1bit | 0 |
8 | channel_configuration | 3bit | 010 (十进制2) |
9 | orininal_copy | 1bit | 0 |
10 | home | 1bit | 0 |
11 | copyrigth_identification_bit | 1bit | 0 |
12 | copyrigth_identification_stat | 1bit | 0 |
13 | aac_frame_length | 13bit | 0000101111100(十进制380) |
14 | adts_bufferfullness | 11bit | 00010010000 |
15 | number_of_raw_data_blocks_in_frame | 2bit | 00 |
3.如何解析?
(1)使用位操作
通过位操作获取具体的字段值,一般就是通过位移、位与、位或。比如:
//adts头部二进制数据
uint8_t data[7];
//获取id的值,id在第13bit,即在第2个byte中的第5位,所以对data[1]右移3位即将其移动到了最低位,再进行位与去掉其他无关位,得到的uint8_t的值即使id的值。
uint8_t id = (data[1] >> 3) & 0x01;
(2)转换字节序
当字段长度超过1byte时就需要考虑字节序的问题了,由于大端小端字节序排列相反,相同的位操作在不同的机器上可能不一致,为了确保结果正确,我们可以统一使用网络字节序(大端)的方式进行位操作,操作完成之后再将得到的字段转换成本地字节序。比如获取synword可以如下操作:
uint8_t* p;
uint16_t synword;
p = (uint8_t*)&synword;
//按网络字节序进行位操作
p[0] = (data[1] >> 4) & 0x0f;
p[1] = data[0];
//将网络字节序转换成本地字节序
synword = ntohs(synword);
二、代码实现
AacADTSHeader.h
#pragma once
#include<stdint.h>
/************************************************************************
* @Project: AC::AacADTSHeader
* @Decription: AAC ADTS头部解析
* @Verision: v1.0.0.0
* @Author: Xin Nie
* @Create: 2022/2/24 13:10:17
* @LastUpdate: 2022/2/25 15:50:26
************************************************************************
* Copyright @ 2022. All rights reserved.
************************************************************************/
namespace AC {
class AacADTSHeader;
/// <summary>
/// AAC ADTS头部解析工具
/// </summary>
class AacADTSParse {
public:
static void BinaryToHeader(const uint8_t data[7], AacADTSHeader&);
static void HeaderToBinary(const AacADTSHeader& ,uint8_t data[7]);
};
/// <summary>
/// AAC ADTS头部实体
/// </summary>
class AacADTSHeader
{
public:
/// <summary>
/// 同步头,12bit,总是0xFFF,all bits must be 1,代表着⼀个ADTS帧的开始。
/// </summary>
uint16_t synword = 0xFFF;
/// <summary>
/// 设置MPEG标识符,1bit,0标识MPEG-4,1标识MPEG-2。
/// </summary>
uint8_t id = 0;
/// <summary>
/// 2bit always: '00'
/// </summary>
uint8_t layer = 0x00;
/// <summary>
/// 误码校验,1bit,表示是否误码校验。标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。为0时头部7bytes后面+2bytesCRC检验位,为1时只有7bytes
/// </summary>
/// <returns>误码校验</returns>
uint8_t protection_absent = 1;
/// <summary>
/// AAC级别,2bit,表示使用哪个级别的AAC,比如AAC LC=1。profile的值等于Audio Object Type的值减1
/// </summary>
uint8_t profile;
/// <summary>
/// 采样率下标,4bit,表示使用的采样率下标
/// 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
/// </summary>
uint8_t sampling_frequency_index;
/// <summary>
/// 私有位,1bit,编码时设置为0,解码时忽略
/// </summary>
/// <returns>私有位</returns>
uint8_t private_bit = 0;
/// <summary>
/// 声道数,3bit
/// 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
/// </summary>
uint8_t channel_configuration;
/// <summary>
/// 1bit,编码时设置为0,解码时忽略。
/// </summary>
uint8_t orininal_copy = 0;
/// <summary>
/// 1bit,编码时设置为0,解码时忽略。
/// </summary>
uint8_t home = 0;
/// <summary>
/// 1bit,编码时设置为0,解码时忽略。
/// </summary>
uint8_t copyrigth_identification_bit = 0;
/// <summary>
/// 1bit,编码时设置为0,解码时忽略。
/// </summary>
uint8_t copyrigth_identification_stat = 0;
/// <summary>
/// aac帧长度,13bit,一个ADTS帧的⻓度包括ADTS头和AAC原始流
/// </summary>
uint16_t aac_frame_length = 0;
/// <summary>
/// 11bit,0x7FF说明是码率可变的码流。
/// </summary>
uint16_t adts_bufferfullness = 0x7FF;
/// <summary>
/// 2bit,表示ADTS帧中有value + 1个AAC原始帧,为0表示说ADTS帧中只有一个AAC数据块。
/// </summary>
uint8_t number_of_raw_data_blocks_in_frame = 0;
};
}
完整代码:
https://download.csdn.net/download/u013113678/85320440
三、使用示例
1.读取aac文件
#include<stdio.h>
#include<stdint.h>
#include<exception>
#include"AacADTSHeader.h"
int main(int argc, char* argv[])
{
AC::AacADTSHeader h;
uint8_t buffer[1024];
uint8_t* aacData;
int aacDataLength;
FILE* f = fopen("test.aac", "rb+");
if (!f)
{
printf("open aac file error!");
return -1;
}
try {
while (1)
{
int size;
size = fread(buffer, 1, 7, f);
if (size < 7)
break;
AC::AacADTSParse::BinaryToHeader(buffer,h);
size = fread(buffer, 1, h.aac_frame_length - 7, f);
if (size != h.aac_frame_length - 7)
{
throw std::exception("incorrect length!");
}
if (h.protection_absent == 0)
//有校验位
{
//处理校验位buffer[0]、buffer[1]
aacData = buffer + 2;
aacDataLength = size - 2;
}
else
{
aacData = buffer;
aacDataLength = size;
}
//TODO:到此取得aac数据aacData、aacDataLength
}
}
catch (const std::exception& e)
{
printf("%s,\n", e.what());
}
if (f)
{
fclose(f);
}
return 0;
}
总结
以上就是今天要讲的内容,对于adts的头部解析其实并不算难,只要懂得基本的位操作以及字节序的转换基本就可以实现,对于文件的读取也是需要注意判断CRC校验即可,总的来虽然单纯解析adts不难,对于adts有些细节还是需要去了解的,比如CBR时adts_bufferfullnessadts_bufferfullness的计算方式,number_of_raw_data_blocks_in_frame不0时如何处理数据等等。
附录
adts_bufferfullness的相关资料http://blog.olivierlanglois.net/index.php/2008/09/12/aac_adts_header_buffer_fullness_field