Added MacOS X CD-ROM audio support (thanks Max and Darrell)
--HG-- extra : convert_revision : svn%3Ac70aab31-4412-0410-b14c-859654838e24/trunk%40614
This commit is contained in:
parent
e44379a4b6
commit
c8ea4a77eb
12 changed files with 2287 additions and 5 deletions
|
@ -2404,8 +2404,9 @@ case "$target" in
|
||||||
fi
|
fi
|
||||||
# Set up files for the cdrom library
|
# Set up files for the cdrom library
|
||||||
if test x$enable_cdrom = xyes; then
|
if test x$enable_cdrom = xyes; then
|
||||||
CDROM_SUBDIRS="$CDROM_SUBDIRS dummy"
|
CDROM_SUBDIRS="$CDROM_SUBDIRS macosx"
|
||||||
CDROM_DRIVERS="$CDROM_DRIVERS dummy/libcdrom_dummy.la"
|
CDROM_DRIVERS="$CDROM_DRIVERS macosx/libcdrom_macosx.la"
|
||||||
|
SYSTEM_LIBS="$SYSTEM_LIBS -framework AudioToolbox -framework AudioUnit -lstdc++"
|
||||||
fi
|
fi
|
||||||
# Set up files for the thread library
|
# Set up files for the thread library
|
||||||
if test x$enable_threads = xyes; then
|
if test x$enable_threads = xyes; then
|
||||||
|
@ -2672,6 +2673,7 @@ src/cdrom/bsdi/Makefile
|
||||||
src/cdrom/freebsd/Makefile
|
src/cdrom/freebsd/Makefile
|
||||||
src/cdrom/linux/Makefile
|
src/cdrom/linux/Makefile
|
||||||
src/cdrom/macos/Makefile
|
src/cdrom/macos/Makefile
|
||||||
|
src/cdrom/macosx/Makefile
|
||||||
src/cdrom/openbsd/Makefile
|
src/cdrom/openbsd/Makefile
|
||||||
src/cdrom/qnx/Makefile
|
src/cdrom/qnx/Makefile
|
||||||
src/cdrom/win32/Makefile
|
src/cdrom/win32/Makefile
|
||||||
|
|
|
@ -5,7 +5,19 @@ noinst_LTLIBRARIES = libcdrom.la
|
||||||
|
|
||||||
# Define which subdirectories need to be built
|
# Define which subdirectories need to be built
|
||||||
SUBDIRS = @CDROM_SUBDIRS@
|
SUBDIRS = @CDROM_SUBDIRS@
|
||||||
DIST_SUBDIRS = aix beos bsdi dc dummy freebsd linux macos openbsd qnx win32
|
DIST_SUBDIRS = \
|
||||||
|
aix \
|
||||||
|
beos \
|
||||||
|
bsdi \
|
||||||
|
dc \
|
||||||
|
dummy \
|
||||||
|
freebsd \
|
||||||
|
linux \
|
||||||
|
macos \
|
||||||
|
macosx \
|
||||||
|
openbsd \
|
||||||
|
qnx \
|
||||||
|
win32
|
||||||
|
|
||||||
DRIVERS = @CDROM_DRIVERS@
|
DRIVERS = @CDROM_DRIVERS@
|
||||||
|
|
||||||
|
|
377
src/cdrom/macosx/AudioFilePlayer.cpp
Normal file
377
src/cdrom/macosx/AudioFilePlayer.cpp
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
This file based on Apple sample code. We haven't changed the file name,
|
||||||
|
so if you want to see the original search for it on apple.com/developer
|
||||||
|
*/
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// AudioFilePlayer.cpp
|
||||||
|
//
|
||||||
|
#include "AudioFilePlayer.h"
|
||||||
|
|
||||||
|
extern const char* AudioFilePlayerErrorStr (OSStatus error)
|
||||||
|
{
|
||||||
|
const char *str;
|
||||||
|
|
||||||
|
switch (error) {
|
||||||
|
case kAudioFileUnspecifiedError: str = "wht?"; break;
|
||||||
|
case kAudioFileUnsupportedFileTypeError: str = "typ?"; break;
|
||||||
|
case kAudioFileUnsupportedDataFormatError: str = "fmt?"; break;
|
||||||
|
case kAudioFileUnsupportedPropertyError: str = "pty?"; break;
|
||||||
|
case kAudioFileBadPropertySizeError: str = "!siz"; break;
|
||||||
|
case kAudioFileNotOptimizedError: str = "optm"; break;
|
||||||
|
case kAudioFilePermissionsError: str = "prm?"; break;
|
||||||
|
case kAudioFileFormatNameUnavailableError: str = "nme?"; break;
|
||||||
|
case kAudioFileInvalidChunkError: str = "chk?"; break;
|
||||||
|
case kAudioFileDoesNotAllow64BitDataSizeError: str = "off?"; break;
|
||||||
|
default: str = "error unspecified";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThrowResult (OSStatus result, const char* str)
|
||||||
|
{
|
||||||
|
SDL_SetError ("Error: %s %d (%s)",
|
||||||
|
str, result, AudioFilePlayerErrorStr(result));
|
||||||
|
throw result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
|
||||||
|
{
|
||||||
|
if (!inDesc) {
|
||||||
|
printf ("Can't print a NULL desc!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("- - - - - - - - - - - - - - - - - - - -\n");
|
||||||
|
printf (" Sample Rate:%f\n", inDesc->mSampleRate);
|
||||||
|
printf (" Format ID:%s\n", (char*)&inDesc->mFormatID);
|
||||||
|
printf (" Format Flags:%lX\n", inDesc->mFormatFlags);
|
||||||
|
printf (" Bytes per Packet:%ld\n", inDesc->mBytesPerPacket);
|
||||||
|
printf (" Frames per Packet:%ld\n", inDesc->mFramesPerPacket);
|
||||||
|
printf (" Bytes per Frame:%ld\n", inDesc->mBytesPerFrame);
|
||||||
|
printf (" Channels per Frame:%ld\n", inDesc->mChannelsPerFrame);
|
||||||
|
printf (" Bits per Channel:%ld\n", inDesc->mBitsPerChannel);
|
||||||
|
printf ("- - - - - - - - - - - - - - - - - - - -\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OSStatus AudioFileManager::FileInputProc (void *inRefCon,
|
||||||
|
AudioUnitRenderActionFlags inActionFlags,
|
||||||
|
const AudioTimeStamp *inTimeStamp,
|
||||||
|
UInt32 inBusNumber,
|
||||||
|
AudioBuffer *ioData)
|
||||||
|
{
|
||||||
|
AudioFileManager* THIS = (AudioFileManager*)inRefCon;
|
||||||
|
return THIS->Render(*ioData);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSStatus AudioFileManager::Render (AudioBuffer &ioData)
|
||||||
|
{
|
||||||
|
OSStatus result = AudioConverterFillBuffer(mParentConverter,
|
||||||
|
AudioFileManager::ACInputProc,
|
||||||
|
this,
|
||||||
|
&ioData.mDataByteSize,
|
||||||
|
ioData.mData);
|
||||||
|
if (result) {
|
||||||
|
SDL_SetError ("AudioConverterFillBuffer:%ld\n", result);
|
||||||
|
mParent.DoNotification (result);
|
||||||
|
} else {
|
||||||
|
mByteCounter += ioData.mDataByteSize / 2;
|
||||||
|
AfterRender();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSStatus AudioFileManager::ACInputProc (AudioConverterRef inAudioConverter,
|
||||||
|
UInt32* outDataSize,
|
||||||
|
void** outData,
|
||||||
|
void* inUserData)
|
||||||
|
{
|
||||||
|
AudioFileManager* THIS = (AudioFileManager*)inUserData;
|
||||||
|
return THIS->GetFileData(outData, outDataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFileManager::~AudioFileManager ()
|
||||||
|
{
|
||||||
|
if (mFileBuffer) {
|
||||||
|
free (mFileBuffer);
|
||||||
|
mFileBuffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFilePlayer::AudioFilePlayer (const FSRef *inFileRef)
|
||||||
|
: mConnected (false),
|
||||||
|
mAudioFileManager (0),
|
||||||
|
mConverter (0),
|
||||||
|
mNotifier (0),
|
||||||
|
mStartFrame (0)
|
||||||
|
{
|
||||||
|
SInt64 fileDataSize = 0;
|
||||||
|
|
||||||
|
OpenFile (inFileRef, fileDataSize);
|
||||||
|
|
||||||
|
// we want about a seconds worth of data for the buffer
|
||||||
|
int secsBytes = UInt32 (mFileDescription.mSampleRate * mFileDescription.mBytesPerFrame);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("File format:\n");
|
||||||
|
PrintStreamDesc (&mFileDescription);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//round to a 32K boundary
|
||||||
|
//if ((secsBytes & 0xFFFF8000) > (128 * 1024))
|
||||||
|
//secsBytes &= 0xFFFF8000;
|
||||||
|
//else
|
||||||
|
//secsBytes = (secsBytes + 0x7FFF) & 0xFFFF8000;
|
||||||
|
|
||||||
|
mAudioFileManager = new AudioFileReaderThread (*this,
|
||||||
|
mAudioFileID,
|
||||||
|
fileDataSize,
|
||||||
|
secsBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// you can put a rate scalar here to play the file faster or slower
|
||||||
|
// by multiplying the same rate by the desired factor
|
||||||
|
// eg fileSampleRate * 2 -> twice as fast
|
||||||
|
// before you create the AudioConverter
|
||||||
|
void AudioFilePlayer::SetDestination (AudioUnit &inDestUnit,
|
||||||
|
int inBusNumber)
|
||||||
|
{
|
||||||
|
if (mConnected) throw static_cast<OSStatus>(-1); //can't set dest if already engaged
|
||||||
|
|
||||||
|
mPlayUnit = inDestUnit;
|
||||||
|
mBusNumber = inBusNumber;
|
||||||
|
|
||||||
|
OSStatus result = noErr;
|
||||||
|
|
||||||
|
if (mConverter) {
|
||||||
|
result = AudioConverterDispose (mConverter);
|
||||||
|
THROW_RESULT("AudioConverterDispose")
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStreamBasicDescription destDesc;
|
||||||
|
UInt32 size = sizeof (destDesc);
|
||||||
|
result = AudioUnitGetProperty (inDestUnit,
|
||||||
|
kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Input,
|
||||||
|
inBusNumber,
|
||||||
|
&destDesc,
|
||||||
|
&size);
|
||||||
|
THROW_RESULT("AudioUnitGetProperty")
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Destination format:\n");
|
||||||
|
PrintStreamDesc (&destDesc);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//we can "down" cast a component instance to a component
|
||||||
|
ComponentDescription desc;
|
||||||
|
result = GetComponentInfo ((Component)inDestUnit, &desc, 0, 0, 0);
|
||||||
|
THROW_RESULT("GetComponentInfo")
|
||||||
|
|
||||||
|
// we're going to use this to know which convert routine to call
|
||||||
|
// a v1 audio unit will have a type of 'aunt'
|
||||||
|
// a v2 audio unit will have one of several different types.
|
||||||
|
mIsAUNTUnit = (desc.componentType == kAudioUnitComponentType);
|
||||||
|
|
||||||
|
if (!mIsAUNTUnit) {
|
||||||
|
result = badComponentInstance;
|
||||||
|
THROW_RESULT("BAD COMPONENT")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HACK - the AIFF files on CDs are in little endian order!
|
||||||
|
if (mFileDescription.mFormatFlags == 0xE)
|
||||||
|
mFileDescription.mFormatFlags &= ~kAudioFormatFlagIsBigEndian;
|
||||||
|
|
||||||
|
|
||||||
|
result = AudioConverterNew (&mFileDescription, &destDesc, &mConverter);
|
||||||
|
THROW_RESULT("AudioConverterNew")
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// if we have a mono source, we're going to copy each channel into
|
||||||
|
// the destination's channel source...
|
||||||
|
if (mFileDescription.mChannelsPerFrame == 1) {
|
||||||
|
|
||||||
|
SInt32* channelMap = new SInt32 [destDesc.mChannelsPerFrame];
|
||||||
|
for (unsigned int i = 0; i < destDesc.mChannelsPerFrame; ++i)
|
||||||
|
channelMap[i] = 0; //set first channel to all output channels
|
||||||
|
|
||||||
|
result = AudioConverterSetProperty(mConverter,
|
||||||
|
kAudioConverterChannelMap,
|
||||||
|
(sizeof(SInt32) * destDesc.mChannelsPerFrame),
|
||||||
|
channelMap);
|
||||||
|
THROW_RESULT("AudioConverterSetProperty")
|
||||||
|
|
||||||
|
delete [] channelMap;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
assert (mFileDescription.mChannelsPerFrame == 2);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// this uses the better quality SRC
|
||||||
|
UInt32 srcID = kAudioUnitSRCAlgorithm_Polyphase;
|
||||||
|
result = AudioConverterSetProperty(mConverter,
|
||||||
|
kAudioConverterSampleRateConverterAlgorithm,
|
||||||
|
sizeof(srcID),
|
||||||
|
&srcID);
|
||||||
|
THROW_RESULT("AudioConverterSetProperty")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFilePlayer::SetStartFrame (int frame)
|
||||||
|
{
|
||||||
|
SInt64 position = frame * 2352;
|
||||||
|
|
||||||
|
mStartFrame = frame;
|
||||||
|
mAudioFileManager->SetPosition (position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int AudioFilePlayer::GetCurrentFrame ()
|
||||||
|
{
|
||||||
|
return mStartFrame + (mAudioFileManager->GetByteCounter() / 2352);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFilePlayer::SetStopFrame (int frame)
|
||||||
|
{
|
||||||
|
SInt64 position = frame * 2352;
|
||||||
|
|
||||||
|
mAudioFileManager->SetEndOfFile (position);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFilePlayer::~AudioFilePlayer()
|
||||||
|
{
|
||||||
|
Disconnect();
|
||||||
|
|
||||||
|
if (mAudioFileManager) {
|
||||||
|
delete mAudioFileManager;
|
||||||
|
mAudioFileManager = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAudioFileID) {
|
||||||
|
::AudioFileClose (mAudioFileID);
|
||||||
|
mAudioFileID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mConverter) {
|
||||||
|
AudioConverterDispose (mConverter);
|
||||||
|
mConverter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFilePlayer::Connect()
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
printf ("Connect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0));
|
||||||
|
#endif
|
||||||
|
if (!mConnected)
|
||||||
|
{
|
||||||
|
mAudioFileManager->Connect(mConverter);
|
||||||
|
|
||||||
|
// set the render callback for the file data to be supplied to the sound converter AU
|
||||||
|
if (mIsAUNTUnit) {
|
||||||
|
mInputCallback.inputProc = AudioFileManager::FileInputProc;
|
||||||
|
mInputCallback.inputProcRefCon = mAudioFileManager;
|
||||||
|
|
||||||
|
OSStatus result = AudioUnitSetProperty (mPlayUnit,
|
||||||
|
kAudioUnitProperty_SetInputCallback,
|
||||||
|
kAudioUnitScope_Input,
|
||||||
|
mBusNumber,
|
||||||
|
&mInputCallback,
|
||||||
|
sizeof(mInputCallback));
|
||||||
|
THROW_RESULT("AudioUnitSetProperty")
|
||||||
|
}
|
||||||
|
mConnected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warning noted, now please go away ;-)
|
||||||
|
// #warning This should redirect the calling of notification code to some other thread
|
||||||
|
void AudioFilePlayer::DoNotification (OSStatus inStatus) const
|
||||||
|
{
|
||||||
|
AudioFilePlayer* THIS = const_cast<AudioFilePlayer*>(this);
|
||||||
|
|
||||||
|
if (mNotifier) {
|
||||||
|
(*mNotifier) (mRefCon, inStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
SDL_SetError ("Notification posted with no notifier in place");
|
||||||
|
|
||||||
|
if (inStatus == kAudioFilePlay_FileIsFinished)
|
||||||
|
THIS->Disconnect();
|
||||||
|
else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun)
|
||||||
|
THIS->Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFilePlayer::Disconnect ()
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
printf ("Disconnect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0));
|
||||||
|
#endif
|
||||||
|
if (mConnected)
|
||||||
|
{
|
||||||
|
mConnected = false;
|
||||||
|
|
||||||
|
if (mIsAUNTUnit) {
|
||||||
|
mInputCallback.inputProc = 0;
|
||||||
|
mInputCallback.inputProcRefCon = 0;
|
||||||
|
OSStatus result = AudioUnitSetProperty (mPlayUnit,
|
||||||
|
kAudioUnitProperty_SetInputCallback,
|
||||||
|
kAudioUnitScope_Input,
|
||||||
|
mBusNumber,
|
||||||
|
&mInputCallback,
|
||||||
|
sizeof(mInputCallback));
|
||||||
|
if (result)
|
||||||
|
SDL_SetError ("AudioUnitSetProperty:RemoveInputCallback:%ld", result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioFileManager->Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFilePlayer::OpenFile (const FSRef *inRef, SInt64& outFileDataSize)
|
||||||
|
{
|
||||||
|
OSStatus result = AudioFileOpen (inRef, fsRdPerm, 0, &mAudioFileID);
|
||||||
|
THROW_RESULT("AudioFileOpen")
|
||||||
|
|
||||||
|
UInt32 dataSize = sizeof(AudioStreamBasicDescription);
|
||||||
|
result = AudioFileGetProperty (mAudioFileID,
|
||||||
|
kAudioFilePropertyDataFormat,
|
||||||
|
&dataSize,
|
||||||
|
&mFileDescription);
|
||||||
|
THROW_RESULT("AudioFileGetProperty")
|
||||||
|
|
||||||
|
dataSize = sizeof (SInt64);
|
||||||
|
result = AudioFileGetProperty (mAudioFileID,
|
||||||
|
kAudioFilePropertyAudioDataByteCount,
|
||||||
|
&dataSize,
|
||||||
|
&outFileDataSize);
|
||||||
|
THROW_RESULT("AudioFileGetProperty")
|
||||||
|
}
|
240
src/cdrom/macosx/AudioFilePlayer.h
Normal file
240
src/cdrom/macosx/AudioFilePlayer.h
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
This file based on Apple sample code. We haven't changed the file name,
|
||||||
|
so if you want to see the original search for it on apple.com/developer
|
||||||
|
*/
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// AudioFilePlayer.h
|
||||||
|
//
|
||||||
|
#ifndef __AudioFilePlayer_H__
|
||||||
|
#define __AudioFilePlayer_H__
|
||||||
|
|
||||||
|
#include <CoreServices/CoreServices.h>
|
||||||
|
|
||||||
|
#include <AudioToolbox/AudioToolbox.h>
|
||||||
|
#include <AudioUnit/AudioUnit.h>
|
||||||
|
|
||||||
|
#include "SDL_Error.h"
|
||||||
|
|
||||||
|
const char* AudioFilePlayerErrorStr (OSStatus error);
|
||||||
|
|
||||||
|
void ThrowResult (OSStatus result, const char *str);
|
||||||
|
|
||||||
|
#define THROW_RESULT(str) \
|
||||||
|
if (result) { \
|
||||||
|
ThrowResult (result, str); \
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void (*AudioFilePlayNotifier)(void *inRefCon,
|
||||||
|
OSStatus inStatus);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
kAudioFilePlayErr_FilePlayUnderrun = -10000,
|
||||||
|
kAudioFilePlay_FileIsFinished = -10001,
|
||||||
|
kAudioFilePlay_PlayerIsUninitialized = -10002
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AudioFileManager;
|
||||||
|
|
||||||
|
#pragma mark __________ AudioFilePlayer
|
||||||
|
class AudioFilePlayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioFilePlayer (const FSRef *inFileRef);
|
||||||
|
|
||||||
|
~AudioFilePlayer();
|
||||||
|
|
||||||
|
void SetDestination (AudioUnit &inDestUnit,
|
||||||
|
int inBusNumber);
|
||||||
|
|
||||||
|
void SetNotifier (AudioFilePlayNotifier inNotifier, void *inRefCon)
|
||||||
|
{
|
||||||
|
mNotifier = inNotifier;
|
||||||
|
mRefCon = inRefCon;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStartFrame (int frame); // seek in the file
|
||||||
|
|
||||||
|
int GetCurrentFrame (); // get the current frame position
|
||||||
|
|
||||||
|
void SetStopFrame (int frame); // set limit in the file
|
||||||
|
|
||||||
|
void Connect();
|
||||||
|
|
||||||
|
void Disconnect();
|
||||||
|
|
||||||
|
void DoNotification (OSStatus inError) const;
|
||||||
|
|
||||||
|
bool IsConnected () const { return mConnected; }
|
||||||
|
|
||||||
|
UInt32 GetBusNumber () const { return mBusNumber; }
|
||||||
|
|
||||||
|
AudioUnit GetDestUnit () const { return mPlayUnit; }
|
||||||
|
|
||||||
|
AudioConverterRef GetAudioConverter() const { return mConverter; }
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
void Print() const
|
||||||
|
{
|
||||||
|
CAShow (mAudioFileID);
|
||||||
|
printf ("Destination Bus:%ld\n", GetBusNumber());
|
||||||
|
printf ("Is 'aunt' unit:%s\n", (mIsAUNTUnit ? "true" : "false"));
|
||||||
|
printf ("Is Connected:%s\n", (IsConnected() ? "true" : "false"));
|
||||||
|
if (mConverter) CAShow (mConverter);
|
||||||
|
printf ("- - - - - - - - - - - - - - \n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const AudioStreamBasicDescription& GetFileFormat() const { return mFileDescription; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioUnit mPlayUnit;
|
||||||
|
UInt32 mBusNumber;
|
||||||
|
AudioFileID mAudioFileID;
|
||||||
|
|
||||||
|
AudioUnitInputCallback mInputCallback;
|
||||||
|
|
||||||
|
AudioStreamBasicDescription mFileDescription;
|
||||||
|
|
||||||
|
bool mConnected;
|
||||||
|
bool mIsAUNTUnit;
|
||||||
|
|
||||||
|
AudioFileManager* mAudioFileManager;
|
||||||
|
AudioConverterRef mConverter;
|
||||||
|
|
||||||
|
AudioFilePlayNotifier mNotifier;
|
||||||
|
void* mRefCon;
|
||||||
|
|
||||||
|
int mStartFrame;
|
||||||
|
|
||||||
|
#pragma mark __________ Private_Methods
|
||||||
|
|
||||||
|
void OpenFile (const FSRef *inRef, SInt64& outFileSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma mark __________ AudioFileManager
|
||||||
|
class AudioFileManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioFileManager (AudioFilePlayer& inParent, AudioFileID inFile)
|
||||||
|
: mParent (inParent),
|
||||||
|
mAudioFileID (inFile),
|
||||||
|
mFileBuffer (0),
|
||||||
|
mByteCounter (0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~AudioFileManager();
|
||||||
|
|
||||||
|
|
||||||
|
void Connect (AudioConverterRef inConverter)
|
||||||
|
{
|
||||||
|
mParentConverter = inConverter;
|
||||||
|
DoConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method should NOT be called by an object of this class
|
||||||
|
// as it is called by the parent's Disconnect() method
|
||||||
|
virtual void Disconnect () {}
|
||||||
|
|
||||||
|
const AudioFileID& GetFileID() const { return mAudioFileID; }
|
||||||
|
|
||||||
|
const char* GetFileBuffer () { return mFileBuffer; }
|
||||||
|
|
||||||
|
const AudioFilePlayer& GetParent () const { return mParent; }
|
||||||
|
|
||||||
|
virtual void SetPosition (SInt64 pos) = 0; // seek/rewind in the file
|
||||||
|
|
||||||
|
virtual int GetByteCounter () { return mByteCounter; } // return actual bytes streamed to audio hardware
|
||||||
|
|
||||||
|
virtual void SetEndOfFile (SInt64 pos) = 0; // set the "EOF" (will behave just like it reached eof)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioFilePlayer& mParent;
|
||||||
|
AudioConverterRef mParentConverter;
|
||||||
|
const AudioFileID mAudioFileID;
|
||||||
|
|
||||||
|
char* mFileBuffer;
|
||||||
|
|
||||||
|
OSStatus Render (AudioBuffer &ioData);
|
||||||
|
|
||||||
|
int mByteCounter;
|
||||||
|
|
||||||
|
virtual OSStatus GetFileData (void** inOutData, UInt32 *inOutDataSize) = 0;
|
||||||
|
|
||||||
|
virtual void DoConnect () = 0;
|
||||||
|
|
||||||
|
virtual void AfterRender () = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static OSStatus FileInputProc (void *inRefCon,
|
||||||
|
AudioUnitRenderActionFlags inActionFlags,
|
||||||
|
const AudioTimeStamp *inTimeStamp,
|
||||||
|
UInt32 inBusNumber,
|
||||||
|
AudioBuffer *ioData);
|
||||||
|
static OSStatus ACInputProc (AudioConverterRef inAudioConverter,
|
||||||
|
UInt32* outDataSize,
|
||||||
|
void** outData,
|
||||||
|
void* inUserData);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark __________ AudioFileReaderThread
|
||||||
|
class AudioFileReaderThread
|
||||||
|
: public AudioFileManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const UInt32 mChunkSize;
|
||||||
|
SInt64 mFileLength;
|
||||||
|
SInt64 mReadFilePosition;
|
||||||
|
bool mWriteToFirstBuffer;
|
||||||
|
bool mFinishedReadingData;
|
||||||
|
|
||||||
|
AudioFileReaderThread (AudioFilePlayer &inParent,
|
||||||
|
AudioFileID &inFile,
|
||||||
|
SInt64 inFileLength,
|
||||||
|
UInt32 inChunkSize);
|
||||||
|
|
||||||
|
virtual void Disconnect ();
|
||||||
|
|
||||||
|
virtual void SetPosition (SInt64 pos); // seek/rewind in the file
|
||||||
|
|
||||||
|
virtual void SetEndOfFile (SInt64 pos); // set the "EOF" (will behave just like it reached eof)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void DoConnect ();
|
||||||
|
|
||||||
|
virtual OSStatus GetFileData (void** inOutData, UInt32 *inOutDataSize);
|
||||||
|
|
||||||
|
virtual void AfterRender ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool mReadFromFirstBuffer;
|
||||||
|
bool mLockUnsuccessful;
|
||||||
|
bool mIsEngaged;
|
||||||
|
|
||||||
|
int mNumTimesAskedSinceFinished;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
419
src/cdrom/macosx/AudioFileReaderThread.cpp
Normal file
419
src/cdrom/macosx/AudioFileReaderThread.cpp
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
This file based on Apple sample code. We haven't changed the file name,
|
||||||
|
so if you want to see the original search for it on apple.com/developer
|
||||||
|
*/
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// AudioFileReaderThread.cpp
|
||||||
|
//
|
||||||
|
#include "AudioFilePlayer.h"
|
||||||
|
#include <mach/mach.h> //used for setting policy of thread
|
||||||
|
#include "CAGuard.h"
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
class FileReaderThread {
|
||||||
|
public:
|
||||||
|
FileReaderThread ();
|
||||||
|
|
||||||
|
CAGuard& GetGuard() { return mGuard; }
|
||||||
|
|
||||||
|
void AddReader();
|
||||||
|
|
||||||
|
void RemoveReader (const AudioFileReaderThread* inItem);
|
||||||
|
|
||||||
|
// returns true if succeeded
|
||||||
|
bool TryNextRead (AudioFileReaderThread* inItem)
|
||||||
|
{
|
||||||
|
bool didLock = false;
|
||||||
|
bool succeeded = false;
|
||||||
|
if (mGuard.Try (didLock))
|
||||||
|
{
|
||||||
|
mFileData.push_back (inItem);
|
||||||
|
mGuard.Notify();
|
||||||
|
succeeded = true;
|
||||||
|
|
||||||
|
if (didLock)
|
||||||
|
mGuard.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mThreadShouldDie;
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef std::list<AudioFileReaderThread*> FileData;
|
||||||
|
|
||||||
|
CAGuard mGuard;
|
||||||
|
UInt32 mThreadPriority;
|
||||||
|
|
||||||
|
int mNumReaders;
|
||||||
|
FileData mFileData;
|
||||||
|
|
||||||
|
|
||||||
|
void ReadNextChunk ();
|
||||||
|
|
||||||
|
void StartFixedPriorityThread ();
|
||||||
|
static UInt32 GetThreadBasePriority (pthread_t inThread);
|
||||||
|
|
||||||
|
static void* DiskReaderEntry (void *inRefCon);
|
||||||
|
};
|
||||||
|
|
||||||
|
FileReaderThread::FileReaderThread ()
|
||||||
|
: mThreadPriority (62),
|
||||||
|
mNumReaders (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileReaderThread::AddReader()
|
||||||
|
{
|
||||||
|
if (mNumReaders == 0)
|
||||||
|
{
|
||||||
|
mThreadShouldDie = false;
|
||||||
|
|
||||||
|
StartFixedPriorityThread ();
|
||||||
|
}
|
||||||
|
mNumReaders++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileReaderThread::RemoveReader (const AudioFileReaderThread* inItem)
|
||||||
|
{
|
||||||
|
if (mNumReaders > 0)
|
||||||
|
{
|
||||||
|
CAGuard::Locker fileReadLock (mGuard);
|
||||||
|
|
||||||
|
for (FileData::iterator iter = mFileData.begin(); iter != mFileData.end(); ++iter)
|
||||||
|
{
|
||||||
|
if ((*iter) == inItem) {
|
||||||
|
mFileData.erase (iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (--mNumReaders == 0) {
|
||||||
|
mThreadShouldDie = true;
|
||||||
|
mGuard.Notify(); // wake up thread so it will quit
|
||||||
|
mGuard.Wait(); // wait for thread to die
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileReaderThread::StartFixedPriorityThread ()
|
||||||
|
{
|
||||||
|
pthread_attr_t theThreadAttrs;
|
||||||
|
pthread_t pThread;
|
||||||
|
|
||||||
|
OSStatus result = pthread_attr_init(&theThreadAttrs);
|
||||||
|
THROW_RESULT("pthread_attr_init - Thread attributes could not be created.")
|
||||||
|
|
||||||
|
result = pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED);
|
||||||
|
THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.")
|
||||||
|
|
||||||
|
result = pthread_create (&pThread, &theThreadAttrs, DiskReaderEntry, this);
|
||||||
|
THROW_RESULT("pthread_create - Create and start the thread.")
|
||||||
|
|
||||||
|
pthread_attr_destroy(&theThreadAttrs);
|
||||||
|
|
||||||
|
// we've now created the thread and started it
|
||||||
|
// we'll now set the priority of the thread to the nominated priority
|
||||||
|
// and we'll also make the thread fixed
|
||||||
|
thread_extended_policy_data_t theFixedPolicy;
|
||||||
|
thread_precedence_policy_data_t thePrecedencePolicy;
|
||||||
|
SInt32 relativePriority;
|
||||||
|
|
||||||
|
// make thread fixed
|
||||||
|
theFixedPolicy.timeshare = false; // set to true for a non-fixed thread
|
||||||
|
result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT);
|
||||||
|
THROW_RESULT("thread_policy - Couldn't set thread as fixed priority.")
|
||||||
|
// set priority
|
||||||
|
// precedency policy's "importance" value is relative to spawning thread's priority
|
||||||
|
relativePriority = mThreadPriority - FileReaderThread::GetThreadBasePriority (pthread_self());
|
||||||
|
|
||||||
|
thePrecedencePolicy.importance = relativePriority;
|
||||||
|
result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT);
|
||||||
|
THROW_RESULT("thread_policy - Couldn't set thread priority.")
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt32 FileReaderThread::GetThreadBasePriority (pthread_t inThread)
|
||||||
|
{
|
||||||
|
thread_basic_info_data_t threadInfo;
|
||||||
|
policy_info_data_t thePolicyInfo;
|
||||||
|
unsigned int count;
|
||||||
|
|
||||||
|
// get basic info
|
||||||
|
count = THREAD_BASIC_INFO_COUNT;
|
||||||
|
thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (integer_t*)&threadInfo, &count);
|
||||||
|
|
||||||
|
switch (threadInfo.policy) {
|
||||||
|
case POLICY_TIMESHARE:
|
||||||
|
count = POLICY_TIMESHARE_INFO_COUNT;
|
||||||
|
thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (integer_t*)&(thePolicyInfo.ts), &count);
|
||||||
|
return thePolicyInfo.ts.base_priority;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POLICY_FIFO:
|
||||||
|
count = POLICY_FIFO_INFO_COUNT;
|
||||||
|
thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (integer_t*)&(thePolicyInfo.fifo), &count);
|
||||||
|
if (thePolicyInfo.fifo.depressed) {
|
||||||
|
return thePolicyInfo.fifo.depress_priority;
|
||||||
|
} else {
|
||||||
|
return thePolicyInfo.fifo.base_priority;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POLICY_RR:
|
||||||
|
count = POLICY_RR_INFO_COUNT;
|
||||||
|
thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (integer_t*)&(thePolicyInfo.rr), &count);
|
||||||
|
if (thePolicyInfo.rr.depressed) {
|
||||||
|
return thePolicyInfo.rr.depress_priority;
|
||||||
|
} else {
|
||||||
|
return thePolicyInfo.rr.base_priority;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *FileReaderThread::DiskReaderEntry (void *inRefCon)
|
||||||
|
{
|
||||||
|
FileReaderThread *This = (FileReaderThread *)inRefCon;
|
||||||
|
This->ReadNextChunk();
|
||||||
|
#if DEBUG
|
||||||
|
printf ("finished with reading file\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileReaderThread::ReadNextChunk ()
|
||||||
|
{
|
||||||
|
OSStatus result;
|
||||||
|
UInt32 dataChunkSize;
|
||||||
|
AudioFileReaderThread* theItem = 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
{ // this is a scoped based lock
|
||||||
|
CAGuard::Locker fileReadLock (mGuard);
|
||||||
|
|
||||||
|
if (this->mThreadShouldDie) {
|
||||||
|
|
||||||
|
mGuard.Notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFileData.empty())
|
||||||
|
{
|
||||||
|
mGuard.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill thread
|
||||||
|
if (this->mThreadShouldDie) {
|
||||||
|
|
||||||
|
mGuard.Notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
theItem = mFileData.front();
|
||||||
|
mFileData.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((theItem->mFileLength - theItem->mReadFilePosition) < theItem->mChunkSize)
|
||||||
|
dataChunkSize = theItem->mFileLength - theItem->mReadFilePosition;
|
||||||
|
else
|
||||||
|
dataChunkSize = theItem->mChunkSize;
|
||||||
|
|
||||||
|
// this is the exit condition for the thread
|
||||||
|
if (dataChunkSize == 0) {
|
||||||
|
theItem->mFinishedReadingData = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// construct pointer
|
||||||
|
char* writePtr = const_cast<char*>(theItem->GetFileBuffer() +
|
||||||
|
(theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSize));
|
||||||
|
|
||||||
|
/*
|
||||||
|
printf ("AudioFileReadBytes: theItem=%.8X fileID=%.8X pos=%.8X sz=%.8X flen=%.8X ptr=%.8X\n",
|
||||||
|
(unsigned int)theItem, (unsigned int)theItem->GetFileID(),
|
||||||
|
(unsigned int)theItem->mReadFilePosition, (unsigned int)dataChunkSize,
|
||||||
|
(unsigned int)theItem->mFileLength, (unsigned int)writePtr);
|
||||||
|
*/
|
||||||
|
result = AudioFileReadBytes (theItem->GetFileID(),
|
||||||
|
false,
|
||||||
|
theItem->mReadFilePosition,
|
||||||
|
&dataChunkSize,
|
||||||
|
writePtr);
|
||||||
|
if (result) {
|
||||||
|
theItem->GetParent().DoNotification(result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataChunkSize != theItem->mChunkSize)
|
||||||
|
{
|
||||||
|
writePtr += dataChunkSize;
|
||||||
|
|
||||||
|
// can't exit yet.. we still have to pass the partial buffer back
|
||||||
|
memset (writePtr, 0, (theItem->mChunkSize - dataChunkSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer; // switch buffers
|
||||||
|
|
||||||
|
theItem->mReadFilePosition += dataChunkSize; // increment count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static FileReaderThread sReaderThread;
|
||||||
|
|
||||||
|
AudioFileReaderThread::AudioFileReaderThread (AudioFilePlayer &inParent,
|
||||||
|
AudioFileID &inFile,
|
||||||
|
SInt64 inFileLength,
|
||||||
|
UInt32 inChunkSize)
|
||||||
|
: AudioFileManager (inParent, inFile),
|
||||||
|
mChunkSize (inChunkSize),
|
||||||
|
mFileLength (inFileLength),
|
||||||
|
mReadFilePosition (0),
|
||||||
|
mWriteToFirstBuffer (false),
|
||||||
|
mFinishedReadingData (false),
|
||||||
|
|
||||||
|
mLockUnsuccessful (false),
|
||||||
|
mIsEngaged (false)
|
||||||
|
{
|
||||||
|
mFileBuffer = (char*) malloc (mChunkSize * 2);
|
||||||
|
assert (mFileBuffer != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFileReaderThread::DoConnect ()
|
||||||
|
{
|
||||||
|
if (!mIsEngaged)
|
||||||
|
{
|
||||||
|
//mReadFilePosition = 0;
|
||||||
|
mFinishedReadingData = false;
|
||||||
|
|
||||||
|
mNumTimesAskedSinceFinished = -1;
|
||||||
|
mLockUnsuccessful = false;
|
||||||
|
|
||||||
|
UInt32 dataChunkSize;
|
||||||
|
|
||||||
|
if ((mFileLength - mReadFilePosition) < mChunkSize)
|
||||||
|
dataChunkSize = mFileLength - mReadFilePosition;
|
||||||
|
else
|
||||||
|
dataChunkSize = mChunkSize;
|
||||||
|
|
||||||
|
OSStatus result = AudioFileReadBytes ( mAudioFileID,
|
||||||
|
false,
|
||||||
|
mReadFilePosition,
|
||||||
|
&dataChunkSize,
|
||||||
|
mFileBuffer);
|
||||||
|
THROW_RESULT("AudioFileReadBytes")
|
||||||
|
|
||||||
|
mReadFilePosition += dataChunkSize;
|
||||||
|
|
||||||
|
mWriteToFirstBuffer = false;
|
||||||
|
mReadFromFirstBuffer = true;
|
||||||
|
|
||||||
|
sReaderThread.AddReader();
|
||||||
|
|
||||||
|
mIsEngaged = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw static_cast<OSStatus>(-1); //thread has already been started
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFileReaderThread::Disconnect ()
|
||||||
|
{
|
||||||
|
if (mIsEngaged)
|
||||||
|
{
|
||||||
|
sReaderThread.RemoveReader (this);
|
||||||
|
mIsEngaged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OSStatus AudioFileReaderThread::GetFileData (void** inOutData, UInt32 *inOutDataSize)
|
||||||
|
{
|
||||||
|
if (mFinishedReadingData)
|
||||||
|
{
|
||||||
|
++mNumTimesAskedSinceFinished;
|
||||||
|
*inOutDataSize = 0;
|
||||||
|
*inOutData = 0;
|
||||||
|
return noErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mReadFromFirstBuffer == mWriteToFirstBuffer) {
|
||||||
|
#if DEBUG
|
||||||
|
printf ("* * * * * * * Can't keep up with reading file:%ld\n", mParent.GetBusNumber());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mParent.DoNotification (kAudioFilePlayErr_FilePlayUnderrun);
|
||||||
|
*inOutDataSize = 0;
|
||||||
|
*inOutData = 0;
|
||||||
|
} else {
|
||||||
|
*inOutDataSize = mChunkSize;
|
||||||
|
*inOutData = mReadFromFirstBuffer ? mFileBuffer : (mFileBuffer + mChunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLockUnsuccessful = !sReaderThread.TryNextRead (this);
|
||||||
|
|
||||||
|
mReadFromFirstBuffer = !mReadFromFirstBuffer;
|
||||||
|
|
||||||
|
return noErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFileReaderThread::AfterRender ()
|
||||||
|
{
|
||||||
|
if (mNumTimesAskedSinceFinished > 0)
|
||||||
|
{
|
||||||
|
bool didLock = false;
|
||||||
|
if (sReaderThread.GetGuard().Try (didLock)) {
|
||||||
|
mParent.DoNotification (kAudioFilePlay_FileIsFinished);
|
||||||
|
if (didLock)
|
||||||
|
sReaderThread.GetGuard().Unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLockUnsuccessful)
|
||||||
|
mLockUnsuccessful = !sReaderThread.TryNextRead (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFileReaderThread::SetPosition (SInt64 pos)
|
||||||
|
{
|
||||||
|
if (pos < 0 || pos >= mFileLength) {
|
||||||
|
SDL_SetError ("AudioFileReaderThread::SetPosition - position invalid: %d filelen=%d\n",
|
||||||
|
(unsigned int)pos, (unsigned int)mFileLength);
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mReadFilePosition = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioFileReaderThread::SetEndOfFile (SInt64 pos)
|
||||||
|
{
|
||||||
|
if (pos <= 0 || pos > mFileLength) {
|
||||||
|
SDL_SetError ("AudioFileReaderThread::SetEndOfFile - position beyond actual eof\n");
|
||||||
|
pos = mFileLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
mFileLength = pos;
|
||||||
|
}
|
160
src/cdrom/macosx/CAGuard.cpp
Normal file
160
src/cdrom/macosx/CAGuard.cpp
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Note: This file hasn't been modified so technically we have to keep the disclaimer :-(
|
||||||
|
|
||||||
|
Copyright: © Copyright 2002 Apple Computer, Inc. All rights reserved.
|
||||||
|
|
||||||
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
|
||||||
|
("Apple") in consideration of your agreement to the following terms, and your
|
||||||
|
use, installation, modification or redistribution of this Apple software
|
||||||
|
constitutes acceptance of these terms. If you do not agree with these terms,
|
||||||
|
please do not use, install, modify or redistribute this Apple software.
|
||||||
|
|
||||||
|
In consideration of your agreement to abide by the following terms, and subject
|
||||||
|
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
|
||||||
|
copyrights in this original Apple software (the "Apple Software"), to use,
|
||||||
|
reproduce, modify and redistribute the Apple Software, with or without
|
||||||
|
modifications, in source and/or binary forms; provided that if you redistribute
|
||||||
|
the Apple Software in its entirety and without modifications, you must retain
|
||||||
|
this notice and the following text and disclaimers in all such redistributions of
|
||||||
|
the Apple Software. Neither the name, trademarks, service marks or logos of
|
||||||
|
Apple Computer, Inc. may be used to endorse or promote products derived from the
|
||||||
|
Apple Software without specific prior written permission from Apple. Except as
|
||||||
|
expressly stated in this notice, no other rights or licenses, express or implied,
|
||||||
|
are granted by Apple herein, including but not limited to any patent rights that
|
||||||
|
may be infringed by your derivative works or by other works in which the Apple
|
||||||
|
Software may be incorporated.
|
||||||
|
|
||||||
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
|
||||||
|
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
|
||||||
|
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||||||
|
COMBINATION WITH YOUR PRODUCTS.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||||
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
||||||
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
|
||||||
|
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
/*=============================================================================
|
||||||
|
CAGuard.cp
|
||||||
|
|
||||||
|
=============================================================================*/
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Includes
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define NDEBUG 1
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "CAGuard.h"
|
||||||
|
|
||||||
|
//#warning Need a try-based Locker too
|
||||||
|
//=============================================================================
|
||||||
|
// CAGuard
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
CAGuard::CAGuard()
|
||||||
|
{
|
||||||
|
OSStatus theError = pthread_mutex_init(&mMutex, NULL);
|
||||||
|
assert(theError == 0);
|
||||||
|
|
||||||
|
theError = pthread_cond_init(&mCondVar, NULL);
|
||||||
|
assert(theError == 0);
|
||||||
|
|
||||||
|
mOwner = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAGuard::~CAGuard()
|
||||||
|
{
|
||||||
|
pthread_mutex_destroy(&mMutex);
|
||||||
|
pthread_cond_destroy(&mCondVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAGuard::Lock()
|
||||||
|
{
|
||||||
|
bool theAnswer = false;
|
||||||
|
|
||||||
|
if(pthread_self() != mOwner)
|
||||||
|
{
|
||||||
|
OSStatus theError = pthread_mutex_lock(&mMutex);
|
||||||
|
assert(theError == 0);
|
||||||
|
mOwner = pthread_self();
|
||||||
|
theAnswer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return theAnswer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAGuard::Unlock()
|
||||||
|
{
|
||||||
|
assert(pthread_self() == mOwner);
|
||||||
|
|
||||||
|
mOwner = 0;
|
||||||
|
OSStatus theError = pthread_mutex_unlock(&mMutex);
|
||||||
|
assert(theError == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAGuard::Try (bool& outWasLocked)
|
||||||
|
{
|
||||||
|
bool theAnswer = false;
|
||||||
|
outWasLocked = false;
|
||||||
|
|
||||||
|
if (pthread_self() == mOwner) {
|
||||||
|
theAnswer = true;
|
||||||
|
outWasLocked = false;
|
||||||
|
} else {
|
||||||
|
OSStatus theError = pthread_mutex_trylock(&mMutex);
|
||||||
|
if (theError == 0) {
|
||||||
|
mOwner = pthread_self();
|
||||||
|
theAnswer = true;
|
||||||
|
outWasLocked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return theAnswer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAGuard::Wait()
|
||||||
|
{
|
||||||
|
assert(pthread_self() == mOwner);
|
||||||
|
|
||||||
|
mOwner = 0;
|
||||||
|
|
||||||
|
OSStatus theError = pthread_cond_wait(&mCondVar, &mMutex);
|
||||||
|
assert(theError == 0);
|
||||||
|
mOwner = pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAGuard::Notify()
|
||||||
|
{
|
||||||
|
OSStatus theError = pthread_cond_signal(&mCondVar);
|
||||||
|
assert(theError == 0);
|
||||||
|
}
|
143
src/cdrom/macosx/CAGuard.h
Normal file
143
src/cdrom/macosx/CAGuard.h
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Note: This file hasn't been modified so technically we have to keep the disclaimer :-(
|
||||||
|
|
||||||
|
|
||||||
|
Copyright: © Copyright 2002 Apple Computer, Inc. All rights reserved.
|
||||||
|
|
||||||
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
|
||||||
|
("Apple") in consideration of your agreement to the following terms, and your
|
||||||
|
use, installation, modification or redistribution of this Apple software
|
||||||
|
constitutes acceptance of these terms. If you do not agree with these terms,
|
||||||
|
please do not use, install, modify or redistribute this Apple software.
|
||||||
|
|
||||||
|
In consideration of your agreement to abide by the following terms, and subject
|
||||||
|
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
|
||||||
|
copyrights in this original Apple software (the "Apple Software"), to use,
|
||||||
|
reproduce, modify and redistribute the Apple Software, with or without
|
||||||
|
modifications, in source and/or binary forms; provided that if you redistribute
|
||||||
|
the Apple Software in its entirety and without modifications, you must retain
|
||||||
|
this notice and the following text and disclaimers in all such redistributions of
|
||||||
|
the Apple Software. Neither the name, trademarks, service marks or logos of
|
||||||
|
Apple Computer, Inc. may be used to endorse or promote products derived from the
|
||||||
|
Apple Software without specific prior written permission from Apple. Except as
|
||||||
|
expressly stated in this notice, no other rights or licenses, express or implied,
|
||||||
|
are granted by Apple herein, including but not limited to any patent rights that
|
||||||
|
may be infringed by your derivative works or by other works in which the Apple
|
||||||
|
Software may be incorporated.
|
||||||
|
|
||||||
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
|
||||||
|
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
|
||||||
|
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||||||
|
COMBINATION WITH YOUR PRODUCTS.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||||
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
||||||
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
|
||||||
|
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
/*=============================================================================
|
||||||
|
CAGuard.h
|
||||||
|
|
||||||
|
=============================================================================*/
|
||||||
|
#if !defined(__CAGuard_h__)
|
||||||
|
#define __CAGuard_h__
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Includes
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
#include <CoreAudio/CoreAudioTypes.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// CAGuard
|
||||||
|
//
|
||||||
|
// This is your typical mutex with signalling implemented via pthreads.
|
||||||
|
// Lock() will return true if and only if the guard is locked on that call.
|
||||||
|
// A thread that already has the guard will receive 'false' if it locks it
|
||||||
|
// again. Use of the stack-based CAGuard::Locker class is highly recommended
|
||||||
|
// to properly manage the recursive nesting. The Wait calls with timeouts
|
||||||
|
// will return true if and only if the timeout period expired. They will
|
||||||
|
// return false if they receive notification any other way.
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
class CAGuard
|
||||||
|
{
|
||||||
|
|
||||||
|
// Construction/Destruction
|
||||||
|
public:
|
||||||
|
CAGuard();
|
||||||
|
virtual ~CAGuard();
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
public:
|
||||||
|
virtual bool Lock();
|
||||||
|
virtual void Unlock();
|
||||||
|
virtual bool Try(bool& outWasLocked); // returns true if lock is free, false if not
|
||||||
|
|
||||||
|
virtual void Wait();
|
||||||
|
|
||||||
|
virtual void Notify();
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
protected:
|
||||||
|
pthread_mutex_t mMutex;
|
||||||
|
pthread_cond_t mCondVar;
|
||||||
|
pthread_t mOwner;
|
||||||
|
|
||||||
|
// Helper class to manage taking and releasing recursively
|
||||||
|
public:
|
||||||
|
class Locker
|
||||||
|
{
|
||||||
|
|
||||||
|
// Construction/Destruction
|
||||||
|
public:
|
||||||
|
Locker(CAGuard& inGuard) : mGuard(inGuard), mNeedsRelease(false) { mNeedsRelease = mGuard.Lock(); }
|
||||||
|
~Locker() { if(mNeedsRelease) { mGuard.Unlock(); } }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Locker(const Locker&);
|
||||||
|
Locker& operator=(const Locker&);
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
public:
|
||||||
|
void Wait() { mGuard.Wait(); }
|
||||||
|
|
||||||
|
void Notify() { mGuard.Notify(); }
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
private:
|
||||||
|
CAGuard& mGuard;
|
||||||
|
bool mNeedsRelease;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
715
src/cdrom/macosx/CDPlayer.cpp
Normal file
715
src/cdrom/macosx/CDPlayer.cpp
Normal file
|
@ -0,0 +1,715 @@
|
||||||
|
/*
|
||||||
|
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 "CDPlayer.h"
|
||||||
|
#include "AudioFilePlayer.h"
|
||||||
|
#include "CAGuard.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 bool playBackWasInit = false;
|
||||||
|
static AudioUnit theUnit;
|
||||||
|
static AudioFilePlayer* thePlayer = NULL;
|
||||||
|
static CDPlayerCompletionProc completionProc = NULL;
|
||||||
|
static pthread_mutex_t apiMutex;
|
||||||
|
static pthread_t callbackThread;
|
||||||
|
static pthread_mutex_t callbackMutex;
|
||||||
|
static volatile int runCallBackThread;
|
||||||
|
static int initMutex = SDL_TRUE;
|
||||||
|
static SDL_CD* theCDROM;
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// Prototypes
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
#pragma mark -- Prototypes --
|
||||||
|
|
||||||
|
OSStatus CheckInit ();
|
||||||
|
|
||||||
|
OSStatus MatchAUFormats (AudioUnit theUnit, UInt32 theInputBus);
|
||||||
|
|
||||||
|
void FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus);
|
||||||
|
|
||||||
|
void* RunCallBackThread (void* inRefCon);
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -- Public Functions --
|
||||||
|
|
||||||
|
void Lock ()
|
||||||
|
{
|
||||||
|
if (initMutex) {
|
||||||
|
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
|
||||||
|
pthread_mutexattr_init (&attr);
|
||||||
|
pthread_mutex_init (&apiMutex, &attr);
|
||||||
|
pthread_mutexattr_destroy (&attr);
|
||||||
|
|
||||||
|
initMutex = SDL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock (&apiMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unlock ()
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock (&apiMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// DetectAudioCDVolumes
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes)
|
||||||
|
{
|
||||||
|
int volumeIndex;
|
||||||
|
int cdVolumeCount = 0;
|
||||||
|
OSStatus result = noErr;
|
||||||
|
|
||||||
|
for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++)
|
||||||
|
{
|
||||||
|
FSVolumeRefNum actualVolume;
|
||||||
|
HFSUniStr255 volumeName;
|
||||||
|
FSVolumeInfo volumeInfo;
|
||||||
|
FSRef rootDirectory;
|
||||||
|
|
||||||
|
memset (&volumeInfo, 0, sizeof(volumeInfo));
|
||||||
|
|
||||||
|
result = FSGetVolumeInfo (kFSInvalidVolumeRefNum,
|
||||||
|
volumeIndex,
|
||||||
|
&actualVolume,
|
||||||
|
kFSVolInfoFSInfo,
|
||||||
|
&volumeInfo,
|
||||||
|
&volumeName,
|
||||||
|
&rootDirectory);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// ReadTOCData
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// ListTrackFiles
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
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);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))) {
|
||||||
|
|
||||||
|
// Extract the track id from the filename
|
||||||
|
int trackID = 0, i = 0;
|
||||||
|
while (nameStr.unicode[i] >= '0' && nameStr.unicode[i] <= '9') {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// LoadFile
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
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");
|
||||||
|
throw (-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
thePlayer->SetDestination(theUnit, 0);
|
||||||
|
|
||||||
|
if (startFrame >= 0)
|
||||||
|
thePlayer->SetStartFrame (startFrame);
|
||||||
|
|
||||||
|
if (stopFrame >= 0 && stopFrame > startFrame)
|
||||||
|
thePlayer->SetStopFrame (stopFrame);
|
||||||
|
|
||||||
|
// we set the notifier later
|
||||||
|
//thePlayer->SetNotifier(FilePlayNotificationHandler, NULL);
|
||||||
|
|
||||||
|
thePlayer->Connect();
|
||||||
|
|
||||||
|
#if DEBUG_CDROM
|
||||||
|
thePlayer->Print();
|
||||||
|
fflush (stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// ReleaseFile
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
int ReleaseFile ()
|
||||||
|
{
|
||||||
|
int error = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (thePlayer != NULL) {
|
||||||
|
|
||||||
|
thePlayer->Disconnect();
|
||||||
|
|
||||||
|
delete thePlayer;
|
||||||
|
|
||||||
|
thePlayer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// PlayFile
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
int PlayFile ()
|
||||||
|
{
|
||||||
|
OSStatus result = -1;
|
||||||
|
|
||||||
|
if (CheckInit () < 0)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// start processing of the audio unit
|
||||||
|
result = AudioOutputUnitStart (theUnit);
|
||||||
|
THROW_RESULT("PlayFile: AudioOutputUnitStart")
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// PauseFile
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
int PauseFile ()
|
||||||
|
{
|
||||||
|
OSStatus result = -1;
|
||||||
|
|
||||||
|
if (CheckInit () < 0)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// stop processing the audio unit
|
||||||
|
result = AudioOutputUnitStop (theUnit);
|
||||||
|
THROW_RESULT("PauseFile: AudioOutputUnitStop")
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
// SetCompletionProc
|
||||||
|
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
|
||||||
|
|
||||||
|
void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom)
|
||||||
|
{
|
||||||
|
assert(thePlayer != NULL);
|
||||||
|
|
||||||
|
theCDROM = cdrom;
|
||||||
|
completionProc = proc;
|
||||||
|
thePlayer->SetNotifier (FilePlayNotificationHandler, cdrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int GetCurrentFrame ()
|
||||||
|
{
|
||||||
|
int frame;
|
||||||
|
|
||||||
|
if (thePlayer == NULL)
|
||||||
|
frame = 0;
|
||||||
|
else
|
||||||
|
frame = thePlayer->GetCurrentFrame ();
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -- Private Functions --
|
||||||
|
|
||||||
|
OSStatus CheckInit ()
|
||||||
|
{
|
||||||
|
if (playBackWasInit)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
OSStatus result = noErr;
|
||||||
|
|
||||||
|
|
||||||
|
// Create the callback mutex
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutexattr_init (&attr);
|
||||||
|
pthread_mutex_init (&callbackMutex, &attr);
|
||||||
|
pthread_mutexattr_destroy (&attr);
|
||||||
|
pthread_mutex_lock (&callbackMutex);
|
||||||
|
|
||||||
|
// Start callback thread
|
||||||
|
pthread_attr_t attr1;
|
||||||
|
pthread_attr_init (&attr1);
|
||||||
|
pthread_create (&callbackThread, &attr1, RunCallBackThread, NULL);
|
||||||
|
pthread_attr_destroy (&attr1);
|
||||||
|
|
||||||
|
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");
|
||||||
|
throw(internalComponentErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = OpenAComponent (comp, &theUnit);
|
||||||
|
THROW_RESULT("CheckInit: OpenAComponent")
|
||||||
|
|
||||||
|
// you need to initialize the output unit before you set it as a destination
|
||||||
|
result = AudioUnitInitialize (theUnit);
|
||||||
|
THROW_RESULT("CheckInit: AudioUnitInitialize")
|
||||||
|
|
||||||
|
|
||||||
|
// In this case we first want to get the output format of the OutputUnit
|
||||||
|
// Then we set that as the input format. Why?
|
||||||
|
// So that only a single conversion process is done
|
||||||
|
// when SetDestination is called it will get the input format of the
|
||||||
|
// unit its supplying data to. This defaults to 44.1K, stereo, so if
|
||||||
|
// the device is not that, then we lose a possibly rendering of data
|
||||||
|
|
||||||
|
result = MatchAUFormats (theUnit, 0);
|
||||||
|
THROW_RESULT("CheckInit: MatchAUFormats")
|
||||||
|
|
||||||
|
playBackWasInit = true;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OSStatus MatchAUFormats (AudioUnit theUnit, UInt32 theInputBus)
|
||||||
|
{
|
||||||
|
AudioStreamBasicDescription theDesc;
|
||||||
|
UInt32 size = sizeof (theDesc);
|
||||||
|
OSStatus result = AudioUnitGetProperty (theUnit,
|
||||||
|
kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Output,
|
||||||
|
0,
|
||||||
|
&theDesc,
|
||||||
|
&size);
|
||||||
|
THROW_RESULT("MatchAUFormats: AudioUnitGetProperty")
|
||||||
|
|
||||||
|
result = AudioUnitSetProperty (theUnit,
|
||||||
|
kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Input,
|
||||||
|
theInputBus,
|
||||||
|
&theDesc,
|
||||||
|
size);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus)
|
||||||
|
{
|
||||||
|
if (inStatus == kAudioFilePlay_FileIsFinished) {
|
||||||
|
|
||||||
|
// notify non-CA thread to perform the callback
|
||||||
|
pthread_mutex_unlock (&callbackMutex);
|
||||||
|
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* RunCallBackThread (void *param)
|
||||||
|
{
|
||||||
|
runCallBackThread = 1;
|
||||||
|
|
||||||
|
while (runCallBackThread) {
|
||||||
|
|
||||||
|
pthread_mutex_lock (&callbackMutex);
|
||||||
|
|
||||||
|
if (completionProc && theCDROM) {
|
||||||
|
#if DEBUG_CDROM
|
||||||
|
printf ("callback!\n");
|
||||||
|
#endif
|
||||||
|
(*completionProc)(theCDROM);
|
||||||
|
} else {
|
||||||
|
#if DEBUG_CDROM
|
||||||
|
printf ("callback?\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runCallBackThread = -1;
|
||||||
|
|
||||||
|
#if DEBUG_CDROM
|
||||||
|
printf ("thread dying now...\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // extern "C"
|
69
src/cdrom/macosx/CDPlayer.h
Normal file
69
src/cdrom/macosx/CDPlayer.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CDPlayer__H__
|
||||||
|
#define __CDPlayer__H__ 1
|
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <Carbon/Carbon.h>
|
||||||
|
|
||||||
|
#include <AudioUnit/AudioUnit.h>
|
||||||
|
#include <AudioToolbox/AudioToolbox.h>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h> // for usleep
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*CDPlayerCompletionProc)(SDL_CD *cdrom) ;
|
||||||
|
|
||||||
|
void Lock ();
|
||||||
|
|
||||||
|
void Unlock();
|
||||||
|
|
||||||
|
int LoadFile (const FSRef *ref, int startFrame, int endFrame); // pass -1 to do nothing
|
||||||
|
|
||||||
|
int ReleaseFile ();
|
||||||
|
|
||||||
|
int PlayFile ();
|
||||||
|
|
||||||
|
int PauseFile ();
|
||||||
|
|
||||||
|
void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom);
|
||||||
|
|
||||||
|
int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD);
|
||||||
|
|
||||||
|
int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks);
|
||||||
|
|
||||||
|
int DetectAudioCDVolumes (FSVolumeRefNum *volumes, int numVolumes);
|
||||||
|
|
||||||
|
int GetCurrentFrame ();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __CD_Player__H__ */
|
13
src/cdrom/macosx/Makefile.am
Normal file
13
src/cdrom/macosx/Makefile.am
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
## Makefile.am for the Mac OS X cdrom driver for SDL
|
||||||
|
|
||||||
|
noinst_LTLIBRARIES = libcdrom_macosx.la
|
||||||
|
libcdrom_macosx_la_SOURCES = $(SRCS)
|
||||||
|
|
||||||
|
# The SDL cdrom driver sources
|
||||||
|
SRCS = \
|
||||||
|
SDL_syscdrom.c \
|
||||||
|
AudioFilePlayer.cpp \
|
||||||
|
AudioFileReaderThread.cpp \
|
||||||
|
CAGuard.cpp \
|
||||||
|
CDPlayer.cpp
|
132
src/cdrom/macosx/SDL_syscdrom_c.h
Normal file
132
src/cdrom/macosx/SDL_syscdrom_c.h
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/***********************************************************************************
|
||||||
|
Implementation Notes
|
||||||
|
*********************
|
||||||
|
|
||||||
|
This code has several limitations currently (all of which are proabaly fixable):
|
||||||
|
|
||||||
|
1. A CD-ROM device is inferred from a mounted cdfs volume, so device 0 is
|
||||||
|
not necessarily the first CD-ROM device on the system. (Somewhat easy to fix
|
||||||
|
by useing the device name from the volume id's to reorder the volumes)
|
||||||
|
|
||||||
|
2. You can only open and control 1 CD-ROM device at a time. (Challenging to fix,
|
||||||
|
due to extensive code restructuring)
|
||||||
|
|
||||||
|
3. The status reported by SDL_CDStatus only changes to from CD_PLAYING to CD_STOPPED in
|
||||||
|
1-second intervals (because the audio is buffered in 1-second chunks) If
|
||||||
|
the audio data is less than 1 second, the remainder is filled with silence.
|
||||||
|
|
||||||
|
If you need to play sequences back-to-back that are less that 1 second long,
|
||||||
|
use the frame position to determine when to play the next sequence, instead
|
||||||
|
of SDL_CDStatus.
|
||||||
|
|
||||||
|
This may be possible to fix with a clever usage of the AudioUnit API.
|
||||||
|
|
||||||
|
4. When new volumes are inserted, our volume information is not updated. The only way
|
||||||
|
to refresh this information is to reinit the CD-ROM subsystem of SDL. To fix this,
|
||||||
|
one would probably have to fix point 1 above first, then figure out how to register
|
||||||
|
for a notification when new media is mounted in order to perform an automatic
|
||||||
|
rescan for cdfs volumes.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
So, here comes a description of how this all works.
|
||||||
|
|
||||||
|
< Initializing >
|
||||||
|
|
||||||
|
To get things rolling, we have to locate mounted volumes that contain
|
||||||
|
audio (since nearly all Macs don't have analog audio-in on the sound card).
|
||||||
|
That's easy, since these volumes have a flag that indicates this special
|
||||||
|
filesystem. See DetectAudioCDVolumes() in CDPlayer.cpp for this code.
|
||||||
|
|
||||||
|
Next, we parse the invisible .TOC.plist in the root of the volume, which gets us
|
||||||
|
the track information (number, offset, length, leadout, etc). See ReadTOCData() in
|
||||||
|
CDPlayer.cpp for the skinny on this.
|
||||||
|
|
||||||
|
|
||||||
|
< The Playback Loop >
|
||||||
|
|
||||||
|
Now come the tricky parts. Let's start with basic audio playback. When a frame
|
||||||
|
range to play is requested, we must first find the .aiff files on the volume,
|
||||||
|
hopefully in the right order. Since these files all begin with a number "1 Audio Track",
|
||||||
|
etc, this is used to determine the correct track order.
|
||||||
|
|
||||||
|
Once all files are determined, we have to find what file corresponds to the start
|
||||||
|
and length parameter to SDL_SYS_CDPlay(). Again, this is quite simple by walking the
|
||||||
|
cdrom's track list. At this point, we also save the offset to the next track and frames
|
||||||
|
remaining, if we're going to have to play another file after the first one. See
|
||||||
|
GetFileForOffset() for this code.
|
||||||
|
|
||||||
|
At this point we have all info needed to start playback, so we hand off to the LoadFile()
|
||||||
|
function, which proceeds to do its magic and plays back the file.
|
||||||
|
|
||||||
|
When the file is finished playing, CompletionProc() is invoked, at which time we can
|
||||||
|
play the next file if the previously saved next track and frames remaining
|
||||||
|
indicates that we should.
|
||||||
|
|
||||||
|
|
||||||
|
< Magic >
|
||||||
|
|
||||||
|
OK, so it's not really magic, but since I don't fully understand all the hidden details it
|
||||||
|
seems like it to me ;-) The API's involved are the AudioUnit and AudioFile API's. These
|
||||||
|
appear to be an extension of CoreAudio for creating modular playback and f/x entities.
|
||||||
|
The important thing is that CPU usage is very low and reliability is very high. You'd
|
||||||
|
be hard-pressed to find a way to stutter the playback with other CPU-intensive tasks.
|
||||||
|
|
||||||
|
One part of this magic is that it uses multiple threads, which carries the usual potential
|
||||||
|
for disaster if not handled carefully. Playback currently requires 4 additional threads:
|
||||||
|
1. The coreaudio runloop thread
|
||||||
|
2. The coreaudio device i/o thread
|
||||||
|
3. The file streaming thread
|
||||||
|
4. The notification/callback thread
|
||||||
|
|
||||||
|
The first 2 threads are necessary evil - CoreAudio creates this no matter what the situation
|
||||||
|
is (even the SDL sound implementation creates theses suckers). The last two are are created
|
||||||
|
by us.
|
||||||
|
|
||||||
|
The file is streamed from disk using a threaded double-buffer approach.
|
||||||
|
This way, the high latency operation of reading from disk can be performed without interrupting
|
||||||
|
the real-time device thread (which amounts to avoiding dropouts). The device thread grabs the
|
||||||
|
buffer that isn't being read and sends it to the CoreAudio mixer where it eventually gets
|
||||||
|
to the sound card.
|
||||||
|
|
||||||
|
The device thread posts a notification when the file streaming thread is out of data. This
|
||||||
|
notification must be handled in a separate thread to avoid potential deadlock in the
|
||||||
|
device thread. That's where the notification thread comes in. This thread is signaled
|
||||||
|
whenever a notification needs to be processed, so another file can be played back if need be.
|
||||||
|
|
||||||
|
The API in CDPlayer.cpp contains synchronization because otherwise both the notification thread
|
||||||
|
and main thread (or another other thread using the SDL CD api) can potentially call it at the same time.
|
||||||
|
|
||||||
|
************************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "SDL_cdrom.h"
|
||||||
|
#include "SDL_syscdrom.h"
|
||||||
|
|
||||||
|
#include "CDPlayer.h"
|
||||||
|
|
||||||
|
#define kErrorFakeDevice "Error: Cannot proceed since we're faking a CD-ROM device. Reinit the CD-ROM subsystem to scan for new volumes."
|
||||||
|
|
|
@ -64,8 +64,8 @@ static void ListTracks(SDL_CD *cdrom)
|
||||||
trtype="unknown";
|
trtype="unknown";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("\tTrack (index %d) %d: %d:%2.2d [%s track]\n", i,
|
printf("\tTrack (index %d) %d: %d:%2.2d / %d [%s track]\n", i,
|
||||||
cdrom->track[i].id, m, s, trtype);
|
cdrom->track[i].id, m, s, cdrom->track[i].length, trtype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue