SDL-1.2.14
[sdl_omap.git] / src / cdrom / macosx / CDPlayer.c
CommitLineData
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
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 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;
307bail:
308 SDL_SetError ("ReadTOCData: %s returned %d", error, theErr);
309 theErr = -1;
310cleanup:
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
324int 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
393int 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
449int 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
475int 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
496bail:
497 return result;
498}
499
500int 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;
519bail:
520 return result;
521}
522
523void 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
532int 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
547static 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
593static 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
612static 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" */