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@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 
Lock()79 void     Lock ()
80 {
81     if (!apiMutex) {
82         apiMutex = SDL_CreateMutex();
83     }
84     SDL_mutexP(apiMutex);
85 }
86 
Unlock()87 void     Unlock ()
88 {
89     SDL_mutexV(apiMutex);
90 }
91 
DetectAudioCDVolumes(FSVolumeRefNum * volumes,int numVolumes)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 
ReadTOCData(FSVolumeRefNum theVolume,SDL_CD * theCD)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 
ListTrackFiles(FSVolumeRefNum theVolume,FSRef * trackFiles,int numTracks)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 
LoadFile(const FSRef * ref,int startFrame,int stopFrame)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 
ReleaseFile()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 
PlayFile()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 
PauseFile()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 
SetCompletionProc(CDPlayerCompletionProc proc,SDL_CD * cdrom)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 
GetCurrentFrame()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 
CheckInit()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 
FilePlayNotificationHandler(void * inRefCon,OSStatus inStatus)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 
RunCallBackThread(void * param)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