AAC原始码流是由一个个的ADTS frame(音频数据传输流帧)组成的;每一帧由ADTS头(ADTS Header)和原始数据块(ADTS ES)组成。
ADTS头一般情况下为7字节(56 bits),它由两部分组成:adts_fixed_header(28bits)和adts_variable_header(28bits)。
1、adts_fixed_header
主要字段 | 描述 |
---|---|
syncword | 12位全为1,0XFFF,分隔不同的帧 |
ID | Mpeg版本,0 for MPEG-4 |
protection_absent | 默认1,不用CRC |
profile | 哪个级别的AAC,01表示LC(低复杂度) |
sampling_frequency_index | 采样率下标,0x4表示44100Khz |
channel_configuration | 声道数,2表示双声道 |
2、adts_variable_header
主要字段 | 描述 |
---|---|
aac_frame_length | ADTS帧长度,最大8KB |
adts_buffer_fullness | 0x7ff表示码率可变的码流 |
number_of_raw_data_blocks_in_frame | 帧内有n+1个原始数据块 |
3、解析代码与测试
1、获取aac码流
从视频中抽取aac码流:ffmpeg -i input.mp4 -acodec copy -vn out.aac
播放aac音频数据:ffplay out.aac -nodisp
2、解析ADTS_Header
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int display_header_msg(char header[], int cnt, int *length) {
if (header[0] != 0xff && (header[1] & 0xF0) != 0xf0) {
return -1; // syncword(12bits)不是0XFFF
}
// profile 17:18位, 取第3字节高2位
char profile_str[8];
int pro = ((int)header[2] & 0xC0) >> 6;
switch (pro) {
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;
};
// frequency_idx 19:23位,取第3字节中间4位
char freq_str[8];
int freq = (header[2] & 0x3C) >> 2;
switch (freq) {
//...
case 3: sprintf(freq_str, "48000KHz");break;
case 4: sprintf(freq_str, "44100KHz");break;
//...
default: sprintf(freq_str, "unknown");break;
}
// aac_frame_length 30:42(13位) header[3]后2位+header[4]+header[5]前3位
int size = (header[5] & 0XE0) >> 5; // low 3bits
size += header[4] << 3; // mid 8bits
size += (header[3] & 0x03) << 11; // high 2bits
*length = size;
printf("|%5d| %8s| %10s| %5d|\n", cnt, profile_str, freq_str, size);
return 0;
}
void aac_parser_adts(const char *aacfile) {
FILE *fp = fopen(aacfile, "r");
char adts[7];
printf("|------- ADTS Frame Message -------|\n");
printf("| NUM | Profile | Frequency | Size |\n");
printf("|-----|---------|-----------|------|\n");
int cnt = 0, len = 0;
while (!feof(fp)) {
fread(adts, 1, 7, fp);
if (display_header_msg(adts, cnt, &len) < 0) {
printf("header bad\n");
break;
}
cnt++;
fseek(fp, len-7, SEEK_CUR); //向后移动n个字节
//printf("seek_cur:%d\n", ftell(fp)); //当前offset
}
fclose(fp);
}
int main(int argc, char const* argv[])
{
aac_parser_adts("output.aac");
return 0;
}
3、运行和测试
gcc aac_parser_adts.c
./a.out > out.txt
4、代码中有个bug
帧个数 > aac文件大小,为什么会出现这种情况?
代码策略:解析一个ADTS头(7字节)后,使用fseek向后偏移len-7个字节到下一个帧头。
fseek(fp, len-7, SEEK_CUR)使文件指针向后移动n个字节;但如果超过文件自身大小,则函数执行失败,不会改变文件的偏移,返回-1。
有两种解决方式:
①fseek返回-1时直接退出循环
②
fseek(fp, 0, SEEK_END);
int file_end = ftell(fp);
rewind(fp);
while (1) {
// ...
if (ftell(fp) + len - 7 > file_end)
break;
fseek(fp, len-7, SEEK_CUR);
}