解码一帧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编写的解码器是一种基于Java编程语言实现的软件组件,专门用于解析和播放MP3音频文件。在本文中,我们将深入探讨Java MP3解码器的原理、实现细节以及其在面试和项目中的应用。 首先,理解MP3格式是至关重要的。...
标签"java mp3 无JMF 解码器"进一步强调了这个解码器的关键特性:它是用Java语言编写的,专注于MP3格式,且不依赖JMF。这使得它具有跨平台性,可以在任何支持Java的系统上运行。 压缩包内的文件"jmp123_400_utf8_...
总结来说,Java MP3音频文件解码器是一个独立的、用纯Java编写的工具,用于将MP3文件转换为可处理的原始音频数据。它的设计和实现涉及了音频编码原理、数据处理优化以及Java编程技术,对于理解和处理音频数据的...
《纯JAVA的MP3解码器jmp:深入解析与应用》 MP3解码器是数字音频处理领域的重要工具,它能够将存储的MP3格式音频数据转化为人类可听的声音信号。在众多的MP3解码器中,“jmp123”以其独特的纯JAVA实现和出色的性能...
《基于通用可编程GPU的视频编解码器——架构、算法与实现》 在现代数字媒体处理中,视频编解码技术扮演着至关重要的角色。随着高性能计算平台的发展,通用可编程图形处理器(GPU)逐渐从图形渲染领域扩展到计算密集...
3. **解码流程**:详细解析MP3解码器的工作流程,包括逆熵编码、反量化、逆离散余弦变换、时域重构等步骤,以及如何处理帧同步和错误隐藏。 4. **Java实现**:Java是一种跨平台的编程语言,适合开发多媒体应用。...
2. **尺度因子解码**:基于帧头信息,解码器计算出尺度因子,这是用于逆量化的关键参数之一。 3. **哈夫曼解码**:通过对已压缩的音频数据进行哈夫曼解码,还原出原本的音频数据。 4. **逆量化**:此步骤通过逆向...
读取MP3文件需要使用Java的I/O流,如FileInputStream和BufferedInputStream,来高效地读取和处理文件内容。 8. **用户界面设计**: 使用JavaFX或Swing库可以创建包含播放/暂停按钮、音量控制器、播放列表等元素的...
MP3播放器与MP3解码器是音频处理领域中的两个关键组件,它们在数字音乐体验中扮演着重要角色。MP3是一种广泛使用的音频压缩格式,它能够在保持相对高质量的同时,大幅度减小音频文件的大小,便于存储和分享。 ### ...
《使用Java编写MP3播放器》是一份关于利用Java编程语言实现MP3音频解码的文档。MP3作为广泛使用的音频压缩格式,其解码技术是数字音频处理领域的重要组成部分。该文档旨在介绍如何利用Java编写一个MP3解码器,并提供...
7. **反量化和IDCT**:结合重排序和反窗函数的结果,解码器进行逆量化和逆离散余弦变换,将频域信号转换回时域信号。 8. **重采样和滤波**:如果原始采样率与目标采样率不同,解码器会进行重采样,确保输出信号的...
大体流程是从SD卡读取MP3格式的文件,然后提取里边音频数据,经过哈夫曼解码、哈夫曼信息解码、尺度因子解码、然后逆量化、重排序,立体声解码,频率翻转、合成多项滤波,最终生成PCM码流,通过IIS将数据丢给DAC放大...
对于初学者来说,理解和编写自定义的编解码器可能会遇到一些挑战。本文将深入探讨mina编解码器的工作原理,提供一个典型编解码器的示例,并解析其代码。 1. **mina框架基础** - Mina提供了一个高效的、事件驱动的...
在本文中,我们将深入探讨MP3解码器的工作原理、千千静听和酷狗这两款流行的音乐播放器如何利用解码库来播放MP3文件,以及libmad这个特定的解码库。 首先,我们来看看MP3解码的基本过程。当一个MP3文件被创建时,...
在本文中,我们将深入探讨如何使用Java编程语言来实现一个MP3播放器。MP3播放器是音频处理软件,能够读取、解码并播放MP3格式的音频文件。Java以其跨平台的特性,成为开发此类应用的理想选择。让我们一起探讨实现这...
MP3解码过程涉及到一系列复杂的数字信号处理步骤,包括帧解包、频谱分析、逆量化、重采样等。JMP123库实现了这些算法,能够高效地解析MP3文件中的压缩音频数据,并将其转换为可播放的PCM(脉冲编码调制)格式。这个...
在描述中提到的VC下的C工程源码,意味着这个编解码器是用C语言编写,并且可以在Visual C++环境下编译和运行。这对于软件开发者来说非常有用,因为C语言的源代码通常更容易理解和修改,便于进行二次开发和定制。 ...
总的来说,"jmf_mp3解码器"结合JLayer1.0.1的使用,为Java开发者提供了一个跨平台的解决方案,使得在Java应用程序中播放MP3文件变得简单而有效。这种技术的应用场景广泛,可以用于开发音乐播放器、多媒体教学软件、...
- 在使用`silk2mp3`解码器时,确保所有相关组件(lame.exe和silk_v3_decoder.exe)都在同一目录下,否则程序可能无法正常工作。 - 转换过程中可能涉及版权问题,确保你有权利处理和转换这些音频文件。 - 音频质量和...