/* 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 "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; 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; } 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 = 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"); 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" */