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

AVAudioEngine中断后无法重启,报错561145187的解决咨询

解决AVAudioEngine中断后重启失败的问题

错误原因分析

错误码561145187对应AVAudioSessionErrorCannotStartPlaying,核心问题是你的中断处理流程存在资源清理不彻底、音频会话状态管理错误、引擎节点配置缺失的问题:

  • 中断开始时未正确移除输入节点的Tap、未去激活音频会话
  • 中断结束时重复创建引擎实例却未清理旧资源,会话激活时机错误,且缺少必要的节点连接逻辑
  • 使用try!隐藏了潜在错误,无法排查会话配置问题

修正后的中断处理实现

@objc func handleInterruption(notification: Notification) {
    guard let userInfo = notification.userInfo,
          let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
          let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
        return
    }

    switch type {
    case .began:
        print("interrupt begin")
        // 1. 停止Socket收发
        socket.close()
        socket = nil
        
        // 2. 彻底清理音频引擎资源
        audioEngine.inputNode.removeTap(onBus: 0) // 必须移除Tap,否则资源泄漏
        audioEngine.stop()
        do {
            try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
        } catch {
            print("Deactivate audio session failed: \(error)")
        }
        print("Audio engine stopped")
        
    case .ended:
        print("interrupt end")
        // 1. 重新初始化Socket
        socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
        DispatchQueue.global(qos: .background).async { [weak self] in
            guard let self = self else { return }
            do {
                self.socket.setIPv4Enabled(true)
                self.socket.setIPv6Enabled(false)
                try self.socket.connect(toHost: "239.10.10.100", onPort: 4545)
                try self.socket.beginReceiving()
                print("Socket started")
            } catch {
                print("Socket Started Error: \(error)")
            }
        }
        
        // 2. 重新配置音频会话
        do {
            try AVAudioSession.sharedInstance().setCategory(.playAndRecord, 
                                                             mode: .voiceChat, 
                                                             options: [.allowBluetooth, .allowBluetoothA2DP])
            try AVAudioSession.sharedInstance().setActive(true)
            print("Audio session activated")
        } catch {
            print("Audio session config failed: \(error)")
            return
        }
        
        // 3. 重置音频引擎(不需要重新创建实例,重置即可)
        audioEngine.reset()
        
        // 4. 重新配置输入节点
        do {
            try audioEngine.inputNode.setVoiceProcessingEnabled(true)
        } catch {
            print("Enable voice processing failed: \(error)")
            return
        }
        
        // 5. 安装输入Tap(捕获错误,避免强制解包)
        let inputFormat = audioEngine.inputNode.inputFormat(forBus: 0)
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { [weak self] buffer, time in
            guard let self = self else { return }
            do {
                let inputBlock: AVAudioConverterInputBlock = { _, outStatus in
                    outStatus.pointee = .haveData
                    return buffer
                }
                let frameCapacity = AVAudioFrameCount(self.outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate)
                guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: self.outputFormat, frameCapacity: frameCapacity) else {
                    print("Create output buffer failed")
                    return
                }
                var error: NSError?
                self.converter.convert(to: outputBuffer, error: &error, withInputFrom: inputBlock)
                if let error = error {
                    print("Convert audio format failed: \(error)")
                    return
                }
                
                let data = Data(buffer: UnsafeBufferPointer(start: outputBuffer.int16ChannelData![0], count: Int(outputBuffer.frameLength)))
                DispatchQueue.global(qos: .background).async { [weak self] in
                    guard let self = self else { return }
                    do {
                        try self.socket.send(data, withTimeout: 0, tag: 0)
                    } catch {
                        print("Socket send Error: \(error)")
                    }
                }
            } catch {
                print("Tap processing error: \(error)")
            }
        }
        
        // 6. 启动引擎
        audioEngine.prepare()
        do {
            try audioEngine.start()
            print("Audio engine started")
        } catch {
            print("Can't start the engine: \(error)")
        }
        
    default:
        print("Unknown interruption type")
    }
}

关键修复点说明

  • 中断开始时的资源清理:必须移除输入节点的Tap,否则旧Tap会占用音频资源导致重启失败;同时去激活音频会话,释放系统音频资源
  • 避免重复创建引擎实例:使用audioEngine.reset()重置引擎即可,无需重新创建,减少资源开销和潜在冲突
  • 正确的会话激活时机:先配置会话类别,再激活会话,确保会话状态正确后再配置引擎
  • 移除强制解包和try!:替换为do-catch捕获错误,便于排查配置过程中的问题
  • Socket初始化优化:将Socket的重新初始化放在音频会话配置之前,避免音频启动延迟

内容的提问来源于stack exchange,提问作者ridvan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:22:08