src/cdrom/macosx/CDPlayer.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 09 Mar 2006 06:33:21 +0000
changeset 1487 dc6b59e925a2
parent 1402 d910939febfa
child 1662 782fd950bd46
child 1895 c121d94672cb
child 4159 a1b03ba2fcd0
permissions -rw-r--r--
Cleaning up warnings on MacOS X
     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@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" */