AudioKit调用Mixer.removeAllInputs崩溃及节点链单节点分离问题求助
我正在用AudioKit处理带音效的音频文件,修改了Cookbook示例代码如下:
class BitCrusherConductor: ObservableObject, ProcessesPlayerInput { let engine = AudioEngine() let player = AudioPlayer() let bitcrusher: BitCrusher let dryWetMixer: DryWetMixer let buffer: AVAudioPCMBuffer let mixer = Mixer() init() { buffer = Cookbook.sourceBuffer player.buffer = buffer player.isLooping = true bitcrusher = BitCrusher(player) dryWetMixer = DryWetMixer(player, bitcrusher) mixer.addInput(dryWetMixer) ///< ----- mixer添加dryWetMixer作为输入 engine.output = mixer } func remove() { self.mixer.removeAllInputs() ///< - 调用此方法会崩溃 } }
调用removeAllInputs()时会触发disconnectAndDetachIfLast,断点调试日志显示AudioPlayer实例被重复处理,最终崩溃报错:
AVAEInternal.h:125 [AUInterface.mm:539:SetFormat: ([[busArray objectAtIndexedSubscript:(NSUInteger)element] setFormat:format error:&nsErr])] returned false, error Error Domain=NSOSStatusErrorDomain Code=-10868 "(null)"
问题1
需要调用mixer.removeAllInputs()来更新已添加到混音器的节点(如删除或新增节点),请问如何正确连接DryWetMixer与Mixer,并安全调用removeAllInputs?
问题2
如何从节点链(例如player -> Delay -> Mixer)中单独分离单个节点?调用delayNode.detach()会递归处理connections,导致player也被分离。另外,我尝试新建Engine实例不会崩溃,这是否是正确的解决方法?
解决方案
问题1:安全连接DryWetMixer并调用removeAllInputs
崩溃根源是AudioPlayer同时被接入DryWetMixer的干信号输入和BitCrusher的输入,移除混音器输入时,AudioPlayer会被多次触发disconnectAndDetachIfLast,导致AVAudioEngine内部状态冲突。
修正方案:
- 调整节点连接逻辑,避免同一节点重复接入多个下游端口
- 修改连接前必须暂停AudioEngine,操作完成后再重启
- 手动维护节点引用,防止被意外释放
修正后的代码示例:
class BitCrusherConductor: ObservableObject, ProcessesPlayerInput { let engine = AudioEngine() let player = AudioPlayer() let bitcrusher: BitCrusher let dryWetMixer: DryWetMixer let buffer: AVAudioPCMBuffer let mixer = Mixer() init() { buffer = Cookbook.sourceBuffer player.buffer = buffer player.isLooping = true // 修正连接逻辑:player仅作为BitCrusher的输入,DryWetMixer处理干湿混合 bitcrusher = BitCrusher(player) dryWetMixer = DryWetMixer(player, bitcrusher) mixer.addInput(dryWetMixer) engine.output = mixer } func updateMixerInputs() { // 暂停引擎,避免运行时修改连接导致崩溃 engine.stop() defer { try? engine.start() } // 安全移除所有输入 mixer.removeAllInputs() // 此处可添加新的节点到混音器 // mixer.addInput(newAudioNode) } }
额外注意:
- 绝对不要在引擎运行状态下直接修改节点连接,必须遵循「暂停→修改→重启」的流程
- 检查所有节点连接,避免同一节点被同时连接到多个下游节点(多路由场景需手动管理引用)
问题2:单独分离节点而非递归移除
调用detach()会递归处理整个连接链,是因为AudioKit默认会清理所有关联节点的引用。要单独分离某个节点,需按以下步骤操作:
- 先断开目标节点的输入和输出连接,而非直接调用
detach() - 手动保留上游节点的引用,防止被自动释放
示例(分离Delay节点):
func removeDelayFromChain(player: AudioPlayer, delay: Delay, mixer: Mixer) { engine.stop() defer { try? engine.start() } // 断开player与delay的连接 player.disconnectOutput() // 断开delay与mixer的连接 delay.disconnectOutput() // 重新连接player到mixer,恢复音频链路 mixer.addInput(player) // 若不再需要delay,可手动detach,此时不会影响player delay.detach() }
关于新建Engine实例:
这是一种临时规避方案,但绝非最优解。新建Engine会重置所有音频状态,导致短暂音频中断,且频繁创建销毁引擎会带来不必要的性能开销。仅在大规模重构音频链路的极端场景下考虑使用,常规节点更新应采用「暂停引擎-修改连接-重启引擎」的标准流程。
内容的提问来源于stack exchange,提问作者薛永伟

