如何在Java中同时录制麦克风与扬声器音频并获取对应设备音频?
嘿,我来帮你搞定Java里同时录制麦克风和扬声器音频的问题,还有怎么获取Line-In(麦克风)和Line-Out(扬声器)的音频信号——这俩需求核心都是Java Sound API的使用,不过扬声器录制得注意点系统相关的细节,咱们一步步来:
不管是录麦克风还是扬声器,第一步得先找到对应的硬件设备。Java Sound API用Mixer类来管理所有音频设备,咱们可以枚举所有可用的混频器,然后筛选出支持输入(Line-In)和输出(Line-Out)的设备。
枚举所有音频设备的代码示例
import javax.sound.sampled.*; public class AudioDeviceLister { public static void main(String[] args) { Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); for (Mixer.Info info : mixerInfos) { Mixer mixer = AudioSystem.getMixer(info); // 检查是否支持输入线路(TargetDataLine对应麦克风/Line-In) Line.Info[] targetLineInfos = mixer.getTargetLineInfo(); // 检查是否支持输出线路(SourceDataLine对应扬声器/Line-Out) Line.Info[] sourceLineInfos = mixer.getSourceLineInfo(); System.out.println("设备名称: " + info.getName()); System.out.println("描述: " + info.getDescription()); System.out.println("支持输入线路: " + (targetLineInfos.length > 0)); System.out.println("支持输出线路: " + (sourceLineInfos.length > 0)); System.out.println("---"); } } }
运行这段代码后,你会看到所有设备的信息:比如Windows上的“立体声混音”就是用来捕获扬声器输出的设备,Mac上可能需要先配置Soundflower或者聚合设备,Linux可以用PulseAudio的回环模块。
麦克风属于输入音频设备,用TargetDataLine来捕获它的音频流,步骤很清晰:
步骤1:定义统一的音频格式
首先得确定音频的参数,比如采样率44100Hz、16位深度、立体声/单声道,这些参数要和设备支持的格式匹配:
AudioFormat format = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, 44100.0f, // 采样率 16, // 位深 2, // 声道数(立体声) 4, // 帧大小(16位*2声道=4字节) 44100.0f, // 帧速率 false // 是否大端字节序 );
步骤2:打开输入线路并录制
找到对应的麦克风设备后,打开TargetDataLine,然后读取音频数据到缓冲区,最后保存为WAV文件:
import javax.sound.sampled.*; import java.io.File; import java.io.IOException; public class MicRecorder { public static void main(String[] args) throws LineUnavailableException, IOException { AudioFormat format = new AudioFormat(44100.0f, 16, 2, true, false); DataLine.Info lineInfo = new DataLine.Info(TargetDataLine.class, format); // 这里可以指定具体的麦克风混频器,比如从之前的枚举结果里选 TargetDataLine line = (TargetDataLine) AudioSystem.getLine(lineInfo); line.open(format); line.start(); // 保存到WAV文件 AudioInputStream ais = new AudioInputStream(line); AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("mic_recording.wav")); // 停止录制的逻辑(比如按回车停止) System.in.read(); line.stop(); line.close(); ais.close(); } }
这个稍微麻烦点,因为Java Sound API没有直接捕获系统输出的接口,得用回环捕获的方式:把扬声器输出的音频路由到一个虚拟输入设备里,然后捕获这个虚拟设备的信号。
关键点:找到回环设备
- Windows:需要启用“立体声混音”设备(在声音设置的录制选项卡,右键显示禁用的设备,然后启用它)
- Mac:安装Soundflower或者用系统自带的“聚合设备”创建回环
- Linux:启用PulseAudio的回环模块
找到回环设备后,录制逻辑和麦克风完全一样——只是把目标设备换成回环设备就行,比如修改上面的代码:
// 枚举时找到回环设备的Info,比如名称包含“立体声混音” Mixer.Info loopbackMixerInfo = null; Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); for (Mixer.Info info : mixerInfos) { if (info.getName().contains("立体声混音")) { loopbackMixerInfo = info; break; } } // 用回环设备打开TargetDataLine Mixer mixer = AudioSystem.getMixer(loopbackMixerInfo); TargetDataLine line = (TargetDataLine) mixer.getLine(lineInfo);
要同时录两个设备,需要开两个独立的线程分别处理麦克风和扬声器的捕获,然后可以选择:
- 分别保存为两个独立的音频文件
- 把两个音频流混合成一个立体声文件(比如左声道麦克风,右声道扬声器)
双线程录制+混合的示例框架
import javax.sound.sampled.*; import java.io.File; public class DualRecorder { private static final AudioFormat FORMAT = new AudioFormat(44100.0f, 16, 2, true, false); private static TargetDataLine micLine; private static TargetDataLine loopbackLine; private static final int BUFFER_SIZE = FORMAT.getFrameSize() * 1024; public static void main(String[] args) throws Exception { // 初始化麦克风线路 DataLine.Info micInfo = new DataLine.Info(TargetDataLine.class, FORMAT); micLine = (TargetDataLine) AudioSystem.getLine(micInfo); micLine.open(FORMAT); // 初始化回环线路(扬声器) Mixer.Info loopbackInfo = findLoopbackMixer(); Mixer loopbackMixer = AudioSystem.getMixer(loopbackInfo); DataLine.Info loopbackLineInfo = new DataLine.Info(TargetDataLine.class, FORMAT); loopbackLine = (TargetDataLine) loopbackMixer.getLine(loopbackLineInfo); loopbackLine.open(FORMAT); // 启动两个录制线程 Thread micThread = new Thread(new MicRecorderRunnable()); Thread loopbackThread = new Thread(new LoopbackRecorderRunnable()); micThread.start(); loopbackThread.start(); // 等待用户输入停止 System.out.println("按回车停止录制..."); System.in.read(); // 停止录制 micLine.stop(); micLine.close(); loopbackLine.stop(); loopbackLine.close(); } private static Mixer.Info findLoopbackMixer() { // 这里根据系统返回对应的回环设备Info,比如Windows的“立体声混音” Mixer.Info[] infos = AudioSystem.getMixerInfo(); for (Mixer.Info info : infos) { if (info.getName().contains("立体声混音")) { return info; } } throw new RuntimeException("未找到回环设备,请检查系统音频设置"); } static class MicRecorderRunnable implements Runnable { @Override public void run() { byte[] buffer = new byte[BUFFER_SIZE]; while (micLine.isRunning()) { int bytesRead = micLine.read(buffer, 0, buffer.length); // 这里可以将麦克风数据写入左声道,和回环数据混合后保存 // 注意需要同步两个线程的缓冲区,避免时间偏移 } } } static class LoopbackRecorderRunnable implements Runnable { @Override public void run() { byte[] buffer = new byte[BUFFER_SIZE]; while (loopbackLine.isRunning()) { int bytesRead = loopbackLine.read(buffer, 0, buffer.length); // 这里可以将回环数据写入右声道,和麦克风数据混合后保存 } } } }
注意事项
- 音频格式必须统一:麦克风和扬声器的录制格式要完全一致,否则混合会出现杂音
- 线程同步:两个录制线程的缓冲区需要同步,避免时间偏移导致音画不同步
- 权限问题:Mac和Windows都需要给程序授予音频录制权限,否则会报错
- 设备兼容性:不同系统的回环设备名称不一样,最好让用户手动选择设备,不要硬编码名称
内容的提问来源于stack exchange,提问作者Ha Thi Hong

