34b6160805
Pipewire allows very small buffer sizes. It's possible that Snes9x will accumulate enough samples in fast-forward to exceed this. Instead of waiting forever for the buffer capacity to free up, just write the number of samples for the free space after one small wait.
278 lines
6.2 KiB
C++
278 lines
6.2 KiB
C++
/*****************************************************************************\
|
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
|
This file is licensed under the Snes9x License.
|
|
For further information, consult the LICENSE file in the root directory.
|
|
\*****************************************************************************/
|
|
|
|
#include "gtk_sound_driver_pulse.h"
|
|
#include "gtk_s9x.h"
|
|
#include "snes9x.h"
|
|
#include "apu/apu.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/time.h>
|
|
|
|
static void pulse_samples_available(void *data)
|
|
{
|
|
((S9xPulseSoundDriver *)data)->samples_available();
|
|
}
|
|
|
|
S9xPulseSoundDriver::S9xPulseSoundDriver()
|
|
{
|
|
mainloop = NULL;
|
|
context = NULL;
|
|
stream = NULL;
|
|
buffer_size = 0;
|
|
}
|
|
|
|
void S9xPulseSoundDriver::init()
|
|
{
|
|
}
|
|
|
|
void S9xPulseSoundDriver::terminate()
|
|
{
|
|
S9xSetSamplesAvailableCallback(NULL, NULL);
|
|
|
|
if (mainloop)
|
|
pa_threaded_mainloop_stop(mainloop);
|
|
|
|
if (stream)
|
|
{
|
|
pa_stream_disconnect(stream);
|
|
pa_stream_unref(stream);
|
|
}
|
|
|
|
if (context)
|
|
{
|
|
pa_context_disconnect(context);
|
|
pa_context_unref(context);
|
|
}
|
|
|
|
if (mainloop)
|
|
{
|
|
pa_threaded_mainloop_free(mainloop);
|
|
}
|
|
}
|
|
|
|
void S9xPulseSoundDriver::start()
|
|
{
|
|
}
|
|
|
|
void S9xPulseSoundDriver::stop()
|
|
{
|
|
}
|
|
|
|
void S9xPulseSoundDriver::lock()
|
|
{
|
|
pa_threaded_mainloop_lock(mainloop);
|
|
}
|
|
|
|
void S9xPulseSoundDriver::unlock()
|
|
{
|
|
pa_threaded_mainloop_unlock(mainloop);
|
|
}
|
|
|
|
void S9xPulseSoundDriver::wait()
|
|
{
|
|
pa_threaded_mainloop_wait(mainloop);
|
|
}
|
|
|
|
static void context_state_cb(pa_context *c, void *userdata)
|
|
{
|
|
S9xPulseSoundDriver *driver = (S9xPulseSoundDriver *)userdata;
|
|
int state;
|
|
|
|
state = pa_context_get_state(c);
|
|
|
|
if (state == PA_CONTEXT_READY ||
|
|
state == PA_CONTEXT_FAILED ||
|
|
state == PA_CONTEXT_TERMINATED)
|
|
{
|
|
pa_threaded_mainloop_signal(driver->mainloop, 0);
|
|
}
|
|
}
|
|
|
|
static void stream_state_callback(pa_stream *p, void *userdata)
|
|
{
|
|
S9xPulseSoundDriver *driver = (S9xPulseSoundDriver *)userdata;
|
|
int state;
|
|
|
|
state = pa_stream_get_state(p);
|
|
|
|
if (state == PA_STREAM_READY ||
|
|
state == PA_STREAM_FAILED ||
|
|
state == PA_STREAM_TERMINATED)
|
|
{
|
|
pa_threaded_mainloop_signal(driver->mainloop, 0);
|
|
}
|
|
}
|
|
|
|
bool S9xPulseSoundDriver::open_device()
|
|
{
|
|
int err = PA_ERR_UNKNOWN;
|
|
pa_sample_spec ss;
|
|
pa_buffer_attr buffer_attr;
|
|
const pa_buffer_attr *actual_buffer_attr;
|
|
|
|
ss.channels = 2;
|
|
ss.format = PA_SAMPLE_S16NE;
|
|
ss.rate = Settings.SoundPlaybackRate;
|
|
|
|
buffer_attr.fragsize = -1;
|
|
buffer_attr.tlength = pa_usec_to_bytes(gui_config->sound_buffer_size * 1000, &ss);
|
|
buffer_attr.maxlength = buffer_attr.tlength * 2;
|
|
buffer_attr.minreq = -1;
|
|
buffer_attr.prebuf = -1;
|
|
|
|
printf("PulseAudio sound driver initializing...\n");
|
|
|
|
printf(" --> (%dhz, 16-bit Stereo, %dms)...",
|
|
Settings.SoundPlaybackRate,
|
|
gui_config->sound_buffer_size);
|
|
fflush(stdout);
|
|
|
|
mainloop = pa_threaded_mainloop_new();
|
|
if (!mainloop)
|
|
{
|
|
fprintf(stderr, "Failed to create mainloop.\n");
|
|
goto error0;
|
|
}
|
|
|
|
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "Snes9x");
|
|
if (!context)
|
|
goto error1;
|
|
|
|
pa_context_set_state_callback(context, context_state_cb, this);
|
|
if ((err = pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL)) != 0)
|
|
goto error2;
|
|
|
|
lock();
|
|
|
|
if ((err = pa_threaded_mainloop_start(mainloop)) != 0)
|
|
goto error2;
|
|
wait();
|
|
|
|
if ((err = pa_context_get_state(context)) != PA_CONTEXT_READY)
|
|
{
|
|
printf("Coundn't create context: State: %d\n", err);
|
|
goto error2;
|
|
}
|
|
|
|
stream = pa_stream_new(context, "Game", &ss, NULL);
|
|
|
|
if (!stream)
|
|
goto error2;
|
|
|
|
pa_stream_set_state_callback(stream, stream_state_callback, this);
|
|
|
|
if (pa_stream_connect_playback(stream,
|
|
NULL,
|
|
&buffer_attr,
|
|
PA_STREAM_ADJUST_LATENCY,
|
|
NULL,
|
|
NULL) < 0)
|
|
goto error3;
|
|
wait();
|
|
|
|
if (pa_stream_get_state(stream) != PA_STREAM_READY)
|
|
{
|
|
goto error3;
|
|
}
|
|
|
|
actual_buffer_attr = pa_stream_get_buffer_attr(stream);
|
|
|
|
buffer_size = actual_buffer_attr->tlength;
|
|
|
|
printf("OK\n");
|
|
|
|
S9xSetSamplesAvailableCallback(pulse_samples_available, this);
|
|
|
|
unlock();
|
|
|
|
return true;
|
|
|
|
error3:
|
|
pa_stream_disconnect(stream);
|
|
pa_stream_unref(stream);
|
|
error2:
|
|
pa_context_disconnect(context);
|
|
pa_context_unref(context);
|
|
unlock();
|
|
error1:
|
|
pa_threaded_mainloop_free(mainloop);
|
|
error0:
|
|
printf("Failed: %s\n", pa_strerror(err));
|
|
|
|
return false;
|
|
}
|
|
|
|
void S9xPulseSoundDriver::samples_available()
|
|
{
|
|
size_t bytes;
|
|
int samples;
|
|
const pa_buffer_attr *buffer_attr;
|
|
void *output_buffer = NULL;
|
|
|
|
lock();
|
|
bytes = pa_stream_writable_size(stream);
|
|
buffer_attr = pa_stream_get_buffer_attr(stream);
|
|
unlock();
|
|
|
|
buffer_size = buffer_attr->tlength;
|
|
|
|
if (Settings.DynamicRateControl)
|
|
{
|
|
S9xUpdateDynamicRate(bytes, buffer_size);
|
|
}
|
|
|
|
samples = S9xGetSampleCount();
|
|
|
|
if (Settings.DynamicRateControl && !Settings.SoundSync)
|
|
{
|
|
if ((int)bytes < (samples * 2))
|
|
{
|
|
S9xClearSamples();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
|
|
{
|
|
if ((int)bytes < samples * 2)
|
|
{
|
|
int usec_to_sleep = ((samples >> 1) - (bytes >> 2)) * 10000 /
|
|
(Settings.SoundPlaybackRate / 100);
|
|
usleep(usec_to_sleep > 0 ? usec_to_sleep : 0);
|
|
lock();
|
|
bytes = pa_stream_writable_size(stream);
|
|
unlock();
|
|
}
|
|
}
|
|
|
|
bytes = MIN((int)bytes, samples * 2) & ~1;
|
|
|
|
if (!bytes)
|
|
return;
|
|
|
|
lock();
|
|
|
|
if (pa_stream_begin_write(stream, &output_buffer, &bytes) != 0)
|
|
{
|
|
pa_stream_flush(stream, NULL, NULL);
|
|
unlock();
|
|
return;
|
|
}
|
|
|
|
if (bytes <= 0 || !output_buffer)
|
|
{
|
|
unlock();
|
|
return;
|
|
}
|
|
|
|
S9xMixSamples((uint8 *)output_buffer, bytes >> 1);
|
|
pa_stream_write(stream, output_buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
|
|
|
|
unlock();
|
|
}
|