MP3详解-MP3代码的总体框架
Mp3解码过程了解
Mp3的解码总体上可分为9个过程:比特流分解,霍夫曼解码,逆量化处理,立体声处理,频谱重排列,抗锯齿处理,IMDCT变换,子
带合成,pcm输出。
为了解上述9个过程的由来,简要描述mp3的压缩流程。声音是一个模拟信号,对声音进行采样,量化,编码将得到PCM数据。
PCM又称为脉冲编码调制数据,是电脑可以播放的最原始的数据,也是MP3压缩的源。为了达到更大的数据压缩率,MPEG标准采用子
带编码技术将PCM数据分成32个子带,每个子带都是独立编码的(参考《数字音频原理与应用》221页)。然后将数据变换到频域下
分析,MPEG采用的是改进的离散余弦变换,也可以使用傅利叶变换(参考《数字音频原理与应用》225)。再下来为了重建立体声进
行了频谱按特定规则的排列,随后立体声处理,处理后的数据按照协议定义进行量化。为了达到更大的压缩,再进行霍夫曼编码。最
后将一些系数与主信息融合形成mp3文件。
解码是编码的反过程大概如下:
所谓比特流分解是指将mp3文件以二进制方式打开,然后根据其压缩格式的定义,依次从这个mp3文件中取出头信息,边信息,
比例因子信息等。这些信息都是后面的解码过程中需要的。(这部分是代码理解中的难点)。
霍夫曼编码是一种无损压缩编码,属于熵编码。Mp3的解码可以通过公式实时进行数据的解码,但往往采用的是通过查表法实现
解码(节省了CPU时间资源)。(这部分是mp3解码工作量中最大的一部分,也是代码理解中的难点)。
逆量化处理只是几个公式的操作,代码理解中不难
立体声处理:这部分的处理也只是对几个公式的操作,代码理解不难,但原理上理解有些难度(**参考:了解下面的部分可以较
好地理解代码中的立体声处理函数JointStereo是一种立体声编码技巧,主要分为IntensityStereo(IS)和Mid/Side(M/S)stereo
两种。IS的是在比较低流量时使用,利用了人耳对于低频讯号指向性分辨能力的不足,将音讯资料中的低频分解出来合成单声道资
料,剩余的高频资料则合成另一个单声道资料,并另外纪录高频资料的位置资讯,来重建立体声的效果。例如钢琴独奏的录音就可以
利用这种方法在有限的资料流量中减少音场资讯却大幅增加音色资讯。Mid/Side(M/S)stereo在左右声道资料相似度大时常被用到,
纪录方式是将左右声道音讯合并(L+R)得到新的一轨,再将左右声道音讯相减(L-R)得到另外一轨,然后再将这两轨资料用上面
提到听觉心理学模型与滤波器处理。Mid/Side(M/S)stereo与IS一样的是利用部分相位(pha)资讯的损失来换得较高的音色纪
录资讯。一般的MP3是Mid/Sidestereo和IntensityStereo交替使用的)
频谱重排列,抗锯齿处理,IMDCT变换,子带合成:这4个过程都是对若干公式的操作代码易懂,至于为什么要用这些公式,估
计需要对MPEG编码有个了解才行。
PCM的输出是与c语言对文件的处理相关的。对文件的处理在比特流分解和霍夫曼解码中最先接触到。看到了上述的两个环节,
这一部分难度为0。
综上,对代码的理解难点集中在前两个环节,一是比特流分解,二是霍夫曼解码。比特流的分解难点在于c语言编程知识的应用。通
过这部分的学习可以进一步熟悉c语言的应用,和一些算法的精短描述(很简约但是有些不好懂)。霍夫曼解码部分的学习可以掌握
霍夫曼的查表解码方法,很有学习价值的„„-_-
2:MP3解码主程序段直观了解
以下罗列一份最简单的MP3解码的主程序段代码,目的是对MP3解码主程序极其大致的结构有个直观的认识。
#include
#include
#include"common.h"
#include"decode.h"
voidmain(intargc,char**argv)
{
FILE*musicout;
Bit_stream_strucbs;
frame_paramsfr_ps;
III_side_info_tIII_side_info;
III_scalefac_tIII_scalefac;
unsignedintold_crc;
layerinfo;
intsync,clip;
intdone=FALSE;
unsignedlongframeNum=0;
unsignedlongbitsPerSlot;
unsignedlongsample_frames;
typedefshortPCM[2][SSLIMIT][SBLIMIT];
PCM*pcm_sample;
pcm_sample=(PCM*)mem_alloc((long)sizeof(PCM),"PCMSamp");
if(argc==1){
printf("Uage:");
return;
}
fr_=&info;
if((musicout=fopen(argv[2],"w+b"))==NULL){
printf("Couldnotcreate"%s".n",argv[2]);
exit(1);
}
open_bit_stream_r(&bs,argv[1],BUFFER_SIZE);
sample_frames=0;
while(!end_bs(&bs)){
//尝试帧同步
sync=ek_sync(&bs,SYNC_WORD,SYNC_WORD_LENGTH);
if(!sync){
done=TRUE;
printf("nFramecannotbelocatedn");
out_fifo(*pcm_sample,3,&fr_ps,done,musicout,&sample_frames);
break;
}
//解码帧头
decode_info(&bs,&fr_ps);
//将fr_中的信息解读到fr_ps的相关域中
hdr_to_frps(&fr_ps);
//输出相关信息
if(frameNum==0)
WriteHdr(&fr_ps);
printf("r%05lu",frameNum++);
if(_protection)
buffer_CRC(&bs,&old_crc);
switch(){
ca3:
{
intnSlots,main_data_end,flush_main;
intbytes_to_discard,gr,ch,ss,sb;
staticintframe_start=0;
bitsPerSlot=8;
//取Side信息
III_get_side_info(&bs,&III_side_info,&fr_ps);
nSlots=main_data_slots(fr_ps);
//读主数据(AudioData)
for(;nSlots>0;nSlots--)/*readmaindata.*/
hputbuf((unsignedint)getbits(&bs,8),8);
main_data_end=hsstell()/8;/*ofpriviousframe*/
if(flush_main=(hsstell()%bitsPerSlot)){
hgetbits((int)(bitsPerSlot-flush_main));
main_data_end++;
}
bytes_to_discard=frame_start-main_data_end-III_side__data_begin;
if(main_data_end>4096){frame_start-=4096;
rewindNbytes(4096);
}
frame_start+=main_data_slots(fr_ps);
if(bytes_to_discard<0){
printf("Notenoughmaindatatodecodeframe%iscarded.n",
frameNum-1);break;
}
for(;bytes_to_discard>0;bytes_to_discard--)hgetbits(8);
clip=0;
for(gr=0;gr<2;gr++){
doublelr[2][SBLIMIT][SSLIMIT],ro[2][SBLIMIT][SSLIMIT];
//主解码
for(ch=0;ch
longintis[SBLIMIT][SSLIMIT];/*保存量化数据*/
intpart2_start;
part2_start=hsstell();
//获取比例因子
III_get_scale_factors(&III_scalefac,&III_side_info,gr,ch,&fr_ps);
//Huffman解码
III_hufman_decode(is,&III_side_info,ch,gr,part2_start,&fr_ps);
//反量化采样
III_dequantize_sample(is,ro[ch],&III_scalefac,&(III_side_[ch].gr[gr]),ch,&fr_ps);
}
//立体声处理
III_stereo(ro,lr,&III_scalefac,&(III_side_[0].gr[gr]),&fr_ps);
for(ch=0;ch
doublere[SBLIMIT][SSLIMIT];
doublehybridIn[SBLIMIT][SSLIMIT];/*Hybridfilterinput*/
doublehybridOut[SBLIMIT][SSLIMIT];/*Hybridfilterout*/
doublepolyPhaIn[SBLIMIT];/*PolyPhaInput.*/
III_reorder(lr[ch],re,&(III_side_[ch].gr[gr]),&fr_ps);
//抗锯齿处理
III_antialias(re,hybridIn,/*Antialiasbutterflies.*/
&(III_side_[ch].gr[gr]),&fr_ps);
//IMDCT
for(sb=0;sb
III_hybrid(hybridIn[sb],hybridOut[sb],sb,ch,&(III_side_[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
polyPhaIn[sb]=hybridOut[sb][ss];
//子带合成
clip+=SubBandSynthesis(polyPhaIn,ch,&((*pcm_sample)[ch][ss][0]));
}
}
//PCM输出
/*OutputPCMsamplepointsforonegranule(颗粒).*/
out_fifo(*pcm_sample,18,&fr_ps,done,musicout,&sample_frames);
}
if(clip>0)
printf("n%dsamplesclipped.n",clip);
}
break;
default:
printf("nOnlylayerIIIsupported!n");
exit(1);
break;
}
}
clo_bit_stream_r(&bs);
fclo(musicout);
printf("nDecodingdone.n");
return;
}
1:比特流分解代码分析
这部分的代码分析是最难的,比重也是最大的。(呵呵,看之前不妨做好心理准备)这一部分包括查找帧同步,头信息提取,边信息提取,主信
息提取,比例因子提取等内容。
所谓帧同步:mp3是以帧为数据组织形式的。每个帧包含有1152个声音采样数据和一些解码需要用的系数数据。但是帧和帧之间并不一定是
紧密排列的。之间可能存在空隙,抑或是无用的信息。MPEG标准规定每个帧以B作为标志开始,所以解码过程中首先要找到这个
标志位,以此为起点依次取出需要的对应的系数。实现该功能的函数名为ek_sync()。
所谓头信息提取:以同步头B为起点,接下来的21位属于头信息。比如,同步头接下来的2位比特是MPEGAudio版本号信
息,根据这个信息我们可以清楚的知道这个音乐文件采用的是什么压缩方式。
所谓边信息的提取:就是从文件中取出后面解码需要的大部分参数
所谓主信息提取:在1帧数据里面,除了头信息,边信息外的信息称为audiodata(主信息),为了方便后面的解码,程序将audiodata数据
整个提取出来存放在全局缓冲区buf中
所谓比例因子提取:根据之前已经得到的一些系数,在audiodata中提取出相应的比特就是我们所需要的比例因子。
代码分析:
#include
#include
#include"common.h"
#include"decode.h"
voidmain(intargc,char**argv)
{
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//比特流分解前的准备工作开始
FILE*musicout;变量定义
Bit_stream_strucbs;变量定义
frame_paramsfr_ps;变量定义
III_side_info_tIII_side_info;变量定义
III_scalefac_tIII_scalefac;变量定义
unsignedintold_crc;变量定义
layerinfo;变量定义
intsync,clip;变量定义
intdone=FALSE;变量定义
unsignedlongframeNum=0;变量定义
unsignedlongbitsPerSlot;变量定义
unsignedlongsample_frames;变量定义
typedefshortPCM[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),"PCMSamp");
说明:为这个3维数组分配相应的内存空间。该空间命名为PCMSamp
fr_=&info;
说明:让管理帧参数的结构体fr_ps中的指针header与info指向相同具体作用后面说
if((musicout=fopen(argv[2],"w+b"))==NULL){
说明:这个程序在执行前,用户要给两个参数,一个是要解码的mp3文件名对应argv[1],一个是保存解码数据的文件名对应argv[2]。这句话的含
义是以“二进制写入”的方式建立文件名为用户提供的argv[2]的文件,如果建立失败,musicout将返回null,一旦返回null就要执行下面的提示
出错语句。
printf("Couldnotcreate"%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=ek_sync(&bs,SYNC_WORD,SYNC_WORD_LENGTH);
从结构体bs指向的文件中查找同步头SYNC_WORD(即是B)
如果找到同步头返回1给sync否则返回0
if(!sync){
done=TRUE;
printf("nFramecannotbelocatedn");
如果没找到同步头提示出错并跳出程序循环
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(_protection)
buffer_CRC(&bs,&old_crc);
这两句话的意思是,如果帧有纠错机制,那么要从文件中再提出16个比特作为纠错校验位。函数buffer_CRC很简单也不作介绍了,代码罗列在后。
switch(){
ca3:
{
检查从头信息取出的参数,当它是3的时候,才说明该文件是mp3文件,才有必要进行下一步的操作,不是的话直接退出
intnSlots,main_data_end,flush_main;
intbytes_to_discard,gr,ch,ss,sb;
staticintframe_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);
说明:在在协议中,帧数据扣掉头信息和边信息后剩下的信息称为(AudioData)。在解码过程中需要先将AudioData另存到一个缓冲区中,以方便
解码。该函数的作用是通过公式计算出1帧数据的字节长度,再减去头信息和边信息占用的字节数。(剩下的字节数就是AudioData所占用的字
节数。)
for(;nSlots>0;nSlots--)/*readmaindata.*/
hputbuf((unsignedint)getbits(&bs,8),8);
在这里函数hputbuf的作用是从文件中提取出字节nSlots次并将每次提取的字节信息存放在全局缓冲区buf中,注意和bs->buf区别,两个表示的
是不同的缓冲区。这样一来,AudioData数据就存放在全局缓冲区buf中了。这个函数不做做具体解释,程序罗列在后
main_data_end=hsstell()/8;/*ofpriviousframe*/
程序需要做的是:因为之前已经开辟了一个全局缓冲区buf,并建立了一个新的结构提来管理这个大小是4096字节的buf。程序希望的是将多帧的
AudioData通过循环都提取出来存放在这个缓冲区里统一处理。函数hsstell是返回全局缓冲区buf的比特位置的,每向buf放入一个字节,结构体
中相应的比特位置计数参量就会加8。main_data_end=hsstell()/8标识的就是buf已经放入了多少个字节,实际上就是指明了上一帧AudioData在
buf中的结束位置。
if(flush_main=(hsstell()%8)){
hgetbits((int)(bitsPerSlot-flush_main));
main_data_end++;
}
为了确保buf中的数据是以字节位组织形式的,有上面的操作(有待更具体的分析,有点想不通,因为它取数据的时候就是以字节为单位存入buf
的,又何必担心buf不是以字节为组织形式,而再加上这3句话来确保它是以字节为单位组织的数据)
。。。。。。接下来的10句话难了我1个多月呢。。。。。。
1bytes_to_discard=frame_start-main_data_end-III_side__data_begin;
先要知道frame_start初始化为0,还要知道,AudioData的有效数据不一定是直接跟在边信息以后,它的开始位置是由III_side__data_begin
指定的,在边信息和有效主信息之间会存在垃圾信息,这是我们需要把它扣除的,这10话的作用也在于此
2if(main_data_end>4096){frame_start-=4096;
3rewindNbytes(4096);
4}
5frame_start+=main_data_slots(fr_ps);
6if(bytes_to_discard<0){
7frameNum-1;
8break;
9}
10for(;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变成了标识上一帧audiodata长度的值,实际上也就是标识了下一帧的开始位置,执行6,7,8句直接跳出了switch,
但依然在while(!end_bs(&bs))死循环体内,还会不断地执行到这10句话。
从第二次开始,执行到第一句后bytes_to_discard反映的就是确确实实的两帧主数据之间的垃圾信息了。可以参看附图,这里有些问题:按我的理
解maid_data_begin一定是反映主数据相对帧结束位置的长度,而不是相对帧起点的长度。(这里理解上有些不确定,总体上这10句话就是为了去
掉垃圾字节所作的工作)
clip=0;
for(gr=0;gr<2;gr++){
doublelr[2][SBLIMIT][SSLIMIT],ro[2][SBLIMIT][SSLIMIT];
for(ch=0;ch
longintis[SBLIMIT][SSLIMIT];
intpart2_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程序是turborC下运行的程序,比特流分解部分已经调试无误通过。紧接着要做的工作是用
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_[ch].gr[gr]),ch,&fr_ps);
}
//立体声处理
III_stereo(ro,lr,&III_scalefac,&(III_side_[0].gr[gr]),&fr_ps);
for(ch=0;ch
doublere[SBLIMIT][SSLIMIT];
doublehybridIn[SBLIMIT][SSLIMIT];/*Hybridfilterinput*/
doublehybridOut[SBLIMIT][SSLIMIT];/*Hybridfilterout*/
doublepolyPhaIn[SBLIMIT];/*PolyPhaInput.*/
III_reorder(lr[ch],re,&(III_side_[ch].gr[gr]),&fr_ps);
//抗锯齿处理
III_antialias(re,hybridIn,/*Antialiasbutterflies.*/
&(III_side_[ch].gr[gr]),&fr_ps);
//IMDCT
for(sb=0;sb
III_hybrid(hybridIn[sb],hybridOut[sb],sb,ch,&(III_side_[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
polyPhaIn[sb]=hybridOut[sb][ss];
//子带合成
clip+=SubBandSynthesis(polyPhaIn,ch,&((*pcm_sample)[ch][ss][0]));
}
}
//PCM输出
/*OutputPCMsamplepointsforonegranule(颗粒).*/
out_fifo(*pcm_sample,18,&fr_ps,done,musicout,&sample_frames);
}
if(clip>0)
printf("n%dsamplesclipped.n",clip);
}
break;
default:
printf("nOnlylayerIIIsupported!n");
exit(1);
break;
}
}
clo_bit_stream_r(&bs);
fclo(musicout);
printf("nDecodingdone.n");
return;
}
3:函数read_decoder_table分析
intread_decoder_table(FILE*fi)
{
该函数的作用是将34个供解码的数值存入了变量ht[n].val中,以方便以后的解码调用
//HTN=34
intn,i,nn,t;
unsignedintv0,v1;
charcommand[100],line[100];
for(n=0;n
要进行34次循环因为有34个子表
/*.tablenumbertreelenxlenylenlinbits*/
do{
fgets(line,99,fi);
从解码树表文件fi中读入98个字符然后在最后加“/0”组成字符窜存入树组line中,但是如果碰到回车则马上停止读入。
}while((line[0]=='#')||(line[0]<''));
因为文件中一行就有一个回车,所以往往取不到98个字符,就碰到了回车。
文件中开始的部分是一些无用的说明部分是用#作为开头的,上面的这个循环就是为了去除无效行,直到取出一行有效行后进行下面的
操作:
sscanf(line,"%s%s%u%u%u%u",command,ht[n].tablename,
&ht[n].treelen,&ht[n].xlen,&ht[n].ylen,&ht[n].linbits);
从一个字符串中读进与指定格式相符的数据存入相应的变量,即取出子表头信息,子表头信息包含表名,解码树长,x长,y长,和linbits这些参
量。在这里的含义就是将.table00000中的.table存入command,0存入ht[n].tablename,...
if(strcmp(command,".end")==0)
判断两个字符串
returnn;
如果command==.end说明已经循环完了34次,已经到了文件尾了,所以返回就可以了
elif(strcmp(command,".table")!=0){
fprintf(stderr,"huffmantable%udatacorruptedn",n);
return-1;
}
如果command!=.end&&!=.table报错
ht[n].linmax=(1<
计算出linmax参量(什么用处?)
do{
fgets(line,99,fi);
}while((line[0]=='#')||(line[0]<''));
取出子表中第2行有效行
sscanf(line,"%s%u",command,&t);
取出该有效行中的有效参数
if(strcmp(command,".reference")==0){
ht[n].ref=t;
ht[n].val=ht[t].val;
ht[n].treelen=ht[t].treelen;
if((ht[n].xlen!=ht[t].xlen)||
(ht[n].ylen!=ht[t].ylen)){
fprintf(stderr,"wrongtable%ureferencen",n);
return(-3);
};
while((line[0]=='#')||(line[0]<'')){
fgets(line,99,fi);
}
}
文件中ht[n].ref的存在只是为了减小解码表的容量,但是增加了解码程序的代码长度
elif(strcmp(command,".treedata")==0){
ht[n].ref=-1;
ht[n].val=(unsignedchar(*)[2])
calloc(2*(ht[n].treelen),sizeof(unsignedchar));
位变量val动态分配内存空间
if((ht[n].val==NULL)&&(ht[n].treelen!=0)){
fprintf(stderr,"heaperrorattable%dn",n);
exit(-10);
}
for(i=0;i
fscanf(fi,"%x%x",&v0,&v1);
ht[n].val[i][0]=(unsignedchar)v0;
ht[n].val[i][1]=(unsignedchar)v1;
}
将34个供解码的数值存入了变量ht[n].val中
fgets(line,99,fi);/*readtherestoftheline*/
}
el{
fprintf(stderr,"huffmandecodertableerrorattable%dn",n);
}
}
returnn;
}
4:函数huffman_decoder分析
inthuffman_decoder(structhuffcodetab*h,int*x,int*y,int*v,int*w)
{
参数说明这个函数需要一个指向相应霍夫曼表的结构体指针,根据霍夫曼解码表来查表解码,解码出来的数据先存到x,y,v,w上,然后在函数外,
将值存入变量is中。本身这里的程序语句不难理解,前提是对霍夫曼查表法解码由足够的了解。往下的部分就不再作程序本身的介绍。只简单的
了解数据的流程就可以了,这里数据是从全局buf中一个比特一个比特的取出,根据霍夫曼解码方法,通过对比表格,得到相应的解码后的数据。
前面已经介绍了,为了方便霍夫曼解码,数据分了3个大区,其中大值区还分为3个小区。不同区里的比特选用的解码表不一样,解码出来的数
据个数也是不一样的。具体实现方法还没有彻底弄通呢。
HUFFBITSlevel;
在前面的文件里面已经定义了#defineHUFFBITSunsignedlongint
还定义了HUFFBITSdmask=1<<(sizeof(HUFFBITS)*8-1);
intpoint=0;
interror=1;
level=dmask;
if(h->val==NULL)return2;
/*table0needsnobits*/
if(h->treelen==0)
{*x=*y=0;
return0;
}
/*LookupinHuffmantable.*/
以下开始霍夫曼的查表法解码,即在解码表中查找对应的解码数据
do{
if(h->val[point][0]==0){/*endoftree*/
*x=h->val[point][1]>>4;
*y=h->val[point][1]&0xf;
在数据组织形式上val是一对出现的val[point][0]表示val中第[point]对中的第一个数据
如果第point对val中的第一个数据是0的话,那么x,y根据上式确定
error=0;
break;
}
if(hget1bit()){
while(h->val[point][1]>=MXOFF)point+=h->val[point][1];
point+=h->val[point][1];
}
el{
while(h->val[point][0]>=MXOFF)point+=h->val[point][0];
point+=h->val[point][0];
}
level>>=1;
}while(level||(point
/*Checkforerror.*/
if(error){/*txandytoamediumvalueasasimpleconcealment*/
printf("IllegalHuffmancodeindata.n");
*x=(h->xlen-1<<1);
*y=(h->ylen-1<<1);
}
/*Processsignencodingsforquadruplestables.*/
if(h->tablename[0]=='3'
&&(h->tablename[1]=='2'||h->tablename[1]=='3')){
*v=(*y>>3)&1;
*w=(*y>>2)&1;
*x=(*y>>1)&1;
*y=*y&1;
/*v,w,xandyarereverdinthebitstream.
switchthemaroundtomaketestbistreamwork.*/
/*{inti=*v;*v=*y;*y=i;i=*w;*w=*x;*x=i;}MI*/
if(*v)
if(hget1bit()==1)*v=-*v;
if(*w)
if(hget1bit()==1)*w=-*w;
if(*x)
if(hget1bit()==1)*x=-*x;
if(*y)
if(hget1bit()==1)*y=-*y;
}
/*Processsignandescapeencodingsfordualtables.*/
el{
/*xandyarereverdinthetestbitstream.
Reverxandyheretomaketestbitstreamwork.*/
/*removed11/11/92-ag
{inti=*x;*x=*y;*y=i;}
*/
if(h->linbits)
if((h->xlen-1)==*x)
*x+=hgetbits(h->linbits);
if(*x)
if(hget1bit()==1)*x=-*x;
if(h->linbits)
if((h->ylen-1)==*y)
*y+=hgetbits(h->linbits);
if(*y)
if(hget1bit()==1)*y=-*y;
}
returnerror;
}
三反量化函数分析
voidIII_dequantize_sample(longintis[SBLIMIT][SSLIMIT],doublexr[SBLIMIT][SSLIMIT],III_scalefac_t*scalefac,structgr_info_s*gr_info,intch,
frame_params*fr_ps)
{
参数说明:is是该函数的解码对象,解码后存入xr,需要比例因子结构体,边信息结构体,以及帧信息
intss,sb,cb=0,sfreq=fr_ps->header->sampling_frequency;
intstereo=fr_ps->stereo;
intnext_cb_boundary,cb_begin,cb_width,sign;
/*choocorrectscalefactorbandperblocktype,initalizeboundary*/
反量化也是以颗粒为单位进行的,霍夫曼解码后的数据还是以颗粒为组织形式
if(gr_info->window_switching_flag&&(gr_info->block_type==2))
if(gr_info->mixed_block_flag)
如果该颗粒是短块并且是混合型的短块则next_cb_boundary有下面的公式确定
next_cb_boundary=sfBandIndex[sfreq].l[1];/*LONGblocks:0,1,3*/
el{
如果是非混合型的短块那么需要的一些解码系数确定如下:
next_cb_boundary=sfBandIndex[sfreq].s[1]*3;/*pureSHORTblock*/
cb_width=sfBandIndex[sfreq].s[1];
cb_begin=0;
}
el
如果是长块,那么系数确定如下
next_cb_boundary=sfBandIndex[sfreq].l[1];/*LONGblocks:0,1,3*/
系数确定后则可以进行解码操作:反量化操作就是一个公式计算过程:
以下便是对SBLIMIT*SSLIMIT即32*18=576个数据的依次反量化计算;
遵循的公式是xr[sb][ss]=pow(2.0,(0.25*(gr_info->global_gain-210.0)));
/*applyformulaperblocktype*/
for(sb=0;sb
for(ss=0;ss
if((sb*18)+ss==next_cb_boundary)
当要解码的数据位置正好落在边界的时候要作适当的调整
???这个所谓的边界是什么的边界???
{/*Adjustcriticalbandboundary*/
if(gr_info->window_switching_flag&&(gr_info->block_type==2)){
if(gr_info->mixed_block_flag){
if(((sb*18)+ss)==sfBandIndex[sfreq].l[8]){
如果这个颗粒是混合型的短块并且数据的位置还与sfBandIndex[sfreq].l[8])相同
那么要对next_cb_boundary等系数重新调整
next_cb_boundary=sfBandIndex[sfreq].s[4]*3;
cb=3;
cb_width=sfBandIndex[sfreq].s[cb+1]-
sfBandIndex[sfreq].s[cb];
cb_begin=sfBandIndex[sfreq].s[cb]*3;
}
elif(((sb*18)+ss)
如果这个颗粒是混合型的短块并且数据的位置还小于sfBandIndex[sfreq].l[8])
那么要对next_cb_boundary等系数重新另一种调整如下
next_cb_boundary=sfBandIndex[sfreq].l[(++cb)+1];
el{
如果这个颗粒是混合型的短块并且数据的位置还大于sfBandIndex[sfreq].l[8])
那么要对next_cb_boundary等系数重新第三种调整如下
next_cb_boundary=sfBandIndex[sfreq].s[(++cb)+1]*3;
cb_width=sfBandIndex[sfreq].s[cb+1]-
sfBandIndex[sfreq].s[cb];
cb_begin=sfBandIndex[sfreq].s[cb]*3;
}
}
el{
如果是非混合型的短块,进行这样的调整:
next_cb_boundary=sfBandIndex[sfreq].s[(++cb)+1]*3;
cb_width=sfBandIndex[sfreq].s[cb+1]-
sfBandIndex[sfreq].s[cb];
cb_begin=sfBandIndex[sfreq].s[cb]*3;
}
}
el/*longblocks*/
长块的话进行如下的调整:
??调整的意义是什么???
next_cb_boundary=sfBandIndex[sfreq].l[(++cb)+1];
}
/*Computeoverall(global)scaling.*/
xr[sb][ss]=pow(2.0,(0.25*(gr_info->global_gain-210.0)));
/*Dolong/shortdependentscalingoperations.*/
if(gr_info->window_switching_flag&&(
((gr_info->block_type==2)&&(gr_info->mixed_block_flag==0))||
((gr_info->block_type==2)&&gr_info->mixed_block_flag&&(sb>=2)))){
如果是一个非混合型的短块,或者是混合型的短块且(sb>=2)
xr[sb][ss]*=pow(2.0,0.25*-8.0*
gr_info->subblock_gain[(((sb*18)+ss)-cb_begin)/cb_width]);
xr[sb][ss]*=pow(2.0,0.25*-2.0*(1.0+gr_info->scalefac_scale)
*(*scalefac)[ch].s[(((sb*18)+ss)-cb_begin)/cb_width][cb]);
}
el{/*LONGblocktypes0,1,3&1st2subbandsofswitchedblocks*/
xr[sb][ss]*=pow(2.0,-0.5*(1.0+gr_info->scalefac_scale)
*((*scalefac)[ch].l[cb]
+gr_info->preflag*pretab[cb]));
}
/*Scalequantizedvalue.*/
sign=(is[sb][ss]<0)?1:0;
xr[sb][ss]*=pow((double)abs(is[sb][ss]),((double)4.0/3.0));
if(sign)xr[sb][ss]=-xr[sb][ss];
}
}
}
四立体声处理函数分析
voidIII_stereo(doublexr[2][SBLIMIT][SSLIMIT],doublelr[2][SBLIMIT][SSLIMIT],III_scalefac_t*scalefac,structgr_info_s*gr_info,frame_params*fr_ps)
{参数说明:操作对象是反量化后的存在xr的数据,结果存放在lr,需要比例因子结构体,颗粒信息结构体,帧结构体
intsfreq=fr_ps->header->sampling_frequency;
intstereo=fr_ps->stereo;
intms_stereo=(fr_ps->header->mode==MPG_MD_JOINT_STEREO)&&
(fr_ps->header->mode_ext&0x2);
inti_stereo=(fr_ps->header->mode==MPG_MD_JOINT_STEREO)&&
(fr_ps->header->mode_ext&0x1);
intsfb;
inti,j,sb,ss,ch,is_pos[576];
doubleis_ratio[576];
/*intialization*/
立体声处理前的初始化工作:让is_pos[i]=0111B
JointStereo是一种立体声编码技巧,主要分为IntensityStereo(IS)
和Mid/Side(M/S)stereo两种。IS的是在比较低流量时使用,利用
了人耳对于低频讯号指向性分辨能力的不足,将音讯资料中的低频分解出
来合成单声道资料,剩余的高频资料则合成另一个单声道资料,并另外纪录
高频资料的位置资讯,来重建立体声的效果。例如钢琴独奏的录音就可以利用
这种方法在有限的资料流量中减少音场资讯却大幅增加音色资讯。
Mid/Side(M/S)stereo在左右声道资料相似度大时常被用到,纪录方式是
将左右声道音讯合并(L+R)得到新的一轨,再将左右声道音讯相减(L-R)得到
另外一轨,然后再将这两轨资料用上面提到听觉心理学模型与滤波器处理。
Mid/Side(M/S)stereo与IS一样的是利用部分相位(pha)资讯的损失
来换得较高的音色纪录资讯。一般的MP3是Mid/Sidestereo和IntensityStereo交替使用的
is_pos[i]=7;
if((stereo==2)&&i_stereo)
这里主要要操作的是改变is_pos[i]如果is_pos[i]始终是初始化时的值7,那么立体声处理的时候
将没有办法进行模式i_stereo的解码。因为i_stereo的解码要将音讯资料中的低频分解出
来合成单声道资料,剩余的高频资料则合成另一个单声道资料,所以得弄清什么时候数据是
低频的,什么时候是高频的以下的代码完成的就是这一识别工作is_ratio[i]从而产生需要的系数
{if(gr_info->window_switching_flag&&(gr_info->block_type==2))
{if(gr_info->mixed_block_flag)
如果该帧是立体声的,并且是属于i_stereo模式的立体声并且该颗粒是混合型的短块
那么参数max_sfb先得到确定为0
{intmax_sfb=0;
for(j=0;j<3;j++)
{intsfbcnt;
sfbcnt=2;
sfbcnt的作用是什么?
for(sfb=12;sfb>=3;sfb--)
{下面的操作进行30次
intlines;
可能表征频带宽度
lines=sfBandIndex[sfreq].s[sfb+1]-sfBandIndex[sfreq].s[sfb];
注意:这样一减最小的结果也是4
i=3*sfBandIndex[sfreq].s[sfb]+(j+1)*lines-1;
这样计算i最小的结果也是3
while(lines>0)
{if(xr[1][i/SSLIMIT][i%SSLIMIT]!=0.0)
如果反量化的结果不是0,是0的话就不用进行立体声处理了
选择参数sfb=-10,lines=-10
{sfbcnt=sfb;
sfb=-10;
lines=-10;
}
lines--;
i--;
}
}
sfb=sfbcnt+1;
if(sfb>max_sfb)
max_sfb=sfb;
while(sfb<12)
{sb=sfBandIndex[sfreq].s[sfb+1]-sfBandIndex[sfreq].s[sfb];
i=3*sfBandIndex[sfreq].s[sfb]+j*sb;
for(;sb>0;sb--)
{is_pos[i]=(*scalefac)[1].s[j][sfb];
if(is_pos[i]!=7)
is_ratio[i]=tan(is_pos[i]*(PI/12));
i++;
}
sfb++;
}
sb=sfBandIndex[sfreq].s[11]-sfBandIndex[sfreq].s[10];
sfb=3*sfBandIndex[sfreq].s[10]+j*sb;
sb=sfBandIndex[sfreq].s[12]-sfBandIndex[sfreq].s[11];
i=3*sfBandIndex[sfreq].s[11]+j*sb;
for(;sb>0;sb--)
{is_pos[i]=is_pos[sfb];
is_ratio[i]=is_ratio[sfb];
i++;
}
}
if(max_sfb<=3)
{i=2;
ss=17;
sb=-1;
while(i>=0)
{if(xr[1][i][ss]!=0.0)
{sb=i*18+ss;
i=-1;
}el
{ss--;
if(ss<0)
{i--;
ss=17;
}
}
}
i=0;
while(sfBandIndex[sfreq].l[i]<=sb)
i++;
sfb=i;
i=sfBandIndex[sfreq].l[i];
for(;sfb<8;sfb++)
{sb=sfBandIndex[sfreq].l[sfb+1]-sfBandIndex[sfreq].l[sfb];
for(;sb>0;sb--)
{is_pos[i]=(*scalefac)[1].l[sfb];
if(is_pos[i]!=7)
is_ratio[i]=tan(is_pos[i]*(PI/12));
i++;
}
}
}
}el
如果该帧是立体声的,并且是属于i_stereo模式的立体声并且该颗粒是非混合型的短块
{for(j=0;j<3;j++)
{intsfbcnt;
sfbcnt=-1;
for(sfb=12;sfb>=0;sfb--)
{intlines;
lines=sfBandIndex[sfreq].s[sfb+1]-sfBandIndex[sfreq].s[sfb];
i=3*sfBandIndex[sfreq].s[sfb]+(j+1)*lines-1;
while(lines>0)
{if(xr[1][i/SSLIMIT][i%SSLIMIT]!=0.0)
{sfbcnt=sfb;
sfb=-10;
lines=-10;
}
lines--;
i--;
}
}
sfb=sfbcnt+1;
while(sfb<12)
{sb=sfBandIndex[sfreq].s[sfb+1]-sfBandIndex[sfreq].s[sfb];
i=3*sfBandIndex[sfreq].s[sfb]+j*sb;
for(;sb>0;sb--)
{is_pos[i]=(*scalefac)[1].s[j][sfb];
if(is_pos[i]!=7)
is_ratio[i]=tan(is_pos[i]*(PI/12));
i++;
}
sfb++;
}
sb=sfBandIndex[sfreq].s[11]-sfBandIndex[sfreq].s[10];
sfb=3*sfBandIndex[sfreq].s[10]+j*sb;
sb=sfBandIndex[sfreq].s[12]-sfBandIndex[sfreq].s[11];
i=3*sfBandIndex[sfreq].s[11]+j*sb;
for(;sb>0;sb--)
{is_pos[i]=is_pos[sfb];
is_ratio[i]=is_ratio[sfb];
i++;
}
}
}
}el
{i=31;
ss=17;
sb=0;
while(i>=0)
{if(xr[1][i][ss]!=0.0)
{sb=i*18+ss;
i=-1;
}el
{ss--;
if(ss<0)
{i--;
ss=17;
}
}
}
i=0;
while(sfBandIndex[sfreq].l[i]<=sb)
i++;
sfb=i;
i=sfBandIndex[sfreq].l[i];
for(;sfb<21;sfb++)
{sb=sfBandIndex[sfreq].l[sfb+1]-sfBandIndex[sfreq].l[sfb];
for(;sb>0;sb--)
{is_pos[i]=(*scalefac)[1].l[sfb];
if(is_pos[i]!=7)
is_ratio[i]=tan(is_pos[i]*(PI/12));
i++;
}
}
sfb=sfBandIndex[sfreq].l[20];
for(sb=576-sfBandIndex[sfreq].l[21];sb>0;sb--)
{is_pos[i]=is_pos[sfb];
is_ratio[i]=is_ratio[sfb];
i++;
}
}
}
for(ch=0;ch<2;ch++)
for(sb=0;sb
for(ss=0;ss
lr[ch][sb][ss]=0;
if(stereo==2)
for(sb=0;sb
for(ss=0;ss
i=(sb*18)+ss;
if(is_pos[i]==7){
if(ms_stereo){
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;
}
el{
lr[0][sb][ss]=xr[0][sb][ss];
lr[1][sb][ss]=xr[1][sb][ss];
}
}
elif(i_stereo){
lr[0][sb][ss]=xr[0][sb][ss]*(is_ratio[i]/(1+is_ratio[i]));
lr[1][sb][ss]=xr[0][sb][ss]*(1/(1+is_ratio[i]));
}
el{
printf("Errorinstreoprocessingn");
}
}
el/*mono,bypassxr[0][][]tolr[0][][]*/
for(sb=0;sb
for(ss=0;ss
lr[0][sb][ss]=xr[0][sb][ss];
}
五数据重排列函数分析
voidIII_reorder(doublexr[SBLIMIT][SSLIMIT],doublero[SBLIMIT][SSLIMIT],structgr_info_s*gr_info,frame_params*fr_ps)
{排列前的数据位置xr,排列后要存放的位置ro,需要颗粒结构体,和帧结构体
intsfreq=fr_ps->header->sampling_frequency;
intsfb,sfb_start,sfb_lines;
intsb,ss,window,freq,src_line,des_line;
for(sb=0;sb
for(ss=0;ss
ro[sb][ss]=0;
if(gr_info->window_switching_flag&&(gr_info->block_type==2)){
if(gr_info->mixed_block_flag){
/*NOREORDERFORLOW2SUBBANDS*/
for(sb=0;sb<2;sb++)
for(ss=0;ss
ro[sb][ss]=xr[sb][ss];
}
在最下面的两个子带中数据的排列不需要变化
其它的子带要根据协议内容来变化
/*REORDERINGFORRESTSWITCHEDSHORT*/
for(sfb=3,sfb_start=sfBandIndex[sfreq].s[3],
sfb_lines=sfBandIndex[sfreq].s[4]-sfb_start;
sfb<13;sfb++,sfb_start=sfBandIndex[sfreq].s[sfb],
(sfb_lines=sfBandIndex[sfreq].s[sfb+1]-sfb_start))
for(window=0;window<3;window++)
for(freq=0;freq
src_line=sfb_start*3+window*sfb_lines+freq;
des_line=(sfb_start*3)+window+(freq*3);
ro[des_line/SSLIMIT][des_line%SSLIMIT]=
xr[src_line/SSLIMIT][src_line%SSLIMIT];
}
}
el{/*pureshort*/
for(sfb=0,sfb_start=0,sfb_lines=sfBandIndex[sfreq].s[1];
sfb<13;sfb++,sfb_start=sfBandIndex[sfreq].s[sfb],
(sfb_lines=sfBandIndex[sfreq].s[sfb+1]-sfb_start))
for(window=0;window<3;window++)
for(freq=0;freq
src_line=sfb_start*3+window*sfb_lines+freq;
des_line=(sfb_start*3)+window+(freq*3);
ro[des_line/SSLIMIT][des_line%SSLIMIT]=
xr[src_line/SSLIMIT][src_line%SSLIMIT];
}
}
}
el{/*longblocks*/
for(sb=0;sb
for(ss=0;ss
ro[sb][ss]=xr[sb][ss];
}}
一比特流分解函数分析
1:函数open_bit_stream_r分析
voidopen_bit_stream_r(Bit_stream_struc*bs,char*bs_filenam,intsize)
{
函数的操作对象是(1)用户提供的mp3文件名filenam(2)用来映像MP3文件的帧结构体bs(3)缓冲区的大小size。
registerunsignedcharflag=1;
定义变量前加个register是因为这个参数flag将频繁使用,为了引用的速度够块,前加register从而建议处理器分配寄存器来存放这个参数。
if((bs->pt=fopen(bs_filenam,"rb"))==NULL){
以二进制读出的方式打开用户指定的mp3文件,并用pt指向这个文件,如果打开失败则执行下面的出错信息。
printf("Couldnotfind"%s".n",bs_filenam);
exit(1);
}
bs->format=BINARY;
alloc_buffer(bs,size);
bs->buf_byte_idx=0;
bs->buf_bit_idx=0;
bs->totbit=0;
bs->mode=READ_MODE;
bs->eob=FALSE;
bs->eobs=FALSE;
为了更好的宏观上用结构体bs映像该mp3文件,要根据文件的一些特点来对结构体中的一些参数进行赋值。比如用bs->pt指向文件,bs->format
设为二进制,bs->buf_byte_idx=0;bs->buf_bit_idx=0;bs->totbit=0;表示这个mp3文件现在还没有被操作过,如果从这个文件中取出了9位比特,那么
相应的bs->buf_byte_idx;bs->buf_bit_idxbs->totbit=0会被从新设定为bs->buf_byte_idx=1表示当前是第2个字节,bs->buf_bit_idx=1表示当前等
待操作的是第2个字节的第2位,bs->totbit表示当前等待操作的是第10位比特。
}
2:函数end_bs分析
intend_bs(Bit_stream_struc*bs)
{
return(bs->eobs);
返回结束标志,一开始初始化位fal表示当前帧还没有操作结束,一旦对当前帧的所有数据都操作过后,bs->eobs为true
}
3:函数ek_sync分析
intek_sync(Bit_stream_struc*bs,unsignedlongsync,intN)
{
该函数的操作对象是结构体bs指向的文件,查找目标是变量sync定义的0xfff,查找目标的长度有变量N定义这里N是12。
unsignedlongaligning;
unsignedlongval;
longmaxi=(int)pow(2.0,(double)N)-1;
定义maxi=[(2的N次方)-1],在主程序中N=12,所以maxi实际上就是1B
aligning=sstell(bs)%ALIGNING;
变量aligning的作用在于确保同步头的识别是从字节边界开始的。比如说在二进制文件里面存在这么一段数据:1111
0010,在这段数据里面,前面连续的11个1不能算是同步头,因为第一个1开始的地方不是字节的边界。如果存在这么一段数据10101
0010,那么前面连续的11个1就可以确定是同步头了。(understand?:)
函数sstell的作用是返回当前要操作的比特位置,比如说,我们已经提取并处理了文件的前50个比特,但还是没有找到同步头(mp3文件的开始
往往有一段无用的乱码数据),虽然没有找到,但是结构体中的bs->totbit始终在累加计数,此时的它应该是50,通过调用函数sstell(&bs)就可以返
回出bs->totbit的取值。如果让bs->totbit对8求余,余数为0说明,当前要被操作的这个比特是一个新字节的起点。%是求余运算,ALIGNING是
常量8。
if(aligning)
如果余数不为0,说明刚要被操作的比特不是以字节为边界的,比如bs->totbit是14,而我们知道第0,8,16个比特是以字节为边界的,为了查找
同步头,我们希望一开始就把比特位置定位在字节边界。就像刚才比如的bs->totbit是14,从这个位置开始查找11个连续的1,即使找到也没有意
义。所以要先进行下面的操作:
getbits(bs,(int)(ALIGNING-aligning));
我们要再提出两个比特让bs->totbit变成16(每提出一个比特,相应的bs->totbit会加1,这是提取比特函数完成的工作)。这里用到了一个新的函
数getbits是比特提取函数,在下面介绍。
val=getbits(bs,N);
定位到字节边界后,以后每次从文件中提取11个比特,只要这11个比特是同步头,就说明解码起点找到了。但是请注意程序中val=getbits(bs,
N)的N实际上是12,程序每次提出了12个比特来进行比对,这主要是因为如果不这样,万一提出的11位数据不是同步头的时候,还得重复进行
比特字节边界定位的操作,会变得麻烦。而如果一次提出12位,即使不是同步头,重新进行比特字节边界定位的操作也比较方便用如下的3句话
完成(自己分析这3句话哈,不难的呵呵,提示maxi是上面定义好的0xfff)
while(((val&maxi)!=sync)&&(!end_bs(bs))){
val<<=ALIGNING;
val|=getbits(bs,ALIGNING);
}
if(end_bs(bs))
return(0);
el
return(1);
}
4:函数getbits分析
unsignedlonggetbits(Bit_stream_struc*bs,intN)
{
该函数是比特分解的核心函数,静下心看还是看得懂的,加油
该函数的操作对象是结构体指向的文件,以及需要取出的比特个数N
unsignedlongval=0;
registerinti;
registerintj=N;
registerintk,tmp;
if(N>MAX_LENGTH)
程序设定最多一次只能取出32个比特,MAX_LENGTH是常量32
printf("Cannotreadorwritemorethan%dbitsatatime.n",MAX_LENGTH);
bs->totbit+=N;
取出多少个比特,结构体中相应的比特位置参数就要相应的计数累加,事实上字节位置也要计数累加,就是每取出8个比特,字节位置变化1,但
由于程序中往往取出的比特个数是不一定的,所以字节位置的计数比较困难,相对麻烦一些
while(j>0){
如果要取出的比特个数不为0,那么进行如下的操作:比特位置的计数很方便上面已经实现,字节位置的计数要麻烦一些:要借助到变量
bs->buf_bit_idx,具体实现看下面:(至于为什么要用到字节位置的计数,那是因为缓冲器是一字节位单位组织数据的)
if(!bs->buf_bit_idx){
注意bs->buf_bit_idx的取值是[0-7],比特位置一直累加,bs->buf_bit_idx就一直在[0-7]区间里面循环。如果bs->buf_bit_idx为0,说明循环一次了,
已经有一个字节长度被处理过了。
以下的代码循环相套可读性很差,得有点耐心分析。。。。。。
为方便分析,在这里加了编号:
1bs->buf_bit_idx=8;
2bs->buf_byte_idx--;
3if((bs->buf_byte_idx
4if(bs->eob)
5bs->eobs=TRUE;
6el{
7for(i=bs->buf_byte_idx;i>=0;i--)
8bs->buf[bs->buf_size-1-bs->buf_byte_idx+i]=bs->buf[i];
9refill_buffer(bs);
10bs->buf_byte_idx=bs->buf_size-1;
}
}
}
刚开始的时候buf_byte_idx初始化为0,那么经过语句2,则它一定是个小于0的数,肯定通过3句的判断,继而执行第4句,以开始的时候,buffer
里还没调入数据,所以eof(endofbuffer)肯定是fal所以不执行5句,而直接执行7句,有因为这次的buf_byte_idx也不符合7句的判断,所以不
会执行8句,而开始9句的执行。(9句的目的是把数据调入buffer中,填满buffer,让eof为真),然后执行10句bs->buf_byte_idx=bs->buf_size-1;
(显然bs->buf_byte_idx的计数方式与bs->totbit的计数方向正好相反。
等第二次循环到这里的时候
2句的执行说明,已经处理完了一个字节,bs->buf_byte_idx计数减1,如果bs->buf_byte_idx值已经小于MINIMUM(常量4)抑或已经是负值
的时候,表明这一帧的数据已经处理完毕,令bs->eobs为真
11k=MIN(j,bs->buf_bit_idx);
12tmp=bs->buf[bs->buf_byte_idx]&putmask[bs->buf_bit_idx];
13tmp=tmp>>(bs->buf_bit_idx-k);
14val|=tmp<<(j-k);
15bs->buf_bit_idx-=k;
16j-=k;
[11-16]的含义是:如果要取出的比特数小于8,比如是5,那么从缓冲区buffer中取出第buf_byte_idx个字节数据(注意缓冲区里的数据应该是从
高位相低位开始填充的,否则没有办法解释,有待确定),通过逻辑与运算只保留前5为比特(在高位)。(这里putmask[bs->buf_bit_idx]是个数组,
intputmask[9]={0x0,0x1,0x3,0x7,0xf,0x1f,0x3f,0x7f,0xff};)。保留后左移3位,存入32为的变量val中,因为值取了5位,没有超过8位,所以
buf_byte_idx不变
如果取出的比特数大于8,比如25,那么。。。。。下面的分析你自己搞定哈,表达出来太麻烦了,呵呵,现在体会到什么是只可意会了呵呵。
额外话:春节快乐!!!
}
returnval;
}
5:函数refill_buffer分析
voidrefill_buffer(Bit_stream_struc*bs)
{
registerinti=bs->buf_size-2-bs->buf_byte_idx;
registerunsignedlongn=1;
while((i>=0)&&(!bs->eob)){
在这里如果(i>=0)&&(!bs->eob),说明buffer有空余的空间
n=fread(&bs->buf[i--],sizeof(unsignedchar),1,bs->pt);
那么从文件中取出数据一次来填这个缓冲区buffer,通过反复执行达到填满buffer的目的
if(!n)
bs->eob=i+1;
直到填满后,将没办法再填入,返回值n开始变为0,于是bs->eob=真,并跳出程序
}
}
6:函数decode_info分析
voiddecode_info(Bit_stream_struc*bs,frame_params*fr_ps)
{该函数的操作对象是结构体bs所映像的文件,
将解码出来的信息存放在帧结构体fr_ps中
layer*hdr=fr_ps->header;
这句话是辅助用的,他的使用让hdr->version=get1bit(bs);
等效于(fr_ps->header)->version=get1bit(bs);)
hdr->version=get1bit(bs);(取出同步头后,接着取出的1个比特是版本信息
hdr->lay=4-getbits(bs,2);(再接着取出的2个比特是协议层数的信息)
hdr->error_protection=!get1bit(bs);/*/FALSE*/
hdr->bitrate_index=getbits(bs,4);
hdr->sampling_frequency=getbits(bs,2);
hdr->padding=get1bit(bs);
hdr->extension=get1bit(bs);
hdr->mode=getbits(bs,2);
hdr->mode_ext=getbits(bs,2);
hdr->copyright=get1bit(bs);
hdr->original=get1bit(bs);
hdr->emphasis=getbits(bs,2);
注意这里有个新的函数get1bit(bs),其实功能等于getbits(bs,1),所有就不再额外分析
}
7:函数hdr_to_frps分析
voidhdr_to_frps(frame_params*fr_ps)
{
layer*hdr=fr_ps->header;/*(orpassinasarg?)*/
fr_ps->actual_mode=hdr->mode;
fr_ps->stereo=(hdr->mode==MPG_MD_MONO)?1:2;
fr_ps->sblimit=SBLIMIT;
if(hdr->mode==MPG_MD_JOINT_STEREO)
fr_ps->jsbound=js_bound(hdr->lay,hdr->mode_ext);
el
fr_ps->jsbound=fr_ps->sblimit;
}
8:函数buffer_CRC分析
voidbuffer_CRC(Bit_stream_struc*bs,unsignedint*old_crc)
{
*old_crc=getbits(bs,16);
}
9:函数III_get_side_info分析
voidIII_get_side_info(Bit_stream_struc*bs,III_side_info_t*si,frame_params*fr_ps)
{
该函数的操作对象是文件结构体bs指向的文件,根据帧结构体fr_ps的相关信息,从文件中提取边信息存放到边信息结构体III_side_info_t*si中管
理。
intch,gr,i;
intstereo=fr_ps->stereo;
si->main_data_begin=getbits(bs,9);
边信息紧接在头信息之后,接在头信息后面的9位是第一个边信息即语音主信息开始位置,取出后存放在si->main_data_begin中
if(stereo==1)
说明:stereo在帧解码出来的头信息中,标志该帧是单声道,还是双声道
如果是单声道那么文件的数据组织形式是,标识语音主信息开始位置的9位比特之后,接着的5位是private_bits信息(解码中没用,呵呵摆设用
的)。如果是双声道的,那么只有接着的3位是private_bits信息
si->private_bits=getbits(bs,5);
el
si->private_bits=getbits(bs,3);
for(ch=0;ch
for(i=0;i<4;i++)
si->ch[ch].scfsi[i]=get1bit(bs);
紧接着取出比例因子解码需要的系数scfsi,每个声道有4个比例因子系数,每个比例因子是1位比特信息。注意此后的信息解码是以颗粒位单位
来进行的。一帧中有一个或者两个声道,每个声道有1152个采样数据,协议把这1152个数据分为两个颗粒来封装编码。颗粒编码得到的相应的一
些系数被放到边信息中的比例因子解码信息之后。对这一部分的系数的解码结果自然要对应相应的颗粒来存放。以下的程序是个循环体,这里取
gr=0,ch=0,来简单说明这个循环体的作用:
for(gr=0;gr<2;gr++){
for(ch=0;ch
si->ch[ch].gr[gr].part2_3_length=getbits(bs,12);
从文件中在取出12个比特作为第1个声道的第一个颗粒的part2_3_length系数,这个系数的作用是:表识1区的大小,上面已经介绍过了,1个
颗粒里面有576个数据。实际上采样数据被分为32个子带,每个子带经过mdct变换后得到18个数据组成的32*18=576个系数。这576个系数经
过霍夫曼编码后得到这576个数据。在编码的时候,为了方便解码这576个数据分为3个区,大值区(big_value),1区,0区。大值区又被细分位
3个小区:0号区,1号区,2号区。分区的目的是为了霍夫曼解码的速度。具体机制,huhu俺还没彻底清楚,可能的话,呵呵有你来讲。
si->ch[ch].gr[gr].big_values=getbits(bs,9);
参数big_values用来标识从主数据开始的多少字节是属于大值区
si->ch[ch].gr[gr].global_gain=getbits(bs,8);
参数global_gain是全局增益因子,解码计算用到
si->ch[ch].gr[gr].scalefac_compress=getbits(bs,4);
参数scalefac_compress比例因子压缩系数,也是后面的计算用到
si->ch[ch].gr[gr].window_switching_flag=get1bit(bs);
参数window_switching_flag带窗标志,后面的解码常常根据这个标志作一些判断,是否带窗决定了不同的解码方法。窗的具体含义还没有彻底明
白,应该跟子带分解,滤波,子带合成,信号处理等有关系。
if(si->ch[ch].gr[gr].window_switching_flag){
si->ch[ch].gr[gr].block_type=getbits(bs,2);
si->ch[ch].gr[gr].mixed_block_flag=get1bit(bs);
for(i=0;i<2;i++)
si->ch[ch].gr[gr].table_lect[i]=getbits(bs,5);
for(i=0;i<3;i++)
si->ch[ch].gr[gr].subblock_gain[i]=getbits(bs,3);
如果该颗粒是带窗的那么进行上述的解码。table_lect[i]是霍夫曼解码表的选择:协议提供了32个表来提供霍夫曼解码使用,具体用那个表,与
该参数table_lect[i]相关
}
if(si->ch[ch].gr[gr].block_type==0){
printf("Sideinfobad:block_type==0insplitblock.n");
exit(0);
elif(si->ch[ch].gr[gr].block_type==2
&&si->ch[ch].gr[gr].mixed_block_flag==0)
si->ch[ch].gr[gr].region0_count=8;/*MI9;*/
elsi->ch[ch].gr[gr].region0_count=7;/*MI8;*/
si->ch[ch].gr[gr].region1_count=20-si->ch[ch].gr[gr].region0_count;
上面这些语句的含义是根据窗的类型,来确定大值区中0号,1号,2号区的起始与结束区间。
}
el{
如果颗粒是不带窗的,那么大值区中0号,1号,2号区的起始与结束区间又是一种新的分布如下:
for(i=0;i<3;i++)
si->ch[ch].gr[gr].table_lect[i]=getbits(bs,5);
si->ch[ch].gr[gr].region0_count=getbits(bs,4);
si->ch[ch].gr[gr].region1_count=getbits(bs,3);
si->ch[ch].gr[gr].block_type=0;
}
si->ch[ch].gr[gr].preflag=get1bit(bs);
si->ch[ch].gr[gr].scalefac_scale=get1bit(bs);
si->ch[ch].gr[gr].count1table_lect=get1bit(bs);
}
}
}
10:函数main_data_slots分析
intmain_data_slots(frame_paramsfr_ps)
{
intnSlots;
nSlots=(144*bitrate[2][fr_->bitrate_index])
/s_freq[fr_->sampling_frequency];
在协议中,帧的长度是固定的。用上述的计算公式可以计算出来
在协议中,帧数据扣掉头信息和边信息后剩下的信息称为(AudioData)
在解码过程中需要先将AudioData另存到一个缓冲区中,以方便解码
下面的操作是为了计算AudioData的长度,通过帧长度减去头信息,边信息长度
注意nSlots表征的是多少字节,而不是多少比特
if(fr_->padding)nSlots++;
nSlots-=4;
if(fr_->error_protection)
nSlots-=2;
if(fr_==1)
nSlots-=17;
el
nSlots-=32;
return(nSlots);
}
11:函数hputbuf分析
voidhputbuf(unsignedintval,intN)
{
if(N!=8){
printf("NotSupportedyet!!n");
exit(-3);
}
buf[offt%BUFSIZE]=val;
offt++;
}
12:函数III_get_scale_factors分析
voidIII_get_scale_factors(III_scalefac_t*scalefac,III_side_info_t*si,intgr,intch,frame_params*fr_ps)
{
以下的代码理解难度不大,让人感觉纷乱的是mp3里面参数之间错综复杂的关系,这个函数的作用无非就是根据已经得到的一些信息(比如是窗
类型,比例因子解码系数等),从全局buf中取出规定位数的比特作为相应的比例因子。更细节的描述,即使写出来呵呵,你也未必有耐心看。
intsfb,i,window;
structgr_info_s*gr_info=&(si->ch[ch].gr[gr]);
if(gr_info->window_switching_flag&&(gr_info->block_type==2)){
if(gr_info->mixed_block_flag){/*MIXED*//*NEW-ag11/25*/
for(sfb=0;sfb<8;sfb++)
(*scalefac)[ch].l[sfb]=hgetbits(
slen[0][gr_info->scalefac_compress]);
for(sfb=3;sfb<6;sfb++)
for(window=0;window<3;window++)
(*scalefac)[ch].s[window][sfb]=hgetbits(
slen[0][gr_info->scalefac_compress]);
for(sfb=6;sfb<12;sfb++)
for(window=0;window<3;window++)
(*scalefac)[ch].s[window][sfb]=hgetbits(
slen[1][gr_info->scalefac_compress]);
for(sfb=12,window=0;window<3;window++)
(*scalefac)[ch].s[window][sfb]=0;
}
el{/*SHORT*/
for(i=0;i<2;i++)
for(sfb=sfbtable.s[i];sfb
for(window=0;window<3;window++)
(*scalefac)[ch].s[window][sfb]=hgetbits(
slen[i][gr_info->scalefac_compress]);
for(sfb=12,window=0;window<3;window++)
(*scalefac)[ch].s[window][sfb]=0;
}
}
el{/*LONGtypes0,1,3*/
for(i=0;i<4;i++){
if((si->ch[ch].scfsi[i]==0)||(gr==0))
for(sfb=sfbtable.l[i];sfb
(*scalefac)[ch].l[sfb]=hgetbits(
slen[(i<2)?0:1][gr_info->scalefac_compress]);
}
(*scalefac)[ch].l[22]=0;
}
}
二霍夫曼解码函数分析
1:函数III_hufman_decode分析
voidIII_hufman_decode(longintis[SBLIMIT][SSLIMIT],III_side_info_t*si,intch,intgr,intpart2_start,frame_params*fr_ps)
参量说明:(a)is是一个32*18的二维数组用来存放解码后的576个数据,既是PCM数据经过频率变换得到的系数。
(b)*si是边信息结构体的入口指针:解码过程需要一些边信息中的系数
(c)ch是声道标示,gr是颗粒标示,解码过程是以没个声道的颗粒为单位进行的
(d)part2_start,解码的位置依据
(e)*fr_ps是帧参量的入口指针:解码过程需要诸如声道数,协议层次的信息来源
{
inti,x,y;
intv,w;
structhuffcodetab*h;
这是一个结构体的定义:它将统一对若干个霍夫曼解码表进行管理。
intregion1Start;
intregion2Start;
intbt=(*si).ch[ch].gr[gr].window_switching_flag&&((*si).ch[ch].gr[gr].block_type==2);
initialize_huffman();
函数initialize_huffman的作用:
(1)霍夫曼解码初始化:打开协议定义好的霍夫曼解码树表(该表中存在34各子表),在理解之后的内容时,请一定一边读程序,一边参考这
个霍夫曼解码树表文件,该文件放在附录里
(2)取出霍夫曼解码树表文件中34个子表的内容以便于解码使用
/*Findregionboundaryforshortblockca.*/
if(((*si).ch[ch].gr[gr].window_switching_flag)&&
((*si).ch[ch].gr[gr].block_type==2)){
如果相应颗粒的窗口标志为1并且块类型是2的话那么等于说明这个颗粒是短块,它的576个系数中前36个数属于0区,从36到576的系数属
于1区。并且不存在属于2区的系数。所在区不同,需要的解码表不同,要根据区从34个表中选出合适的表来解码。
region1Start=36;/*sfb[9/3]*3=36*/
region2Start=576;/*NoRegion2forshortblockca.*/
}
el{/*Findregionboundaryforlongblockca.*/
相反如果不符合上述的条件就是长块,长块的数据区分布情况是:
region1Start=sfBandIndex[fr_ps->header->sampling_frequency]
.l[(*si).ch[ch].gr[gr].region0_count+1];/*MI*/
region2Start=sfBandIndex[fr_ps->header->sampling_frequency]
.l[(*si).ch[ch].gr[gr].region0_count+
(*si).ch[ch].gr[gr].region1_count+2];/*MI*/
注意:
(1)首先要了解sfBandIndex这一特殊的数据结构。可以理解它是一个特殊的数组,在这个数组里面还包含了两个子数组l[n]和s[n]
(2)根据头信息的采样率和边信息中的相关参数可以确定0区,1区,2区的范围
(3)576个系数大体上分为大值区,1号区和零区。大值区又分为0,1,2三个小区
(4)大值区解码一次得到两个数据,1号区解码一次得到4个数据。
}
/*Readbigvaluesarea.*/
for(i=0;i<(*si).ch[ch].gr[gr].big_values*2;i+=2){
(1)循环执行big_values次以下的代码,big_values是边信息中的一个参数。
(2)大数值区是成对出现的
(3)上面已经介绍过h是结构体统一对若干个霍夫曼解码表进行管理。
(4)结果霍夫曼解码表的解读后,解码表数据已经存入了变量ht[n].val中,ht是个全局的管理霍夫曼解码表的结构体
if(i
elif(i
elh=&ht[(*si).ch[ch].gr[gr].table_lect[2]];
先根据数据的位置选择相应的用来解码的霍夫曼表
huffman_decoder(h,&x,&y,&v,&w);
函数huffman_decoder的作用就是霍夫曼解码核心程序,解码的数据通过下面的2句,保存到变量is中
is[i/SSLIMIT][i%SSLIMIT]=x;
is[(i+1)/SSLIMIT][(i+1)%SSLIMIT]=y;
}
大值区的霍夫曼解码到此为止,存放在is数组中
/*Readcount1area.*/
1号区的霍夫曼解码:
h=&ht[(*si).ch[ch].gr[gr].count1table_lect+32];
先选择适当的霍夫曼解码表
while((hsstell()
(i
当位比特的位置在1号区范围的时候并且i
huffman_decoder(h,&x,&y,&v,&w);
is[i/SSLIMIT][i%SSLIMIT]=v;
is[(i+1)/SSLIMIT][(i+1)%SSLIMIT]=w;
is[(i+2)/SSLIMIT][(i+2)%SSLIMIT]=x;
is[(i+3)/SSLIMIT][(i+3)%SSLIMIT]=y;
i+=4;
}
if(hsstell()>part2_start+(*si).ch[ch].gr[gr].part2_3_length)
如果比特位置已经到了0值区,进行0值区的解码前重新组织一下缓冲区,以便于下一帧的解码。
{i-=4;
rewindNbits(hsstell()-part2_start-(*si).ch[ch].gr[gr].part2_3_length);
}
/*DismissstuffingBits*/
if(hsstell()
hgetbits(part2_start+(*si).ch[ch].gr[gr].part2_3_length-hsstell());
/*Zerooutrest.*/
for(;i
is[i/SSLIMIT][i%SSLIMIT]=0;
}
2:函数initialize_huffman分析
voidinitialize_huffman()
{
该函数的作用就是打开协议定义好的霍夫曼解码树表(该表中存在34各子表)。MPEG标准通过大量的统计已经为霍夫曼编码作了规定,也为其解
码制作了相应的解码表以供查表。在理解之后的内容时,请一定先看一下这个霍夫曼解码树表文件结构,该文件放在附录里
另一个作用读取霍夫曼解码树表中的数据
FILE*fi;
if(huffman_initialized)return;
if(!(fi=OpenTableFile(""))){
如果打开名为霍夫曼解码树表文件出错则提示并推出
printf("Pleacheckhuffmantable''n");
exit(1);
}
if(read_decoder_table(fi)!=HTN){
函数read_decoder_table的作用是读取文件指针fi指向的霍夫曼解码树表文件,从中读出相应的数据,如果读取失败则提示出错
fprintf(stderr,"decodertablereaderrorn");
exit(4);
}
huffman_initialized=TRUE;
}
本文发布于:2022-12-30 21:33:47,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/61758.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |