//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AudioFilePlayer.cpp // // $Log: AudioFilePlayer.cpp,v $ // Revision 1.15 2003/06/04 20:40:22 baron // Remove old comment about CBR only support. // // Revision 1.14 2003/04/18 23:05:14 baron // Use AudioFileReadPackets() in the special case of short files that are read entirely into memory. // // Revision 1.13 2003/04/18 17:37:43 baron // Make play file code work for aac. // // Revision 1.12 2003/04/09 22:33:11 baron // Pass audio data around by packets instead of bytes. // // Revision 1.11 2003/03/19 21:33:47 baron // Add magic cookie support. (QDesign and Purevoice work now) // // Revision 1.10 2003/03/18 20:22:32 bills // fix warning // // Revision 1.9 2003/03/18 20:21:45 bills // add a diagnostic to print the num packets // // Revision 1.8 2003/03/13 22:11:33 baron // Remove V1 code. Replace usage of AudioFile Read/WriteByte APIs with the Read/WritePacket versions. // // Revision 1.7 2002/07/14 21:03:49 bills // add notificaitons in the case of rendering errors // // Revision 1.6 2002/06/18 06:09:33 bills // change to render proc for V2 units // // Revision 1.5 2002/06/02 04:38:23 bills // tweaks to now fully support v1 or v2 audio unit // // Revision 1.4 2002/06/01 23:58:19 bills // first pass at v2 units // // Revision 1.3 2002/05/19 23:23:23 bills // cleanup header // // Revision 1.2 2002/05/18 03:46:21 bills // add some additional API // // Revision 1.1 2002/05/18 01:19:47 bills // new location // // Revision 1.1 2002/05/17 07:56:45 bills // new dir loc // // Revision 1.5 2002/05/17 07:48:05 bills // Add FilePlay "C" API // // Revision 1.4 2002/05/16 09:22:09 bills // add notifications for file underruns // // Revision 1.3 2002/05/16 09:07:13 bills // use a single read thread for files // // Revision 1.2 2002/05/15 06:21:59 bills // now use AudioConverter to do file reading // // Revision 1.1 2002/05/14 08:12:23 bills // initial checkin // // // General class for playing back a file //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #include "AudioFilePlayer.h" #if DEBUG #define LOG_DATA_FLOW 0 #endif OSStatus AudioFileManager::FileRenderProc (void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { AudioFileManager* THIS = (AudioFileManager*)inRefCon; return THIS->Render (*ioData, inNumFrames); } OSStatus AudioFileManager::Render (AudioBufferList &ioData, UInt32 inNumFrames) { UInt32 numDataPacketsNeeded = inNumFrames; #if LOG_DATA_FLOW fprintf(stdout, "***** requested packets from Render Proc = %ld\n", numDataPacketsNeeded); #endif OSStatus result = AudioConverterFillComplexBuffer( mParentConverter, ACComplexInputProc, this, &numDataPacketsNeeded, &ioData, 0); #if LOG_DATA_FLOW fprintf(stdout, "***** Render Proc returned packets = %ld\n", numDataPacketsNeeded); #endif if (result) fprintf (stderr, "AudioConverterFillComplexBuffer:%ld,%-4.4s\n", result, (char*)&result); #if 0 //debug test assumes framePerPacket == 1 if (numDataPacketsNeeded != inNumFrames) printf ("after conv:%ld,%ld\n", numDataPacketsNeeded, inNumFrames); #endif if (!result) AfterRender (); else GetParent().DoNotification (result); return result; } OSStatus AudioFileManager::ACComplexInputProc (AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData) { AudioFileManager* THIS = (AudioFileManager*)inUserData; AudioBuffer* firstBuffer = ioData->mBuffers; UInt32 packetCount = 0; AudioStreamPacketDescription *packetDescs = NULL; #if 0 // we can leave the channels alone.. this would have been set for us // (that's why ioData is both in and out -> in has channels set UInt32 size = firstBuffer->mDataByteSize; void* ptr = firstBuffer->mData; UInt32 framesIn = *ioNumberDataPackets; #endif #if LOG_DATA_FLOW fprintf(stdout, "***** ACComplexInputProc - requested packets from AC = %ld\n", *ioNumberDataPackets); #endif OSStatus res = THIS->GetFileData (&firstBuffer->mData, &firstBuffer->mDataByteSize, &packetCount, &packetDescs); #if LOG_DATA_FLOW fprintf(stdout, "***** ACComplexInputProc - packets returned to AC = %ld\n", packetCount); #endif if (firstBuffer->mDataByteSize == 0) *ioNumberDataPackets = 0; else { if(outDataPacketDescription) *outDataPacketDescription = packetDescs; *ioNumberDataPackets = packetCount; } #if 0 // this is debug code to just check out how the data handling is doing printf ("fIn = %ld, fOut=%ld, bytesIn=%ld, bytesOut=%ld, ptrIn=%x, ptrOut=%x\n", framesIn, *ioNumberDataPackets, size, firstBuffer->mDataByteSize, (int)ptr, (int)firstBuffer->mData); #endif return res; } AudioFileManager::~AudioFileManager () { if (mFileBuffer) { free (mFileBuffer); mFileBuffer = 0; } } AudioFilePlayer::AudioFilePlayer (const FSRef& inFileRef) : mConnected (false), mAudioFileManager (0), mConverter (0), mNotifier (0) { SInt64 fileDataSize = 0; SInt64 packetCount = 0; UInt32 maxPacketSize = 0; OpenFile (inFileRef, fileDataSize, packetCount, maxPacketSize); #if DEBUG printf ("There are %qd frames (packets) in this file\n", packetCount); #endif // we'll automatically load files that are smaller than 256k if (fileDataSize < (1024 * 256)) { mAudioFileManager = new AudioFileData ( *this, mAudioFileID, fileDataSize, packetCount, maxPacketSize, packetCount); mUsingReaderThread = false; } else { // we'll also check to see if the file is smaller // than what we would create buffers for // we want about a seconds worth of data for the buffer int secsPackets = UInt32 (mFileDescription.mSampleRate / mFileDescription.mFramesPerPacket); #if DEBUG PrintStreamDesc (&mFileDescription); #endif mAudioFileManager = new AudioFileReaderThread ( *this, mAudioFileID, fileDataSize, packetCount, maxPacketSize, secsPackets); mUsingReaderThread = true; } } // you can put a rate scalar here to play the file faster or slower // by multiplying the same rate by the desired factor // eg fileSampleRate * 2 -> twice as fast // before you create the AudioConverter void AudioFilePlayer::SetDestination (AudioUnit &inDestUnit, int inBusNumber) { if (mConnected) throw static_cast(-1); //can't set dest if already engaged mPlayUnit = inDestUnit; mBusNumber = inBusNumber; OSStatus result = noErr; if (mConverter) { result = AudioConverterDispose (mConverter); THROW_RESULT("AudioConverterDispose") } AudioStreamBasicDescription destDesc; UInt32 size = sizeof (destDesc); result = AudioUnitGetProperty (inDestUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, inBusNumber, &destDesc, &size); THROW_RESULT("AudioUnitGetProperty") #if DEBUG PrintStreamDesc (&destDesc); #endif //we can "down" cast a component instance to a component ComponentDescription desc; result = GetComponentInfo ((Component)inDestUnit, &desc, 0, 0, 0); THROW_RESULT("GetComponentInfo") // a "neat" trick: // if you want to play the file back faster or slower then you can // alter the sample rate of the fileDescription before you create the converter. // thus... // mFileDescription.mSampleRate *= 2; // the file will play back twice as fast // mFileDescription.mSampeRate *= 0.5; // half speed result = AudioConverterNew (&mFileDescription, &destDesc, &mConverter); THROW_RESULT("AudioConverterNew") UInt32 magicCookieSize = 0; result = AudioFileGetPropertyInfo( mAudioFileID, kAudioFilePropertyMagicCookieData, &magicCookieSize, NULL); if (result == noErr) { void *magicCookie = calloc (1, magicCookieSize); if (magicCookie) { result = AudioFileGetProperty ( mAudioFileID, kAudioFilePropertyMagicCookieData, &magicCookieSize, magicCookie); // Give the AudioConverter the magic cookie decompression params if there are any if (result == noErr) { result = AudioConverterSetProperty( mConverter, kAudioConverterDecompressionMagicCookie, magicCookieSize, magicCookie); } if (magicCookie) free(magicCookie); } } // if we have a mono source, we're going to copy each channel into // the destination's channel source... if (mFileDescription.mChannelsPerFrame == 1) { SInt32* channelMap = new SInt32 [destDesc.mChannelsPerFrame]; for (unsigned int i = 0; i < destDesc.mChannelsPerFrame; ++i) channelMap[i] = 0; //set first channel to all output channels result = AudioConverterSetProperty(mConverter, kAudioConverterChannelMap, (sizeof(SInt32) * destDesc.mChannelsPerFrame), channelMap); THROW_RESULT("AudioConverterSetProperty") delete [] channelMap; } #if 0 // this uses the better quality SRC UInt32 srcID = kAudioUnitSRCAlgorithm_Polyphase; result = AudioConverterSetProperty(mConverter, kAudioConverterSampleRateConverterAlgorithm, sizeof(srcID), &srcID); THROW_RESULT("AudioConverterSetProperty") #endif } AudioFilePlayer::~AudioFilePlayer() { Disconnect(); if (mAudioFileManager) { delete mAudioFileManager; mAudioFileManager = 0; } if (mAudioFileID) { ::AudioFileClose (mAudioFileID); mAudioFileID = 0; } if (mConverter) { AudioConverterDispose (mConverter); mConverter = 0; } } void AudioFilePlayer::Connect() { #if DEBUG printf ("Connect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0)); #endif if (!mConnected) { mAudioFileManager->Connect(mConverter); // set the render callback for the file data to be supplied to the sound converter AU mRenderCallback.inputProc = AudioFileManager::FileRenderProc; mRenderCallback.inputProcRefCon = mAudioFileManager; OSStatus result = AudioUnitSetProperty (mPlayUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mBusNumber, &mRenderCallback, sizeof(mRenderCallback)); THROW_RESULT("AudioUnitSetProperty") mConnected = true; } } #warning This should redirect the calling of notification code to some other thread void AudioFilePlayer::DoNotification (OSStatus inStatus) const { AudioFilePlayer* THIS = const_cast(this); if (mNotifier) { (*mNotifier) (mRefCon, inStatus); } else { if (inStatus == kAudioFilePlay_FileIsFinished) THIS->Disconnect(); else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun) THIS->Disconnect(); } } void AudioFilePlayer::Disconnect () { #if DEBUG printf ("Disconnect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0)); #endif if (mConnected) { mConnected = false; mRenderCallback.inputProc = 0; mRenderCallback.inputProcRefCon = 0; OSStatus result = AudioUnitSetProperty (mPlayUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mBusNumber, &mRenderCallback, sizeof(mRenderCallback)); if (result) fprintf(stderr, "AudioUnitSetProperty:RemoveRenderCallback:%ld", result); mAudioFileManager->Disconnect(); } } void AudioFilePlayer::SetLooping (bool inLoop) { mAudioFileManager->SetLooping (inLoop); } bool AudioFilePlayer::IsLooping () const { return mAudioFileManager->IsLooping(); } void AudioFilePlayer::OpenFile (const FSRef& inRef, SInt64& outFileDataSize, SInt64& outPacketCount, UInt32& outMaxPacketSize) { OSStatus result = AudioFileOpen (&inRef, fsRdPerm, 0, &mAudioFileID); THROW_RESULT("AudioFileOpen") UInt32 dataSize = sizeof(AudioStreamBasicDescription); result = AudioFileGetProperty (mAudioFileID, kAudioFilePropertyDataFormat, &dataSize, &mFileDescription); THROW_RESULT("AudioFileGetProperty") dataSize = sizeof (SInt64); result = AudioFileGetProperty (mAudioFileID, kAudioFilePropertyAudioDataByteCount, &dataSize, &outFileDataSize); THROW_RESULT("AudioFileGetProperty") dataSize = sizeof (SInt64); result = AudioFileGetProperty (mAudioFileID, kAudioFilePropertyAudioDataPacketCount, &dataSize, &outPacketCount); THROW_RESULT("AudioFileGetProperty") dataSize = sizeof (UInt32); result = AudioFileGetProperty (mAudioFileID, kAudioFilePropertyMaximumPacketSize, &dataSize, &outMaxPacketSize); THROW_RESULT("AudioFileGetProperty") }