From 6531fb00139dc8723b8ffa4447eca7519dad3907 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Fri, 22 Dec 2006 22:43:24 +0000 Subject: [PATCH] transition: move RageSound_Generic_Software logic into RageSoundDriver --- stepmania/src/arch/Sound/RageSoundDriver.h | 152 +++++++++++++++++- .../RageSoundDriver_Generic_Software.cpp | 44 ++--- .../Sound/RageSoundDriver_Generic_Software.h | 152 ------------------ 3 files changed, 168 insertions(+), 180 deletions(-) diff --git a/stepmania/src/arch/Sound/RageSoundDriver.h b/stepmania/src/arch/Sound/RageSoundDriver.h index 565d95ae59..fac25150c2 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver.h +++ b/stepmania/src/arch/Sound/RageSoundDriver.h @@ -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 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. diff --git a/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.cpp b/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.cpp index 92e61e9691..b5eac1ab53 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.cpp @@ -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" ); } } diff --git a/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.h b/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.h index d46b4c1654..39bf9d9d4d 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_Generic_Software.h @@ -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 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