Hello everyone,
I am working on a project to playback portions of audio files. I am using an AUGraph to achieve this since I want to route the audio signal through another component, ie Apple's Time Pitch component, kAudioUnitSubType_TimePitch. I set up the graph, the playback region and can get the audio playing correctly. I just cannot determine how to setup a callback method to tell me when the audio components have finished playing the audio clip I've setup. I have been following the examples from the book "Learning Core Audio: A Hands-On Guide to Audio Programming for Mac and iOS" by Chris Adamson and the examples are great, except how do I set up a callback to let me know when the audio clip is finished.
Here is some of my code. I create the graph like this:
+(void) createAUGraph:(AUAudioPlayback*)audio {
// create a new AUGraph CheckError(NewAUGraph(&audio->_audioPlaybackAudioFile.graph), "NewAUGraph failed");
// generate description that will match out output device (speakers) AudioComponentDescription outputcd = {0}; outputcd.componentType = kAudioUnitType_Output; outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
// adds a node with above description to the graph AUNode outputNode; CheckError(AUGraphAddNode(audio->_audioPlaybackAudioFile.graph, &outputcd, &outputNode), "AUGraphAddNode[kAudioUnitSubType_DefaultOutput] failed");
// generate description that will match a generator AU of type: audio file player AudioComponentDescription fileplayercd = {0}; fileplayercd.componentType = kAudioUnitType_Generator; fileplayercd.componentSubType = kAudioUnitSubType_AudioFilePlayer; fileplayercd.componentManufacturer = kAudioUnitManufacturer_Apple;
// adds a node with above description to the graph AUNode fileNode; CheckError(AUGraphAddNode(audio->_audioPlaybackAudioFile.graph, &fileplayercd, &fileNode), "AUGraphAddNode[kAudioUnitSubType_AudioFilePlayer] failed");
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // generate description that will match a timePitchComponent AU of type: Format Converter AudioComponentDescription auTimePitchComponent = {0}; auTimePitchComponent.componentType = kAudioUnitType_FormatConverter; auTimePitchComponent.componentSubType = kAudioUnitSubType_TimePitch; auTimePitchComponent.componentManufacturer = kAudioUnitManufacturer_Apple;
// adds a node with above description to the graph AUNode auTimePitchNode; CheckError(AUGraphAddNode(audio->_audioPlaybackAudioFile.graph, &auTimePitchComponent, &auTimePitchNode), "AUGraphAddNode[kAudioUnitSubType_auTimePitchNode] failed");
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // opening the graph opens all contained audio units but does not allocate any resources yet CheckError(AUGraphOpen(audio->_audioPlaybackAudioFile.graph), "AUGraphOpen failed");
// get the reference to the AudioUnit object for the file player graph node CheckError(AUGraphNodeInfo(audio->_audioPlaybackAudioFile.graph, auTimePitchNode, NULL, &audio->_audioPlaybackAudioFile.timePitchAU), "AUGraphNodeInfo failed");
// get the reference to the outputNode object for the file player graph node CheckError(AUGraphNodeInfo(audio->_audioPlaybackAudioFile.graph, outputNode, NULL, &audio->_audioPlaybackAudioFile.outputAU), "AUGraphNodeInfo failed");
// get the reference to the AudioUnit object for the file player graph node CheckError(AUGraphNodeInfo(audio->_audioPlaybackAudioFile.graph, fileNode, NULL, &audio->_audioPlaybackAudioFile.fileAU), "AUGraphNodeInfo failed");
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // connect the output source of the file player AU to the input source of the output node CheckError(AUGraphConnectNodeInput(audio->_audioPlaybackAudioFile.graph, fileNode, 0, auTimePitchNode, 0), "AUGraphConnectNodeInput"); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// connect the output source of the file player AU to the input source of the output node CheckError(AUGraphConnectNodeInput(audio->_audioPlaybackAudioFile.graph, auTimePitchNode, 0, outputNode, 0), "AUGraphConnectNodeInput"); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // set the output stream format of the timepitch unit float value = 1.0; printf("Set rate %f\n", value);
OSStatus result = AudioUnitSetParameter(audio->_audioPlaybackAudioFile.timePitchAU, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, value, 0); if (result) { printf("AudioUnitSetParameter kTimePitchParam_Rate Global result %ld X %4.4s\n", (long)result, (unsigned int)result, (char*)&result); return; }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // now initialize the graph (causes resources to be allocated) CheckError(AUGraphInitialize(audio->_audioPlaybackAudioFile.graph), "AUGraphInitialize failed"); }
and then I setup the playback region like this:
SInt64 AUAudioPlayback::PrepareFileAU(audioFilePlayerDescription *player, long startOffset, long endOffset) {
// tell the file player unit to load the file we want to play CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduledFileIDs, kAudioUnitScope_Global, 0, &player->inputFile, sizeof(player->inputFile)), "AudioUnitSetProperty[kAudioUnitProperty_ScheduledFileIDs] failed");
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Float64 audioStartOffset = startOffset / 1000.0; SInt64 computeStartFrame = audioStartOffset * player->inputFormat.mSampleRate; Float64 audioStopOffset = endOffset / 1000.0; SInt64 computeStopFrame = (audioStopOffset * player->inputFormat.mSampleRate); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // tell the file player AU to play from computeStartFrame for computeStopFrame - computeStartFrame frames. ScheduledAudioFileRegion rgn; memset (&rgn.mTimeStamp, 0, sizeof(rgn.mTimeStamp)); rgn.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; rgn.mTimeStamp.mSampleTime = 0; rgn.mCompletionProc = NULL; //audioCompletionProcess; rgn.mCompletionProcUserData = _receiver; rgn.mAudioFile = player->inputFile; rgn.mLoopCount = 0; rgn.mStartFrame = computeStartFrame; rgn.mFramesToPlay = computeStopFrame - computeStartFrame; // * player->inputFormat.mFramesPerPacket;
Boolean graphStatus; OSStatus testForFailure = AUGraphIsInitialized(player->graph, &graphStatus);
CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &rgn, sizeof(rgn)), "AudioUnitSetProperty[kAudioUnitProperty_ScheduledFileRegion] failed");
// prime the file player AU with default values UInt32 defaultVal = 0; CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduledFilePrime, kAudioUnitScope_Global, 0, &defaultVal, sizeof(defaultVal)), "AudioUnitSetProperty[kAudioUnitProperty_ScheduledFilePrime] failed");
// tell the file player AU when to start playing (-1 sample time means next render cycle) AudioTimeStamp startTime; memset (&startTime, 0, sizeof(startTime)); startTime.mFlags = kAudioTimeStampSampleTimeValid; startTime.mSampleTime = -1; CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, 0, &startTime, sizeof(startTime)), "AudioUnitSetProperty[kAudioUnitProperty_ScheduleStartTimeStamp]");
testForFailure = AudioUnitAddPropertyListener (player->fileAU, kAudioUnitOfflineProperty_StartOffset, listenForPlaybackComplete, _receiver); testForFailure = AudioUnitInitialize(player->fileAU); // file duration // AudioUnit anAU; // AUGraphGetNodeInfo((AUGraph)player->graph, (AUNode)player->fileAU, NULL, NULL, NULL, &anAU); return (computeStartFrame); }
Everything works but how do I setup a finished playing callback??
Thank for any help.
Jim Griffin
|