首页
登录 | 注册

【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

一 前言

  最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大。那写这篇随笔的理由是在看他写的AAC音频码流解析文章时(传送门)遇到一些问题,因为雷神毕竟等级与初学者不同,一些在他看来很基础的东西菜鸟(比如我)一看就懵逼了,看得是云里雾里,而且我在评论中也看到有人提问相同的问题,但是并没有人给出解答,我自己花了将近三个小时仔细看了AAC码流的介绍才明白,这里也献丑讲解一下。

二 AAC码流数据存储格式

  这里先把雷神的话看一遍

【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

这当然是没问题的,不过雷神说的有点过于简单了,ADTS frame内部的结构的什么样子的?数据存储在ADTS frame的哪一部分?这些并没有说清楚,所以下面看代码时就会搞不懂。我通过AAC Audio ES Viewer打开了一个AAC码流文件,这个软件能将一个AAC码流文件解析成一个个的ADTS frame,咱们来看一下(图片较大,如果看不清可以在新窗口打开查看)

【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

我这里选择了第一个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部分(这个很关键)

好了,上面就是比较重要的参数介绍,知道这些,有助于理解雷神的代码思路。

三 代码解析

先把雷神的代码抄过来

  1 int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data ,int* data_size){
  2     int size = 0;
  3 
  4     if(!buffer || !data || !data_size ){
  5         return -1;
  6     }
  7 
  8     while(1){
  9         if(buf_size  < 7 ){
 10             return -1;
 11         }
 12         //Sync words
 13         if((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){
 14             size |= ((buffer[3] & 0x03) <<11);     //high 2 bit
 15             size |= buffer[4]<<3;                //middle 8 bit
 16             size |= ((buffer[5] & 0xe0)>>5);        //low 3bit
 17             break;
 18         }
 19         --buf_size;
 20         ++buffer;
 21     }
 22 
 23     if(buf_size < size){
 24         return 1;
 25     }
 26 
 27     memcpy(data, buffer, size);
 28     *data_size = size;
 29 
 30     return 0;
 31 }
 32 
 33 int simplest_aac_parser(char *url)
 34 {
 35     int data_size = 0;
 36     int size = 0;
 37     int cnt=0;
 38     int offset=0;
 39 
 40     //FILE *myout=fopen("output_log.txt","wb+");
 41     FILE *myout=stdout;
 42 
 43     unsigned char *aacframe=(unsigned char *)malloc(1024*5);
 44     unsigned char *aacbuffer=(unsigned char *)malloc(1024*1024);
 45 
 46     FILE *ifile = fopen(url, "rb");
 47     if(!ifile){
 48         printf("Open file error");
 49         return -1;
 50     }
 51 
 52     printf("-----+- ADTS Frame Table -+------+\n");
 53     printf(" NUM | Profile | Frequency| Size |\n");
 54     printf("-----+---------+----------+------+\n");
 55 
 56     while(!feof(ifile)){
 57         data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);
 58         unsigned char* input_data = aacbuffer;
 59 
 60         while(1)
 61         {
 62             int ret=getADTSframe(input_data, data_size, aacframe, &size);
 63             if(ret==-1){
 64                 break;
 65             }else if(ret==1){
 66                 memcpy(aacbuffer,input_data,data_size);
 67                 offset=data_size;
 68                 break;
 69             }
 70 
 71             char profile_str[10]={0};
 72             char frequence_str[10]={0};
 73 
 74             unsigned char profile=aacframe[2]&0xC0;
 75             profile=profile>>6;
 76             switch(profile){
 77             case 0: sprintf(profile_str,"Main");break;
 78             case 1: sprintf(profile_str,"LC");break;
 79             case 2: sprintf(profile_str,"SSR");break;
 80             default:sprintf(profile_str,"unknown");break;
 81             }
 82 
 83             unsigned char sampling_frequency_index=aacframe[2]&0x3C;
 84             sampling_frequency_index=sampling_frequency_index>>2;
 85             switch(sampling_frequency_index){
 86             case 0: sprintf(frequence_str,"96000Hz");break;
 87             case 1: sprintf(frequence_str,"88200Hz");break;
 88             case 2: sprintf(frequence_str,"64000Hz");break;
 89             case 3: sprintf(frequence_str,"48000Hz");break;
 90             case 4: sprintf(frequence_str,"44100Hz");break;
 91             case 5: sprintf(frequence_str,"32000Hz");break;
 92             case 6: sprintf(frequence_str,"24000Hz");break;
 93             case 7: sprintf(frequence_str,"22050Hz");break;
 94             case 8: sprintf(frequence_str,"16000Hz");break;
 95             case 9: sprintf(frequence_str,"12000Hz");break;
 96             case 10: sprintf(frequence_str,"11025Hz");break;
 97             case 11: sprintf(frequence_str,"8000Hz");break;
 98             default:sprintf(frequence_str,"unknown");break;
 99             }
100 
101 
102             fprintf(myout,"%5d| %8s|  %8s| %5d|\n",cnt,profile_str ,frequence_str,size);
103             data_size -= size;
104             input_data += size;
105             cnt++;
106         }   
107 
108     }
109     fclose(ifile);
110     free(aacbuffer);
111     free(aacframe);
112 
113     return 0;
114 }

