Gtk: Simplify drivers by moving port code to gtk_sound.cpp.

This commit is contained in:
BearOso 2023-06-07 15:34:10 -05:00
parent 9a0712b258
commit 8c5b6d012e
12 changed files with 242 additions and 365 deletions

View File

@ -150,7 +150,7 @@ void S9xPortSoundDeinit()
S9xSoundStop(); S9xSoundStop();
if (driver) if (driver)
driver->terminate(); driver->deinit();
delete driver; delete driver;
} }
@ -167,6 +167,55 @@ void S9xSoundStop()
driver->stop(); driver->stop();
} }
static std::vector<int16_t> temp_buffer;
void S9xSamplesAvailable(void *userdata)
{
bool clear_leftover_samples = false;
int samples = S9xGetSampleCount();
int space_free = driver->space_free();
if (space_free < samples)
{
if (!Settings.SoundSync)
clear_leftover_samples = true;
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{
for (int i = 0; i < 200; i++) // Wait for a max of 5ms
{
space_free = driver->space_free();
if (space_free < samples)
usleep(50);
else
break;
}
}
}
if (space_free < samples)
samples = space_free & ~1;
if (samples == 0)
{
S9xClearSamples();
return;
}
if ((int)temp_buffer.size() < samples)
temp_buffer.resize(samples);
S9xMixSamples((uint8_t *)temp_buffer.data(), samples);
driver->write_samples(temp_buffer.data(), samples);
if (clear_leftover_samples)
S9xClearSamples();
if (Settings.DynamicRateControl)
{
auto level = driver->buffer_level();
S9xUpdateDynamicRate(level.first, level.second);
}
}
bool8 S9xOpenSoundDevice() bool8 S9xOpenSoundDevice()
{ {
if (gui_config->mute_sound) if (gui_config->mute_sound)
@ -174,7 +223,8 @@ bool8 S9xOpenSoundDevice()
gui_config->sound_buffer_size = CLAMP(gui_config->sound_buffer_size, 2, 256); gui_config->sound_buffer_size = CLAMP(gui_config->sound_buffer_size, 2, 256);
return driver->open_device(); S9xSetSamplesAvailableCallback(S9xSamplesAvailable, nullptr);
return driver->open_device(Settings.SoundPlaybackRate, gui_config->sound_buffer_size);
} }
/* This really shouldn't be in the port layer */ /* This really shouldn't be in the port layer */

View File

@ -4,10 +4,11 @@
For further information, consult the LICENSE file in the root directory. For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/ \*****************************************************************************/
#ifndef __GTK_SOUND_DRIVER_H #ifndef __S9X_SOUND_DRIVER_H
#define __GTK_SOUND_DRIVER_H #define __S9X_SOUND_DRIVER_H
#include "gtk_s9x.h" #include <cstdint>
#include <tuple>
class S9xSoundDriver class S9xSoundDriver
{ {
@ -15,9 +16,12 @@ class S9xSoundDriver
virtual ~S9xSoundDriver() virtual ~S9xSoundDriver()
{ {
} }
virtual void write_samples(int16_t *data, int samples) = 0;
virtual int space_free() = 0;
virtual std::pair<int, int> buffer_level() = 0;
virtual void init() = 0; virtual void init() = 0;
virtual void terminate() = 0; virtual void deinit() = 0;
virtual bool open_device() = 0; virtual bool open_device(int playback_rate, int buffer_size) = 0;
virtual void start() = 0; virtual void start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
}; };

View File

