2025年3月24日 星期一 甲辰(龙)年 月廿三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 文件格式与编码

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

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

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