Capturing Audio

catch是指从计算机外部获取 signal 的过程。音频catch的常见应用是录制,例如将麦克风 Importing 录制到声音文件中。但是,catch并不等同于录制,因为录制意味着应用程序始终会保存传入的声音数据。catch音频的应用程序不一定会存储音频。取而代之的是,它可能会对传入的数据进行某些处理(例如将语音转录为文本),但是一旦音频缓冲完成,就立即丢弃每个音频缓冲。

采样包概述所述,Java Sound API 的实现中的典型音频 Importing 系统包括:

  • Importing 端口,例如麦克风端口或线路 Importing 端口,用于将其传入的音频数据馈送到:

  • 混合器,将 Importing 数据放置在:

  • 一个或多个目标数据行,应用程序可以从中检索数据。

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

线路interface层次结构下简要介绍了TargetDataLineinterface。 TargetDataLine直接类似于SourceDataLineinterface,在播放音频中对此进行了广泛讨论。回想一下SourceDataLineinterface包括:

  • 一种write方法将音频发送到调音台

  • 一种available方法,用于确定可以在不阻塞的情况下将多少数据写入缓冲区

同样,TargetDataLine包含:

  • 一种read方法从调音台获取音频

  • 一种available方法,用于确定可以从缓冲区读取多少数据而不会阻塞

设置 TargetDataLine

访问音频系统资源中描述了获取目标数据行的过程,但为方便起见,在此重复此过程:

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保留它供应用程序使用,这与播放音频中的源数据行的情况完全相同。 open方法的单参数版本使该行的缓冲区具有默认大小。您可以通过调用两个参数的版本来根据应用程序的需要设置缓冲区大小:

void open(AudioFormat format, int bufferSize)

从 TargetDataLine 读取数据

该行打开后,就可以开始catch数据了,但是它尚未激活。要实际开始音频catch,请使用DataLine方法start。这开始将 Importing 音频数据传送到线路的缓冲区,以供您的应用程序读取。您的应用程序只有在准备好开始从该行开始读取时,才应调用 start。否则,浪费大量的处理时间来填充catch缓冲区,只是使其溢出(即丢弃数据)。

要开始从缓冲区中检索数据,请调用TargetDataLine's read 方法:

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

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

SourceDataLine's write方法一样,您可以请求的数据量超出缓冲区中的实际容量,因为即使您请求许多缓冲区中的数据,该方法也会阻塞直到所请求的数据量已交付为止。

为避免您的应用程序在录制过程中挂起,可以在循环中调用 read 方法,直到检索到所有音频 Importing 为止,如以下示例所示:

// 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);
}

请注意,在此示例中,将读取数据的字节数组的大小设置为行缓冲区的大小的五分之一。如果改为将其设置为与行的缓冲区一样大并try读取整个缓冲区,则需要非常精确的时间,因为如果混频器需要在从行中读取数据时将数据传输到该行,则数据将被转储。 。通过使用行缓冲区大小的一部分,如此处所示,您的应用程序将可以更成功地与混频器共享对行缓冲区的访问。

TargetDataLineread方法采用三个参数:字节数组,该数组的offset量以及要读取的 Importing 数据的字节数。在此示例中,第三个参数只是字节数组的 Long 度。 read方法返回实际读入数组的字节数。

通常,如本例所示,您是从循环中的一行读取数据。在while循环内,以适合应用程序的任何方式处理每个检索到的数据块-在这里,将它们写入ByteArrayOutputStream。这里未显示使用单独的线程设置布尔值stopped,从而终止循环。当用户单击“停止”按钮时,以及当侦听器从该行接收到CLOSESTOP事件时,此布尔值的值可能设置为true。对于CLOSE事件,侦听器是必需的,对于STOP事件,建议使用侦听器。否则,如果以某种方式停止了该行而没有将其停止设置为true,则while循环将在每次迭代中catch零字节,从而运行很快并且浪费 CPU 周期。一个更详尽的代码示例将显示如果catch再次变为活动状态,则重新进入循环。

与源数据线一样,可以排空或冲洗目标数据线。例如,如果要将 Importing 记录到文件中,则可能需要在用户单击“停止”按钮时调用drain方法。 drain方法将导致混频器的剩余数据被传递到目标数据线的缓冲区。如果不清除数据,catch的声音可能会在结尾时被过早地截断。

在某些情况下,您可能想刷新数据。无论如何,如果您既不刷新也不清空数据,则数据将留在混频器中。这意味着当重新开始catch时,在新录制的开始时会有一些剩余的声音,这可能是不希望的。然后,重新启动catch之前刷新目标数据行可能很有用。

监视生产线的状态

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

处理传入的音频

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