@ -5,9 +5,6 @@
\*****************************************************************************/ \*****************************************************************************/
#include "gtk_sound_driver_alsa.h" #include "gtk_sound_driver_alsa.h"
#include "gtk_s9x.h"
#include "snes9x.h"
#include "apu/apu.h"
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -16,27 +13,22 @@
S9xAlsaSoundDriver::S9xAlsaSoundDriver() S9xAlsaSoundDriver::S9xAlsaSoundDriver()
{ {
pcm = {}; pcm = {};
sound_buffer.clear();
} }
void S9xAlsaSoundDriver::init() void S9xAlsaSoundDriver::init()
{ {
} }
void S9xAlsaSoundDriver::terminate() void S9xAlsaSoundDriver::deinit()
{ {
stop(); stop();
S9xSetSamplesAvailableCallback(nullptr, nullptr);
if (pcm) if (pcm)
{ {
snd_pcm_drain(pcm); snd_pcm_drain(pcm);
snd_pcm_close(pcm); snd_pcm_close(pcm);
pcm = nullptr; pcm = nullptr;
} }
sound_buffer.clear();
} }
void S9xAlsaSoundDriver::start() void S9xAlsaSoundDriver::start()
@ -47,16 +39,17 @@ void S9xAlsaSoundDriver::stop()
{ {
} }
bool S9xAlsaSoundDriver::open_device() bool S9xAlsaSoundDriver::open_device(int playback_rate, int buffer_size_ms)
{ {
int err; int err;
unsigned int periods = 8; unsigned int periods = 8;
unsigned int buffer_size = gui_config->sound_buffer_size * 1000; unsigned int buffer_size = buffer_size_ms * 1000;
snd_pcm_sw_params_t *sw_params; snd_pcm_sw_params_t *sw_params;
snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t alsa_buffer_size, alsa_period_size; snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
unsigned int min = 0; unsigned int min = 0;
unsigned int max = 0; unsigned int max = 0;
unsigned int playback_rate_param = 0;
printf("ALSA sound driver initializing...\n"); printf("ALSA sound driver initializing...\n");
printf(" --> (Device: default)...\n"); printf(" --> (Device: default)...\n");
@ -69,8 +62,8 @@ bool S9xAlsaSoundDriver::open_device()
} }
printf(" --> (16-bit Stereo, %dhz, %d ms)...\n", printf(" --> (16-bit Stereo, %dhz, %d ms)...\n",
Settings.SoundPlaybackRate, playback_rate,
gui_config->sound_buffer_size); buffer_size_ms);
snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_alloca(&hw_params);
snd_pcm_hw_params_any(pcm, hw_params); snd_pcm_hw_params_any(pcm, hw_params);
@ -82,12 +75,14 @@ bool S9xAlsaSoundDriver::open_device()
snd_pcm_hw_params_get_rate_min(hw_params, &min, nullptr); snd_pcm_hw_params_get_rate_min(hw_params, &min, nullptr);
snd_pcm_hw_params_get_rate_max(hw_params, &max, nullptr); snd_pcm_hw_params_get_rate_max(hw_params, &max, nullptr);
printf(" --> Available rates: %d to %d\n", min, max); printf(" --> Available rates: %d to %d\n", min, max);
if (Settings.SoundPlaybackRate > max && Settings.SoundPlaybackRate < min) if (playback_rate > (int)max || playback_rate < (int)min)
{ {
printf(" Rate %d not available. Using %d instead.\n", Settings.SoundPlaybackRate, max); printf(" Rate %d not available. Using %d instead.\n", playback_rate, max);
Settings.SoundPlaybackRate = max; playback_rate = max;
} }
snd_pcm_hw_params_set_rate_near(pcm, hw_params, &Settings.SoundPlaybackRate, nullptr);
playback_rate_param = playback_rate;
snd_pcm_hw_params_set_rate_near(pcm, hw_params, &playback_rate_param, nullptr);
snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, nullptr); snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, nullptr);
snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, nullptr); snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, nullptr);
@ -121,7 +116,7 @@ bool S9xAlsaSoundDriver::open_device()
snd_pcm_sw_params_set_start_threshold(pcm, sw_params, (alsa_buffer_size / 2)); snd_pcm_sw_params_set_start_threshold(pcm, sw_params, (alsa_buffer_size / 2));
err = snd_pcm_sw_params(pcm, sw_params); err = snd_pcm_sw_params(pcm, sw_params);
output_buffer_size = snd_pcm_frames_to_bytes(pcm, alsa_buffer_size); output_buffer_size_bytes = snd_pcm_frames_to_bytes(pcm, alsa_buffer_size);
if (err < 0) if (err < 0)
{ {
@ -131,10 +126,6 @@ bool S9xAlsaSoundDriver::open_device()
printf("OK\n"); printf("OK\n");
S9xSetSamplesAvailableCallback([](void *userdata) {
((decltype(this)) userdata)->samples_available();;
}, this);
return true; return true;
close_fail: close_fail:
@ -148,7 +139,7 @@ fail:
return false; return false;
} }
void S9xAlsaSoundDriver::samples_available() void S9xAlsaSoundDriver::write_samples(int16_t *data, int samples)
{ {
snd_pcm_sframes_t frames_written, frames; snd_pcm_sframes_t frames_written, frames;
size_t bytes; size_t bytes;
@ -161,47 +152,21 @@ void S9xAlsaSoundDriver::samples_available()
return; return;
} }
int snes_frames_available = S9xGetSampleCount() >> 1;
if (Settings.DynamicRateControl && !Settings.SoundSync)
{
// Using rate control, we should always keep the emulator's sound buffers empty to
// maintain an accurate measurement.
if (frames < snes_frames_available)
{
S9xClearSamples();
return;
}
}
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{
snd_pcm_nonblock(pcm, 0); snd_pcm_nonblock(pcm, 0);
frames = snes_frames_available; if (frames > samples / 2)
} frames = samples / 2;
else
{
snd_pcm_nonblock(pcm, 1);
frames = MIN(frames, snes_frames_available);
}
bytes = snd_pcm_frames_to_bytes(pcm, frames); bytes = snd_pcm_frames_to_bytes(pcm, frames);
if (bytes <= 0) if (bytes <= 0)
return; return;
if (sound_buffer.size() < bytes)
sound_buffer.resize(bytes);
S9xMixSamples(sound_buffer.data(), frames * 2);
frames_written = 0; frames_written = 0;
while (frames_written < frames) while (frames_written < frames)
{ {
int result; int result;
result = snd_pcm_writei(pcm, result = snd_pcm_writei(pcm,
&sound_buffer[snd_pcm_frames_to_bytes(pcm, frames_written)], &data[snd_pcm_frames_to_bytes(pcm, frames_written) / 2],
frames - frames_written); frames - frames_written);
if (result < 0) if (result < 0)
@ -218,11 +183,14 @@ void S9xAlsaSoundDriver::samples_available()
frames_written += result; frames_written += result;
} }
} }
}
if (Settings.DynamicRateControl) int S9xAlsaSoundDriver::space_free()
{ {
frames = snd_pcm_avail(pcm); return snd_pcm_avail(pcm) * 2;
S9xUpdateDynamicRate(snd_pcm_frames_to_bytes(pcm, frames),
output_buffer_size);
} }
std::pair<int, int> S9xAlsaSoundDriver::buffer_level()
{
return { snd_pcm_avail(pcm), output_buffer_size_bytes / 4 };
} }

