view src/cdrom/macosx/SDL_syscdrom.c @ 2271:60b4c52a7906

Ported PulseAudio target from 1.2 to 1.3 interfaces, and added it to the trunk. Fixes Bugzilla #439.
author Ryan C. Gordon <icculus@icculus.org>
date Mon, 20 Aug 2007 01:02:37 +0000
parents c121d94672cb
children 99210400e8b9
line wrap: on
line source

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2006 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

#ifdef SDL_CDROM_MACOSX

#include "SDL_syscdrom_c.h"

#pragma mark -- Globals --

static FSRef **tracks;
static FSVolumeRefNum *volumes;
static CDstatus status;
static int nextTrackFrame;
static int nextTrackFramesRemaining;
static int fakeCD;
static int currentTrack;
static int didReadTOC;
static int cacheTOCNumTracks;
static int currentDrive;        /* Only allow 1 drive in use at a time */

#pragma mark -- Prototypes --

static const char *SDL_SYS_CDName(int drive);
static int SDL_SYS_CDOpen(int drive);
static int SDL_SYS_CDGetTOC(SDL_CD * cdrom);
static CDstatus SDL_SYS_CDStatus(SDL_CD * cdrom, int *position);
static int SDL_SYS_CDPlay(SDL_CD * cdrom, int start, int length);
static int SDL_SYS_CDPause(SDL_CD * cdrom);
static int SDL_SYS_CDResume(SDL_CD * cdrom);
static int SDL_SYS_CDStop(SDL_CD * cdrom);
static int SDL_SYS_CDEject(SDL_CD * cdrom);
static void SDL_SYS_CDClose(SDL_CD * cdrom);

#pragma mark -- Helper Functions --

/* Read a list of tracks from the volume */
static int
LoadTracks(SDL_CD * cdrom)
{
    /* Check if tracks are already loaded */
    if (tracks[cdrom->id] != NULL)
        return 0;

    /* Allocate memory for tracks */
    tracks[cdrom->id] =
        (FSRef *) SDL_calloc(1, sizeof(**tracks) * cdrom->numtracks);
    if (tracks[cdrom->id] == NULL) {
        SDL_OutOfMemory();
        return -1;
    }

    /* Load tracks */
    if (ListTrackFiles
        (volumes[cdrom->id], tracks[cdrom->id], cdrom->numtracks) < 0)
        return -1;

    return 0;
}

/* Find a file for a given start frame and length */
static FSRef *
GetFileForOffset(SDL_CD * cdrom, int start, int length, int *outStartFrame,
                 int *outStopFrame)
{
    int i;

    for (i = 0; i < cdrom->numtracks; i++) {

        if (cdrom->track[i].offset <= start &&
            start < (cdrom->track[i].offset + cdrom->track[i].length))
            break;
    }

    if (i == cdrom->numtracks)
        return NULL;

    currentTrack = i;

    *outStartFrame = start - cdrom->track[i].offset;

    if ((*outStartFrame + length) < cdrom->track[i].length) {
        *outStopFrame = *outStartFrame + length;
        length = 0;
        nextTrackFrame = -1;
        nextTrackFramesRemaining = -1;
    } else {
        *outStopFrame = -1;
        length -= cdrom->track[i].length - *outStartFrame;
        nextTrackFrame = cdrom->track[i + 1].offset;
        nextTrackFramesRemaining = length;
    }

    return &tracks[cdrom->id][i];
}

/* Setup another file for playback, or stop playback (called from another thread) */
static void
CompletionProc(SDL_CD * cdrom)
{

    Lock();

    if (nextTrackFrame > 0 && nextTrackFramesRemaining > 0) {

        /* Load the next file to play */
        int startFrame, stopFrame;
        FSRef *file;

        PauseFile();
        ReleaseFile();

        file = GetFileForOffset(cdrom, nextTrackFrame,
                                nextTrackFramesRemaining, &startFrame,
                                &stopFrame);

        if (file == NULL) {
            status = CD_STOPPED;
            Unlock();
            return;
        }

        LoadFile(file, startFrame, stopFrame);

        SetCompletionProc(CompletionProc, cdrom);

        PlayFile();
    } else {

        /* Release the current file */
        PauseFile();
        ReleaseFile();
        status = CD_STOPPED;
    }

    Unlock();
}


