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