(十四)用JAVA编写MP3解码器——多相合成滤波

编程入门 行业动态 更新时间:2024-10-09 18:18:05

(十四)用JAVA编写MP3解码器——<a href=https://www.elefans.com/category/jswz/34/1761521.html style=多相合成滤波"/>

(十四)用JAVA编写MP3解码器——多相合成滤波

解码一帧Layer3第8步:多相频率倒置 (Inverse Quantize Samples)

      也可以称为频率倒相(Frequency Inversion),在数据进入多相滤波器前进行频率倒相,把奇数序号子带的奇数号样本乘上-1,这样做的目的是校正多相滤波器组对频率的倒相。为了充分利用decodeFrame方法内的循环,把这部分简短的代码放进decodeFrame内。

 

解码一帧Layer3第9步:多相合成滤波 (Poly Phase Synthesis Filterbank) 

      多相合成滤波是调用频度最高的一个模块,采用标准立体声编码的MP3一帧要调用18*2*2=72次。多相合成滤波是解码的关键模块,算法及实现代码都可能再优化,加之Layer1和Layer2也要调用,所以将多相合成滤波单独封装在Synthesis类。

      解码一帧Layer3的各步快讲解完了,把解码一帧写进class Layer3内的decodeFrame方法。这部分源码如下:

	//8.//>>>>INVERSE QUANTIZE SAMPLES=============================================//// 在class Layer3的decodeFrame方法内实现////<<<<INVERSE QUANTIZE SAMPLES=============================================//9.//>>>>SYNTHESIZE VIA POLYPHASE MDCT========================================//// 在decodeFrame方法内用objFilter.synthesisSubBand()调用class Synthesis// 的synthesisSubBand方法实现多相合成滤波////<<<<SYNTHESIZE VIA POLYPHASE MDCT========================================//10.//>>>>OUTPUT PCM SAMPLES===================================================//// 见Audio.java////<<<<OUTPUT PCM SAMPLES===================================================private final static float[] floatSamples = new float[32];/** decodeFrame -- 解码1帧Layer3*/public void decodeFrame() throws Exception {getSideInfo();int nSlots = objHeader.getMainDataSlots();int buflen = objInBitStream.getBuffBytes();int data_begin = objSI.main_data_begin;// 若出错(buflen<data_begin): // 不解码当前这一帧,将下一帧(或几帧)主数据(main_data)填入位流缓冲区.//while (buflen < data_begin) {objInBitStream.append(nSlots);//System.out.println("Skip Bytes: " + objHeader.getFrameSize());objHeader.syncFrame();nSlots = objHeader.getMainDataSlots();getSideInfo();buflen = objInBitStream.getBuffBytes();data_begin = objSI.main_data_begin;}//丢弃帧的填充位int discard = buflen - objInBitStream.getBytePos() - data_begin;objInBitStream.skipBytes(discard);objInBitStream.append(nSlots);int gr, ch, sb, ss;for (gr = 0; gr < intMaxGr; gr++) {for (ch = 0; ch < intChannels; ch++) {if (objHeader.getVersion() == Header.MPEG1)getScaleFactors_1(ch, gr);elsegetScaleFactors_2(ch, gr);huffmanDecoder(ch, gr);requantizer(ch, gr,xr[ch]);}if(boolIntensityStereo)i_stereo(gr);if(objHeader.isMSStereo())ms_stereo();for (ch = intFirstChannel; ch <= intLastChannel; ch++) {antialias(ch, gr);hybrid(ch, gr);//>>>>INVERSE QUANTIZE SAMPLESint rzero_sb = (17 + rzero_index[ch]) / 18;for (sb = 1; sb < rzero_sb; sb += 2)for (ss = 1; ss < 18; ss += 2)xr[ch][sb][ss] = -xr[ch][sb][ss];//<<<<INVERSE QUANTIZE SAMPLESfor (ss = 0; ss < 18; ss++) {for (sb = 0; sb < 32; sb++)floatSamples[sb] = xr[ch][sb][ss];objFilter.synthesisSubBand(floatSamples, ch);}}}}

     在decodeFrame方法内调用解码一帧MP3的10个步骤的方法,其中还要考虑容错处理。我们知道一帧的字节数是可以计算出来的,依据什么去计算呢?无论是Layer1、Layer2还是Layer3,帧的长度用槽(slot)描述,Layer2和Layer3一槽是一字节,Layer1一槽是4字节。根据MPEG Audio层的压缩方式,就可以计算出一帧的长度。再看上面代码中“丢弃帧的填充位”就容易理解了:一帧的长度事先可以计算出来,如果MP3编码器压缩后的一帧小于计算出的帧长,就要凑足帧长,加之现在有的MP3编码器(MP3 pro)可以在这个位置写入自己辅助信息来提升MP3的高频谱增强对音乐的细节表现,不处理辅助位(况且绝大多数MP3不是用MP3 pro压缩的)不影响解码结果,所以这里对填充数据直接作舍弃处理。class Layer3申明了Synthesis类对象objFilter,调用objFilter.synthesisSubBand(floatSamples, ch)完成多相合成滤,请注意这一句是放在3重循环体内的。

 

