| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997-2012 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 "SDL_config.h" |
| |
| #include "CDPlayer.h" |
| #include "AudioFilePlayer.h" |
| #include "SDLOSXCAGuard.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 int playBackWasInit = 0; |
| static AudioUnit theUnit; |
| static AudioFilePlayer* thePlayer = NULL; |
| static CDPlayerCompletionProc completionProc = NULL; |
| static SDL_mutex *apiMutex = NULL; |
| static SDL_sem *callbackSem; |
| static SDL_CD* theCDROM; |
| |
| /*/////////////////////////////////////////////////////////////////////////// |
| Prototypes |
| //////////////////////////////////////////////////////////////////////////*/ |
| |
| #pragma mark -- Prototypes -- |
| |
| static OSStatus CheckInit (); |
| |
| static void FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus); |
| |
| static int RunCallBackThread (void* inRefCon); |
| |
| |
| #pragma mark -- Public Functions -- |
| |
| void Lock () |
| { |
| if (!apiMutex) { |
| apiMutex = SDL_CreateMutex(); |
| } |
| SDL_mutexP(apiMutex); |
| } |
| |
| void Unlock () |
| { |
| SDL_mutexV(apiMutex); |
| } |
| |
| int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes) |
| { |
| int volumeIndex; |
| int cdVolumeCount = 0; |
| OSStatus result = noErr; |
| |
| for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++) |
| { |
| FSVolumeRefNum actualVolume; |
| FSVolumeInfo volumeInfo; |
| |
| memset (&volumeInfo, 0, sizeof(volumeInfo)); |
| |
| result = FSGetVolumeInfo (kFSInvalidVolumeRefNum, |
| volumeIndex, |
| &actualVolume, |
| kFSVolInfoFSInfo, |
| &volumeInfo, |
| NULL, |
| NULL); |
| |
| 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; |
| } |
| |
| int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD) |
| { |
| HFSUniStr255 dataForkName; |
| OSStatus theErr; |
| FSIORefNum forkRefNum; |
| SInt64 forkSize; |
| Ptr forkData = 0; |
| ByteCount actualRead; |
| CFDataRef dataRef = 0; |
| CFPropertyListRef propertyListRef = 0; |
| FSRefParam fsRefPB; |
| FSRef tocPlistFSRef; |
| FSRef rootRef; |
| const char* error = "Unspecified Error"; |
| const UniChar uniName[] = { '.','T','O','C','.','p','l','i','s','t' }; |
| |
| theErr = FSGetVolumeInfo(theVolume, 0, 0, kFSVolInfoNone, 0, 0, &rootRef); |
| if(theErr != noErr) { |
| error = "FSGetVolumeInfo"; |
| goto bail; |
| } |
| |
| SDL_memset(&fsRefPB, '\0', sizeof (fsRefPB)); |
| |
| /* get stuff from .TOC.plist */ |
| fsRefPB.ref = &rootRef; |
| fsRefPB.newRef = &tocPlistFSRef; |
| fsRefPB.nameLength = sizeof (uniName) / sizeof (uniName[0]); |
| fsRefPB.name = uniName; |
| fsRefPB.textEncodingHint = kTextEncodingUnknown; |
| |
| theErr = PBMakeFSRefUnicodeSync (&fsRefPB); |
| if(theErr != noErr) { |
| error = "PBMakeFSRefUnicodeSync"; |
| 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; |
| } |
| |
| 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); |
| return result; |
| } |
| |
| 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")) || |
| CFStringHasSuffix (name, CFSTR(".cdda"))) { |
| |
| /* Extract the track id from the filename */ |
| int trackID = 0, i = 0; |
| while (i < nameStr.length && !isdigit(nameStr.unicode[i])) { |
| ++i; |
| } |
| while (i < nameStr.length && isdigit(nameStr.unicode[i])) { |
| 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); |
| } |
| |
| return 0; |
| } |
| |
| 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"); |
| return -3; /*throw (-3);*/ |
| } |
| |
| if (!thePlayer->SetDestination(thePlayer, &theUnit)) |
| goto bail; |
| |
| if (startFrame >= 0) |
| thePlayer->SetStartFrame (thePlayer, startFrame); |
| |
| if (stopFrame >= 0 && stopFrame > startFrame) |
| thePlayer->SetStopFrame (thePlayer, stopFrame); |
| |
| /* we set the notifier later */ |
| /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/ |
| |
| if (!thePlayer->Connect(thePlayer)) |
| goto bail; |
| |
| #if DEBUG_CDROM |
| thePlayer->Print(thePlayer); |
| fflush (stdout); |
| #endif |
| /*} |
| catch (...) |
| { |
| goto bail; |
| }*/ |
| |
| error = 0; |
| |
| bail: |
| return error; |
| } |
| |
| int ReleaseFile () |
| { |
| int error = -1; |
| |
| /* (Don't see any way that the original C++ code could throw here.) --ryan. */ |
| /*try {*/ |
| if (thePlayer != NULL) { |
| |
| thePlayer->Disconnect(thePlayer); |
| |
| delete_AudioFilePlayer(thePlayer); |
| |
| thePlayer = NULL; |
| } |
| /*} |
| catch (...) |
| { |
| goto bail; |
| }*/ |
| |
| error = 0; |
| |
| /* bail: */ |
| return error; |
| } |
| |
| int PlayFile () |
| { |
| OSStatus result = -1; |
| |
| if (CheckInit () < 0) |
| goto bail; |
| |
| /*try {*/ |
| |
| // start processing of the audio unit |
| result = AudioOutputUnitStart (theUnit); |
| if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart") |
| |
| /*} |
| catch (...) |
| { |
| goto bail; |
| }*/ |
| |
| result = 0; |
| |
| bail: |
| return result; |
| } |
| |
| int PauseFile () |
| { |
| OSStatus result = -1; |
| |
| if (CheckInit () < 0) |
| goto bail; |
| |
| /*try {*/ |
| |
| /* stop processing the audio unit */ |
| result = AudioOutputUnitStop (theUnit); |
| if (result) goto bail; /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/ |
| /*} |
| catch (...) |
| { |
| goto bail; |
| }*/ |
| |
| result = 0; |
| bail: |
| return result; |
| } |
| |
| void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom) |
| { |
| assert(thePlayer != NULL); |
| |
| theCDROM = cdrom; |
| completionProc = proc; |
| thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom); |
| } |
| |
| int GetCurrentFrame () |
| { |
| int frame; |
| |
| if (thePlayer == NULL) |
| frame = 0; |
| else |
| frame = thePlayer->GetCurrentFrame (thePlayer); |
| |
| return frame; |
| } |
| |
| |
| #pragma mark -- Private Functions -- |
| |
| static OSStatus CheckInit () |
| { |
| if (playBackWasInit) |
| return 0; |
| |
| OSStatus result = noErr; |
| |
| /* Create the callback semaphore */ |
| callbackSem = SDL_CreateSemaphore(0); |
| |
| /* Start callback thread */ |
| SDL_CreateThread(RunCallBackThread, NULL); |
| |
| { /*try {*/ |
| ComponentDescription desc; |
| |
| desc.componentType = kAudioUnitType_Output; |
| desc.componentSubType = kAudioUnitSubType_DefaultOutput; |
| desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| desc.componentFlags = 0; |
| desc.componentFlagsMask = 0; |
| |
| Component comp = FindNextComponent (NULL, &desc); |
| if (comp == NULL) { |
| SDL_SetError ("CheckInit: FindNextComponent returned NULL"); |
| if (result) return -1; //throw(internalComponentErr); |
| } |
| |
| result = OpenAComponent (comp, &theUnit); |
| if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent") |
| |
| // you need to initialize the output unit before you set it as a destination |
| result = AudioUnitInitialize (theUnit); |
| if (result) return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize") |
| |
| |
| playBackWasInit = true; |
| } |
| /*catch (...) |
| { |
| return -1; |
| }*/ |
| |
| return 0; |
| } |
| |
| static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus) |
| { |
| if (inStatus == kAudioFilePlay_FileIsFinished) { |
| |
| /* notify non-CA thread to perform the callback */ |
| SDL_SemPost(callbackSem); |
| |
| } 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); |
| } |
| } |
| |
| static int RunCallBackThread (void *param) |
| { |
| for (;;) { |
| |
| SDL_SemWait(callbackSem); |
| |
| if (completionProc && theCDROM) { |
| #if DEBUG_CDROM |
| printf ("callback!\n"); |
| #endif |
| (*completionProc)(theCDROM); |
| } else { |
| #if DEBUG_CDROM |
| printf ("callback?\n"); |
| #endif |
| } |
| } |
| |
| #if DEBUG_CDROM |
| printf ("thread dying now...\n"); |
| #endif |
| |
| return 0; |
| } |
| |
| /*}; // extern "C" */ |