然后说一下当初我看的时候迷惑的地方。

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存储在哪一位中,从哪里开始?在哪里结束?头大!!别急,我画了一张图(图片比较大,如果看不情,可以在新窗口中查看,或点这里下载)【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

从这一张图中可以很清晰的看到,frame_length存储在第4个字节的后两位,第5个字节,第6个字节的前三位。好了,知道这些再看上面的三行代码,不难理解了吧,如果还理解不了,说明得补充一下编程知识啦。

4、74和83行什么意思?

答:这两行代码分别是求取profile和sampling_frequency_index值的,理解了上面的第2和第3个问题,这个问题也就不是问题啦。

四 结言

以上是我学习时的问题,由于我在视频方面是纯新手,所以我的问题应该大部分人都会有,上面四个问题理解了后,整体代码对你就没有秘密而言了。我不希望别人也像我一样花几个小时搞明白,太浪费时间了。

 参考资料:AAC的ADTS头文件信息介绍

 


相关文章

  • SpringBoot集成Lombok,应用+源码解析,让代码优雅起来
    一.Lombok简介 (1)Lombok官网(https://projectlombok.org/)对lombok的介绍 (2)GitHub项目地址:https://github.com/rzwitserloot/lombok 虽然是生硬的 ...
  • 《k8s 源码分析》- Custom Controller 之 Informer
    Custom Controller 之 Informer 概述 架构概览 reflector - List & Watch API Server Reflector 对象 ListAndWatch watchHandler - ad ...
  • Android6.0 源码修改之 Contacts应用
    一.Contacts应用的主界面和联系人详情界面增加顶部菜单添加退出按钮 通过Hierarchy View 工具可以发现 主界面对应的类为 PeopleActivity 联系人详情界面对应的类为 QuickContactActivity 左 ...
  • Dom4J配合XPath解析schema约束的xml配置文件问题
    如果一个xml文件没有引入约束,或者引入的是DTD约束时,那么使用dom4j和xpath是可以正常解析的,不引入约束的情况本文不再展示. 引入DTD约束的情况 mybook.dtd: <?xml version="1.0&q ...
  • 前言:今天在做一个小项目时,客户要求的xml,跟现在有系统要求的不一样,所以要自己重新写函数支持返回,进行简单总结,希望对大家有所帮助. 首先,使用xml函数需要链上动态库libxml2,需要在电脑上安装libxml的开发包,安装方法如下: ...
  • 如何零基础开始自学Python编程
    转载——原作者:赛门喵 链接:https://www.zhihu.com/question/29138020/answer/141170242 0. 明确目标 我是真正零基础开始学Python的,从一开始的一窍不通,到3个月后成功搭建了一个 ...

2020 cecdns.com webmaster#cecdns.com
12 q. 0.078 s.
京ICP备10005923号