导读:本期聚焦于小伙伴创作的《Java怎么用Sound API从麦克风实时录音并保存为WAV文件》,敬请观看详情。想做语音采集却卡在录音这一步?Java标准库里的Sound API其实自带了完整的麦克风捕获能力。核心是要拿到TargetDataLine,用AudioFormat定好采样率、位深和通道数,再开一个线程循环read字节数组,把 PCM 数据直接写进ByteArrayOutputStream。最后用AudioSystem写出WAV,注意AudioInputStream必须包住格式和流,否则播放器读不出头。本文给出可跑的通代码,并说明缓冲区大小怎么算、停止录音如何优雅打断阻塞读取,以及常见Linux权限问题怎么处理。

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

Java怎么用Sound API从麦克风实时录音并保存为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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。