论坛首页 Java企业应用论坛

(十)用JAVA编写MP3解码器——逆量化和重排序

浏览 8303 次
该帖已经被评为精华帖
作者 正文
   发表时间:2010-08-22   最后修改:2010-10-07

解码一帧Layer3第4步:逆量化和重排序 -- requantizer方法

 

      1.逆量化  逆量化的功能是把哈夫曼解码得到的值还原成576个频谱值。长块、短块分别用不同的公式:

其中ISi为第i个哈夫曼码值,XRi为第i个逆量化值。

 

  长块  576个频谱值分为21个增益因子(scale factor)频带(band),由intWidthLong[]保存这21个频带的子带(subband)宽度。intWidthLong[]的初始化见《(六)用JAVA编写MP3解码器——帧数据结构》Layer3.java第203--204行。

 

  短块  576个频谱值分为13个增益因子频带,由intWidthShort[]保存这13个频带的子带宽度。每一个频带包含3个窗。intWidthShort[]的初始化见《(六)用JAVA编写MP3解码器——帧数据结构》Layer3.java第205--206行。

 

  混合块  576个频谱值分为17个增益因子频带。前8个频带为长块,各用一个增益因子逆量化,这8个子带的总和为36;后9个频带为短块。

      子带可以理解为一个增益因子逆量化频率线的条数(Subband: Subdivision of the audio frequency band)。

 

      2.重排序  经逆量化后得到的频谱值不是按时间顺序排列的,在编码的MDCT过程中,对于长块产生的频谱值先子带后频率排列;对于短块,产生的频谱值按子带、窗、频率的顺序排列。为了提供哈夫曼编码效率,短窗中的数据被重新排列,按照子带、频率、窗的顺序排列。重排序就是将频率值按时间顺序重新排列,重排序按窗、子带、频带顺序进行排序。

 

      将逆量化和重排序放一块儿同时进行可以减少函数调用,更重要的是可以减少循环,使程序运行效率更高一点。这样带来的不好的一面是代码看起来很复杂,让人理不清头绪。


     从逆量化公式可以看出这一模块要进行大量的浮点运算,为减少浮点运算提高运行效率,逆量化采用查表法实现,用到的floatPow2和floatPowIS两张表在构造方法中已经在Layer3的构造方法内初始化过了,公式中用到的其它输入变量的值已经在解码帧边信息这一步初始化过了,以输入变量的值作为索引直接查表,就得到逆量化的结果了;逆量化得到的576个频谱值保存到形参的xr[]中。

 

