Home | History | Annotate | Download | only in macosx
      1 /*
      2     SDL - Simple DirectMedia Layer
      3     Copyright (C) 1997-2012 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     FSIORefNum        forkRefNum;
    138     SInt64            forkSize;
    139     Ptr               forkData = 0;
    140     ByteCount         actualRead;
    141     CFDataRef         dataRef = 0;
    142     CFPropertyListRef propertyListRef = 0;
    143     FSRefParam      fsRefPB;
    144     FSRef           tocPlistFSRef;
    145     FSRef           rootRef;
    146     const char* error = "Unspecified Error";
    147     const UniChar uniName[] = { '.','T','O','C','.','p','l','i','s','t' };
    148 
    149     theErr = FSGetVolumeInfo(theVolume, 0, 0, kFSVolInfoNone, 0, 0, &rootRef);
    150     if(theErr != noErr) {
    151         error = "FSGetVolumeInfo";
    152         goto bail;
    153     }
    154 
    155     SDL_memset(&fsRefPB, '\0', sizeof (fsRefPB));
    156 
    157     /* get stuff from .TOC.plist */
    158     fsRefPB.ref = &rootRef;
    159     fsRefPB.newRef = &tocPlistFSRef;
    160     fsRefPB.nameLength = sizeof (uniName) / sizeof (uniName[0]);
    161     fsRefPB.name = uniName;
    162     fsRefPB.textEncodingHint = kTextEncodingUnknown;
    163 
    164     theErr = PBMakeFSRefUnicodeSync (&fsRefPB);
    165     if(theErr != noErr) {
    166         error = "PBMakeFSRefUnicodeSync";
    167         goto bail;
    168     }
    169 
    170     /* Load and parse the TOC XML data */
    171 
    172     theErr = FSGetDataForkName (&dataForkName);
    173     if (theErr != noErr) {
    174         error = "FSGetDataForkName";
    175         goto bail;
    176     }
    177 
    178     theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum);
    179     if (theErr != noErr) {
    180         error = "FSOpenFork";
    181         goto bail;
    182     }
    183 
    184     theErr = FSGetForkSize (forkRefNum, &forkSize);
    185     if (theErr != noErr) {
    186         error = "FSGetForkSize";
    187         goto bail;
    188     }
    189 
    190     /* Allocate some memory for the XML data */
    191     forkData = NewPtr (forkSize);
    192     if(forkData == NULL) {
    193         error = "NewPtr";
    194         goto bail;
    195     }
    196 
    197     theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead);
    198     if(theErr != noErr) {
    199         error = "FSReadFork";
    200         goto bail;
    201     }
    202 
    203     dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize);
    204     if(dataRef == 0) {
    205         error = "CFDataCreate";
    206         goto bail;
    207     }
    208 
    209     propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault,
    210                                                        dataRef,
    211                                                        kCFPropertyListImmutable,
    212                                                        NULL);
    213     if (propertyListRef == NULL) {
    214         error = "CFPropertyListCreateFromXMLData";
    215         goto bail;
    216     }
    217 
    218     /* Now we got the Property List in memory. Parse it. */
    219 
    220     /* First, make sure the root item is a CFDictionary. If not, release and bail. */
    221     if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID())
    222     {
    223         CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef;
    224 
    225         CFDataRef   theRawTOCDataRef;
    226         CFArrayRef  theSessionArrayRef;
    227         CFIndex     numSessions;
    228         CFIndex     index;
    229 
    230         /* This is how we get the Raw TOC Data */
    231         theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString));
    232 
    233         /* Get the session array info. */
    234         theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString));
    235 
    236         /* Find out how many sessions there are. */
    237         numSessions = CFArrayGetCount (theSessionArrayRef);
    238 
    239         /* Initialize the total number of tracks to 0 */
    240         theCD->numtracks = 0;
    241 
    242         /* Iterate over all sessions, collecting the track data */
    243         for(index = 0; index < numSessions; index++)
    244         {
    245             CFDictionaryRef theSessionDict;
    246             CFNumberRef     leadoutBlock;
    247             CFArrayRef      trackArray;
    248             CFIndex         numTracks;
    249             CFIndex         trackIndex;
    250             UInt32          value = 0;
    251 
    252             theSessionDict      = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index);
    253             leadoutBlock        = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString));
    254 
    255             trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString));
    256 
    257             numTracks = CFArrayGetCount (trackArray);
    258 
    259             for(trackIndex = 0; trackIndex < numTracks; trackIndex++) {
    260 
    261                 CFDictionaryRef theTrackDict;
    262                 CFNumberRef     trackNumber;
    263                 CFNumberRef     sessionNumber;
    264                 CFNumberRef     startBlock;
    265                 CFBooleanRef    isDataTrack;
    266                 UInt32          value;
    267 
    268                 theTrackDict  = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex);
    269 
    270                 trackNumber   = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kPointKeyString));
    271                 sessionNumber = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kSessionNumberKeyString));
    272                 startBlock    = (CFNumberRef)  CFDictionaryGetValue (theTrackDict, CFSTR(kStartBlockKeyString));
    273                 isDataTrack   = (CFBooleanRef) CFDictionaryGetValue (theTrackDict, CFSTR(kDataKeyString));
    274 
    275                 /* Fill in the SDL_CD struct */
    276                 int idx = theCD->numtracks++;
    277 
    278                 CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value);
    279                 theCD->track[idx].id = value;
    280 
    281                 CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value);
    282                 theCD->track[idx].offset = value;
    283 
    284                 theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK;
    285 
    286                 /* Since the track lengths are not stored in .TOC.plist we compute them. */
    287                 if (trackIndex > 0) {
    288                     theCD->track[idx-1].length = theCD->track[idx].offset - theCD->track[idx-1].offset;
    289                 }
    290             }
    291 
    292             /* Compute the length of the last track */
    293             CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value);
    294 
    295             theCD->track[theCD->numtracks-1].length =
    296                 value - theCD->track[theCD->numtracks-1].offset;
    297 
    298             /* Set offset to leadout track */
    299             theCD->track[theCD->numtracks].offset = value;
    300         }
    301 
    302     }
    303 
    304     theErr = 0;
    305     goto cleanup;
    306 bail:
    307     SDL_SetError ("ReadTOCData: %s returned %d", error, theErr);
    308     theErr = -1;
    309 cleanup:
    310 
    311     if (propertyListRef != NULL)
    312         CFRelease(propertyListRef);
    313     if (dataRef != NULL)
    314         CFRelease(dataRef);
    315     if (forkData != NULL)
    316         DisposePtr(forkData);
    317 
    318     FSCloseFork (forkRefNum);
    319 
    320     return theErr;
    321 }
    322 
    323 int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks)
    324 {
    325     OSStatus        result = -1;
    326     FSIterator      iterator;
    327     ItemCount       actualObjects;
    328     FSRef           rootDirectory;
    329     FSRef           ref;
    330     HFSUniStr255    nameStr;
    331 
    332     result = FSGetVolumeInfo (theVolume,
    333                               0,
    334                               NULL,
    335                               kFSVolInfoFSInfo,
    336                               NULL,
    337                               NULL,
    338                               &rootDirectory);
    339 
    340     if (result != noErr) {
    341         SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result);
    342         return result;
    343     }
    344 
    345     result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator);
    346     if (result == noErr) {
    347         do
    348         {
    349             result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects,
    350                                            NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr);
    351             if (result == noErr) {
    352 
    353                 CFStringRef  name;
    354                 name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length);
    355 
    356                 /* Look for .aiff extension */
    357                 if (CFStringHasSuffix (name, CFSTR(".aiff")) ||
    358                     CFStringHasSuffix (name, CFSTR(".cdda"))) {
    359 
    360                     /* Extract the track id from the filename */
    361                     int trackID = 0, i = 0;
    362                     while (i < nameStr.length && !isdigit(nameStr.unicode[i])) {
    363                         ++i;
    364                     }
    365                     while (i < nameStr.length && isdigit(nameStr.unicode[i])) {
    366                         trackID = 10 * trackID +(nameStr.unicode[i] - '0');
    367                         ++i;
    368                     }
    369 
    370                     #if DEBUG_CDROM
    371                     printf("Found AIFF for track %d: '%s'\n", trackID,
    372                     CFStringGetCStringPtr (name, CFStringGetSystemEncoding()));
    373                     #endif
    374 
    375                     /* Track ID's start at 1, but we want to start at 0 */
    376                     trackID--;
    377 
    378                     assert(0 <= trackID && trackID <= SDL_MAX_TRACKS);
    379 
    380                     if (trackID < numTracks)
    381                         memcpy (&trackFiles[trackID], &ref, sizeof(FSRef));
    382                 }
    383                 CFRelease (name);
    384             }
    385         } while(noErr == result);
    386         FSCloseIterator (iterator);
    387     }
    388 
    389     return 0;
    390 }
    391 
    392 int LoadFile (const FSRef *ref, int startFrame, int stopFrame)
    393 {
    394     int error = -1;
    395 
    396     if (CheckInit () < 0)
    397         goto bail;
    398 
    399     /* release any currently playing file */
    400     if (ReleaseFile () < 0)
    401         goto bail;
    402 
    403     #if DEBUG_CDROM
    404     printf ("LoadFile: %d %d\n", startFrame, stopFrame);
    405     #endif
    406 
    407     /*try {*/
    408 
    409         /* create a new player, and attach to the audio unit */
    410 
    411         thePlayer = new_AudioFilePlayer(ref);
    412         if (thePlayer == NULL) {
    413             SDL_SetError ("LoadFile: Could not create player");
    414             return -3; /*throw (-3);*/
    415         }
    416 
    417         if (!thePlayer->SetDestination(thePlayer, &theUnit))
    418             goto bail;
    419 
    420         if (startFrame >= 0)
    421             thePlayer->SetStartFrame (thePlayer, startFrame);
    422 
    423         if (stopFrame >= 0 && stopFrame > startFrame)
    424             thePlayer->SetStopFrame (thePlayer, stopFrame);
    425 
    426         /* we set the notifier later */
    427         /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/
    428 
    429         if (!thePlayer->Connect(thePlayer))
    430             goto bail;
    431 
    432         #if DEBUG_CDROM
    433         thePlayer->Print(thePlayer);
    434         fflush (stdout);
    435         #endif
    436     /*}
    437       catch (...)
    438       {
    439           goto bail;
    440       }*/
    441 
    442     error = 0;
    443 
    444     bail:
    445     return error;
    446 }
    447 
    448 int ReleaseFile ()
    449 {
    450     int error = -1;
    451 
    452     /* (Don't see any way that the original C++ code could throw here.) --ryan. */
    453     /*try {*/
    454         if (thePlayer != NULL) {
    455 
    456             thePlayer->Disconnect(thePlayer);
    457 
    458             delete_AudioFilePlayer(thePlayer);
    459 
    460             thePlayer = NULL;
    461         }
    462     /*}
    463       catch (...)
    464       {
    465           goto bail;
    466       }*/
    467 
    468     error = 0;
    469 
    470 /*  bail: */
    471     return error;
    472 }
    473 
    474 int PlayFile ()
    475 {
    476     OSStatus result = -1;
    477 
    478     if (CheckInit () < 0)
    479         goto bail;
    480 
    481     /*try {*/
    482 
    483         // start processing of the audio unit
    484         result = AudioOutputUnitStart (theUnit);
    485             if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart")
    486 
    487     /*}
    488     catch (...)
    489     {
    490         goto bail;
    491     }*/
    492 
    493     result = 0;
    494 
    495 bail:
    496     return result;
    497 }
    498 
    499 int PauseFile ()
    500 {
    501     OSStatus result = -1;
    502 
    503     if (CheckInit () < 0)
    504         goto bail;
    505 
    506     /*try {*/
    507 
    508         /* stop processing the audio unit */
    509         result = AudioOutputUnitStop (theUnit);
    510             if (result) goto bail;  /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/
    511     /*}
    512       catch (...)
    513       {
    514           goto bail;
    515       }*/
    516 
    517     result = 0;
    518 bail:
    519     return result;
    520 }
    521 
    522 void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom)
    523 {
    524     assert(thePlayer != NULL);
    525 
    526     theCDROM = cdrom;
    527     completionProc = proc;
    528     thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom);
    529 }
    530 
    531 int GetCurrentFrame ()
    532 {
    533     int frame;
    534 
    535     if (thePlayer == NULL)
    536         frame = 0;
    537     else
    538         frame = thePlayer->GetCurrentFrame (thePlayer);
    539 
    540     return frame;
    541 }
    542 
    543 
    544 #pragma mark -- Private Functions --
    545 
    546 static OSStatus CheckInit ()
    547 {
    548     if (playBackWasInit)
    549         return 0;
    550 
    551     OSStatus result = noErr;
    552 
    553     /* Create the callback semaphore */
    554     callbackSem = SDL_CreateSemaphore(0);
    555 
    556     /* Start callback thread */
    557     SDL_CreateThread(RunCallBackThread, NULL);
    558 
    559     { /*try {*/
    560         ComponentDescription desc;
    561 
    562         desc.componentType = kAudioUnitType_Output;
    563         desc.componentSubType = kAudioUnitSubType_DefaultOutput;
    564         desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    565         desc.componentFlags = 0;
    566         desc.componentFlagsMask = 0;
    567 
    568         Component comp = FindNextComponent (NULL, &desc);
    569         if (comp == NULL) {
    570             SDL_SetError ("CheckInit: FindNextComponent returned NULL");
    571             if (result) return -1; //throw(internalComponentErr);
    572         }
    573 
    574         result = OpenAComponent (comp, &theUnit);
    575             if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent")
    576 
    577         // you need to initialize the output unit before you set it as a destination
    578         result = AudioUnitInitialize (theUnit);
    579             if (result) return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize")
    580 
    581 
    582         playBackWasInit = true;
    583     }
    584     /*catch (...)
    585       {
    586           return -1;
    587       }*/
    588 
    589     return 0;
    590 }
    591 
    592 static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus)
    593 {
    594     if (inStatus == kAudioFilePlay_FileIsFinished) {
    595 
    596         /* notify non-CA thread to perform the callback */
    597         SDL_SemPost(callbackSem);
    598 
    599     } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) {
    600 
    601         SDL_SetError ("CDPlayer Notification: buffer underrun");
    602     } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) {
    603 
    604         SDL_SetError ("CDPlayer Notification: player is uninitialized");
    605     } else {
    606 
    607         SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus);
    608     }
    609 }
    610 
    611 static int RunCallBackThread (void *param)
    612 {
    613     for (;;) {
    614 
    615 	SDL_SemWait(callbackSem);
    616 
    617         if (completionProc && theCDROM) {
    618             #if DEBUG_CDROM
    619             printf ("callback!\n");
    620             #endif
    621             (*completionProc)(theCDROM);
    622         } else {
    623             #if DEBUG_CDROM
    624             printf ("callback?\n");
    625             #endif
    626         }
    627     }
    628 
    629     #if DEBUG_CDROM
    630     printf ("thread dying now...\n");
    631     #endif
    632 
    633     return 0;
    634 }
    635 
    636 /*}; // extern "C" */
    637