diff --git a/audio/alsa.c b/audio/alsa.c index a32154e46f..ec9f0173ba 100644 --- a/audio/alsa.c +++ b/audio/alsa.c @@ -30,6 +30,8 @@ typedef struct alsa snd_pcm_t *pcm; bool nonblock; bool has_float; + + size_t buffer_size; } alsa_t; static bool alsa_use_float(void *data) @@ -62,8 +64,8 @@ static void *alsa_init(const char *device, unsigned rate, unsigned latency) if (device) alsa_dev = device; - unsigned int latency_usec = latency * 1000; - unsigned int channels = 2; + unsigned latency_usec = latency * 1000; + unsigned channels = 2; unsigned periods = 4; snd_pcm_uframes_t buffer_size; snd_pcm_format_t format; @@ -89,6 +91,7 @@ static void *alsa_init(const char *device, unsigned rate, unsigned latency) SSNES_LOG("ALSA: Period size: %d frames\n", (int)buffer_size); snd_pcm_hw_params_get_buffer_size(params, &buffer_size); SSNES_LOG("ALSA: Buffer size: %d frames\n", (int)buffer_size); + alsa->buffer_size = snd_pcm_frames_to_bytes(alsa->pcm, buffer_size); TRY_ALSA(snd_pcm_sw_params_malloc(&sw_params)); TRY_ALSA(snd_pcm_sw_params_current(alsa->pcm, sw_params)); @@ -190,6 +193,18 @@ static void alsa_free(void *data) } } +static size_t alsa_write_avail(void *data) +{ + alsa_t *alsa = (alsa_t*)data; + return snd_pcm_frames_to_bytes(alsa->pcm, snd_pcm_avail_update(alsa->pcm)); +} + +static size_t alsa_buffer_size(void *data) +{ + alsa_t *alsa = (alsa_t*)data; + return alsa->buffer_size; +} + const audio_driver_t audio_alsa = { alsa_init, alsa_write, @@ -198,6 +213,8 @@ const audio_driver_t audio_alsa = { alsa_set_nonblock_state, alsa_free, alsa_use_float, - "alsa" + "alsa", + alsa_write_avail, + alsa_buffer_size, }; diff --git a/audio/dsound.c b/audio/dsound.c index ce7b03d219..2403dd4cc6 100644 --- a/audio/dsound.c +++ b/audio/dsound.c @@ -373,6 +373,20 @@ static ssize_t dsound_write(void *data, const void *buf_, size_t size) return written; } +static size_t dsound_write_avail(void *data) +{ + dsound_t *ds = (dsound_t*)data; + EnterCriticalSection(&ds->crit); + size_t avail = fifo_write_avail(ds->buffer); + LeaveCriticalSection(&ds->crit); + return avail; +} + +static size_t dsound_buffer_size(void *data) +{ + return 4 * 1024; +} + const audio_driver_t audio_dsound = { dsound_init, dsound_write, @@ -381,6 +395,8 @@ const audio_driver_t audio_dsound = { dsound_set_nonblock_state, dsound_free, NULL, - "dsound" + "dsound", + dsound_write_avail, + dsound_buffer_size, }; diff --git a/audio/jack.c b/audio/jack.c index aa5d166fa9..87e911bc0f 100644 --- a/audio/jack.c +++ b/audio/jack.c @@ -41,6 +41,7 @@ typedef struct jack pthread_cond_t cond; pthread_mutex_t cond_lock; + size_t buffer_size; } jack_t; static int process_cb(jack_nframes_t nframes, void *data) @@ -163,6 +164,8 @@ static void *ja_init(const char *device, unsigned rate, unsigned latency) } bufsize = find_buffersize(jd, latency); + jd->buffer_size = bufsize; + SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t))); for (int i = 0; i < 2; i++) { @@ -304,6 +307,18 @@ static bool ja_use_float(void *data) return true; } +static size_t ja_write_avail(void *data) +{ + jack_t *jd = (jack_t*)data; + return jack_ringbuffer_write_space(jd->buffer[0]); +} + +static size_t ja_buffer_size(void *data) +{ + jack_t *jd = (jack_t*)data; + return jd->buffer_size; +} + const audio_driver_t audio_jack = { ja_init, ja_write, @@ -312,6 +327,8 @@ const audio_driver_t audio_jack = { ja_set_nonblock_state, ja_free, ja_use_float, - "jack" + "jack", + ja_write_avail, + ja_buffer_size, }; diff --git a/audio/oss.c b/audio/oss.c index 4d01778a12..f907ec02ef 100644 --- a/audio/oss.c +++ b/audio/oss.c @@ -58,8 +58,8 @@ static void *oss_init(const char *device, unsigned rate, unsigned latency) return NULL; } - int frags = (latency * rate * 4)/(1000 * (1 << 9)); - int frag = (frags << 16) | 9; + int frags = (latency * rate * 4)/(1000 * (1 << 10)); + int frag = (frags << 16) | 10; if (ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0) { @@ -135,7 +135,7 @@ static void oss_set_nonblock_state(void *data, bool state) else rc = fcntl(*fd, F_SETFL, fcntl(*fd, F_GETFL) & (~O_NONBLOCK)); if (rc != 0) - fprintf(stderr, "SSNES [ERROR]: Could not set nonblocking on OSS file descriptor. Will not be able to fast-forward.\n"); + SSNES_WARN("Could not set nonblocking on OSS file descriptor. Will not be able to fast-forward.\n"); } static void oss_free(void *data) @@ -147,6 +147,34 @@ static void oss_free(void *data) free(fd); } +static size_t oss_write_avail(void *data) +{ + int *fd = (int*)data; + + audio_buf_info info; + if (ioctl(*fd, SNDCTL_DSP_GETOSPACE, &info) < 0) + { + SSNES_ERR("SNDCTL_DSP_GETOSPACE failed ...\n"); + return 0; + } + + return info.bytes; +} + +static size_t oss_buffer_size(void *data) +{ + int *fd = (int*)data; + + audio_buf_info info; + if (ioctl(*fd, SNDCTL_DSP_GETOSPACE, &info) < 0) + { + SSNES_ERR("SNDCTL_DSP_GETOSPACE failed ...\n"); + return 1; // Return something non-zero to avoid SIGFPE. + } + + return info.fragsize * info.fragstotal; +} + const audio_driver_t audio_oss = { oss_init, oss_write, @@ -155,6 +183,8 @@ const audio_driver_t audio_oss = { oss_set_nonblock_state, oss_free, NULL, - "oss" + "oss", + oss_write_avail, + oss_buffer_size, }; diff --git a/audio/pulse.c b/audio/pulse.c index 7124a2e0b9..a368fe998e 100644 --- a/audio/pulse.c +++ b/audio/pulse.c @@ -30,6 +30,7 @@ typedef struct pa_context *context; pa_stream *stream; bool nonblock; + size_t buffer_size; } pa_t; static void pulse_free(void *data) @@ -153,6 +154,8 @@ static void *pulse_init(const char *device, unsigned rate, unsigned latency) buffer_attr.minreq = -1; buffer_attr.fragsize = -1; + pa->buffer_size = buffer_attr.tlength; + if (pa_stream_connect_playback(pa->stream, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0) goto error; @@ -177,8 +180,9 @@ static ssize_t pulse_write(void *data, const void *buf, size_t size) pa_t *pa = (pa_t*)data; pa_threaded_mainloop_lock(pa->mainloop); - unsigned length = pa_stream_writable_size(pa->stream); + size_t length = pa_stream_writable_size(pa->stream); pa_threaded_mainloop_unlock(pa->mainloop); + while (length < size) { pa_threaded_mainloop_wait(pa->mainloop); @@ -222,6 +226,21 @@ static bool pulse_use_float(void *data) return true; } +static size_t pulse_write_avail(void *data) +{ + pa_t *pa = (pa_t*)data; + pa_threaded_mainloop_lock(pa->mainloop); + size_t length = pa_stream_writable_size(pa->stream); + pa_threaded_mainloop_unlock(pa->mainloop); + return length; +} + +static size_t pulse_buffer_size(void *data) +{ + pa_t *pa = (pa_t*)data; + return pa->buffer_size; +} + const audio_driver_t audio_pulse = { pulse_init, pulse_write, @@ -230,6 +249,8 @@ const audio_driver_t audio_pulse = { pulse_set_nonblock_state, pulse_free, pulse_use_float, - "pulse" + "pulse", + pulse_write_avail, + pulse_buffer_size, }; diff --git a/audio/rsound.c b/audio/rsound.c index 83f4cda5b0..4ac89fcba3 100644 --- a/audio/rsound.c +++ b/audio/rsound.c @@ -181,6 +181,24 @@ static void rs_free(void *data) free(rsd); } +static size_t rs_write_avail(void *data) +{ + rsd_t *rsd = (rsd_t*)data; + + if (rsd->has_error) + return 0; + rsd_callback_lock(rsd->rd); + size_t val = fifo_write_avail(rsd->buffer); + rsd_callback_unlock(rsd->rd); + return val; +} + +static size_t rs_buffer_size(void *data) +{ + (void)data; + return 1024 * 4; +} + const audio_driver_t audio_rsound = { rs_init, rs_write, @@ -189,6 +207,8 @@ const audio_driver_t audio_rsound = { rs_set_nonblock_state, rs_free, NULL, - "rsound" + "rsound", + rs_write_avail, + rs_buffer_size, }; diff --git a/audio/xaudio.c b/audio/xaudio.c index ca4be7ee9c..eac513187e 100644 --- a/audio/xaudio.c +++ b/audio/xaudio.c @@ -24,6 +24,7 @@ typedef struct { xaudio2_t *xa; bool nonblock; + size_t bufsize; } xa_t; static void *xa_init(const char *device, unsigned rate, unsigned latency) @@ -39,7 +40,9 @@ static void *xa_init(const char *device, unsigned rate, unsigned latency) SSNES_LOG("XAudio2: Requesting %d ms latency, using %d ms latency.\n", latency, (int)bufsize * 1000 / rate); - xa->xa = xaudio2_new(rate, 2, bufsize * 2 * sizeof(float)); + xa->bufsize = bufsize * 2 * sizeof(float); + + xa->xa = xaudio2_new(rate, 2, xa->bufsize); if (!xa->xa) { SSNES_ERR("Failed to init XAudio2.\n"); @@ -103,6 +106,18 @@ static void xa_free(void *data) } } +static size_t xa_write_avail(void *data) +{ + xa_t *xa = (xa_t*)data; + return xaudio2_write_avail(xa->xa); +} + +static size_t xa_buffer_size(void *data) +{ + xa_t *xa = (xa_t*)data; + return xa->bufsize; +} + const audio_driver_t audio_xa = { xa_init, xa_write, @@ -111,5 +126,7 @@ const audio_driver_t audio_xa = { xa_set_nonblock_state, xa_free, xa_use_float, - "xaudio" + "xaudio", + xa_write_avail, + xa_buffer_size, }; diff --git a/config.def.h b/config.def.h index 57e26bf29c..5980cc6de5 100644 --- a/config.def.h +++ b/config.def.h @@ -231,6 +231,16 @@ static const int out_latency = 64; // Will sync audio. (recommended) static const bool audio_sync = true; +// Experimental rate control +static const bool rate_control = false; + +// Rate control delta. Defines how much rate_control is allowed to adjust input rate. +static const float rate_control_delta = 0.005; + +////////////// +// Misc +////////////// + // Enables use of rewind. This will incur some memory footprint depending on the save state buffer. // This rewind only works when using bSNES core atm. static const bool rewind_enable = false; diff --git a/driver.c b/driver.c index ad773ca2ae..a27c6eeae3 100644 --- a/driver.c +++ b/driver.c @@ -335,9 +335,21 @@ void init_audio(void) ssnes_assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO); ssnes_assert(g_extern.audio_data.outsamples = (float*)malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO)); - g_extern.audio_data.src_ratio = + g_extern.audio_data.orig_src_ratio = + g_extern.audio_data.src_ratio = (double)g_settings.audio.out_rate / g_settings.audio.in_rate; + if (g_settings.audio.rate_control) + { + if (driver.audio->buffer_size && driver.audio->write_avail) + { + g_extern.audio_data.driver_buffer_size = driver.audio->buffer_size(driver.audio_data); + g_extern.audio_data.rate_control = true; + } + else + SSNES_WARN("Audio rate control was desired, but driver does not support needed features.\n"); + } + #ifdef HAVE_DYLIB init_dsp_plugin(); #endif diff --git a/driver.h b/driver.h index dce12bbfbb..35069c5341 100644 --- a/driver.h +++ b/driver.h @@ -98,6 +98,9 @@ typedef struct audio_driver void (*free)(void *data); bool (*use_float)(void *data); // Defines if driver will take standard floating point samples, or int16_t samples. const char *ident; + + size_t (*write_avail)(void *data); // Optional + size_t (*buffer_size)(void *data); // Optional } audio_driver_t; #define AXIS_NEG(x) (((uint32_t)(x) << 16) | UINT16_C(0xFFFF)) diff --git a/general.h b/general.h index b6b01b4792..d7680c1b13 100644 --- a/general.h +++ b/general.h @@ -138,6 +138,9 @@ struct settings char dsp_plugin[PATH_MAX]; char external_driver[PATH_MAX]; + + bool rate_control; + float rate_control_delta; } audio; struct @@ -300,6 +303,10 @@ struct global dylib_t dsp_lib; const ssnes_dsp_plugin_t *dsp_plugin; void *dsp_handle; + + bool rate_control; + double orig_src_ratio; + size_t driver_buffer_size; } audio_data; struct diff --git a/settings.c b/settings.c index fc39b0c408..a5691f4241 100644 --- a/settings.c +++ b/settings.c @@ -195,6 +195,8 @@ void config_set_defaults(void) strlcpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device)); g_settings.audio.latency = out_latency; g_settings.audio.sync = audio_sync; + g_settings.audio.rate_control = rate_control; + g_settings.audio.rate_control_delta = rate_control_delta; g_settings.rewind_enable = rewind_enable; g_settings.rewind_buffer_size = rewind_buffer_size; @@ -429,6 +431,8 @@ bool config_load_file(const char *path) CONFIG_GET_STRING(audio.device, "audio_device"); CONFIG_GET_INT(audio.latency, "audio_latency"); CONFIG_GET_BOOL(audio.sync, "audio_sync"); + CONFIG_GET_BOOL(audio.rate_control, "audio_rate_control"); + CONFIG_GET_FLOAT(audio.rate_control_delta, "audio_rate_control_delta"); CONFIG_GET_STRING(video.driver, "video_driver"); CONFIG_GET_STRING(audio.driver, "audio_driver"); diff --git a/ssnes.c b/ssnes.c index 389e767512..0b386baae7 100644 --- a/ssnes.c +++ b/ssnes.c @@ -246,6 +246,24 @@ void ssnes_render_cached_frame(void) #endif } +static void readjust_audio_input_rate(void) +{ + int avail = driver.audio->write_avail(driver.audio_data); + //fprintf(stderr, "Audio buffer is %u%% full\n", + // (unsigned)(100 - (avail * 100) / g_extern.audio_data.driver_buffer_size)); + + int half_size = g_extern.audio_data.driver_buffer_size / 2; + int delta_mid = avail - half_size; + double direction = (double)delta_mid / half_size; + + double adjust = 1.0 + g_settings.audio.rate_control_delta * direction; + + g_extern.audio_data.src_ratio = g_extern.audio_data.orig_src_ratio * adjust; + + //fprintf(stderr, "New rate: %lf, Orig rate: %lf\n", + // g_extern.audio_data.src_ratio, g_extern.audio_data.orig_src_ratio); +} + static bool audio_flush(const int16_t *data, size_t samples) { #ifdef HAVE_FFMPEG @@ -284,6 +302,10 @@ static bool audio_flush(const int16_t *data, size_t samples) src_data.data_in = dsp_output.samples ? dsp_output.samples : g_extern.audio_data.data; src_data.data_out = g_extern.audio_data.outsamples; src_data.input_frames = dsp_output.samples ? dsp_output.frames : (samples / 2); + + if (g_extern.audio_data.rate_control) + readjust_audio_input_rate(); + src_data.ratio = g_extern.audio_data.src_ratio; hermite_process(g_extern.audio_data.source, &src_data); diff --git a/ssnes.cfg b/ssnes.cfg index 6045182034..f018ec4795 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -146,6 +146,12 @@ # Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency. # audio_latency = 64 +# Enable experimental audio rate control. +# audio_rate_control = false + +# Controls audio rate control delta. Defines how much input rate can be adjusted dynamically. +# Input rate = in_rate * (1.0 +/- audio_rate_control_delta) +# audio_rate_control_delta = 0.005 #### Input