You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS语音应用启用AEC后仍存在TTS音频回灌麦克风问题求助

iOS语音应用启用AEC后仍存在TTS音频回灌麦克风问题求助

老哥我太懂你这个坑了!之前做类似的语音交互APP时,在老款iPhone SE上也栽过同样的跟头——系统自带的AEC有时候对TTS这类非实时通话的音频,处理效果确实拉胯。结合我踩过的坑,给你几个具体的解决思路,亲测有效:

1. 先把AVAudioSession的配置掰正

你当前的配置里有几个可能拖后腿的地方,先调整试试:

let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setCategory(
        .playAndRecord,
        mode: .voiceChat,
        options: [
            .allowBluetooth, // 只留这个,A2DP是纯播放模式,不支持双向音频的AEC
            .defaultToSpeaker // 强制用扬声器播放,让AEC更精准匹配硬件路径
        ]
    )
    // 一定要激活会话,并且加上这个选项避免打断其他音频
    try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
    print("音频会话配置失败: \(error.localizedDescription)")
}

重点说下:

  • 去掉.duckOthers:这个是让其他APP声音变小,和AEC没关系,反而可能干扰系统对当前音频路径的判断
  • 删掉.allowBluetoothA2DP:A2DP是蓝牙立体声播放模式,不支持双向音频的回声消除逻辑,换成普通的.allowBluetooth(对应HFP通话模式)才会触发AEC
  • 加上.defaultToSpeaker:老款SE的听筒和麦克风距离近,用扬声器播放时系统AEC的算法会更适配

2. 用AVAudioUnitVoiceProcessing手动强化AEC

系统的voiceChat模式AEC是针对实时通话优化的,对TTS这类预渲染的音频,有时候识别不准。可以直接用AVAudioUnitVoiceProcessing这个专门的语音处理单元,手动接管AEC逻辑:

import AVFoundation

class AudioProcessor {
    private let engine = AVAudioEngine()
    private let voiceProcessingUnit = AVAudioUnitVoiceProcessing()
    
    func setupAudioPipeline() {
        let inputNode = engine.inputNode
        let inputFormat = inputNode.inputFormat(forBus: 0)
        
        // 启用语音处理单元的AEC和降噪
        voiceProcessingUnit.isVoiceProcessingEnabled = true
        voiceProcessingUnit.isBypassEffect = false
        
        // 连接音频节点
        engine.attach(voiceProcessingUnit)
        engine.connect(inputNode, to: voiceProcessingUnit, format: inputFormat)
        engine.connect(voiceProcessingUnit, to: engine.outputNode, format: inputFormat)
        
        // 启动音频引擎
        do {
            try engine.start()
        } catch {
            print("音频引擎启动失败: \(error.localizedDescription)")
        }
    }
}

这个单元是苹果专门为语音交互做的,AEC效果比系统会话模式的自动处理更刚性,能精准过滤掉APP自身播放的TTS音频。

3. 结合TTS播放状态,动态调整语音检测阈值

如果上面的方法还没完全解决,那就从语音检测的逻辑上补漏:用AVSpeechSynthesizer的代理方法,监控TTS的播放状态,在播放期间临时提高语音检测的音量阈值,过滤掉TTS的回灌噪音:

class TTSManager: NSObject, AVSpeechSynthesizerDelegate {
    let synthesizer = AVSpeechSynthesizer()
    var isTTSSpeaking = false
    
    override init() {
        super.init()
        synthesizer.delegate = self
    }
    
    func speak(text: String) {
        let utterance = AVSpeechUtterance(string: text)
        synthesizer.speak(utterance)
    }
    
    // TTS开始播放时标记状态
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
        isTTSSpeaking = true
        // 这里通知语音检测模块,把触发阈值提高10-15dB(根据你TTS的音量调整)
        NotificationCenter.default.post(name: NSNotification.Name("TTSSpeakingStarted"), object: nil)
    }
    
    // TTS结束播放时恢复阈值
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        isTTSSpeaking = false
        NotificationCenter.default.post(name: NSNotification.Name("TTSSpeakingEnded"), object: nil)
    }
}

比如你的语音检测模块里,正常状态下-40dB触发,TTS播放时就改成-25dB触发,这样TTS的回灌音量一般达不到这个阈值,只有用户真实说话的音量才会触发。

4. 针对iPhone SE的硬件特殊处理

老款iPhone SE的音频硬件没有新款机型的多麦克风降噪,所以额外加个小技巧:TTS播放时,通过AVAudioSession临时降低录音输入的增益,播放结束再恢复:

// TTS开始播放时调用
func lowerMicGainDuringTTS() {
    let audioSession = AVAudioSession.sharedInstance()
    do {
        // 把麦克风增益降到原来的一半(根据实际情况调整)
        try audioSession.setInputGain(0.5)
    } catch {
        print("调整麦克风增益失败: \(error.localizedDescription)")
    }
}

// TTS结束时恢复
func restoreMicGain() {
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setInputGain(1.0)
    } catch {
        print("恢复麦克风增益失败: \(error.localizedDescription)")
    }
}

你可以先从调整音频会话配置开始试,这个成本最低,不行再上手动AEC单元,最后用阈值调整兜底,应该能解决问题!

火山引擎 最新活动