文档

Java™教程
隐藏目录
使用控制处理音频
路径:音频

使用控件处理音频

之前的部分讨论了如何播放或捕获音频样本。隐含的目标是尽可能忠实地传递样本,除了可能将样本与其他音频线的样本混合之外,不进行修改。然而,有时您希望能够修改信号。用户可能希望声音更大、更小、更丰满、更具混响、音高更高或更低等等。本页面讨论了Java Sound API提供的这些信号处理功能。

有两种应用信号处理的方法:

本页面更详细地讨论了第一种技术,因为第二种技术没有特殊的API。

控件简介

混音器的一些或所有线路可以具有各种类型的信号处理控件。例如,用于音频捕获的混音器可以具有带有增益控制的输入端口,并带有增益和平衡控制的目标数据线。用于音频播放的混音器可以在其源数据线上具有采样率控制。在每种情况下,这些控件都通过Line接口的方法访问。

因为Mixer接口扩展了Line接口,所以混音器本身可以具有自己的一组控件。这些控件可以作为主控件,影响所有混音器的源或目标线路。例如,混音器可以具有主增益控制,其分贝值被添加到其目标线路上各个增益控制的值中。

混音器自身的其他控件可能会影响一条特殊的线路,既不是源也不是目标线路,混音器在其处理中使用该线路。例如,全局混响控制可能选择应用于输入信号混合物的混响类型,然后将这个“湿”(带有混响的)信号混合回“干”信号,然后传递到混音器的目标线路。

如果混音器或其任何线路具有控件,您可能希望通过程序用户界面中的图形对象公开这些控件,以便用户可以根据需要调整音频特性。这些控件本身并不是图形的;它们只允许您检索和更改其设置。您可以根据需要决定在程序中使用何种图形表示(滑块、按钮等)。

所有的控件都是Control的具体子类实现。许多典型的音频处理控件可以通过基于数据类型(如布尔、枚举或浮点数)的Control的抽象子类来描述。例如,布尔控件表示二进制状态控件,如静音或混响的开/关控件。另一方面,浮点控件非常适合表示连续可变的控件,如声像、平衡或音量。

Java Sound API指定了以下Control的抽象子类:

上述每个Control的子类都有适用于其底层数据类型的方法。大多数类包括设置和获取控件当前值的方法,获取控件标签等。

当然,每个类都有特定于它和类所代表的数据模型的方法。例如,EnumControl有一个方法可以让您获取其可能值的集合,FloatControl允许您获取其最小和最大值,以及控件的精度(增量或步长)。

每个Control的子类都有对应的Control.Type子类,其中包含用于标识特定控件的静态实例。

下表显示了每个Control子类、对应的Control.Type子类以及表示特定控件类型的静态实例:

Control Control.Type Control.Type 实例
BooleanControl BooleanControl.Type MUTE – 静音状态
APPLY_REVERB – 混响开关
CompoundControl CompoundControl.Type (无)
EnumControl EnumControl.Type REVERB – 混响设置访问(每个是ReverbType的实例)
FloatControl FloatControl.Type AUX_RETURN – 线路的辅助返回增益
AUX_SEND – 线路的辅助发送增益
BALANCE – 左右音量平衡
MASTER_GAIN – 线路的整体增益
PAN – 左右位置
REVERB_RETURN – 混响后增益
REVERB_SEND – 混响前增益
SAMPLE_RATE – 播放采样率
VOLUME – 线路音量

Java音频API的实现可以在其混音器和线路上提供任何或所有这些控件类型。它还可以提供在Java音频API中未定义的其他控件类型。这些控件类型可以通过这四个抽象子类的具体子类或不继承这四个抽象子类的其他Control子类来实现。应用程序可以查询每个线路以确定它支持哪些控件。

获取具有所需控件的线路

在许多情况下,应用程序将简单地显示由所讨论的线路支持的任何控件。如果该线路没有任何控件,那就这样吧。但是如果找到具有特定控件的线路很重要怎么办?在这种情况下,您可以使用Line.Info来获取具有正确特性的线路,如先前在获取所需类型的线路中所述。

例如,假设您喜欢一个输入端口,该端口允许用户设置音频输入的音量。以下代码摘录显示了如何查询默认混音器以确定是否具有所需端口和控件:

