前言
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)使用位操作
通过位操作获取具体的字段值,一般就是通过位移、位与、位或。比如:
uint8_t data[7];
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>
namespace AC {
class AacADTSHeader;
class AacADTSParse {
public:
static void BinaryToHeader(const uint8_t data[7], AacADTSHeader&);
static void HeaderToBinary(const AacADTSHeader& ,uint8_t data[7]);
};
class AacADTSHeader
{
public:
uint16_t synword = 0xFFF;
uint8_t id = 0;
uint8_t layer = 0x00;
uint8_t protection_absent = 1;
uint8_t profile;
uint8_t sampling_frequency_index;
uint8_t private_bit = 0;
uint8_t channel_configuration;
uint8_t orininal_copy = 0;
uint8_t home = 0;
uint8_t copyrigth_identification_bit = 0;
uint8_t copyrigth_identification_stat = 0;
uint16_t aac_frame_length = 0;
uint16_t adts_bufferfullness = 0x7FF;
uint8_t number_of_raw_data_blocks_in_frame = 0;
};
}
AacADTSHeader.cpp
#include"AacADTSHeader.h"
#include <exception>
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include <sys/socket.h>
#endif
namespace AC {
void AacADTSParse::BinaryToHeader(const uint8_t data[7], AacADTSHeader& header)
{
uint8_t* p;
p = (uint8_t*)&header.synword;
p[0] = (data[1] >> 4) & 0x0f;
p[1] = data[0];
header.synword = ntohs(header.synword);
if (header.synword != (uint16_t)0x0FFF)
throw std::exception("The data is not an adts header!");
header.id = (data[1] >> 3) & 0x01;
header.layer = (data[1] >> 1) & 0x03;
if (header.layer != 0x00)
throw std::exception("The data is not an adts header!");
header.protection_absent = (data[1]) & 0x01;
header.profile = ((data[2] >> 6) & 0x03);
header.sampling_frequency_index = (data[2] >> 2 & 0x0f);
header.private_bit = (data[2] >> 1) & 0x01;
header.channel_configuration = ((data[2] & 0x01) << 2) | ((data[3] >> 6) & 0x03);
header.orininal_copy = (data[3] >> 5) & 0x01;
header.home = (data[3] >> 4) & 0x01;
header.copyrigth_identification_bit = (data[3] >> 3) & 0x01;
header.copyrigth_identification_stat = (data[3] >> 2) & 0x01;
p = (uint8_t*)&header.aac_frame_length;
p[0] = ((data[3] & 0x03) << 3) | ((data[4] >> 5) & 0x07);
p[1] = ((data[4] << 3) & 0xf8) | ((data[5] >> 5) & 0x07);
header.aac_frame_length = ntohs(header.aac_frame_length);
p = (uint8_t*)&header.adts_bufferfullness;
p[0] = ((data[5] & 0x07) >> 2);
p[1] = ((data[5] << 6) & 0xc0) | ((data[6] >> 2) & 0x3f);
header.adts_bufferfullness = ntohs(header.adts_bufferfullness);
header.number_of_raw_data_blocks_in_frame = (data[6] & 0x03);
}
void AacADTSParse::HeaderToBinary(const AacADTSHeader&header, uint8_t data[7])
{
uint8_t* p;
uint16_t synword = htons(header.synword);
p = (uint8_t*)&synword;
data[0] = ((p[0] << 4) & 0xf0) | ((p[1] >> 4) & 0x0f);
data[1] = ((p[1] << 4) & 0xf0) | ((header.id << 3) & 0x08) | ((header.layer << 1) & 0x06) | (header.protection_absent & 0x01);
data[2] = ((header.profile << 6) & 0xc0) | ((header.sampling_frequency_index << 2) & 0x36) | ((header.private_bit << 1) & 0x02) | ((header.channel_configuration >> 2) & 0x01);
uint16_t aac_frame_length = htons(header.aac_frame_length);
p = (uint8_t*)&aac_frame_length;
data[3] = ((header.channel_configuration << 6) & 0xc0) | ((header.orininal_copy << 5) & 0x20) | ((header.home << 4) & 0x10) | ((header.copyrigth_identification_bit << 3) & 0x08) | ((header.copyrigth_identification_stat << 2) & 0x04) | ((p[0] >> 3) & 0x03);
data[4] = ((p[0] << 5) & 0xe0) | ((p[1] >> 3) & 0x1f);
data[5] = ((p[1] << 5) & 0xe0);
uint16_t adts_bufferfullness = htons(header.adts_bufferfullness);
p = (uint8_t*)&adts_bufferfullness;
data[5] = data[5] | ((p[0] << 2) & 0x1c) | ((p[1] >> 6) & 0x03);
data[6] = ((p[1] << 2) & 0xfc) | (header.number_of_raw_data_blocks_in_frame & 0x03);
}
}
三、使用示例
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)
{
aacData = buffer + 2;
aacDataLength = size - 2;
}
else
{
aacData = buffer;
aacDataLength = size;
}
}
}
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
|