Java教程是为JDK 8编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及其后续版本中更新的语言特性的摘要。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能以及已删除或不推荐使用的选项的信息。
捕获是指从计算机外部获取信号的过程。音频捕获的常见应用是录音,例如将麦克风输入录制到音频文件中。然而,捕获并不等同于录制,因为录制意味着应用程序始终保存正在进入的声音数据。捕获音频的应用程序不一定存储音频。相反,它可能会在音频到达时对数据进行处理 - 例如将语音转录成文本 - 但是一旦完成了每个缓冲区的处理,就会立即丢弃该缓冲区的音频。
如Sampled Package概述中所讨论的那样,Java Sound API的实现中,典型的音频输入系统由以下组成:
通常,一次只能打开一个输入端口,但也可以使用多个端口混音音频的音频输入混音器。另一种情况是混音器没有端口,而是通过网络获取其音频输入。
在线路接口层次结构中简要介绍了TargetDataLine
接口。 TargetDataLine
与SourceDataLine
接口直接对应,后者在回放音频中进行了详细讨论。回想一下,SourceDataLine
接口由以下内容组成:
获取目标数据线的过程在访问音频系统资源中已经描述过,但为了方便起见,在这里重复一下:
TargetDataLine line; DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format是一个AudioFormat对象 if (!AudioSystem.isLineSupported(info)) { // 处理错误... } // 获取并打开线路。 try { line = (TargetDataLine) AudioSystem.getLine(info); line.open(format); } catch (LineUnavailableException ex) { // 处理错误... }
您可以调用Mixer的
getLine
方法,而不是AudioSystem的
方法。
如本示例所示,一旦您获得了目标数据行,您可以通过调用SourceDataLine
方法open
将其保留给您的应用程序使用,就像在播放音频中描述源数据行的情况一样。 open
方法的单参数版本使线路的缓冲区具有默认大小。您也可以通过调用两参数版本来根据您的应用程序需求设置缓冲区大小:
void open(AudioFormat format, int bufferSize)
一旦打开了该线路,它就准备好开始捕获数据,但还不活动。要实际开始音频捕获,请使用DataLine
方法start
。这将开始将输入音频数据传递到线路的缓冲区,以供您的应用程序读取。只有当应用程序准备好从线路中读取时,才应调用start,否则将浪费大量处理时间来填充捕获缓冲区,只有让其溢出(即丢弃数据)。
要从缓冲区开始检索数据,请调用TargetDataLine的
read方法:
int read(byte[] b, int offset, int length)
此方法尝试将length
字节的数据读入数组b
,从数组中的字节位置offset
开始。该方法返回实际读取的字节数。
与SourceDataLine的write
方法一样,您可以请求比缓冲区实际容量更多的数据,因为该方法会阻塞,直到已传递所请求的数据量,即使您请求了多个缓冲区的数据。
为避免在录制过程中出现应用程序挂起,可以在循环中调用read方法,直到检索完所有音频输入,如下例所示:
// 假设TargetDataLine已经获得并打开。 ByteArrayOutputStream out = new ByteArrayOutputStream(); int numBytesRead; byte[] data = new byte[line.getBufferSize() / 5]; // 开始音频捕获。 line.start(); // 这里,stopped是由另一个线程设置的全局布尔变量。 while (!stopped) { // 从TargetDataLine读取下一个数据块。 numBytesRead = line.read(data, 0, data.length); // 保存这个数据块。 out.write(data, 0, numBytesRead); }
请注意,在此示例中,将数据读入的字节数组的大小设置为线路缓冲区大小的五分之一。如果您将其设置为与线路缓冲区大小相同并尝试读取整个缓冲区,您需要非常准确地控制时间,因为如果混音器在您读取数据时需要将数据传递给线路,数据将被丢弃。通过使用线路缓冲区大小的一部分,如此示例所示,您的应用程序将更成功地与混音器共享对线路缓冲区的访问。
TargetDataLine
的read
方法接受三个参数:一个字节数组,一个数组中的偏移量,以及您想要读取的输入数据的字节数。在这个示例中,第三个参数只是您的字节数组的长度。read
方法返回实际读取到数组中的字节数。
通常情况下,您会在循环中从线路中读取数据,就像这个示例中一样。在while
循环内,每个检索到的数据块都根据应用程序的需要进行处理,这里将数据写入ByteArrayOutputStream
。这里没有显示的是使用单独的线程来设置布尔值stopped
,该值用于终止循环。当用户点击停止按钮时,可以将该布尔值设置为true
,同时当监听器从线路接收到CLOSE
或STOP
事件时也可以将其设置为true
。对于CLOSE
事件是必要的,对于STOP
事件是推荐的。否则,如果线路在没有将stopped
设置为true
的情况下被停止,while
循环将在每次迭代中捕获零字节,运行速度快,浪费CPU周期。更详细的代码示例将显示在捕获重新启用时重新进入循环。
与源数据线一样,也可以清空或刷新目标数据线。例如,如果您正在将输入录制到文件中,当用户点击停止按钮时,您可能希望调用drain
方法。drain
方法将导致混音器的剩余数据传送到目标数据线的缓冲区。如果不清空数据,则捕获的声音可能在结束时似乎被截断。
也可能有一些情况下,您希望刷新数据。无论如何,如果您既不刷新也不清空数据,则数据将留在混音器中。这意味着当捕获重新开始时,新录音的开头将有一些残留的声音,这可能是不希望的。因此,在重新启动捕获之前刷新目标数据线可能很有用。
因为TargetDataLine
接口扩展了DataLine
,所以目标数据线以与源数据线相同的方式生成事件。您可以注册一个对象,以便在目标数据线打开、关闭、启动或停止时接收事件。有关更多信息,请参阅前面关于监控线路状态的讨论。
与某些源数据线一样,某些混音器的目标数据线具有信号处理控件,例如增益、平衡、混响或采样率控制。输入端口可能具有类似的控件,尤其是增益控件。在下一节中,您将学习如何确定一条线路是否具有此类控件,以及如果具有这些控件如何使用它们。