封装多相合成滤波类class Synthesis   多相合成滤波过程示意图如下:

上图清晰地示意出一个声道的多相合成滤波的过程(图中bit应为float,DCT表示矩阵运算),共5个步骤。

 

      1.移位 (Shift)确保每一次将数据写入FIFO队列内的正确位置,首先计算出本次写入到FIFO队列的首址。

 

      2.矩阵运算 (Matrixing) 将32个输入数据变换为64个输出数据。为了提高程序运行的效率,将64个输出数据直接写进FIFO队列,这64个数据在FIFO中是邻接的,本次写入的首址由第1步计算得到。矩阵运算的快速算法请参考《 MP3解码之DCT(32→64)快速算法的展开 》,该贴详细讲解了各点DCT快速算法代码编写和展开式。矩阵运算的快速算法DCT(32->64)推导过程如下:

 3.构建U向量   计算u_vector可以用下述代码实现:

// Build the U vector
for (i = 0; i < 512; i += 64) {k = i << 1;for (j = 0; j < 32; j++) {u_vector[i + j]      = curfifo[(off + k + j) & 0x3FF];u_vector[i + j + 32] = curfifo[(off + k + j + 96) & 0x3FF];}
}

   构建U向量就是把FIFO队列中的数据抽取一部分出来写入u_vector。这里的off由第1步计算得到,从这段代码可以看出一个off值对应u_vector中的16个下标值。u_vector用于第4步的加窗运算,同样是出于运行效率的考虑,u_vector可以省掉,第4步时根据“u_vector中的16个下标值规律” 直接到FIFO队列中相应的位置去取数。找出u_vector下标值的规律之后,再将窗口系数按这个规律打乱顺序重新排列。是怎样的规律自己琢磨下上面的代码就看出来了,我相信你的观察能力哈。省掉u_vector带来的好处一是可以减少运算,二是可以减小存储开销,有好处滴~

 

      4.加窗运算 (Dewindowing)  这是滤波的最后一步。如果没有省掉u_vector,这一步应该这么算:

<SPAN>// Dewindowing
for (i = 0; i < 512; i++)u_vector[i] *= dewin[i];</SPAN>


   其中的dewin[i]是窗口系数D[i]*32768,窗口系数D[]的512个常量由解码规范的文档中给出,如果要对解码器加入多段频率均衡,就在这一步进行。通过加窗后得到的u_vector用于计算PCM样本。

 

      5.计算32个PCM样本   如果没有省掉u_vector,这一步应该这么算:

<SPAN>// Calculate and output 32 samples
for (i = 0; i < 32; i++) {sum = 0.0f;for (j = 0; j < 512; j += 32)sum += u_vector[j + i];PCMi = sum > 32767 ? 32767 : (sum < -32768 ? -32768 : (int)sum);pcmbuf[idx]     = (byte)(PCMi >>> 1);pcmbuf[idx + 1] = (byte)(PCMi >>> 9);idx += idx_step;
}</SPAN>


     计算得到的PCM样本暂存到pcmbuf[],解码完一帧将PCM数据送入音频输出模块播放,解码一帧的任务就结束了。采用16位PCM输出的话,一个PCM样本值占2字节,输出的是立体声的话要求左右声道的PCM样本值交替排列在pcmbuf[]内,上述代码中idx完成“交替”作用。

      一个粒度组内的一个声道的PCM样本数为18*32=576个,立体声编码的MP3一帧的PCM样本数为2*2*576=2304个,字节数为2*2304=4608字节。计算32个PCM样本以极高的频度被调用(解码一帧被调用72次),JAVA没有宏定义,出于效率考虑,没有编写计算PCM样本的方法供调用,而是在需要计算的每一处单独放入这部分进代码。取消计算32个PCM的调用、取消掉u_vector、矩阵运算采用了展开式,这3方面的原因导致class Synthesis的代码看起来很长,乍一看也复杂得让人理不清头绪。不过,换来的运行效率提大幅度升,通过对比测试,这3项优化措施使解码速度提升30%以上 ,所以这里对代码做这样的优化很成功。前面讲到的哈夫曼解码、逆量化(用查表法)和重排序、IMDCT等模块,编写代码时也都充分考虑到了提高运行效率,所以这个用JAVA写的MP3解码器速度是很快的。我说她解码快,是同MADLIB和MPG123(名气比较大的开源的用C写的MP3解码器)实测对比的结论,都不用音频输出模块,赛对同一MP3的解码时间,我的解码器一胜一负,不错的成绩。自己赞一下~

      多相合成滤波Synthesis.java源码较长,不贴这了。需要的话到 / 下载。


 

更多推荐

(十四)用JAVA编写MP3解码器——多相合成滤波

本文发布于:2024-02-13 10:20:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1758241.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多相   解码器   十四   JAVA

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!