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:
- use
ExtAudioFile
Read the file. AudioMixerUnit
The specific use of.
useExtAudioFile
Read 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 hereAudioUnit
To mix and play,AudioMixerUnit
For mixing data,AudioOutputUnit
Used 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 theAudioUnit
attribute
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 theAudioUnit
Input 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