Port lineIn;
FloatControl volCtrl;
try {
  mixer = AudioSystem.getMixer(null);
  lineIn = (Port)mixer.getLine(Port.Info.LINE_IN);
  lineIn.open();
  volCtrl = (FloatControl) lineIn.getControl(
FloatControl.Type.VOLUME); // 假设getControl调用成功,我们现在有了LINE_IN VOLUME控件。 } catch (Exception e) { System.out.println("Failed trying to find LINE_IN" + " VOLUME control: exception = " + e); } if (volCtrl != null) // ...

从线路获取控件

需要在其用户界面中公开控件的应用程序可以简单地查询可用的线路和控件,然后为感兴趣的每条线路的每个控件显示适当的用户界面元素。在这种情况下,程序的唯一任务是为用户提供控件的“句柄”;而不是知道这些控件对音频信号做什么。只要程序知道如何将线路的控件映射到用户界面元素,Java Sound API的MixerLineControl架构通常会处理其余部分。

例如,假设您的程序播放声音。您正在使用一个SourceDataLine,如先前在获取所需类型的线路中所述。您可以通过调用以下Line方法来访问线路的控件:

Control[] getControls()

然后,对于返回的控件数组中的每个控件,您可以使用以下Control方法来获取控件的类型:

Control.Type getType()

了解特定的Control.Type实例后,您的程序可以显示相应的用户界面元素。当然,为特定的Control.Type选择“相应的用户界面元素”取决于程序采用的方法。一方面,您可以使用相同类型的元素来表示相同类别的Control.Type实例。这将要求您使用例如Object.getClass方法查询Control.Type实例的。假设结果匹配BooleanControl.Type。在这种情况下,您的程序可能会显示一个通用的复选框或切换按钮,但如果其类匹配FloatControl.Type,则您可能会显示一个图形滑块。

另一方面,您的程序可以区分不同类型的控件 - 即使是同一类的控件 - 并为每个控件使用不同的用户界面元素。这需要您测试 Control's getType 方法返回的 实例。例如,如果类型匹配 BooleanControl.Type.APPLY_REVERB,您的程序可以显示一个复选框;而如果类型匹配 BooleanControl.Type.MUTE,您可以显示一个切换按钮。

使用控件改变音频信号

现在您知道如何访问控件并确定其类型,本节将介绍如何使用 Controls 来改变音频信号的方面。本节不涵盖所有可用的控件;相反,它提供了一些示例,以帮助您入门。这些示例包括:

假设您的程序已经访问了所有混音器、它们的线路和线路上的控件,并且它有一个数据结构来管理控件与其对应的用户界面元素之间的逻辑关联。然后,将用户对这些控件的操作转换为相应的 Control 方法就变得相对简单。

以下小节描述了必须调用的一些方法来影响特定控件的更改。

控制线路的静音状态

控制任何线路的静音状态只是简单地调用以下 BooleanControl 方法:

void setValue(boolean value)

(假设程序通过引用其控制管理数据结构知道静音是 BooleanControl 的一个实例。)要静音通过线路传递的信号,程序调用上述方法,将值设置为 true。要关闭静音,允许信号通过线路流动,程序将参数设置为 false

更改线路的音量

假设您的程序将特定的图形滑块与特定线路的音量控制相关联。使用以下 FloatControl 方法设置音量控制(即 FloatControl.Type.VOLUME)的值:

void setValue(float newValue)

检测到用户移动滑块后,程序获取滑块的当前值,并将其作为参数 newValue 传递给上述方法。这将更改流经“拥有”该控件的线路的音量。

在各种混响预设之间进行选择

假设我们的程序有一个带有类型为EnumControl.Type.REVERB的控制器的混音器。调用EnumControl方法:

java.lang.Objects[] getValues()

该控制器将产生一个ReverbType对象数组。如果需要,可以使用以下ReverbType方法访问每个对象的特定参数设置:

int getDecayTime() 
int getEarlyReflectionDelay() 
float getEarlyReflectionIntensity() 
int getLateReflectionDelay() 
float getLateReflectionIntensity() 

例如,如果程序只想要一个听起来像洞穴的单一混响设置,可以迭代ReverbType对象,直到找到一个getDecayTime返回大于2000的值的对象。关于这些方法的详细解释,包括代表性返回值的表格,请参阅javax.sound.sampled.ReverbType的API参考文档。

通常,一个程序将为getValues方法返回的数组中的每个ReverbType对象创建一个用户界面元素,例如单选按钮。当用户点击其中一个单选按钮时,程序调用EnumControl方法

void setValue(java.lang.Object value) 

其中value被设置为对应于新选定按钮的ReverbType。然后,通过拥有此EnumControl的线路发送的音频信号将根据构成控制的当前ReverbType的参数设置进行混响处理(即在setValue方法的value参数中指定的特定ReverbType)。

因此,从我们的应用程序的角度来看,使用户能够从一个混响预设(即ReverbType)转换到另一个预设只是将getValues返回的数组的每个元素连接到不同的单选按钮。

直接操作音频数据

Control API允许Java Sound API的实现或混音器的第三方提供商通过控制提供任意类型的信号处理。但是如果没有混音器提供您所需的信号处理类型怎么办?这将需要更多的工作,但您可能能够在程序中实现信号处理。因为Java Sound API允许您以字节数组的形式访问音频数据,所以您可以按照任何您选择的方式更改这些字节。

如果您正在处理传入的声音,可以从TargetDataLine读取字节,然后对其进行操作。一个算法上的简单示例,可以产生声音上的有趣结果是通过将帧按相反的顺序排列来倒放声音。这个简单的示例可能对您的程序没有太多用处,但是有许多复杂的数字信号处理(DSP)技术可能更合适。一些例子包括均衡、动态范围压缩、峰值限制、时间拉伸或压缩,以及延迟、合唱、副音、失真等特效。

要播放处理后的声音,您可以将处理后的字节数组放入SourceDataLineClip中。当然,字节数组不一定是从现有的声音中得到的。您可以从头开始合成声音,尽管这需要一些声学知识或者访问声音合成函数。无论是处理还是合成,您可能需要参考音频DSP教材中您感兴趣的算法,或者将第三方信号处理函数库导入到您的程序中。对于合成声音的播放,您可以考虑使用javax.sound.midi包中的Synthesizer API是否满足您的需求。您将在稍后的合成声音教程中了解更多关于javax.sound.midi的内容。


上一页: 捕获音频
下一页: 使用文件和格式转换器