Building on the previous AudioUnit and ExtAudioFile articles, let’s do something more interesting and challenging, which is to read two audio files, mix them and play them back.

This article is divided into the following two parts:

  1. useExtAudioFileRead the file.
  2. AudioMixerUnitThe specific use of.

useExtAudioFileRead the file

ExtAudioFile can easily read files according to the data format we set, refer to this article for details. How to use ExtAudioFile

AudioMixerUnit Specific use of

We’re going to use two hereAudioUnitTo mix and play,AudioMixerUnit For mixing data,AudioOutputUnitUsed to play data. I drew a sketch as follows:

createAudioUnit

Set described first, then get a AudioUnit by AudioComponentFindNext and AudioComponentInstanceNew instance, can also be accomplished by AudioGraph.

- (void)createUnits {
    AudioComponentDescription ioUnitDesc = {};
    ioUnitDesc.componentType = kAudioUnitType_Output;
    ioUnitDesc.componentSubType = kAudioUnitSubType_RemoteIO;
    ioUnitDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
    ioUnitDesc.componentFlags = 0;
    ioUnitDesc.componentFlagsMask = 0;
    AudioComponent outputComp = AudioComponentFindNext(NULL, &ioUnitDesc);
    if (outputComp == NULL) {
        printf("can't get AudioComponent");
    }
    OSStatus status = AudioComponentInstanceNew(outputComp, &_ioUnit);
    CheckError(status, "creat output unit");
    
    AudioComponentDescription mixerDesc = {};
    mixerDesc.componentType = kAudioUnitType_Mixer;
    mixerDesc.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    mixerDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
    mixerDesc.componentFlags = 0;
    mixerDesc.componentFlagsMask = 0;
    AudioComponent mixerComp = AudioComponentFindNext(NULL, &mixerDesc);
    if (mixerComp == NULL) {
        printf("can't get AudioComponent");
    }
    status = AudioComponentInstanceNew(mixerComp, &_mixerUnit);
    CheckError(status, "creat mixer unit");
}
Copy the code

Set up theAudioUnitattribute

Set the AudioMixerUnit output format and connect the output of AudioMixerUnit to the input of AudioOutputUnit. The Bus or Element here can be seen as the subscript of the arrow, and the output of the AudioMixerUnit and the input of the AudioOutputUnit have only one line, so it’s 0.

- (void)setupUnits {
    OSStatus status;
    UInt32 propertySize = sizeof(AudioStreamBasicDescription);
    // Set the mixer output format
    status = AudioUnitSetProperty(_mixerUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Output,
                                  0,
                                  _asbd,
                                  propertySize);
    CheckError(status, "set stream format");

    //make connection
    AudioUnitElement inputBus  = 0;
    AudioUnitElement outputBus = 0;
    AudioUnitConnection mixerOutToIoUnitIn;
    mixerOutToIoUnitIn.sourceAudioUnit    = _mixerUnit;
    mixerOutToIoUnitIn.sourceOutputNumber = outputBus;
    mixerOutToIoUnitIn.destInputNumber    = inputBus;

    status = AudioUnitSetProperty(_ioUnit,                         // connection destination
                                  kAudioUnitProperty_MakeConnection,   // property key
                                  kAudioUnitScope_Input,              // destination scope
                                  outputBus,                            // destination element
                                  &mixerOutToIoUnitIn,                 // connection definition
                                  sizeof(mixerOutToIoUnitIn));
    CheckError(status, "make connection");
}
Copy the code

Set up theAudioUnitInput source callback

Here the AudioMixerUnit is set to have several input sources, input formats, and callback to get data.

- (void)setStreamCount:(UInt32)count {
    OSStatus status;
    UInt32 propertySize = sizeof(AudioStreamBasicDescription);
    status = AudioUnitSetProperty(_mixerUnit,
                                  kAudioUnitProperty_ElementCount,
                                  kAudioUnitScope_Input,
                                  0,
                                  &count,
                                  sizeof(UInt32));
    CheckError(status, "set stream format");
    for (UInt32 i = 0; i < count; i++) {
        // Set the callback method
        AURenderCallbackStruct callbackStruct;
        callbackStruct.inputProc = inputCallback;
        callbackStruct.inputProcRefCon = (__bridge void * _Nullable)(self);
        status = AudioUnitSetProperty(_mixerUnit,
                                      kAudioUnitProperty_SetRenderCallback,
                                      kAudioUnitScope_Input,
                                      i,
                                      &callbackStruct,
                                      sizeof(callbackStruct));
        CheckError(status, "set callback");
        status = AudioUnitSetProperty(_mixerUnit,
                                      kAudioUnitProperty_StreamFormat,
                                      kAudioUnitScope_Input,
                                      i,
                                      _asbd,
                                      propertySize);
        CheckError(status, "set stream format"); }}Copy the code

controlAudioOutputUnit

As with playback and recording, Initialize and Start are called at the beginning and Stop and Uninitialize are called at the end.

- (void)startMix {
    dispatch_async(_queue, ^{
        OSStatus status;
        status = AudioUnitInitialize(self.mixerUnit);
        CheckError(status, "initialize mixer unit");
        status = AudioUnitInitialize(self.ioUnit);
        CheckError(status, "initialize output unit");
        status = AudioOutputUnitStart(self.ioUnit);
        CheckError(status, "start output unit");
    });
}
- (void)stopMix {
    dispatch_async(_queue, ^{
        OSStatus status;
        status = AudioOutputUnitStop(self.ioUnit);
        CheckError(status, "stop output unit");
        status = AudioUnitUninitialize(self.ioUnit);
        CheckError(status, "uninitialize output unit");
        status = AudioUnitUninitialize(self.mixerUnit);
        CheckError(status, "uninitialize mixer unit");
    });
}
Copy the code

Fill in the data

There is a proxy set up to fetch data when the callback is triggered, which in my demo is reading data from a file.

OSStatus inputCallback(void *inRefCon,
                       AudioUnitRenderActionFlags *ioActionFlags,
                       const AudioTimeStamp *inTimeStamp,
                       UInt32 inBusNumber,
                       UInt32 inNumberFrames,
                       AudioBufferList *ioData) {
    ZFAudioUnitMixer *mixer = (__bridge ZFAudioUnitMixer *)inRefCon;
    AudioBuffer buffer = ioData->mBuffers[0];
    [mixer.delegate audioMixer:mixer data:buffer.mData size:buffer.mDataByteSize forStreamIndex:inBusNumber];

    return noErr;
}
Copy the code

Making the address