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

Android如何为音频播放添加延迟?语音干扰器开发求助

Android 实现音频录制后延迟500ms播放的解决方案

问题背景

开发语音干扰器应用,需要实现麦克风录制音频后延迟约500ms再通过耳机播放。当前已实现实时录制播放,但添加延迟的尝试未成功:

  • 延迟启动audioTrack.play(),无法播放启动前录制的音频
  • 将录制的shortAudioData存入ArrayList后再播放,未达到预期效果

现有核心代码:

val audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC,
    intRecordSampleRate,
    AudioFormat.CHANNEL_IN_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    intBufferSize
)

audioTrack = AudioTrack(
    AudioManager.STREAM_MUSIC,
    intRecordSampleRate,
    AudioFormat.CHANNEL_IN_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    intBufferSize,
    AudioTrack.MODE_STREAM,
)

audioTrack.playbackRate = intRecordSampleRate
audioRecord.startRecording()
audioTrack.play()

while (isActive){
    audioRecord.read(shortAudioData, 0, shortAudioData.size)
    shortAudioList.add(shortAudioData)
    audioTrack.write(shortAudioData, 0, shortAudioData.size)
}

问题分析

  1. 延迟启动播放:AudioTrack启动前的录制数据未被缓存,自然无法回放
  2. ArrayList存储失败:直接添加shortAudioData的引用,而该数组会被每次read操作覆盖,导致列表中所有元素都是同一数组的最新数据,没有保留历史录制内容

解决方案

通过缓存音频数据到队列,先积累足够500ms的音频量后再开始播放,同时避免数组引用覆盖问题:

  1. 计算500ms所需的音频样本总量:基于采样率和声道数计算缓存阈值
  2. 使用队列存储复制后的音频数组,确保历史数据不被覆盖
  3. 缓存达到阈值后,同步进行"存新数据-取旧数据播放"的循环,维持稳定延迟

修改后的完整代码:

val audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC,
    intRecordSampleRate,
    AudioFormat.CHANNEL_IN_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    intBufferSize
)

audioTrack = AudioTrack(
    AudioManager.STREAM_MUSIC,
    intRecordSampleRate,
    AudioFormat.CHANNEL_IN_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    intBufferSize,
    AudioTrack.MODE_STREAM,
)

audioTrack.playbackRate = intRecordSampleRate
audioRecord.startRecording()
audioTrack.play()

// 配置延迟参数
val delayMs = 500
// 计算500ms对应的short样本数:采样率 * 声道数(2) * 延迟秒数
val requiredCacheSize = (intRecordSampleRate * 2 * (delayMs / 1000.0)).toInt()
var cachedSampleCount = 0
val audioQueue = LinkedList<ShortArray>()

while (isActive) {
    val readCount = audioRecord.read(shortAudioData, 0, shortAudioData.size)
    if (readCount > 0) {
        // 复制录制的音频数据,避免原数组被后续read覆盖
        val audioCopy = shortAudioData.copyOf(readCount)
        audioQueue.add(audioCopy)
        cachedSampleCount += readCount

        // 当缓存的样本量达到延迟要求后,开始播放
        if (cachedSampleCount >= requiredCacheSize) {
            val playData = audioQueue.poll()
            playData?.let {
                audioTrack.write(it, 0, it.size)
                cachedSampleCount -= it.size
            }
        }
    }
}

// 停止时清理资源
audioRecord.stop()
audioRecord.release()
audioTrack.stop()
audioTrack.release()

关键细节说明

  • 数组复制:必须通过copyOf()复制录制的音频数据,否则队列中存储的都是同一数组的引用,数据会被后续录制操作覆盖
  • 缓存阈值计算:确保缓存足够500ms的音频后再开始播放,精准控制延迟时间
  • 队列同步:通过LinkedList实现队列的先进先出,保证播放顺序和录制顺序一致

内容的提问来源于stack exchange,提问作者Kaan Karagöz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:50:37