From 71d7ab099c87687f91cf960ec38f7280474b27ab Mon Sep 17 00:00:00 2001 From: Tim Hentenaar Date: Tue, 18 Feb 2003 19:24:18 +0000 Subject: [PATCH] Added RageSound Driver for Linux --- .../src/arch/Sound/RageSoundDriver_Linux.cpp | 248 ++++++++++++++++++ .../src/arch/Sound/RageSoundDriver_Linux.h | 52 ++++ 2 files changed, 300 insertions(+) create mode 100644 stepmania/src/arch/Sound/RageSoundDriver_Linux.cpp create mode 100644 stepmania/src/arch/Sound/RageSoundDriver_Linux.h diff --git a/stepmania/src/arch/Sound/RageSoundDriver_Linux.cpp b/stepmania/src/arch/Sound/RageSoundDriver_Linux.cpp new file mode 100644 index 0000000000..9bab834ea1 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_Linux.cpp @@ -0,0 +1,248 @@ +/* Rage Sound Driver for Linux using ALSA + * (C) 2003 Tim Hentenaar + */ + +#include +#include +#include +#include +#include +#include + +#define ALSA_PCM_NEW_HW_PARAMS_API +#include + +#include "RageSoundDriver_Linux.h" +#include "RageTimer.h" +#include "RageLog.h" +#include "RageSound.h" +#include "RageUtil.h" +#include "SDL.h" +#include "SDL_Thread.h" + +/* Get the chunk size */ +#define CHUNKSIZE(x,y) y = x / 8; + +/* Buffer size in frames */ +#define BUFSIZE_F(x,y) y = x / 2; + +/* Minimum Buffer Size (bytes) */ +#define BUFSIZE(x) if (x < 1024*16) x = 1024*16; + +void RageSound_Linux::OpenAudio() { + /* Open Device [default hardware sound device] (non-blocking) */ + snd_pcm_open(&playback_handle, "hw", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + /* Set up the device parameter struct */ + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_malloc(&hw_params); + snd_pcm_hw_params_any(&hw_params); + + /* Non-Interleaved Access (since we hold the samples in a buffer) */ + snd_pcm_hw_params_set_access(playback_handle,hw_params,SND_PCM_ACCESS_RW_NONINTERLEAVED); + + /* Set the audio format */ + + /* 16-bit signed CPU-Endian */ + snd_pcm_hw_params_set_format(playback_handle,hw_params,SND_PCM_FORMAT_S16); + + /* 44100 Hz Rate (or closest supported match) */ + snd_pcm_hw_params_set_rate_near(playback_handle,hw_params,44100,0); + + /* 2 channels (stereo) */ + snd_pcm_hw_params_set_channels(playback_handle,hw_params,2); + + /* Set Sample Size (channels * 2) */ + snd_pcm_hw_params_set_period_size_near(playback_handle,hw_params,4,NULL); + + /* Get hardware Buffer Size and allocate buffer + * It should be as big as the hardware buffer or 8K whichever + * is larger + */ + snd_pcm_hw_params_get_buffer_size(params, &buffer_size); + sbuffer = (Sint16 *)malloc(BUFSIZE(buffer_size)); /* snd_pcm_sframes_t = long */ + + /* Apply the settings to the device */ + snd_pcm_hw_params(playback_handle,hw_params); + + /* free hw_params struct */ + snd_pcm_hw_params_free(hw_params); +} + +void RageSound_Linux::CloseAudio() { + /* close the PCM */ + snd_pcm_close(playback_handle); + + if (sbuffer) free(sbuffer); /* free sound buffer */ + sbuffer = NULL; +} + +void RageSound_Linux::RecoverState(long state) { + /* Revcover from suspended/overflowed state + * by re-preparing the device for use + */ + + int err = 0; + + switch((int)state) { + case -EPIPE: /* buffer under-run */ + if (snd_pcm_prepare(playback_handle) < 0) + LOG->Trace("RageSound_Linux::RecoverState(): Unable to recover from sound buffer underrun!"); + break; + case -ESTRPIPE: /* suspend */ + if (err = snd_pcm_resume(playback_handle) == -EAGAIN) sleep(1); + err = snd_pcm_resume(playback_handle); + if (err < 0) RageException::Throw(CString("RageSound_Linux::RecoverState(): Unable To recover from suspend!")); + break; + }; + + snd_pcm_prepare(playback_handle); +} + +void RageSound_Linux::StartMixing(RageSound *snd) { + sound *s = new sound; + s->snd = snd; + + SDL_LockAudio(); + sounds.push_back(s); + SDL_UnlockAudio(); +} + +void RageSound_Linux::StopMixing(RageSound *snd) { + LockMutex L(SOUNDMAN->lock); + + /* Find the sound. */ + for(int i = 0; i < sounds.size(); ++i) if(sounds[i]->snd == snd) break; + + if(i == sounds.size()) { + LOG->Trace("RageSound_Linux::StopMixing(): not stopping a sound because it's not playing"); + return; + } + + delete sounds[i]; + sounds.erase(sounds.begin()+i, sounds.begin()+i+1); +} + + +bool RageSound_Linux::GetData() { + + LockMutex L(SOUNDMAN->lock); + + Sint16 *buf = NULL; + + /* Find a free chunk */ + + for (int b = 0; b<8; b++) { + if (chunks[b] != NULL) break; + if (b == 8) return false; + + int bufsiz = 0; + BUFSIZE_F(buffer_size,bufsiz); + bufsiz *= 16; + if (!buf) buf = (Sint16 *)malloc(bufsiz); + + int cs = 0; + CHUNKSIZE(buffer_size,cs); + + memset(buf,0,sizeof(buf)); + memset(chunks[b],0,sizeof(chunks[b])); + + SoundMixBuffer mix; + + for (int i = 0; istopping) continue; + /* Call the Callback */ + unsigned int got = sounds[i]->snd->GetPCM((char *)buf,cs,last_pos); + + mix.write(buf,got/2); + + if (got < chunksize) { + /* sound is finishing */ + sounds[i].stopping = true; + sounds[i].flush_pos = last_pos + (got / 16); + } + + } + + mix.read(chunks[b]); /* Get the sound */ + + snd_pcm_prepare(playback_handle); /* Prepare for write */ + + /* Non-Interleaved Write */ + long ret = snd_pcm_writen(playback_handle,&chunks[b],sizeof(chunks[b])); + RecoverState(ret); /* Check for an error and recover */ + last_pos += cs; + } + + if (buf) free(buf); + return true; +} + +void RageSound_Linux::Update(float delta) { + LockMutex L(SOUNDMAN->lock); + + /* SoundStopped might erase sounds out from under us, so make a copy + * of the sound list. */ + vector snds = sounds; + for(unsigned i = 0; i < snds.size(); ++i) + { + if(!sounds[i]->stopping) continue; + + if(GetPosition(snds[i]->snd) < sounds[i]->flush_pos) continue; /* stopping but still flushing */ + + /* This sound is done. */ + snds[i]->snd->StopPlaying(); + } + +} + +int RageSound_Linux::GetPosition(const RageSound *snd) const { + LockMutex L(SOUNDMAN->lock); + + /* Get a timestamp from the stream's status */ + int err; + snd_pcm_status_t *status; + snd_timestamp_t *x; + + snd_pcm_status_alloca(&status); + if ((err = snd_pcm_status(handle, status)) < 0) { + RageException::Throw(CString(ssprintf("RageSound_Linux::GetPosition(): Stream status error: %s\n", snd_strerror(err)))); + return 0; + } + snd_pcm_status_get_trigger_tstamp(status, x); + + /* Convert it to samples + * To be exact, we should get the rate from the + * hardware, but this should do. (The rate will + * be 44100 Hz anyway :P) + */ + + return (x->tv_usec / 1000) * 44100; +} + +float RageSound_Linux::GetPlayLatency() const { + int bufsize_frames; + BUFSIZE_F(bufsize_frames,buffer_size); + return (float)((1.0f / 44100) * ( bufsize_frames - (bufsize_frames / 8))); +} + +RageSound_Linux::RageSound_Linux() { + last_pos = 0; + MixerThread = SDL_CreateThread(MixerThread_start,this); + sound_event = CreateEvent(NULL, false, true, NULL); +} + +RageSound_Linux::~RageSound_Linux() { + LOG->Trace("RageSoundDriver_Linux::~RageSoundDriver_Linux(): Shutting down mixer thread ..."); + SDL_WaitThread(MixerThreadPtr, NULL); + LOG->Trace("Mixer thread shut down."); + CloseHandle(sound_event); + CloseAudio(); +} + + + + + + + diff --git a/stepmania/src/arch/Sound/RageSoundDriver_Linux.h b/stepmania/src/arch/Sound/RageSoundDriver_Linux.h new file mode 100644 index 0000000000..414d905e39 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_Linux.h @@ -0,0 +1,52 @@ +/* (C) 2003 Tim Hentenaar */ + +#ifndef RAGE_SOUND_LINUX +#define RAGE_SOUND_LINUX + +#include +using namespace std; + +#include "RageSoundDriver.h" +#include "SDL_Thread.h" + + +/* A lot of this is from Glenn's RageSoundDriver_WaveOut */ + +class RageSound_Linux : public RageSoundDriver { + public: + + struct sound { + RageSound *snd; + bool stopping; + int flush_pos; /* state == STOPPING only */ + sound() { snd = NULL; stopping = false; } + }; + + /* Currently Playing Sounds */ + vector sounds; + SDL_Thread *MixerThread; + snd_pcm_sframes_t buffer_size; + Sint16 *sbuffer; /* Sound Buffer */ + Sint16 *chunks[8]; /* chunks of sound buffer */ + snd_pcm_t *playback_handle; /* Handle for PCM device */ + int last_pos; + + void RecoverState(long state); /* Recover from various states */ + + void MixerThread(); + static int MixerThread_start(void *p); + bool GetData(); + void OpenAudio(); + void CloseAudio(); + void StartMixing(RageSound *snd); + void StopMixing(RageSound *snd); + int GetPosition(const RageSound *snd) const; + float GetPlayLatency() const; + + void Update(float delta); + RageSound_Linux(); + ~RageSound_Linux(); +}; + +#endif +