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

2023-11-20 16:40

本文主要是介绍(十四)用JAVA编写MP3解码器——多相合成滤波,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

解码一帧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   多相合成滤波过程示意图如下:

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

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

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


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

 

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

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


     计算得到的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解码器——多相合成滤波的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/396007

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

Open3D 基于法线的双边滤波

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 输入参数: 输出参数: 参数影响: 2.2完整代码 三、实现效果 3.1原始点云 3.2滤波后点云 Open3D点云算法汇总及实战案例汇总的目录地址: Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客 一、概述         基于法线的双边

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听