transition: move RageSound_Generic_Software logic into RageSoundDriver

This commit is contained in:
Glenn Maynard
2006-12-22 22:43:24 +00:00
parent bafea3b7ab
commit 6531fb0013
3 changed files with 168 additions and 180 deletions
+146 -6
View File
@@ -3,9 +3,16 @@
#include "RageUtil.h"
#include "arch/RageDriver.h"
#include "RageThreads.h"
#include "RageTimer.h"
#include "RageUtil_CircularBuffer.h"
class RageSoundBase;
class RageTimer;
class RageSoundMixBuffer;
static const int samples_per_block = 512;
#define RageSound_Generic_Software RageSoundDriver
class RageSoundDriver: public RageDriver
{
public:
@@ -14,37 +21,41 @@ public:
friend class RageSoundManager;
RageSoundDriver();
virtual ~RageSoundDriver();
/* Initialize. On failure, an error message is returned. */
virtual RString Init() { return RString(); }
/* A RageSound calls this to request to be played.
* XXX: define what we should do when it can't be played (eg. out of
* channels) */
virtual void StartMixing( RageSoundBase *pSound ) = 0;
void StartMixing( RageSoundBase *pSound );
/* A RageSound calls this to request it not be played. When this function
* returns, snd is no longer valid; ensure no running threads are still
* accessing it before returning. This must handle gracefully the case where
* snd was not actually being played, though it may print a warning. */
virtual void StopMixing( RageSoundBase *pSound ) = 0;
void StopMixing( RageSoundBase *pSound );
/* Pause or unpause the given sound. If the sound was stopped (not paused),
* return false and do nothing; otherwise return true and pause or unpause
* the sound. Unlike StopMixing, pausing and unpause a sound will not lose
* any buffered sound (but will not release any resources associated with
* playing the sound, either). */
virtual bool PauseMixing( RageSoundBase *pSound, bool bStop ) = 0;
bool PauseMixing( RageSoundBase *pSound, bool bStop );
/* Get the current hardware frame position, in the same time base as passed to
* RageSound::CommitPlayingPosition. */
virtual int64_t GetHardwareFrame( RageTimer *pTimer ) const = 0;
int64_t GetHardwareFrame( RageTimer *pTimer ) const;
virtual int64_t GetPosition() const = 0;
/* When a sound is finished playing (GetDataToPlay returns 0) and the sound has
* been completely flushed (so GetPosition is no longer meaningful), call
* RageSoundBase::SoundIsFinishedPlaying(). */
/* Optional, if needed: */
virtual void Update() { }
virtual void Update();
/* Sound startup latency--delay between Play() being called and actually
* hearing it. (This isn't necessarily the same as the buffer latency.) */
@@ -52,7 +63,136 @@ public:
virtual int GetSampleRate() const { return 44100; }
virtual ~RageSoundDriver() { }
protected:
/* Start the decoding. This should be called once the hardware is set up and
* GetSampleRate will return the correct value. */
void StartDecodeThread();
/* Call this before calling StartDecodeThread to set the desired decoding buffer
* size. This is the number of frames that Mix() will try to be able to return
* at once. This should generally be slightly larger than the sound writeahead,
* to allow filling the buffer after an underrun. The default is 4096 frames. */
void SetDecodeBufferSize( int frames );
/* Override this to set the priority of the decoding thread, which should be above
* normal priority but not realtime. */
virtual void SetupDecodingThread() { }
/*
* Read mixed data.
*
* pBuf: buffer to read into
* iFrames: number of frames (not samples) to read
* frameno: frame number at which this sound will be heard
* iCurrentFrame: frame number that is currently being heard
*
* iCurrentFrame is used for handling start timing.
*
* This function only mixes data; it will not lock any mutexes or do any file access, and
* is safe to call from a realtime thread.
*/
void Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
void Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
/* This mutex is used for serializing with the decoder thread. Locking this mutex
* can take a while. */
RageMutex m_Mutex;
/* This mutex locks all sounds[] which are "available". (Other sound may safely
* be accessed, and sounds may be set to available, without locking this.) */
RageMutex m_SoundListMutex;
private:
/*
* Thread safety and state transitions:
*
* AVAILABLE: The sound is available to play a new sound. The decoding and mixing threads
* will not touch a sound in this state.
*
* BUFFERING: The sound is stopped but StartMixing() is prebuffering. No other threads
* will touch a sound that is BUFFERING. This isn't necessary if only the main thread
* can call StartMixing().
*
* STOPPED: The sound is idle, but memory is still allocated for its buffer. Update()
* will deallocate memory and the sound will be changed to AVAILABLE.
*
* PLAYING: The sound is being decoded by the decoding thread, and played by the mixing
* thread. If the decoding thread hits EOF, the decoding thread will change the state
* to STOPPING.
*
* STOPPING: The sound is being played by the mixing thread. No new data will be decoded.
* Once the data buffer is empty (all sound has been played), Update() will change the
* sound to HALTING.
*
* HALTING: The main thread has called StopMixing or the data buffer is empty. The mixing
* thread will flush any remaining buffered data without playing it, and then move the
* sound to STOPPED.
*
* The mixing thread operates without any locks. This can lead to a little overlap. For
* example, if StopMixing() is called, moving the sound from PLAYING to HALTING, the mixing
* thread might be in the middle of mixing data. Although HALTING means "discard buffered
* data", some data will still be mixed. This is OK; the data is valid, and the flush will
* happen on the next iteration.
*
* The only state change made by the decoding thread is on EOF: the state is changed
* from PLAYING to STOPPING. This is done while m_Mutex is held, to prevent
* races with other threads.
*
* The only state change made by the mixing thread is from HALTING to STOPPED.
* This is done with no locks; no other thread can take a sound out of the HALTING state.
*
* Do not allocate or deallocate memory in the mixing thread since allocating memory
* involves taking a lock. Instead, push the deallocation to the main thread.
*/
struct sound_block
{
int16_t m_Buffer[samples_per_block];
int16_t *m_BufferNext; // beginning of the unread data
int m_FramesInBuffer; // total number of frames at m_BufferNext
int64_t m_iPosition; // stream frame of m_BufferNext
sound_block() { m_FramesInBuffer = m_iPosition = 0; m_BufferNext = m_Buffer; }
};
struct Sound
{
Sound();
void Allocate( int iFrames );
void Deallocate();
RageSoundBase *m_pSound;
int m_iSoundID;
RageTimer m_StartTime;
float m_fVolume;
CircBuf<sound_block> m_Buffer;
bool m_bPaused;
enum
{
AVAILABLE,
BUFFERING,
STOPPED, /* idle */
/* This state is set by the decoder thread, indicating that the sound has just
* reached EOF. Once the mixing thread finishes flushing buffer, it'll change
* to the STOPPING_FINISH state. */
STOPPING,
HALTING, /* stop immediately */
PLAYING
} m_State;
};
/* List of currently playing sounds: XXX no vector */
Sound m_Sounds[32];
bool m_bShutdownDecodeThread;
static int DecodeThread_start( void *p );
void DecodeThread();
RageSoundMixBuffer &MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
RageThread m_DecodeThread;
int GetDataForSound( Sound &s );
};
// Can't use Create##name because many of these have -sw suffixes.
@@ -18,14 +18,14 @@ static int chunksize() { return 512; }
static int underruns = 0, logged_underruns = 0;
RageSound_Generic_Software::Sound::Sound()
RageSoundDriver::Sound::Sound()
{
m_pSound = NULL;
m_State = AVAILABLE;
m_bPaused = false;
}
void RageSound_Generic_Software::Sound::Allocate( int iFrames )
void RageSoundDriver::Sound::Allocate( int iFrames )
{
/* Reserve enough blocks in the buffer to hold the buffer. Add one, to account for
* the fact that we may have a partial block due to a previous Mix() call. */
@@ -34,23 +34,23 @@ void RageSound_Generic_Software::Sound::Allocate( int iFrames )
m_Buffer.reserve( iBlocksToPrebuffer + 1 );
}
void RageSound_Generic_Software::Sound::Deallocate()
void RageSoundDriver::Sound::Deallocate()
{
m_Buffer.reserve( 0 );
}
int RageSound_Generic_Software::DecodeThread_start( void *p )
int RageSoundDriver::DecodeThread_start( void *p )
{
((RageSound_Generic_Software *) p)->DecodeThread();
((RageSoundDriver *) p)->DecodeThread();
return 0;
}
static int g_iTotalAhead = 0;
static int g_iTotalAheadCount = 0;
RageSoundMixBuffer &RageSound_Generic_Software::MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
RageSoundMixBuffer &RageSoundDriver::MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
ASSERT_M( m_DecodeThread.IsCreated(), "RageSound_Generic_Software::StartDecodeThread() was never called" );
ASSERT_M( m_DecodeThread.IsCreated(), "RageSoundDriver::StartDecodeThread() was never called" );
if( iFrameNumber - iCurrentFrame + iFrames > 0 )
{
@@ -152,19 +152,19 @@ RageSoundMixBuffer &RageSound_Generic_Software::MixIntoBuffer( int iFrames, int6
return mix;
}
void RageSound_Generic_Software::Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
void RageSoundDriver::Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
memset( pBuf, 0, iFrames*bytes_per_frame );
MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
}
void RageSound_Generic_Software::Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
void RageSoundDriver::Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
memset( pBuf, 0, iFrames*bytes_per_frame*2 );
MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
}
void RageSound_Generic_Software::DecodeThread()
void RageSoundDriver::DecodeThread()
{
/* SOUNDMAN will be set once RageSoundManager's ctor returns and
* assigns it; we might get here before that happens, though. */
@@ -217,7 +217,7 @@ void RageSound_Generic_Software::DecodeThread()
/* Buffer a block of sound data for the given sound. Return the number of
* frames buffered, or a RageSoundReader return code. */
int RageSound_Generic_Software::GetDataForSound( Sound &s )
int RageSoundDriver::GetDataForSound( Sound &s )
{
sound_block *p[2];
unsigned psize[2];
@@ -242,7 +242,7 @@ int RageSound_Generic_Software::GetDataForSound( Sound &s )
}
void RageSound_Generic_Software::Update()
void RageSoundDriver::Update()
{
/* We must not lock here, since the decoder thread might hold the lock for a
* while at a time. This is threadsafe, because once a sound is in STOPPING,
@@ -294,7 +294,7 @@ void RageSound_Generic_Software::Update()
}
}
void RageSound_Generic_Software::StartMixing( RageSoundBase *pSound )
void RageSoundDriver::StartMixing( RageSoundBase *pSound )
{
/* Lock available m_Sounds[], and reserve a slot. */
m_SoundListMutex.Lock();
@@ -343,7 +343,7 @@ void RageSound_Generic_Software::StartMixing( RageSoundBase *pSound )
// LOG->Trace("StartMixing: (#%i) finished prebuffering(%s) (%p)", i, s.m_pSound->GetLoadedFilePath().c_str(), s.m_pSound );
}
void RageSound_Generic_Software::StopMixing( RageSoundBase *pSound )
void RageSoundDriver::StopMixing( RageSoundBase *pSound )
{
/* Lock, to make sure the decoder thread isn't running on this sound while we do this. */
LockMut( m_Mutex );
@@ -379,7 +379,7 @@ void RageSound_Generic_Software::StopMixing( RageSoundBase *pSound )
}
bool RageSound_Generic_Software::PauseMixing( RageSoundBase *pSound, bool bStop )
bool RageSoundDriver::PauseMixing( RageSoundBase *pSound, bool bStop )
{
LockMut( m_Mutex );
@@ -404,22 +404,22 @@ bool RageSound_Generic_Software::PauseMixing( RageSoundBase *pSound, bool bStop
return true;
}
void RageSound_Generic_Software::StartDecodeThread()
void RageSoundDriver::StartDecodeThread()
{
ASSERT( !m_DecodeThread.IsCreated() );
m_DecodeThread.Create( DecodeThread_start, this );
}
void RageSound_Generic_Software::SetDecodeBufferSize( int iFrames )
void RageSoundDriver::SetDecodeBufferSize( int iFrames )
{
ASSERT( !m_DecodeThread.IsCreated() );
frames_to_buffer = iFrames;
}
RageSound_Generic_Software::RageSound_Generic_Software():
m_Mutex("RageSound_Generic_Software"),
RageSoundDriver::RageSoundDriver():
m_Mutex("RageSoundDriver"),
m_SoundListMutex("SoundListMutex")
{
m_bShutdownDecodeThread = false;
@@ -428,7 +428,7 @@ RageSound_Generic_Software::RageSound_Generic_Software():
m_DecodeThread.SetName("Decode thread");
}
RageSound_Generic_Software::~RageSound_Generic_Software()
RageSoundDriver::~RageSoundDriver()
{
/* Signal the decoding thread to quit. */
if( m_DecodeThread.IsCreated() )
@@ -445,7 +445,7 @@ RageSound_Generic_Software::~RageSound_Generic_Software()
}
}
int64_t RageSound_Generic_Software::GetHardwareFrame( RageTimer *pTimestamp ) const
int64_t RageSoundDriver::GetHardwareFrame( RageTimer *pTimestamp ) const
{
if( pTimestamp == NULL )
return GetPosition();
@@ -473,7 +473,7 @@ int64_t RageSound_Generic_Software::GetHardwareFrame( RageTimer *pTimestamp ) co
if( !bLogged )
{
bLogged = true;
LOG->Warn( "RageSound_Generic_Software::GetHardwareFrame: too many tries" );
LOG->Warn( "RageSoundDriver::GetHardwareFrame: too many tries" );
}
}
@@ -2,158 +2,6 @@
#define RAGE_SOUND_GENERIC_SOFTWARE
#include "RageSoundDriver.h"
#include "RageThreads.h"
#include "RageTimer.h"
#include "RageUtil_CircularBuffer.h"
class RageSoundMixBuffer;
static const int samples_per_block = 512;
class RageSound_Generic_Software: public RageSoundDriver
{
public:
virtual void Update();
void StartMixing( RageSoundBase *pSound ); /* used by RageSound */
void StopMixing( RageSoundBase *pSound ); /* used by RageSound */
bool PauseMixing( RageSoundBase *pSound, bool bStop );
RageSound_Generic_Software();
virtual ~RageSound_Generic_Software();
virtual int64_t GetHardwareFrame( RageTimer *pTimer ) const;
virtual int64_t GetPosition() const = 0;
protected:
/* Start the decoding. This should be called once the hardware is set up and
* GetSampleRate will return the correct value. */
void StartDecodeThread();
/* Call this before calling StartDecodeThread to set the desired decoding buffer
* size. This is the number of frames that Mix() will try to be able to return
* at once. This should generally be slightly larger than the sound writeahead,
* to allow filling the buffer after an underrun. The default is 4096 frames. */
void SetDecodeBufferSize( int frames );
/* Override this to set the priority of the decoding thread, which should be above
* normal priority but not realtime. */
virtual void SetupDecodingThread() { }
/*
* Read mixed data.
*
* pBuf: buffer to read into
* iFrames: number of frames (not samples) to read
* frameno: frame number at which this sound will be heard
* iCurrentFrame: frame number that is currently being heard
*
* iCurrentFrame is used for handling start timing.
*
* This function only mixes data; it will not lock any mutexes or do any file access, and
* is safe to call from a realtime thread.
*/
void Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
void Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
/* This mutex is used for serializing with the decoder thread. Locking this mutex
* can take a while. */
RageMutex m_Mutex;
/* This mutex locks all sounds[] which are "available". (Other sound may safely
* be accessed, and sounds may be set to available, without locking this.) */
RageMutex m_SoundListMutex;
private:
/*
* Thread safety and state transitions:
*
* AVAILABLE: The sound is available to play a new sound. The decoding and mixing threads
* will not touch a sound in this state.
*
* BUFFERING: The sound is stopped but StartMixing() is prebuffering. No other threads
* will touch a sound that is BUFFERING. This isn't necessary if only the main thread
* can call StartMixing().
*
* STOPPED: The sound is idle, but memory is still allocated for its buffer. Update()
* will deallocate memory and the sound will be changed to AVAILABLE.
*
* PLAYING: The sound is being decoded by the decoding thread, and played by the mixing
* thread. If the decoding thread hits EOF, the decoding thread will change the state
* to STOPPING.
*
* STOPPING: The sound is being played by the mixing thread. No new data will be decoded.
* Once the data buffer is empty (all sound has been played), Update() will change the
* sound to HALTING.
*
* HALTING: The main thread has called StopMixing or the data buffer is empty. The mixing
* thread will flush any remaining buffered data without playing it, and then move the
* sound to STOPPED.
*
* The mixing thread operates without any locks. This can lead to a little overlap. For
* example, if StopMixing() is called, moving the sound from PLAYING to HALTING, the mixing
* thread might be in the middle of mixing data. Although HALTING means "discard buffered
* data", some data will still be mixed. This is OK; the data is valid, and the flush will
* happen on the next iteration.
*
* The only state change made by the decoding thread is on EOF: the state is changed
* from PLAYING to STOPPING. This is done while m_Mutex is held, to prevent
* races with other threads.
*
* The only state change made by the mixing thread is from HALTING to STOPPED.
* This is done with no locks; no other thread can take a sound out of the HALTING state.
*
* Do not allocate or deallocate memory in the mixing thread since allocating memory
* involves taking a lock. Instead, push the deallocation to the main thread.
*/
struct sound_block
{
int16_t m_Buffer[samples_per_block];
int16_t *m_BufferNext; // beginning of the unread data
int m_FramesInBuffer; // total number of frames at m_BufferNext
int64_t m_iPosition; // stream frame of m_BufferNext
sound_block() { m_FramesInBuffer = m_iPosition = 0; m_BufferNext = m_Buffer; }
};
struct Sound
{
Sound();
void Allocate( int iFrames );
void Deallocate();
RageSoundBase *m_pSound;
int m_iSoundID;
RageTimer m_StartTime;
float m_fVolume;
CircBuf<sound_block> m_Buffer;
bool m_bPaused;
enum
{
AVAILABLE,
BUFFERING,
STOPPED, /* idle */
/* This state is set by the decoder thread, indicating that the sound has just
* reached EOF. Once the mixing thread finishes flushing buffer, it'll change
* to the STOPPING_FINISH state. */
STOPPING,
HALTING, /* stop immediately */
PLAYING
} m_State;
};
/* List of currently playing sounds: XXX no vector */
Sound m_Sounds[32];
bool m_bShutdownDecodeThread;
static int DecodeThread_start( void *p );
void DecodeThread();
RageSoundMixBuffer &MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
RageThread m_DecodeThread;
int GetDataForSound( Sound &s );
};
#endif