如何在Java中通过AudioSystem终止line.write音频输出并动态调参
解决方案
要实现音频终止和动态参数调整,需要重构原代码的播放逻辑,从一次性生成全部采样改为实时流式生成,同时引入线程和状态控制变量。以下是具体实现步骤和修改后的代码:
核心改动说明
- 将
SourceDataLine设为类成员变量,方便终止时操作 - 添加
volatile修饰的状态变量isPlaying,线程安全控制播放启停 - 重构
play方法为线程播放,循环生成小块采样数据,实时读取最新参数 - 添加
stop方法终止音频输出 - 修改
createSinWaveBuffer为生成指定长度的采样块,适配流式播放
修改后的完整代码
import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; public class SineGenerator { protected static final int SAMPLE_RATE = 16 * 1024; // 每次生成的采样块大小,可根据需求调整 private static final int BUFFER_SIZE = 1024; // 用volatile保证多线程下参数可见性 private volatile int freq; private volatile double amplitude; private volatile boolean inclAng; private volatile double modAmp; private volatile int numMod; private volatile boolean isPlaying; private SourceDataLine line; public SineGenerator(int freq, double amplitude, boolean incAng, double modAmp, int numMod) { this.freq = freq; this.amplitude = amplitude; this.inclAng = incAng; this.modAmp = modAmp; this.numMod = numMod; } // 生成指定长度的采样块 private byte[] createSinWaveBuffer(int length) { byte[] output = new byte[length]; // 维护全局角度累计,保证波形连续 static double globalAngle = 0; for (int i = 0; i < output.length; i++) { globalAngle += 2.0 * Math.PI * freq / SAMPLE_RATE; double angle = globalAngle; if (modAmp <= 0) { output[i] = (byte) (Math.sin(angle) * amplitude); } else { output[i] = (byte) (Math.sin(recur(modAmp, angle, numMod, inclAng)) * 127f); } } return output; } public void play() throws LineUnavailableException { if (isPlaying) return; // 避免重复启动 final AudioFormat af = new AudioFormat(SAMPLE_RATE, 8, 1, true, true); line = AudioSystem.getSourceDataLine(af); line.open(af, BUFFER_SIZE * 2); line.start(); isPlaying = true; // 启动播放线程,避免阻塞主线程 new Thread(() -> { while (isPlaying) { byte[] buffer = createSinWaveBuffer(BUFFER_SIZE); line.write(buffer, 0, buffer.length); } // 播放结束后清理资源 line.drain(); line.close(); line = null; }).start(); } // 终止音频输出 public void stop() { isPlaying = false; if (line != null) { line.stop(); } } public static double recur(double amplitude, double angle, int num, boolean inclAng) { if (num <= 1) { return inclAng ? angle + amplitude * Math.sin(angle) : amplitude * Math.sin(angle); } else { double inner = recur(amplitude, angle, num - 1, inclAng); return inclAng ? angle + amplitude * Math.sin(inner) : amplitude * Math.sin(inner); } } // 所有setter保持原样,volatile修饰的参数会被播放线程实时读取 public void setFreq(int freq) { this.freq = freq; } public void setAmplitude(double amplitude) { this.amplitude = amplitude; } public void setInclAng(boolean inclAng) { this.inclAng = inclAng; } public void setModAmp(double modAmp) { this.modAmp = modAmp; } public void setNumMod(int numMod) { this.numMod = numMod; } }
关键功能说明
- 终止音频:调用
stop()方法时,会将isPlaying设为false,播放线程会退出循环,随后关闭音频线,终止输出。 - 动态修改参数:所有音频参数(频率、振幅等)都用
volatile修饰,播放线程每次生成采样块时都会读取最新的参数值,修改后立刻生效,无需停止播放。 - 流式播放:每次生成
BUFFER_SIZE大小的采样块,避免一次性生成大量数据占用内存,同时保证参数修改的实时性;新增全局角度累计,确保波形连续无断层。
使用示例
public class Main { public static void main(String[] args) throws LineUnavailableException, InterruptedException { SineGenerator generator = new SineGenerator(440, 127, false, 0.5, 2); generator.play(); // 播放3秒后修改频率 Thread.sleep(3000); generator.setFreq(880); // 再播放2秒后终止 Thread.sleep(2000); generator.stop(); } }
内容的提问来源于stack exchange,提问作者user25332023
相关产品推荐
相关产品推荐

