使用控件处理音频
前面的部分讨论了如何播放或catch音频 samples。隐含的目标是尽可能忠实地交付 samples,而不进行修改(除了可能将 samples 与来自其他音频线路的 samples 混合)。但是,有时您希望能够修改 signal。用户可能希望它听起来更大声,更安静,更饱满,更具回响感,音高更高或更低,等等。该页面讨论了提供这些 signal 处理的 Java Sound API 功能。
-
通过查询Control对象,然后根据用户需要设置控件,可以使用混合器或其组成部分行支持的任何处理。混音器和线路支持的典型控件包括增益,声相和混响控件。
此页面更详细地讨论了第一种技术,因为第二种技术没有特殊的 API。
控件简介
调音台的某些或全部线路上可以具有各种 signal 处理控件。例如,用于音频catch的混频器可能具有带增益控制的 Importing 端口和带增益和平移控制的目标数据线。用于音频播放的混音器可能在其源数据线上具有采样率控件。在每种情况下,都可以通过Line
interface的方法来访问控件。
因为Mixer
interface扩展了Line
,所以混音器本身可以拥有自己的一组控件。这些可能用作影响所有混音器源线或目标线的主控件。例如,混频器可能具有一个主增益控制,该主增益控制的分贝值被添加到其目标线上的各个增益控制的值。
混合器自己的其他控件可能会影响混合器内部用于处理的特殊行,无论是源还是目标。例如,全局混响控件可能会选择混响的种类,以应用于 Importingsignal 的混合,并且这种“湿”(混响)signal 在传送到混音器的目标线路之前会混回到“干”signal 中。
如果混音器或其任何行都具有控件,则您可能希望通过程序用户interface中的图形对象来显示控件,以便用户可以根据需要调整音频特性。控件本身不是图形的;它们只是允许您检索和更改其设置。您可以决定在程序中使用哪种图形表示形式(滑块,按钮等)(如果有)。
所有控件都实现为抽象类Control
的具体子类。 Control
的抽象子类可以基于数据类型(例如布尔,枚举或浮点数)来描述许多典型的音频处理控件。例如,布尔控制代表二进制状态控制,例如静音或混响的开/关控制。另一方面,Float 控件非常适合表示连续可变的控件,例如声像,平衡或音量。
Java Sound API 指定了Control
的以下抽象子类:
-
BooleanControl —表示二进制状态(对或错)控件。例如,静音,独奏和开/关switch将是
BooleanControls
的不错选择。 -
FloatControl —数据模型,可控制一系列浮点值。例如,音量和声像是
FloatControls
,可以通过刻度盘或滑块进行操作。 -
EnumControl —提供了一组对象的选择。例如,您可以将用户interface中的一组按钮与
EnumControl
关联,以选择多个预设混响设置之一。 -
CompoundControl-提供对相关项目集合的访问,每个相关项目本身都是
Control
子类的实例。CompoundControls
表示多控制模块,例如图形均衡器。 (图形均衡器通常由一组滑块描绘,每个滑块影响FloatControl
.)
上面Control
的每个子类都有适合其基础数据类型的方法。大多数类都包含设置和获取控件当前值,获取控件标签等的方法。
当然,每个类都有特定于其的方法以及该类表示的数据模型。例如,EnumControl
有一个方法可让您获取其可能值的集合,而FloatControl
则可让您获取其最小值和最大值以及控件的精度(增量或步 Long)。
Control
的每个子类都有一个对应的Control.Type
子类,其中包括标识特定控件的静态实例。
下表显示了每个Control
子类,其对应的Control.Type
子类以及指示特定种类控件的静态实例:
Java Sound API 的实现可以在其混音器和线路上提供任何或所有这些控件类型。它还可以提供 Java Sound API 中未定义的其他控件类型。可以通过这四个抽象子类中的任何一个的具体子类或不从这四个抽象子类中的任何一个继承的其他Control
子类来实现此类控件类型。应用程序可以查询每一行以找到其支持的控件。
获取具有所需控件的行
在许多情况下,应用程序将仅显示该行可能支持的任何控件。如果该行没有任何控件,那就去吧。但是,如果找到具有某些控件的行很重要,该怎么办?在这种情况下,您可以使用Line.Info
来获得具有正确 Feature 的线,如先前在获取所需类型的线下所述。
例如,假设您希望使用一个 Importing 端口,以便用户设置声音 Importing 的音量。以下代码摘录显示了如何查询默认混音器以确定其是否具有所需的端口和控件:
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);
// Assuming getControl call succeeds,
// we now have our LINE_IN VOLUME control.
} catch (Exception e) {
System.out.println("Failed trying to find LINE_IN"
+ " VOLUME control: exception = " + e);
}
if (volCtrl != null)
// ...
从生产线获取控件
需要在用户interface中公开控件的应用程序可能会简单地查询可用的行和控件,然后为每个感兴趣的行上的每个控件显示适当的用户interface元素。在这种情况下,该程序的唯一任务是为用户提供控件上的“句柄”。不知道这些控件对音频 signal 做了什么。只要程序知道如何将行的控件 Map 到用户interface元素,则Mixer
,Line
和Control
的 Java Sound API 体系结构通常会处理其余部分。
例如,假设您的程序播放声音。您使用的是SourceDataLine
,如先前在获取所需类型的线下所述。您可以通过调用以下Line
方法来访问该行的控件:
Control[] getControls()
然后,对于此返回数组中的每个控件,然后使用以下Control
方法获取控件的类型:
Control.Type getType()
知道了特定的Control.Type
实例,您的程序可以显示相应的用户interface元素。当然,为特定的Control.Type
选择“相应的用户interface元素”取决于您的程序采用的方法。一方面,您可能使用相同类型的元素来表示同一类的所有Control.Type
实例。这将要求您使用Object.getClass
方法查询Control.Type
实例的* class *。假设结果匹配BooleanControl.Type
。在这种情况下,您的程序可能会显示一个通用复选框或切换按钮,但是如果其类与FloatControl.Type
相匹配,则您可能会显示一个图形滑块。
另一方面,您的程序可能会区分不同类型的控件(甚至是同一类的控件),并为每个控件使用不同的用户interface元素。这将需要您测试Control's getType
方法返回的* instance *。然后,例如,如果类型匹配BooleanControl.Type.APPLY_REVERB
,则您的程序可能会显示一个复选框;而如果类型匹配BooleanControl.Type.MUTE
,则可以显示一个切换按钮。
使用控件更改音频 signal
现在您知道如何访问控件并确定其类型,本节将说明如何使用Controls
更改音频 signal 的各个方面。本节并不涵盖所有可用的控件;相反,它提供了该区域的一些示例来向您展示如何入门。这些示例包括:
假设您的程序已经访问了其所有混合器,它们的行和这些行上的控件,并且它具有一个数据结构来 管理 控件及其相应的用户interface元素之间的逻辑关联。然后,将用户对这些控件的操作转换为相应的Control
方法就变得相当简单。
控制线路的静音状态
控制任何线路的静音状态仅需调用以下BooleanControl
方法即可:
void setValue(boolean value)
(大概程序通过引用其控制 管理 数据结构来知道该静音是BooleanControl
的实例.)要使通过该行的 signal 静音,程序将调用上面的方法,将true
指定为值。要关闭静音,使 signal 流过线路,程序将调用参数设置为false
的方法。
更改线路的音量
假设您的程序将特定的图形滑块与特定行的音量控件相关联。使用以下FloatControl
方法设置音量控件(即FloatControl.Type.VOLUME
)的值:
void setValue(float newValue)
检测到用户移动了滑块,程序将获取滑块的当前值,并将其作为参数newValue
传递给上述方法。这会改变流经“拥有”控件的线路的 signal 的音量。
在各种混响预设中选择
假设我们的程序有一个混音器,其中的一行具有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
对象创建一个用户interface元素,例如单选按钮。当用户单击这些单选按钮之一时,程序将调用EnumControl
方法
void setValue(java.lang.Object value)
其中value
设置为与新启用的按钮相对应的ReverbType
。然后,通过“拥有”此EnumControl
的线路发送的音频 signal 将根据构成控件当前ReverbType
的参数设置(即,在setValue
方法的value
参数中指定的特定ReverbType
)进行混响。
因此,从我们的应用程序的角度来看,使用户能够从一个混响预设(即 ReverbType)移动到另一个混响预设,仅是将getValues
返回的数组的每个元素连接到一个单独的单选按钮的问题。
直接处理音频数据
Control
API 允许 Java Sound API 的实现或调音台的第三方提供程序通过控件提供任意种类的 signal 处理。但是,如果没有混频器提供您所需的那种 signal 处理呢?这将需要更多的工作,但是您也许可以在程序中实现 signal 处理。因为 Java Sound API 允许您以字节数组的形式访问音频数据,所以您可以选择任何方式更改这些字节。
如果要处理传入的声音,则可以从TargetDataLine
读取字节,然后对其进行操作。可以产生令人神往的结果的算法上不重要的示例是通过以相反的 Sequences 排列其帧来向后播放声音。这个琐碎的示例可能在您的程序中用处不大,但是有许多复杂的数字 signal 处理(DSP)技术可能更合适。一些示例包括均衡,动态范围压缩,峰值限制和时间拉伸或压缩,以及特殊效果,例如延迟,合唱,镶边,失真等。
要播放处理后的声音,可以将经过控制的字节数组放入SourceDataLine
或Clip
。当然,字节数组不必从现有的声音派生。您可以从头开始合成声音,尽管这需要一些声学知识,或者需要使用声音合成功能。对于处理或综合,您可能需要查阅音频 DSP 教科书中感兴趣的算法,或者将第三方的 signal 处理功能库导入程序中。要播放合成声音,请考虑javax.sound.midi
包中的Synthesizer
API 是否满足您的需求。稍后您将在Synthesizing Sound下了解有关javax.sound.midi
的更多信息。