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