介绍
AAC的音频文件格式有ADIF & ADTS:
ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
ADTS:Audio Data Transport Stream。是AAC音频的传输流格式。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。
总结:ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。
ADTS 格式
从图上可以总结出两点:
ADTS Frame = ADTS头+AAC ES(AAC音频数据)
ADTS头包含了AAC文件的采样率、通道数、帧数据长度等信息。ADTS头分为固定头信息和可变头信息两个部分,固定头信息在每个帧中的是一样的,可变头信息在各个帧中并不是固定值。ADTS头一般是7个字节((28+28)/ 8)长度,如果需要对数据进行CRC校验,则会有2个Byte的校验码,所以ADTS头的实际长度是7个字节或9个字节。
固定头信息:adts_fixed_header()
- syncword :同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始
- Layer:always: '00'
- profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了4种:
0: AAC Main
1:AAC LC (Low Complexity)
2:AAC SSR (Scalable Sample Rate)
3:AAC LTP (Long Term Prediction)
- sampling_frequency_index:表示使用的采样率下标
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,
- 可变头信息:adts_variable_header()
- copyrighted_id_bit:编码时设置为0,解码时忽略
- copyrighted_id_start:编码时设置为0,解码时忽略
- aac_frame_length:ADTS帧长度包括ADTS长度和AAC声音数据长度的和。即 - - aac_frame_length = (protection_absent == 0 ? 9 : 7) + audio_data_length
- adts_buffer_fullness:固定为0x7FF。表示是码率可变的码流
- number_of_raw_data_blocks_in_frame:表示当前帧有number_of_raw_data_blocks_in_frame + 1 个原始帧(一个AAC原始帧包含一段时间内1024个采样及相关数据)。
实例分析
这部分来自雷神的博客,不过在解析的地方进行了大量的注解,方便理解解析过程。
本文中的程序是一个AAC码流解析程序。该程序可以从AAC码流中分析得到它的基本单元ADTS frame,并且可以简单解析ADTS frame首部的字段。通过修改该程序可以实现不同的AAC码流处理功能。
原理
AAC原始码流(又称为“裸流”)是由一个一个的ADTS frame组成的。他们的结构如下图所示。
其中每个ADTS frame之间通过syncword(同步字)进行分隔。同步字为0xFFF(二进制“111111111111”)。AAC码流解析的步骤就是首先从码流中搜索0x0FFF,分离出ADTS frame;然后再分析ADTS frame的首部各个字段。本文的程序即实现了上述的两个步骤
代码
整个程序位于simplest_aac_parser()函数中,如下所示。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/// 从buffer里获取ADTS帧数据到data里,获取大小为data_size
/// @param buffer buffer地址
/// @param buf_size buffer大小
/// @param data data数据地址
/// @param data_size data数据大小
int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data ,int* data_size){
int size = 0;
if(!buffer || !data || !data_size ){
return -1;
}
while(1){
if(buf_size < 7 ){// ADTS帧的ADTS header为7个字节或者9个字节,所以判断<7,则肯定不存在一个ADTS,因为ADTS = ADTS header + AAC ES
return -1;
}
//Sync words
if((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){
/**
1. aac_frame_length 为13bit, 从第31bit开始,到43bit结束
2. 按位或|= 运算: a |= b 为 a = a | b, 只要有一个为1, 则结果为1
3. >>, << 运算符优先级比按位或|=优先级高
4. 0x03,代表一个字节,其二进制为0000 0011
5. ((buffer[3] & 0x03)这个是取第4个字节和(0000 0011)进行与运算,结果是取第4个字节后2位,即第31位开始,到32位结束
6. (第31位到第32位)<< 11, 一共13bit,左移动11bit,则这2bit为13bit中的高2bit,假如(buffer[3] & 0x03)为二进制11, 则左移11bit后,为1 1000 0000 0000, 一共13位,11就变成13位里的高2位
7.size = size | (1 1000 0000 0000) = 1 1000 0000 0000
*/
size |= ((buffer[3] & 0x03) <<11); //high 2 bit
/**
1.buffer[4]拿到第5个字节,即33bit到40bit,
2.左移3位,假如buffer[4]为1111 1111 , 则左移动3位,为 111 1111 1000 一共11位
3. size = size | (buffer[4]<<3) 即 1 1000 0000 0000 |111 1111 1000 = 1 1111 1111 1000, 可以看出这8位是13bit中间的8位
*/
size |= buffer[4]<<3; //middle 8 bit
/**
1. buffer[5]拿到第6个字节,即41bit到48bit,
2.0xe0的二进制为1110 0000
3. (buffer[5] & 0xe0) 即获取字节的高3位,即41bit到43bit,右移5位,即1110 0000 变成0000 0111
4. size = size | ((buffer[5] & 0xe0)>>5) = 1 1111 1111 1000 |0 000 0000 0111 = 1 1111 1111 1111
5. 可以看出这个是13bit里的低三位
*/
size |= ((buffer[5] & 0xe0)>>5); //low 3bit
break;
}
// 从下一个字节继续查找Sync words
--buf_size;
++buffer;
}
if(buf_size < size){
return 1;
}
//
memcpy(data, buffer, size);
*data_size = size;
return 0;
}
int simplest_aac_parser(char *url)
{
int data_size = 0;
int size = 0;
int cnt=0;
int offset=0;
//FILE *myout=fopen("output_log.txt","wb+");
FILE *myout=stdout;
unsigned char *aacframe=(unsigned char *)malloc(1024*5);
unsigned char *aacbuffer=(unsigned char *)malloc(1024*1024);
// 打开aac文件
FILE *ifile = fopen(url, "rb");
if(!ifile){
printf("Open file error");
return -1;
}
printf("-----+- ADTS Frame Table -+------+\n");
printf(" NUM | Profile | Frequency| Size |\n");
printf("-----+---------+----------+------+\n");
while(!feof(ifile)){
/**
ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
size_t fread(void * __restrict __ptr, size_t __size, size_t __nitems, FILE * __restrict __stream);
size -- 这是要读取的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
从给定流 stream 读取数据到 ptr 所指向的数组中。
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。
读取1024*1024-offset 个元素大小为1个字节的数据到aacbuffer+offset地址
data_size为字节个数
*/
data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);
fprintf(myout,"----------offset: %d, data_size: %d, aacbuffer: %p\n",offset, data_size, aacbuffer);
unsigned char* input_data = aacbuffer;
while(1)
{
// 从input_data里获取aacframe,input_data的大小为data_size, 获取的大小为size
int ret=getADTSframe(input_data, data_size, aacframe, &size);
if(ret==-1){
break;
}else if(ret==1){
/**
void *memcpy(void *__dst, const void *__src, size_t __n);
从存储区 __src 复制 n 个字节到存储区 __dst
当获取时,data_size < 一帧的大小时,则将剩余的data_size大小写入到aacbuffer里
*/
memcpy(aacbuffer,input_data,data_size);
offset=data_size;
break;
}
char profile_str[10]={0};
char frequence_str[10]={0};
/**
profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。2bit, 从第17bit开始,到18bit结束,即第三个字节前2位
在MPEG-2 AAC中定义了3种:
0: AAC Main
1:AAC LC (Low Complexity)
2:AAC SSR (Scalable Sample Rate)
3:AAC LTP (Long Term Prediction)
0xC0------11000000
aacframe[2]&0xC0取第3个字节的前2位
*/
unsigned char profile=aacframe[2]&0xC0;
profile=profile>>6;
switch(profile){
case 0: sprintf(profile_str,"Main");break;
case 1: sprintf(profile_str,"LC");break;
case 2: sprintf(profile_str,"SSR");break;
default:sprintf(profile_str,"unknown");break;
}
/**
0x3C 二进制为 0011 1100
aacframe[2]&0x3C取第三个字节中间4位,即从第19位开始,到22位结束
比如aacframe[2]为 0111 0101, 则aacframe[2]&0x3C为0011 0100
0011 0100右移2位,则为0000 1101
*/
unsigned char sampling_frequency_index=aacframe[2]&0x3C;
sampling_frequency_index=sampling_frequency_index>>2;
switch(sampling_frequency_index){
case 0: sprintf(frequence_str,"96000Hz");break;
case 1: sprintf(frequence_str,"88200Hz");break;
case 2: sprintf(frequence_str,"64000Hz");break;
case 3: sprintf(frequence_str,"48000Hz");break;
case 4: sprintf(frequence_str,"44100Hz");break;
case 5: sprintf(frequence_str,"32000Hz");break;
case 6: sprintf(frequence_str,"24000Hz");break;
case 7: sprintf(frequence_str,"22050Hz");break;
case 8: sprintf(frequence_str,"16000Hz");break;
case 9: sprintf(frequence_str,"12000Hz");break;
case 10: sprintf(frequence_str,"11025Hz");break;
case 11: sprintf(frequence_str,"8000Hz");break;
default:sprintf(frequence_str,"unknown");break;
}
fprintf(myout,"%5d| %8s| %8s| %5d|\n",cnt,profile_str ,frequence_str,size);
// 读取后data_size减去一帧ADTS Frame的大小, 数据buffer则向前移动到下一帧的位置
data_size -= size;
input_data += size;
cnt++;
}
}
fclose(ifile);
free(aacbuffer);
free(aacframe);
return 0;
}
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self parseAAC];
}
- (void)parseAAC {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"131" ofType:@"aac"];
NSLog(@"filePath: %@", filePath);
if (filePath) {
const char *c_filePath = [filePath UTF8String];
if (c_filePath) {
simplest_aac_parser(c_filePath);
}
}
}
@end
结果
参考
AAC格式ADTS+实例剖析 https://www.jianshu.com/p/c48cac7eb962
视音频数据处理入门:AAC音频码流解析 https://blog.csdn.net/leixiaohua1020/article/details/50535042