e14743d1 |
1 | /* |
2 | SDL - Simple DirectMedia Layer |
3 | Copyright (C) 1997-2009 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 | |
79 | void Lock () |
80 | { |
81 | if (!apiMutex) { |
82 | apiMutex = SDL_CreateMutex(); |
83 | } |
84 | SDL_mutexP(apiMutex); |
85 | } |
86 | |
87 | void Unlock () |
88 | { |
89 | SDL_mutexV(apiMutex); |
90 | } |
91 | |
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 | |
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 | int i; |
144 | FSRefParam fsRefPB; |
145 | FSRef tocPlistFSRef; |
146 | FSRef rootRef; |
147 | const char* error = "Unspecified Error"; |
148 | const UniChar uniName[] = { '.','T','O','C','.','p','l','i','s','t' }; |
149 | |
150 | theErr = FSGetVolumeInfo(theVolume, 0, 0, kFSVolInfoNone, 0, 0, &rootRef); |
151 | if(theErr != noErr) { |
152 | error = "FSGetVolumeInfo"; |
153 | goto bail; |
154 | } |
155 | |
156 | SDL_memset(&fsRefPB, '\0', sizeof (fsRefPB)); |
157 | |
158 | /* get stuff from .TOC.plist */ |
159 | fsRefPB.ref = &rootRef; |
160 | fsRefPB.newRef = &tocPlistFSRef; |
161 | fsRefPB.nameLength = sizeof (uniName) / sizeof (uniName[0]); |
162 | fsRefPB.name = uniName; |
163 | fsRefPB.textEncodingHint = kTextEncodingUnknown; |
164 | |
165 | theErr = PBMakeFSRefUnicodeSync (&fsRefPB); |
166 | if(theErr != noErr) { |
167 | error = "PBMakeFSRefUnicodeSync"; |
168 | goto bail; |
169 | } |
170 | |
171 | /* Load and parse the TOC XML data */ |
172 | |
173 | theErr = FSGetDataForkName (&dataForkName); |
174 | if (theErr != noErr) { |
175 | error = "FSGetDataForkName"; |
176 | goto bail; |
177 | } |
178 | |
179 | theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum); |
180 | if (theErr != noErr) { |
181 | error = "FSOpenFork"; |
182 | goto bail; |
183 | } |
184 | |
185 | theErr = FSGetForkSize (forkRefNum, &forkSize); |
186 | if (theErr != noErr) { |
187 | error = "FSGetForkSize"; |
188 | goto bail; |
189 | } |
190 | |
191 | /* Allocate some memory for the XML data */ |
192 | forkData = NewPtr (forkSize); |
193 | if(forkData == NULL) { |
194 | error = "NewPtr"; |
195 | goto bail; |
196 | } |
197 | |
198 | theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead); |
199 | if(theErr != noErr) { |
200 | error = "FSReadFork"; |
201 | goto bail; |
202 | } |
203 | |
204 | dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize); |
205 | if(dataRef == 0) { |
206 | error = "CFDataCreate"; |
207 | goto bail; |
208 | } |
209 | |
210 | propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault, |
211 | dataRef, |
212 | kCFPropertyListImmutable, |
213 | NULL); |
214 | if (propertyListRef == NULL) { |
215 | error = "CFPropertyListCreateFromXMLData"; |
216 | goto bail; |
217 | } |
218 | |
219 | /* Now we got the Property List in memory. Parse it. */ |
220 | |
221 | /* First, make sure the root item is a CFDictionary. If not, release and bail. */ |
222 | if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID()) |
223 | { |
224 | CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef; |
225 | |
226 | CFDataRef theRawTOCDataRef; |
227 | CFArrayRef theSessionArrayRef; |
228 | CFIndex numSessions; |
229 | CFIndex index; |
230 | |
231 | /* This is how we get the Raw TOC Data */ |
232 | theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString)); |
233 | |
234 | /* Get the session array info. */ |
235 | theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString)); |
236 | |
237 | /* Find out how many sessions there are. */ |
238 | numSessions = CFArrayGetCount (theSessionArrayRef); |
239 | |
240 | /* Initialize the total number of tracks to 0 */ |
241 | theCD->numtracks = 0; |
242 | |
243 | /* Iterate over all sessions, collecting the track data */ |
244 | for(index = 0; index < numSessions; index++) |
245 | { |
246 | CFDictionaryRef theSessionDict; |
247 | CFNumberRef leadoutBlock; |
248 | CFArrayRef trackArray; |
249 | CFIndex numTracks; |
250 | CFIndex trackIndex; |
251 | UInt32 value = 0; |
252 | |
253 | theSessionDict = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index); |
254 | leadoutBlock = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString)); |
255 | |
256 | trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString)); |
257 | |
258 | numTracks = CFArrayGetCount (trackArray); |
259 | |
260 | for(trackIndex = 0; trackIndex < numTracks; trackIndex++) { |
261 | |
262 | CFDictionaryRef theTrackDict; |
263 | CFNumberRef trackNumber; |
264 | CFNumberRef sessionNumber; |
265 | CFNumberRef startBlock; |
266 | CFBooleanRef isDataTrack; |
267 | UInt32 value; |
268 | |
269 | theTrackDict = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex); |
270 | |
271 | trackNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kPointKeyString)); |
272 | sessionNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kSessionNumberKeyString)); |
273 | startBlock = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kStartBlockKeyString)); |
274 | isDataTrack = (CFBooleanRef) CFDictionaryGetValue (theTrackDict, CFSTR(kDataKeyString)); |
275 | |
276 | /* Fill in the SDL_CD struct */ |
277 | int idx = theCD->numtracks++; |
278 | |
279 | CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value); |
280 | theCD->track[idx].id = value; |
281 | |
282 | CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value); |
283 | theCD->track[idx].offset = value; |
284 | |
285 | theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK; |
286 | |
287 | /* Since the track lengths are not stored in .TOC.plist we compute them. */ |
288 | if (trackIndex > 0) { |
289 | theCD->track[idx-1].length = theCD->track[idx].offset - theCD->track[idx-1].offset; |
290 | } |
291 | } |
292 | |
293 | /* Compute the length of the last track */ |
294 | CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value); |
295 | |
296 | theCD->track[theCD->numtracks-1].length = |
297 | value - theCD->track[theCD->numtracks-1].offset; |
298 | |
299 | /* Set offset to leadout track */ |
300 | theCD->track[theCD->numtracks].offset = value; |
301 | } |
302 | |
303 | } |
304 | |
305 | theErr = 0; |
306 | goto cleanup; |
307 | bail: |
308 | SDL_SetError ("ReadTOCData: %s returned %d", error, theErr); |
309 | theErr = -1; |
310 | cleanup: |
311 | |
312 | if (propertyListRef != NULL) |
313 | CFRelease(propertyListRef); |
314 | if (dataRef != NULL) |
315 | CFRelease(dataRef); |
316 | if (forkData != NULL) |
317 | DisposePtr(forkData); |
318 | |
319 | FSCloseFork (forkRefNum); |
320 | |
321 | return theErr; |
322 | } |
323 | |
324 | int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks) |
325 | { |
326 | OSStatus result = -1; |
327 | FSIterator iterator; |
328 | ItemCount actualObjects; |
329 | FSRef rootDirectory; |
330 | FSRef ref; |
331 | HFSUniStr255 nameStr; |
332 | |
333 | result = FSGetVolumeInfo (theVolume, |
334 | 0, |
335 | NULL, |
336 | kFSVolInfoFSInfo, |
337 | NULL, |
338 | NULL, |
339 | &rootDirectory); |
340 | |
341 | if (result != noErr) { |
342 | SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result); |
343 | return result; |
344 | } |
345 | |
346 | result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator); |
347 | if (result == noErr) { |
348 | do |
349 | { |
350 | result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects, |
351 | NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr); |
352 | if (result == noErr) { |
353 | |
354 | CFStringRef name; |
355 | name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length); |
356 | |
357 | /* Look for .aiff extension */ |
358 | if (CFStringHasSuffix (name, CFSTR(".aiff")) || |
359 | CFStringHasSuffix (name, CFSTR(".cdda"))) { |
360 | |
361 | /* Extract the track id from the filename */ |
362 | int trackID = 0, i = 0; |
363 | while (i < nameStr.length && !isdigit(nameStr.unicode[i])) { |
364 | ++i; |
365 | } |
366 | while (i < nameStr.length && isdigit(nameStr.unicode[i])) { |
367 | trackID = 10 * trackID +(nameStr.unicode[i] - '0'); |
368 | ++i; |
369 | } |
370 | |
371 | #if DEBUG_CDROM |
372 | printf("Found AIFF for track %d: '%s'\n", trackID, |
373 | CFStringGetCStringPtr (name, CFStringGetSystemEncoding())); |
374 | #endif |
375 | |
376 | /* Track ID's start at 1, but we want to start at 0 */ |
377 | trackID--; |
378 | |
379 | assert(0 <= trackID && trackID <= SDL_MAX_TRACKS); |
380 | |
381 | if (trackID < numTracks) |
382 | memcpy (&trackFiles[trackID], &ref, sizeof(FSRef)); |
383 | } |
384 | CFRelease (name); |
385 | } |
386 | } while(noErr == result); |
387 | FSCloseIterator (iterator); |
388 | } |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | int LoadFile (const FSRef *ref, int startFrame, int stopFrame) |
394 | { |
395 | int error = -1; |
396 | |
397 | if (CheckInit () < 0) |
398 | goto bail; |
399 | |
400 | /* release any currently playing file */ |
401 | if (ReleaseFile () < 0) |
402 | goto bail; |
403 | |
404 | #if DEBUG_CDROM |
405 | printf ("LoadFile: %d %d\n", startFrame, stopFrame); |
406 | #endif |
407 | |
408 | /*try {*/ |
409 | |
410 | /* create a new player, and attach to the audio unit */ |
411 | |
412 | thePlayer = new_AudioFilePlayer(ref); |
413 | if (thePlayer == NULL) { |
414 | SDL_SetError ("LoadFile: Could not create player"); |
415 | return -3; /*throw (-3);*/ |
416 | } |
417 | |
418 | if (!thePlayer->SetDestination(thePlayer, &theUnit)) |
419 | goto bail; |
420 | |
421 | if (startFrame >= 0) |
422 | thePlayer->SetStartFrame (thePlayer, startFrame); |
423 | |
424 | if (stopFrame >= 0 && stopFrame > startFrame) |
425 | thePlayer->SetStopFrame (thePlayer, stopFrame); |
426 | |
427 | /* we set the notifier later */ |
428 | /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/ |
429 | |
430 | if (!thePlayer->Connect(thePlayer)) |
431 | goto bail; |
432 | |
433 | #if DEBUG_CDROM |
434 | thePlayer->Print(thePlayer); |
435 | fflush (stdout); |
436 | #endif |
437 | /*} |
438 | catch (...) |
439 | { |
440 | goto bail; |
441 | }*/ |
442 | |
443 | error = 0; |
444 | |
445 | bail: |
446 | return error; |
447 | } |
448 | |
449 | int ReleaseFile () |
450 | { |
451 | int error = -1; |
452 | |
453 | /* (Don't see any way that the original C++ code could throw here.) --ryan. */ |
454 | /*try {*/ |
455 | if (thePlayer != NULL) { |
456 | |
457 | thePlayer->Disconnect(thePlayer); |
458 | |
459 | delete_AudioFilePlayer(thePlayer); |
460 | |
461 | thePlayer = NULL; |
462 | } |
463 | /*} |
464 | catch (...) |
465 | { |
466 | goto bail; |
467 | }*/ |
468 | |
469 | error = 0; |
470 | |
471 | /* bail: */ |
472 | return error; |
473 | } |
474 | |
475 | int PlayFile () |
476 | { |
477 | OSStatus result = -1; |
478 | |
479 | if (CheckInit () < 0) |
480 | goto bail; |
481 | |
482 | /*try {*/ |
483 | |
484 | // start processing of the audio unit |
485 | result = AudioOutputUnitStart (theUnit); |
486 | if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart") |
487 | |
488 | /*} |
489 | catch (...) |
490 | { |
491 | goto bail; |
492 | }*/ |
493 | |
494 | result = 0; |
495 | |
496 | bail: |
497 | return result; |
498 | } |
499 | |
500 | int PauseFile () |
501 | { |
502 | OSStatus result = -1; |
503 | |
504 | if (CheckInit () < 0) |
505 | goto bail; |
506 | |
507 | /*try {*/ |
508 | |
509 | /* stop processing the audio unit */ |
510 | result = AudioOutputUnitStop (theUnit); |
511 | if (result) goto bail; /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/ |
512 | /*} |
513 | catch (...) |
514 | { |
515 | goto bail; |
516 | }*/ |
517 | |
518 | result = 0; |
519 | bail: |
520 | return result; |
521 | } |
522 | |
523 | void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom) |
524 | { |
525 | assert(thePlayer != NULL); |
526 | |
527 | theCDROM = cdrom; |
528 | completionProc = proc; |
529 | thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom); |
530 | } |
531 | |
532 | int GetCurrentFrame () |
533 | { |
534 | int frame; |
535 | |
536 | if (thePlayer == NULL) |
537 | frame = 0; |
538 | else |
539 | frame = thePlayer->GetCurrentFrame (thePlayer); |
540 | |
541 | return frame; |
542 | } |
543 | |
544 | |
545 | #pragma mark -- Private Functions -- |
546 | |
547 | static OSStatus CheckInit () |
548 | { |
549 | if (playBackWasInit) |
550 | return 0; |
551 | |
552 | OSStatus result = noErr; |
553 | |
554 | /* Create the callback semaphore */ |
555 | callbackSem = SDL_CreateSemaphore(0); |
556 | |
557 | /* Start callback thread */ |
558 | SDL_CreateThread(RunCallBackThread, NULL); |
559 | |
560 | { /*try {*/ |
561 | ComponentDescription desc; |
562 | |
563 | desc.componentType = kAudioUnitType_Output; |
564 | desc.componentSubType = kAudioUnitSubType_DefaultOutput; |
565 | desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
566 | desc.componentFlags = 0; |
567 | desc.componentFlagsMask = 0; |
568 | |
569 | Component comp = FindNextComponent (NULL, &desc); |
570 | if (comp == NULL) { |
571 | SDL_SetError ("CheckInit: FindNextComponent returned NULL"); |
572 | if (result) return -1; //throw(internalComponentErr); |
573 | } |
574 | |
575 | result = OpenAComponent (comp, &theUnit); |
576 | if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent") |
577 | |
578 | // you need to initialize the output unit before you set it as a destination |
579 | result = AudioUnitInitialize (theUnit); |
580 | if (result) return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize") |
581 | |
582 | |
583 | playBackWasInit = true; |
584 | } |
585 | /*catch (...) |
586 | { |
587 | return -1; |
588 | }*/ |
589 | |
590 | return 0; |
591 | } |
592 | |
593 | static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus) |
594 | { |
595 | if (inStatus == kAudioFilePlay_FileIsFinished) { |
596 | |
597 | /* notify non-CA thread to perform the callback */ |
598 | SDL_SemPost(callbackSem); |
599 | |
600 | } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) { |
601 | |
602 | SDL_SetError ("CDPlayer Notification: buffer underrun"); |
603 | } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) { |
604 | |
605 | SDL_SetError ("CDPlayer Notification: player is uninitialized"); |
606 | } else { |
607 | |
608 | SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus); |
609 | } |
610 | } |
611 | |
612 | static int RunCallBackThread (void *param) |
613 | { |
614 | for (;;) { |
615 | |
616 | SDL_SemWait(callbackSem); |
617 | |
618 | if (completionProc && theCDROM) { |
619 | #if DEBUG_CDROM |
620 | printf ("callback!\n"); |
621 | #endif |
622 | (*completionProc)(theCDROM); |
623 | } else { |
624 | #if DEBUG_CDROM |
625 | printf ("callback?\n"); |
626 | #endif |
627 | } |
628 | } |
629 | |
630 | #if DEBUG_CDROM |
631 | printf ("thread dying now...\n"); |
632 | #endif |
633 | |
634 | return 0; |
635 | } |
636 | |
637 | /*}; // extern "C" */ |