/* 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 /* used for setting policy of thread */ #include "SDLOSXCAGuard.h" #include /*#include */ /*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 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(-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; } /* vi: set ts=4 sw=4 expandtab: */