1. 概述
在本文中,我们将了解如何在 Java 中捕获麦克风并录制传入的音频,并将其保存为 WAV 文件。为了捕获来自麦克风的传入声音,我们使用 Java Sound API,它是 Java 生态系统的一部分。
Java Sound API 是一个强大的 API,用于捕获、处理和播放音频,包含 4 个包。我们将专注于 javax.sound.sampled 包,它提供了捕获传入音频所需的所有接口和类。
2. 什么是 TargetDataLine?
TargetDataLine 是一种 DataLine 对象,我们使用它来捕获和读取音频相关数据,它从麦克风等音频捕获设备捕获数据。 该接口提供了读取和捕获数据所需的所有方法,它从目标数据行的缓冲区读取数据。
我们可以调用 AudioSystem 的 getLine() 方法并提供 DataLine.Info 对象,该对象提供了音频的所有传输控制方法。Oracle 文档详细解释了 Java Sound API 的工作原理。
让我们来了解在 Java 中从麦克风捕获音频所需的步骤。
3. 捕获声音的步骤
为了保存捕获的音频,Java 支持以下文件格式:AU、AIFF、AIFC、SND 和 WAVE。我们将使用 WAVE (.wav) 文件格式来保存我们的文件。
此过程的第一步是初始化 AudioFormat 实例。AudioFormat 通知 Java 如何解释和处理传入声音流中的信息位。我们在示例中使用以下 AudioFormat 类构造函数:
AudioFormat(AudioFormat.Encoding encoding, float sampleRate, int sampleSizeInBits, int channels, int frameSize, float frameRate, boolean bigEndian)
之后,我们打开一个 DataLine.Info 对象。该对象保存与数据行(输入)相关的所有信息。使用 DataLine.Info 对象,我们可以创建 TargetDataLine 的实例,它将所有传入数据读入音频流。 为了生成 TargetDataLine 实例,我们使用 AudioSystem.getLine() 方法并传递 DataLine.Info 对象:
line = (TargetDataLine) AudioSystem.getLine(info);
line 是 TargetDataLine 实例,info 是 DataLine.Info 实例。
创建后,我们可以打开该行以读取所有传入的声音。我们可以使用 AudioInputStream 来读取传入的数据。最后,我们可以将这些数据写入 WAV 文件并关闭所有流。
为了理解这个过程,我们将看一个录制输入声音的小程序。
4. 示例应用程序
为了了解 Java Sound API 的实际应用,让我们创建一个简单的程序。我们将其分为三个部分,首先构建 AudioFormat,其次构建 TargetDataLine,最后将数据保存为文件。
4.1. 构建 AudioFormat
AudioFormat 类定义了 TargetDataLine 实例可以捕获的数据类型。因此,第一步是在打开新的数据行之前初始化 AudioFormat 类实例。App class 是应用程序的主类,进行所有调用。我们在名为 ApplicationProperties 的常量类中定义 AudioFormat 的属性。我们通过传递所有必要的参数来构建 AudioFormat 实例:
public static AudioFormat buildAudioFormatInstance() {
ApplicationProperties aConstants = new ApplicationProperties();
AudioFormat.Encoding encoding = aConstants.ENCODING;
float rate = aConstants.RATE;
int channels = aConstants.CHANNELS;
int sampleSize = aConstants.SAMPLE_SIZE;
boolean bigEndian = aConstants.BIG_ENDIAN;
return new AudioFormat(encoding, rate, sampleSize, channels, (sampleSize / 8) * channels, rate, bigEndian);
}
现在我们准备好了 AudioFormat,可以继续构建 TargetDataLine 实例。
4.2. 构建 TargetDataLine
我们使用 TargetDataLine 类从麦克风读取音频数据。在我们的示例中,我们在 SoundRecorder 类中获取并运行 TargetDataLine。getTargetDataLineForRecord() 方法构建 TargetDataLine 实例。
我们读取和处理音频输入并将其转储到 AudioInputStream 对象中。我们创建 TargetDataLine 实例的方式是:
private TargetDataLine getTargetDataLineForRecord() {
TargetDataLine line;
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
if (!AudioSystem.isLineSupported(info)) {
return null;
}
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format, line.getBufferSize());
return line;
}
4.3. 构建和填充 AudioInputStream
在我们的示例中,到目前为止,我们已经创建了一个 AudioFormat 实例并将其应用于 TargetDataLine,并打开了数据行以读取音频数据。我们还创建了一个线程来帮助自动运行 SoundRecorder 实例。当线程运行时,我们首先构建一个字节输出流,然后将其转换为 AudioInputStream 实例。构建 AudioInputStream 实例所需的参数是:
int frameSizeInBytes = format.getFrameSize();
int bufferLengthInFrames = line.getBufferSize() / 8;
final int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
注意,在上面的代码中,我们将 bufferSize 减少了 8。我们这样做是为了使缓冲区和数组长度相同,以便录音器可以在读取数据后立即将数据传递到数据行。
现在我们已经初始化了所有需要的参数,下一步是构建字节输出流。下一步是将生成的输出流(捕获的声音数据)转换为 AudioInputStream 实例。
buildByteOutputStream(out, line, frameSizeInBytes, bufferLengthInBytes);
this.audioInputStream = new AudioInputStream(line);
setAudioInputStream(convertToAudioIStream(out, frameSizeInBytes));
audioInputStream.reset();
在设置 InputStream 之前,我们将构建字节 OutputStream:
public void buildByteOutputStream(final ByteArrayOutputStream out, final TargetDataLine line, int frameSizeInBytes, final int bufferLengthInBytes) throws IOException {
final byte[] data = new byte[bufferLengthInBytes];
int numBytesRead;
line.start();
while (thread != null) {
if ((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1) {
break;
}
out.write(data, 0, numBytesRead);
}
}
然后我们将字节 Outstream 转换为 AudioInputStream:
public AudioInputStream convertToAudioIStream(final ByteArrayOutputStream out, int frameSizeInBytes) {
byte audioBytes[] = out.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
AudioInputStream audioStream = new AudioInputStream(bais, format, audioBytes.length / frameSizeInBytes);
long milliseconds = (long) ((audioInputStream.getFrameLength() * 1000) / format.getFrameRate());
duration = milliseconds / 1000.0;
return audioStream;
}
4.4. 将 AudioInputStream 保存为 Wav 文件
我们已经创建并填充了 AudioInputStream,并将其存储为 SoundRecorder 类的成员变量。我们将使用 SoundRecorder 实例 getter 属性在 App 类中检索此 AudioInputStream,并将其传递给 WaveDataUtil 类:
wd.saveToFile("/SoundClip", AudioFileFormat.Type.WAVE, soundRecorder.getAudioInputStream());
WaveDataUtil 类包含将 AudioInputStream 转换为 .wav 文件的代码:
AudioSystem.write(audioInputStream, fileType, myFile);