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

AVAudioEngine连接inputNode与mainMixerNode时崩溃问题排查

问题描述

我用以下代码将AVAudioEngine的inputNode连接到mainMixerNode:

public func setupAudioEngine() {
    self.engine = AVAudioEngine()

    let format = engine.inputNode.inputFormat(forBus: 0)
    //main mixer node is connected to output node by default
    engine.connect(self.engine.inputNode, to: self.engine.mainMixerNode, format: format)

    do {
        engine.prepare()
        try self.engine.start()
    }
    catch {
        print("error couldn't start engine")
    }

    engineRunning = true
}

但在Crashlytics控制台中出现了无法复现的崩溃,崩溃信息如下:

Fatal Exception: com.apple.coreaudio.avfaudio
required condition is false: IsFormatSampleRateAndChannelCountValid(format)

调用setupAudioEngine前,我已经确认AVAudioSession类别不是仅播放类(麦克风不可用的类别),该函数是在处理音频路由变化通知时调用的,并且专门做了这个条件检查。请问哪里操作有误?

完整崩溃堆栈:

Fatal Exception: com.apple.coreaudio.avfaudio
0  CoreFoundation                 0x99288 __exceptionPreprocess
1  libobjc.A.dylib                0x16744 objc_exception_throw
2  CoreFoundation                 0x17048c -[NSException initWithCoder:]
3  AVFAudio                       0x9f64 AVAE_RaiseException(NSString*, ...)
4  AVFAudio                       0x55738 AVAudioEngineGraph::_Connect(AVAudioNodeImplBase*, AVAudioNodeImplBase*, unsigned int, unsigned int, AVAudioFormat*)
5  AVFAudio                       0x5cce0 AVAudioEngineGraph::Connect(AVAudioNode*, AVAudioNode*, unsigned long, unsigned long, AVAudioFormat*)
6  AVFAudio                       0xdf1a8 AVAudioEngineImpl::Connect(AVAudioNode*, AVAudioNode*, unsigned long, unsigned long, AVAudioFormat*)
7  AVFAudio                       0xe0fc8 -[AVAudioEngine connect:to:format:]
8  MyApp                       0xa6af8 setupAudioEngine + 701 (MicrophoneOutput.swift:701)
9  MyApp                       0xa46f0 handleRouteChange + 378 (MicrophoneOutput.swift:378)
10 MyApp                       0xa4f50 @objc MicrophoneOutput.handleRouteChange(note:)
11 CoreFoundation                 0x2a834 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
12 CoreFoundation                 0xc6fd4 ___CFXRegistrationPost_block_invoke
13 CoreFoundation                 0x9a1d0 _CFXRegistrationPost
14 CoreFoundation                 0x408ac _CFXNotificationPost
15 Foundation                     0x1b754 -[NSNotificationCenter postNotificationName:object:userInfo:]
16 AudioSession                   0x56f0 (anonymous namespace)::HandleRouteChange(AVAudioSession*, NSDictionary*)
17 AudioSession                   0x5cbc invocation function for block in avfaudio::AVAudioSessionPropertyListener(void*, unsigned int, unsigned int, void const*)
18 libdispatch.dylib              0x1e6c _dispatch_call_block_and_release
19 libdispatch.dylib              0x3a30 _dispatch_client_callout
20 libdispatch.dylib              0x11f48 _dispatch_main_queue_drain
21 libdispatch.dylib              0x11b98 _dispatch_main_queue_callback_4CF
22 CoreFoundation                 0x51800 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
23 CoreFoundation                 0xb704 __CFRunLoopRun
24 CoreFoundation                 0x1ebc8 CFRunLoopRunSpecific
25 GraphicsServices               0x1374 GSEventRunModal 
26 UIKitCore                      0x514648 -[UIApplication _run]
27 UIKitCore                      0x295d90 UIApplicationMain
28 libswiftUIKit.dylib            0x30ecc UIApplicationMain(_:_:_:_:)
29 MyApp                       0xc358 main (WhiteBalanceUI.swift)
30 ???                            0x104b1dce4 (Missing) 
问题分析与解决方案

核心原因

崩溃提示IsFormatSampleRateAndChannelCountValid(format)不成立,说明获取的format无效——要么采样率非法,要么声道数异常。即便检查了AVAudioSession类别,在音频路由变化的瞬间,inputNode的格式可能还未完成初始化,或者路由切换后麦克风设备暂时不可用,导致inputFormat(forBus:0)返回无效格式。

另外,每次调用setupAudioEngine都会重新创建AVAudioEngine实例,但旧实例如果未正确停止/断开,会和新实例争夺音频资源,间接引发inputNode格式异常。

修复步骤

  1. 添加格式有效性检查
    在使用format前先验证合法性,避免传入无效格式导致崩溃:

    let format = engine.inputNode.inputFormat(forBus: 0)
    guard format.sampleRate > 0, format.channelCount > 0 else {
        print("Invalid input format, aborting setup")
        return
    }
    engine.connect(self.engine.inputNode, to: self.engine.mainMixerNode, format: format)
    
  2. 正确清理旧AVAudioEngine实例
    重新创建引擎前,先停止并重置旧实例,释放音频资源:

    public func setupAudioEngine() {
        // 清理旧引擎
        if let existingEngine = self.engine {
            existingEngine.stop()
            existingEngine.disconnectNodeOutput(existingEngine.inputNode)
            existingEngine.reset()
        }
        
        self.engine = AVAudioEngine()
        
        // 后续格式检查和连接逻辑...
    }
    
  3. 延迟执行setup逻辑(可选)
    路由变化通知触发时系统音频状态可能不稳定,延迟几十毫秒再执行setup,可避免瞬间设备不可用问题:

    func handleRouteChange(note: NSNotification) {
        // 检查会话类别后...
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
            self?.setupAudioEngine()
        }
    }
    
  4. 捕获AVAudioEngine连接时的异常
    将connect方法放入do-catch块,避免未捕获异常:

    do {
        let format = engine.inputNode.inputFormat(forBus: 0)
        guard format.sampleRate > 0, format.channelCount > 0 else {
            throw NSError(domain: "AudioSetup", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid input format"])
        }
        engine.connect(self.engine.inputNode, to: self.engine.mainMixerNode, format: format)
        
        engine.prepare()
        try self.engine.start()
        engineRunning = true
    }
    catch {
        print("Audio engine setup failed: \(error.localizedDescription)")
        engineRunning = false
    }
    

补充说明

音频路由变化场景复杂(如插拔耳机、切换蓝牙设备),系统需要短暂时间完成设备切换。此时直接访问inputNode格式可能得到临时无效值,通过格式校验、资源清理和延迟执行,可覆盖绝大多数边缘情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:06:27