#pragma mark -- Driver Functions --

/* Initialize */
int
SDL_SYS_CDInit(void)
{
    /* Initialize globals */
    volumes = NULL;
    tracks = NULL;
    status = CD_STOPPED;
    nextTrackFrame = -1;
    nextTrackFramesRemaining = -1;
    fakeCD = SDL_FALSE;
    currentTrack = -1;
    didReadTOC = SDL_FALSE;
    cacheTOCNumTracks = -1;
    currentDrive = -1;

    /* Fill in function pointers */
    SDL_CDcaps.Name = SDL_SYS_CDName;
    SDL_CDcaps.Open = SDL_SYS_CDOpen;
    SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC;
    SDL_CDcaps.Status = SDL_SYS_CDStatus;
    SDL_CDcaps.Play = SDL_SYS_CDPlay;
    SDL_CDcaps.Pause = SDL_SYS_CDPause;
    SDL_CDcaps.Resume = SDL_SYS_CDResume;
    SDL_CDcaps.Stop = SDL_SYS_CDStop;
    SDL_CDcaps.Eject = SDL_SYS_CDEject;
    SDL_CDcaps.Close = SDL_SYS_CDClose;

    /* 
       Read the list of "drives"

       This is currently a hack that infers drives from
       mounted audio CD volumes, rather than
       actual CD-ROM devices - which means it may not
       act as expected sometimes.
     */

    /* Find out how many cd volumes are mounted */
    SDL_numcds = DetectAudioCDVolumes(NULL, 0);

    /*
       If there are no volumes, fake a cd device
       so tray empty can be reported.
     */
    if (SDL_numcds == 0) {

        fakeCD = SDL_TRUE;
        SDL_numcds = 1;
        status = CD_TRAYEMPTY;

        return 0;
    }

    /* Allocate space for volumes */
    volumes = (FSVolumeRefNum *) SDL_calloc(1, sizeof(*volumes) * SDL_numcds);
    if (volumes == NULL) {
        SDL_OutOfMemory();
        return -1;
    }

    /* Allocate space for tracks */
    tracks = (FSRef **) SDL_calloc(1, sizeof(*tracks) * (SDL_numcds + 1));
    if (tracks == NULL) {
        SDL_OutOfMemory();
        return -1;
    }

    /* Mark the end of the tracks array */
    tracks[SDL_numcds] = (FSRef *) - 1;

    /* 
       Redetect, now save all volumes for later
       Update SDL_numcds just in case it changed
     */
    {
        int numVolumes = SDL_numcds;

        SDL_numcds = DetectAudioCDVolumes(volumes, numVolumes);

        /* If more cds suddenly show up, ignore them */
        if (SDL_numcds > numVolumes) {
            SDL_SetError("Some CD's were added but they will be ignored");
            SDL_numcds = numVolumes;
        }
    }

    return 0;
}

/* Shutdown and cleanup */
void
SDL_SYS_CDQuit(void)
{
    ReleaseFile();

    if (volumes != NULL)
        free(volumes);

    if (tracks != NULL) {

        FSRef **ptr;
        for (ptr = tracks; *ptr != (FSRef *) - 1; ptr++)
            if (*ptr != NULL)
                free(*ptr);

        free(tracks);
    }
}

/* Get the Unix disk name of the volume */
static const char *
SDL_SYS_CDName(int drive)
{
    OSStatus err = noErr;
    HParamBlockRec pb;
    GetVolParmsInfoBuffer volParmsInfo;

    if (fakeCD)
        return "Fake CD-ROM Device";

    pb.ioParam.ioNamePtr = NULL;
    pb.ioParam.ioVRefNum = volumes[drive];
    pb.ioParam.ioBuffer = (Ptr) & volParmsInfo;
    pb.ioParam.ioReqCount = (SInt32) sizeof(volParmsInfo);
    err = PBHGetVolParmsSync(&pb);

    if (err != noErr) {
        SDL_SetError("PBHGetVolParmsSync returned %d", err);
        return NULL;
    }

    return volParmsInfo.vMDeviceID;
}

