在桌面程序或语音交互项目中,使用Java直接捕获麦克风声音是一项基础但容易踩坑的能力。Java Sound API作为JDK标准库的一部分,提供了TargetDataLine来表示音频输入设备,开发者无需引入第三方库就能完成实时录音。下面通过一个完整实例,说明如何从麦克风采集声音并保存为WAV文件。

一、核心类与录音原理
Java Sound API的录音功能主要依赖javax.sound.sampled包。其中AudioFormat用来描述音频的格式参数,包括采样率、样本大小、声道数、是否带符号、字节顺序等。TargetDataLine代表一个可读取数据的音频输入设备,比如麦克风。AudioSystem则负责查询系统可用的混音器并打开数据线。
实时录音的本质是:向系统申请一条TargetDataLine,启动后系统会把麦克风捕捉到的PCM(脉冲编码调制)原始音频数据持续推入内部缓冲区。我们的程序通过read方法从缓冲区取出字节,写入内存流或文件。因为PCM是无压缩的原始数据,直接保存成WAV只需在前面加上44字节的标准WAV头即可被播放器识别。
1.1 音频格式参数选择
常用的语音录音格式为:采样率16000Hz、16位、单声道、小端、有符号。这个配置在绝大多数系统上都受支持,且数据量适中。如果选择双声道或更高采样率,文件体积会明显变大,但对简单录音场景并无必要。
需要特别注意的是,TargetDataLine打开时使用的AudioFormat必须和后续写WAV时用的格式完全一致,否则生成的文件虽能写出但播放会失真或无法解析。下面代码展示了格式定义方式。
// 定义音频格式:16k采样率,16位,单声道,小端,有符号
AudioFormat format = new AudioFormat(
16000, // 采样率
16, // 每个样本位数
1, // 声道数,1为单声道
true, // 是否有符号
false // 字节顺序,false表示小端
);
二、打开麦克风并开始录音
通过AudioSystem.getTargetDataLine获取系统默认的录音线,然后调用open方法以指定格式和缓冲区大小打开它。缓冲区建议设置为采样率乘以样本字节数再乘以声道数,乘以一个时间系数(如1秒),这样能平衡延迟与稳定性。
打开后调用start方法,麦克风就开始往内部缓冲送数据。我们在独立线程里循环调用read,把数据追加到ByteArrayOutputStream。当接收到停止信号时,跳出循环并关闭线路。下面给出录音线程的核心实现。
// 获取并打开TargetDataLine
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
int bufferSize = (int) format.getSampleRate() * format.getFrameSize();
line.open(format, bufferSize);
line.start();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
boolean recording = true;
while (recording) {
int n = line.read(buf, 0, buf.length);
if (n > 0) {
out.write(buf, 0, n);
}
}
line.stop();
line.close();
2.1 如何优雅停止录音
上面的循环依赖recording变量控制退出。由于line.read是阻塞调用,当没有新数据时线程会停在该行。我们可以在另一个线程将recording设为false,但read可能还卡着,此时可以调用line.stop(),这会令read返回已缓冲数据或负值,循环自然结束。
为避免强行中断造成数据截断,推荐用volatile修饰recording,并在停止按钮的事件里先置false再调stop。这样既能退出循环,也能保证已读出的数据完整写入流中。
三、将录音数据保存为WAV文件
PCM字节数组本身不是合法音频文件,必须包装成AudioInputStream并借助AudioSystem.write写出WAV。AudioInputStream需要格式对象和数据源,我们用ByteArrayInputStream包住前面收集到的字节即可。
以下代码演示了从内存流生成WAV并保存到磁盘。注意write方法的第三个参数指定文件类型AudioFileFormat.Type.WAVE,JDK会自动补全WAV头信息。
byte[] audioData = out.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(audioData);
AudioInputStream ais = new AudioInputStream(bais, format, audioData.length / format.getFrameSize());
File wavFile = new File("record.wav");
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, wavFile);
ais.close();
3.1 完整可运行示例
把前面片段组合,再加一个主方法就能直接跑。下面示例包含简单的控制台停止逻辑:录入回车后结束录音。实际GUI程序可把录音逻辑放到SwingWorker或普通Thread里,用按钮控制。
import javax.sound.sampled.*;
import java.io.*;
public class MicRecorder {
public static void main(String[] args) throws Exception {
AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
Thread stopper = new Thread(() -> {
try {
System.out.println("按回车停止录音");
new BufferedReader(new InputStreamReader(System.in)).readLine();
line.stop();
line.close();
} catch (Exception e) { }
});
stopper.start();
System.out.println("录音中...");
while (line.isOpen()) {
int n = line.read(buf, 0, buf.length);
if (n > 0) out.write(buf, 0, n);
}
byte[] data = out.toByteArray();
AudioInputStream ais = new AudioInputStream(
new ByteArrayInputStream(data), format, data.length / format.getFrameSize());
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("record.wav"));
ais.close();
System.out.println("已保存 record.wav");
}
}
四、常见问题与排查
在Linux服务器或无界面环境中,常因没有声音服务或权限不足导致getLine抛出LineUnavailableException。可先确认是否安装了alsa及pulseaudio,并确保运行用户属于audio组。Windows上若有多麦克风,可通过Mixer列表选择指定设备而非默认线。
另一个易错点是缓冲区过小造成爆音。若录音出现断续,可把open时的bufferSize调大到采样率乘帧大小乘2或更高。此外,WAV头中的长度字段由AudioSystem自动计算,手动拼头容易写错,建议始终用AudioSystem.write导出。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| LineUnavailableException | 无录音设备权限 | 加入audio组或检查声音服务 |
| 录音有杂音断续 | 缓冲区太小 | 增大open时的bufferSize |
| 生成的WAV无法播放 | 格式与写出不一致 | 保证AudioFormat对象同一实例 |
4.1 选择指定麦克风
当系统存在多个输入源时,遍历AudioSystem.getMixerInfo,根据名称匹配后再用Mixer.getLine拿到TargetDataLine,可避免录到错误设备。以下片段展示如何枚举混音器。
Mixer.Info[] mixers = AudioSystem.getMixerInfo();
for (Mixer.Info m : mixers) {
System.out.println(m.getName() + " | " + m.getDescription());
}
拿到目标Mixer后,用mixer.getLine(info)替代AudioSystem.getLine,即可绑定具体硬件。这在做语音助手多设备切换时非常实用。
Java_Sound_APITargetDataLineaudio_capture修改时间:2026-08-07 20:33:20