Updated the SDL sound code so that the main thread and the sound thread

stay synchronized with each other.  Also added code to disable the audio
if the driver doesn't support "real time" audio (under linux this usually
means a sound server is running).  This code appears to be working almost
as well as the X11 sound code under the 1.2.5 release of SDL.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@128 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
bwmott 2002-11-11 02:07:21 +00:00
parent 5d592bdfa3
commit 470de4f24a
3 changed files with 349 additions and 108 deletions

View File

@ -8,66 +8,90 @@
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-1998 by Bradford W. Mott
// Copyright (c) 1995-2002 by Bradford W. Mott
//
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SoundSDL.cxx,v 1.2 2002-10-12 15:24:49 stephena Exp $
// $Id: SoundSDL.cxx,v 1.3 2002-11-11 02:07:21 bwmott Exp $
//============================================================================
#include <SDL.h>
#include "SoundSDL.hxx"
static uInt8 _myCurrentVolume;
static MediaSource* _myMediaSource;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL::SoundSDL(bool activate)
: myIsInitializedFlag(false),
: myCurrentVolume(SDL_MIX_MAXVOLUME),
myFragmentSize(1024),
myIsInitializedFlag(false),
myIsMuted(false),
mySampleRate(31400),
myFragSize(512),
myIsMuted(true)
mySampleQueue(mySampleRate)
{
myIsInitializedFlag = activate;
if(!myIsInitializedFlag)
return;
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
if(activate)
{
cerr << "Couldn't init SDL audio system: " << SDL_GetError() << endl;
myIsInitializedFlag = false;
mySampleRate = 0;
return;
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
cerr << "WARNING: Couldn't initialize SDL audio system! " << endl;
cerr << " " << SDL_GetError() << endl;
myIsInitializedFlag = false;
mySampleRate = 0;
return;
}
SDL_AudioSpec desired;
desired.freq = mySampleRate;
desired.format = AUDIO_U8;
desired.channels = 1;
desired.samples = myFragmentSize;
desired.callback = callback;
desired.userdata = (void*)this;
if(SDL_OpenAudio(&desired, &myHardwareSpec) < 0)
{
cerr << "WARNING: Couldn't open SDL audio system! " << endl;
cerr << " " << SDL_GetError() << endl;
myIsInitializedFlag = false;
mySampleRate = 0;
return;
}
// Make sure the sample buffer isn't to big (if it is the sound code
// will not work so we'll need to disable the audio support)
if(((float)myHardwareSpec.size / (float)myHardwareSpec.freq) >= 0.25)
{
cerr << "WARNING: Audio device doesn't support real time audio! Make ";
cerr << "sure a sound" << endl;
cerr << " server isn't running. Audio is disabled..." << endl;
SDL_CloseAudio();
myIsInitializedFlag = false;
mySampleRate = 0;
return;
}
myIsInitializedFlag = true;
myIsMuted = false;
mySampleRate = myHardwareSpec.freq;
myFragmentSize = myHardwareSpec.samples;
// cerr << "Freq: " << (int)mySampleRate << endl;
// cerr << "Format: " << (int)myHardwareSpec.format << endl;
// cerr << "Channels: " << (int)myHardwareSpec.channels << endl;
// cerr << "Silence: " << (int)myHardwareSpec.silence << endl;
// cerr << "Samples: " << (int)myHardwareSpec.samples << endl;
// cerr << "Size: " << (int)myHardwareSpec.size << endl;
SDL_PauseAudio(0);
}
SDL_AudioSpec desired, obtained;
desired.freq = mySampleRate;
desired.samples = myFragSize;
desired.format = AUDIO_U8;
desired.callback = fillAudio;
desired.userdata = NULL;
desired.channels = 1;
if(SDL_OpenAudio(&desired, &obtained) < 0)
else
{
cerr << "Couldn't open SDL audio: " << SDL_GetError() << endl;
myIsInitializedFlag = false;
myIsMuted = true;
mySampleRate = 0;
return;
}
myIsMuted = false;
mySampleRate = obtained.freq;
// Take care of the static stuff ...
_myCurrentVolume = 0;
_myMediaSource = (MediaSource*) NULL;
SDL_PauseAudio(0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -92,11 +116,15 @@ bool SoundSDL::isSuccessfullyInitialized() const
void SoundSDL::mute(bool state)
{
if(!myIsInitializedFlag)
{
return;
}
// Ignore multiple calls to do the same thing
if(myIsMuted == state)
{
return;
}
myIsMuted = state;
@ -107,7 +135,9 @@ void SoundSDL::mute(bool state)
void SoundSDL::close()
{
if(myIsInitializedFlag)
{
SDL_CloseAudio();
}
myIsInitializedFlag = false;
}
@ -115,30 +145,177 @@ void SoundSDL::close()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::setSoundVolume(uInt32 percent)
{
if(!myIsInitializedFlag)
return;
if((percent >= 0) && (percent <= 100))
_myCurrentVolume = (int) (((float) percent / 100.0) * (float) SDL_MIX_MAXVOLUME);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::setMediaSource(MediaSource& mediaSource)
{
_myMediaSource = &mediaSource;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::fillAudio(void* udata, uInt8* stream, Int32 len)
{
if(!_myMediaSource)
return;
// Dequeue samples as long as full fragments are available
if(_myMediaSource->numberOfAudioSamples() >= (uInt32) len)
if(myIsInitializedFlag)
{
uInt8 buffer[len];
_myMediaSource->dequeueAudioSamples(buffer, (uInt32)len);
SDL_MixAudio(stream, buffer, len, _myCurrentVolume);
if((percent >= 0) && (percent <= 100))
{
SDL_LockAudio();
myCurrentVolume = (uInt32)(((float)percent / 100.0) * SDL_MIX_MAXVOLUME);
SDL_UnlockAudio();
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::updateSound(MediaSource& mediaSource)
{
if(myIsInitializedFlag)
{
// Make sure we have exclusive access to the sample queue
SDL_LockAudio();
// Move all of the generated samples into the our private sample queue
uInt8 buffer[4096];
while(mediaSource.numberOfAudioSamples() > 0)
{
uInt32 size = mediaSource.dequeueAudioSamples(buffer, 4096);
mySampleQueue.enqueue(buffer, size);
}
// Release lock on the sample queue
SDL_UnlockAudio();
// Block until the sound thread has consumed all but 100 milliseconds
// of the available audio samples
uInt32 leave = mySampleRate / 10;
for(;;)
{
uInt32 size = 0;
SDL_LockAudio();
size = mySampleQueue.size();
SDL_UnlockAudio();
if(size <= leave)
{
break;
}
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::callback(void* udata, uInt8* stream, int len)
{
SoundSDL* sound = (SoundSDL*)udata;
if(!sound->isSuccessfullyInitialized())
{
return;
}
// Don't use samples unless there's at least 50 milliseconds worth of data
if(sound->mySampleQueue.size() < (sound->mySampleRate / 20))
{
return;
}
//cerr << "len: " << len << " Q.size: " << sound->mySampleQueue.size() << endl;
if(sound->mySampleQueue.size() > 0)
{
Int32 offset;
uInt8 buffer[4096];
for(offset = 0; (offset < len) && (sound->mySampleQueue.size() > 0); )
{
uInt32 s = sound->mySampleQueue.dequeue(buffer,
(4096 > (len - offset) ? (len - offset) : 4096));
SDL_MixAudio(stream + offset, buffer, s, sound->myCurrentVolume);
offset += s;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL::SampleQueue::SampleQueue(uInt32 capacity)
: myCapacity(capacity),
myBuffer(0),
mySize(0),
myHead(0),
myTail(0)
{
myBuffer = new uInt8[myCapacity];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL::SampleQueue::~SampleQueue()
{
delete[] myBuffer;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::SampleQueue::clear()
{
myHead = myTail = mySize = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundSDL::SampleQueue::dequeue(uInt8* buffer, uInt32 size)
{
// We can only dequeue up to the number of items in the queue
if(size > mySize)
{
size = mySize;
}
if((myHead + size) < myCapacity)
{
memcpy((void*)buffer, (const void*)(myBuffer + myHead), size);
myHead += size;
}
else
{
uInt32 s1 = myCapacity - myHead;
uInt32 s2 = size - s1;
memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1);
memcpy((void*)(buffer + s1), (const void*)myBuffer, s2);
myHead = (myHead + size) % myCapacity;
}
mySize -= size;
return size;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL::SampleQueue::enqueue(uInt8* buffer, uInt32 size)
{
// If an attempt is made to enqueue more than the queue can hold then
// we'll only enqueue the last myCapacity elements.
if(size > myCapacity)
{
buffer += (size - myCapacity);
size = myCapacity;
}
if((myTail + size) < myCapacity)
{
memcpy((void*)(myBuffer + myTail), (const void*)buffer, size);
myTail += size;
}
else
{
uInt32 s1 = myCapacity - myTail;
uInt32 s2 = size - s1;
memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1);
memcpy((void*)myBuffer, (const void*)(buffer + s1), s2);
myTail = (myTail + size) % myCapacity;
}
if((mySize + size) > myCapacity)
{
myHead = (myHead + ((mySize + size) - myCapacity)) % myCapacity;
mySize = myCapacity;
}
else
{
mySize += size;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundSDL::SampleQueue::size() const
{
return mySize;
}

View File

@ -8,12 +8,12 @@
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-1998 by Bradford W. Mott
// Copyright (c) 1995-2002 by Bradford W. Mott
//
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SoundSDL.hxx,v 1.2 2002-10-12 15:24:49 stephena Exp $
// $Id: SoundSDL.hxx,v 1.3 2002-11-11 02:07:21 bwmott Exp $
//============================================================================
#ifndef SOUNDSDL_HXX
@ -27,8 +27,8 @@
/**
This class implements the sound API for SDL.
@author Stephen Anthony
@version $Id: SoundSDL.hxx,v 1.2 2002-10-12 15:24:49 stephena Exp $
@author Stephen Anthony and Bradford W. Mott
@version $Id: SoundSDL.hxx,v 1.3 2002-11-11 02:07:21 bwmott Exp $
*/
class SoundSDL
{
@ -44,6 +44,11 @@ class SoundSDL
virtual ~SoundSDL();
public:
/**
Closes the sound device
*/
void close();
/**
Return the playback sample rate for the sound device.
@ -58,6 +63,13 @@ class SoundSDL
*/
bool isSuccessfullyInitialized() const;
/**
Set the mute state of the sound object.
@param state Mutes sound if true, unmute if false
*/
void mute(bool state);
/**
Sets the volume of the sound device to the specified level. The
volume is given as a precentage from 0 to 100.
@ -67,46 +79,93 @@ class SoundSDL
void setSoundVolume(uInt32 volume);
/**
Notifies this class of the MediaSource object where sample data
may be obtained. The SDL sound api is thread-based, so the SDL
audio callback directly queries the MediaSource when it requires
more audio samples.
Update the sound device using the audio sample from the specified
media source.
@param mediaSource The MediaSource where sample data is obtained
@param mediaSource The media source to get audio samples from.
*/
void setMediaSource(MediaSource& mediaSource);
/**
Set the mute state of the sound object.
@param state Mutes sound if true, unmute if false
*/
void mute(bool state);
/**
Closes the sound device
*/
void close();
void updateSound(MediaSource& mediaSource);
private:
// Indicates if the sound device was successfully initialized
bool myIsInitializedFlag;
/**
A bounded queue class used to hold audio samples after they are
produced by the MediaSource.
*/
class SampleQueue
{
public:
/**
Create a new SampleQueue instance which can hold the specified
number of samples. If the queue ever reaches its capacity then
older samples are discarded.
*/
SampleQueue(uInt32 capacity);
// DSP sample rate
uInt32 mySampleRate;
/**
Destroy this SampleQueue instance.
*/
virtual ~SampleQueue();
public:
/**
Clear any samples stored in the queue.
*/
void clear();
/**
Dequeue the upto the specified number of samples and store them
in the buffer. Returns the actual number of samples removed from
the queue.
@return the actual number of samples removed from the queue.
*/
uInt32 dequeue(uInt8* buffer, uInt32 size);
/**
Enqueue the specified number of samples from the buffer.
*/
void enqueue(uInt8* buffer, uInt32 size);
/**
Answers the number of samples currently in the queue.
@return The number of samples in the queue.
*/
uInt32 size() const;
private:
const uInt32 myCapacity;
uInt8* myBuffer;
uInt32 mySize;
uInt32 myHead;
uInt32 myTail;
};
private:
// Current volume
uInt32 myCurrentVolume;
// SDL fragment size
uInt32 myFragSize;
uInt32 myFragmentSize;
// Audio specification structure
SDL_AudioSpec myHardwareSpec;
// Indicates if the sound device was successfully initialized
bool myIsInitializedFlag;
// Indicates if the sound is currently muted
bool myIsMuted;
private:
/**
The callback used by the SDL sound API. It obtains samples
from the MediaSource as it needs them.
*/
static void fillAudio(void* udata, uInt8* stream, Int32 len);
// DSP sample rate
uInt32 mySampleRate;
// Queue which holds samples from the media source before they are played
SampleQueue mySampleQueue;
private:
// Callback function invoked by the SDL Audio library when it needs data
static void callback(void* udata, uInt8* stream, int len);
};
#endif

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: mainSDL.cxx,v 1.33 2002-11-10 19:43:16 stephena Exp $
// $Id: mainSDL.cxx,v 1.34 2002-11-11 02:07:21 bwmott Exp $
//============================================================================
#include <fstream>
@ -276,7 +276,8 @@ bool setupDisplay()
// Set the window title and icon
ostringstream name;
name << "Stella: \"" << theConsole->properties().get("Cartridge.Name") << "\"";
name << "Stella: \"" << theConsole->properties().get("Cartridge.Name")
<< "\"";
SDL_WM_SetCaption(name.str().c_str(), "stella");
// Create the screen
@ -1446,13 +1447,14 @@ bool setupProperties(PropertiesSet& set)
{
if(access(settings->theAlternateProFile.c_str(), R_OK) == 0)
{
set.load(settings->theAlternateProFile, &Console::defaultProperties(), false);
set.load(settings->theAlternateProFile, &Console::defaultProperties(),
false);
return true;
}
else
{
cerr << "ERROR: Couldn't find \"" << settings->theAlternateProFile <<
"\" properties file." << endl;
cerr << "ERROR: Couldn't find \"" << settings->theAlternateProFile
<< "\" properties file." << endl;
return false;
}
}
@ -1652,10 +1654,6 @@ int main(int argc, char* argv[])
theEvent, propertiesSet, sound.getSampleRate());
#endif
// Let the sound object know about the MediaSource.
// The sound object takes care of getting samples from the MediaSource.
sound.setMediaSource(theConsole->mediaSource());
// Free the image since we don't need it any longer
delete[] image;
@ -1683,7 +1681,8 @@ int main(int argc, char* argv[])
{
// Set up accurate timing stuff
uInt32 startTime, delta;
uInt32 timePerFrame = (uInt32) (1000000.0 / (double) settings->theDesiredFrameRate);
uInt32 timePerFrame =
(uInt32)(1000000.0 / (double)settings->theDesiredFrameRate);
// Set the base for the timers
frameTime = 0;
@ -1709,6 +1708,7 @@ int main(int argc, char* argv[])
startTime = getTicks();
theConsole->mediaSource().update();
updateDisplay(theConsole->mediaSource());
sound.updateSound(theConsole->mediaSource());
handleEvents();
// Now, waste time if we need to so that we are at the desired frame rate
@ -1728,7 +1728,8 @@ int main(int argc, char* argv[])
{
// Set up less accurate timing stuff
uInt32 startTime, virtualTime, currentTime;
uInt32 timePerFrame = (uInt32) (1000000.0 / (double) settings->theDesiredFrameRate);
uInt32 timePerFrame =
(uInt32)(1000000.0 / (double)settings->theDesiredFrameRate);
// Set the base for the timers
virtualTime = getTicks();
@ -1749,6 +1750,10 @@ int main(int argc, char* argv[])
theConsole->mediaSource().update();
}
updateDisplay(theConsole->mediaSource());
if(!thePauseIndicator)
{
sound.updateSound(theConsole->mediaSource());
}
handleEvents();
currentTime = getTicks();