/* Open the "device" */
static int
SDL_SYS_CDOpen(int drive)
{
    /* Only allow 1 device to be open */
    if (currentDrive >= 0) {
        SDL_SetError("Only one cdrom is supported");
        return -1;
    } else
        currentDrive = drive;

    return drive;
}

/* Get the table of contents */
static int
SDL_SYS_CDGetTOC(SDL_CD * cdrom)
{
    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    if (didReadTOC) {
        cdrom->numtracks = cacheTOCNumTracks;
        return 0;
    }


    ReadTOCData(volumes[cdrom->id], cdrom);
    didReadTOC = SDL_TRUE;
    cacheTOCNumTracks = cdrom->numtracks;

    return 0;
}

/* Get CD-ROM status */
static CDstatus
SDL_SYS_CDStatus(SDL_CD * cdrom, int *position)
{
    if (position) {
        int trackFrame;

        Lock();
        trackFrame = GetCurrentFrame();
        Unlock();

        *position = cdrom->track[currentTrack].offset + trackFrame;
    }

    return status;
}

/* Start playback */
static int
SDL_SYS_CDPlay(SDL_CD * cdrom, int start, int length)
{
    int startFrame, stopFrame;
    FSRef *ref;

    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    Lock();

    if (LoadTracks(cdrom) < 0)
        return -2;

    if (PauseFile() < 0)
        return -3;

    if (ReleaseFile() < 0)
        return -4;

    ref = GetFileForOffset(cdrom, start, length, &startFrame, &stopFrame);
    if (ref == NULL) {
        SDL_SetError("SDL_SYS_CDPlay: No file for start=%d, length=%d",
                     start, length);
        return -5;
    }

    if (LoadFile(ref, startFrame, stopFrame) < 0)
        return -6;

    SetCompletionProc(CompletionProc, cdrom);

    if (PlayFile() < 0)
        return -7;

    status = CD_PLAYING;

    Unlock();

    return 0;
}

/* Pause playback */
static int
SDL_SYS_CDPause(SDL_CD * cdrom)
{
    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    Lock();

    if (PauseFile() < 0) {
        Unlock();
        return -2;
    }

    status = CD_PAUSED;

    Unlock();

    return 0;
}

/* Resume playback */
static int
SDL_SYS_CDResume(SDL_CD * cdrom)
{
    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    Lock();

    if (PlayFile() < 0) {
        Unlock();
        return -2;
    }

    status = CD_PLAYING;

    Unlock();

    return 0;
}

/* Stop playback */
static int
SDL_SYS_CDStop(SDL_CD * cdrom)
{
    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    Lock();

    if (PauseFile() < 0) {
        Unlock();
        return -2;
    }

    if (ReleaseFile() < 0) {
        Unlock();
        return -3;
    }

    status = CD_STOPPED;

    Unlock();

    return 0;
}

/* Eject the CD-ROM (Unmount the volume) */
static int
SDL_SYS_CDEject(SDL_CD * cdrom)
{
    OSStatus err;
    pid_t dissenter;

    if (fakeCD) {
        SDL_SetError(kErrorFakeDevice);
        return -1;
    }

    Lock();

    if (PauseFile() < 0) {
        Unlock();
        return -2;
    }

    if (ReleaseFile() < 0) {
        Unlock();
        return -3;
    }

    status = CD_STOPPED;

    /* Eject the volume */
    err = FSEjectVolumeSync(volumes[cdrom->id], kNilOptions, &dissenter);

    if (err != noErr) {
        Unlock();
        SDL_SetError("PBUnmountVol returned %d", err);
        return -4;
    }

    status = CD_TRAYEMPTY;

    /* Invalidate volume and track info */
    volumes[cdrom->id] = 0;
    free(tracks[cdrom->id]);
    tracks[cdrom->id] = NULL;

    Unlock();

    return 0;
}

/* Close the CD-ROM */
static void
SDL_SYS_CDClose(SDL_CD * cdrom)
{
    currentDrive = -1;
    return;
}

#endif /* SDL_CDROM_MACOSX */
/* vi: set ts=4 sw=4 expandtab: */