CoreAudio error and crash on AVAudioEngine.connect
Asked Answered
S

2

5

I'm attempting to connect an AVAudioUnitEffect to an instance of AVAudioEngine like so:

required init(inputFormat: AVAudioFormat, outputFormat: AVAudioFormat, andAVAudioEngine avAudioEngine:AVAudioEngine) {
    self.inputFormat = inputFormat
    self.outputFormat = outputFormat

    self.avAudioEngine = avAudioEngine
    self.myAudioUnit = MyAVAudioUnit()

    super.init()

    avAudioEngine.attach(myAudioUnit)
    avAudioEngine.connect(myAudioUnit, to: avAudioEngine.outputNode, format: self.inputFormat)
}

The overarching class is simply a subclass of NSObject and MyAudioUnit is a subclass of AVAudioUnitEffect.

At seemingly random times, the last line of this initializer (the call to connect) will throw a SIGABRT with the following error: com.apple.coreaudio.avfaudio: error -10875

Which amounts to kAudioUnitErr_FailedInitialization.

Can anyone shed some light on this error and what might be going on here? I thought that maybe the initializer for MyAVAudioUnit was failing, but its internal initializer (init(audioComponentDescription: AudioComponentDescription)) does not throw any errors and has a non-optional return type. Has anyone else had any experience with this particular error?

UPDATE

Here is the initialization of inputFormat:

guard let stereoFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32,
                                        sampleRate: 44100,
                                        channels: 2,
                                        interleaved: false) else {
                                            return                                             
}

let numChannels = UInt32(10)
guard let multiChannelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Unknown | numChannels) else {
    return
}

inputFormat = AVAudioFormat(commonFormat: stereoFormat.commonFormat,
                                   sampleRate: stereoFormat.sampleRate,
                                   interleaved: stereoFormat.isInterleaved,
                                   channelLayout: multiChannelLayout)

MyAVAudioUnit contains one additional custom parameter (volumeParameter) and is initialized as such:

required override init() {
    var componentDescription = AudioComponentDescription()
    componentDescription.componentType = kAudioUnitType_Effect
    componentDescription.componentSubType = xxxxx
    componentDescription.componentManufacturer = xxxxx
    componentDescription.componentFlags = 0
    componentDescription.componentFlagsMask = 0

    AUAudioUnit.registerSubclass(MyAVAudioUnit.self,
                                 as: componentDescription,
                                 name:"MyAVAudioUnit",
                                 version: UInt32.max)

    super.init(audioComponentDescription: componentDescription)

    guard let paramTree = self.auAudioUnit.parameterTree else { return }
    volumeParameter = paramTree.value(forKey: "volumeParameter") as? AUParameter
}
Skewbald answered 6/8, 2019 at 15:27 Comment(5)
What kind of inputFormats are you passing in?Befall
Can you show the MyAVAudioUnit or reproduce directly with an unsubclassed AVAudioUnitEffect?Befall
@RhythmicFistman updated original question. Thanks for taking a look.Skewbald
No problem - nice work getting the 10 channel AVAudioFormat working. Can you simplify the repro further? Does the problem reproduce without the parameter? With stereo instead of 10 channel? With a built-in audio unit instead of custom? A runnable snippet would be great.Befall
Was the audio engine recently stopped? Did you wait long enough after calling stop? (which is not actually synchronous)Raffin
S
5

Here's what ended up solving this issue. I noticed from crash logs that a print-out of the audio graph just before attempting to connect the audio unit showed this:

“ ________ GraphDescription ________ AVAudioEngineGraph 0x101538ba0: initialized = 1, running = 0, number of nodes = 23 ”

The interesting part is that, while running is false, initialized is true. There isn't much documentation on what this initialized piece of information means, but I figured out that it shows true when the AVAudioEngine has been started before, but paused. Now, I wasn't explicitly calling avAudioEngine.pause() from anywhere, but I supposed the system might be initiating the pause as part of AVAudioSessionRouteChangeNotification (which is what I'm responding to in this whole series of events).

A lot of that is speculation, but it's clear that calling avAudioEngine.connect() while a print-out of the engine shows initialized = 1 (or by inference, the engine is paused) will cause this crash.

The audio engine must be FULLY stopped before attempting to connect audio units. My problem was that my attempt to call avAudioEngine.stop() was wrapped in an if statement like so:

if avAudioEngine.isRunning {
    avAudioEngine.stop()
}

Of course, this if statement was skipped over because the audio engine was, in fact, not running. So stop() was never called. Removing the if statement and calling stop() unquestionably sets initialized = 0 on the engine and allows connection without this crash.

Hope this helps someone else. Took me ages to unravel.

Skewbald answered 30/8, 2019 at 2:42 Comment(0)
R
1

You say this happens at “random times” plural. That might mean you are doing this often, and possibly soon after releasing or de-initing the unit. Note that the audio unit subsystem runs in different completely asynchronous threads, and so the unit may still be in use by the OS (for a short period of time) after your app stops and releases the unit. If you put a long enough delay between any stop/release calls and any (re)inits, say perhaps one or two seconds, it may prevent this issue.

Raffin answered 17/8, 2019 at 16:52 Comment(1)
Really great insight. Will definitely give this a try. Thanks!Skewbald

© 2022 - 2024 — McMap. All rights reserved.