//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AudioFileReaderThread.cpp // // $Log: AudioFileReaderThread.cpp,v $ // Revision 1.10 2003/07/29 22:36:23 luke // [3326532] fixed // // Revision 1.9 2003/05/09 18:38:08 baron // check for IsLooping(). // // Revision 1.8 2003/04/18 17:37:43 baron // Make play file code work for aac. // // Revision 1.7 2003/04/09 22:33:10 baron // Pass audio data around by packets instead of bytes. // // Revision 1.6 2003/03/13 22:11:33 baron // Remove V1 code. Replace usage of AudioFile Read/WriteByte APIs with the Read/WritePacket versions. // // Revision 1.5 2002/07/18 19:02:16 luke // Updated to use new mach thread APIs. // // Revision 1.4 2002/07/14 21:18:06 bills // tweaks to setting thread policies // // Revision 1.3 2002/06/10 00:52:19 bills // remove warning // // Revision 1.2 2002/06/01 23:58:19 bills // first pass at v2 units // // 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.6 2002/05/17 07:48:05 bills // Add FilePlay "C" API // // Revision 1.5 2002/05/16 09:22:09 bills // add notifications for file underruns // // Revision 1.4 2002/05/16 09:07:47 bills // use a read thread for files (code is safe! for multiple read threads) // // Revision 1.3 2002/05/15 07:33:02 bills // add warnings // // 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 // // // file reading class that reads a file from disk //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #include "AudioFilePlayer.h" #include //used for setting policy of thread #include "CAGuard.h" #include #include #if DEBUG #define LOG_DATA_FLOW 0 #endif class FileReaderThread { public: FileReaderThread (); CAGuard& GetGuard() { return mGuard; } void AddReader(); void RemoveReader (const FileThreadVariables* inItem); // returns true if succeeded bool TryNextRead (FileThreadVariables* inItem) { bool didLock = false; bool succeeded = false; if (mGuard.Try (didLock)) { mFileData.push_back (inItem); mGuard.Notify(); succeeded = true; } if (didLock) mGuard.Unlock(); return succeeded; } private: typedef std::vector FileData; CAGuard mGuard; UInt32 mThreadPriority; bool mThreadShouldDie; int mNumReaders; FileData mFileData; void ReadNextChunk (); void StartFixedPriorityThread (); static UInt32 GetThreadBasePriority (pthread_t inThread); static void* DiskReaderEntry (void *inRefCon); }; FileReaderThread::FileReaderThread () : mGuard ("AudioFileReaderThread"), mThreadPriority (62), mNumReaders (0) { mFileData.reserve (48); } void FileReaderThread::AddReader() { if (mNumReaders == 0) { mThreadShouldDie = false; StartFixedPriorityThread (); } mNumReaders++; } void FileReaderThread::RemoveReader (const FileThreadVariables* inItem) { if (mNumReaders > 0) { CAGuard::Locker fileReadLock (mGuard); for (FileData::iterator iter = mFileData.begin(); iter != mFileData.end(); ++iter) { if ((*iter) == inItem) { mFileData.erase (iter); } } if (--mNumReaders == 0) { mThreadShouldDie = true; mGuard.Notify(); } } } void FileReaderThread::StartFixedPriorityThread () { pthread_attr_t theThreadAttrs; pthread_t pThread; OSStatus result = pthread_attr_init(&theThreadAttrs); THROW_RESULT("pthread_attr_init - Thread attributes could not be created.") result = pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED); THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.") result = pthread_create (&pThread, &theThreadAttrs, DiskReaderEntry, this); 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 = false; // set to true 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); 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 = mThreadPriority - FileReaderThread::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); THROW_RESULT("thread_policy - Couldn't set thread priority.") } 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; } void *FileReaderThread::DiskReaderEntry (void *inRefCon) { FileReaderThread *This = (FileReaderThread *)inRefCon; This->ReadNextChunk(); #if DEBUG printf ("finished with reading file\n"); #endif return 0; } void FileReaderThread::ReadNextChunk () { OSStatus result; UInt32 dataChunkSize; UInt32 dataChunkSizeInPackets; AudioStreamPacketDescription *packetDescriptions = NULL; FileThreadVariables* theItem = 0; for (;;) { { // this is a scoped based lock CAGuard::Locker fileReadLock (mGuard); if (mThreadShouldDie) return; while (mFileData.empty()) { fileReadLock.Wait(); } // kill thread if (mThreadShouldDie) return; theItem = mFileData[0]; mFileData.erase (mFileData.begin()); } packetDescriptions = theItem->mPacketDescriptions; if (!theItem->mWriteToFirstBuffer) packetDescriptions += theItem->mChunkSizeInPackets; if ((theItem->mPacketCount - theItem->mReadPacketPosition) < theItem->mChunkSizeInPackets) { dataChunkSizeInPackets = theItem->mPacketCount - theItem->mReadPacketPosition; if (!theItem->IsLooping()) { theItem->mFinishedReadingData = true; } } else dataChunkSizeInPackets = theItem->mChunkSizeInPackets; // this is the exit condition for the thread if (dataChunkSizeInPackets == 0 && !theItem->IsLooping()) { theItem->mFinishedReadingData = true; continue; } // construct pointer char* writePtr = const_cast(theItem->GetFileBuffer() + (theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSizeInPackets * theItem->mMaxPacketSize)); #if LOG_DATA_FLOW fprintf(stdout, "***** ReadNextChunk(1) - AFReadPackets (pkts/offset) = %ld/%qd\n", dataChunkSizeInPackets, theItem->mReadPacketPosition); #endif result = AudioFileReadPackets (theItem->GetFileID(), false, &dataChunkSize, packetDescriptions, theItem->mReadPacketPosition, &dataChunkSizeInPackets, writePtr); if (result) { theItem->GetParent().DoNotification(result); continue; } theItem->mCurrentPacketCountInBuffer = dataChunkSizeInPackets; theItem->mCurrentByteCountInBuffer = dataChunkSize; if (dataChunkSizeInPackets != theItem->mChunkSizeInPackets) { writePtr += dataChunkSize; packetDescriptions += dataChunkSizeInPackets; if (theItem->IsLooping()) { packetDescriptions = theItem->mPacketDescriptions + dataChunkSizeInPackets; dataChunkSizeInPackets = theItem->mChunkSizeInPackets - dataChunkSizeInPackets; theItem->mReadPacketPosition = 0; #if LOG_DATA_FLOW fprintf(stdout, "***** ReadNextChunk(2) - AFReadPackets (pkts/offset) = %ld/%qd\n", dataChunkSizeInPackets, theItem->mReadPacketPosition); #endif result = AudioFileReadPackets (theItem->GetFileID(), false, &dataChunkSize, packetDescriptions, theItem->mReadPacketPosition, &dataChunkSizeInPackets, writePtr); if (result) { theItem->GetParent().DoNotification(result); continue; } theItem->mCurrentPacketCountInBuffer += dataChunkSizeInPackets; theItem->mCurrentByteCountInBuffer += dataChunkSize; } else { // can't exit yet.. we still have to pass the partial buffer back memset (writePtr, 0, ((theItem->mChunkSizeInPackets - dataChunkSizeInPackets) * theItem->mMaxPacketSize)); } } theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer; // switch buffers theItem->mReadPacketPosition += dataChunkSizeInPackets; // increment count } } static FileReaderThread sReaderThread; AudioFileReaderThread::AudioFileReaderThread (AudioFilePlayer &inParent, AudioFileID &inFile, SInt64 inFileLength, SInt64 inPacketCount, UInt32 inMaxPacketSize, UInt32 inChunkSizeInPackets) : FileThreadVariables (inChunkSizeInPackets, inFileLength, inPacketCount, inMaxPacketSize, inParent, inFile), mLockUnsuccessful (false), mIsEngaged (false) { mFileBuffer = (char*) malloc (mChunkSizeInPackets * mMaxPacketSize * 2); mPacketDescriptions = (AudioStreamPacketDescription *) calloc (1, (mChunkSizeInPackets * sizeof(AudioStreamPacketDescription)) * 2); } void AudioFileReaderThread::DoConnect () { if (!mIsEngaged) { mFinishedReadingData = false; mNumTimesAskedSinceFinished = -1; mLockUnsuccessful = false; UInt32 dataChunkSize = 0; mCurrentPacketCountInBuffer = mChunkSizeInPackets; #if LOG_DATA_FLOW fprintf(stdout, "***** DoConnect - AFReadPackets from (pkts/offset) %ld/%qd\n", mCurrentPacketCountInBuffer, mReadPacketPosition); #endif OSStatus result = AudioFileReadPackets ( mAudioFileID, false, &dataChunkSize, mPacketDescriptions, mReadPacketPosition, &mCurrentPacketCountInBuffer, mFileBuffer); THROW_RESULT("AudioFileReadPackets") mCurrentByteCountInBuffer = dataChunkSize; mReadPacketPosition = mCurrentPacketCountInBuffer; mWriteToFirstBuffer = false; mReadFromFirstBuffer = true; sReaderThread.AddReader(); mIsEngaged = true; } else throw static_cast(-1); //thread has already been started } void AudioFileReaderThread::Disconnect () { if (mIsEngaged) { sReaderThread.RemoveReader (this); mIsEngaged = false; } } OSStatus AudioFileReaderThread::GetFileData (void** inOutData, UInt32 *inOutDataSize, UInt32 *outPacketCount, AudioStreamPacketDescription **outPacketDescriptions) { if (mFinishedReadingData) { ++mNumTimesAskedSinceFinished; *inOutDataSize = 0; *inOutData = 0; return noErr; } if (mReadFromFirstBuffer == mWriteToFirstBuffer) { #if DEBUG printf ("* * * * * * * Can't keep up with reading file:%ld\n", mParent.GetBusNumber()); #endif mParent.DoNotification (kAudioFilePlayErr_FilePlayUnderrun); *inOutDataSize = 0; *inOutData = 0; } else { *inOutDataSize = mCurrentByteCountInBuffer; *inOutData = mReadFromFirstBuffer ? mFileBuffer : (mFileBuffer + (mChunkSizeInPackets * mMaxPacketSize)); *outPacketCount = mCurrentPacketCountInBuffer; *outPacketDescriptions = mReadFromFirstBuffer ? mPacketDescriptions : (mPacketDescriptions + mChunkSizeInPackets); } mLockUnsuccessful = !sReaderThread.TryNextRead (this); mReadFromFirstBuffer = !mReadFromFirstBuffer; return noErr; } void AudioFileReaderThread::AfterRender () { if (mNumTimesAskedSinceFinished > 0) { bool didLock = false; if (sReaderThread.GetGuard().Try (didLock)) { mParent.DoNotification (kAudioFilePlay_FileIsFinished); if (didLock) sReaderThread.GetGuard().Unlock(); } } if (mLockUnsuccessful) mLockUnsuccessful = !sReaderThread.TryNextRead (this); }