一 前言

  最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大。那写这篇随笔的理由是在看他写的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 = ; if(!buffer || !data || !data_size ){
return -;
} while(){
if(buf_size < ){
return -;
}
//Sync words
if((buffer[] == 0xff) && ((buffer[] & 0xf0) == 0xf0) ){
size |= ((buffer[] & 0x03) <<); //high 2 bit
size |= buffer[]<<; //middle 8 bit
size |= ((buffer[] & 0xe0)>>); //low 3bit
break;
}
--buf_size;
++buffer;
} if(buf_size < size){
return ;
} memcpy(data, buffer, size);
*data_size = size; return ;
} int simplest_aac_parser(char *url)
{
int data_size = ;
int size = ;
int cnt=;
int offset=; //FILE *myout=fopen("output_log.txt","wb+");
FILE *myout=stdout; unsigned char *aacframe=(unsigned char *)malloc(*);
unsigned char *aacbuffer=(unsigned char *)malloc(*); FILE *ifile = fopen(url, "rb");
if(!ifile){
printf("Open file error");
return -;
} printf("-----+- ADTS Frame Table -+------+\n");
printf(" NUM | Profile | Frequency| Size |\n");
printf("-----+---------+----------+------+\n"); while(!feof(ifile)){
data_size = fread(aacbuffer+offset, , *-offset, ifile);
unsigned char* input_data = aacbuffer; while()
{
int ret=getADTSframe(input_data, data_size, aacframe, &size);
if(ret==-){
break;
}else if(ret==){
memcpy(aacbuffer,input_data,data_size);
offset=data_size;
break;
} char profile_str[]={};
char frequence_str[]={}; unsigned char profile=aacframe[]&0xC0;
profile=profile>>;
switch(profile){
case : sprintf(profile_str,"Main");break;
case : sprintf(profile_str,"LC");break;
case : sprintf(profile_str,"SSR");break;
default:sprintf(profile_str,"unknown");break;
} unsigned char sampling_frequency_index=aacframe[]&0x3C;
sampling_frequency_index=sampling_frequency_index>>;
switch(sampling_frequency_index){
case : sprintf(frequence_str,"96000Hz");break;
case : sprintf(frequence_str,"88200Hz");break;
case : sprintf(frequence_str,"64000Hz");break;
case : sprintf(frequence_str,"48000Hz");break;
case : sprintf(frequence_str,"44100Hz");break;
case : sprintf(frequence_str,"32000Hz");break;
case : sprintf(frequence_str,"24000Hz");break;
case : sprintf(frequence_str,"22050Hz");break;
case : sprintf(frequence_str,"16000Hz");break;
case : sprintf(frequence_str,"12000Hz");break;
case : sprintf(frequence_str,"11025Hz");break;
case : sprintf(frequence_str,"8000Hz");break;
default:sprintf(frequence_str,"unknown");break;
} fprintf(myout,"%5d| %8s| %8s| %5d|\n",cnt,profile_str ,frequence_str,size);
data_size -= size;
input_data += size;
cnt++;
} }
fclose(ifile);
free(aacbuffer);
free(aacframe); return ;
}

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

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[] & 0x03) <<);     //high 2 bit
size |= buffer[]<<; //middle 8 bit
size |= ((buffer[] & 0xe0)>>); //low 3bit

