如何按需生成音频写入SourceDataLine,替代不间断写入?
按需生成动态音频并响应状态变化的实现方案
当然可以实现按需生成音频数据到SourceDataLine,让声音实时响应用户输入等状态变化,无需依赖Thread.sleep等待缓冲区时长。以下是几种高效的实现思路:
方案1:利用SourceDataLine.available()判断缓冲区可用空间
SourceDataLine的available()方法会返回当前音频缓冲区中可写入的字节数。我们可以循环检查这个值,当可用空间足够容纳一个完整的音频缓冲区时,再生成对应的数据并写入,这样就能做到按需填充缓冲区,避免无意义的持续生成。
修改后的代码示例
import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer import javax.sound.sampled.* import kotlin.math.PI import kotlin.math.sin const val SAMPLING_RATE = 44100F const val BITS16 = 16 const val STEREO = 2 val format = AudioFormat( AudioFormat.Encoding.PCM_SIGNED, SAMPLING_RATE, BITS16, STEREO, BITS16 * STEREO / 8, SAMPLING_RATE * BITS16 * STEREO / 8, false) const val BUFFER_SIZE = 4096 // 用于响应状态变化的全局变量(示例:当前播放频率) @Volatile var currentFrequency = 440F var phase = 0F fun generateBuffer(n: Int, buffer: FloatBuffer) { // 根据当前状态生成音频(此处用currentFrequency响应外部输入) repeat(n) { buffer.put(sin((phase + it * 2 * PI * currentFrequency / SAMPLING_RATE).toFloat())) } phase += (n * 2 * PI * currentFrequency / SAMPLING_RATE).toFloat() } fun main() { val dataLineInfo = DataLine.Info( SourceDataLine::class.java, format) val dataLine = AudioSystem.getLine(dataLineInfo) as SourceDataLine dataLine.open() val byteBuffer = ByteBuffer.allocate(2 * 2 * BUFFER_SIZE).order(ByteOrder.LITTLE_ENDIAN) val intBuffer = byteBuffer.asIntBuffer() val floatBuffer = FloatBuffer.allocate(BUFFER_SIZE) dataLine.start() // 按需生成并写入音频数据 while (!Thread.currentThread().isInterrupted) { // 等待缓冲区有足够空间容纳一个完整的BUFFER_SIZE帧 if (dataLine.available() >= format.frameSize * BUFFER_SIZE) { floatBuffer.rewind() generateBuffer(BUFFER_SIZE, floatBuffer) floatBuffer.rewind() intBuffer.rewind() repeat(BUFFER_SIZE) { val sample = (floatBuffer.get() * 32767).toInt() // 立体声:左右声道写入相同样本 intBuffer.put(sample) intBuffer.put(sample) } dataLine.write(byteBuffer.array(), 0, byteBuffer.capacity()) byteBuffer.rewind() } // 短暂让出CPU,避免空循环占用过高资源 Thread.yield() } dataLine.drain() dataLine.stop() dataLine.close() }
方案2:使用LineListener监听缓冲区状态变化
通过实现LineListener接口,监听音频线的UPDATE事件,当缓冲区有可用空间时触发数据生成逻辑。这种方式更符合事件驱动的设计,适合复杂的音频处理场景:
核心实现逻辑
class AudioBufferListener(private val dataLine: SourceDataLine, private val generator: () -> ByteArray) : LineListener { override fun update(event: LineEvent) { if (event.type == LineEvent.Type.UPDATE && dataLine.available() >= format.frameSize * BUFFER_SIZE) { val audioData = generator() dataLine.write(audioData, 0, audioData.size) } } } // 在main函数中添加监听 dataLine.addLineListener(AudioBufferListener(dataLine) { // 此处生成音频数据的逻辑 floatBuffer.rewind() generateBuffer(BUFFER_SIZE, floatBuffer) // ... 转换为字节数组的逻辑 byteBuffer.array() })
关键注意事项
- 线程安全:如果用户输入在主线程处理,音频生成在后台线程,需要用
@Volatile修饰状态变量(如示例中的currentFrequency),确保线程间状态可见性。 - 避免阻塞:不要在音频生成线程中处理耗时的用户输入逻辑,保持音频线程专注于数据生成和写入,防止音频卡顿。
- 资源释放:程序退出时务必调用
drain()、stop()和close()方法,确保音频缓冲区数据播放完毕并释放资源。
内容的提问来源于stack exchange,提问作者user2649681
相关产品推荐
相关产品推荐

