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