From dd6191bdf17bd77f9a429ddc048378fd42dbfb22 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Tue, 21 Oct 2003 22:51:25 +0000 Subject: [PATCH] split out ALSA stream code --- stepmania/src/arch/Sound/ALSA9Helpers.cpp | 174 ++++++++++++++++++ stepmania/src/arch/Sound/ALSA9Helpers.h | 43 +++++ .../src/arch/Sound/RageSoundDriver_ALSA9.cpp | 172 ++--------------- .../src/arch/Sound/RageSoundDriver_ALSA9.h | 35 +--- 4 files changed, 239 insertions(+), 185 deletions(-) create mode 100644 stepmania/src/arch/Sound/ALSA9Helpers.cpp create mode 100644 stepmania/src/arch/Sound/ALSA9Helpers.h diff --git a/stepmania/src/arch/Sound/ALSA9Helpers.cpp b/stepmania/src/arch/Sound/ALSA9Helpers.cpp new file mode 100644 index 0000000000..3baffd6f67 --- /dev/null +++ b/stepmania/src/arch/Sound/ALSA9Helpers.cpp @@ -0,0 +1,174 @@ +#include "global.h" +#include "RageLog.h" +#include "ALSA9Helpers.h" +#include "SDL_utils.h" + +/* int err; must be defined before using this macro */ +#define ALSA_ASSERT(x) \ + if (err < 0) { LOG->Trace("RageSound_ALSA9: ASSERT %s: %s", x, snd_strerror(err)); } + +Alsa9Buf::Alsa9Buf( hw hardware, int channels_, int samplerate_, int writeahead_ ) +{ + channels = channels_; + samplerate = samplerate_; + samplebits = 16; + writeahead = writeahead_; + // buffer_locked = false; + last_cursor_pos = 0; + //write_cursor = LastPosition = buffer_bytes_filled = 0; + + /* open the device */ + // if we instead use plughw: then our requested format WILL be provided + int err; + err = snd_pcm_open( &pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ); + if (err < 0) + RageException::ThrowNonfatal("snd_pcm_open: %s", snd_strerror(err)); + + /* allocate the hardware parameters structure */ + snd_pcm_hw_params_t *hwparams; + err = snd_pcm_hw_params_malloc(&hwparams); + ALSA_ASSERT("snd_pcm_hw_params_malloc"); + + /* not exactly sure what this does */ + err = snd_pcm_hw_params_any(pcm, hwparams); + ALSA_ASSERT("snd_pcm_hw_params_any"); + + /* set to mmap mode (with channels interleaved) */ + err = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED); + ALSA_ASSERT("snd_pcm_hw_params_set_access"); + + /* set PCM format (signed 16bit, little endian) */ + err = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE); + ALSA_ASSERT("snd_pcm_hw_params_set_format"); + + /* set number of channels */ + err = snd_pcm_hw_params_set_channels(pcm, hwparams, 2); + ALSA_ASSERT("snd_pcm_hw_params_set_channels"); + + unsigned int rate = samplerate; + err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0); + // check if we got the rate we desire + + /* write the hardware parameters to the device */ + err = snd_pcm_hw_params(pcm, hwparams); + ALSA_ASSERT("snd_pcm_hw_params"); + + snd_pcm_hw_params_free(hwparams); + + /* prepare the device to reveice data */ + err = snd_pcm_prepare(pcm); + ALSA_ASSERT("snd_pcm_prepare"); + + //XXX should RageException::ThrowNonfatal if something went wrong + + + /* prepare a snd_output_t for use with LOG->Trace */ + /* snd_output_t *errout = NULL; + snd_output_buffer_open(&errout); + snd_pcm_dump(pcm, errout); + snd_output_flush(errout); + + char *errstring; + snd_output_buffer_string(errout, &errstring); + LOG->Trace("%s", errstring); + snd_output_close( errout ); +*/ + total_frames = snd_pcm_avail_update(pcm); + + +} + +Alsa9Buf::~Alsa9Buf() +{ + snd_pcm_close(pcm); +} + + +int Alsa9Buf::GetNumFramesToFill() +{ + snd_pcm_sframes_t avail_frames = snd_pcm_avail_update(pcm); + if( avail_frames < 0 && Recover(avail_frames) ) + avail_frames = snd_pcm_avail_update(pcm); + + if( avail_frames < 0 ) + { + LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_avail_update: %s", snd_strerror(avail_frames) ); + return 0; + } + + const snd_pcm_sframes_t filled_frames = total_frames - avail_frames; + const snd_pcm_sframes_t frames_to_fill = (writeahead - filled_frames) & ~3; + ASSERT( frames_to_fill <= (int)writeahead ); + + // LOG->Trace("%li avail, %li filled, %li to", avail_frames, filled_frames, frames_to_fill); + return max( 0l, frames_to_fill ); +} + +void Alsa9Buf::Write( const Sint16 *buffer, int frames ) +{ + /* We should be able to write it all. If we don't, treat it as an error. */ + int wrote = snd_pcm_mmap_writei( pcm, (const char *) buffer, frames ); + if( wrote < 0 && Recover(wrote) ) + wrote = snd_pcm_mmap_writei( pcm, (const char *) buffer, frames ); + + if( wrote < 0 ) + { + LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_mmap_writei: %s", snd_strerror(wrote) ); + return; + } + + last_cursor_pos += wrote; + if( wrote < frames ) + LOG->Trace("Couldn't write whole buffer? (%i < %i)\n", wrote, frames ); +} + + + +/* + * When the play buffer underruns, subsequent writes to the buffer + * return -EPIPE. When this happens, call Recover() to restart playback. + */ +bool Alsa9Buf::Recover( int r ) +{ + if( r == -EPIPE ) + { + LOG->Trace("RageSound_ALSA9::Recover (prepare)"); + int err = snd_pcm_prepare(pcm); + ALSA_ASSERT("snd_pcm_prepare (Recover)"); + return true; + } + + if( r == -ESTRPIPE ) + { + LOG->Trace("RageSound_ALSA9::Recover (resume)"); + int err; + while ((err = snd_pcm_resume(pcm)) == -EAGAIN) + SDL_Delay(10); + + ALSA_ASSERT("snd_pcm_resume (Recover)"); + return true; + } + + return false; +} + +int Alsa9Buf::GetPosition() const +{ + snd_pcm_status_t *status; + snd_pcm_status_alloca(&status); + + int err = snd_pcm_status( pcm, status ); + + ALSA_ASSERT("snd_pcm_status"); + + snd_pcm_state_t state = snd_pcm_status_get_state( status ); + if ( state == SND_PCM_STATE_PREPARED ) + return 0; + + snd_pcm_hwsync( pcm ); + /* delay is returned in frames */ + snd_pcm_sframes_t delay = snd_pcm_status_get_delay(status); + + return last_cursor_pos - delay; +} + diff --git a/stepmania/src/arch/Sound/ALSA9Helpers.h b/stepmania/src/arch/Sound/ALSA9Helpers.h new file mode 100644 index 0000000000..a1ca49ba43 --- /dev/null +++ b/stepmania/src/arch/Sound/ALSA9Helpers.h @@ -0,0 +1,43 @@ +#ifndef ALSA9_HELPERS_H +#define ALSA9_HELPERS_H + +#define ALSA_PCM_NEW_HW_PARAMS_API +#include + +#include "SDL_types.h" + +class Alsa9Buf +{ +private: + int channels, samplerate, samplebits, writeahead; + int buffersize, last_cursor_pos; + + snd_pcm_sframes_t total_frames; + + + snd_pcm_t *pcm; + + bool Recover( int r ); + +public: + enum hw { HW_HARDWARE, HW_SOFTWARE, HW_DONT_CARE }; + + /* If samplerate is DYNAMIC_SAMPLERATE, then call SetSampleRate before + * you use the sample. */ + enum { DYNAMIC_SAMPLERATE = -1 }; + Alsa9Buf( hw hardware, int channels, int samplerate, int writeahead ); + ~Alsa9Buf(); + + int GetNumFramesToFill(); + void Write( const Sint16 *buffer, int frames ); + + void Reset(); + void Play(); + void Stop(); + void SetVolume(float vol); + void SetSampleRate(int hz); + + int GetPosition() const; + int GetPlayPos() const { return last_cursor_pos; } +}; +#endif diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp index 03ce82b17b..ce69c0125e 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp @@ -7,8 +7,6 @@ #include "RageSoundManager.h" #include "RageUtil.h" -#include "SDL_utils.h" - const int channels = 2; const int samplerate = 44100; @@ -17,17 +15,6 @@ const int bytes_per_frame = sizeof(Sint16) * samples_per_frame; const unsigned max_writeahead = 1024*8; - -/* int err; must be defined before using this macro */ -#define ALSA_ASSERT(x) \ - if (err < 0) \ - { \ - LOG->Trace("RageSound_ALSA9: ASSERT %s: %s", \ - x, snd_strerror(err)); \ - } - - - int RageSound_ALSA9::MixerThread_start(void *p) { ((RageSound_ALSA9 *) p)->MixerThread(); @@ -42,35 +29,18 @@ void RageSound_ALSA9::MixerThread() while(!shutdown) { - unsigned int frames_read = GetData(); + GetData(); const float delay_ms = 1000 * float(max_writeahead) / samplerate; SDL_Delay( int(delay_ms) / 2); } } /* Returns the number of frames processed */ -int RageSound_ALSA9::GetData() +void RageSound_ALSA9::GetData() { - LockMutex L(SOUNDMAN->lock); - - snd_pcm_sframes_t avail_frames = snd_pcm_avail_update(pcm); - if( avail_frames < 0 && Recover(avail_frames) ) - avail_frames = snd_pcm_avail_update(pcm); - - if( avail_frames < 0 ) - { - LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_avail_update: %s", snd_strerror(avail_frames) ); - return 0; - } - - const snd_pcm_sframes_t filled_frames = total_frames - avail_frames; - const snd_pcm_sframes_t frames_to_fill = (max_writeahead - filled_frames) & ~3; - ASSERT( frames_to_fill >= 0 ); - ASSERT( frames_to_fill <= (int)max_writeahead ); - -// LOG->Trace("%li avail, %li filled, %li to", avail_frames, filled_frames, frames_to_fill); + const int frames_to_fill = pcm->GetNumFramesToFill(); if( frames_to_fill <= 0 ) - return 0; + return; /* Sint16 represents a single sample * each frame contains one sample per channel @@ -82,6 +52,7 @@ int RageSound_ALSA9::GetData() static SoundMixBuffer mix; mix.SetVolume( SOUNDMAN->GetMixVolume() ); + LockMutex L(SOUNDMAN->lock); for(unsigned i = 0; i < sounds.size(); ++i) { if(sounds[i]->stopping) @@ -91,7 +62,7 @@ int RageSound_ALSA9::GetData() * Get the units straight, * = GetPCM(, , ) */ - unsigned got = sounds[i]->snd->GetPCM( (char *) buf, frames_to_fill*bytes_per_frame, last_cursor_pos ) / sizeof(Sint16); + unsigned got = sounds[i]->snd->GetPCM( (char *) buf, frames_to_fill*bytes_per_frame, pcm->GetPlayPos() ) / sizeof(Sint16); mix.write((Sint16 *) buf, got); if( int(got) < frames_to_fill ) @@ -100,55 +71,12 @@ int RageSound_ALSA9::GetData() sounds[i]->stopping = true; } } + L.Unlock(); memset( buf, 0, sizeof(Sint16) * frames_to_fill * samples_per_frame ); - mix.read((Sint16*)buf); + mix.read( buf ); - /* We should be able to write it all. If we don't, treat it as an error. */ - int wrote = snd_pcm_mmap_writei( pcm, buf, frames_to_fill ); - if( wrote < 0 && Recover(wrote) ) - wrote = snd_pcm_mmap_writei( pcm, buf, frames_to_fill ); - - if( wrote < 0 ) - { - LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_mmap_writei: %s", snd_strerror(wrote) ); - return -1; - } - - last_cursor_pos += wrote; - if( wrote < frames_to_fill ) - LOG->Trace("Couldn't write whole buffer? (%i < %li)\n", wrote, frames_to_fill ); - - return frames_to_fill; -} - -/** - * When the play buffer underruns, subsequent writes to the buffer - * return -EPIPE. When this happens, call Recover() to restart - * playback. - */ -bool RageSound_ALSA9::Recover(int r) -{ - if( r == -EPIPE ) - { - LOG->Trace("RageSound_ALSA9::Recover (prepare)"); - int err = snd_pcm_prepare(pcm); - ALSA_ASSERT("snd_pcm_prepare (Recover)"); - return true; - } - - if( r == -ESTRPIPE ) - { - LOG->Trace("RageSound_ALSA9::Recover (resume)"); - int err; - while ((err = snd_pcm_resume(pcm)) == -EAGAIN) - SDL_Delay(10); - - ALSA_ASSERT("snd_pcm_resume (Recover)"); - return true; - } - - return false; + pcm->Write( buf, frames_to_fill ); } @@ -201,90 +129,16 @@ void RageSound_ALSA9::StopMixing(RageSound *snd) int RageSound_ALSA9::GetPosition(const RageSound *snd) const { - LockMutex L(SOUNDMAN->lock); - - snd_pcm_status_t *status; - snd_pcm_status_alloca(&status); - - int err = snd_pcm_status( pcm, status ); - - ALSA_ASSERT("snd_pcm_status"); - - snd_pcm_state_t state = snd_pcm_status_get_state( status ); - if ( state == SND_PCM_STATE_PREPARED ) - return 0; - - snd_pcm_hwsync( pcm ); - /* delay is returned in frames */ - snd_pcm_sframes_t delay = snd_pcm_status_get_delay(status); - - return last_cursor_pos - delay; + return pcm->GetPosition(); } RageSound_ALSA9::RageSound_ALSA9() { shutdown = false; - last_cursor_pos = 0; - - /* open the device */ - // if we instead use plughw: then our requested format WILL be provided - int err; - err = snd_pcm_open( &pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ); - if (err < 0) - RageException::ThrowNonfatal("snd_pcm_open: %s", snd_strerror(err)); - - /* allocate the hardware parameters structure */ - snd_pcm_hw_params_t *hwparams; - err = snd_pcm_hw_params_malloc(&hwparams); - ALSA_ASSERT("snd_pcm_hw_params_malloc"); - - /* not exactly sure what this does */ - err = snd_pcm_hw_params_any(pcm, hwparams); - ALSA_ASSERT("snd_pcm_hw_params_any"); - - /* set to mmap mode (with channels interleaved) */ - err = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED); - ALSA_ASSERT("snd_pcm_hw_params_set_access"); - - /* set PCM format (signed 16bit, little endian) */ - err = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE); - ALSA_ASSERT("snd_pcm_hw_params_set_format"); - - /* set number of channels */ - err = snd_pcm_hw_params_set_channels(pcm, hwparams, 2); - ALSA_ASSERT("snd_pcm_hw_params_set_channels"); - - unsigned int rate = samplerate; - err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0); - // check if we got the rate we desire - - /* write the hardware parameters to the device */ - err = snd_pcm_hw_params(pcm, hwparams); - ALSA_ASSERT("snd_pcm_hw_params"); - - snd_pcm_hw_params_free(hwparams); - - /* prepare the device to reveice data */ - err = snd_pcm_prepare(pcm); - ALSA_ASSERT("snd_pcm_prepare"); - - //XXX should RageException::ThrowNonfatal if something went wrong - - - /* prepare a snd_output_t for use with LOG->Trace */ - snd_output_t *errout = NULL; - snd_output_buffer_open(&errout); - snd_pcm_dump(pcm, errout); - snd_output_flush(errout); - - char *errstring; - snd_output_buffer_string(errout, &errstring); - LOG->Trace("%s", errstring); - snd_output_close( errout ); - - total_frames = snd_pcm_avail_update(pcm); + pcm = new Alsa9Buf( Alsa9Buf::HW_DONT_CARE, channels, samplerate, max_writeahead ); + MixingThread.SetName( "RageSound_ALSA9" ); MixingThread.Create( MixerThread_start, this ); } @@ -297,7 +151,7 @@ RageSound_ALSA9::~RageSound_ALSA9() MixingThread.Wait(); LOG->Trace("Mixer thread shut down."); - snd_pcm_close(pcm); + delete pcm; } float RageSound_ALSA9::GetPlayLatency() const diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h index d12d59fd1f..05367de594 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h @@ -1,54 +1,37 @@ #ifndef RAGE_SOUND_ALSA9 #define RAGE_SOUND_ALSA9 -/* RageSound_ALSA9 fakes playing sounds having GetPosition() - * return seconds since the constructor was called. - * - * Only tested in linux, but intended to work across the globe. - * ( uses time_t, sleep() and nanosleep() from ) - * - * The timing probably isn't accurate, but at least it is fairly - * steady. Someone with more knowledge of RageSound, feel free - * to play around with this (not that it's any sort of priority). - */ - #include "RageSound.h" #include "RageThreads.h" #include "RageSoundDriver.h" -#define ALSA_PCM_NEW_HW_PARAMS_API -#include - -/* needed because ALSA returns struct timeval info */ -#include +#include "ALSA9Helpers.h" class RageSound_ALSA9: public RageSoundDriver { -public: - struct sound { +private: + struct sound + { RageSound *snd; bool stopping; - int flush_pos; /* state == STOPPING only */ - sound() { snd = NULL; stopping=false; } + int flush_pos; /* state == STOPPING only */ + sound() { snd = NULL; stopping=false; } }; /* List of currently playing sounds: */ vector sounds; - snd_pcm_sframes_t total_frames; - bool shutdown; - int last_cursor_pos; - snd_pcm_t *pcm; + Alsa9Buf *pcm; static int MixerThread_start(void *p); void MixerThread(); RageThread MixingThread; - int GetData(); - bool Recover( int r ); + void GetData(); +public: /* virtuals: */ void StartMixing(RageSound *snd); void StopMixing(RageSound *snd);