View File

@ -15,17 +15,18 @@ class S9xAlsaSoundDriver : public S9xSoundDriver
{ {
public: public:
S9xAlsaSoundDriver(); S9xAlsaSoundDriver();
void init(); void init() override;
void terminate(); void deinit() override;
bool open_device(); bool open_device(int playback_rate, int buffer_size_ms) override;
void start(); void start() override;
void stop(); void stop() override;
void samples_available(); void write_samples(int16_t *data, int samples) override;
int space_free() override;
std::pair<int, int> buffer_level() override;
private: private:
snd_pcm_t *pcm; snd_pcm_t *pcm;
std::vector<uint8_t> sound_buffer; int output_buffer_size_bytes;
int output_buffer_size;
}; };
#endif /* __GTK_SOUND_DRIVER_ALSA_H */ #endif /* __GTK_SOUND_DRIVER_ALSA_H */

View File

@ -5,47 +5,30 @@
\*****************************************************************************/ \*****************************************************************************/
#include "gtk_sound_driver_oss.h" #include "gtk_sound_driver_oss.h"
#include "gtk_s9x.h"
#include "snes9x.h"
#include "apu/apu.h"
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/soundcard.h> #include <sys/soundcard.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h>
static void oss_samples_available(void *data)
{
((S9xOSSSoundDriver *)data)->samples_available();
}
S9xOSSSoundDriver::S9xOSSSoundDriver() S9xOSSSoundDriver::S9xOSSSoundDriver()
{ {
filedes = -1; filedes = -1;
sound_buffer = NULL;
sound_buffer_size = 0;
} }
void S9xOSSSoundDriver::init() void S9xOSSSoundDriver::init()
{ {
} }
void S9xOSSSoundDriver::terminate() void S9xOSSSoundDriver::deinit()
{ {
stop(); stop();
S9xSetSamplesAvailableCallback(NULL, NULL);
if (filedes >= 0) if (filedes >= 0)
{ {
close(filedes); close(filedes);
} }
if (sound_buffer)
{
free(sound_buffer);
sound_buffer = NULL;
}
} }
void S9xOSSSoundDriver::start() void S9xOSSSoundDriver::start()
@ -56,16 +39,16 @@ void S9xOSSSoundDriver::stop()
{ {
} }
bool S9xOSSSoundDriver::open_device() bool S9xOSSSoundDriver::open_device(int playback_rate, int buffer_size_ms)
{ {
int temp; int temp;
audio_buf_info info; audio_buf_info info;
output_buffer_size = (gui_config->sound_buffer_size * Settings.SoundPlaybackRate) / 1000; output_buffer_size_bytes = (buffer_size_ms * playback_rate) / 1000;
output_buffer_size *= 4; output_buffer_size_bytes *= 4;
if (output_buffer_size < 256) if (output_buffer_size_bytes < 256)
output_buffer_size = 256; output_buffer_size_bytes = 256;
printf("OSS sound driver initializing...\n"); printf("OSS sound driver initializing...\n");
@ -115,31 +98,29 @@ bool S9xOSSSoundDriver::open_device()
printf("OK\n"); printf("OK\n");
printf(" --> (Frequency: %d)...", Settings.SoundPlaybackRate); printf(" --> (Frequency: %d)...", playback_rate);
if (ioctl(filedes, SNDCTL_DSP_SPEED, &Settings.SoundPlaybackRate) < 0) if (ioctl(filedes, SNDCTL_DSP_SPEED, &playback_rate) < 0)
goto close_fail; goto close_fail;
printf("OK\n"); printf("OK\n");
/* OSS requires a power-of-two buffer size, first 16 bits are the number /* OSS requires a power-of-two buffer size, first 16 bits are the number
* of fragments to generate, second 16 are the respective power-of-two. */ * of fragments to generate, second 16 are the respective power-of-two. */
temp = (4 << 16) | (S9xSoundBase2log(output_buffer_size / 4)); temp = (4 << 16) | (S9xSoundBase2log(output_buffer_size_bytes / 4));
if (ioctl(filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0) if (ioctl(filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0)
goto close_fail; goto close_fail;
ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info); ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info);
output_buffer_size = info.fragsize * info.fragstotal; output_buffer_size_bytes = info.fragsize * info.fragstotal;
printf(" --> (Buffer size: %d bytes, %dms latency)...", printf(" --> (Buffer size: %d bytes, %dms latency)...",
output_buffer_size, output_buffer_size_bytes,
(output_buffer_size * 250) / Settings.SoundPlaybackRate); (output_buffer_size_bytes * 250) / playback_rate);
printf("OK\n"); printf("OK\n");
S9xSetSamplesAvailableCallback(oss_samples_available, this);
return true; return true;
close_fail: close_fail:
@ -152,68 +133,41 @@ fail:
return false; return false;
} }
void S9xOSSSoundDriver::samples_available() int S9xOSSSoundDriver::space_free()
{
audio_buf_info info;
ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info);
return info.bytes / 2;
}
std::pair<int, int> S9xOSSSoundDriver::buffer_level()
{
return { space_free(), output_buffer_size_bytes / 2};
}
void S9xOSSSoundDriver::write_samples(int16_t *data, int samples)
{ {
audio_buf_info info; audio_buf_info info;
int samples_to_write;
int bytes_to_write; int bytes_to_write;
int bytes_written; int bytes_written;
ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info); ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info);
if (Settings.DynamicRateControl) if (samples > info.bytes / 2)
{ samples = info.bytes / 2;
S9xUpdateDynamicRate(info.bytes, output_buffer_size);
}
samples_to_write = S9xGetSampleCount(); if (samples == 0)
if (Settings.DynamicRateControl && !Settings.SoundSync)
{
// Using rate control, we should always keep the emulator's sound buffers empty to
// maintain an accurate measurement.
if (samples_to_write > (info.bytes >> 1))
{
S9xClearSamples();
return; return;
}
}
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{
while (info.bytes >> 1 < samples_to_write)
{
int usec_to_sleep = ((samples_to_write >> 1) - (info.bytes >> 2)) * 10000 /
(Settings.SoundPlaybackRate / 100);
usleep(usec_to_sleep > 0 ? usec_to_sleep : 0);
ioctl(filedes, SNDCTL_DSP_GETOSPACE, &info);
}
}
else
{
samples_to_write = MIN(info.bytes >> 1, samples_to_write) & ~1;
}
if (samples_to_write < 0)
return;
if (sound_buffer_size < samples_to_write * 2)
{
sound_buffer = (uint8 *)realloc(sound_buffer, samples_to_write * 2);
sound_buffer_size = samples_to_write * 2;
}
S9xMixSamples(sound_buffer, samples_to_write);
bytes_written = 0; bytes_written = 0;
bytes_to_write = samples_to_write * 2; bytes_to_write = samples * 2;
while (bytes_to_write > bytes_written) while (bytes_to_write > bytes_written)
{ {
int result; int result;
result = write(filedes, result = write(filedes,
((char *)sound_buffer) + bytes_written, ((char *)data) + bytes_written,
bytes_to_write - bytes_written); bytes_to_write - bytes_written);
if (result < 0) if (result < 0)

View File

@ -14,18 +14,18 @@ class S9xOSSSoundDriver : public S9xSoundDriver
{ {
public: public:
S9xOSSSoundDriver(); S9xOSSSoundDriver();
void init(); void init() override;
void terminate(); void deinit() override;
bool open_device(); bool open_device(int playback_rate, int buffer_size_ms) override;
void start(); void start() override;
void stop(); void stop() override;
void samples_available(); void write_samples(int16_t *data, int samples) override;
int space_free() override;
std::pair<int, int> buffer_level() override;
private: private:
int filedes; int filedes;
uint8 *sound_buffer; int output_buffer_size_bytes;
int sound_buffer_size;
int output_buffer_size;
}; };
#endif /* __GTK_SOUND_DRIVER_OSS_H */ #endif /* __GTK_SOUND_DRIVER_OSS_H */

View File

@ -5,20 +5,11 @@
\*****************************************************************************/ \*****************************************************************************/
#include "gtk_sound_driver_portaudio.h" #include "gtk_sound_driver_portaudio.h"
#include "gtk_s9x.h" #include <cstring>
#include "apu/apu.h"
#include "snes9x.h"
static void port_audio_samples_available_callback(void *data)
{
((S9xPortAudioSoundDriver *)data)->samples_available();
}
S9xPortAudioSoundDriver::S9xPortAudioSoundDriver() S9xPortAudioSoundDriver::S9xPortAudioSoundDriver()
{ {
audio_stream = NULL; audio_stream = NULL;
sound_buffer = NULL;
sound_buffer_size = 0;
} }
void S9xPortAudioSoundDriver::init() void S9xPortAudioSoundDriver::init()
@ -33,26 +24,18 @@ void S9xPortAudioSoundDriver::init()
Pa_GetErrorText(err)); Pa_GetErrorText(err));
} }
void S9xPortAudioSoundDriver::terminate() void S9xPortAudioSoundDriver::deinit()
{ {
stop(); stop();
S9xSetSamplesAvailableCallback(NULL, NULL);
Pa_Terminate(); Pa_Terminate();
if (sound_buffer)
{
free(sound_buffer);
sound_buffer = NULL;
}
} }
void S9xPortAudioSoundDriver::start() void S9xPortAudioSoundDriver::start()
{ {
PaError err; PaError err;
if (audio_stream != NULL && !(gui_config->mute_sound)) if (audio_stream != NULL)
{ {
if ((Pa_IsStreamActive(audio_stream))) if ((Pa_IsStreamActive(audio_stream)))
return; return;
@ -74,16 +57,7 @@ void S9xPortAudioSoundDriver::stop()
} }
} }
void S9xPortAudioSoundDriver::set_buffer_min(int frames) bool S9xPortAudioSoundDriver::open_device(int playback_rate, int buffer_size_ms)
{
if ((sound_buffer_size < frames) || sound_buffer == NULL)
{
sound_buffer = (uint8 *)realloc(sound_buffer, frames << 2);
sound_buffer_size = frames;
}
}
bool S9xPortAudioSoundDriver::open_device()
{ {
PaStreamParameters param; PaStreamParameters param;
const PaDeviceInfo *device_info; const PaDeviceInfo *device_info;
@ -133,7 +107,7 @@ bool S9xPortAudioSoundDriver::open_device()
} }
param.device = hostapi_info->defaultOutputDevice; param.device = hostapi_info->defaultOutputDevice;
param.suggestedLatency = gui_config->sound_buffer_size * 0.001; param.suggestedLatency = buffer_size_ms * 0.001;
printf("(%s : %s, latency %dms)...\n", printf("(%s : %s, latency %dms)...\n",
hostapi_info->name, hostapi_info->name,
@ -145,7 +119,7 @@ bool S9xPortAudioSoundDriver::open_device()
err = Pa_OpenStream(&audio_stream, err = Pa_OpenStream(&audio_stream,
NULL, NULL,
&param, &param,
Settings.SoundPlaybackRate, playback_rate,
0, 0,
paNoFlag, paNoFlag,
NULL, NULL,
@ -174,15 +148,20 @@ bool S9xPortAudioSoundDriver::open_device()
return false; return false;
} }
S9xSetSamplesAvailableCallback(port_audio_samples_available_callback, this);
fflush(stdout);
fflush(stderr);
return true; return true;
} }
void S9xPortAudioSoundDriver::samples_available() int S9xPortAudioSoundDriver::space_free()
{
return Pa_GetStreamWriteAvailable(audio_stream) * 2;
}
std::pair<int, int> S9xPortAudioSoundDriver::buffer_level()
{
return { Pa_GetStreamWriteAvailable(audio_stream), output_buffer_size };
}
void S9xPortAudioSoundDriver::write_samples(int16_t *data, int samples)
{ {
int frames; int frames;
@ -191,38 +170,14 @@ void S9xPortAudioSoundDriver::samples_available()
if (frames == output_buffer_size) if (frames == output_buffer_size)
{ {
// Prime the stream // Prime the stream
set_buffer_min(output_buffer_size >> 1); std::vector<int16_t> tmp(output_buffer_size);
memset(sound_buffer, 0, output_buffer_size << 1); memset(tmp.data(), 0, output_buffer_size << 1);
Pa_WriteStream(audio_stream, sound_buffer, output_buffer_size >> 1); Pa_WriteStream(audio_stream, tmp.data(), output_buffer_size >> 1);
frames -= output_buffer_size >> 1; frames -= output_buffer_size >> 1;
} }
int snes_frames_available = S9xGetSampleCount() >> 1; if (frames > samples / 2)
frames = samples / 2;
if (Settings.DynamicRateControl && !Settings.SoundSync) Pa_WriteStream(audio_stream, data, frames);
{
// Using rate control, we should always keep the emulator's sound buffers empty to
// maintain an accurate measurement.
if (frames < snes_frames_available)
{
S9xClearSamples();
return;
}
}
// Rely on PortAudio's blocking behavior to sync
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
frames = snes_frames_available;
frames = MIN(frames, snes_frames_available);
set_buffer_min(frames);
S9xMixSamples(sound_buffer, frames << 1);
Pa_WriteStream(audio_stream, sound_buffer, frames);
if (Settings.DynamicRateControl)
{
frames = Pa_GetStreamWriteAvailable(audio_stream);
S9xUpdateDynamicRate(frames, output_buffer_size);
}
} }

