From 973c7123c9d5d012260016b98a97fb09479e4479 Mon Sep 17 00:00:00 2001 From: bgk Date: Tue, 30 Dec 2008 14:34:33 +0000 Subject: [PATCH] Simplified the SDL audio driver by using a generic ring buffer (from Gambatte). Changed the A/V max delay and sound update framerate to saner values. Emulation smoothness and sound quality have improved on my system. Please report any issue. --- src/Sound.cpp | 7 +- src/common/Array.h | 40 ++++++++ src/common/RingBuffer.h | 112 ++++++++++++++++++++++ src/common/SoundDriver.h | 7 +- src/common/SoundSDL.cpp | 177 ++++++++++++++--------------------- src/common/SoundSDL.h | 26 +++-- src/dmg/gb_apu/Blip_Buffer.h | 2 +- src/win32/DirectSound.cpp | 10 +- src/win32/OpenAL.cpp | 10 +- src/win32/XAudio2.cpp | 11 +-- 10 files changed, 248 insertions(+), 154 deletions(-) create mode 100644 src/common/Array.h create mode 100644 src/common/RingBuffer.h diff --git a/src/Sound.cpp b/src/Sound.cpp index 652641d3..d246e763 100644 --- a/src/Sound.cpp +++ b/src/Sound.cpp @@ -348,8 +348,11 @@ static void end_frame( blip_time_t time ) void flush_samples(Multi_Buffer * buffer) { - // get the size in bytes of the sound driver buffer - int soundBufferLen = soundDriver->getBufferLength(); + // We want to write the data frame by frame to support legacy audio drivers + // that don't use the length parameter of the write method. + // TODO: Update the Win32 audio drivers (DS, OAL, XA2), and flush all the + // samples at once to help reducing the audio delay on all platforms. + int soundBufferLen = ( soundSampleRate / 60 ) * 4; // soundBufferLen should have a whole number of sample pairs assert( soundBufferLen % (2 * sizeof *soundFinalWave) == 0 ); diff --git a/src/common/Array.h b/src/common/Array.h new file mode 100644 index 00000000..f01806ea --- /dev/null +++ b/src/common/Array.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef ARRAY_H +#define ARRAY_H + +#include + +template +class Array { + T *a; + std::size_t sz; + + Array(const Array &ar); + +public: + Array(const std::size_t size = 0) : a(size ? new T[size] : 0), sz(size) {} + ~Array() { delete []a; } + void reset(const std::size_t size) { delete []a; a = size ? new T[size] : 0; sz = size; } + std::size_t size() const { return sz; } + operator T*() { return a; } + operator const T*() const { return a; } +}; + +#endif diff --git a/src/common/RingBuffer.h b/src/common/RingBuffer.h new file mode 100644 index 00000000..137df0a0 --- /dev/null +++ b/src/common/RingBuffer.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RINGBUFFER_H +#define RINGBUFFER_H + +#include "Array.h" +#include +#include +#include + +template +class RingBuffer { + Array buf; + std::size_t sz; + std::size_t rpos; + std::size_t wpos; + +public: + RingBuffer(const std::size_t sz_in = 0) : sz(0), rpos(0), wpos(0) { reset(sz_in); } + + std::size_t avail() const { + return (wpos < rpos ? 0 : sz) + rpos - wpos - 1; + } + + void clear() { + wpos = rpos = 0; + } + + void fill(T value); + + void read(T *out, std::size_t num); + + void reset(std::size_t sz_in); + + std::size_t size() const { + return sz - 1; + } + + std::size_t used() const { + return (wpos < rpos ? sz : 0) + wpos - rpos; + } + + void write(const T *in, std::size_t num); +}; + +template +void RingBuffer::fill(const T value) { + std::fill(buf + 0, buf + sz, value); + rpos = 0; + wpos = sz - 1; +} + +template +void RingBuffer::read(T *out, std::size_t num) { + if (rpos + num > sz) { + const std::size_t n = sz - rpos; + + std::memcpy(out, buf + rpos, n * sizeof(T)); + + rpos = 0; + num -= n; + out += n; + } + + std::memcpy(out, buf + rpos, num * sizeof(T)); + + if ((rpos += num) == sz) + rpos = 0; +} + +template +void RingBuffer::reset(const std::size_t sz_in) { + sz = sz_in + 1; + rpos = wpos = 0; + buf.reset(sz_in ? sz : 0); +} + +template +void RingBuffer::write(const T *in, std::size_t num) { + if (wpos + num > sz) { + const std::size_t n = sz - wpos; + + std::memcpy(buf + wpos, in, n * sizeof(T)); + + wpos = 0; + num -= n; + in += n; + } + + std::memcpy(buf + wpos, in, num * sizeof(T)); + + if ((wpos += num) == sz) + wpos = 0; +} + +#endif diff --git a/src/common/SoundDriver.h b/src/common/SoundDriver.h index 7ecd1182..578b3ec0 100644 --- a/src/common/SoundDriver.h +++ b/src/common/SoundDriver.h @@ -55,12 +55,7 @@ public: /** * Write length bytes of data from the finalWave buffer to the driver output buffer. */ - virtual void write(const u16 * finalWave, int length) = 0; - - /** - * Return the size in bytes of the core sound buffer. - */ - virtual int getBufferLength() = 0; + virtual void write(u16 * finalWave, int length) = 0; virtual void setThrottle(unsigned short throttle) { }; }; diff --git a/src/common/SoundSDL.cpp b/src/common/SoundSDL.cpp index ef170149..82249d4c 100644 --- a/src/common/SoundSDL.cpp +++ b/src/common/SoundSDL.cpp @@ -6,153 +6,120 @@ extern int emulating; -u8 SoundSDL::_buffer[_bufferTotalLen]; -int SoundSDL::_readPosition; -int SoundSDL::_writePosition; -SDL_cond * SoundSDL::_cond; -SDL_mutex * SoundSDL::_mutex; - -inline int SoundSDL::getBufferFree() +SoundSDL::SoundSDL(): + _rbuf(0) { - int ret = _readPosition - _writePosition - _bufferAlign; - if (ret < 0) - ret += _bufferTotalLen; - return ret; + } -inline int SoundSDL::getBufferUsed() +void SoundSDL::soundCallback(void *data, u8 *stream, int len) { - int ret = _writePosition - _readPosition; - if (ret < 0) - ret += _bufferTotalLen; - return ret; + reinterpret_cast(data)->read(reinterpret_cast(stream), len); } -void SoundSDL::soundCallback(void *,u8 *stream,int len) +void SoundSDL::read(u16 * stream, int length) { - if (len <= 0 || !emulating) - return; + if (length <= 0 || !emulating) + return; - SDL_mutexP(_mutex); - const int nAvail = getBufferUsed(); - if (len > nAvail) - len = nAvail; - const int nAvail2 = _bufferTotalLen - _readPosition; - if (len >= nAvail2) { - memcpy(stream, &_buffer[_readPosition], nAvail2); - _readPosition = 0; - stream += nAvail2; - len -= nAvail2; - } - if (len > 0) { - memcpy(stream, &_buffer[_readPosition], len); - _readPosition = (_readPosition + len) % _bufferTotalLen; - stream += len; - } - SDL_CondSignal(_cond); - SDL_mutexV(_mutex); + SDL_mutexP(_mutex); + + _rbuf.read(stream, std::min(static_cast(length) / 2, _rbuf.used())); + + SDL_CondSignal(_cond); + SDL_mutexV(_mutex); } -void SoundSDL::write(const u16 * finalWave, int length) +void SoundSDL::write(u16 * finalWave, int length) { - if (SDL_GetAudioStatus() != SDL_AUDIO_PLAYING) - { - SDL_PauseAudio(0); - } + if (SDL_GetAudioStatus() != SDL_AUDIO_PLAYING) + SDL_PauseAudio(0); - int remain = length; - const u8 *wave = reinterpret_cast(finalWave); + SDL_mutexP(_mutex); - SDL_mutexP(_mutex); + unsigned int samples = length / 4; - int n; - while (remain >= (n = getBufferFree())) { - const int nAvail = (_bufferTotalLen - _writePosition) < n ? (_bufferTotalLen - _writePosition) : n; - memcpy(&_buffer[_writePosition], wave, nAvail); - _writePosition = (_writePosition + nAvail) % _bufferTotalLen; - wave += nAvail; - remain -= nAvail; + std::size_t avail; + while ((avail = _rbuf.avail() / 2) < samples) + { + _rbuf.write(finalWave, avail * 2); - if (!emulating || speedup || systemThrottle) { - SDL_mutexV(_mutex); - return; - } - SDL_CondWait(_cond, _mutex); - } + finalWave += avail * 2; + samples -= avail; - const int nAvail = _bufferTotalLen - _writePosition; - if (remain >= nAvail) { - memcpy(&_buffer[_writePosition], wave, nAvail); - _writePosition = 0; - wave += nAvail; - remain -= nAvail; - } - if (remain > 0) { - memcpy(&_buffer[_writePosition], wave, remain); - _writePosition = (_writePosition + remain) % _bufferTotalLen; - } - SDL_mutexV(_mutex); + // If emulating and not in speed up mode, synchronize to audio + // by waiting till there is enough room in the buffer + if (emulating && !speedup && !systemThrottle) + { + SDL_CondWait(_cond, _mutex); + } + else + { + // Drop the remaining of the audio data + SDL_mutexV(_mutex); + return; + } + } + + _rbuf.write(finalWave, samples * 2); + + SDL_mutexV(_mutex); } + bool SoundSDL::init(long sampleRate) { - SDL_AudioSpec audio; + SDL_AudioSpec audio; + audio.freq = sampleRate; + audio.format = AUDIO_S16SYS; + audio.channels = 2; + audio.samples = sampleRate / 60; + audio.callback = soundCallback; + audio.userdata = this; - _bufferLen = sampleRate * 4 / 60; + if(SDL_OpenAudio(&audio, NULL)) + { + fprintf(stderr,"Failed to open audio: %s\n", SDL_GetError()); + return false; + } - audio.freq = sampleRate; - audio.format = AUDIO_S16SYS; - audio.channels = 2; - audio.samples = 1024; - audio.callback = soundCallback; - audio.userdata = NULL; - if(SDL_OpenAudio(&audio, NULL)) { - fprintf(stderr,"Failed to open audio: %s\n", SDL_GetError()); - return false; - } + _rbuf.reset(_delay * sampleRate * 2); - _cond = SDL_CreateCond(); - _mutex = SDL_CreateMutex(); - - _readPosition = _writePosition = 0; - return true; + _cond = SDL_CreateCond(); + _mutex = SDL_CreateMutex(); + return true; } SoundSDL::~SoundSDL() { - SDL_mutexP(_mutex); - int iSave = emulating; - emulating = 0; - SDL_CondSignal(_cond); - SDL_mutexV(_mutex); + SDL_mutexP(_mutex); + int iSave = emulating; + emulating = 0; + SDL_CondSignal(_cond); + SDL_mutexV(_mutex); - SDL_DestroyCond(_cond); - _cond = NULL; + SDL_DestroyCond(_cond); + _cond = NULL; - SDL_DestroyMutex(_mutex); - _mutex = NULL; + SDL_DestroyMutex(_mutex); + _mutex = NULL; - SDL_CloseAudio(); + SDL_CloseAudio(); - emulating = iSave; + emulating = iSave; } void SoundSDL::pause() { - SDL_PauseAudio(1); + SDL_PauseAudio(1); } void SoundSDL::resume() { - SDL_PauseAudio(0); + SDL_PauseAudio(0); } void SoundSDL::reset() { } - -int SoundSDL::getBufferLength() -{ - return _bufferLen; -} diff --git a/src/common/SoundSDL.h b/src/common/SoundSDL.h index e7de9f79..8a00f00f 100644 --- a/src/common/SoundSDL.h +++ b/src/common/SoundSDL.h @@ -19,35 +19,31 @@ #define __VBA_SOUND_SDL_H__ #include "SoundDriver.h" +#include "RingBuffer.h" class SoundSDL: public SoundDriver { public: + SoundSDL(); virtual ~SoundSDL(); virtual bool init(long sampleRate); virtual void pause(); virtual void reset(); virtual void resume(); - virtual void write(const u16 * finalWave, int length); - virtual int getBufferLength(); + virtual void write(u16 * finalWave, int length); private: - static const int _sampleCount = 4096; - static const int _bufferAlign = 4; - static const int _bufferCapacity = _sampleCount * 2; - static const int _bufferTotalLen = _bufferCapacity + _bufferAlign; + RingBuffer _rbuf; - static u8 _buffer[_bufferTotalLen]; - static int _readPosition; - static int _writePosition; - static SDL_cond * _cond; - static SDL_mutex * _mutex; - int _bufferLen; + SDL_cond * _cond; + SDL_mutex * _mutex; - static int getBufferFree(); - static int getBufferUsed(); - static void soundCallback(void *, u8 *stream, int len); + // Hold up to 100 ms of data in the ring buffer + static const float _delay = 0.1f; + + static void soundCallback(void *data, u8 *stream, int length); + virtual void read(u16 * stream, int length); }; #endif // __VBA_SOUND_SDL_H__ diff --git a/src/dmg/gb_apu/Blip_Buffer.h b/src/dmg/gb_apu/Blip_Buffer.h index 850f0957..699bff7d 100644 --- a/src/dmg/gb_apu/Blip_Buffer.h +++ b/src/dmg/gb_apu/Blip_Buffer.h @@ -40,7 +40,7 @@ public: // a new time frame at the end of the current frame. void end_frame( blip_time_t time ); - // Reads at most 'max_samples' out of buffer into 'dest', removing them from from + // Reads at most 'max_samples' out of buffer into 'dest', removing them from // the buffer. Returns number of samples actually read and removed. If stereo is // true, increments 'dest' one extra time after writing each sample, to allow // easy interleving of two channels into a stereo output buffer. diff --git a/src/win32/DirectSound.cpp b/src/win32/DirectSound.cpp index 051d6658..9967194a 100644 --- a/src/win32/DirectSound.cpp +++ b/src/win32/DirectSound.cpp @@ -38,8 +38,7 @@ public: void pause(); // pause the secondary sound buffer void reset(); // stop and reset the secondary sound buffer void resume(); // resume the secondary sound buffer - void write(const u16 * finalWave, int length); // write the emulated sound to the secondary sound buffer - virtual int getBufferLength(); + void write(u16 * finalWave, int length); // write the emulated sound to the secondary sound buffer }; @@ -224,7 +223,7 @@ void DirectSound::resume() } -void DirectSound::write(const u16 * finalWave, int length) +void DirectSound::write(u16 * finalWave, int length) { if(!pDirectSound) return; @@ -307,11 +306,6 @@ void DirectSound::write(const u16 * finalWave, int length) } } -int DirectSound::getBufferLength() -{ - return soundBufferLen; -} - SoundDriver *newDirectSound() { return new DirectSound(); diff --git a/src/win32/OpenAL.cpp b/src/win32/OpenAL.cpp index 765ae303..b0c80634 100644 --- a/src/win32/OpenAL.cpp +++ b/src/win32/OpenAL.cpp @@ -41,8 +41,7 @@ public: void pause(); // pause the secondary sound buffer void reset(); // stop and reset the secondary sound buffer void resume(); // play/resume the secondary sound buffer - void write(const u16 * finalWave, int length); // write the emulated sound to a sound buffer - virtual int getBufferLength(); + void write(u16 * finalWave, int length); // write the emulated sound to a sound buffer private: OPENALFNTABLE ALFunction; @@ -242,7 +241,7 @@ void OpenAL::reset() } -void OpenAL::write(const u16 * finalWave, int length) +void OpenAL::write(u16 * finalWave, int length) { if( !initialized ) return; winlog( "OpenAL::write\n" ); @@ -321,11 +320,6 @@ void OpenAL::write(const u16 * finalWave, int length) } } -int OpenAL::getBufferLength() -{ - return soundBufferLen; -} - SoundDriver *newOpenAL() { winlog( "newOpenAL\n" ); diff --git a/src/win32/XAudio2.cpp b/src/win32/XAudio2.cpp index 10feca84..be658435 100644 --- a/src/win32/XAudio2.cpp +++ b/src/win32/XAudio2.cpp @@ -59,7 +59,7 @@ public: bool init(long sampleRate); // Sound Data Feed - void write(const u16 * finalWave, int length); + void write(u16 * finalWave, int length); // Play Control void pause(); @@ -69,8 +69,6 @@ public: // Configuration Changes void setThrottle( unsigned short throttle ); - virtual int getBufferLength(); - private: bool failed; bool initialized; @@ -280,7 +278,7 @@ bool XAudio2_Output::init(long sampleRate) } -void XAudio2_Output::write(const u16 * finalWave, int length) +void XAudio2_Output::write(u16 * finalWave, int length) { if( !initialized || failed ) return; @@ -373,11 +371,6 @@ void XAudio2_Output::setThrottle( unsigned short throttle ) ASSERT( hr == S_OK ); } -int XAudio2_Output::getBufferLength() -{ - return soundBufferLen; -} - SoundDriver *newXAudio2_Output() { return new XAudio2_Output();