使用控件处理音频
原文: https://docs.oracle.com/javase/tutorial/sound/controls.html
前面的部分讨论了如何播放或捕获音频样例。隐含的目标是尽可能忠实地传送样例,而不进行修改(除了可能将样例与来自其他音频线的样例混合)。但是,有时您希望能够修改信号。用户可能希望它听起来更响亮,更安静,更饱满,更具混响,音高更高或更低,等等。本页讨论了提供这些信号处理的 Java Sound API 功能。
有两种方法可以应用信号处理:
- 您可以使用调音台或其组件线支持的任何处理,查询
Control
对象,然后根据用户需要设置控件。混音器和线路支持的典型控制包括增益,声像和混响控制。 - 如果混音器或其线路不提供您需要的处理类型,您的程序可以直接在音频字节上操作,根据需要进行操作。
本页更详细地讨论了第一种技术,因为第二种技术没有特殊的 API。
控制简介
混音器可以在其部分或全部线路上具有各种信号处理控制。例如,用于音频捕获的混频器可能具有带增益控制的输入端口,以及带增益和平移控制的目标数据线。用于音频回放的混音器可能在其源数据线上具有采样率控制。在每种情况下,都可以通过Line
接口的方法访问控件。
因为Mixer
接口扩展了Line
,所以混音器本身可以拥有自己的一组控件。这些可以作为影响所有调音台源或目标线的主控制。例如,混音器可能具有主增益控制,其以分贝为单位的值被添加到其目标线上的各个增益控制的值。
混音器自身控制的其他部分可能会影响混音器在内部进行处理的特殊线路,既不是源也不是目标。例如,全局混响控制可以选择应用于混合输入信号的混响类型,并且这种“湿”(混响)信号在传送到混音器的目标线之前会混合回“干”信号。
如果调音台或其任何一行有控件,您可能希望通过程序用户界面中的图形对象公开控件,以便用户可以根据需要调整音频特性。控件本身不是图形化的;它们只允许您检索和更改其设置。由您决定在程序中使用哪种图形表示(滑块,按钮等)(如果有)。
所有控件都实现为抽象类Control
的具体子类。许多典型的音频处理控件可以通过Control
的抽象子类基于数据类型(例如 boolean,enumerated 或 float)来描述。例如,布尔控件表示二进制状态控件,例如静音或混响的开/关控件。另一方面,浮动控制非常适合表示连续变量控制,例如平移,平衡或体积。
Java Sound API 指定Control
的以下抽象子类:
BooleanControl
- 表示二元状态(真或假)对照。例如,静音,独奏和开/关开关将是BooleanControls
的良好候选者。FloatControl
- 提供对一系列浮点值的控制的数据模型。例如,音量和声像是FloatControls
,可以通过拨盘或滑块进行操作。EnumControl
- 提供一组对象的选择。例如,您可以将用户界面中的一组按钮与EnumControl
相关联,以选择多个预设混响设置中的一个。CompoundControl
- 提供对相关项集合的访问,每个项目本身都是Control
子类的实例。CompoundControls
表示多控制模块,例如图形均衡器。 (图形均衡器通常由一组滑块描绘,每个滑块都会影响FloatControl
。)
上面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 Sound API 的实现可以在其混音器和线路上提供任何或所有这些控件类型。它还可以提供 Java Sound 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);
// 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)
// ...
从线上获取控件
需要在其用户界面中公开控件的应用程序可能只是查询可用的行和控件,然后为每个感兴趣的行显示一个适当的用户界面元素。在这种情况下,程序的唯一任务是为用户提供控件上的“句柄”;不知道那些控件对音频信号做了什么。只要程序知道如何将行控件映射到用户界面元素,Mixer
,Line
和Control
的 Java Sound API 架构通常会处理其余部分。
例如,假设您的程序播放声音。你正在使用SourceDataLine
,你已经按照获得所需类型的线中的描述获得了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
的参数设置(即value
参数中指定的特定ReverbType
进行混响。 COD6]方法)。
因此,从我们的应用程序的角度来看,允许用户从一个混响预设(即 ReverbType)移动到另一个混响预设只是将getValues
返回的数组的每个元素连接到不同的无线电的问题按钮。
直接操作音频数据
Control
API 允许实现 Java Sound API 或混音器的第三方提供商,通过控件提供任意类型的信号处理。但是,如果没有调音台提供您需要的信号处理怎么办?这将需要更多的工作,但您可能能够在您的程序中实现信号处理。由于 Java Sound API 允许您以字节数组的形式访问音频数据,因此您可以以您选择的任何方式更改这些字节。
如果您正在处理传入的声音,您可以从TargetDataLine
中读取字节然后进行操作。可以产生声音有趣的结果的算法上无关紧要的例子是通过以相反的顺序排列其帧来向后播放声音。这个简单的例子可能对您的程序没什么用处,但是有许多复杂的数字信号处理(DSP)技术可能更合适。一些示例是均衡,动态范围压缩,峰值限制,时间拉伸或压缩,以及延迟,合唱,翻边,失真等特殊效果。
要播放处理过的声音,可以将操作的字节数组放入SourceDataLine
或Clip
。当然,字节数组不需要从现有声音中导出。你可以从头开始合成声音,虽然这需要一些声学知识或者获得声音合成功能。对于处理或综合,您可能需要查阅音频 DSP 教科书以了解您感兴趣的算法,或者将第三方信号处理函数库导入您的程序。要播放合成声音,请考虑javax.sound.midi
包中的Synthesizer
API 是否满足您的需求。您将在 Synthesizing Sound 中了解更多关于javax.sound.midi
的信息。