class Layer3的requantizer方法源码如下:

	//4.
	//>>>>REQUANTIZATION & REORDER=============================================
	private static float[][][] xr;		// [2][32][18];
	private static float[] floatPow2;	// [256 + 118 + 4]
	private static float[] floatPowIS;	// [8207]
	private static int[] intWidthLong;	// [22] 长块的增益因子频带(用一个增益因子逆量化频率线的条数)
	private static int[] intWidthShort;	// [13] 短块的增益因子频带
	private static int rzero_bandL;
	private static int[] rzero_bandS = new int[3];
	/*
	 * ISO/IEC 11172-3 ANNEX B,Table 3-B.6. Layer III Preemphasis
	 */
	private final int[] pretab = {0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0};

	/*
	* requantizer方法:逆量化并对短块(纯短块和混合块中的短块)重排序
	*
	* 在 requantizer 方法内赋值的变量:
	* rzero_bandL -- 长块非零哈夫曼值的频带数,用于强度立体声(intensity stereo)处理
	* rzero_bandS -- 短块非零哈夫曼值的频带数,用于强度立体声(intensity stereo)处理
	* nozero_index -- 非零哈夫曼值的"子带"数
	*
	* Layer3 逆量化公式(ISO/IEC 11172-3, 2.4.3.4.7.1)
	* LONG blocks:
	* xr[i] = sign(is[i]) * abs(is[i])^(4/3) * 2^((1/4) * (global_gain - 210)) *
	*		2^-((scalefac_scale + 1) / 2 * (scalefac_l[sfb] + preflag * pretab[sfb]))
	* SHORT blocks:
	* xr[i] = sign(is[i]) * abs(is[i])^(4/3) * 2^((1/4) * (global_gain - 210 - 8 *
	*		subblock_gain[w])) * 2^-((scalefac_scale + 1) / 2 * scalefac_s[sfb][w])
	*/
	private void requantizer(int ch, int gr, float[][] xr) {
		int bi, wi, band, width, pre, v, is_idx = 0, sb = 0, ss = 0;
		float requ_v;
		int sb_start, ss_start; // 用于计算短块重排序后的下标
		final GRInfo gr_info = objSI.ch[ch].gr[gr];
		final int preflag = gr_info.preflag;
		final int shift = 1 + gr_info.scalefac_scale;
		final int l[] = scfL[ch];
		final int s[][] = scfS[ch];
		final int[] sub_gain = gr_info.subblock_gain;
		sub_gain[0] <<= 3;
		sub_gain[1] <<= 3;
		sub_gain[2] <<= 3;
		int pow2_idx = 256 - gr_info.global_gain;
		if( objHeader.isMSStereo())
			pow2_idx += 2;	// ms_stereo,相当于除以根2

		// pure SHORT blocks:
		// window_switching_flag = 1, block_type = 2, mixed_block_flag=0

		if ((gr_info.window_switching_flag == 1) && (gr_info.block_type == 2)) {
	          if (gr_info.mixed_block_flag == 1) {
				//-------------------------------------------------------------
				// 混合块
				//-------------------------------------------------------------
				rzero_bandS[0] = rzero_bandS[1] = rzero_bandS[2] = 2;
				rzero_bandL = -1;
				/*
				 * 混合块的前8个频带是长块。 前8块各用一个增益因子逆量化,这8个增益因子
				 * 的频带总和为36, 这36条频率线用长块公式逆量化。
				 */
				for (band = 0; band < 8; band++) {
					pre = (preflag == 0) ? 0 : pretab[band];
					requ_v = floatPow2[pow2_idx + ((l[band] + pre) << shift)];
					width = intWidthLong[band];
					for (bi = 0; bi < width; bi++) {
						v = is[is_idx];
						if (v < 0) {
							xr[sb][ss] = -requ_v * floatPowIS[-v];
							rzero_bandL = band;
						}
						else if (v > 0) {
							xr[sb][ss] = requ_v * floatPowIS[v];
							rzero_bandL = band;
						}
						else
							xr[sb][ss] = 0;
						is_idx++;
						if (++ss == 18) {
							ss = 0;
							sb++;
						}
					}
				}

				sb_start = 2; ss_start = 0; // 为短块重排序准备好下标

				/*
				 * 混合块的后9个频带是被加窗的短块,其每一块内3个值的增益因子频带相同。
				 * 后9块增益因子对应的频率子带值为intWidthShort[3..11]
				 */
				for (band = 3; band < 12; band++) {
					width = intWidthShort[band];
					for (wi = 0; wi < 3; wi++) {
						requ_v = floatPow2[pow2_idx + sub_gain[wi] + (s[wi][band] << shift)];
						/*
						 * 计算当前窗wi内重排序后的下标
						 */
						sb = sb_start;
						ss = ss_start + wi;
						if (ss >= 18) {
							ss -= 18;
							sb++;
						}

						for (bi = 0; bi < width; bi++) {
							v = is[is_idx];
							if (v < 0) {
								xr[sb][ss] = -requ_v * floatPowIS[-v];
								rzero_bandS[wi] = band;
							}
							else if (v > 0) {
								xr[sb][ss] = requ_v * floatPowIS[v];
								rzero_bandS[wi] = band;
							}
							else
								xr[sb][ss] = 0;
							is_idx++;
							ss += 3;
							if (ss >= 18) {
								ss -= 18;
								sb++;
							}
						}
					}
					/*
					 * 调整下标,准备开始在下一增益因子频带内重排序
					 */
					ss -= 2; 	//wi=2时已执行过ss += 3
					if (ss < 0) {
						ss = 0;
						sb--;
					}
					sb_start = sb; ss_start = ss;
				}
			} else {
				//-------------------------------------------------------------
				// 纯短块
				//-------------------------------------------------------------
				rzero_bandS[0] = rzero_bandS[1] = rzero_bandS[2] = rzero_bandL = -1;
				sb_start = ss_start = 0;
				for (band = 0; band < 12; band++) {
					width = intWidthShort[band];
					for (wi = 0; wi < 3; wi++) {
						requ_v = floatPow2[pow2_idx + sub_gain[wi] + (s[wi][band] << shift)];
						sb = sb_start;
						ss = ss_start + wi;
						if (ss >= 18) {
							ss -= 18;
							sb++;
						}
						for (bi = 0; bi < width; bi++) {
							v = is[is_idx];
							if (v < 0) {
								xr[sb][ss] = -requ_v * floatPowIS[-v];
								rzero_bandS[wi] = band;
							}
							else if (v > 0) {
								xr[sb][ss] = requ_v * floatPowIS[v];
								rzero_bandS[wi] = band;
							}
							else
								xr[sb][ss] = 0;
							is_idx++;
							ss += 3;
							if (ss >= 18) {
								ss -= 18;
								sb++;
							}
						}
					}
					ss -= 2;
					if (ss < 0) {
						ss = 0;
						sb--;
					}
					sb_start = sb; ss_start = ss;
				}
			}
			rzero_bandS[0]++; rzero_bandS[1]++; rzero_bandS[2]++; rzero_bandL++;
		} else {
			//-------------------------------------------------------------
			// 长块
			//-------------------------------------------------------------
			rzero_bandL=-1;
			for (band = 0; band < 21; band++) {
				pre = (preflag == 0) ? 0 : pretab[band];
				requ_v = floatPow2[pow2_idx + ((l[band] + pre) << shift)];
				width = intWidthLong[band];
				for (bi = 0; bi < width; bi++) {
					v = is[is_idx];
					if (v < 0) {
						xr[sb][ss] = -requ_v * floatPowIS[-v];
						rzero_bandL = band;
					}
					else if (v > 0) {
						xr[sb][ss] = requ_v * floatPowIS[v];
						rzero_bandL = band;
					}
					else
						xr[sb][ss] = 0;
					is_idx++;
					if (++ss == 18) {
						ss = 0;
						sb++;
					}
				}
			}
			rzero_bandL++;
		}

		/*
		 * is_index之后xr[][]清零
		 *
		 * iso11172-3上指出应该用"缺省值"逆量化剩余部分,"缺省值"是0还是pow2[pow2_idx]?
		 * 取pow2[pow2_idx]时有的歌曲有爆音,应该取0才对?
		 */
		for (; sb < 32; sb++) {
			for (; ss < 18; ss++)
				xr[sb][ss] = 0;
			ss = 0;
		}
	}
	//<<<<REQUANTIZATION & REORDER=============================================

 

      官方文档中没有实现重排序方法的描述,如何进行重排序手册是对我们没有帮助的。我在调试运行时打印出纯短块的576条频率线重排序前后下标的对应关系,想了解重排序细节请先看附件里的pure_SHORT_reorder.txt,琢磨出重排序前后下标的变化规律,再回过来看源码就容易理解其流程了。逆量化究竟如何进行,我这儿就不作讲解了,相信你能从这个.txt文件中琢磨出来。

 

