文档

Java™教程
隐藏目录
录制音频
路径: 声音

捕获音频

捕获是指从计算机外部获取信号的过程。音频捕获的常见应用是录音,例如将麦克风输入录制到音频文件中。然而,捕获并不等同于录制,因为录制意味着应用程序始终保存正在进入的声音数据。捕获音频的应用程序不一定存储音频。相反,它可能会在音频到达时对数据进行处理 - 例如将语音转录成文本 - 但是一旦完成了每个缓冲区的处理,就会立即丢弃该缓冲区的音频。

Sampled Package概述中所讨论的那样,Java Sound API的实现中,典型的音频输入系统由以下组成:

  1. 输入端口,如麦克风端口或线路输入端口,将其传入的音频数据传送到:
  2. 混音器,将输入数据放置在:
  3. 一个或多个目标数据线中,应用程序可以从中检索数据。

通常,一次只能打开一个输入端口,但也可以使用多个端口混音音频的音频输入混音器。另一种情况是混音器没有端口,而是通过网络获取其音频输入。

线路接口层次结构中简要介绍了TargetDataLine接口。 TargetDataLineSourceDataLine接口直接对应,后者在回放音频中进行了详细讨论。回想一下,SourceDataLine接口由以下内容组成:

类似地,TargetDataLine由以下内容组成:

设置TargetDataLine

获取目标数据线的过程在访问音频系统资源中已经描述过,但为了方便起见,在这里重复一下:

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)

从TargetDataLine中读取数据

一旦打开了该线路,它就准备好开始捕获数据,但还不活动。要实际开始音频捕获,请使用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);
}     

请注意,在此示例中,将数据读入的字节数组的大小设置为线路缓冲区大小的五分之一。如果您将其设置为与线路缓冲区大小相同并尝试读取整个缓冲区,您需要非常准确地控制时间,因为如果混音器在您读取数据时需要将数据传递给线路,数据将被丢弃。通过使用线路缓冲区大小的一部分,如此示例所示,您的应用程序将更成功地与混音器共享对线路缓冲区的访问。

TargetDataLineread方法接受三个参数:一个字节数组,一个数组中的偏移量,以及您想要读取的输入数据的字节数。在这个示例中,第三个参数只是您的字节数组的长度。read方法返回实际读取到数组中的字节数。

通常情况下,您会在循环中从线路中读取数据,就像这个示例中一样。在while循环内,每个检索到的数据块都根据应用程序的需要进行处理,这里将数据写入ByteArrayOutputStream。这里没有显示的是使用单独的线程来设置布尔值stopped,该值用于终止循环。当用户点击停止按钮时,可以将该布尔值设置为true,同时当监听器从线路接收到CLOSESTOP事件时也可以将其设置为true。对于CLOSE事件是必要的,对于STOP事件是推荐的。否则,如果线路在没有将stopped设置为true的情况下被停止,while循环将在每次迭代中捕获零字节,运行速度快,浪费CPU周期。更详细的代码示例将显示在捕获重新启用时重新进入循环。

与源数据线一样,也可以清空或刷新目标数据线。例如,如果您正在将输入录制到文件中,当用户点击停止按钮时,您可能希望调用drain方法。drain方法将导致混音器的剩余数据传送到目标数据线的缓冲区。如果不清空数据,则捕获的声音可能在结束时似乎被截断。

也可能有一些情况下,您希望刷新数据。无论如何,如果您既不刷新也不清空数据,则数据将留在混音器中。这意味着当捕获重新开始时,新录音的开头将有一些残留的声音,这可能是不希望的。因此,在重新启动捕获之前刷新目标数据线可能很有用。

监控线路的状态

因为TargetDataLine接口扩展了DataLine,所以目标数据线以与源数据线相同的方式生成事件。您可以注册一个对象,以便在目标数据线打开、关闭、启动或停止时接收事件。有关更多信息,请参阅前面关于监控线路状态的讨论。

处理传入的音频

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


上一页:播放音频
下一页:使用控件处理音频