您当前的位置:首页 > 计算机 > 文件格式与编码

音视频数据处理入门:AAC音频码流解析

时间:01-02来源:作者:点击数:

AAC原始码流是由一个个的ADTS frame(音频数据传输流帧)组成的;每一帧由ADTS头(ADTS Header)和原始数据块(ADTS ES)组成。

image-20210423170022777

ADTS头一般情况下为7字节(56 bits),它由两部分组成:adts_fixed_header(28bits)和adts_variable_header(28bits)。

1、adts_fixed_header

image-20210423163859984
主要字段 描述
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

image-20210423165247807
主要字段 描述
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
image-20210423202022983

4、代码中有个bug

帧个数 > aac文件大小,为什么会出现这种情况?

image-20210423203248174

代码策略:解析一个ADTS头(7字节)后,使用fseek向后偏移len-7个字节到下一个帧头。

image-20210423203615511

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);
}
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门