其实雷神注释中已经说了,但是不了解数据结构的依然会懵逼。第二部分说了,ADTS header中有ADTS frame的大小,但是根据上面同步字咱们可以看出来,这些数据并不是以字节为单位连续排列的,而是按位排列的,这就有点纠结了不是?那size存储在哪一位中,从哪里开始?在哪里结束?头大!!别急,我画了一张图(图片比较大,如果看不情,可以在新窗口中查看,或点这里下载

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

4、74和83行什么意思?

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

四 结言

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

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

【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我的更多相关文章

  1. 关于c语言的位运算&,|,^(看懂汉字的都能看懂)

    其中|,&可以当作逻辑运算符,当|,&当成逻辑运算符时,与||,&&的用法基本相似,&&,||运算时会当前面的表达式能够决定整个表达式,则不进行对后面的 ...

  2. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  3. dubbo面试题,会这些说明你真正看懂了dubbo源码

    整理了一些dubbo可能会被面试的面试题,感觉非常不错.如果你基本能回答说明你看懂了dubbo源码,对dubbo了解的足够全面.你可以尝试看能不能回答下.我们一起看下有哪些问题吧? 1.dubbo中& ...

  4. 从源码带你看懂functools的partial方法

    1.what? partial是什么, partial也叫偏函数.源码的描述是: 部分应用给定参数和关键字的新函数. New function with partial application of ...

  5. 把nc v6的源码看懂

    看懂nc v6的源码! 碧桂园全部的正式环境的补丁都在我手里. 2015-11-18 2:33 谢谢两位一起努力的兄弟 谢谢超哥,谢谢祈冰哥,谢谢连老师,陈明大哥,谢谢龙哥,珍玉,谢谢廖生哥,谢谢林春 ...

  6. (原创)超详细一步一步在eclipse中配置Struts2环境,无基础也能看懂

    (原创)超详细一步一步在eclipse中配置Struts2环境,无基础也能看懂 1. 在官网https://struts.apache.org下载Struts2,建议下载2.3系列版本.从图中可以看出 ...

  7. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  8. 小白都能看懂的 Spring 源码揭秘之Spring MVC

    目录 前言 Spring MVC 请求流程 Spring MVC 两大阶段 初始化 HttpServletBean#init() FrameworkServlet#initServletBean Fr ...

  9. 透过源码看懂Flink核心框架的执行流程

    前言 Flink是大数据处理领域最近很火的一个开源的分布式.高性能的流式处理框架,其对数据的处理可以达到毫秒级别.本文以一个来自官网的WordCount例子为引,全面阐述flink的核心架构及执行流程 ...

随机推荐

  1. mysql 数据库的备份和还原

    1. 逻辑备份 (和存储引擎无关) mysqldump -uroot -p schoolDB TSubject > /mysqlbackup/schoolDB.TSubject.sql  (备份 ...

  2. VS 2017显示“高级保存选项”命令操作方法

    Visual Studio提供“高级保存选项”功能,它能指定特定代码文件的编码规范和行尾所使用的换行符.在Visual Studio 2017中,该命令默认是没有显示在“文件”菜单中的.用户需要手工设 ...

  3. js跳转到页面指定元素

    var scrollDistance = $("#设置了的overflow元素").scrollTop() + $('#' + 当前屏幕元素).offset().top; $(&q ...

  4. MongoDB学习记录(三) - MongoDB的"增查改删"操作之"查"

    查找使用的方法: db.collection.find() 查找所有文档 db.collection.find({})或者db.collection.find({}) 指定键值对 db.collect ...

  5. hw1

    如上面两个小程序中,分析下列问题: 1.Identify the fault. 2. If possible, identify a test case that does not execute t ...

  6. 《Linux就该这么学》第十一天课程

     防火墙常用的一些命令参数 原创地址:https://www.linuxprobe.com/chapter-08.html firewalld中常用的区域名称及策略规则 区域 默认规则策略 trust ...

  7. 更适用于JavaScript的设计模式:面向委托的设计,了解一下?(下)

    先来看一下传统的面向类式的写法: function Foo(name) { this.name = name; } Foo.prototype.sayName = function() { conso ...

  8. php final

    final:如果父类中的方法被声明为final,则子类无法覆盖该方法.如果一个类被声明为final,则不能被继承. 方法示例: <?phpclass BaseClass {   public f ...

  9. k8s storageClass对接nfs

    前提:已存在一个nfs服务 192.168.137.11:/home/nfs_data 下面以rbac方式对接nfs 1.创建/root/k8s-nfs-rbac/serviceaccount.yam ...

  10. 第49章:MongoDB-数据导入导出

    ①导出工具mongoexport Mongodb中的mongoexport工具可以把一个collection导出成JSON格式或CSV格式的文件.可以通过参数指定导出的数据项,也可以根据指定的条件导出 ...