# 捕获音频 > 原文: [https://docs.oracle.com/javase/tutorial/sound/capturing.html](https://docs.oracle.com/javase/tutorial/sound/capturing.html) _ 捕获*是指从计算机外部获取信号的过程。音频捕获的常见应用是记录,例如将麦克风输入记录到声音文件中。但是,捕获并不是录制的同义词,因为录制意味着应用程序始终保存正在进入的声音数据。捕获音频的应用程序不一定存储音频。相反,它可能会对数据进行处理 - 例如将语音转录为文本 - 但是一旦完成该缓冲区就会丢弃每个音频缓冲区。 如 [Sampled Package 概述](sampled-overview.html)中所述,Java Sound API 实现中的典型音频输入系统包括: 1. 输入端口,如麦克风端口或输入端口,将输入的音频数据输入: 2. 一个混音器,它将输入数据放入: 3. 一个或多个目标数据行,应用程序可以从中检索数据。 通常,一次只能打开一个输入端口,但也可以混合来自多个端口的音频的音频输入混音器。另一种情况包括一个没有端口但是通过网络获得音频输入的混音器。 在[线路接口层次结构](sampled-overview.html#lineHierarchy)下简要介绍了`TargetDataLine`接口。 `TargetDataLine`直接类似于`SourceDataLine`接口,[播放音频](playing.html)中对此进行了广泛讨论。回想一下`SourceDataLine`接口包括: * 一种`write`方法将音频发送到调音台 * 一种`available`方法,用于确定可以在不阻塞的情况下将多少数据写入缓冲区 同样,`TargetDataLine`包括: * 一种`read`方法从混音器获取音频 * 一种`available`方法,用于确定可以从缓冲区中读取多少数据而不会阻塞 ## 设置 TargetDataLine [访问音频系统资源](accessing.html)中描述了获取目标数据线的过程,但为方便起见,我们在此重复: ```java TargetDataLine line; DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object if (!AudioSystem.isLineSupported(info)) { // Handle the error ... } // Obtain and open the line. try { line = (TargetDataLine) AudioSystem.getLine(info); line.open(format); } catch (LineUnavailableException ex) { // Handle the error ... } ``` 您可以改为调用`Mixer's` `getLine`方法,而不是`AudioSystem's`。 如本例所示,一旦获得了目标数据行,就可以通过调用`SourceDataLine`方法`open`来保留它以供应用程序使用,完全如源的情况所述[播放音频](playing.html)中的数据线。 `open`方法的单参数版本导致行的缓冲区具有默认大小。您可以通过调用双参数版本来根据应用程序的需要设置缓冲区大小: ```java void open(AudioFormat format, int bufferSize) ``` ## 从 TargetDataLine 读取数据 一旦线路打开,就可以开始捕获数据,但它还没有激活。要实际开始音频捕获,请使用`DataLine`方法`start`。这开始将输入音频数据传送到线路的缓冲区,供应用程序读取。您的应用程序只有在准备开始从线路读取时才应启动;否则会在填充捕获缓冲区时浪费大量处理,只会使其溢出(即丢弃数据)。 要开始从缓冲区中检索数据,请调用`TargetDataLine's`读取方法: ```java int read(byte[] b, int offset, int length) ``` 此方法尝试将`length`字节的数据读入数组`b`,从数组中的字节位置`offset`开始。该方法返回实际读取的字节数。 与`SourceDataLine's write`方法一样,您可以请求比实际适合缓冲区更多的数据,因为即使您请求了许多缓冲区的数据,该方法也会阻塞,直到所请求的数据量已经传送完毕。 为避免在录制过程中挂起应用程序,可以在循环内调用 read 方法,直到检索完所有音频输入为止,如下例所示: ```java // Assume that the TargetDataLine, line, has already // been obtained and opened. ByteArrayOutputStream out = new ByteArrayOutputStream(); int numBytesRead; byte[] data = new byte[line.getBufferSize() / 5]; // Begin audio capture. line.start(); // Here, stopped is a global boolean set by another thread. while (!stopped) { // Read the next chunk of data from the TargetDataLine. numBytesRead = line.read(data, 0, data.length); // Save this chunk of data. out.write(data, 0, numBytesRead); } ``` 请注意,在此示例中,读取数据的字节数组的大小设置为行缓冲区大小的五分之一。如果您改为使其与行缓冲区一样大并尝试读取整个缓冲区,则需要非常准确地计算时间,因为如果混音器需要在读取数据时将数据传输到线路,则会转储数据。通过使用线路缓冲区大小的一部分,如此处所示,您的应用程序将更加成功地使用混频器共享对线路缓冲区的访问。 `TargetDataLine`的`read`方法有三个参数:一个字节数组,一个数组的偏移量,以及你想要读取的输入数据的字节数。在此示例中,第三个参数只是字节数组的长度。 `read`方法返回实际读入数组的字节数。 通常,您在循环中读取行中的数据,如本例所示。在`while`循环中,每个检索到的数据块都以适合应用程序的任何方式处理 - 这里,它被写入`ByteArrayOutputStream`。这里没有显示使用单独的线程来设置布尔`stopped`,它终止循环。当用户单击“停止”按钮时,以及当监听器从该行收到`CLOSE`或`STOP`事件时,此布尔值可能会设置为`true`。监听器是`CLOSE`事件所必需的,并推荐用于`STOP`事件。否则,如果线路以某种方式停止而没有停止设置为`true`,则`while`循环将在每次迭代时捕获零字节,快速运行并浪费 CPU 周期。如果捕获再次变为活动状态,则更完整的代码示例将显示重新进入的循环。 与源数据线一样,可以排空或刷新目标数据线。例如,如果要将输入记录到文件中,则可能需要在用户单击“停止”按钮时调用`drain`方法。 `drain`方法将使混音器的剩余数据传送到目标数据线的缓冲区。如果不排空数据,捕获的声音可能会在结束时过早地被截断。 在某些情况下,您可能需要刷新数据。在任何情况下,如果您既不刷新也不排空数据,它将留在混音器中。这意味着当捕获重新开始时,在新录制开始时会有一些剩余的声音,这可能是不合需要的。因此,在重新启动捕获之前刷新目标数据行会很有用。 ## 监控线路状态 因为`TargetDataLine`接口扩展了`DataLine`,目标数据线以与源数据线相同的方式生成事件。只要目标数据行打开,关闭,启动或停止,您就可以注册一个对象来接收事件。有关更多信息,请参阅前面关于[监控线路状态](playing.html#113711)的讨论。 ## 处理传入音频 与某些源数据线一样,某些混频器的目标数据线具有信号处理控制,例如增益,声像,混响或采样率控制。输入端口可能具有类似的控件,尤其是增益控制。在下一节中,您将学习如何确定一行是否具有此类控件,以及如何使用它们。