2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997-2009 Sam Lantinga
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.
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.
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
22 #include "SDL_config.h"
25 #include "AudioFilePlayer.h"
26 #include "SDLOSXCAGuard.h"
28 /* we're exporting these functions into C land for SDL_syscdrom.c */
31 /*///////////////////////////////////////////////////////////////////////////
33 //////////////////////////////////////////////////////////////////////////*/
35 #define kAudioCDFilesystemID (UInt16)(('J' << 8) | 'H') /* 'JH'; this avoids compiler warning */
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"
50 /*///////////////////////////////////////////////////////////////////////////
52 //////////////////////////////////////////////////////////////////////////*/
54 #pragma mark -- Globals --
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;
64 /*///////////////////////////////////////////////////////////////////////////
66 //////////////////////////////////////////////////////////////////////////*/
68 #pragma mark -- Prototypes --
70 static OSStatus CheckInit ();
72 static void FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus);
74 static int RunCallBackThread (void* inRefCon);
77 #pragma mark -- Public Functions --
82 apiMutex = SDL_CreateMutex();
92 int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes)
95 int cdVolumeCount = 0;
96 OSStatus result = noErr;
98 for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++)
100 FSVolumeRefNum actualVolume;
101 FSVolumeInfo volumeInfo;
103 memset (&volumeInfo, 0, sizeof(volumeInfo));
105 result = FSGetVolumeInfo (kFSInvalidVolumeRefNum,
115 if (volumeInfo.filesystemID == kAudioCDFilesystemID) /* It's an audio CD */
117 if (volumes != NULL && cdVolumeCount < numVolumes)
118 volumes[cdVolumeCount] = actualVolume;
125 /* I'm commenting this out because it seems to be harmless */
126 /*SDL_SetError ("DetectAudioCDVolumes: FSGetVolumeInfo returned %d", result);*/
130 return cdVolumeCount;
133 int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD)
135 HFSUniStr255 dataForkName;
137 FSIORefNum forkRefNum;
140 ByteCount actualRead;
141 CFDataRef dataRef = 0;
142 CFPropertyListRef propertyListRef = 0;
147 const char* error = "Unspecified Error";
148 const UniChar uniName[] = { '.','T','O','C','.','p','l','i','s','t' };
150 theErr = FSGetVolumeInfo(theVolume, 0, 0, kFSVolInfoNone, 0, 0, &rootRef);
151 if(theErr != noErr) {
152 error = "FSGetVolumeInfo";
156 SDL_memset(&fsRefPB, '\0', sizeof (fsRefPB));
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;
165 theErr = PBMakeFSRefUnicodeSync (&fsRefPB);
166 if(theErr != noErr) {
167 error = "PBMakeFSRefUnicodeSync";
171 /* Load and parse the TOC XML data */
173 theErr = FSGetDataForkName (&dataForkName);
174 if (theErr != noErr) {
175 error = "FSGetDataForkName";
179 theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum);
180 if (theErr != noErr) {
181 error = "FSOpenFork";
185 theErr = FSGetForkSize (forkRefNum, &forkSize);
186 if (theErr != noErr) {
187 error = "FSGetForkSize";
191 /* Allocate some memory for the XML data */
192 forkData = NewPtr (forkSize);
193 if(forkData == NULL) {
198 theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead);
199 if(theErr != noErr) {
200 error = "FSReadFork";
204 dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize);
206 error = "CFDataCreate";
210 propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault,
212 kCFPropertyListImmutable,
214 if (propertyListRef == NULL) {
215 error = "CFPropertyListCreateFromXMLData";
219 /* Now we got the Property List in memory. Parse it. */
221 /* First, make sure the root item is a CFDictionary. If not, release and bail. */
222 if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID())
224 CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef;
226 CFDataRef theRawTOCDataRef;
227 CFArrayRef theSessionArrayRef;
231 /* This is how we get the Raw TOC Data */
232 theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString));
234 /* Get the session array info. */
235 theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString));
237 /* Find out how many sessions there are. */
238 numSessions = CFArrayGetCount (theSessionArrayRef);
240 /* Initialize the total number of tracks to 0 */
241 theCD->numtracks = 0;
243 /* Iterate over all sessions, collecting the track data */
244 for(index = 0; index < numSessions; index++)
246 CFDictionaryRef theSessionDict;
247 CFNumberRef leadoutBlock;
248 CFArrayRef trackArray;
253 theSessionDict = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index);
254 leadoutBlock = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString));
256 trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString));
258 numTracks = CFArrayGetCount (trackArray);
260 for(trackIndex = 0; trackIndex < numTracks; trackIndex++) {
262 CFDictionaryRef theTrackDict;
263 CFNumberRef trackNumber;
264 CFNumberRef sessionNumber;
265 CFNumberRef startBlock;
266 CFBooleanRef isDataTrack;
269 theTrackDict = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex);
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));
276 /* Fill in the SDL_CD struct */
277 int idx = theCD->numtracks++;
279 CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value);
280 theCD->track[idx].id = value;
282 CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value);
283 theCD->track[idx].offset = value;
285 theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK;
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;
293 /* Compute the length of the last track */
294 CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value);
296 theCD->track[theCD->numtracks-1].length =
297 value - theCD->track[theCD->numtracks-1].offset;
299 /* Set offset to leadout track */
300 theCD->track[theCD->numtracks].offset = value;
308 SDL_SetError ("ReadTOCData: %s returned %d", error, theErr);
312 if (propertyListRef != NULL)
313 CFRelease(propertyListRef);
316 if (forkData != NULL)
317 DisposePtr(forkData);
319 FSCloseFork (forkRefNum);
324 int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks)
326 OSStatus result = -1;
328 ItemCount actualObjects;
331 HFSUniStr255 nameStr;
333 result = FSGetVolumeInfo (theVolume,
341 if (result != noErr) {
342 SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result);
346 result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator);
347 if (result == noErr) {
350 result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects,
351 NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr);
352 if (result == noErr) {
355 name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length);
357 /* Look for .aiff extension */
358 if (CFStringHasSuffix (name, CFSTR(".aiff")) ||
359 CFStringHasSuffix (name, CFSTR(".cdda"))) {
361 /* Extract the track id from the filename */
362 int trackID = 0, i = 0;
363 while (i < nameStr.length && !isdigit(nameStr.unicode[i])) {
366 while (i < nameStr.length && isdigit(nameStr.unicode[i])) {
367 trackID = 10 * trackID +(nameStr.unicode[i] - '0');
372 printf("Found AIFF for track %d: '%s'\n", trackID,
373 CFStringGetCStringPtr (name, CFStringGetSystemEncoding()));
376 /* Track ID's start at 1, but we want to start at 0 */
379 assert(0 <= trackID && trackID <= SDL_MAX_TRACKS);
381 if (trackID < numTracks)
382 memcpy (&trackFiles[trackID], &ref, sizeof(FSRef));
386 } while(noErr == result);
387 FSCloseIterator (iterator);
393 int LoadFile (const FSRef *ref, int startFrame, int stopFrame)
397 if (CheckInit () < 0)
400 /* release any currently playing file */
401 if (ReleaseFile () < 0)
405 printf ("LoadFile: %d %d\n", startFrame, stopFrame);
410 /* create a new player, and attach to the audio unit */
412 thePlayer = new_AudioFilePlayer(ref);
413 if (thePlayer == NULL) {
414 SDL_SetError ("LoadFile: Could not create player");
415 return -3; /*throw (-3);*/
418 if (!thePlayer->SetDestination(thePlayer, &theUnit))
422 thePlayer->SetStartFrame (thePlayer, startFrame);
424 if (stopFrame >= 0 && stopFrame > startFrame)
425 thePlayer->SetStopFrame (thePlayer, stopFrame);
427 /* we set the notifier later */
428 /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/
430 if (!thePlayer->Connect(thePlayer))
434 thePlayer->Print(thePlayer);
453 /* (Don't see any way that the original C++ code could throw here.) --ryan. */
455 if (thePlayer != NULL) {
457 thePlayer->Disconnect(thePlayer);
459 delete_AudioFilePlayer(thePlayer);
477 OSStatus result = -1;
479 if (CheckInit () < 0)
484 // start processing of the audio unit
485 result = AudioOutputUnitStart (theUnit);
486 if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart")
502 OSStatus result = -1;
504 if (CheckInit () < 0)
509 /* stop processing the audio unit */
510 result = AudioOutputUnitStop (theUnit);
511 if (result) goto bail; /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/
523 void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom)
525 assert(thePlayer != NULL);
528 completionProc = proc;
529 thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom);
532 int GetCurrentFrame ()
536 if (thePlayer == NULL)
539 frame = thePlayer->GetCurrentFrame (thePlayer);
545 #pragma mark -- Private Functions --
547 static OSStatus CheckInit ()
552 OSStatus result = noErr;
554 /* Create the callback semaphore */
555 callbackSem = SDL_CreateSemaphore(0);
557 /* Start callback thread */
558 SDL_CreateThread(RunCallBackThread, NULL);
561 ComponentDescription desc;
563 desc.componentType = kAudioUnitType_Output;
564 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
565 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
566 desc.componentFlags = 0;
567 desc.componentFlagsMask = 0;
569 Component comp = FindNextComponent (NULL, &desc);
571 SDL_SetError ("CheckInit: FindNextComponent returned NULL");
572 if (result) return -1; //throw(internalComponentErr);
575 result = OpenAComponent (comp, &theUnit);
576 if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent")
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")
583 playBackWasInit = true;
593 static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus)
595 if (inStatus == kAudioFilePlay_FileIsFinished) {
597 /* notify non-CA thread to perform the callback */
598 SDL_SemPost(callbackSem);
600 } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) {
602 SDL_SetError ("CDPlayer Notification: buffer underrun");
603 } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) {
605 SDL_SetError ("CDPlayer Notification: player is uninitialized");
608 SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus);
612 static int RunCallBackThread (void *param)
616 SDL_SemWait(callbackSem);
618 if (completionProc && theCDROM) {
620 printf ("callback!\n");
622 (*completionProc)(theCDROM);
625 printf ("callback?\n");
631 printf ("thread dying now...\n");
637 /*}; // extern "C" */