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: */