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);
- }
-