# 捕获音频

> 原文： [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)中描述了获取目标数据线的过程，但为方便起见，我们在此重复：

```
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`方法的单参数版本导致行的缓冲区具有默认大小。您可以通过调用双参数版本来根据应用程序的需要设置缓冲区大小：

```
void open(AudioFormat format, int bufferSize)

```

## 从 TargetDataLine 读取数据

一旦线路打开，就可以开始捕获数据，但它还没有激活。要实际开始音频捕获，请使用`DataLine`方法`start`。这开始将输入音频数据传送到线路的缓冲区，供应用程序读取。您的应用程序只有在准备开始从线路读取时才应启动;否则会在填充捕获缓冲区时浪费大量处理，只会使其溢出（即丢弃数据）。

要开始从缓冲区中检索数据，请调用`TargetDataLine's`读取方法：

```
int read(byte[] b, int offset, int length)

```

此方法尝试将`length`字节的数据读入数组`b`，从数组中的字节位置`offset`开始。该方法返回实际读取的字节数。

与`SourceDataLine's write`方法一样，您可以请求比实际适合缓冲区更多的数据，因为即使您请求了许多缓冲区的数据，该方法也会阻塞，直到所请求的数据量已经传送完毕。

为避免在录制过程中挂起应用程序，可以在循环内调用 read 方法，直到检索完所有音频输入为止，如下例所示：

```
// 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)的讨论。

## 处理传入音频

与某些源数据线一样，某些混频器的目标数据线具有信号处理控制，例如增益，声像，混响或采样率控制。输入端口可能具有类似的控件，尤其是增益控制。在下一节中，您将学习如何确定一行是否具有此类控件，以及如何使用它们。