Home | History | Annotate | Download | only in macosx
      1 /*
      2     SDL - Simple DirectMedia Layer
      3     Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002  Sam Lantinga
      4 
      5     This library is free software; you can redistribute it and/or
      6     modify it under the terms of the GNU Library General Public
      7     License as published by the Free Software Foundation; either
      8     version 2 of the License, or (at your option) any later version.
      9 
     10     This library is distributed in the hope that it will be useful,
     11     but WITHOUT ANY WARRANTY; without even the implied warranty of
     12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13     Library General Public License for more details.
     14 
     15     You should have received a copy of the GNU Library General Public
     16     License along with this library; if not, write to the Free
     17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     18 
     19     Sam Lantinga
     20     slouken (at) libsdl.org
     21 */
     22 #include "SDL_config.h"
     23 
     24 #include "CDPlayer.h"
     25 #include "AudioFilePlayer.h"
     26 #include "SDLOSXCAGuard.h"
     27 
     28 /* we're exporting these functions into C land for SDL_syscdrom.c */
     29 /*extern "C" {*/
     30 
     31 /*///////////////////////////////////////////////////////////////////////////
     32     Constants
     33   //////////////////////////////////////////////////////////////////////////*/
     34 
     35 #define kAudioCDFilesystemID   (UInt16)(('J' << 8) | 'H') /* 'JH'; this avoids compiler warning */
     36 
     37 /* XML PList keys */
     38 #define kRawTOCDataString           "Format 0x02 TOC Data"
     39 #define kSessionsString             "Sessions"
     40 #define kSessionTypeString          "Session Type"
     41 #define kTrackArrayString           "Track Array"
     42 #define kFirstTrackInSessionString      "First Track"
     43 #define kLastTrackInSessionString       "Last Track"
     44 #define kLeadoutBlockString         "Leadout Block"
     45 #define kDataKeyString              "Data"
     46 #define kPointKeyString             "Point"
     47 #define kSessionNumberKeyString         "Session Number"
     48 #define kStartBlockKeyString            "Start Block"
     49 
     50 /*///////////////////////////////////////////////////////////////////////////
     51     Globals
     52   //////////////////////////////////////////////////////////////////////////*/
     53 
     54 #pragma mark -- Globals --
     55 
     56 static int             playBackWasInit = 0;
     57 static AudioUnit        theUnit;
     58 static AudioFilePlayer* thePlayer = NULL;
     59 static CDPlayerCompletionProc   completionProc = NULL;
     60 static SDL_mutex       *apiMutex = NULL;
     61 static SDL_sem         *callbackSem;
     62 static SDL_CD*          theCDROM;
     63 
     64 /*///////////////////////////////////////////////////////////////////////////
     65     Prototypes
     66   //////////////////////////////////////////////////////////////////////////*/
     67 
     68 #pragma mark -- Prototypes --
     69 
     70 static OSStatus CheckInit ();
     71 
     72 static void     FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus);
     73 
     74 static int      RunCallBackThread (void* inRefCon);
     75 
     76 
     77 #pragma mark -- Public Functions --
     78 
     79 void     Lock ()
     80 {
     81     if (!apiMutex) {
     82         apiMutex = SDL_CreateMutex();
     83     }
     84     SDL_mutexP(apiMutex);
     85 }
     86 
     87 void     Unlock ()
     88 {
     89     SDL_mutexV(apiMutex);
     90 }
     91 
     92 int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes)
     93 {
     94     int volumeIndex;
     95     int cdVolumeCount = 0;
     96     OSStatus result = noErr;
     97 
     98     for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++)
     99     {
    100         FSVolumeRefNum  actualVolume;
    101         FSVolumeInfo    volumeInfo;
    102 
    103         memset (&volumeInfo, 0, sizeof(volumeInfo));
    104 
    105         result = FSGetVolumeInfo (kFSInvalidVolumeRefNum,
    106                                   volumeIndex,
    107                                   &actualVolume,
    108                                   kFSVolInfoFSInfo,
    109                                   &volumeInfo,
    110                                   NULL,
    111                                   NULL);
    112 
    113         if (result == noErr)
    114         {
    115             if (volumeInfo.filesystemID == kAudioCDFilesystemID) /* It's an audio CD */
    116             {
    117                 if (volumes != NULL && cdVolumeCount < numVolumes)
    118                     volumes[cdVolumeCount] = actualVolume;
    119 
    120                 cdVolumeCount++;
    121             }
    122         }
    123         else
    124         {
    125             /* I'm commenting this out because it seems to be harmless */
    126             /*SDL_SetError ("DetectAudioCDVolumes: FSGetVolumeInfo returned %d", result);*/
    127         }
    128     }
    129 
    130     return cdVolumeCount;
    131 }
    132 
    133 int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD)
    134 {
    135     HFSUniStr255      dataForkName;
    136     OSStatus          theErr;
    137     SInt16            forkRefNum;
    138     SInt64            forkSize;
    139     Ptr               forkData = 0;
    140     ByteCount         actualRead;
    141     CFDataRef         dataRef = 0;
    142     CFPropertyListRef propertyListRef = 0;
    143 
    144     FSRefParam      fsRefPB;
    145     FSRef           tocPlistFSRef;
    146 
    147     const char* error = "Unspecified Error";
    148 
    149     /* get stuff from .TOC.plist */
    150     fsRefPB.ioCompletion = NULL;
    151     fsRefPB.ioNamePtr = "\p.TOC.plist";
    152     fsRefPB.ioVRefNum = theVolume;
    153     fsRefPB.ioDirID = 0;
    154     fsRefPB.newRef = &tocPlistFSRef;
    155 
    156     theErr = PBMakeFSRefSync (&fsRefPB);
    157     if(theErr != noErr) {
    158         error = "PBMakeFSRefSync";
    159         goto bail;
    160     }
    161 
    162     /* Load and parse the TOC XML data */
    163 
    164     theErr = FSGetDataForkName (&dataForkName);
    165     if (theErr != noErr) {
    166         error = "FSGetDataForkName";
    167         goto bail;
    168     }
    169 
    170     theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum);
    171     if (theErr != noErr) {
    172         error = "FSOpenFork";
    173         goto bail;
    174     }
    175 
    176     theErr = FSGetForkSize (forkRefNum, &forkSize);
    177     if (theErr != noErr) {
    178         error = "FSGetForkSize";
    179         goto bail;
    180     }
    181 
    182     /* Allocate some memory for the XML data */
    183     forkData = NewPtr (forkSize);
    184     if(forkData == NULL) {
    185         error = "NewPtr";
    186         goto bail;
    187     }
    188 
    189     theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead);
    190     if(theErr != noErr) {
    191         error = "FSReadFork";
    192         goto bail;
    193     }
    194 
    195     dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize);
    196     if(dataRef == 0) {
    197         error = "CFDataCreate";
    198         goto bail;
    199     }
    200 
    201     propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault,
    202                                                        dataRef,
    203                                                        kCFPropertyListImmutable,
    204                                                        NULL);
    205     if (propertyListRef == NULL) {
    206         error = "CFPropertyListCreateFromXMLData";
    207         goto bail;
    208     }
    209 
    210     /* Now we got the Property List in memory. Parse it. */
    211 
    212     /* First, make sure the root item is a CFDictionary. If not, release and bail. */
    213     if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID())
    214     {
    215         CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef;
    216 
    217         CFDataRef   theRawTOCDataRef;
    218         CFArrayRef  theSessionArrayRef;
    219         CFIndex     numSessions;
    220         CFIndex     index;
    221 
    222         /* This is how we get the Raw TOC Data */
    223         theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString));
    224 
    225         /* Get the session array info. */
    226         theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString));
    227 
    228         /* Find out how many sessions there are. */
    229         numSessions = CFArrayGetCount (theSessionArrayRef);
    230 
    231         /* Initialize the total number of tracks to 0 */
    232         theCD->numtracks = 0;
    233 
    234         /* Iterate over all sessions, collecting the track data */
    235         for(index = 0; index < numSessions; index++)
    236         {
    237             CFDictionaryRef theSessionDict;
    238             CFNumberRef     leadoutBlock;
    239             CFArrayRef      trackArray;
    240             CFIndex         numTracks;
    241             CFIndex         trackIndex;
    242             UInt32          value = 0;
    243 
    244             theSessionDict      = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index);
    245             leadoutBlock        = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString));
    246 
    247             trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString));
    248 
    249             numTracks = CFArrayGetCount (trackArray);
    250 
    251             for(trackIndex = 0; trackIndex < numTracks; trackIndex++) {
    252 
    253                 CFDictionaryRef theTrackDict;
    254                 CFNumberRef     trackNumber;
    255                 CFNumberRef     sessionNumber;
    256                 CFNumberRef     startBlock;
    257                 CFBooleanRef    isDataTrack;
    258                 UInt32          value;
    259 
    260                 theTrackDict  = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex);
    261 
    262                 trackNumber   = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kPointKeyString));
    263                 sessionNumber = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kSessionNumberKeyString));
    264                 startBlock    = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kStartBlockKeyString));
    265                 isDataTrack   = (CFBooleanRef) CFDictionaryGetValue (theTrackDict, CFSTR(kDataKeyString));
    266 
    267                 /* Fill in the SDL_CD struct */
    268                 int idx = theCD->numtracks++;
    269 
    270                 CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value);
    271                 theCD->track[idx].id = value;
    272 
    273                 CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value);
    274                 theCD->track[idx].offset = value;
    275 
    276                 theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK;
    277 
    278                 /* Since the track lengths are not stored in .TOC.plist we compute them. */
    279                 if (trackIndex > 0) {
    280                     theCD->track[idx-1].length = theCD->track[idx].offset - theCD->track[idx-1].offset;
    281                 }
    282             }
    283 
    284             /* Compute the length of the last track */
    285             CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value);
    286 
    287             theCD->track[theCD->numtracks-1].length =
    288                 value - theCD->track[theCD->numtracks-1].offset;
    289 
    290             /* Set offset to leadout track */
    291             theCD->track[theCD->numtracks].offset = value;
    292         }
    293 
    294     }
    295 
    296     theErr = 0;
    297     goto cleanup;
    298 bail:
    299     SDL_SetError ("ReadTOCData: %s returned %d", error, theErr);
    300     theErr = -1;
    301 cleanup:
    302 
    303     if (propertyListRef != NULL)
    304         CFRelease(propertyListRef);
    305     if (dataRef != NULL)
    306         CFRelease(dataRef);
    307     if (forkData != NULL)
    308         DisposePtr(forkData);
    309 
    310     FSCloseFork (forkRefNum);
    311 
    312     return theErr;
    313 }
    314 
    315 int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks)
    316 {
    317     OSStatus        result = -1;
    318     FSIterator      iterator;
    319     ItemCount       actualObjects;
    320     FSRef           rootDirectory;
    321     FSRef           ref;
    322     HFSUniStr255    nameStr;
    323 
    324     result = FSGetVolumeInfo (theVolume,
    325                               0,
    326                               NULL,
    327                               kFSVolInfoFSInfo,
    328                               NULL,
    329                               NULL,
    330                               &rootDirectory);
    331 
    332     if (result != noErr) {
    333         SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result);
    334         return result;
    335     }
    336 
    337     result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator);
    338     if (result == noErr) {
    339         do
    340         {
    341             result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects,
    342                                            NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr);
    343             if (result == noErr) {
    344 
    345                 CFStringRef  name;
    346                 name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length);
    347 
    348                 /* Look for .aiff extension */
    349                 if (CFStringHasSuffix (name, CFSTR(".aiff")) ||
    350                     CFStringHasSuffix (name, CFSTR(".cdda"))) {
    351 
    352                     /* Extract the track id from the filename */
    353                     int trackID = 0, i = 0;
    354                     while (i < nameStr.length && !isdigit(nameStr.unicode[i])) {
    355                         ++i;
    356                     }
    357                     while (i < nameStr.length && isdigit(nameStr.unicode[i])) {
    358                         trackID = 10 * trackID +(nameStr.unicode[i] - '0');
    359                         ++i;
    360                     }
    361 
    362                     #if DEBUG_CDROM
    363                     printf("Found AIFF for track %d: '%s'\n", trackID,
    364                     CFStringGetCStringPtr (name, CFStringGetSystemEncoding()));
    365                     #endif
    366 
    367                     /* Track ID's start at 1, but we want to start at 0 */
    368                     trackID--;
    369 
    370                     assert(0 <= trackID && trackID <= SDL_MAX_TRACKS);
    371 
    372                     if (trackID < numTracks)
    373                         memcpy (&trackFiles[trackID], &ref, sizeof(FSRef));
    374                 }
    375                 CFRelease (name);
    376             }
    377         } while(noErr == result);
    378         FSCloseIterator (iterator);
    379     }
    380 
    381     return 0;
    382 }
    383 
    384 int LoadFile (const FSRef *ref, int startFrame, int stopFrame)
    385 {
    386     int error = -1;
    387 
    388     if (CheckInit () < 0)
    389         goto bail;
    390 
    391     /* release any currently playing file */
    392     if (ReleaseFile () < 0)
    393         goto bail;
    394 
    395     #if DEBUG_CDROM
    396     printf ("LoadFile: %d %d\n", startFrame, stopFrame);
    397     #endif
    398 
    399     /*try {*/
    400 
    401         /* create a new player, and attach to the audio unit */
    402 
    403         thePlayer = new_AudioFilePlayer(ref);
    404         if (thePlayer == NULL) {
    405             SDL_SetError ("LoadFile: Could not create player");
    406             return -3; /*throw (-3);*/
    407         }
    408 
    409         if (!thePlayer->SetDestination(thePlayer, &theUnit))
    410             goto bail;
    411 
    412         if (startFrame >= 0)
    413             thePlayer->SetStartFrame (thePlayer, startFrame);
    414 
    415         if (stopFrame >= 0 && stopFrame > startFrame)
    416             thePlayer->SetStopFrame (thePlayer, stopFrame);
    417 
    418         /* we set the notifier later */
    419         /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/
    420 
    421         if (!thePlayer->Connect(thePlayer))
    422             goto bail;
    423 
    424         #if DEBUG_CDROM
    425         thePlayer->Print(thePlayer);
    426         fflush (stdout);
    427         #endif
    428     /*}
    429       catch (...)
    430       {
    431           goto bail;
    432       }*/
    433 
    434     error = 0;
    435 
    436     bail:
    437     return error;
    438 }
    439 
    440 int ReleaseFile ()
    441 {
    442     int error = -1;
    443 
    444     /* (Don't see any way that the original C++ code could throw here.) --ryan. */
    445     /*try {*/
    446         if (thePlayer != NULL) {
    447 
    448             thePlayer->Disconnect(thePlayer);
    449 
    450             delete_AudioFilePlayer(thePlayer);
    451 
    452             thePlayer = NULL;
    453         }
    454     /*}
    455       catch (...)
    456       {
    457           goto bail;
    458       }*/
    459 
    460     error = 0;
    461 
    462 /*  bail: */
    463     return error;
    464 }
    465 
    466 int PlayFile ()
    467 {
    468     OSStatus result = -1;
    469 
    470     if (CheckInit () < 0)
    471         goto bail;
    472 
    473     /*try {*/
    474 
    475         // start processing of the audio unit
    476         result = AudioOutputUnitStart (theUnit);
    477             if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart")
    478 
    479     /*}
    480     catch (...)
    481     {
    482         goto bail;
    483     }*/
    484 
    485     result = 0;
    486 
    487 bail:
    488     return result;
    489 }
    490 
    491 int PauseFile ()
    492 {
    493     OSStatus result = -1;
    494 
    495     if (CheckInit () < 0)
    496         goto bail;
    497 
    498     /*try {*/
    499 
    500         /* stop processing the audio unit */
    501         result = AudioOutputUnitStop (theUnit);
    502             if (result) goto bail;  /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/
    503     /*}
    504       catch (...)
    505       {
    506           goto bail;
    507       }*/
    508 
    509     result = 0;
    510 bail:
    511     return result;
    512 }
    513 
    514 void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom)
    515 {
    516     assert(thePlayer != NULL);
    517 
    518     theCDROM = cdrom;
    519     completionProc = proc;
    520     thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom);
    521 }
    522 
    523 int GetCurrentFrame ()
    524 {
    525     int frame;
    526 
    527     if (thePlayer == NULL)
    528         frame = 0;
    529     else
    530         frame = thePlayer->GetCurrentFrame (thePlayer);
    531 
    532     return frame;
    533 }
    534 
    535 
    536 #pragma mark -- Private Functions --
    537 
    538 static OSStatus CheckInit ()
    539 {
    540     if (playBackWasInit)
    541         return 0;
    542 
    543     OSStatus result = noErr;
    544 
    545     /* Create the callback semaphore */
    546     callbackSem = SDL_CreateSemaphore(0);
    547 
    548     /* Start callback thread */
    549     SDL_CreateThread(RunCallBackThread, NULL);
    550 
    551     { /*try {*/
    552         ComponentDescription desc;
    553 
    554         desc.componentType = kAudioUnitComponentType;
    555         desc.componentSubType = kAudioUnitSubType_Output;
    556         desc.componentManufacturer = kAudioUnitID_DefaultOutput;
    557         desc.componentFlags = 0;
    558         desc.componentFlagsMask = 0;
    559 
    560         Component comp = FindNextComponent (NULL, &desc);
    561         if (comp == NULL) {
    562             SDL_SetError ("CheckInit: FindNextComponent returned NULL");
    563             if (result) return -1; //throw(internalComponentErr);
    564         }
    565 
    566         result = OpenAComponent (comp, &theUnit);
    567             if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent")
    568 
    569         // you need to initialize the output unit before you set it as a destination
    570         result = AudioUnitInitialize (theUnit);
    571             if (result) return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize")
    572 
    573 
    574         playBackWasInit = true;
    575     }
    576     /*catch (...)
    577       {
    578           return -1;
    579       }*/
    580 
    581     return 0;
    582 }
    583 
    584 static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus)
    585 {
    586     if (inStatus == kAudioFilePlay_FileIsFinished) {
    587 
    588         /* notify non-CA thread to perform the callback */
    589         SDL_SemPost(callbackSem);
    590 
    591     } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) {
    592 
    593         SDL_SetError ("CDPlayer Notification: buffer underrun");
    594     } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) {
    595 
    596         SDL_SetError ("CDPlayer Notification: player is uninitialized");
    597     } else {
    598 
    599         SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus);
    600     }
    601 }
    602 
    603 static int RunCallBackThread (void *param)
    604 {
    605     for (;;) {
    606 
    607 	SDL_SemWait(callbackSem);
    608 
    609         if (completionProc && theCDROM) {
    610             #if DEBUG_CDROM
    611             printf ("callback!\n");
    612             #endif
    613             (*completionProc)(theCDROM);
    614         } else {
    615             #if DEBUG_CDROM
    616             printf ("callback?\n");
    617             #endif
    618         }
    619     }
    620 
    621     #if DEBUG_CDROM
    622     printf ("thread dying now...\n");
    623     #endif
    624 
    625     return 0;
    626 }
    627 
    628 /*}; // extern "C" */
    629