如何拍摄延时建筑工地:MP3详解-MP3主程序代码详析
来源:百度文库 编辑:偶看新闻 时间:2024/04/29 07:27:48
1:比特流分解代码分析
这部分的代码分析是最难的,比重也是最大的。(呵呵,看之前不妨做好心理准备)这一部分包括查找帧同步,头信息提取,边信息提取,主信息提取,比例因子提取等内容。
l 所谓帧同步:mp3是以帧为数据组织形式的。每个帧包含有1152个声音采样数据和一些解码需要用的系数数据。但是帧和帧之间并不一定是紧密排列的。之间可能存在空隙,抑或是无用的信息。MPEG标准规定每个帧以1111 1111 111B作为标志开始,所以解码过程中首先要找到这个标志位,以此为起点依次取出需要的对应的系数。实现该功能的函数名为seek_sync( )。
l 所谓头信息提取:以同步头1111 1111 111B为起点,接下来的21位属于头信息。比如,同步头接下来的2位比特是MPEG Audio版本号信息,根据这个信息我们可以清楚的知道这个音乐文件采用的是什么压缩方式。
l 所谓边信息的提取:就是从文件中取出后面解码需要的大部分参数
l 所谓主信息提取:在1帧数据里面,除了头信息,边信息外的信息称为audio data(主信息),为了方便后面的解码,程序将audio data数据整个提取出来存放在全局缓冲区buf中
l 所谓比例因子提取:根据之前已经得到的一些系数,在audio data中提取出相应的比特就是我们所需要的比例因子。
代码分析:
#include
#include
#include "common.h"
#include "decode.h"
void main(int argc, char**argv)
{
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//比特流分解前的准备工作开始
FILE *musicout; 变量定义
Bit_stream_struc bs; 变量定义
frame_params fr_ps; 变量定义
III_side_info_t III_side_info; 变量定义
III_scalefac_t III_scalefac; 变量定义
unsigned int old_crc; 变量定义
layer info; 变量定义
int sync, clip; 变量定义
int done = FALSE; 变量定义
unsigned long frameNum=0; 变量定义
unsigned long bitsPerSlot; 变量定义
unsigned long sample_frames; 变量定义
typedef short PCM[2][SSLIMIT][SBLIMIT]; 变量定义
说明:声明PCM为短型的3维数组名,以后就可以用PCM来定义变量这3个维的含义分别是PCM[2][SSLIMIT][SBLIMIT]中的2表示每个颗粒 SSLIMIT是个常量32,表示每个子带。SBLIMIT是常量18表示每个子带里的每个数据。比如PCM[0][0][0]将存放的是第1个颗粒中的第1个子带中的第1个数据。
PCM *pcm_sample; 变量定义
说明:定义pcm_sample指向一个3维数组的首地址
pcm_sample = (PCM *) mem_alloc((long) sizeof(PCM), "PCM Samp");
说明:为这个3维数组分配相应的内存空间。该空间命名为PCM Samp
fr_ps.header = &info;
说明:让管理帧参数的结构体fr_ps中的指针header与info指向相同具体作用后面说
if ((musicout = fopen(argv[2], "w+b")) == NULL) {
说明:这个程序在执行前,用户要给两个参数,一个是要解码的mp3文件名对应argv[1],一个是保存解码数据的文件名对应argv[2]。这句话的含义是以“二进制写入”的方式建立文件名为用户提供的argv[2]的文件,如果建立失败,musicout将返回null,一旦返回null就要执行下面的提示出错语句。
printf ("Could not create \"%s\".\n", argv[2]);
exit(1);
}
//比特流分解前的准备工作结束
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//比特流分解开始(包括*头信息解码,*边信息解码,*主数据读取,*比例因子解码)
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
open_bit_stream_r(&bs, argv[1], BUFFER_SIZE);
说明:这个函数的作用是根据用户提供的参数argv[1]找到要解码的MP3文件,用二进制的方式打开它,并用帧结构体bs(bitstream的缩写)来“映像”这个二进制文件(所谓映像主要是将结构体的某些指针指向这个二进制文件以便于后面的操作)。BUFFER_SIZE是个常量4096,提供给函数让函数建立4096个字节的缓冲区来存放语音数据。
sample_frames = 0;
while(!end_bs(&bs)) {
说明:end_bs(&bs)这个函数比较简单,作用在于返回结构体bs中的参数值bs->eobs帧结束标志,意思是如果帧还没有操作完毕那么要始终进行下面的操作,直到帧所有的比特信息都被处理过
sync = seek_sync(&bs, SYNC_WORD, SYNC_WORD_LENGTH);
从结构体bs指向的文件中查找同步头SYNC_WORD ( 即是1111 1111 111B )
如果找到同步头返回1给sync否则返回0
if (!sync) {
done = TRUE;
printf("\nFrame cannot be located\n");
如果没找到同步头提示出错并跳出程序循环
break;
}
如果找到了同步头则开始解码帧头
decode_info(&bs, &fr_ps);
函数decode_info的作用是:以同步头为起点,根据头信息的组织形式,依次取出信息,并存放到结构体中统一管理。比如说,根据协议,我们知道紧跟着同步头后面的1位比特是版本信息,那么就用函数getbits(&bs,1)取出一位比特存入fr_ps->header)->version中,在下来的2位是协议层数信息,
所以用getbits(&bs,2)取出2位比特存入fr_ps->header)->lay。以下的以此类推直到根据头信息的组织形式取出所有的头信息(头信息有32位,其中11位是同步头信息)。在头信息中,对解码有用的是比特率信息,采样率信息,声道模式等,至于版本号,版权保护信息等呵呵只是个样子摆设。
hdr_to_frps(&fr_ps);
函数hdr_to_frps的作用是:对帧结构体fr_ps的其他参数根据取出头信息进行初始化,方便后面的解码,这些参数使用到后再说明,对该函数不具体解释。只将代码列在后面。
frameNum++;(计数解码到第几帧,初始化时为0)
if (info.error_protection)
buffer_CRC(&bs, &old_crc);
这两句话的意思是,如果帧有纠错机制,那么要从文件中再提出16个比特作为纠错校验位。函数buffer_CRC很简单也不作介绍了,代码罗列在后。
switch (info.lay) {
case 3:
{
检查从头信息取出的参数info.lay,当它是3的时候,才说明该文件是mp3文件,才有必要进行下一步的操作,不是的话直接退出
int nSlots, main_data_end, flush_main;
int bytes_to_discard, gr, ch, ss, sb;
static int frame_start = 0;
bitsPerSlot = 8;
III_get_side_info(&bs, &III_side_info, &fr_ps);
函数III_get_side_info的作用是根据帧结构体fr_ps内的相关信息,从文件结构体bs指向的文件中提取出边信息,存放到边信息结构体III_side_info中统一管理。(这个函数的理解有些困难,主要难在对mp3数据组织形式的认识)。边信息里面包含了语音主信息开始位置信息,比例因子解码系数信息等等。说明,边信息里面包含着非常非常多的参数信息,都是直接与解码相联系的。到目前为止只是明白这些参数的大概含义,以及他们在解码中是如何使用的。他们的具体含义分析起来要涉及到编码过程,很难进行,目前这一块的工作我还没有进行下去。
nSlots = main_data_slots(fr_ps);
说明:在在协议中,帧数据扣掉头信息和边信息后剩下的信息称为 (Audio Data)。在解码过程中需要先将Audio Data另存到一个缓冲区中,以方便解码。该函数的作用是通过公式计算出1帧数据的字节长度,再减去头信息和边信息占用的字节数。(剩下的字节数就是Audio Data所占用的字节数。)
for (; nSlots > 0; nSlots--) /* read main data. */
hputbuf((unsigned int) getbits(&bs,8), 8);
在这里函数hputbuf的作用是从文件中提取出字节nSlots次并将每次提取的字节信息存放在全局缓冲区buf中,注意和bs->buf区别,两个表示的是不同的缓冲区。这样一来,Audio Data数据就存放在全局缓冲区buf中了。这个函数不做做具体解释,程序罗列在后
main_data_end = hsstell() / 8; /*of privious frame*/
程序需要做的是:因为之前已经开辟了一个全局缓冲区buf,并建立了一个新的结构提来管理这个大小是4096字节的buf。程序希望的是将多帧的Audio Data通过循环都提取出来存放在这个缓冲区里统一处理。函数hsstell是返回全局缓冲区buf的比特位置的,每向buf放入一个字节,结构体中相应的比特位置计数参量就会加8。main_data_end = hsstell() / 8标识的就是buf已经放入了多少个字节,实际上就是指明了上一帧Audio Data在buf中的结束位置。
if ( flush_main=(hsstell() % 8) ) {
hgetbits((int)(bitsPerSlot - flush_main));
main_data_end ++;
}
为了确保buf中的数据是以字节位组织形式的,有上面的操作(有待更具体的分析,有点想不通,因为它取数据的时候就是以字节为单位存入buf的,又何必担心buf不是以字节为组织形式,而再加上这3句话来确保它是以字节为单位组织的数据)
。。。。。。接下来的10句话难了我1个多月呢。。。。。。
1 bytes_to_discard = frame_start - main_data_end - III_side_info.main_data_begin ;
先要知道frame_start初始化为0,还要知道,Audio Data的有效数据不一定是直接跟在边信息以后,它的开始位置是由III_side_info.main_data_begin指定的,在边信息和有效主信息之间会存在垃圾信息,这是我们需要把它扣除的,这10话的作用也在于此
2 if( main_data_end > 4096 ) { frame_start -= 4096;
3 rewindNbytes( 4096 );
4 }
5 frame_start += main_data_slots(fr_ps);
6 if (bytes_to_discard < 0) {
7 frameNum - 1;
8 break;
9 }
10 for (; bytes_to_discard > 0; bytes_to_discard--) hgetbits(8);
一开始frame_start=0,经过第一句程序后bytes_to_discard肯定为负值,main_data_end的取值也一定还没有到4096,所以2,3,4句程序是不执行的,经过5句后frame_start由0变成了标识上一帧audio data长度的值,实际上也就是标识了下一帧的开始位置,执行6,7,8句直接跳出了switch,但依然在while(!end_bs(&bs))死循环体内,还会不断地执行到这10句话。
从第二次开始,执行到第一句后bytes_to_discard反映的就是确确实实的两帧主数据之间的垃圾信息了。可以参看附图,这里有些问题:按我的理解maid_data_begin一定是反映主数据相对帧结束位置的长度,而不是相对帧起点的长度。(这里理解上有些不确定,总体上这10句话就是为了去掉垃圾字节所作的工作)
clip = 0;
for (gr=0;gr<2;gr++) {
double lr[2][SBLIMIT][SSLIMIT],ro[2][SBLIMIT][SSLIMIT];
for (ch=0; ch
long int is[SBLIMIT][SSLIMIT];
int part2_start;
part2_start = hsstell();
变量part2_start是对全局buf比特位置的一个标识,往后的解码对象已经不是结构体bs指向的bs->buf,而是全局buf,对全局buf的第一个操作是上述的取出垃圾信息丢弃,第二个操作是将要进行的比例因子解码。比例因子的解码是从part2_start位置开始的,此前的信息是垃圾信息。
注意函数sstell的操作对象是bs->buf, hsstell的操作对象是buf。同样的getbits的操作对象是bs->buf,而hgetbits的操作对象是buf。这些函数的功能相同,只是操作对象不一样,所以不重复介绍相应的一些函数
//获取比例因子开始
III_get_scale_factors(&III_scalefac,&III_side_info, gr, ch, &fr_ps);
函数III_get_scale_factors的作用自然是获取比例因子。比例因子将用于后面解码的相关计算
//比特流分解结束(包括*头信息解码,*边信息解码,*主数据读取,*比例因子解码)
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
//霍夫曼解码开始
////////////////////////////////////////////////////
////////////////////////////////////////////////////
。。。。。。
2:霍夫曼解码代码分析
霍夫曼编码是基于概率统计的无损压缩,是一种变长变码。这一块的了解还不是很彻底,在接下来的工作里,首先是对这个解码程序的调试,而最先要进行的调试是霍夫曼编解码这模块。这个mp3程序是turbor C 下运行的程序,比特流分解部分已经调试无误通过。紧接着要做的工作是用C语言编写一个压缩,解压缩的简单程序。通过这个工作进一步了解霍夫曼编码的运作。最终彻底了解认识mp3上霍夫曼解码工作。霍夫曼解码是mp3程序工作运算量的1/3以上,这一块应该有进一步深入的必要性。
代码分析:
//Huffman解码开始
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
III_hufman_decode(is, &III_side_info, ch, gr, part2_start, &fr_ps);
该函数的作用是通过之前得到的一系列相关的参数,将每个颗粒的576个数据,霍夫曼解码出数据,并存放在32行18列的二维变量is中。具体的解码方法看这个函数的具体解释。
//Huffman解码结束
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
3:程序其他解码代码分析
这其中包括//反量化采样//立体声处理//重排列//抗锯齿处理//imdct//多相频率倒置//多相合成//子带合成//PCM输出等内容。除了PCM输出以外,其他部分的处理仅仅是规则和公式的应用,程序解读难度不大,已经没有必要一句一句的解释了。至于为什么用这些公式和规则,要解释起来,呵呵我可没有那个本事。在这里我值简单的介绍数据的流向,和大概处理。处理方法原理,涉及到编码,和信号处理的很多东西,暂时没有办法说出来。
反量化采样:数据流向是经霍夫曼解码出的数据is[2][32][18],反量化解码到ro[2][32][18]数据的大概处理就是在is中调用数据,通过若干复杂的公式和规则计算出新的数据存到ro[2][32][18]。[2]表示2个颗粒,[32]表示32个子带,[18]表示每个子带的18个系数
立体声处理:数据流向是经反量化后的数据ro[2][32][18],经过立体声处理后得到的数据存入变量lr[2][32][18]中。数据大概的处理是:这里的规则很多,也很麻烦。举简单地说,如果帧采用的立体声模式是ms模式,那么它的立体声处理方法相对简单,只用两个公式lr[0][sb][ss] = (xr[0][sb][ss]+xr[1][sb][ss])/1.41421356;
lr[1][sb][ss] = (xr[0][sb][ss]-xr[1][sb][ss])/1.41421356;
就可以求出处理后的数据,并把它存入lr数组就可以了。但如果是i_stereo立体声模式那就苦拉。。。。。。,这种模式用到很多规则和公式,理不清,目前。
重排列,抗锯齿处理,imdct,多相频率倒置,多相合成,子带合成也都只是公式和规则的应用,大概有这样的了解就可以了。如果要做程序移植,参考着来一定可以。子带合成后的数据就是我们所需要的pcm数据存放在pcm_sample指向的空间里面
PCM输出函数完成的功能也很简单,就是以用户提供的第二个参数为文件名建立文件,并将pcm数据逐个保存到这个文件里面。从而完成了mp3文件的解码。它的代码如下:
代码分析:
//Huffman解码结束
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//反量化采样
III_dequantize_sample(is, ro[ch], &III_scalefac, &(III_side_info.ch[ch].gr[gr]), ch, &fr_ps);
}
//立体声处理
III_stereo(ro, lr, &III_scalefac, &(III_side_info.ch[0].gr[gr]), &fr_ps);
for (ch=0; ch
double re[SBLIMIT][SSLIMIT];
double hybridIn[SBLIMIT][SSLIMIT];/* Hybrid filter input */
double hybridOut[SBLIMIT][SSLIMIT];/* Hybrid filter out */
double polyPhaseIn[SBLIMIT]; /* PolyPhase Input. */
III_reorder(lr[ch], re, &(III_side_info.ch[ch].gr[gr]), &fr_ps);
//抗锯齿处理
III_antialias(re, hybridIn, /* Antialias butterflies. */
&(III_side_info.ch[ch].gr[gr]), &fr_ps);
//IMDCT
for (sb=0; sb
III_hybrid(hybridIn[sb], hybridOut[sb], sb, ch, &(III_side_info.ch[ch].gr[gr]), &fr_ps);
}
for (ss=0;ss<18;ss++) //多相频率倒置
for (sb=0; sb
if ((ss%2) && (sb%2))
hybridOut[sb][ss] = -hybridOut[sb][ss];
for (ss=0;ss<18;ss++) { //多相合成
for (sb=0; sb
polyPhaseIn[sb] = hybridOut[sb][ss];
//子带合成
clip += SubBandSynthesis(polyPhaseIn, ch, &((*pcm_sample)[ch][ss][0]));
}
}
//PCM输出
/* Output PCM sample points for one granule(颗粒). */
out_fifo(*pcm_sample, 18, &fr_ps, done, musicout, &sample_frames);
}
if(clip > 0)
printf("\n%d samples clipped.\n", clip);
}
break;
default:
printf("\nOnly layer III supported!\n");
exit(1);
break;
}
}
close_bit_stream_r(&bs);
fclose(musicout);
printf("\nDecoding done.\n");
return;
}