AVAudioEngine and Inter-App Audio setup

I'm trying to create a very basic inter-app audio setup with AVAudioEngine. The app will crash when I try to connect the inter-app audio unit to the main mixer. I've checked to make sure the audio unit is created and not nil. Here is what I have:


- (void)viewDidLoad {
    [super viewDidLoad];

    //create session with category play and record and options mix with others.
    AVAudioSession* session = [AVAudioSession sharedInstance];
    NSError* err;
    [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&err];
    [session setPreferredSampleRate:44100.0 error:&err];
    [session setActive:YES error:&err];
    
    AVAudioEngine* audioEngine = [[AVAudioEngine alloc] init];
    
    
    //Find the first available remote instrument for testing.
    //This requires you have a remote instrument installed. I've been using a free one called DRC.
    //I tested DRC with GarageBand so I know it works as a remote instrument.
    //Make sure componentArray is filled with at least 1 object.
    AudioComponentDescription description = { kAudioUnitType_RemoteInstrument, 0, 0, 0, 0 };
    NSArray* componentArray = [[AVAudioUnitComponentManager sharedAudioUnitComponentManager] componentsMatchingDescription:description];
    AVAudioUnitComponent* comp = [componentArray objectAtIndex:0];
    AVAudioUnitMIDIInstrument* instUnit = [[AVAudioUnitMIDIInstrument alloc] initWithAudioComponentDescription:comp.audioComponentDescription];   
    
    //
    //Just checking stream formats
    //Oddly enough, none of these crash. The internal AudioUnit wrapped in the AVAudioUnit is clearly working fine.
    //Commenting these lines out changes nothing
    //
    AVAudioFormat* outputFormat = [[audioEngine outputNode] inputFormatForBus:0];
    AudioStreamBasicDescription outDesc;
    UInt32 outSize = sizeof(outDesc);
    AudioUnitSetProperty(instUnit.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, outputFormat.streamDescription, sizeof(outputFormat.streamDescription));
    AudioUnitSetProperty(instUnit.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 1, outputFormat.streamDescription, sizeof(outputFormat.streamDescription));
    AudioUnitGetProperty(instUnit.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, &outDesc, &outSize);
    AudioUnitGetProperty(instUnit.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 1, &outDesc, &outSize);
    
    //
    //Any of these will crash with error: *** Terminating app due to uncaught exception 'NSRangeException'
    //, reason: '*** -[__NSArrayM objectAtIndexedSubscript:]: index 0 beyond bounds for empty array'
    //
//    [instUnit_ outputFormatForBus:0];
//    [instUnit_ inputFormatForBus:0];
//    instUnit_.name;
    
    
    //!!!!!!!!!!!!!!!!!!!
    //HERE IS THE TROUBLE
    //!!!!!!!!!!!!!!!!!!!
    //this will crash during the connect with a mysterious index out of bounds error for an NSArray with 0 elements
    //in it.
    
    //attach AudioUnit
    [audioEngine attachNode:instUnit];
    
    //connect audio unit
    //Crash happens here
    [audioEngine connect:instUnit to:[audioEngine mainMixerNode] format:nil];
    
    [audioEngine startAndReturnError:&err];
    
    if(err) {
        NSLog(@"error: %@", err.localizedDescription);
    }
}

Accepted Reply

I figured it out. Since there are no busses in the AUAudioUnit object, I created my own. I'm not sure why I need to create my own after instantiating via AVAudioUnit's instantiation method. A non-remote instrument does not have this problem. Maybe a bug? Anyway, here's what the solution looks like


AUAudioUnit* auInst = instUnit.AUAudioUnit;
NSMutableArray<auaudiounitbus*>* busArray = [[NSMutableArray alloc] init];
AVAudioFormat* outFormat = [[audioEngine outputNode] outputFormatForBus:0];
AUAudioUnitBus* bus = [[AUAudioUnitBus alloc] initWithFormat:outFormat error:&err];
[busArrays addObject:bus];
[[auInst outputBusses] replaceBusses:busArray];

Replies

I have more information. If Icheck instUnit.numberOfOutputs in the debugger, it shows that there are 0, but if do a AudioUnitGetProperty on the instUnit.audioUnit like this:


UInt32 elemCount;
UInt32 elemCountSize = sizeof(elemCount);
AudioUnitGetProperty(instUnit_.audioUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, 0, &elemCount, &elemCountSize);


Then elemCount shows 1, which to my understanding means that there is 1 output bus. This makes me think the AudioUnit object is created correctly, but the AVAudioUnit object is not (even though it's just supposed to be a wrapper, no?)


Well, if I look at instUnit.AUAudioUnit I can see that there is an outputBusses object of type AUV2BridgeBusArray. It is an empty array. Trying to access it throws the exact error I was experiencing in the OP: "[__NSArrayM objectAtIndexedSubscript:]: index 0 beyond bounds for empty array."


If I make the AUAudioUnit like this:


AUAudioUnit* auInst = [[AUAudioUnit alloc] initWithComponentDescription:comp.audioComponentDescription error:&err];

I still end up with the same issue, no busses in the AUV2BridgeBusArray. Why would this happen only with remote instruments or remote effects (I have tried both)?

I figured it out. Since there are no busses in the AUAudioUnit object, I created my own. I'm not sure why I need to create my own after instantiating via AVAudioUnit's instantiation method. A non-remote instrument does not have this problem. Maybe a bug? Anyway, here's what the solution looks like


AUAudioUnit* auInst = instUnit.AUAudioUnit;
NSMutableArray<auaudiounitbus*>* busArray = [[NSMutableArray alloc] init];
AVAudioFormat* outFormat = [[audioEngine outputNode] outputFormatForBus:0];
AUAudioUnitBus* bus = [[AUAudioUnitBus alloc] initWithFormat:outFormat error:&err];
[busArrays addObject:bus];
[[auInst outputBusses] replaceBusses:busArray];