Rewrite SoundSDL (the SDL sound driver).

Clean up the code and eliminate all deadlocks/hangs/crashes (hopefully.)

Many of the deadlocks were caused by initialize() not de-initializing
properly and causing the audio callback thread to deadlock, fix this.

Also use better logic for the semaphore controls, which will also
hopefully increase audio quality.

Use better logic for the throttle control, with throttle == 0 being the
same as throttle == 100 and implement setThrottle().

Also increase the buffer size to 300ms and the number of samples to
2048, for hopefully less choppiness in audio overall.
This commit is contained in:
Rafael Kitover 2017-08-17 02:44:40 -07:00
parent f88faef1b2
commit 1e3a85a34b
2 changed files with 140 additions and 146 deletions

View File

@ -15,6 +15,8 @@
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <cmath>
#include <iostream>
#include "SoundSDL.h"
#include "ConfigManager.h"
#include "../gba/Globals.h"
@ -22,173 +24,161 @@
extern int emulating;
// Hold up to 32 ms of data in the ring buffer
const float SoundSDL::_delay = 0.032f;
// Hold up to 300 ms of data in the ring buffer
const double SoundSDL::buftime = 0.300;
SoundSDL::SoundSDL():
_rbuf(0),
_dev(-1),
current_rate(throttle),
_initialized(false)
{
samples_buf(0),
sound_device(-1),
current_rate(throttle ? throttle : 100),
initialized(false)
{}
void SoundSDL::soundCallback(void* data, uint8_t* stream, int len) {
reinterpret_cast<SoundSDL*>(data)->read(reinterpret_cast<uint16_t *>(stream), len);
}
void SoundSDL::soundCallback(void *data, uint8_t *stream, int len)
{
reinterpret_cast<SoundSDL*>(data)->read(reinterpret_cast<uint16_t *>(stream), len);
bool SoundSDL::should_wait() {
return emulating && !speedup && current_rate && !gba_joybus_active;
}
bool SoundSDL::should_wait()
{
return emulating && !speedup && throttle && !gba_joybus_active;
std::size_t SoundSDL::buffer_size() {
SDL_LockMutex(mutex);
std::size_t size = samples_buf.used();
SDL_UnlockMutex(mutex);
return size;
}
void SoundSDL::read(uint16_t * stream, int length)
{
if (!_initialized || length <= 0)
return;
void SoundSDL::read(uint16_t* stream, int length) {
if (!initialized || length <= 0)
return;
if (!emulating) {
SDL_memset(stream, _audio_spec.silence, length);
SDL_memset(stream, audio_spec.silence, length);
if (!emulating)
return;
if (!buffer_size())
if (should_wait())
SDL_SemWait(data_available);
else
return;
}
SDL_LockMutex(mutex);
samples_buf.read(stream, std::min((std::size_t)(length / 2), samples_buf.used()));
SDL_UnlockMutex(mutex);
SDL_SemPost(data_read);
}
void SoundSDL::write(uint16_t * finalWave, int length) {
if (!initialized)
return;
SDL_LockMutex(mutex);
if (SDL_GetAudioDeviceStatus(sound_device) != SDL_AUDIO_PLAYING)
SDL_PauseAudioDevice(sound_device, 0);
unsigned int samples = length / 4;
std::size_t avail;
while ((avail = samples_buf.avail() / 2) < samples) {
samples_buf.write(finalWave, avail * 2);
finalWave += avail * 2;
samples -= avail;
SDL_UnlockMutex(mutex);
SDL_SemPost(data_available);
if (should_wait())
SDL_SemWait (_semBufferFull);
SDL_SemWait(data_read);
else
// Drop the remaining of the audio data
return;
SDL_mutexP(_mutex);
SDL_LockMutex(mutex);
}
_rbuf.read(stream, std::min(static_cast<std::size_t>(length) / 2, _rbuf.used()));
samples_buf.write(finalWave, samples * 2);
SDL_mutexV(_mutex);
SDL_SemPost (_semBufferEmpty);
}
void SoundSDL::write(uint16_t * finalWave, int length)
{
if (!_initialized)
return;
if (SDL_GetAudioDeviceStatus(_dev) != SDL_AUDIO_PLAYING)
SDL_PauseAudioDevice(_dev, 0);
SDL_mutexP(_mutex);
unsigned int samples = length / 4;
std::size_t avail;
while ((avail = _rbuf.avail() / 2) < samples)
{
_rbuf.write(finalWave, avail * 2);
finalWave += avail * 2;
samples -= avail;
SDL_mutexV(_mutex);
SDL_SemPost(_semBufferFull);
if (should_wait())
{
SDL_SemWait(_semBufferEmpty);
if (throttle > 0 && throttle != current_rate)
{
SDL_CloseAudioDevice(_dev);
//Reinit on throttle change:
init(soundGetSampleRate());
current_rate = throttle;
}
}
else
{
// Drop the remaining of the audio data
return;
}
SDL_mutexP(_mutex);
}
_rbuf.write(finalWave, samples * 2);
SDL_mutexV(_mutex);
SDL_UnlockMutex(mutex);
}
bool SoundSDL::init(long sampleRate)
{
SDL_AudioSpec audio;
SDL_memset(&audio, 0, sizeof(audio));
audio.freq = throttle ? sampleRate * (throttle / 100.0) : sampleRate;
audio.format = AUDIO_S16SYS;
audio.channels = 2;
audio.samples = 1024;
audio.callback = soundCallback;
audio.userdata = this;
bool SoundSDL::init(long sampleRate) {
if (initialized) deinit();
if (!SDL_WasInit(SDL_INIT_AUDIO)) SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec audio;
SDL_memset(&audio, 0, sizeof(audio));
audio.freq = sampleRate * (current_rate / 100.0);
audio.format = AUDIO_S16SYS;
audio.channels = 2;
audio.samples = 2048;
audio.callback = soundCallback;
audio.userdata = this;
_dev = SDL_OpenAudioDevice(NULL, 0, &audio, &_audio_spec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if(_dev < 0)
{
fprintf(stderr,"Failed to open audio: %s\n", SDL_GetError());
return false;
}
if (!SDL_WasInit(SDL_INIT_AUDIO)) SDL_Init(SDL_INIT_AUDIO);
_rbuf.reset(_delay * sampleRate * 2);
sound_device = SDL_OpenAudioDevice(NULL, 0, &audio, &audio_spec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (!_initialized)
{
_mutex = SDL_CreateMutex();
_semBufferFull = SDL_CreateSemaphore (0);
_semBufferEmpty = SDL_CreateSemaphore (1);
_initialized = true;
}
if(sound_device < 0) {
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
return false;
}
return true;
samples_buf.reset(std::ceil(buftime * sampleRate * 2));
mutex = SDL_CreateMutex();
data_available = SDL_CreateSemaphore(0);
data_read = SDL_CreateSemaphore(1);
return initialized = true;
}
SoundSDL::~SoundSDL()
{
if (!_initialized)
return;
void SoundSDL::deinit() {
if (!initialized)
return;
SDL_mutexP(_mutex);
int iSave = emulating;
emulating = 0;
SDL_SemPost(_semBufferFull);
SDL_SemPost(_semBufferEmpty);
SDL_mutexV(_mutex);
SDL_LockMutex(mutex);
int is_emulating = emulating;
emulating = 0;
SDL_SemPost(data_available);
SDL_SemPost(data_read);
SDL_UnlockMutex(mutex);
SDL_DestroySemaphore(_semBufferFull);
SDL_DestroySemaphore(_semBufferEmpty);
_semBufferFull = NULL;
_semBufferEmpty = NULL;
SDL_DestroySemaphore(data_available);
data_available = nullptr;
SDL_DestroySemaphore(data_read);
data_read = nullptr;
SDL_DestroyMutex(_mutex);
_mutex = NULL;
SDL_DestroyMutex(mutex);
mutex = nullptr;
SDL_CloseAudioDevice(_dev);
SDL_CloseAudioDevice(sound_device);
emulating = iSave;
emulating = is_emulating;
_initialized = false;
initialized = false;
}
void SoundSDL::pause()
{
if (!_initialized)
return;
//SDL_PauseAudioDevice(_dev, 1); // this causes thread deadlocks
SoundSDL::~SoundSDL() {
deinit();
}
void SoundSDL::resume()
{
if (!_initialized)
return;
void SoundSDL::pause() {}
void SoundSDL::resume() {}
//SDL_PauseAudioDevice(_dev, 0); // this causes thread deadlocks
void SoundSDL::reset() {
init(soundGetSampleRate());
}
void SoundSDL::reset()
{
void SoundSDL::setThrottle(unsigned short throttle_) {
current_rate = throttle_ ? throttle_ : 100;
reset();
}

View File

@ -23,9 +23,8 @@
#include "SDL.h"
class SoundSDL : public SoundDriver
{
public:
class SoundSDL : public SoundDriver {
public:
SoundSDL();
virtual ~SoundSDL();
@ -34,26 +33,31 @@ class SoundSDL : public SoundDriver
virtual void reset();
virtual void resume();
virtual void write(uint16_t *finalWave, int length);
virtual void setThrottle(unsigned short throttle_);
protected:
static void soundCallback(void* data, uint8_t* stream, int length);
virtual void read(uint16_t* stream, int length);
virtual bool should_wait();
virtual std::size_t buffer_size();
virtual void deinit();
private:
RingBuffer<uint16_t> _rbuf;
private:
RingBuffer<uint16_t> samples_buf;
SDL_mutex *_mutex;
SDL_sem *_semBufferFull;
SDL_sem *_semBufferEmpty;
SDL_AudioDeviceID _dev;
SDL_AudioSpec _audio_spec;
SDL_AudioDeviceID sound_device = -1;
int current_rate;
SDL_mutex* mutex;
SDL_sem* data_available;
SDL_sem* data_read;
SDL_AudioSpec audio_spec;
bool _initialized;
unsigned short current_rate;
bool initialized = false;
// Defines what delay in seconds we keep in the sound buffer
static const float _delay;
static void soundCallback(void *data, uint8_t *stream, int length);
virtual void read(uint16_t *stream, int length);
static const double buftime;
};
#endif // __VBA_SOUND_SDL_H__