src/cdrom/macosx/CDPlayer.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 28 May 2006 13:04:16 +0000
branchSDL-1.3
changeset 1662 782fd950bd46
parent 1487 dc6b59e925a2
child 1668 4da1ee79c9af
permissions -rw-r--r--
Revamp of the video system in progress - adding support for multiple displays, multiple windows, and a full video mode selection API. WARNING: None of the video drivers have been updated for the new API yet! The API is still under design and very fluid. The code is now run through a consistent indent format: indent -i4 -nut -nsc -br -ce The headers are being converted to automatically generate doxygen documentation.

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002  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: */