View File

@ -17,18 +17,18 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
{ {
public: public:
S9xPortAudioSoundDriver(); S9xPortAudioSoundDriver();
void init(); void init() override;
void terminate(); void deinit() override;
bool open_device(); bool open_device(int playback_rate, int buffer_size) override;
void start(); void start() override;
void stop(); void stop() override;
void write_samples(int16_t *data, int samples) override;
int space_free() override;
std::pair<int, int> buffer_level() override;
void samples_available(); void samples_available();
private: private:
void set_buffer_min(int frames);
PaStream *audio_stream; PaStream *audio_stream;
int sound_buffer_size;
uint8 *sound_buffer;
int output_buffer_size; int output_buffer_size;
}; };

View File

@ -5,10 +5,8 @@
\*****************************************************************************/ \*****************************************************************************/
#include "gtk_sound_driver_pulse.h" #include "gtk_sound_driver_pulse.h"
#include "gtk_s9x.h"
#include "snes9x.h"
#include "apu/apu.h"
#include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/time.h> #include <sys/time.h>
@ -26,10 +24,8 @@ void S9xPulseSoundDriver::init()
buffer_size = {}; buffer_size = {};
} }
void S9xPulseSoundDriver::terminate() void S9xPulseSoundDriver::deinit()
{ {
S9xSetSamplesAvailableCallback(nullptr, nullptr);
if (mainloop) if (mainloop)
pa_threaded_mainloop_stop(mainloop); pa_threaded_mainloop_stop(mainloop);
@ -104,17 +100,17 @@ static void stream_state_callback(pa_stream *p, void *userdata)
} }
} }
bool S9xPulseSoundDriver::open_device() bool S9xPulseSoundDriver::open_device(int playback_rate, int buffer_size_ms)
{ {
init(); init();
pa_sample_spec ss; pa_sample_spec ss;
ss.channels = 2; ss.channels = 2;
ss.format = PA_SAMPLE_S16NE; ss.format = PA_SAMPLE_S16NE;
ss.rate = Settings.SoundPlaybackRate; ss.rate = playback_rate;
pa_buffer_attr buffer_attr; pa_buffer_attr buffer_attr;
buffer_attr.tlength = pa_usec_to_bytes(gui_config->sound_buffer_size * 1000, &ss); buffer_attr.tlength = pa_usec_to_bytes(buffer_size_ms * 1000, &ss);
buffer_attr.maxlength = buffer_attr.tlength * 2; buffer_attr.maxlength = buffer_attr.tlength * 2;
buffer_attr.minreq = pa_usec_to_bytes(3000, &ss); buffer_attr.minreq = pa_usec_to_bytes(3000, &ss);
buffer_attr.prebuf = -1; buffer_attr.prebuf = -1;
@ -122,8 +118,8 @@ bool S9xPulseSoundDriver::open_device()
printf("PulseAudio sound driver initializing...\n"); printf("PulseAudio sound driver initializing...\n");
printf(" --> (%dhz, 16-bit Stereo, %dms)...", printf(" --> (%dhz, 16-bit Stereo, %dms)...",
Settings.SoundPlaybackRate, playback_rate,
gui_config->sound_buffer_size); buffer_size_ms);
int err = PA_ERR_UNKNOWN; int err = PA_ERR_UNKNOWN;
mainloop = pa_threaded_mainloop_new(); mainloop = pa_threaded_mainloop_new();
@ -164,73 +160,39 @@ bool S9xPulseSoundDriver::open_device()
printf("OK\n"); printf("OK\n");
S9xSetSamplesAvailableCallback([](void *userdata) {
((decltype(this)) userdata)->samples_available();;
}, this);
return true; return true;
} }
void S9xPulseSoundDriver::samples_available() int S9xPulseSoundDriver::space_free()
{ {
bool clear_samples = false;
lock(); lock();
size_t bytes = pa_stream_writable_size(stream); size_t bytes = pa_stream_writable_size(stream);
auto buffer_attr = pa_stream_get_buffer_attr(stream);
unlock(); unlock();
return bytes / 2;
buffer_size = buffer_attr->tlength;
size_t samples = S9xGetSampleCount();
int frames_available = samples / 2;
int frames_writable = bytes / 4;
if (frames_writable < frames_available)
{
if (!Settings.SoundSync)
{
clear_samples = true;
} }
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute) std::pair<int, int> S9xPulseSoundDriver::buffer_level()
{ {
lock(); lock();
int i; size_t bytes = pa_stream_writable_size(stream);
for (i = 0; i < 500; i++)
{
bytes = pa_stream_writable_size(stream);
if (bytes / 2 < samples)
{
unlock(); unlock();
usleep(50);
lock(); return { bytes, buffer_size };
}
else
{
unlock();
break;
}
} }
if (i == 500) void S9xPulseSoundDriver::write_samples(int16_t *data, int samples)
printf("PulseAudio: Timed out waiting for sound sync.\n"); {
} lock();
} size_t bytes = pa_stream_writable_size(stream);
unlock();
bytes = MIN(bytes, samples * 2) & ~1; bytes = MIN(bytes, samples * 2) & ~1;
if (!bytes) if (bytes == 0)
{
S9xClearSamples();
return; return;
}
lock(); lock();
void *output_buffer;
void *output_buffer;;
if (pa_stream_begin_write(stream, &output_buffer, &bytes) != 0) if (pa_stream_begin_write(stream, &output_buffer, &bytes) != 0)
{ {
pa_stream_flush(stream, nullptr, nullptr); pa_stream_flush(stream, nullptr, nullptr);
@ -244,17 +206,7 @@ void S9xPulseSoundDriver::samples_available()
return; return;
} }
S9xMixSamples((uint8_t *)output_buffer, bytes >> 1); std::memcpy(output_buffer, data, bytes);
pa_stream_write(stream, output_buffer, bytes, nullptr, 0, PA_SEEK_RELATIVE); pa_stream_write(stream, output_buffer, bytes, nullptr, 0, PA_SEEK_RELATIVE);
if (Settings.DynamicRateControl)
{
bytes = pa_stream_writable_size(stream);
S9xUpdateDynamicRate(bytes, buffer_size);
}
unlock(); unlock();
if (clear_samples)
S9xClearSamples();
} }

