• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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     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 
ListTrackFiles(FSVolumeRefNum theVolume,FSRef * trackFiles,int numTracks)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 
LoadFile(const FSRef * ref,int startFrame,int stopFrame)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 
ReleaseFile()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 
PlayFile()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 
PauseFile()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 
SetCompletionProc(CDPlayerCompletionProc proc,SDL_CD * cdrom)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 
GetCurrentFrame()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 
CheckInit()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 
FilePlayNotificationHandler(void * inRefCon,OSStatus inStatus)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 
RunCallBackThread(void * param)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" */
629