| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997-2009 Sam Lantinga |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Library General Public |
| License as published by the Free Software Foundation; either |
| version 2 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Library General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public |
| License along with this library; if not, write to the Free |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| Sam Lantinga |
| slouken@libsdl.org |
| */ |
| #include "SDL_config.h" |
| |
| #include "CDPlayer.h" |
| #include "AudioFilePlayer.h" |
| #include "SDLOSXCAGuard.h" |
| |
| /* we're exporting these functions into C land for SDL_syscdrom.c */ |
| /*extern "C" {*/ |
| |
| /*/////////////////////////////////////////////////////////////////////////// |
| Constants |
| //////////////////////////////////////////////////////////////////////////*/ |
| |
| #define kAudioCDFilesystemID (UInt16)(('J' << 8) | 'H') /* 'JH'; this avoids compiler warning */ |
| |
| /* XML PList keys */ |
| #define kRawTOCDataString "Format 0x02 TOC Data" |
| #define kSessionsString "Sessions" |
| #define kSessionTypeString "Session Type" |
| #define kTrackArrayString "Track Array" |
| #define kFirstTrackInSessionString "First Track" |
| #define kLastTrackInSessionString "Last Track" |
| #define kLeadoutBlockString "Leadout Block" |
| #define kDataKeyString "Data" |
| #define kPointKeyString "Point" |
| #define kSessionNumberKeyString "Session Number" |
| #define kStartBlockKeyString "Start Block" |
| |
| /*/////////////////////////////////////////////////////////////////////////// |
| Globals |
| //////////////////////////////////////////////////////////////////////////*/ |
| |
| #pragma mark -- Globals -- |
| |
| static int playBackWasInit = 0; |
| static AudioUnit theUnit; |
| static AudioFilePlayer *thePlayer = NULL; |
| static CDPlayerCompletionProc completionProc = NULL; |
| static SDL_mutex *apiMutex = NULL; |
| static SDL_sem *callbackSem; |
| static SDL_CD *theCDROM; |
| |
| /*/////////////////////////////////////////////////////////////////////////// |
| Prototypes |
| //////////////////////////////////////////////////////////////////////////*/ |
| |
| #pragma mark -- Prototypes -- |
| |
| static OSStatus CheckInit(); |
| |
| static void FilePlayNotificationHandler(void *inRefCon, OSStatus inStatus); |
| |
| static int RunCallBackThread(void *inRefCon); |
| |
| |
| #pragma mark -- Public Functions -- |
| |
| void |
| Lock() |
| { |
| if (!apiMutex) { |
| apiMutex = SDL_CreateMutex(); |
| } |
| SDL_mutexP(apiMutex); |
| } |
| |
| void |
| Unlock() |
| { |
| SDL_mutexV(apiMutex); |
| } |
| |
| int |
| DetectAudioCDVolumes(FSVolumeRefNum * volumes, int numVolumes) |
| { |
| int volumeIndex; |
| int cdVolumeCount = 0; |
| OSStatus result = noErr; |
| |
| for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++) { |
| FSVolumeRefNum actualVolume; |
| FSVolumeInfo volumeInfo; |
| |
| memset(&volumeInfo, 0, sizeof(volumeInfo)); |
| |
| result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, |
| volumeIndex, |
| &actualVolume, |
| kFSVolInfoFSInfo, &volumeInfo, NULL, NULL); |
| |
| if (result == noErr) { |
| if (volumeInfo.filesystemID == kAudioCDFilesystemID) { /* It's an audio CD */ |
| if (volumes != NULL && cdVolumeCount < numVolumes) |
| volumes[cdVolumeCount] = actualVolume; |
| |
| cdVolumeCount++; |
| } |
| } else { |
| /* I'm commenting this out because it seems to be harmless */ |
| /*SDL_SetError ("DetectAudioCDVolumes: FSGetVolumeInfo returned %d", result); */ |
| } |
| } |
| |
| return cdVolumeCount; |
| } |
| |
| int |
| ReadTOCData(FSVolumeRefNum theVolume, SDL_CD * theCD) |
| { |
| HFSUniStr255 dataForkName; |
| OSStatus theErr; |
| SInt16 forkRefNum; |
| SInt64 forkSize; |
| Ptr forkData = 0; |
| ByteCount actualRead; |
| CFDataRef dataRef = 0; |
| CFPropertyListRef propertyListRef = 0; |
| |
| FSRefParam fsRefPB; |
| FSRef tocPlistFSRef; |
| |
| const char *error = "Unspecified Error"; |
| |
| /* get stuff from .TOC.plist */ |
| fsRefPB.ioCompletion = NULL; |
| fsRefPB.ioNamePtr = "\p.TOC.plist"; |
| fsRefPB.ioVRefNum = theVolume; |
| fsRefPB.ioDirID = 0; |
| fsRefPB.newRef = &tocPlistFSRef; |
| |
| theErr = PBMakeFSRefSync(&fsRefPB); |
| if (theErr != noErr) { |
| error = "PBMakeFSRefSync"; |
| goto bail; |
| } |
| |
| /* Load and parse the TOC XML data */ |
| |
| theErr = FSGetDataForkName(&dataForkName); |
| if (theErr != noErr) { |
| error = "FSGetDataForkName"; |
| goto bail; |
| } |
| |
| theErr = |
| FSOpenFork(&tocPlistFSRef, dataForkName.length, dataForkName.unicode, |
| fsRdPerm, &forkRefNum); |
| if (theErr != noErr) { |
| error = "FSOpenFork"; |
| goto bail; |
| } |
| |
| theErr = FSGetForkSize(forkRefNum, &forkSize); |
| if (theErr != noErr) { |
| error = "FSGetForkSize"; |
| goto bail; |
| } |
| |
| /* Allocate some memory for the XML data */ |
| forkData = NewPtr(forkSize); |
| if (forkData == NULL) { |
| error = "NewPtr"; |
| goto bail; |
| } |
| |
| theErr = FSReadFork(forkRefNum, fsFromStart, 0 /* offset location */ , |
| forkSize, forkData, &actualRead); |
| if (theErr != noErr) { |
| error = "FSReadFork"; |
| goto bail; |
| } |
| |
| dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *) forkData, forkSize); |
| if (dataRef == 0) { |
| error = "CFDataCreate"; |
| goto bail; |
| } |
| |
| propertyListRef = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, |
| dataRef, |
| kCFPropertyListImmutable, |
| NULL); |
| if (propertyListRef == NULL) { |
| error = "CFPropertyListCreateFromXMLData"; |
| goto bail; |
| } |
| |
| /* Now we got the Property List in memory. Parse it. */ |
| |
| /* First, make sure the root item is a CFDictionary. If not, release and bail. */ |
| if (CFGetTypeID(propertyListRef) == CFDictionaryGetTypeID()) { |
| CFDictionaryRef dictRef = (CFDictionaryRef) propertyListRef; |
| |
| CFDataRef theRawTOCDataRef; |
| CFArrayRef theSessionArrayRef; |
| CFIndex numSessions; |
| CFIndex index; |
| |
| /* This is how we get the Raw TOC Data */ |
| theRawTOCDataRef = |
| (CFDataRef) CFDictionaryGetValue(dictRef, |
| CFSTR(kRawTOCDataString)); |
| |
| /* Get the session array info. */ |
| theSessionArrayRef = |
| (CFArrayRef) CFDictionaryGetValue(dictRef, |
| CFSTR(kSessionsString)); |
| |
| /* Find out how many sessions there are. */ |
| numSessions = CFArrayGetCount(theSessionArrayRef); |
| |
| /* Initialize the total number of tracks to 0 */ |
| theCD->numtracks = 0; |
| |
| /* Iterate over all sessions, collecting the track data */ |
| for (index = 0; index < numSessions; index++) { |
| CFDictionaryRef theSessionDict; |
| CFNumberRef leadoutBlock; |
| CFArrayRef trackArray; |
| CFIndex numTracks; |
| CFIndex trackIndex; |
| UInt32 value = 0; |
| |
| theSessionDict = (CFDictionaryRef) |
| CFArrayGetValueAtIndex(theSessionArrayRef, index); |
| leadoutBlock = |
| (CFNumberRef) CFDictionaryGetValue(theSessionDict, |
| CFSTR |
| (kLeadoutBlockString)); |
| |
| trackArray = |
| (CFArrayRef) CFDictionaryGetValue(theSessionDict, |
| CFSTR(kTrackArrayString)); |
| |
| numTracks = CFArrayGetCount(trackArray); |
| |
| for (trackIndex = 0; trackIndex < numTracks; trackIndex++) { |
| |
| CFDictionaryRef theTrackDict; |
| CFNumberRef trackNumber; |
| CFNumberRef sessionNumber; |
| CFNumberRef startBlock; |
| CFBooleanRef isDataTrack; |
| UInt32 value; |
| |
| theTrackDict = (CFDictionaryRef) |
| CFArrayGetValueAtIndex(trackArray, trackIndex); |
| |
| trackNumber = |
| (CFNumberRef) CFDictionaryGetValue(theTrackDict, |
| CFSTR |
| (kPointKeyString)); |
| sessionNumber = |
| (CFNumberRef) CFDictionaryGetValue(theTrackDict, |
| CFSTR |
| (kSessionNumberKeyString)); |
| startBlock = |
| (CFNumberRef) CFDictionaryGetValue(theTrackDict, |
| CFSTR |
| (kStartBlockKeyString)); |
| isDataTrack = |
| (CFBooleanRef) CFDictionaryGetValue(theTrackDict, |
| CFSTR |
| (kDataKeyString)); |
| |
| /* Fill in the SDL_CD struct */ |
| int idx = theCD->numtracks++; |
| |
| CFNumberGetValue(trackNumber, kCFNumberSInt32Type, &value); |
| theCD->track[idx].id = value; |
| |
| CFNumberGetValue(startBlock, kCFNumberSInt32Type, &value); |
| theCD->track[idx].offset = value; |
| |
| theCD->track[idx].type = |
| (isDataTrack == |
| kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK; |
| |
| /* Since the track lengths are not stored in .TOC.plist we compute them. */ |
| if (trackIndex > 0) { |
| theCD->track[idx - 1].length = |
| theCD->track[idx].offset - theCD->track[idx - |
| 1].offset; |
| } |
| } |
| |
| /* Compute the length of the last track */ |
| CFNumberGetValue(leadoutBlock, kCFNumberSInt32Type, &value); |
| |
| theCD->track[theCD->numtracks - 1].length = |
| value - theCD->track[theCD->numtracks - 1].offset; |
| |
| /* Set offset to leadout track */ |
| theCD->track[theCD->numtracks].offset = value; |
| } |
| |
| } |
| |
| theErr = 0; |
| goto cleanup; |
| bail: |
| SDL_SetError("ReadTOCData: %s returned %d", error, theErr); |
| theErr = -1; |
| cleanup: |
| |
| if (propertyListRef != NULL) |
| CFRelease(propertyListRef); |
| if (dataRef != NULL) |
| CFRelease(dataRef); |
| if (forkData != NULL) |
| DisposePtr(forkData); |
| |
| FSCloseFork(forkRefNum); |
| |
| return theErr; |
| } |
| |
| int |
| ListTrackFiles(FSVolumeRefNum theVolume, FSRef * trackFiles, int numTracks) |
| { |
| OSStatus result = -1; |
| FSIterator iterator; |
| ItemCount actualObjects; |
| FSRef rootDirectory; |
| FSRef ref; |
| HFSUniStr255 nameStr; |
| |
| result = FSGetVolumeInfo(theVolume, |
| 0, |
| NULL, |
| kFSVolInfoFSInfo, NULL, NULL, &rootDirectory); |
| |
| if (result != noErr) { |
| SDL_SetError("ListTrackFiles: FSGetVolumeInfo returned %d", result); |
| return result; |
| } |
| |
| result = FSOpenIterator(&rootDirectory, kFSIterateFlat, &iterator); |
| if (result == noErr) { |
| do { |
| result = FSGetCatalogInfoBulk(iterator, 1, &actualObjects, |
| NULL, kFSCatInfoNone, NULL, |
| &ref, NULL, &nameStr); |
| if (result == noErr) { |
| |
| CFStringRef name; |
| name = |
| CFStringCreateWithCharacters(NULL, nameStr.unicode, |
| nameStr.length); |
| |
| /* Look for .aiff extension */ |
| if (CFStringHasSuffix(name, CFSTR(".aiff")) || |
| CFStringHasSuffix(name, CFSTR(".cdda"))) { |
| |
| /* Extract the track id from the filename */ |
| int trackID = 0, i = 0; |
| while (i < nameStr.length && !isdigit(nameStr.unicode[i])) { |
| ++i; |
| } |
| while (i < nameStr.length && isdigit(nameStr.unicode[i])) { |
| trackID = 10 * trackID + (nameStr.unicode[i] - '0'); |
| ++i; |
| } |
| |
| #if DEBUG_CDROM |
| printf("Found AIFF for track %d: '%s'\n", |
| trackID, CFStringGetCStringPtr(name, |
| CFStringGetSystemEncoding |
| ())); |
| #endif |
| |
| /* Track ID's start at 1, but we want to start at 0 */ |
| trackID--; |
| |
| assert(0 <= trackID && trackID <= SDL_MAX_TRACKS); |
| |
| if (trackID < numTracks) |
| memcpy(&trackFiles[trackID], &ref, sizeof(FSRef)); |
| } |
| CFRelease(name); |
| } |
| } while (noErr == result); |
| FSCloseIterator(iterator); |
| } |
| |
| return 0; |
| } |
| |
| int |
| LoadFile(const FSRef * ref, int startFrame, int stopFrame) |
| { |
| int error = -1; |
| |
| if (CheckInit() < 0) |
| goto bail; |
| |
| /* release any currently playing file */ |
| if (ReleaseFile() < 0) |
| goto bail; |
| |
| #if DEBUG_CDROM |
| printf("LoadFile: %d %d\n", startFrame, stopFrame); |
| #endif |
| |
| /*try { */ |
| |
| /* create a new player, and attach to the audio unit */ |
| |
| thePlayer = new_AudioFilePlayer(ref); |
| if (thePlayer == NULL) { |
| SDL_SetError("LoadFile: Could not create player"); |
| return -3; /*throw (-3); */ |
| } |
| |
| if (!thePlayer->SetDestination(thePlayer, &theUnit)) |
| goto bail; |
| |
| if (startFrame >= 0) |
| thePlayer->SetStartFrame(thePlayer, startFrame); |
| |
| if (stopFrame >= 0 && stopFrame > startFrame) |
| thePlayer->SetStopFrame(thePlayer, stopFrame); |
| |
| /* we set the notifier later */ |
| /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL); */ |
| |
| if (!thePlayer->Connect(thePlayer)) |
| goto bail; |
| |
| #if DEBUG_CDROM |
| thePlayer->Print(thePlayer); |
| fflush(stdout); |
| #endif |
| /*} |
| catch (...) |
| { |
| goto bail; |
| } */ |
| |
| error = 0; |
| |
| bail: |
| return error; |
| } |
| |
| int |
| ReleaseFile() |
| { |
| int error = -1; |
| |
| /* (Don't see any way that the original C++ code could throw here.) --ryan. */ |
| /*try { */ |
| if (thePlayer != NULL) { |
| |
| thePlayer->Disconnect(thePlayer); |
| |
| delete_AudioFilePlayer(thePlayer); |
| |
| thePlayer = NULL; |
| } |
| /*} |
| catch (...) |
| { |
| goto bail; |
| } */ |
| |
| error = 0; |
| |
| /* bail: */ |
| return error; |
| } |
| |
| int |
| PlayFile() |
| { |
| OSStatus result = -1; |
| |
| if (CheckInit() < 0) |
| goto bail; |
| |
| /*try { */ |
| |
| // start processing of the audio unit |
| result = AudioOutputUnitStart(theUnit); |
| if (result) |
| goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart") |
| |
| /*} |
| catch (...) |
| { |
| goto bail; |
| } */ |
| |
| result = 0; |
| |
| bail: |
| return result; |
| } |
| |
| int |
| PauseFile() |
| { |
| OSStatus result = -1; |
| |
| if (CheckInit() < 0) |
| goto bail; |
| |
| /*try { */ |
| |
| /* stop processing the audio unit */ |
| result = AudioOutputUnitStop(theUnit); |
| if (result) |
| goto bail; /*THROW_RESULT("PauseFile: AudioOutputUnitStop") */ |
| /*} |
| catch (...) |
| { |
| goto bail; |
| } */ |
| |
| result = 0; |
| bail: |
| return result; |
| } |
| |
| void |
| SetCompletionProc(CDPlayerCompletionProc proc, SDL_CD * cdrom) |
| { |
| assert(thePlayer != NULL); |
| |
| theCDROM = cdrom; |
| completionProc = proc; |
| thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, cdrom); |
| } |
| |
| int |
| GetCurrentFrame() |
| { |
| int frame; |
| |
| if (thePlayer == NULL) |
| frame = 0; |
| else |
| frame = thePlayer->GetCurrentFrame(thePlayer); |
| |
| return frame; |
| } |
| |
| |
| #pragma mark -- Private Functions -- |
| |
| static OSStatus |
| CheckInit() |
| { |
| if (playBackWasInit) |
| return 0; |
| |
| OSStatus result = noErr; |
| |
| /* Create the callback semaphore */ |
| callbackSem = SDL_CreateSemaphore(0); |
| |
| /* Start callback thread */ |
| SDL_CreateThread(RunCallBackThread, NULL); |
| |
| { /*try { */ |
| ComponentDescription desc; |
| |
| desc.componentType = kAudioUnitComponentType; |
| desc.componentSubType = kAudioUnitSubType_Output; |
| desc.componentManufacturer = kAudioUnitID_DefaultOutput; |
| desc.componentFlags = 0; |
| desc.componentFlagsMask = 0; |
| |
| Component comp = FindNextComponent(NULL, &desc); |
| if (comp == NULL) { |
| SDL_SetError("CheckInit: FindNextComponent returned NULL"); |
| if (result) |
| return -1; //throw(internalComponentErr); |
| } |
| |
| result = OpenAComponent(comp, &theUnit); |
| if (result) |
| return -1; //THROW_RESULT("CheckInit: OpenAComponent") |
| |
| // you need to initialize the output unit before you set it as a destination |
| result = AudioUnitInitialize(theUnit); |
| if (result) |
| return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize") |
| |
| |
| playBackWasInit = true; |
| } |
| /*catch (...) |
| { |
| return -1; |
| } */ |
| |
| return 0; |
| } |
| |
| static void |
| FilePlayNotificationHandler(void *inRefCon, OSStatus inStatus) |
| { |
| if (inStatus == kAudioFilePlay_FileIsFinished) { |
| |
| /* notify non-CA thread to perform the callback */ |
| SDL_SemPost(callbackSem); |
| |
| } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) { |
| |
| SDL_SetError("CDPlayer Notification: buffer underrun"); |
| } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) { |
| |
| SDL_SetError("CDPlayer Notification: player is uninitialized"); |
| } else { |
| |
| SDL_SetError("CDPlayer Notification: unknown error %ld", inStatus); |
| } |
| } |
| |
| static int |
| RunCallBackThread(void *param) |
| { |
| for (;;) { |
| |
| SDL_SemWait(callbackSem); |
| |
| if (completionProc && theCDROM) { |
| #if DEBUG_CDROM |
| printf("callback!\n"); |
| #endif |
| (*completionProc) (theCDROM); |
| } else { |
| #if DEBUG_CDROM |
| printf("callback?\n"); |
| #endif |
| } |
| } |
| |
| #if DEBUG_CDROM |
| printf("thread dying now...\n"); |
| #endif |
| |
| return 0; |
| } |
| |
| /*}; // extern "C" */ |
| /* vi: set ts=4 sw=4 expandtab: */ |