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
56static int             playBackWasInit = 0;
57static AudioUnit        theUnit;
58static AudioFilePlayer* thePlayer = NULL;
59static CDPlayerCompletionProc   completionProc = NULL;
60static SDL_mutex       *apiMutex = NULL;
61static SDL_sem         *callbackSem;
62static SDL_CD*          theCDROM;
63
64/*///////////////////////////////////////////////////////////////////////////
65    Prototypes
66  //////////////////////////////////////////////////////////////////////////*/
67
68#pragma mark -- Prototypes --
69
70static OSStatus CheckInit ();
71
72static void     FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus);
73
74static int      RunCallBackThread (void* inRefCon);
75
76
77#pragma mark -- Public Functions --
78
79void     Lock ()
80{
81    if (!apiMutex) {
82        apiMutex = SDL_CreateMutex();
83    }
84    SDL_mutexP(apiMutex);
85}
86
87void     Unlock ()
88{
89    SDL_mutexV(apiMutex);
90}
91
92int 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
133int 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;
306bail:
307    SDL_SetError ("ReadTOCData: %s returned %d", error, theErr);
308    theErr = -1;
309cleanup:
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
323int 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
392int 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
448int 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
474int 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
495bail:
496    return result;
497}
498
499int 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;
518bail:
519    return result;
520}
521
522void 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
531int 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
546static 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
592static 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
611static 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