View File

@ -15,21 +15,23 @@ class S9xPulseSoundDriver : public S9xSoundDriver
{ {
public: public:
S9xPulseSoundDriver(); S9xPulseSoundDriver();
void init(); void init() override;
void terminate(); void deinit() override;
bool open_device(); void write_samples(int16_t *data, int samples) override;
void start(); bool open_device(int playback_rate, int buffer_size) override;
void stop(); void start() override;
void samples_available(); void stop() override;
void lock(); int space_free() override;
void unlock(); std::pair<int, int> buffer_level() override;
void wait();
pa_threaded_mainloop *mainloop; pa_threaded_mainloop *mainloop;
pa_context *context; pa_context *context;
pa_stream *stream; pa_stream *stream;
private: private:
void lock();
void unlock();
void wait();
int buffer_size; int buffer_size;
}; };

View File

@ -6,34 +6,13 @@
#include "gtk_sound_driver_sdl.h" #include "gtk_sound_driver_sdl.h"
#include "SDL_audio.h" #include "SDL_audio.h"
#include "gtk_s9x.h"
#include "apu/apu.h"
#include "snes9x.h"
void S9xSDLSoundDriver::samples_available() void S9xSDLSoundDriver::write_samples(int16_t *data, int samples)
{
int snes_samples_available = S9xGetSampleCount();
S9xMixSamples((uint8 *)temp, snes_samples_available);
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{ {
mutex.lock(); mutex.lock();
int samples = buffer.space_empty(); if (samples > buffer.space_empty())
mutex.unlock();
while (samples < snes_samples_available)
{
usleep(100);
mutex.lock();
samples = buffer.space_empty(); samples = buffer.space_empty();
mutex.unlock(); buffer.push(data, samples);
}
}
mutex.lock();
buffer.push(temp, snes_samples_available);
if (Settings.DynamicRateControl)
S9xUpdateDynamicRate(buffer.space_empty(), buffer.buffer_size);
mutex.unlock(); mutex.unlock();
} }
@ -55,7 +34,7 @@ void S9xSDLSoundDriver::init()
stop(); stop();
} }
void S9xSDLSoundDriver::terminate() void S9xSDLSoundDriver::deinit()
{ {
stop(); stop();
SDL_CloseAudio(); SDL_CloseAudio();
@ -63,22 +42,19 @@ void S9xSDLSoundDriver::terminate()
} }
void S9xSDLSoundDriver::start() void S9xSDLSoundDriver::start()
{
if (!gui_config->mute_sound)
{ {
SDL_PauseAudio(0); SDL_PauseAudio(0);
} }
}
void S9xSDLSoundDriver::stop() void S9xSDLSoundDriver::stop()
{ {
SDL_PauseAudio(1); SDL_PauseAudio(1);
} }
bool S9xSDLSoundDriver::open_device() bool S9xSDLSoundDriver::open_device(int playback_rate, int buffer_size)
{ {
audiospec = {}; audiospec = {};
audiospec.freq = Settings.SoundPlaybackRate; audiospec.freq = playback_rate;
audiospec.channels = 2; audiospec.channels = 2;
audiospec.format = AUDIO_S16SYS; audiospec.format = AUDIO_S16SYS;
audiospec.samples = audiospec.freq * 4 / 1000; // 4ms per sampling audiospec.samples = audiospec.freq * 4 / 1000; // 4ms per sampling
@ -101,11 +77,23 @@ bool S9xSDLSoundDriver::open_device()
printf("OK\n"); printf("OK\n");
buffer.resize(gui_config->sound_buffer_size * audiospec.freq / 500); buffer.resize(buffer_size * audiospec.freq / 1000);
S9xSetSamplesAvailableCallback([](void *userdata) {
((decltype(this)) userdata)->samples_available();;
}, this);
return true; return true;
} }
int S9xSDLSoundDriver::space_free()
{
mutex.lock();
auto space_empty = buffer.space_empty();
mutex.unlock();
return space_empty;
}
std::pair<int, int> S9xSDLSoundDriver::buffer_level()
{
mutex.lock();
std::pair<int, int> level = { buffer.space_empty(), buffer.buffer_size };
mutex.unlock();
return level;
}

View File

@ -23,15 +23,18 @@ class S9xSDLSoundDriver : public S9xSoundDriver
{ {
public: public:
S9xSDLSoundDriver(); S9xSDLSoundDriver();
void init(); void init() override;
void terminate(); void deinit() override;
bool open_device(); bool open_device(int playback_rate, int buffer_size) override;
void start(); void start() override;
void stop(); void stop() override;
void mix(unsigned char *output, int bytes); void write_samples(int16_t *data, int samples) override;
void samples_available(); int space_free() override;
std::pair<int, int> buffer_level() override;
private: private:
void mix(unsigned char *output, int bytes);
SDL_AudioSpec audiospec; SDL_AudioSpec audiospec;
Resampler buffer; Resampler buffer;
std::mutex mutex; std::mutex mutex;