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 (at) 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 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 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 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 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 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 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 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 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 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 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 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