一 前言
最近在尝试学习一些视频相关的知识, 随便一搜才知道原来国内有雷神这么一个真正神级的人物存在, 尤其是在这里 (传送门) 看到他的感言更是对他膜拜不已, 雷神这种无私奉献的精神应当被我辈发扬光大. 那写这篇随笔的理由是在看他写的 AAC 音频码流解析文章时 (传送门) 遇到一些问题, 因为雷神毕竟等级与初学者不同, 一些在他看来很基础的东西菜鸟 (比如我) 一看就懵逼了, 看得是云里雾里, 而且我在评论中也看到有人提问相同的问题, 但是并没有人给出解答, 我自己花了将近三个小时仔细看了 AAC 码流的介绍才明白, 这里也献丑讲解一下.
二 AAC 码流数据存储格式
这里先把雷神的话看一遍
这当然是没问题的, 不过雷神说的有点过于简单了, ADTS frame 内部的结构的什么样子的? 数据存储在 ADTS frame 的哪一部分? 这些并没有说清楚, 所以下面看代码时就会搞不懂. 我通过 AAC Audio ES Viewer 打开了一个 AAC 码流文件, 这个软件能将一个 AAC 码流文件解析成一个个的 ADTS frame, 咱们来看一下
我这里选择了第一个 ADTS 段, 看右边的部分, 可以看到一个 ADTS 内部其实又有四个部分组成: adts_fixed_header/adts_variable_header/adts_error_check/raw_data_block, 其中后两个部分中并没有什么东西, 咱们就先不管它们, 重点分析下前两个部分. 上面图中每一个部分后面都标了所占的 bit, 咱们可以计算一下, 可以知道总共是 56bit, 也就是 7 个 byte. 也就是说 ADTS header 占 7 个字节, header 也有可能占 9 个字节, 看 adts_fixed_header 部分中的 protection_absent, 当这个值为 0 时, 占 7 字节, 为 1 时会占 9 个字节, 当然这个就先说到这里, 不是今天的重点, 先不讨论. 接下来咱们说下前面两个部分中比较重要的参数含义:
adts_fixed_header
syncword: 同步字, 占 12bit, 值固定, 都是 0xFFF, 转成二进制就是 111111111111, 这是每个 ADTS frame 的开头, 就像上面雷神说的, 咱们可以找到这个值, 就能把 AAC 码流一个一个的分割开
ID: 表示使用的 MPEG 的版本, 0 表示 MPEG-4,1 表示 MPEG-2
layer: 同 syncword, 值固定, 都是 00
protection_absent: 是否有同步校验, 如果有值是 0, 没有是 1
profile: 使用的 AAC 级别
sampling_frequency_index: 采样率, 上图中可以看到是 48000 Hz
channel_configuration: 声道数, 上图中可以看到两个声道, LF RF 表示左右声道
adts_variable_header
aac_frame_length:ADTS frame 长度, 包括 header 和 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 ){
- return -1;
- }
- //Sync words
- if((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){
- size |= ((buffer[3] & 0x03) <<11); //high 2 bit
- size |= buffer[4]<<3; //middle 8 bit
- size |= ((buffer[5] & 0xe0)>>5); //low 3bit
- break;
- }
- --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);
- 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)){
- data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);
- unsigned char* input_data = aacbuffer;
- while(1)
- {
- int ret=getADTSframe(input_data, data_size, aacframe, &size);
- if(ret==-1){
- break;
- }else if(ret==1){
- memcpy(aacbuffer,input_data,data_size);
- offset=data_size;
- break;
- }
- char profile_str[10]={0};
- char frequence_str[10]={0};
- 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;
- }
- 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,"]| %8s| %8s| ]|\n",cnt,profile_str ,frequence_str,size);
- data_size -= size;
- input_data += size;
- cnt++;
- }
- }
- fclose(ifile);
- free(aacbuffer);
- free(aacframe);
- return 0;
- }
然后说一下当初我看的时候迷惑的地方.
1, 代码第 9 行, 为什么要判断 size 是否小于 7?
答: 第二部分时有说, 一个 ADTS header 最少占 7 字节, 当小于 7 字节时, 说明不是一个 ADTS frame 或数据不完整, 没必要解析了.
2, 第 13 行,((buffer[1] & 0xf0) == 0xf0), 为什么要进行位运算?
答: 第二部分也有说, 同步字占 12bit, 也就是它占了 1.5 个字节, 第一个字节和第二个字节的前四位, 0xF0 用二进制表示是 11110000, 和 buffer[1]进行 & 运算后如果还是 11110000, 说明第二个字节的前四位是 1111, 再加上前面的 buffer[0]=0xFF, 就可以判定 buffer 的前 12bit 是 111111111111, 也就取得了 syncword.
3, 取 size 的三行代码到底是什么鬼????
- size |= ((buffer[3] & 0x03) <<11); //high 2 bit
- size |= buffer[4]<<3; //middle 8 bit
- size |= ((buffer[5] & 0xe0)>>5); //low 3bit
其实雷神注释中已经说了, 但是不了解数据结构的依然会懵逼. 第二部分说了, ADTS header 中有 ADTS frame 的大小, 但是根据上面同步字咱们可以看出来, 这些数据并不是以字节为单位连续排列的, 而是按位排列的, 这就有点纠结了不是? 那 size 存储在哪一位中, 从哪里开始? 在哪里结束? 头大!! 别急, 我画了一张图(图片比较大, 如果看不情, 请点这里下载)
从这一张图中可以很清晰的看到, frame_length 存储在第 4 个字节的后两位, 第 5 个字节, 第 6 个字节的前三位. 好了, 知道这些再看上面的三行代码, 不难理解了吧, 如果还理解不了, 说明得补充一下编程知识啦.
4,74 和 83 行什么意思?
答: 这两行代码分别是求取 profile 和 sampling_frequency_index 值的, 理解了上面的第 2 和第 3 个问题, 这个问题也就不是问题啦.
四 结言
以上是我学习时的问题, 由于我在视频方面是纯新手, 所以我的问题应该大部分人都会有, 上面四个问题理解了后, 整体代码对你就没有秘密而言了. 我不希望别人也像我一样花几个小时搞明白, 太浪费时间了.
参考资料: AAC 的 ADTS 头文件信息介绍
来源: https://www.cnblogs.com/daner1257/p/10709233.html