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