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