Reputation: 1485
My task is to play an audio file that is saved locally in documents directory, apply audio effect in that audio file using Effect Audio Unit and save a new audio file in documents directory with that effect. Here is my code that i have written so far, but its not working. Effects are not being applied in the audio. Please suggest me what is wrong in this code ?? Thanks in advance..
- (void) setUpAudioUnits
{
OSStatus setupErr = noErr;
// describe unit
AudioComponentDescription audioCompDesc;
audioCompDesc.componentType = kAudioUnitType_Output;
audioCompDesc.componentSubType = kAudioUnitSubType_RemoteIO;
audioCompDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioCompDesc.componentFlags = 0;
audioCompDesc.componentFlagsMask = 0;
// get rio unit from audio component manager
AudioComponent rioComponent = AudioComponentFindNext(NULL, &audioCompDesc);
setupErr = AudioComponentInstanceNew(rioComponent, &remoteIOUnit);
NSAssert (setupErr == noErr, @"Couldn't get RIO unit instance");
// set up the rio unit for playback
UInt32 oneFlag = 1;
AudioUnitElement outputElement = 0;
setupErr =
AudioUnitSetProperty (remoteIOUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
outputElement,
&oneFlag,
sizeof(oneFlag));
NSAssert (setupErr == noErr, @"Couldn't enable RIO output");
// enable rio input
AudioUnitElement inputElement = 1;
// setup an asbd in the iphone canonical format
AudioStreamBasicDescription myASBD;
memset (&myASBD, 0, sizeof (myASBD));
// myASBD.mSampleRate = 44100;
myASBD.mSampleRate = hardwareSampleRate;
myASBD.mFormatID = kAudioFormatLinearPCM;
myASBD.mFormatFlags = kAudioFormatFlagsCanonical;
myASBD.mBytesPerPacket = 4;
myASBD.mFramesPerPacket = 1;
myASBD.mBytesPerFrame = 4;
myASBD.mChannelsPerFrame = 2;
myASBD.mBitsPerChannel = 16;
/*
// set format for output (bus 0) on rio's input scope
*/
setupErr =
AudioUnitSetProperty (remoteIOUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
outputElement,
&myASBD,
sizeof (myASBD));
NSAssert (setupErr == noErr, @"Couldn't set ASBD for RIO on input scope / bus 0");
// song must be an LPCM file, preferably in caf container
// to convert, use /usr/bin/afconvert, like this:
// /usr/bin/afconvert --data LEI16 Girlfriend.m4a song.caf
// read in the entire audio file (NOT recommended)
// better to use a ring buffer: thread or timer fills, render callback drains
NSURL *songURL = [NSURL fileURLWithPath:
[[NSBundle mainBundle] pathForResource: @"song"
ofType: @"caf"]];
AudioFileID songFile;
setupErr = AudioFileOpenURL((CFURLRef) songURL,
kAudioFileReadPermission,
0,
&songFile);
NSAssert (setupErr == noErr, @"Couldn't open audio file");
UInt64 audioDataByteCount;
UInt32 audioDataByteCountSize = sizeof (audioDataByteCount);
setupErr = AudioFileGetProperty(songFile,
kAudioFilePropertyAudioDataByteCount,
&audioDataByteCountSize,
&audioDataByteCount);
NSAssert (setupErr == noErr, @"Couldn't get size property");
musicPlaybackState.audioData = malloc (audioDataByteCount);
musicPlaybackState.audioDataByteCount = audioDataByteCount;
musicPlaybackState.samplePtr = musicPlaybackState.audioData;
NSLog (@"reading %qu bytes from file", audioDataByteCount);
UInt32 bytesRead = audioDataByteCount;
setupErr = AudioFileReadBytes(songFile,
false,
0,
&bytesRead,
musicPlaybackState.audioData);
NSAssert (setupErr == noErr, @"Couldn't read audio data");
NSLog (@"read %d bytes from file", bytesRead);
AudioStreamBasicDescription fileASBD;
UInt32 asbdSize = sizeof (fileASBD);
setupErr = AudioFileGetProperty(songFile,
kAudioFilePropertyDataFormat,
&asbdSize,
&fileASBD);
NSAssert (setupErr == noErr, @"Couldn't get file asbd");
ExtAudioFileCreateWithURL(outputFileURL,
kAudioFileCAFType,
&fileASBD,
nil,
kAudioFileFlags_EraseFile,
&musicPlaybackState.extAudioFile);
// get the mixer unit
AudioComponentDescription mixerDesc;
mixerDesc.componentType = kAudioUnitType_Effect;
mixerDesc.componentSubType = kAudioUnitSubType_Delay;
mixerDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
mixerDesc.componentFlags = 0;
mixerDesc.componentFlagsMask = 0;
// get mixer unit from audio component manager
AudioComponent mixerComponent = AudioComponentFindNext(NULL, &mixerDesc);
setupErr = AudioComponentInstanceNew(mixerComponent, &mixerUnit);
NSAssert (setupErr == noErr, @"Couldn't get mixer unit instance");
// set up connections and callbacks
// connect mixer bus 0 input to robot voice render callback
effectState.rioUnit = remoteIOUnit;
effectState.sineFrequency = 23;
effectState.sinePhase = 0;
effectState.asbd = myASBD;
// connect mixer bus 1 input to music player callback
AURenderCallbackStruct musicPlayerCallbackStruct;
musicPlayerCallbackStruct.inputProc = MusicPlayerCallback; // callback function
musicPlayerCallbackStruct.inputProcRefCon = &musicPlaybackState;
setupErr =
AudioUnitSetProperty(mixerUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
outputElement,
&musicPlayerCallbackStruct,
sizeof (musicPlayerCallbackStruct));
NSAssert (setupErr == noErr, @"Couldn't set mixer render callback on bus 1");
// direct connect mixer to output
AudioUnitConnection connection;
connection.sourceAudioUnit = mixerUnit;
connection.sourceOutputNumber = outputElement;
connection.destInputNumber = outputElement;
setupErr =
AudioUnitSetProperty(remoteIOUnit,
kAudioUnitProperty_MakeConnection,
kAudioUnitScope_Input,
outputElement,
&connection,
sizeof (connection));
NSAssert (setupErr == noErr, @"Couldn't set mixer-to-RIO connection");
setupErr = AudioUnitInitialize(mixerUnit);
NSAssert (setupErr == noErr, @"Couldn't initialize mixer unit");
setupErr = AudioUnitInitialize(remoteIOUnit);
NSAssert (setupErr == noErr, @"Couldn't initialize RIO unit");
setupErr = AudioOutputUnitStart (remoteIOUnit);
}
Upvotes: 3
Views: 2861
Reputation: 965
When you have instance of initialized audio unit, you can apply effect to sound using AudioUnitRender
by providing AudioBufferList to it.
First of all, make sure that you have sound in format which accepted by Audio Unit. You can get this format by getting kAudioUnitProperty_StreamFormat
property.
If your audio file has different format than one you got from audio unit, you can convert audio "on the fly" by using ExtAudioFile. To achieve this, you must set kExtAudioFileProperty_ClientDataFormat
property in ExtAudioFile to format which you got from 'kAudioUnitProperty_StreamFormat'. Now, when you will read audio file you will get audio in needed format.
Also, make sure that kAudioUnitProperty_ShouldAllocateBuffer
property of Audio Unit is set to 1
.
To call AudioUnitRender
you must prepare valid AudioTimeStamp
, AudioUnitRenderActionFlags
(can be set to 0
) and AudioBufferList
. You don't need to allocate memory for buffers, you need just provide number of buffers and it's size.
AudioBufferList *buffer = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer));
buffer->mNumberBuffers = 2; // at least 2 buffers
buffer->mBuffers[0].mDataByteSize = ...; // size of one buffer
buffer->mBuffers[1].mDataByteSize = ...;
AudioUnitRenderActionFlags flags = 0;
AudioTimeStamp timeStamp;
memset(&timeStamp, 0, sizeof(AudioTimeStamp));
timeStamp.mFlags = kAudioTimeStampSampleTimeValid;
UInt32 frames = ...; // number of frames in buffer
AudioUnit unit = ...; // your Delay unit
Now you can call AudioUnitRender
:
AudioUnitRender(unit, &flags, &timeStamp, 0, frames, buffer);
Audio unit will ask callback for fill buffers and apply effect to sound, after that you will have buffers with valid audio. In this case you need to set kAudioUnitProperty_SetRenderCallback
property to valid callback.
Upvotes: 4