文档章节

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

暗之幻影
 暗之幻影
发布于 2015/01/04 15:29
字数 2044
阅读 16
收藏 0

解码一帧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解码器——音频输出

本文转载自:http://lfp001.iteye.com/blog/749974

暗之幻影
粉丝 20
博文 377
码字总数 71245
作品 0
南京
高级程序员
私信 提问
(五)用JAVA编写MP3解码器——解析文件信息

前文提到解析MP3标签,程序源码中也已经出现了调用解析MP3标签、打印MP3文件信息的功能,这儿先说说MP3文件信息的解析。 解析MP3的文件信息对MP3解码器来说只是一个附加功能,如果不加入这部...

暗之幻影
2015/01/04
51
0
疯狂Spring Cloud连载(11)——Feign的编码器与解码器

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/23
2.8K
5
《Java程序员由笨鸟到菜鸟》电子版书正式发布,欢迎大家下载

在众多朋友的支持和鼓励下,《Java程序员由菜鸟到笨鸟》电子版终于和大家见面了。本电子书涵盖了从java基础到javaweb开放框架的大部分内容。在编写的过程中,难免会出现一些错误,希望大家能...

长平狐
2012/11/12
243
0
《Java程序员由笨鸟到菜鸟》电子版书正式发布,欢迎大家下载

在众多朋友的支持和鼓励下,《Java程序员由菜鸟到笨鸟》电子版终于和大家见面了。本电子书涵盖了从java基础到javaweb开放框架的大部分内容。在编写的过程中,难免会出现一些错误,希望大家能...

长平狐
2012/11/12
142
0
Java程序员从笨鸟到菜鸟全部博客目录【2012年十一月七日更新】

本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188 大学上了一年半,接触java也一年半了,虽然中间也有其他东西的学习,但是还是以java为主路线,想想这一年半,...

长平狐
2012/11/12
208
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周六乱弹 —— 早上儿子问我他是怎么来的

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @凉小生 :#今日歌曲推荐# 少点戾气,愿你和这个世界温柔以待。中岛美嘉的单曲《僕が死のうと思ったのは (曾经我也想过一了百了)》 《僕が死の...

小小编辑
29分钟前
25
0
Excption与Error包结构,OOM 你遇到过哪些情况,SOF 你遇到过哪些情况

Throwable 是 Java 中所有错误与异常的超类,Throwable 包含两个子类,Error 与 Exception 。用于指示发生了异常情况。 Java 抛出的 Throwable 可以分成三种类型。 被检查异常(checked Exc...

Garphy
今天
9
0
计算机实现原理专题--二进制减法器(二)

在计算机实现原理专题--二进制减法器(一)中说明了基本原理,现准备说明如何来实现。 首先第一步255-b运算相当于对b进行按位取反,因此可将8个非门组成如下图的形式: 由于每次做减法时,我...

FAT_mt
昨天
6
0
好程序员大数据学习路线分享函数+map映射+元祖

好程序员大数据学习路线分享函数+map映射+元祖,大数据各个平台上的语言实现 hadoop 由java实现,2003年至今,三大块:数据处理,数据存储,数据计算 存储: hbase --> 数据成表 处理: hive --> 数...

好程序员官方
昨天
7
0
tabel 中含有复选框的列 数据理解

1、el-ui中实现某一列为复选框 实现多选非常简单: 手动添加一个el-table-column,设type属性为selction即可; 2、@selection-change事件:选项发生勾选状态变化时触发该事件 <el-table @sel...

everthing
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部