如何基于javax.sound.sampled在Processing中查询多通道音频输入线路及振幅
嘿,我之前在Processing里做过多通道音频电平可视化,刚好踩过javax.sound.sampled的坑,给你一步步拆解解决方案:
第一步:查询系统可用的音频输入线路数量
javax.sound.sampled里用Mixer表示音频设备,输入线路则对应TargetDataLine接口。我们可以遍历所有系统音频设备,筛选出支持输入线路的设备,就能统计数量了。
直接上Processing里可运行的代码:
import javax.sound.sampled.*; void setup() { size(400, 200); // 获取所有音频设备的信息 Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); int inputLineCount = 0; println("=== 可用音频输入线路 ==="); for (Mixer.Info info : mixerInfos) { Mixer mixer = AudioSystem.getMixer(info); // 检查当前设备是否支持输入线路 if (mixer.isLineSupported(new Line.Info(TargetDataLine.class))) { inputLineCount++; println(inputLineCount + ". " + info.getName() + "\n 描述:" + info.getDescription()); } } println("\n共找到 " + inputLineCount + " 条输入线路"); } void draw() { background(0); fill(255); text("查看控制台输出的输入线路列表", 20, 100); }
运行后控制台会列出所有可用于输入的音频设备,比如内置麦克风、外接USB声卡等。
第二步:获取每条线路的音频振幅
要获取振幅,需要先打开目标输入线路,读取PCM音频数据,再将字节数据转换成可计算的样本值,最后统计振幅(常用平均振幅或峰值)。
这里给一个支持立体声的完整示例,多通道只需要修改音频格式和样本解析逻辑即可:
import javax.sound.sampled.*; TargetDataLine activeInputLine; // 存储最近60帧的振幅数据,用于可视化 float[] channelAmplitudes; int channelCount = 2; // 立体声,可修改为对应通道数 Thread audioReadThread; void setup() { size(800, 400); background(0); // 选择第一个可用的输入线路(你也可以让用户手动选择) Mixer targetMixer = findFirstInputMixer(); if (targetMixer == null) { println("未找到可用音频输入线路"); exit(); return; } // 配置音频格式:44100采样率,16位深度,对应通道数,小端字节序,有符号 AudioFormat audioFormat = new AudioFormat(44100, 16, channelCount, true, false); try { // 打开并启动输入线路 activeInputLine = (TargetDataLine) targetMixer.getLine(new Line.Info(TargetDataLine.class)); activeInputLine.open(audioFormat); activeInputLine.start(); // 初始化振幅存储数组 channelAmplitudes = new float[channelCount]; // 启动独立线程读取音频数据,避免阻塞Processing的draw循环 audioReadThread = new Thread(new AudioProcessor()); audioReadThread.start(); } catch (LineUnavailableException e) { e.printStackTrace(); exit(); } } void draw() { background(0); stroke(255); // 绘制每个通道的电平条 float barWidth = width / channelCount; for (int i = 0; i < channelCount; i++) { float barHeight = channelAmplitudes[i] * height; rect(i * barWidth, height - barHeight, barWidth - 5, barHeight); fill(255); text("通道 " + (i+1), i * barWidth + 10, height - 10); noFill(); } } // 查找第一个支持输入的音频设备 Mixer findFirstInputMixer() { Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); for (Mixer.Info info : mixerInfos) { Mixer mixer = AudioSystem.getMixer(info); if (mixer.isLineSupported(new Line.Info(TargetDataLine.class))) { println("使用输入设备:" + info.getName()); return mixer; } } return null; } // 音频数据处理线程 class AudioProcessor implements Runnable { public void run() { byte[] buffer = new byte[2048]; // 每次读取的字节数,可根据需求调整 while (!Thread.currentThread().isInterrupted()) { int bytesRead = activeInputLine.read(buffer, 0, buffer.length); if (bytesRead <= 0) break; // 计算每个通道的平均振幅 float[] channelSums = new float[channelCount]; int samplePerChannel = bytesRead / (2 * channelCount); // 16位样本占2字节 for (int i = 0; i < bytesRead; i += 2 * channelCount) { for (int ch = 0; ch < channelCount; ch++) { // 将两个字节转换为16位有符号样本值 short sample = (short)((buffer[i + 1 + 2*ch] << 8) | (buffer[i + 2*ch] & 0xFF)); // 转换为0-1的振幅比例(16位样本最大绝对值为32767) channelSums[ch] += abs(sample) / 32767.0; } } // 更新每个通道的平均振幅 for (int ch = 0; ch < channelCount; ch++) { channelAmplitudes[ch] = channelSums[ch] / samplePerChannel; } } } } // 程序停止时清理资源 void stop() { if (activeInputLine != null) { activeInputLine.stop(); activeInputLine.close(); } if (audioReadThread != null) { audioReadThread.interrupt(); } super.stop(); }
一些关键注意点:
- 如果需要支持更多通道,只需要修改
channelCount变量,并确保音频格式的通道数与设备支持的一致。 - 不同设备支持的音频格式可能不同,你可以通过
Mixer.getLineInfo()获取设备支持的所有格式,选择兼容的配置。 - 振幅计算可以替换为峰值(记录读取周期内的最大样本值),这样视觉反馈会更灵敏。
内容的提问来源于stack exchange,提问作者matt
相关产品推荐
相关产品推荐

