文档章节

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

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

解码一帧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解码器——立体声处理

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

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

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

暗之幻影
2015/01/04
61
0
Java并发(2)- 聊聊happens-before

引言 上一篇文章聊到了Java内存模型,在其中我们说JMM是建立在happens-before(先行发生)原则之上的。 为什么这么说呢?因为在Java程序的执行过程中,编译器和处理器对我们所写的代码进行了...

knock_小新
2018/07/19
0
0
(深度)Java多线程系列(6):volatile 关键字的使用

原文地址:https://segmentfault.com/a/1190000008637202 作为 Java 语言的一个关键字,被看作是轻量级的 (锁)。虽然 只具有的部分功能,但是一般使用 会比使用 更有效率。在编写多线程程序...

芝麻粒儿
03/24
0
0
Java虚拟机基础——1Java的内存模型

最近和几个之前一起做安卓的朋友喝酒,他最近在研究JVM,我们就简单的讨论了起来,他比我研究的深很多,我也不甘堕落,自己也开始研究了一下,写了4篇文章整理了一下自己的思路,Java虚拟机整...

隔壁老李头
2018/10/03
0
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
3.1K
5

没有更多内容

加载失败,请刷新页面

加载更多

Android面试常客之Handler全解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/fnhfire_7030/article/details/79518819 前言:又到了一年...

shzwork
7分钟前
0
0
position sticky 定位

本文转载于:专业的前端网站➫position sticky 定位 1、兼容性 https://caniuse.com/#search=sticky chrome、ios和firefox兼容性良好。 2、使用场景 sticky:粘性。粘性布局。 在屏幕范围内时...

前端老手
14分钟前
1
0
CentOS 7 yum 安装 PHP7.3 教程

参考:https://www.mf8.biz/centos-rhel-install-php7-3/ 1、首先安装 EPEL 源: yum install epel-release 安装 REMI 源: yum install http://rpms.remirepo.net/enterprise/remi-release......

dragon_tech
29分钟前
1
0
Linux物理网卡聚合及桥接

Linux内部实现的bridge可以把一台机器上的多张网卡桥接起来,从而把自己作为一台交换机。同时,LInux bridge还支持虚拟端口,即桥接的不一定都是物理网卡接口,还可以是虚拟接口。目前主要表...

xiangyunyan
29分钟前
1
0
一起来学Java8(一)——函数式编程

在这篇文章中,我们将了解到在Java8下如何进行函数式编程。 函数式编程 所谓的函数式编程就是把函数名字当做值进行传递,然后接收方拿到这个函数名进行调用。 首先来看下JavaScript如何进行函...

猿敲月下码
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部