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

2015/01/04 15:29
阅读数 53

解码一帧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);
				else
					getScaleFactors_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 SAMPLES
				int 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 SAMPLES

				for (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   多相合成滤波过程示意图如下:

 

Poly phase synthesis filterband

 

      上图清晰地示意出一个声道的多相合成滤波的过程(图中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)推导过程如下:

矩阵运算的快速算法DCT(32->64)

 

 

conclusion

 

 

     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,这一步应该这么算:

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

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

 

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

// 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;
}

      计算得到的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源码较长,不贴这了。需要的话到 http://jmp123.sf.net/ 下载。

 

上一篇: (十三)用JAVA编写MP3解码器——IMDCT快速算法

下一篇: (十五)用JAVA编写MP3解码器——音频输出

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部