You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按需生成音频写入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:30:04