上一篇:(九)用JAVA编写MP3解码器——哈夫曼解码

下一篇:(十一)用JAVA编写MP3解码器——立体声处理

  • 大小: 19.4 KB
   发表时间:2010-08-23  
写的真棒,楼主一定要坚持完整更新到最后一篇!
0 请登录后投票
   发表时间:2010-08-24  
问一下,支持 VBR 吗?
0 请登录后投票
   发表时间:2010-08-24  
helloghui 写道
问一下,支持 VBR 吗?

支持VBR。VBR除第一帧外,其余帧的解码与CBR、ABR是相同的。
0 请登录后投票
   发表时间:2010-08-25  
楼主是不是可以出一个完整的Java MP3解码的开源库?
0 请登录后投票
   发表时间:2010-08-25  
厉害呀!希望能继续更新到最后!
0 请登录后投票
   发表时间:2010-08-25  
期待最后一篇
0 请登录后投票
   发表时间:2010-08-25  

被踩。可能不该写下去了。。。

  • 大小: 7.4 KB
0 请登录后投票
   发表时间:2010-08-26   最后修改:2010-08-26
lfp001 写道

被踩。可能不该写下去了。。。

有些人的逻辑你可以不用理会,大部分人都是支持你的。LZ加油

0 请登录后投票
   发表时间:2010-08-27   最后修改:2010-08-27
投隐藏的什么居心啊。 说起来中国软件不行, 有人公开自己努力写的代码, 讲解原理居然这么邪恶的乱投。

肉饼真应该把随意投别人隐藏的帐号晒一晒。


可能有人觉得代码的命名不规范, 我估计这些命名应该来自规范里的缩写。  通常规范里会定义一堆的缩写, 简易标示, 也是用来指导写代码的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics