From c58e5016a856b8277efaade1201c06a97183492a Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 15 Apr 2003 16:33:56 +0000 Subject: [PATCH] Added MacOS X CD-ROM audio support (thanks Max and Darrell) --- configure.in | 6 +- src/cdrom/Makefile.am | 14 +- src/cdrom/macosx/AudioFilePlayer.cpp | 377 +++++++++++ src/cdrom/macosx/AudioFilePlayer.h | 240 +++++++ src/cdrom/macosx/AudioFileReaderThread.cpp | 419 ++++++++++++ src/cdrom/macosx/CAGuard.cpp | 160 +++++ src/cdrom/macosx/CAGuard.h | 143 +++++ src/cdrom/macosx/CDPlayer.cpp | 715 +++++++++++++++++++++ src/cdrom/macosx/CDPlayer.h | 69 ++ src/cdrom/macosx/Makefile.am | 13 + src/cdrom/macosx/SDL_syscdrom_c.h | 132 ++++ test/testcdrom.c | 4 +- 12 files changed, 2287 insertions(+), 5 deletions(-) create mode 100644 src/cdrom/macosx/AudioFilePlayer.cpp create mode 100644 src/cdrom/macosx/AudioFilePlayer.h create mode 100644 src/cdrom/macosx/AudioFileReaderThread.cpp create mode 100644 src/cdrom/macosx/CAGuard.cpp create mode 100644 src/cdrom/macosx/CAGuard.h create mode 100644 src/cdrom/macosx/CDPlayer.cpp create mode 100644 src/cdrom/macosx/CDPlayer.h create mode 100644 src/cdrom/macosx/Makefile.am create mode 100644 src/cdrom/macosx/SDL_syscdrom_c.h diff --git a/configure.in b/configure.in index a14f8d5ce..40232c68f 100644 --- a/configure.in +++ b/configure.in @@ -2404,8 +2404,9 @@ case "$target" in fi # Set up files for the cdrom library if test x$enable_cdrom = xyes; then - CDROM_SUBDIRS="$CDROM_SUBDIRS dummy" - CDROM_DRIVERS="$CDROM_DRIVERS dummy/libcdrom_dummy.la" + CDROM_SUBDIRS="$CDROM_SUBDIRS macosx" + CDROM_DRIVERS="$CDROM_DRIVERS macosx/libcdrom_macosx.la" + SYSTEM_LIBS="$SYSTEM_LIBS -framework AudioToolbox -framework AudioUnit -lstdc++" fi # Set up files for the thread library if test x$enable_threads = xyes; then @@ -2672,6 +2673,7 @@ src/cdrom/bsdi/Makefile src/cdrom/freebsd/Makefile src/cdrom/linux/Makefile src/cdrom/macos/Makefile +src/cdrom/macosx/Makefile src/cdrom/openbsd/Makefile src/cdrom/qnx/Makefile src/cdrom/win32/Makefile diff --git a/src/cdrom/Makefile.am b/src/cdrom/Makefile.am index 29b81d2b5..5dbd7bc8e 100644 --- a/src/cdrom/Makefile.am +++ b/src/cdrom/Makefile.am @@ -5,7 +5,19 @@ noinst_LTLIBRARIES = libcdrom.la # Define which subdirectories need to be built SUBDIRS = @CDROM_SUBDIRS@ -DIST_SUBDIRS = aix beos bsdi dc dummy freebsd linux macos openbsd qnx win32 +DIST_SUBDIRS = \ + aix \ + beos \ + bsdi \ + dc \ + dummy \ + freebsd \ + linux \ + macos \ + macosx \ + openbsd \ + qnx \ + win32 DRIVERS = @CDROM_DRIVERS@ diff --git a/src/cdrom/macosx/AudioFilePlayer.cpp b/src/cdrom/macosx/AudioFilePlayer.cpp new file mode 100644 index 000000000..6d680f4e3 --- /dev/null +++ b/src/cdrom/macosx/AudioFilePlayer.cpp @@ -0,0 +1,377 @@ +/* + 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 +*/ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// AudioFilePlayer.cpp +// +#include "AudioFilePlayer.h" + +extern const char* AudioFilePlayerErrorStr (OSStatus error) +{ + const char *str; + + switch (error) { + case kAudioFileUnspecifiedError: str = "wht?"; break; + case kAudioFileUnsupportedFileTypeError: str = "typ?"; break; + case kAudioFileUnsupportedDataFormatError: str = "fmt?"; break; + case kAudioFileUnsupportedPropertyError: str = "pty?"; break; + case kAudioFileBadPropertySizeError: str = "!siz"; break; + case kAudioFileNotOptimizedError: str = "optm"; break; + case kAudioFilePermissionsError: str = "prm?"; break; + case kAudioFileFormatNameUnavailableError: str = "nme?"; break; + case kAudioFileInvalidChunkError: str = "chk?"; break; + case kAudioFileDoesNotAllow64BitDataSizeError: str = "off?"; break; + default: str = "error unspecified"; + } + + return str; +} + +void ThrowResult (OSStatus result, const char* str) +{ + SDL_SetError ("Error: %s %d (%s)", + str, result, AudioFilePlayerErrorStr(result)); + throw result; +} + +#if DEBUG +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 + +OSStatus AudioFileManager::FileInputProc (void *inRefCon, + AudioUnitRenderActionFlags inActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + AudioBuffer *ioData) +{ + AudioFileManager* THIS = (AudioFileManager*)inRefCon; + return THIS->Render(*ioData); +} + +OSStatus AudioFileManager::Render (AudioBuffer &ioData) +{ + OSStatus result = AudioConverterFillBuffer(mParentConverter, + AudioFileManager::ACInputProc, + this, + &ioData.mDataByteSize, + ioData.mData); + if (result) { + SDL_SetError ("AudioConverterFillBuffer:%ld\n", result); + mParent.DoNotification (result); + } else { + mByteCounter += ioData.mDataByteSize / 2; + AfterRender(); + } + return result; +} + +OSStatus AudioFileManager::ACInputProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData) +{ + AudioFileManager* THIS = (AudioFileManager*)inUserData; + return THIS->GetFileData(outData, outDataSize); +} + +AudioFileManager::~AudioFileManager () +{ + if (mFileBuffer) { + free (mFileBuffer); + mFileBuffer = 0; + } +} + +AudioFilePlayer::AudioFilePlayer (const FSRef *inFileRef) + : mConnected (false), + mAudioFileManager (0), + mConverter (0), + mNotifier (0), + mStartFrame (0) +{ + SInt64 fileDataSize = 0; + + OpenFile (inFileRef, fileDataSize); + + // we want about a seconds worth of data for the buffer + int secsBytes = UInt32 (mFileDescription.mSampleRate * mFileDescription.mBytesPerFrame); + +#if DEBUG + printf("File format:\n"); + PrintStreamDesc (&mFileDescription); +#endif + + //round to a 32K boundary + //if ((secsBytes & 0xFFFF8000) > (128 * 1024)) + //secsBytes &= 0xFFFF8000; + //else + //secsBytes = (secsBytes + 0x7FFF) & 0xFFFF8000; + + mAudioFileManager = new AudioFileReaderThread (*this, + mAudioFileID, + fileDataSize, + secsBytes); +} + +// 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 + printf("Destination format:\n"); + 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") + + // 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. + mIsAUNTUnit = (desc.componentType == kAudioUnitComponentType); + + if (!mIsAUNTUnit) { + result = badComponentInstance; + THROW_RESULT("BAD COMPONENT") + } + + + // HACK - the AIFF files on CDs are in little endian order! + if (mFileDescription.mFormatFlags == 0xE) + mFileDescription.mFormatFlags &= ~kAudioFormatFlagIsBigEndian; + + + result = AudioConverterNew (&mFileDescription, &destDesc, &mConverter); + THROW_RESULT("AudioConverterNew") + + +/* + // 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; + } +*/ + assert (mFileDescription.mChannelsPerFrame == 2); + +#if 0 + // this uses the better quality SRC + UInt32 srcID = kAudioUnitSRCAlgorithm_Polyphase; + result = AudioConverterSetProperty(mConverter, + kAudioConverterSampleRateConverterAlgorithm, + sizeof(srcID), + &srcID); + THROW_RESULT("AudioConverterSetProperty") +#endif +} + +void AudioFilePlayer::SetStartFrame (int frame) +{ + SInt64 position = frame * 2352; + + mStartFrame = frame; + mAudioFileManager->SetPosition (position); +} + + +int AudioFilePlayer::GetCurrentFrame () +{ + return mStartFrame + (mAudioFileManager->GetByteCounter() / 2352); +} + +void AudioFilePlayer::SetStopFrame (int frame) +{ + SInt64 position = frame * 2352; + + mAudioFileManager->SetEndOfFile (position); +} + +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 + if (mIsAUNTUnit) { + mInputCallback.inputProc = AudioFileManager::FileInputProc; + mInputCallback.inputProcRefCon = mAudioFileManager; + + OSStatus result = AudioUnitSetProperty (mPlayUnit, + kAudioUnitProperty_SetInputCallback, + kAudioUnitScope_Input, + mBusNumber, + &mInputCallback, + sizeof(mInputCallback)); + THROW_RESULT("AudioUnitSetProperty") + } + mConnected = true; + } +} + +// warning noted, now please go away ;-) +// #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 { + SDL_SetError ("Notification posted with no notifier in place"); + + 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; + + if (mIsAUNTUnit) { + mInputCallback.inputProc = 0; + mInputCallback.inputProcRefCon = 0; + OSStatus result = AudioUnitSetProperty (mPlayUnit, + kAudioUnitProperty_SetInputCallback, + kAudioUnitScope_Input, + mBusNumber, + &mInputCallback, + sizeof(mInputCallback)); + if (result) + SDL_SetError ("AudioUnitSetProperty:RemoveInputCallback:%ld", result); + + } + + mAudioFileManager->Disconnect(); + } +} + +void AudioFilePlayer::OpenFile (const FSRef *inRef, SInt64& outFileDataSize) +{ + 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") +} \ No newline at end of file diff --git a/src/cdrom/macosx/AudioFilePlayer.h b/src/cdrom/macosx/AudioFilePlayer.h new file mode 100644 index 000000000..d7a10be96 --- /dev/null +++ b/src/cdrom/macosx/AudioFilePlayer.h @@ -0,0 +1,240 @@ +/* + 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 +*/ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// AudioFilePlayer.h +// +#ifndef __AudioFilePlayer_H__ +#define __AudioFilePlayer_H__ + +#include + +#include +#include + +#include "SDL_Error.h" + +const char* AudioFilePlayerErrorStr (OSStatus error); + +void ThrowResult (OSStatus result, const char *str); + +#define THROW_RESULT(str) \ + if (result) { \ + ThrowResult (result, str); \ + } + +typedef void (*AudioFilePlayNotifier)(void *inRefCon, + OSStatus inStatus); + +enum { + kAudioFilePlayErr_FilePlayUnderrun = -10000, + kAudioFilePlay_FileIsFinished = -10001, + kAudioFilePlay_PlayerIsUninitialized = -10002 +}; + + +class AudioFileManager; + +#pragma mark __________ AudioFilePlayer +class AudioFilePlayer +{ +public: + AudioFilePlayer (const FSRef *inFileRef); + + ~AudioFilePlayer(); + + void SetDestination (AudioUnit &inDestUnit, + int inBusNumber); + + void SetNotifier (AudioFilePlayNotifier inNotifier, void *inRefCon) + { + mNotifier = inNotifier; + mRefCon = inRefCon; + } + + void SetStartFrame (int frame); // seek in the file + + int GetCurrentFrame (); // get the current frame position + + void SetStopFrame (int frame); // set limit in the file + + void Connect(); + + void Disconnect(); + + void DoNotification (OSStatus inError) const; + + bool IsConnected () const { return mConnected; } + + UInt32 GetBusNumber () const { return mBusNumber; } + + AudioUnit GetDestUnit () const { return mPlayUnit; } + + AudioConverterRef GetAudioConverter() const { return mConverter; } + +#if DEBUG + void Print() const + { + CAShow (mAudioFileID); + printf ("Destination Bus:%ld\n", GetBusNumber()); + printf ("Is 'aunt' unit:%s\n", (mIsAUNTUnit ? "true" : "false")); + printf ("Is Connected:%s\n", (IsConnected() ? "true" : "false")); + if (mConverter) CAShow (mConverter); + printf ("- - - - - - - - - - - - - - \n"); + } +#endif + + const AudioStreamBasicDescription& GetFileFormat() const { return mFileDescription; } + +private: + AudioUnit mPlayUnit; + UInt32 mBusNumber; + AudioFileID mAudioFileID; + + AudioUnitInputCallback mInputCallback; + + AudioStreamBasicDescription mFileDescription; + + bool mConnected; + bool mIsAUNTUnit; + + AudioFileManager* mAudioFileManager; + AudioConverterRef mConverter; + + AudioFilePlayNotifier mNotifier; + void* mRefCon; + + int mStartFrame; + +#pragma mark __________ Private_Methods + + void OpenFile (const FSRef *inRef, SInt64& outFileSize); +}; + +#pragma mark __________ AudioFileManager +class AudioFileManager +{ +public: + AudioFileManager (AudioFilePlayer& inParent, AudioFileID inFile) + : mParent (inParent), + mAudioFileID (inFile), + mFileBuffer (0), + mByteCounter (0) + {} + + virtual ~AudioFileManager(); + + + void Connect (AudioConverterRef inConverter) + { + mParentConverter = inConverter; + DoConnect(); + } + + // this method should NOT be called by an object of this class + // as it is called by the parent's Disconnect() method + virtual void Disconnect () {} + + const AudioFileID& GetFileID() const { return mAudioFileID; } + + const char* GetFileBuffer () { return mFileBuffer; } + + const AudioFilePlayer& GetParent () const { return mParent; } + + virtual void SetPosition (SInt64 pos) = 0; // seek/rewind in the file + + virtual int GetByteCounter () { return mByteCounter; } // return actual bytes streamed to audio hardware + + virtual void SetEndOfFile (SInt64 pos) = 0; // set the "EOF" (will behave just like it reached eof) + +protected: + AudioFilePlayer& mParent; + AudioConverterRef mParentConverter; + const AudioFileID mAudioFileID; + + char* mFileBuffer; + + OSStatus Render (AudioBuffer &ioData); + + int mByteCounter; + + virtual OSStatus GetFileData (void** inOutData, UInt32 *inOutDataSize) = 0; + + virtual void DoConnect () = 0; + + virtual void AfterRender () = 0; + +public: + static OSStatus FileInputProc (void *inRefCon, + AudioUnitRenderActionFlags inActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + AudioBuffer *ioData); + static OSStatus ACInputProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData); +}; + + +#pragma mark __________ AudioFileReaderThread +class AudioFileReaderThread + : public AudioFileManager +{ +public: + const UInt32 mChunkSize; + SInt64 mFileLength; + SInt64 mReadFilePosition; + bool mWriteToFirstBuffer; + bool mFinishedReadingData; + + AudioFileReaderThread (AudioFilePlayer &inParent, + AudioFileID &inFile, + SInt64 inFileLength, + UInt32 inChunkSize); + + virtual void Disconnect (); + + virtual void SetPosition (SInt64 pos); // seek/rewind in the file + + virtual void SetEndOfFile (SInt64 pos); // set the "EOF" (will behave just like it reached eof) + +protected: + virtual void DoConnect (); + + virtual OSStatus GetFileData (void** inOutData, UInt32 *inOutDataSize); + + virtual void AfterRender (); + +private: + bool mReadFromFirstBuffer; + bool mLockUnsuccessful; + bool mIsEngaged; + + int mNumTimesAskedSinceFinished; +}; + + +#endif diff --git a/src/cdrom/macosx/AudioFileReaderThread.cpp b/src/cdrom/macosx/AudioFileReaderThread.cpp new file mode 100644 index 000000000..1551a4601 --- /dev/null +++ b/src/cdrom/macosx/AudioFileReaderThread.cpp @@ -0,0 +1,419 @@ +/* + 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 +*/ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// AudioFileReaderThread.cpp +// +#include "AudioFilePlayer.h" +#include //used for setting policy of thread +#include "CAGuard.h" +#include + +#include + +class FileReaderThread { +public: + FileReaderThread (); + + CAGuard& GetGuard() { return mGuard; } + + void AddReader(); + + void RemoveReader (const AudioFileReaderThread* inItem); + + // returns true if succeeded + bool TryNextRead (AudioFileReaderThread* 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; + } + + int mThreadShouldDie; + +private: + typedef std::list FileData; + + CAGuard mGuard; + UInt32 mThreadPriority; + + int mNumReaders; + FileData mFileData; + + + void ReadNextChunk (); + + void StartFixedPriorityThread (); + static UInt32 GetThreadBasePriority (pthread_t inThread); + + static void* DiskReaderEntry (void *inRefCon); +}; + +FileReaderThread::FileReaderThread () + : mThreadPriority (62), + mNumReaders (0) +{ +} + +void FileReaderThread::AddReader() +{ + if (mNumReaders == 0) + { + mThreadShouldDie = false; + + StartFixedPriorityThread (); + } + mNumReaders++; +} + +void FileReaderThread::RemoveReader (const AudioFileReaderThread* 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(); // wake up thread so it will quit + mGuard.Wait(); // wait for thread to die + } + } +} + +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; + AudioFileReaderThread* theItem = 0; + + for (;;) + { + { // this is a scoped based lock + CAGuard::Locker fileReadLock (mGuard); + + if (this->mThreadShouldDie) { + + mGuard.Notify(); + return; + } + + if (mFileData.empty()) + { + mGuard.Wait(); + } + + // kill thread + if (this->mThreadShouldDie) { + + mGuard.Notify(); + return; + } + + theItem = mFileData.front(); + mFileData.pop_front(); + } + + 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 = true; + continue; + } + // construct pointer + char* writePtr = const_cast(theItem->GetFileBuffer() + + (theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSize)); + +/* + printf ("AudioFileReadBytes: theItem=%.8X fileID=%.8X pos=%.8X sz=%.8X flen=%.8X ptr=%.8X\n", + (unsigned int)theItem, (unsigned int)theItem->GetFileID(), + (unsigned int)theItem->mReadFilePosition, (unsigned int)dataChunkSize, + (unsigned int)theItem->mFileLength, (unsigned int)writePtr); +*/ + result = AudioFileReadBytes (theItem->GetFileID(), + false, + theItem->mReadFilePosition, + &dataChunkSize, + writePtr); + if (result) { + theItem->GetParent().DoNotification(result); + continue; + } + + if (dataChunkSize != theItem->mChunkSize) + { + writePtr += dataChunkSize; + + // can't exit yet.. we still have to pass the partial buffer back + memset (writePtr, 0, (theItem->mChunkSize - dataChunkSize)); + } + + theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer; // switch buffers + + theItem->mReadFilePosition += dataChunkSize; // increment count + } +} + + +static FileReaderThread sReaderThread; + +AudioFileReaderThread::AudioFileReaderThread (AudioFilePlayer &inParent, + AudioFileID &inFile, + SInt64 inFileLength, + UInt32 inChunkSize) + : AudioFileManager (inParent, inFile), + mChunkSize (inChunkSize), + mFileLength (inFileLength), + mReadFilePosition (0), + mWriteToFirstBuffer (false), + mFinishedReadingData (false), + + mLockUnsuccessful (false), + mIsEngaged (false) +{ + mFileBuffer = (char*) malloc (mChunkSize * 2); + assert (mFileBuffer != NULL); +} + +void AudioFileReaderThread::DoConnect () +{ + if (!mIsEngaged) + { + //mReadFilePosition = 0; + mFinishedReadingData = false; + + mNumTimesAskedSinceFinished = -1; + mLockUnsuccessful = false; + + UInt32 dataChunkSize; + + if ((mFileLength - mReadFilePosition) < mChunkSize) + dataChunkSize = mFileLength - mReadFilePosition; + else + dataChunkSize = mChunkSize; + + OSStatus result = AudioFileReadBytes ( mAudioFileID, + false, + mReadFilePosition, + &dataChunkSize, + mFileBuffer); + THROW_RESULT("AudioFileReadBytes") + + mReadFilePosition += dataChunkSize; + + 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) +{ + 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 = mChunkSize; + *inOutData = mReadFromFirstBuffer ? mFileBuffer : (mFileBuffer + mChunkSize); + } + + 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); +} + +void AudioFileReaderThread::SetPosition (SInt64 pos) +{ + if (pos < 0 || pos >= mFileLength) { + SDL_SetError ("AudioFileReaderThread::SetPosition - position invalid: %d filelen=%d\n", + (unsigned int)pos, (unsigned int)mFileLength); + pos = 0; + } + + mReadFilePosition = pos; +} + +void AudioFileReaderThread::SetEndOfFile (SInt64 pos) +{ + if (pos <= 0 || pos > mFileLength) { + SDL_SetError ("AudioFileReaderThread::SetEndOfFile - position beyond actual eof\n"); + pos = mFileLength; + } + + mFileLength = pos; +} \ No newline at end of file diff --git a/src/cdrom/macosx/CAGuard.cpp b/src/cdrom/macosx/CAGuard.cpp new file mode 100644 index 000000000..05aac9f2c --- /dev/null +++ b/src/cdrom/macosx/CAGuard.cpp @@ -0,0 +1,160 @@ +/* + 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 +*/ +/* + Note: This file hasn't been modified so technically we have to keep the disclaimer :-( + + Copyright: © Copyright 2002 Apple Computer, Inc. All rights reserved. + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under AppleΥs + copyrights in this original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks or logos of + Apple Computer, Inc. may be used to endorse or promote products derived from the + Apple Software without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any patent rights that + may be infringed by your derivative works or by other works in which the Apple + Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT + (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/*============================================================================= + CAGuard.cp + +=============================================================================*/ + +//============================================================================= +// Includes +//============================================================================= + +#include + +#define NDEBUG 1 +#include + + +#include "CAGuard.h" + +//#warning Need a try-based Locker too +//============================================================================= +// CAGuard +//============================================================================= + +CAGuard::CAGuard() +{ + OSStatus theError = pthread_mutex_init(&mMutex, NULL); + assert(theError == 0); + + theError = pthread_cond_init(&mCondVar, NULL); + assert(theError == 0); + + mOwner = 0; +} + +CAGuard::~CAGuard() +{ + pthread_mutex_destroy(&mMutex); + pthread_cond_destroy(&mCondVar); +} + +bool CAGuard::Lock() +{ + bool theAnswer = false; + + if(pthread_self() != mOwner) + { + OSStatus theError = pthread_mutex_lock(&mMutex); + assert(theError == 0); + mOwner = pthread_self(); + theAnswer = true; + } + + return theAnswer; +} + +void CAGuard::Unlock() +{ + assert(pthread_self() == mOwner); + + mOwner = 0; + OSStatus theError = pthread_mutex_unlock(&mMutex); + assert(theError == 0); +} + +bool CAGuard::Try (bool& outWasLocked) +{ + bool theAnswer = false; + outWasLocked = false; + + if (pthread_self() == mOwner) { + theAnswer = true; + outWasLocked = false; + } else { + OSStatus theError = pthread_mutex_trylock(&mMutex); + if (theError == 0) { + mOwner = pthread_self(); + theAnswer = true; + outWasLocked = true; + } + } + + return theAnswer; +} + +void CAGuard::Wait() +{ + assert(pthread_self() == mOwner); + + mOwner = 0; + + OSStatus theError = pthread_cond_wait(&mCondVar, &mMutex); + assert(theError == 0); + mOwner = pthread_self(); +} + +void CAGuard::Notify() +{ + OSStatus theError = pthread_cond_signal(&mCondVar); + assert(theError == 0); +} diff --git a/src/cdrom/macosx/CAGuard.h b/src/cdrom/macosx/CAGuard.h new file mode 100644 index 000000000..8f44e0a99 --- /dev/null +++ b/src/cdrom/macosx/CAGuard.h @@ -0,0 +1,143 @@ +/* + 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 +*/ +/* + Note: This file hasn't been modified so technically we have to keep the disclaimer :-( + + + Copyright: © Copyright 2002 Apple Computer, Inc. All rights reserved. + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under AppleΥs + copyrights in this original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks or logos of + Apple Computer, Inc. may be used to endorse or promote products derived from the + Apple Software without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any patent rights that + may be infringed by your derivative works or by other works in which the Apple + Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT + (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/*============================================================================= + CAGuard.h + +=============================================================================*/ +#if !defined(__CAGuard_h__) +#define __CAGuard_h__ + +//============================================================================= +// Includes +//============================================================================= + +#include +#include + + +//============================================================================= +// CAGuard +// +// This is your typical mutex with signalling implemented via pthreads. +// Lock() will return true if and only if the guard is locked on that call. +// A thread that already has the guard will receive 'false' if it locks it +// again. Use of the stack-based CAGuard::Locker class is highly recommended +// to properly manage the recursive nesting. The Wait calls with timeouts +// will return true if and only if the timeout period expired. They will +// return false if they receive notification any other way. +//============================================================================= + +class CAGuard +{ + +// Construction/Destruction +public: + CAGuard(); + virtual ~CAGuard(); + +// Actions +public: + virtual bool Lock(); + virtual void Unlock(); + virtual bool Try(bool& outWasLocked); // returns true if lock is free, false if not + + virtual void Wait(); + + virtual void Notify(); + +// Implementation +protected: + pthread_mutex_t mMutex; + pthread_cond_t mCondVar; + pthread_t mOwner; + +// Helper class to manage taking and releasing recursively +public: + class Locker + { + + // Construction/Destruction + public: + Locker(CAGuard& inGuard) : mGuard(inGuard), mNeedsRelease(false) { mNeedsRelease = mGuard.Lock(); } + ~Locker() { if(mNeedsRelease) { mGuard.Unlock(); } } + + private: + Locker(const Locker&); + Locker& operator=(const Locker&); + + // Actions + public: + void Wait() { mGuard.Wait(); } + + void Notify() { mGuard.Notify(); } + + // Implementation + private: + CAGuard& mGuard; + bool mNeedsRelease; + + }; + +}; + +#endif diff --git a/src/cdrom/macosx/CDPlayer.cpp b/src/cdrom/macosx/CDPlayer.cpp new file mode 100644 index 000000000..1bc989ba1 --- /dev/null +++ b/src/cdrom/macosx/CDPlayer.cpp @@ -0,0 +1,715 @@ +/* + 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 +*/ + +#include "CDPlayer.h" +#include "AudioFilePlayer.h" +#include "CAGuard.h" + +// we're exporting these functions into C land for SDL_syscdrom.c +extern "C" { + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// Constants +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +#define kAudioCDFilesystemID (UInt16)(('J' << 8) | 'H') // 'JH'; this avoids compiler warning + +// XML PList keys +#define kRawTOCDataString "Format 0x02 TOC Data" +#define kSessionsString "Sessions" +#define kSessionTypeString "Session Type" +#define kTrackArrayString "Track Array" +#define kFirstTrackInSessionString "First Track" +#define kLastTrackInSessionString "Last Track" +#define kLeadoutBlockString "Leadout Block" +#define kDataKeyString "Data" +#define kPointKeyString "Point" +#define kSessionNumberKeyString "Session Number" +#define kStartBlockKeyString "Start Block" + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// Globals +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +#pragma mark -- Globals -- + +static bool playBackWasInit = false; +static AudioUnit theUnit; +static AudioFilePlayer* thePlayer = NULL; +static CDPlayerCompletionProc completionProc = NULL; +static pthread_mutex_t apiMutex; +static pthread_t callbackThread; +static pthread_mutex_t callbackMutex; +static volatile int runCallBackThread; +static int initMutex = SDL_TRUE; +static SDL_CD* theCDROM; + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// Prototypes +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +#pragma mark -- Prototypes -- + +OSStatus CheckInit (); + +OSStatus MatchAUFormats (AudioUnit theUnit, UInt32 theInputBus); + +void FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus); + +void* RunCallBackThread (void* inRefCon); + + +#pragma mark -- Public Functions -- + +void Lock () +{ + if (initMutex) { + + pthread_mutexattr_t attr; + + pthread_mutexattr_init (&attr); + pthread_mutex_init (&apiMutex, &attr); + pthread_mutexattr_destroy (&attr); + + initMutex = SDL_FALSE; + } + + pthread_mutex_lock (&apiMutex); +} + +void Unlock () +{ + pthread_mutex_unlock (&apiMutex); +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// DetectAudioCDVolumes +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes) +{ + int volumeIndex; + int cdVolumeCount = 0; + OSStatus result = noErr; + + for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++) + { + FSVolumeRefNum actualVolume; + HFSUniStr255 volumeName; + FSVolumeInfo volumeInfo; + FSRef rootDirectory; + + memset (&volumeInfo, 0, sizeof(volumeInfo)); + + result = FSGetVolumeInfo (kFSInvalidVolumeRefNum, + volumeIndex, + &actualVolume, + kFSVolInfoFSInfo, + &volumeInfo, + &volumeName, + &rootDirectory); + + if (result == noErr) + { + if (volumeInfo.filesystemID == kAudioCDFilesystemID) // It's an audio CD + { + if (volumes != NULL && cdVolumeCount < numVolumes) + volumes[cdVolumeCount] = actualVolume; + + cdVolumeCount++; + } + } + else + { + // I'm commenting this out because it seems to be harmless + //SDL_SetError ("DetectAudioCDVolumes: FSGetVolumeInfo returned %d", result); + } + } + + return cdVolumeCount; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// ReadTOCData +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD) +{ + HFSUniStr255 dataForkName; + OSStatus theErr; + SInt16 forkRefNum; + SInt64 forkSize; + Ptr forkData = 0; + ByteCount actualRead; + CFDataRef dataRef = 0; + CFPropertyListRef propertyListRef = 0; + + FSRefParam fsRefPB; + FSRef tocPlistFSRef; + + const char* error = "Unspecified Error"; + + // get stuff from .TOC.plist + fsRefPB.ioCompletion = NULL; + fsRefPB.ioNamePtr = "\p.TOC.plist"; + fsRefPB.ioVRefNum = theVolume; + fsRefPB.ioDirID = 0; + fsRefPB.newRef = &tocPlistFSRef; + + theErr = PBMakeFSRefSync (&fsRefPB); + if(theErr != noErr) { + error = "PBMakeFSRefSync"; + goto bail; + } + + // Load and parse the TOC XML data + + theErr = FSGetDataForkName (&dataForkName); + if (theErr != noErr) { + error = "FSGetDataForkName"; + goto bail; + } + + theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum); + if (theErr != noErr) { + error = "FSOpenFork"; + goto bail; + } + + theErr = FSGetForkSize (forkRefNum, &forkSize); + if (theErr != noErr) { + error = "FSGetForkSize"; + goto bail; + } + + // Allocate some memory for the XML data + forkData = NewPtr (forkSize); + if(forkData == NULL) { + error = "NewPtr"; + goto bail; + } + + theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead); + if(theErr != noErr) { + error = "FSReadFork"; + goto bail; + } + + dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize); + if(dataRef == 0) { + error = "CFDataCreate"; + goto bail; + } + + propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault, + dataRef, + kCFPropertyListImmutable, + NULL); + if (propertyListRef == NULL) { + error = "CFPropertyListCreateFromXMLData"; + goto bail; + } + + // Now we got the Property List in memory. Parse it. + + // First, make sure the root item is a CFDictionary. If not, release and bail. + if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID()) + { + CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef; + + CFDataRef theRawTOCDataRef; + CFArrayRef theSessionArrayRef; + CFIndex numSessions; + CFIndex index; + + // This is how we get the Raw TOC Data + theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString)); + + // Get the session array info. + theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString)); + + // Find out how many sessions there are. + numSessions = CFArrayGetCount (theSessionArrayRef); + + // Initialize the total number of tracks to 0 + theCD->numtracks = 0; + + // Iterate over all sessions, collecting the track data + for(index = 0; index < numSessions; index++) + { + CFDictionaryRef theSessionDict; + CFNumberRef leadoutBlock; + CFArrayRef trackArray; + CFIndex numTracks; + CFIndex trackIndex; + UInt32 value = 0; + + theSessionDict = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index); + leadoutBlock = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString)); + + trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString)); + + numTracks = CFArrayGetCount (trackArray); + + for(trackIndex = 0; trackIndex < numTracks; trackIndex++) { + + CFDictionaryRef theTrackDict; + CFNumberRef trackNumber; + CFNumberRef sessionNumber; + CFNumberRef startBlock; + CFBooleanRef isDataTrack; + UInt32 value; + + theTrackDict = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex); + + trackNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kPointKeyString)); + sessionNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kSessionNumberKeyString)); + startBlock = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kStartBlockKeyString)); + isDataTrack = (CFBooleanRef) CFDictionaryGetValue (theTrackDict, CFSTR(kDataKeyString)); + + // Fill in the SDL_CD struct + int idx = theCD->numtracks++; + + CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value); + theCD->track[idx].id = value; + + CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value); + theCD->track[idx].offset = value; + + theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK; + + // Since the track lengths are not stored in .TOC.plist we compute them. + if (trackIndex > 0) { + theCD->track[idx-1].length = theCD->track[idx].offset - theCD->track[idx-1].offset; + } + } + + // Compute the length of the last track + CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value); + + theCD->track[theCD->numtracks-1].length = + value - theCD->track[theCD->numtracks-1].offset; + + // Set offset to leadout track + theCD->track[theCD->numtracks].offset = value; + } + + } + + theErr = 0; + goto cleanup; +bail: + SDL_SetError ("ReadTOCData: %s returned %d", error, theErr); + theErr = -1; +cleanup: + + if (propertyListRef != NULL) + CFRelease(propertyListRef); + if (dataRef != NULL) + CFRelease(dataRef); + if (forkData != NULL) + DisposePtr(forkData); + + FSCloseFork (forkRefNum); + + return theErr; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// ListTrackFiles +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks) +{ + OSStatus result = -1; + FSIterator iterator; + ItemCount actualObjects; + FSRef rootDirectory; + FSRef ref; + HFSUniStr255 nameStr; + + result = FSGetVolumeInfo (theVolume, + 0, + NULL, + kFSVolInfoFSInfo, + NULL, + NULL, + &rootDirectory); + + if (result != noErr) { + SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result); + goto bail; + } + + result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator); + if (result == noErr) { + do + { + result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects, + NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr); + if (result == noErr) { + + CFStringRef name; + name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length); + + // Look for .aiff extension + if (CFStringHasSuffix (name, CFSTR(".aiff"))) { + + // Extract the track id from the filename + int trackID = 0, i = 0; + while (nameStr.unicode[i] >= '0' && nameStr.unicode[i] <= '9') { + trackID = 10 * trackID +(nameStr.unicode[i] - '0'); + i++; + } + + #if DEBUG_CDROM + printf("Found AIFF for track %d: '%s'\n", trackID, + CFStringGetCStringPtr (name, CFStringGetSystemEncoding())); + #endif + + // Track ID's start at 1, but we want to start at 0 + trackID--; + + assert(0 <= trackID && trackID <= SDL_MAX_TRACKS); + + if (trackID < numTracks) + memcpy (&trackFiles[trackID], &ref, sizeof(FSRef)); + } + CFRelease (name); + } + } while(noErr == result); + FSCloseIterator (iterator); + } + + result = 0; + bail: + return result; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// LoadFile +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int LoadFile (const FSRef *ref, int startFrame, int stopFrame) +{ + int error = -1; + + if (CheckInit () < 0) + goto bail; + + // release any currently playing file + if (ReleaseFile () < 0) + goto bail; + + #if DEBUG_CDROM + printf ("LoadFile: %d %d\n", startFrame, stopFrame); + #endif + + try { + + // create a new player, and attach to the audio unit + + thePlayer = new AudioFilePlayer(ref); + if (thePlayer == NULL) { + SDL_SetError ("LoadFile: Could not create player"); + throw (-3); + } + + thePlayer->SetDestination(theUnit, 0); + + if (startFrame >= 0) + thePlayer->SetStartFrame (startFrame); + + if (stopFrame >= 0 && stopFrame > startFrame) + thePlayer->SetStopFrame (stopFrame); + + // we set the notifier later + //thePlayer->SetNotifier(FilePlayNotificationHandler, NULL); + + thePlayer->Connect(); + + #if DEBUG_CDROM + thePlayer->Print(); + fflush (stdout); + #endif + } + catch (...) + { + goto bail; + } + + error = 0; + + bail: + return error; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// ReleaseFile +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int ReleaseFile () +{ + int error = -1; + + try { + if (thePlayer != NULL) { + + thePlayer->Disconnect(); + + delete thePlayer; + + thePlayer = NULL; + } + } + catch (...) + { + goto bail; + } + + error = 0; + + bail: + return error; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// PlayFile +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int PlayFile () +{ + OSStatus result = -1; + + if (CheckInit () < 0) + goto bail; + + try { + + // start processing of the audio unit + result = AudioOutputUnitStart (theUnit); + THROW_RESULT("PlayFile: AudioOutputUnitStart") + + } + catch (...) + { + goto bail; + } + + result = 0; + +bail: + return result; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// PauseFile +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +int PauseFile () +{ + OSStatus result = -1; + + if (CheckInit () < 0) + goto bail; + + try { + + // stop processing the audio unit + result = AudioOutputUnitStop (theUnit); + THROW_RESULT("PauseFile: AudioOutputUnitStop") + } + catch (...) + { + goto bail; + } + + result = 0; +bail: + return result; +} + +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ +// SetCompletionProc +//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ + +void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom) +{ + assert(thePlayer != NULL); + + theCDROM = cdrom; + completionProc = proc; + thePlayer->SetNotifier (FilePlayNotificationHandler, cdrom); +} + + +int GetCurrentFrame () +{ + int frame; + + if (thePlayer == NULL) + frame = 0; + else + frame = thePlayer->GetCurrentFrame (); + + return frame; +} + + +#pragma mark -- Private Functions -- + +OSStatus CheckInit () +{ + if (playBackWasInit) + return 0; + + OSStatus result = noErr; + + + // Create the callback mutex + pthread_mutexattr_t attr; + pthread_mutexattr_init (&attr); + pthread_mutex_init (&callbackMutex, &attr); + pthread_mutexattr_destroy (&attr); + pthread_mutex_lock (&callbackMutex); + + // Start callback thread + pthread_attr_t attr1; + pthread_attr_init (&attr1); + pthread_create (&callbackThread, &attr1, RunCallBackThread, NULL); + pthread_attr_destroy (&attr1); + + try { + ComponentDescription desc; + + desc.componentType = kAudioUnitComponentType; + desc.componentSubType = kAudioUnitSubType_Output; + desc.componentManufacturer = kAudioUnitID_DefaultOutput; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent (NULL, &desc); + if (comp == NULL) { + SDL_SetError ("CheckInit: FindNextComponent returned NULL"); + throw(internalComponentErr); + } + + result = OpenAComponent (comp, &theUnit); + THROW_RESULT("CheckInit: OpenAComponent") + + // you need to initialize the output unit before you set it as a destination + result = AudioUnitInitialize (theUnit); + THROW_RESULT("CheckInit: AudioUnitInitialize") + + + // In this case we first want to get the output format of the OutputUnit + // Then we set that as the input format. Why? + // So that only a single conversion process is done + // when SetDestination is called it will get the input format of the + // unit its supplying data to. This defaults to 44.1K, stereo, so if + // the device is not that, then we lose a possibly rendering of data + + result = MatchAUFormats (theUnit, 0); + THROW_RESULT("CheckInit: MatchAUFormats") + + playBackWasInit = true; + } + catch (...) + { + return -1; + } + + return 0; +} + + +OSStatus MatchAUFormats (AudioUnit theUnit, UInt32 theInputBus) +{ + AudioStreamBasicDescription theDesc; + UInt32 size = sizeof (theDesc); + OSStatus result = AudioUnitGetProperty (theUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 0, + &theDesc, + &size); + THROW_RESULT("MatchAUFormats: AudioUnitGetProperty") + + result = AudioUnitSetProperty (theUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + theInputBus, + &theDesc, + size); + + return result; +} + +void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus) +{ + if (inStatus == kAudioFilePlay_FileIsFinished) { + + // notify non-CA thread to perform the callback + pthread_mutex_unlock (&callbackMutex); + + } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) { + + SDL_SetError ("CDPlayer Notification: buffer underrun"); + } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) { + + SDL_SetError ("CDPlayer Notification: player is uninitialized"); + } else { + + SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus); + } +} + +void* RunCallBackThread (void *param) +{ + runCallBackThread = 1; + + while (runCallBackThread) { + + pthread_mutex_lock (&callbackMutex); + + if (completionProc && theCDROM) { + #if DEBUG_CDROM + printf ("callback!\n"); + #endif + (*completionProc)(theCDROM); + } else { + #if DEBUG_CDROM + printf ("callback?\n"); + #endif + } + } + + runCallBackThread = -1; + + #if DEBUG_CDROM + printf ("thread dying now...\n"); + #endif + + return NULL; +} + +}; // extern "C" diff --git a/src/cdrom/macosx/CDPlayer.h b/src/cdrom/macosx/CDPlayer.h new file mode 100644 index 000000000..7bbd53071 --- /dev/null +++ b/src/cdrom/macosx/CDPlayer.h @@ -0,0 +1,69 @@ +/* + 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 +*/ + +#ifndef __CDPlayer__H__ +#define __CDPlayer__H__ 1 + +#include +#include + +#include +#include + +#include + +#include +#include // for usleep + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*CDPlayerCompletionProc)(SDL_CD *cdrom) ; + +void Lock (); + +void Unlock(); + +int LoadFile (const FSRef *ref, int startFrame, int endFrame); // pass -1 to do nothing + +int ReleaseFile (); + +int PlayFile (); + +int PauseFile (); + +void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom); + +int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD); + +int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks); + +int DetectAudioCDVolumes (FSVolumeRefNum *volumes, int numVolumes); + +int GetCurrentFrame (); + +#ifdef __cplusplus +}; +#endif + +#endif /* __CD_Player__H__ */ diff --git a/src/cdrom/macosx/Makefile.am b/src/cdrom/macosx/Makefile.am new file mode 100644 index 000000000..b9b623eb4 --- /dev/null +++ b/src/cdrom/macosx/Makefile.am @@ -0,0 +1,13 @@ + +## Makefile.am for the Mac OS X cdrom driver for SDL + +noinst_LTLIBRARIES = libcdrom_macosx.la +libcdrom_macosx_la_SOURCES = $(SRCS) + +# The SDL cdrom driver sources +SRCS = \ + SDL_syscdrom.c \ + AudioFilePlayer.cpp \ + AudioFileReaderThread.cpp \ + CAGuard.cpp \ + CDPlayer.cpp diff --git a/src/cdrom/macosx/SDL_syscdrom_c.h b/src/cdrom/macosx/SDL_syscdrom_c.h new file mode 100644 index 000000000..d4474dcc4 --- /dev/null +++ b/src/cdrom/macosx/SDL_syscdrom_c.h @@ -0,0 +1,132 @@ +/* + 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 +*/ + + +/*********************************************************************************** + Implementation Notes + ********************* + + This code has several limitations currently (all of which are proabaly fixable): + + 1. A CD-ROM device is inferred from a mounted cdfs volume, so device 0 is + not necessarily the first CD-ROM device on the system. (Somewhat easy to fix + by useing the device name from the volume id's to reorder the volumes) + + 2. You can only open and control 1 CD-ROM device at a time. (Challenging to fix, + due to extensive code restructuring) + + 3. The status reported by SDL_CDStatus only changes to from CD_PLAYING to CD_STOPPED in + 1-second intervals (because the audio is buffered in 1-second chunks) If + the audio data is less than 1 second, the remainder is filled with silence. + + If you need to play sequences back-to-back that are less that 1 second long, + use the frame position to determine when to play the next sequence, instead + of SDL_CDStatus. + + This may be possible to fix with a clever usage of the AudioUnit API. + + 4. When new volumes are inserted, our volume information is not updated. The only way + to refresh this information is to reinit the CD-ROM subsystem of SDL. To fix this, + one would probably have to fix point 1 above first, then figure out how to register + for a notification when new media is mounted in order to perform an automatic + rescan for cdfs volumes. + + + + So, here comes a description of how this all works. + + < Initializing > + + To get things rolling, we have to locate mounted volumes that contain + audio (since nearly all Macs don't have analog audio-in on the sound card). + That's easy, since these volumes have a flag that indicates this special + filesystem. See DetectAudioCDVolumes() in CDPlayer.cpp for this code. + + Next, we parse the invisible .TOC.plist in the root of the volume, which gets us + the track information (number, offset, length, leadout, etc). See ReadTOCData() in + CDPlayer.cpp for the skinny on this. + + + < The Playback Loop > + + Now come the tricky parts. Let's start with basic audio playback. When a frame + range to play is requested, we must first find the .aiff files on the volume, + hopefully in the right order. Since these files all begin with a number "1 Audio Track", + etc, this is used to determine the correct track order. + + Once all files are determined, we have to find what file corresponds to the start + and length parameter to SDL_SYS_CDPlay(). Again, this is quite simple by walking the + cdrom's track list. At this point, we also save the offset to the next track and frames + remaining, if we're going to have to play another file after the first one. See + GetFileForOffset() for this code. + + At this point we have all info needed to start playback, so we hand off to the LoadFile() + function, which proceeds to do its magic and plays back the file. + + When the file is finished playing, CompletionProc() is invoked, at which time we can + play the next file if the previously saved next track and frames remaining + indicates that we should. + + + < Magic > + + OK, so it's not really magic, but since I don't fully understand all the hidden details it + seems like it to me ;-) The API's involved are the AudioUnit and AudioFile API's. These + appear to be an extension of CoreAudio for creating modular playback and f/x entities. + The important thing is that CPU usage is very low and reliability is very high. You'd + be hard-pressed to find a way to stutter the playback with other CPU-intensive tasks. + + One part of this magic is that it uses multiple threads, which carries the usual potential + for disaster if not handled carefully. Playback currently requires 4 additional threads: + 1. The coreaudio runloop thread + 2. The coreaudio device i/o thread + 3. The file streaming thread + 4. The notification/callback thread + + The first 2 threads are necessary evil - CoreAudio creates this no matter what the situation + is (even the SDL sound implementation creates theses suckers). The last two are are created + by us. + + The file is streamed from disk using a threaded double-buffer approach. + This way, the high latency operation of reading from disk can be performed without interrupting + the real-time device thread (which amounts to avoiding dropouts). The device thread grabs the + buffer that isn't being read and sends it to the CoreAudio mixer where it eventually gets + to the sound card. + + The device thread posts a notification when the file streaming thread is out of data. This + notification must be handled in a separate thread to avoid potential deadlock in the + device thread. That's where the notification thread comes in. This thread is signaled + whenever a notification needs to be processed, so another file can be played back if need be. + + The API in CDPlayer.cpp contains synchronization because otherwise both the notification thread + and main thread (or another other thread using the SDL CD api) can potentially call it at the same time. + +************************************************************************************/ + + +#include "SDL_cdrom.h" +#include "SDL_syscdrom.h" + +#include "CDPlayer.h" + +#define kErrorFakeDevice "Error: Cannot proceed since we're faking a CD-ROM device. Reinit the CD-ROM subsystem to scan for new volumes." + diff --git a/test/testcdrom.c b/test/testcdrom.c index ecc840ce1..2ace1b8a1 100644 --- a/test/testcdrom.c +++ b/test/testcdrom.c @@ -64,8 +64,8 @@ static void ListTracks(SDL_CD *cdrom) trtype="unknown"; break; } - printf("\tTrack (index %d) %d: %d:%2.2d [%s track]\n", i, - cdrom->track[i].id, m, s, trtype); + printf("\tTrack (index %d) %d: %d:%2.2d / %d [%s track]\n", i, + cdrom->track[i].id, m, s, cdrom->track[i].length, trtype); } }