| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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" |
| |
| /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| AudioFileManager.cpp |
| */ |
| #include "AudioFilePlayer.h" |
| #include <mach/mach.h> /* used for setting policy of thread */ |
| #include "SDLOSXCAGuard.h" |
| #include <pthread.h> |
| |
| /*#include <list>*/ |
| |
| /*typedef void *FileData;*/ |
| typedef struct S_FileData |
| { |
| AudioFileManager *obj; |
| struct S_FileData *next; |
| } FileData; |
| |
| |
| typedef struct S_FileReaderThread { |
| /*public:*/ |
| SDLOSXCAGuard* (*GetGuard)(struct S_FileReaderThread *frt); |
| void (*AddReader)(struct S_FileReaderThread *frt); |
| void (*RemoveReader)(struct S_FileReaderThread *frt, AudioFileManager* inItem); |
| int (*TryNextRead)(struct S_FileReaderThread *frt, AudioFileManager* inItem); |
| |
| int mThreadShouldDie; |
| |
| /*private:*/ |
| /*typedef std::list<AudioFileManager*> FileData;*/ |
| |
| SDLOSXCAGuard *mGuard; |
| UInt32 mThreadPriority; |
| |
| int mNumReaders; |
| FileData *mFileData; |
| |
| |
| void (*ReadNextChunk)(struct S_FileReaderThread *frt); |
| int (*StartFixedPriorityThread)(struct S_FileReaderThread *frt); |
| /*static*/ |
| UInt32 (*GetThreadBasePriority)(pthread_t inThread); |
| /*static*/ |
| void* (*DiskReaderEntry)(void *inRefCon); |
| } FileReaderThread; |
| |
| |
| static SDLOSXCAGuard* FileReaderThread_GetGuard(FileReaderThread *frt) |
| { |
| return frt->mGuard; |
| } |
| |
| /* returns 1 if succeeded */ |
| static int FileReaderThread_TryNextRead (FileReaderThread *frt, AudioFileManager* inItem) |
| { |
| int didLock = 0; |
| int succeeded = 0; |
| if (frt->mGuard->Try(frt->mGuard, &didLock)) |
| { |
| /*frt->mFileData.push_back (inItem);*/ |
| /* !!! FIXME: this could be faster with a "tail" member. --ryan. */ |
| FileData *i = frt->mFileData; |
| FileData *prev = NULL; |
| |
| FileData *newfd = (FileData *) SDL_malloc(sizeof (FileData)); |
| newfd->obj = inItem; |
| newfd->next = NULL; |
| |
| while (i != NULL) { prev = i; i = i->next; } |
| if (prev == NULL) |
| frt->mFileData = newfd; |
| else |
| prev->next = newfd; |
| |
| frt->mGuard->Notify(frt->mGuard); |
| succeeded = 1; |
| |
| if (didLock) |
| frt->mGuard->Unlock(frt->mGuard); |
| } |
| |
| return succeeded; |
| } |
| |
| static void FileReaderThread_AddReader(FileReaderThread *frt) |
| { |
| if (frt->mNumReaders == 0) |
| { |
| frt->mThreadShouldDie = 0; |
| frt->StartFixedPriorityThread (frt); |
| } |
| frt->mNumReaders++; |
| } |
| |
| static void FileReaderThread_RemoveReader (FileReaderThread *frt, AudioFileManager* inItem) |
| { |
| if (frt->mNumReaders > 0) |
| { |
| int bNeedsRelease = frt->mGuard->Lock(frt->mGuard); |
| |
| /*frt->mFileData.remove (inItem);*/ |
| FileData *i = frt->mFileData; |
| FileData *prev = NULL; |
| while (i != NULL) |
| { |
| FileData *next = i->next; |
| if (i->obj != inItem) |
| prev = i; |
| else |
| { |
| if (prev == NULL) |
| frt->mFileData = next; |
| else |
| prev->next = next; |
| SDL_free(i); |
| } |
| i = next; |
| } |
| |
| if (--frt->mNumReaders == 0) { |
| frt->mThreadShouldDie = 1; |
| frt->mGuard->Notify(frt->mGuard); /* wake up thread so it will quit */ |
| frt->mGuard->Wait(frt->mGuard); /* wait for thread to die */ |
| } |
| |
| if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard); |
| } |
| } |
| |
| static int FileReaderThread_StartFixedPriorityThread (FileReaderThread *frt) |
| { |
| pthread_attr_t theThreadAttrs; |
| pthread_t pThread; |
| |
| OSStatus result = pthread_attr_init(&theThreadAttrs); |
| if (result) return 0; /*THROW_RESULT("pthread_attr_init - Thread attributes could not be created.")*/ |
| |
| result = pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED); |
| if (result) return 0; /*THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.")*/ |
| |
| result = pthread_create (&pThread, &theThreadAttrs, frt->DiskReaderEntry, frt); |
| if (result) return 0; /*THROW_RESULT("pthread_create - Create and start the thread.")*/ |
| |
| pthread_attr_destroy(&theThreadAttrs); |
| |
| /* we've now created the thread and started it |
| we'll now set the priority of the thread to the nominated priority |
| and we'll also make the thread fixed */ |
| thread_extended_policy_data_t theFixedPolicy; |
| thread_precedence_policy_data_t thePrecedencePolicy; |
| SInt32 relativePriority; |
| |
| /* make thread fixed */ |
| theFixedPolicy.timeshare = 0; /* set to 1 for a non-fixed thread */ |
| result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT); |
| if (result) return 0; /*THROW_RESULT("thread_policy - Couldn't set thread as fixed priority.")*/ |
| /* set priority */ |
| /* precedency policy's "importance" value is relative to spawning thread's priority */ |
| relativePriority = frt->mThreadPriority - frt->GetThreadBasePriority(pthread_self()); |
| |
| thePrecedencePolicy.importance = relativePriority; |
| result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT); |
| if (result) return 0; /*THROW_RESULT("thread_policy - Couldn't set thread priority.")*/ |
| |
| return 1; |
| } |
| |
| static UInt32 FileReaderThread_GetThreadBasePriority (pthread_t inThread) |
| { |
| thread_basic_info_data_t threadInfo; |
| policy_info_data_t thePolicyInfo; |
| unsigned int count; |
| |
| /* get basic info */ |
| count = THREAD_BASIC_INFO_COUNT; |
| thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (integer_t*)&threadInfo, &count); |
| |
| switch (threadInfo.policy) { |
| case POLICY_TIMESHARE: |
| count = POLICY_TIMESHARE_INFO_COUNT; |
| thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (integer_t*)&(thePolicyInfo.ts), &count); |
| return thePolicyInfo.ts.base_priority; |
| break; |
| |
| case POLICY_FIFO: |
| count = POLICY_FIFO_INFO_COUNT; |
| thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (integer_t*)&(thePolicyInfo.fifo), &count); |
| if (thePolicyInfo.fifo.depressed) { |
| return thePolicyInfo.fifo.depress_priority; |
| } else { |
| return thePolicyInfo.fifo.base_priority; |
| } |
| break; |
| |
| case POLICY_RR: |
| count = POLICY_RR_INFO_COUNT; |
| thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (integer_t*)&(thePolicyInfo.rr), &count); |
| if (thePolicyInfo.rr.depressed) { |
| return thePolicyInfo.rr.depress_priority; |
| } else { |
| return thePolicyInfo.rr.base_priority; |
| } |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static void *FileReaderThread_DiskReaderEntry (void *inRefCon) |
| { |
| FileReaderThread *frt = (FileReaderThread *)inRefCon; |
| frt->ReadNextChunk(frt); |
| #if DEBUG |
| printf ("finished with reading file\n"); |
| #endif |
| |
| return 0; |
| } |
| |
| static void FileReaderThread_ReadNextChunk (FileReaderThread *frt) |
| { |
| OSStatus result; |
| UInt32 dataChunkSize; |
| AudioFileManager* theItem = 0; |
| |
| for (;;) |
| { |
| { /* this is a scoped based lock */ |
| int bNeedsRelease = frt->mGuard->Lock(frt->mGuard); |
| |
| if (frt->mThreadShouldDie) { |
| frt->mGuard->Notify(frt->mGuard); |
| if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard); |
| return; |
| } |
| |
| /*if (frt->mFileData.empty())*/ |
| if (frt->mFileData == NULL) |
| { |
| frt->mGuard->Wait(frt->mGuard); |
| } |
| |
| /* kill thread */ |
| if (frt->mThreadShouldDie) { |
| |
| frt->mGuard->Notify(frt->mGuard); |
| if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard); |
| return; |
| } |
| |
| /*theItem = frt->mFileData.front();*/ |
| /*frt->mFileData.pop_front();*/ |
| theItem = NULL; |
| if (frt->mFileData != NULL) |
| { |
| FileData *next = frt->mFileData->next; |
| theItem = frt->mFileData->obj; |
| SDL_free(frt->mFileData); |
| frt->mFileData = next; |
| } |
| |
| if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard); |
| } |
| |
| if ((theItem->mFileLength - theItem->mReadFilePosition) < theItem->mChunkSize) |
| dataChunkSize = theItem->mFileLength - theItem->mReadFilePosition; |
| else |
| dataChunkSize = theItem->mChunkSize; |
| |
| /* this is the exit condition for the thread */ |
| if (dataChunkSize <= 0) { |
| theItem->mFinishedReadingData = 1; |
| continue; |
| } |
| /* construct pointer */ |
| char* writePtr = (char *) (theItem->GetFileBuffer(theItem) + |
| (theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSize)); |
| |
| /* read data */ |
| result = theItem->Read(theItem, writePtr, &dataChunkSize); |
| if (result != noErr && result != eofErr) { |
| AudioFilePlayer *afp = (AudioFilePlayer *) theItem->GetParent(theItem); |
| afp->DoNotification(afp, result); |
| continue; |
| } |
| |
| if (dataChunkSize != theItem->mChunkSize) |
| { |
| writePtr += dataChunkSize; |
| |
| /* can't exit yet.. we still have to pass the partial buffer back */ |
| SDL_memset(writePtr, 0, (theItem->mChunkSize - dataChunkSize)); |
| } |
| |
| theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer; /* switch buffers */ |
| |
| if (result == eofErr) |
| theItem->mReadFilePosition = theItem->mFileLength; |
| else |
| theItem->mReadFilePosition += dataChunkSize; /* increment count */ |
| } |
| } |
| |
| void delete_FileReaderThread(FileReaderThread *frt) |
| { |
| if (frt != NULL) |
| { |
| delete_SDLOSXCAGuard(frt->mGuard); |
| SDL_free(frt); |
| } |
| } |
| |
| FileReaderThread *new_FileReaderThread () |
| { |
| FileReaderThread *frt = (FileReaderThread *) SDL_malloc(sizeof (FileReaderThread)); |
| if (frt == NULL) |
| return NULL; |
| SDL_memset(frt, '\0', sizeof (*frt)); |
| |
| frt->mGuard = new_SDLOSXCAGuard(); |
| if (frt->mGuard == NULL) |
| { |
| SDL_free(frt); |
| return NULL; |
| } |
| |
| #define SET_FILEREADERTHREAD_METHOD(m) frt->m = FileReaderThread_##m |
| SET_FILEREADERTHREAD_METHOD(GetGuard); |
| SET_FILEREADERTHREAD_METHOD(AddReader); |
| SET_FILEREADERTHREAD_METHOD(RemoveReader); |
| SET_FILEREADERTHREAD_METHOD(TryNextRead); |
| SET_FILEREADERTHREAD_METHOD(ReadNextChunk); |
| SET_FILEREADERTHREAD_METHOD(StartFixedPriorityThread); |
| SET_FILEREADERTHREAD_METHOD(GetThreadBasePriority); |
| SET_FILEREADERTHREAD_METHOD(DiskReaderEntry); |
| #undef SET_FILEREADERTHREAD_METHOD |
| |
| frt->mThreadPriority = 62; |
| return frt; |
| } |
| |
| |
| static FileReaderThread *sReaderThread; |
| |
| |
| static int AudioFileManager_DoConnect (AudioFileManager *afm) |
| { |
| if (!afm->mIsEngaged) |
| { |
| OSStatus result; |
| |
| /*afm->mReadFilePosition = 0;*/ |
| afm->mFinishedReadingData = 0; |
| |
| afm->mNumTimesAskedSinceFinished = 0; |
| afm->mLockUnsuccessful = 0; |
| |
| UInt32 dataChunkSize; |
| |
| if ((afm->mFileLength - afm->mReadFilePosition) < afm->mChunkSize) |
| dataChunkSize = afm->mFileLength - afm->mReadFilePosition; |
| else |
| dataChunkSize = afm->mChunkSize; |
| |
| result = afm->Read(afm, afm->mFileBuffer, &dataChunkSize); |
| if (result) return 0; /*THROW_RESULT("AudioFileManager::DoConnect(): Read")*/ |
| |
| afm->mReadFilePosition += dataChunkSize; |
| |
| afm->mWriteToFirstBuffer = 0; |
| afm->mReadFromFirstBuffer = 1; |
| |
| sReaderThread->AddReader(sReaderThread); |
| |
| afm->mIsEngaged = 1; |
| } |
| /* |
| else |
| throw static_cast<OSStatus>(-1); */ /* thread has already been started */ |
| |
| return 1; |
| } |
| |
| static void AudioFileManager_Disconnect (AudioFileManager *afm) |
| { |
| if (afm->mIsEngaged) |
| { |
| sReaderThread->RemoveReader (sReaderThread, afm); |
| afm->mIsEngaged = 0; |
| } |
| } |
| |
| static OSStatus AudioFileManager_Read(AudioFileManager *afm, char *buffer, UInt32 *len) |
| { |
| return FSReadFork (afm->mForkRefNum, |
| fsFromStart, |
| afm->mReadFilePosition + afm->mAudioDataOffset, |
| *len, |
| buffer, |
| len); |
| } |
| |
| static OSStatus AudioFileManager_GetFileData (AudioFileManager *afm, void** inOutData, UInt32 *inOutDataSize) |
| { |
| if (afm->mFinishedReadingData) |
| { |
| ++afm->mNumTimesAskedSinceFinished; |
| *inOutDataSize = 0; |
| *inOutData = 0; |
| return noErr; |
| } |
| |
| if (afm->mReadFromFirstBuffer == afm->mWriteToFirstBuffer) { |
| #if DEBUG |
| printf ("* * * * * * * Can't keep up with reading file\n"); |
| #endif |
| |
| afm->mParent->DoNotification (afm->mParent, kAudioFilePlayErr_FilePlayUnderrun); |
| *inOutDataSize = 0; |
| *inOutData = 0; |
| } else { |
| *inOutDataSize = afm->mChunkSize; |
| *inOutData = afm->mReadFromFirstBuffer ? afm->mFileBuffer : (afm->mFileBuffer + afm->mChunkSize); |
| } |
| |
| afm->mLockUnsuccessful = !sReaderThread->TryNextRead (sReaderThread, afm); |
| |
| afm->mReadFromFirstBuffer = !afm->mReadFromFirstBuffer; |
| |
| return noErr; |
| } |
| |
| static void AudioFileManager_AfterRender (AudioFileManager *afm) |
| { |
| if (afm->mNumTimesAskedSinceFinished > 0) |
| { |
| int didLock = 0; |
| SDLOSXCAGuard *guard = sReaderThread->GetGuard(sReaderThread); |
| if (guard->Try(guard, &didLock)) { |
| afm->mParent->DoNotification (afm->mParent, kAudioFilePlay_FileIsFinished); |
| if (didLock) |
| guard->Unlock(guard); |
| } |
| } |
| |
| if (afm->mLockUnsuccessful) |
| afm->mLockUnsuccessful = !sReaderThread->TryNextRead (sReaderThread, afm); |
| } |
| |
| static void AudioFileManager_SetPosition (AudioFileManager *afm, SInt64 pos) |
| { |
| if (pos < 0 || pos >= afm->mFileLength) { |
| SDL_SetError ("AudioFileManager::SetPosition - position invalid: %d filelen=%d\n", |
| (unsigned int)pos, (unsigned int)afm->mFileLength); |
| pos = 0; |
| } |
| |
| afm->mReadFilePosition = pos; |
| } |
| |
| static void AudioFileManager_SetEndOfFile (AudioFileManager *afm, SInt64 pos) |
| { |
| if (pos <= 0 || pos > afm->mFileLength) { |
| SDL_SetError ("AudioFileManager::SetEndOfFile - position beyond actual eof\n"); |
| pos = afm->mFileLength; |
| } |
| |
| afm->mFileLength = pos; |
| } |
| |
| static const char *AudioFileManager_GetFileBuffer(AudioFileManager *afm) |
| { |
| return afm->mFileBuffer; |
| } |
| |
| const AudioFilePlayer *AudioFileManager_GetParent(AudioFileManager *afm) |
| { |
| return afm->mParent; |
| } |
| |
| static int AudioFileManager_GetByteCounter(AudioFileManager *afm) |
| { |
| return afm->mByteCounter; |
| } |
| |
| |
| static OSStatus AudioFileManager_FileInputProc (void *inRefCon, |
| AudioUnitRenderActionFlags inActionFlags, |
| const AudioTimeStamp *inTimeStamp, |
| UInt32 inBusNumber, |
| AudioBuffer *ioData) |
| { |
| AudioFileManager* afm = (AudioFileManager*)inRefCon; |
| return afm->Render(afm, ioData); |
| } |
| |
| static OSStatus AudioFileManager_Render (AudioFileManager *afm, AudioBuffer *ioData) |
| { |
| OSStatus result = noErr; |
| |
| if (afm->mBufferOffset >= afm->mBufferSize) { |
| result = afm->GetFileData(afm, &afm->mTmpBuffer, &afm->mBufferSize); |
| if (result) { |
| SDL_SetError ("AudioConverterFillBuffer:%ld\n", result); |
| afm->mParent->DoNotification(afm->mParent, result); |
| return result; |
| } |
| |
| afm->mBufferOffset = 0; |
| } |
| |
| if (ioData->mDataByteSize > afm->mBufferSize - afm->mBufferOffset) |
| ioData->mDataByteSize = afm->mBufferSize - afm->mBufferOffset; |
| ioData->mData = (char *)afm->mTmpBuffer + afm->mBufferOffset; |
| afm->mBufferOffset += ioData->mDataByteSize; |
| |
| afm->mByteCounter += ioData->mDataByteSize; |
| afm->AfterRender(afm); |
| return result; |
| } |
| |
| |
| void delete_AudioFileManager (AudioFileManager *afm) |
| { |
| if (afm != NULL) { |
| if (afm->mFileBuffer) { |
| free(afm->mFileBuffer); |
| } |
| |
| SDL_free(afm); |
| } |
| } |
| |
| |
| AudioFileManager *new_AudioFileManager(AudioFilePlayer *inParent, |
| SInt16 inForkRefNum, |
| SInt64 inFileLength, |
| UInt32 inChunkSize) |
| { |
| AudioFileManager *afm; |
| |
| if (sReaderThread == NULL) |
| { |
| sReaderThread = new_FileReaderThread(); |
| if (sReaderThread == NULL) |
| return NULL; |
| } |
| |
| afm = (AudioFileManager *) SDL_malloc(sizeof (AudioFileManager)); |
| if (afm == NULL) |
| return NULL; |
| SDL_memset(afm, '\0', sizeof (*afm)); |
| |
| #define SET_AUDIOFILEMANAGER_METHOD(m) afm->m = AudioFileManager_##m |
| SET_AUDIOFILEMANAGER_METHOD(Disconnect); |
| SET_AUDIOFILEMANAGER_METHOD(DoConnect); |
| SET_AUDIOFILEMANAGER_METHOD(Read); |
| SET_AUDIOFILEMANAGER_METHOD(GetFileBuffer); |
| SET_AUDIOFILEMANAGER_METHOD(GetParent); |
| SET_AUDIOFILEMANAGER_METHOD(SetPosition); |
| SET_AUDIOFILEMANAGER_METHOD(GetByteCounter); |
| SET_AUDIOFILEMANAGER_METHOD(SetEndOfFile); |
| SET_AUDIOFILEMANAGER_METHOD(Render); |
| SET_AUDIOFILEMANAGER_METHOD(GetFileData); |
| SET_AUDIOFILEMANAGER_METHOD(AfterRender); |
| SET_AUDIOFILEMANAGER_METHOD(FileInputProc); |
| #undef SET_AUDIOFILEMANAGER_METHOD |
| |
| afm->mParent = inParent; |
| afm->mForkRefNum = inForkRefNum; |
| afm->mBufferSize = inChunkSize; |
| afm->mBufferOffset = inChunkSize; |
| afm->mChunkSize = inChunkSize; |
| afm->mFileLength = inFileLength; |
| afm->mFileBuffer = (char*) SDL_malloc(afm->mChunkSize * 2); |
| FSGetForkPosition(afm->mForkRefNum, &afm->mAudioDataOffset); |
| assert (afm->mFileBuffer != NULL); |
| return afm; |
| } |
| |