| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997-2009 Sam Lantinga |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Library General Public |
| License as published by the Free Software Foundation; either |
| version 2 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Library General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public |
| License along with this library; if not, write to the Free |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| Sam Lantinga |
| slouken@libsdl.org |
| |
| This file based on Apple sample code. We haven't changed the file name, |
| so if you want to see the original search for it on apple.com/developer |
| */ |
| #include "SDL_config.h" |
| |
| /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| AudioFilePlayer.cpp |
| */ |
| #include "AudioFilePlayer.h" |
| |
| /* |
| void ThrowResult (OSStatus result, const char* str) |
| { |
| SDL_SetError ("Error: %s %d", str, result); |
| throw result; |
| } |
| */ |
| |
| #if DEBUG |
| static void |
| PrintStreamDesc(AudioStreamBasicDescription * inDesc) |
| { |
| if (!inDesc) { |
| printf("Can't print a NULL desc!\n"); |
| return; |
| } |
| |
| printf("- - - - - - - - - - - - - - - - - - - -\n"); |
| printf(" Sample Rate:%f\n", inDesc->mSampleRate); |
| printf(" Format ID:%s\n", (char *) &inDesc->mFormatID); |
| printf(" Format Flags:%lX\n", inDesc->mFormatFlags); |
| printf(" Bytes per Packet:%ld\n", inDesc->mBytesPerPacket); |
| printf(" Frames per Packet:%ld\n", inDesc->mFramesPerPacket); |
| printf(" Bytes per Frame:%ld\n", inDesc->mBytesPerFrame); |
| printf(" Channels per Frame:%ld\n", inDesc->mChannelsPerFrame); |
| printf(" Bits per Channel:%ld\n", inDesc->mBitsPerChannel); |
| printf("- - - - - - - - - - - - - - - - - - - -\n"); |
| } |
| #endif |
| |
| |
| static int |
| AudioFilePlayer_SetDestination(AudioFilePlayer * afp, AudioUnit * inDestUnit) |
| { |
| /*if (afp->mConnected) throw static_cast<OSStatus>(-1); *//* can't set dest if already engaged */ |
| if (afp->mConnected) |
| return 0; |
| |
| SDL_memcpy(&afp->mPlayUnit, inDestUnit, sizeof(afp->mPlayUnit)); |
| |
| OSStatus result = noErr; |
| |
| |
| /* we can "down" cast a component instance to a component */ |
| ComponentDescription desc; |
| result = GetComponentInfo((Component) * inDestUnit, &desc, 0, 0, 0); |
| if (result) |
| return 0; /*THROW_RESULT("GetComponentInfo") */ |
| |
| /* we're going to use this to know which convert routine to call |
| a v1 audio unit will have a type of 'aunt' |
| a v2 audio unit will have one of several different types. */ |
| if (desc.componentType != kAudioUnitComponentType) { |
| result = badComponentInstance; |
| /*THROW_RESULT("BAD COMPONENT") */ |
| if (result) |
| return 0; |
| } |
| |
| /* Set the input format of the audio unit. */ |
| result = AudioUnitSetProperty(*inDestUnit, |
| kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mFileDescription, |
| sizeof(afp->mFileDescription)); |
| /*THROW_RESULT("AudioUnitSetProperty") */ |
| if (result) |
| return 0; |
| return 1; |
| } |
| |
| static void |
| AudioFilePlayer_SetNotifier(AudioFilePlayer * afp, |
| AudioFilePlayNotifier inNotifier, void *inRefCon) |
| { |
| afp->mNotifier = inNotifier; |
| afp->mRefCon = inRefCon; |
| } |
| |
| static int |
| AudioFilePlayer_IsConnected(AudioFilePlayer * afp) |
| { |
| return afp->mConnected; |
| } |
| |
| static AudioUnit |
| AudioFilePlayer_GetDestUnit(AudioFilePlayer * afp) |
| { |
| return afp->mPlayUnit; |
| } |
| |
| static void |
| AudioFilePlayer_Print(AudioFilePlayer * afp) |
| { |
| #if DEBUG |
| printf("Is Connected:%s\n", (IsConnected()? "true" : "false")); |
| printf("- - - - - - - - - - - - - - \n"); |
| #endif |
| } |
| |
| static void |
| AudioFilePlayer_SetStartFrame(AudioFilePlayer * afp, int frame) |
| { |
| SInt64 position = frame * 2352; |
| |
| afp->mStartFrame = frame; |
| afp->mAudioFileManager->SetPosition(afp->mAudioFileManager, position); |
| } |
| |
| |
| static int |
| AudioFilePlayer_GetCurrentFrame(AudioFilePlayer * afp) |
| { |
| return afp->mStartFrame + |
| (afp->mAudioFileManager->GetByteCounter(afp->mAudioFileManager) / |
| 2352); |
| } |
| |
| static void |
| AudioFilePlayer_SetStopFrame(AudioFilePlayer * afp, int frame) |
| { |
| SInt64 position = frame * 2352; |
| |
| afp->mAudioFileManager->SetEndOfFile(afp->mAudioFileManager, position); |
| } |
| |
| void |
| delete_AudioFilePlayer(AudioFilePlayer * afp) |
| { |
| if (afp != NULL) { |
| afp->Disconnect(afp); |
| |
| if (afp->mAudioFileManager) { |
| delete_AudioFileManager(afp->mAudioFileManager); |
| afp->mAudioFileManager = 0; |
| } |
| |
| if (afp->mForkRefNum) { |
| FSCloseFork(afp->mForkRefNum); |
| afp->mForkRefNum = 0; |
| } |
| SDL_free(afp); |
| } |
| } |
| |
| static int |
| AudioFilePlayer_Connect(AudioFilePlayer * afp) |
| { |
| #if DEBUG |
| printf("Connect:%x, engaged=%d\n", (int) afp->mPlayUnit, |
| (afp->mConnected ? 1 : 0)); |
| #endif |
| if (!afp->mConnected) { |
| if (!afp->mAudioFileManager->DoConnect(afp->mAudioFileManager)) |
| return 0; |
| |
| /* set the render callback for the file data to be supplied to the sound converter AU */ |
| afp->mInputCallback.inputProc = afp->mAudioFileManager->FileInputProc; |
| afp->mInputCallback.inputProcRefCon = afp->mAudioFileManager; |
| |
| OSStatus result = AudioUnitSetProperty(afp->mPlayUnit, |
| kAudioUnitProperty_SetInputCallback, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mInputCallback, |
| sizeof(afp->mInputCallback)); |
| if (result) |
| return 0; /*THROW_RESULT("AudioUnitSetProperty") */ |
| afp->mConnected = 1; |
| } |
| |
| return 1; |
| } |
| |
| /* warning noted, now please go away ;-) */ |
| /* #warning This should redirect the calling of notification code to some other thread */ |
| static void |
| AudioFilePlayer_DoNotification(AudioFilePlayer * afp, OSStatus inStatus) |
| { |
| if (afp->mNotifier) { |
| (*afp->mNotifier) (afp->mRefCon, inStatus); |
| } else { |
| SDL_SetError("Notification posted with no notifier in place"); |
| |
| if (inStatus == kAudioFilePlay_FileIsFinished) |
| afp->Disconnect(afp); |
| else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun) |
| afp->Disconnect(afp); |
| } |
| } |
| |
| static void |
| AudioFilePlayer_Disconnect(AudioFilePlayer * afp) |
| { |
| #if DEBUG |
| printf("Disconnect:%x,%ld, engaged=%d\n", (int) afp->mPlayUnit, 0, |
| (afp->mConnected ? 1 : 0)); |
| #endif |
| if (afp->mConnected) { |
| afp->mConnected = 0; |
| |
| afp->mInputCallback.inputProc = 0; |
| afp->mInputCallback.inputProcRefCon = 0; |
| OSStatus result = AudioUnitSetProperty(afp->mPlayUnit, |
| kAudioUnitProperty_SetInputCallback, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mInputCallback, |
| sizeof(afp->mInputCallback)); |
| if (result) |
| SDL_SetError("AudioUnitSetProperty:RemoveInputCallback:%ld", |
| result); |
| |
| afp->mAudioFileManager->Disconnect(afp->mAudioFileManager); |
| } |
| } |
| |
| typedef struct |
| { |
| UInt32 offset; |
| UInt32 blockSize; |
| } SSNDData; |
| |
| static int |
| AudioFilePlayer_OpenFile(AudioFilePlayer * afp, const FSRef * inRef, |
| SInt64 * outFileDataSize) |
| { |
| ContainerChunk chunkHeader; |
| ChunkHeader chunk; |
| SSNDData ssndData; |
| |
| OSErr result; |
| HFSUniStr255 dfName; |
| ByteCount actual; |
| SInt64 offset; |
| |
| /* Open the data fork of the input file */ |
| result = FSGetDataForkName(&dfName); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSGetDataForkName") */ |
| |
| result = |
| FSOpenFork(inRef, dfName.length, dfName.unicode, fsRdPerm, |
| &afp->mForkRefNum); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSOpenFork") */ |
| |
| /* Read the file header, and check if it's indeed an AIFC file */ |
| result = |
| FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(chunkHeader), |
| &chunkHeader, &actual); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork") */ |
| |
| if (chunkHeader.ckID != 'FORM') { |
| result = -1; |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): chunk id is not 'FORM'"); */ |
| } |
| |
| if (chunkHeader.formType != 'AIFC') { |
| result = -1; |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): file format is not 'AIFC'"); */ |
| } |
| |
| /* Search for the SSND chunk. We ignore all compression etc. information |
| in other chunks. Of course that is kind of evil, but for now we are lazy |
| and rely on the cdfs to always give us the same fixed format. |
| TODO: Parse the COMM chunk we currently skip to fill in mFileDescription. |
| */ |
| offset = 0; |
| do { |
| result = |
| FSReadFork(afp->mForkRefNum, fsFromMark, offset, |
| sizeof(chunk), &chunk, &actual); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork") */ |
| |
| /* Skip the chunk data */ |
| offset = chunk.ckSize; |
| } while (chunk.ckID != 'SSND'); |
| |
| /* Read the header of the SSND chunk. After this, we are positioned right |
| at the start of the audio data. */ |
| result = |
| FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(ssndData), |
| &ssndData, &actual); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork") */ |
| |
| result = FSSetForkPosition(afp->mForkRefNum, fsFromMark, ssndData.offset); |
| if (result) |
| return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSSetForkPosition") */ |
| |
| /* Data size */ |
| *outFileDataSize = chunk.ckSize - ssndData.offset - 8; |
| |
| /* File format */ |
| afp->mFileDescription.mSampleRate = 44100; |
| afp->mFileDescription.mFormatID = kAudioFormatLinearPCM; |
| afp->mFileDescription.mFormatFlags = |
| kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; |
| afp->mFileDescription.mBytesPerPacket = 4; |
| afp->mFileDescription.mFramesPerPacket = 1; |
| afp->mFileDescription.mBytesPerFrame = 4; |
| afp->mFileDescription.mChannelsPerFrame = 2; |
| afp->mFileDescription.mBitsPerChannel = 16; |
| |
| return 1; |
| } |
| |
| AudioFilePlayer * |
| new_AudioFilePlayer(const FSRef * inFileRef) |
| { |
| SInt64 fileDataSize = 0; |
| |
| AudioFilePlayer *afp = |
| (AudioFilePlayer *) SDL_malloc(sizeof(AudioFilePlayer)); |
| if (afp == NULL) |
| return NULL; |
| SDL_memset(afp, '\0', sizeof(*afp)); |
| |
| #define SET_AUDIOFILEPLAYER_METHOD(m) afp->m = AudioFilePlayer_##m |
| SET_AUDIOFILEPLAYER_METHOD(SetDestination); |
| SET_AUDIOFILEPLAYER_METHOD(SetNotifier); |
| SET_AUDIOFILEPLAYER_METHOD(SetStartFrame); |
| SET_AUDIOFILEPLAYER_METHOD(GetCurrentFrame); |
| SET_AUDIOFILEPLAYER_METHOD(SetStopFrame); |
| SET_AUDIOFILEPLAYER_METHOD(Connect); |
| SET_AUDIOFILEPLAYER_METHOD(Disconnect); |
| SET_AUDIOFILEPLAYER_METHOD(DoNotification); |
| SET_AUDIOFILEPLAYER_METHOD(IsConnected); |
| SET_AUDIOFILEPLAYER_METHOD(GetDestUnit); |
| SET_AUDIOFILEPLAYER_METHOD(Print); |
| SET_AUDIOFILEPLAYER_METHOD(OpenFile); |
| #undef SET_AUDIOFILEPLAYER_METHOD |
| |
| if (!afp->OpenFile(afp, inFileRef, &fileDataSize)) { |
| SDL_free(afp); |
| return NULL; |
| } |
| |
| /* we want about 4 seconds worth of data for the buffer */ |
| int bytesPerSecond = |
| (UInt32) (4 * afp->mFileDescription.mSampleRate * |
| afp->mFileDescription.mBytesPerFrame); |
| |
| #if DEBUG |
| printf("File format:\n"); |
| PrintStreamDesc(&afp->mFileDescription); |
| #endif |
| |
| afp->mAudioFileManager = new_AudioFileManager(afp, afp->mForkRefNum, |
| fileDataSize, |
| bytesPerSecond); |
| if (afp->mAudioFileManager == NULL) { |
| delete_AudioFilePlayer(afp); |
| return NULL; |
| } |
| |
| return afp; |
| } |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |