Classes/AUGraphController.mm
/* |
File: AUGraphController.mm |
Abstract: Demonstrates using the AUTimePitch. |
Version: 1.0.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2012 Apple Inc. All Rights Reserved. |
*/ |
#import "AUGraphController.h" |
#pragma mark- Render |
// render some silence |
static void SilenceData(AudioBufferList *inData) |
{ |
for (UInt32 i=0; i < inData->mNumberBuffers; i++) |
memset(inData->mBuffers[i].mData, 0, inData->mBuffers[i].mDataByteSize); |
} |
// audio render procedure to render our client data format |
// 2 ch interleaved 'lpcm' platform Canonical format - this is the mClientFormat data, see CAStreamBasicDescription SetCanonical() |
// note that this format can differ between platforms so be sure of the data type you're working with, |
// for example AudioSampleType may be Float32 or may be SInt16 |
static OSStatus renderInput(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) |
{ |
SourceAudioBufferDataPtr userData = (SourceAudioBufferDataPtr)inRefCon; |
AudioSampleType *in = userData->soundBuffer[inBusNumber].data; |
AudioSampleType *out = (AudioSampleType *)ioData->mBuffers[0].mData; |
UInt32 sample = userData->frameNum * userData->soundBuffer[inBusNumber].asbd.mChannelsPerFrame; |
// make sure we don't attempt to render more data than we have available in the source buffer |
if ((userData->frameNum + inNumberFrames) > userData->soundBuffer[inBusNumber].numFrames) { |
UInt32 offset = (userData->frameNum + inNumberFrames) - userData->soundBuffer[inBusNumber].numFrames; |
if (offset < inNumberFrames) { |
// copy the last bit of source |
SilenceData(ioData); |
memcpy(out, &in[sample], ((inNumberFrames - offset) * userData->soundBuffer[inBusNumber].asbd.mBytesPerFrame)); |
} |
} else { |
memcpy(out, &in[sample], ioData->mBuffers[0].mDataByteSize); |
} |
// in the iPhone sample using the iPodEQ, a graph notification was used to count rendered source samples at output to know when to loop the source |
// because there is time compression/expansion AU being used in this sample as well as rate conversion, you can't really use a render notification |
// on the output of the graph since you can't assume the graph is producing output at the same rate that it is consuming input |
// therefore, this kind of sample counting needs to happen somewhere upstream of the timepich AU and in this case the AUConverter |
// ** doing it here is the place for it ** |
userData->frameNum += inNumberFrames; |
if (userData->frameNum >= userData->maxNumFrames) { |
userData->frameNum = 0; |
} |
//printf("render input bus %u sample %u\n", inBusNumber, sample); |
return noErr; |
} |
#pragma mark- AUGraphController |
@interface AUGraphController (hidden) |
- (void)loadSpeechTrack:(Float64)inGraphSampleRate; |
@end |
@implementation AUGraphController |
- (void)dealloc |
{ |
printf("AUGraphController dealloc\n"); |
DisposeAUGraph(mGraph); |
free(mUserData.soundBuffer[0].data); |
CFRelease(sourceURL); |
[super dealloc]; |
} |
- (void)awakeFromNib |
{ |
printf("AUGraphController awakeFromNib\n"); |
// clear the mSoundBuffer struct |
memset(&mUserData.soundBuffer, 0, sizeof(mUserData.soundBuffer)); |
// create the URL we'll use for source |
// AAC demo track |
NSString *source = [[NSBundle mainBundle] pathForResource:@"SpeechTrack" ofType:@"mp4"]; |
sourceURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)source, kCFURLPOSIXPathStyle, false); |
} |
- (void)initializeAUGraph:(Float64)inSampleRate |
{ |
printf("initializeAUGraph\n"); |
AUNode outputNode; |
AUNode timePitchNode; |
AUNode mixerNode; |
AUNode converterNode; |
AudioUnit converterAU; |
printf("create client format ASBD\n"); |
// client format audio going into the converter |
mClientFormat.SetCanonical(2, true); |
mClientFormat.mSampleRate = 22050.0; // arbitrary sample rate chosen to demonstrate working with 3 different sample rates |
// 1) the rate passed in which ends up being the the graph rate (in this sample the arbitrary choice of 11KHz) |
// 2) the rate of the original file which in this case is 44.1kHz (rate conversion to client format of 22k is done by ExtAudioFile) |
// 3) the rate we want for our source data which in this case is 22khz (AUConverter taking care of conversion to Graph Rate) |
// File @ 44.1kHz - > ExtAudioFile - > Client Format @ 22kHz - > AUConverter graph @ 11kHz -> Output |
// while this type of multiple rate conversions isn't what you'd probably want to do in your application, this sample simply demonstrates |
// this flexibility -- where you perform rate conversions is important and some thought should be put into the decision |
mClientFormat.Print(); |
printf("create output format ASBD\n"); |
// output format |
mOutputFormat.SetAUCanonical(2, false); |
mOutputFormat.mSampleRate = inSampleRate; |
mOutputFormat.Print(); |
OSStatus result = noErr; |
// load up the demo audio data |
[self loadSpeechTrack: inSampleRate]; |
printf("-----------\n"); |
printf("new AUGraph\n"); |
// create a new AUGraph |
result = NewAUGraph(&mGraph); |
if (result) { printf("NewAUGraph result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// create four CAComponentDescription for the AUs we want in the graph |
// output unit |
CAComponentDescription output_desc(kAudioUnitType_Output, kAudioUnitSubType_DefaultOutput, kAudioUnitManufacturer_Apple); |
// timePitchNode unit |
CAComponentDescription timePitch_desc(kAudioUnitType_FormatConverter, kAudioUnitSubType_TimePitch, kAudioUnitManufacturer_Apple); |
// multichannel mixer unit |
CAComponentDescription mixer_desc(kAudioUnitType_Mixer, kAudioUnitSubType_MultiChannelMixer, kAudioUnitManufacturer_Apple); |
// AU Converter |
CAComponentDescription converter_desc(kAudioUnitType_FormatConverter, kAudioUnitSubType_AUConverter, kAudioUnitManufacturer_Apple); |
printf("add nodes\n"); |
// create a node in the graph that is an AudioUnit, using the supplied component description to find and open that unit |
result = AUGraphAddNode(mGraph, &output_desc, &outputNode); |
if (result) { printf("AUGraphNewNode 1 result %lu %4.4s\n", (unsigned long)result, (char *)&result); return; } |
result = AUGraphAddNode(mGraph, &timePitch_desc, &timePitchNode); |
if (result) { printf("AUGraphNewNode 2 result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
result = AUGraphAddNode(mGraph, &mixer_desc, &mixerNode); |
if (result) { printf("AUGraphNewNode 3 result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
result = AUGraphAddNode(mGraph, &converter_desc, &converterNode); |
if (result) { printf("AUGraphNewNode 3 result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
// connect a node's output to a node's input |
// au converter -> mixer -> timepitch -> output |
result = AUGraphConnectNodeInput(mGraph, converterNode, 0, mixerNode, 0); |
if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
result = AUGraphConnectNodeInput(mGraph, mixerNode, 0, timePitchNode, 0); |
if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
result = AUGraphConnectNodeInput(mGraph, timePitchNode, 0, outputNode, 0); |
if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", (unsigned long)result, (char*)&result); return; } |
// open the graph -- AudioUnits are open but not initialized (no resource allocation occurs here) |
result = AUGraphOpen(mGraph); |
if (result) { printf("AUGraphOpen result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// grab audio unit instances from the nodes |
result = AUGraphNodeInfo(mGraph, converterNode, NULL, &converterAU); |
if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
result = AUGraphNodeInfo(mGraph, mixerNode, NULL, &mMixer); |
if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
result = AUGraphNodeInfo(mGraph, timePitchNode, NULL, &mTimeAU); |
if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// set bus count |
UInt32 numbuses = 1; |
printf("set input bus count %lu\n", (unsigned long)numbuses); |
result = AudioUnitSetProperty(mMixer, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &numbuses, sizeof(numbuses)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// enable metering |
UInt32 onValue = 1; |
printf("enable metering for input bus 0\n"); |
result = AudioUnitSetProperty(mMixer, kAudioUnitProperty_MeteringMode, kAudioUnitScope_Input, 0, &onValue, sizeof(onValue)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// setup render callback struct |
AURenderCallbackStruct rcbs; |
rcbs.inputProc = &renderInput; |
rcbs.inputProcRefCon = &mUserData; |
printf("set AUGraphSetNodeInputCallback\n"); |
// set a callback for the specified node's specified input bus (bus 1) |
result = AUGraphSetNodeInputCallback(mGraph, converterNode, 0, &rcbs); |
if (result) { printf("AUGraphSetNodeInputCallback result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
printf("set converter input bus %d client kAudioUnitProperty_StreamFormat\n", 0); |
// set the input stream format, this is the format of the audio for the converter input bus (bus 1) |
result = AudioUnitSetProperty(converterAU, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &mClientFormat, sizeof(mClientFormat)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// in an au graph, each nodes output stream format (including sample rate) needs to be set explicitly |
// this stream format is propagated to its destination's input stream format |
printf("set converter output kAudioUnitProperty_StreamFormat\n"); |
// set the output stream format of the converter |
result = AudioUnitSetProperty(converterAU, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
printf("set mixer output kAudioUnitProperty_StreamFormat\n"); |
// set the output stream format of the mixer |
result = AudioUnitSetProperty(mMixer, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
printf("set timepitch output kAudioUnitProperty_StreamFormat\n"); |
// set the output stream format of the timepitch unit |
result = AudioUnitSetProperty(mTimeAU, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat)); |
if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
printf("AUGraphInitialize\n"); |
// now that we've set everything up we can initialize the graph, this will also validate the connections |
result = AUGraphInitialize(mGraph); |
if (result) { printf("AUGraphInitialize result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
CAShow(mGraph); |
} |
// load up audio data from the demo file into mSoundBuffer.data which is then used in the render proc as the source data to render |
- (void)loadSpeechTrack:(Float64)inGraphSampleRate |
{ |
mUserData.frameNum = 0; |
mUserData.maxNumFrames = 0; |
printf("loadSpeechTrack, %d\n", 1); |
ExtAudioFileRef xafref = 0; |
// open one of the two source files |
OSStatus result = ExtAudioFileOpenURL(sourceURL, &xafref); |
if (result || !xafref) { printf("ExtAudioFileOpenURL result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// get the file data format, this represents the file's actual data format, we need to know the actual source sample rate |
// note that the client format set on ExtAudioFile is the format of the date we really want back |
CAStreamBasicDescription fileFormat; |
UInt32 propSize = sizeof(fileFormat); |
result = ExtAudioFileGetProperty(xafref, kExtAudioFileProperty_FileDataFormat, &propSize, &fileFormat); |
if (result) { printf("ExtAudioFileGetProperty kExtAudioFileProperty_FileDataFormat result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
printf("file %d, native file format\n", 1); |
fileFormat.Print(); |
// get the file's length in sample frames |
UInt64 numFrames = 0; |
propSize = sizeof(numFrames); |
result = ExtAudioFileGetProperty(xafref, kExtAudioFileProperty_FileLengthFrames, &propSize, &numFrames); |
if (result) { printf("ExtAudioFileGetProperty kExtAudioFileProperty_FileLengthFrames result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// account for any sample rate conversion between the file and client sample rates |
double rateRatio = mClientFormat.mSampleRate / fileFormat.mSampleRate; |
numFrames *= rateRatio; |
// set the client format to be what we want back -- this is the same format audio we're giving to the input callback |
result = ExtAudioFileSetProperty(xafref, kExtAudioFileProperty_ClientDataFormat, sizeof(mClientFormat), &mClientFormat); |
if (result) { printf("ExtAudioFileSetProperty kExtAudioFileProperty_ClientDataFormat %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
// set up and allocate memory for the source buffer |
mUserData.soundBuffer[0].numFrames = numFrames; |
mUserData.soundBuffer[0].asbd = mClientFormat; |
UInt32 samples = numFrames * mUserData.soundBuffer[0].asbd.mChannelsPerFrame; |
mUserData.soundBuffer[0].data = (AudioSampleType *)calloc(samples, sizeof(AudioSampleType)); |
// set up a AudioBufferList to read data into |
AudioBufferList bufList; |
bufList.mNumberBuffers = 1; |
bufList.mBuffers[0].mNumberChannels = mUserData.soundBuffer[0].asbd.mChannelsPerFrame; |
bufList.mBuffers[0].mData = mUserData.soundBuffer[0].data; |
bufList.mBuffers[0].mDataByteSize = samples * sizeof(AudioSampleType); |
// perform a synchronous sequential read of the audio data out of the file into our allocated data buffer |
UInt32 numPackets = numFrames; |
result = ExtAudioFileRead(xafref, &numPackets, &bufList); |
if (result) { |
printf("ExtAudioFileRead result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); |
free(mUserData.soundBuffer[0].data); |
mUserData.soundBuffer[0].data = 0; |
return; |
} |
// update after the read to reflect the real number of frames read into the buffer |
// note that ExtAudioFile will automatically trim the 2112 priming frames off the AAC demo source |
mUserData.soundBuffer[0].numFrames = numPackets; |
// maxNumFrames is used to know when we need to loop the source |
mUserData.maxNumFrames = mUserData.soundBuffer[0].numFrames; |
// close the file and dispose the ExtAudioFileRef |
ExtAudioFileDispose(xafref); |
} |
#pragma mark- |
// enable or disables a specific bus |
- (void)enableInput:(UInt32)inputNum isOn:(AudioUnitParameterValue)isONValue |
{ |
printf("BUS %ld isON %f\n", (long)inputNum, isONValue); |
OSStatus result = AudioUnitSetParameter(mMixer, kMultiChannelMixerParam_Enable, kAudioUnitScope_Input, inputNum, isONValue, 0); |
if (result) { printf("AudioUnitSetParameter kMultiChannelMixerParam_Enable result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} |
// sets the input volume for a specific bus |
- (void)setInputVolume:(UInt32)inputNum value:(AudioUnitParameterValue)value |
{ |
printf("BUS %ld volume %f\n", (long)inputNum, value); |
OSStatus result = AudioUnitSetParameter(mMixer, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, inputNum, value, 0); |
if (result) { printf("AudioUnitSetParameter kMultiChannelMixerParam_Volume Input result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} |
// sets the overall mixer output volume |
- (void)setOutputVolume:(AudioUnitParameterValue)value |
{ |
printf("Output volume %f\n", value); |
OSStatus result = AudioUnitSetParameter(mMixer, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, value, 0); |
if (result) { printf("AudioUnitSetParameter kMultiChannelMixerParam_Volume Output result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} |
// sets the rate of the timepitch Audio Unit |
- (void)setTimeRate:(AudioUnitParameterValue)value |
{ |
printf("Set rate %f\n", value); |
OSStatus result = AudioUnitSetParameter(mTimeAU, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, value, 0); |
if (result) { printf("AudioUnitSetParameter kTimePitchParam_Rate Global result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} |
// return the levels from the multichannel mixer |
- (Float32)getMeterLevel |
{ |
Float32 value = -120.0; |
OSStatus result = AudioUnitGetParameter(mMixer, kMultiChannelMixerParam_PostAveragePower, kAudioUnitScope_Input, 0, &value); |
if (result) { printf("AudioUnitGetParameter kMultiChannelMixerParam_PostAveragePower Input result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); } |
return value; |
} |
// start or stop graph |
- (void)runAUGraph |
{ |
Boolean isRunning = false; |
OSStatus result = AUGraphIsRunning(mGraph, &isRunning); |
if (result) { printf("AUGraphIsRunning result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
if (isRunning) { |
printf("STOP\n"); |
result = AUGraphStop(mGraph); |
if (result) { printf("AUGraphStop result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} else { |
printf("PLAY\n"); |
result = AUGraphStart(mGraph); |
if (result) { printf("AUGraphStart result %ld %08X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; } |
} |
} |
@end |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-08-21