aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorneonloop2022-03-27 16:33:44 +0000
committerneonloop2022-03-27 16:33:44 +0000
commit8870de89ba1ad9ae08e5f4c7602007b05fab5f3e (patch)
tree21447aa0ef726eb04317cdd9f0d9481fcb69f297
parentcaa956d120b34e4c0deadb9e61af509a88debd09 (diff)
downloadpicoarch-8870de89ba1ad9ae08e5f4c7602007b05fab5f3e.tar.gz
picoarch-8870de89ba1ad9ae08e5f4c7602007b05fab5f3e.tar.bz2
picoarch-8870de89ba1ad9ae08e5f4c7602007b05fab5f3e.zip
Adds dynamic audio rate control option
When DRC is on, game syncs to frame rate instead of audio buffer capacity. Audio is resampled to generate more samples when buffer is low and less when buffer is high, to keep buffer 40%-60% full. Buffer size doubled to keep same avg. audio latency value. Audio can distort when buffer is out of range, not often during gameplay. Better resampler could improve but would be slower. When buffer is always out of range (heavy frameskip), it is better to leave off, DRC doesn't help anyway then. Idea from RetroArch audio_driver.c and https://near.sh/articles/audio/dynamic-rate-control
-rw-r--r--config.c1
-rw-r--r--main.c8
-rw-r--r--menu.c6
-rw-r--r--options.c1
-rw-r--r--options.h1
-rw-r--r--plat_sdl.c71
6 files changed, 77 insertions, 11 deletions
diff --git a/config.c b/config.c
index 6f3670a..91835f8 100644
--- a/config.c
+++ b/config.c
@@ -25,6 +25,7 @@ static const struct {
CE_NUM(show_fps),
CE_NUM(show_cpu),
CE_NUM(limit_frames),
+ CE_NUM(enable_drc),
CE_NUM(audio_buffer_size),
CE_NUM(scale_size),
CE_NUM(scale_filter),
diff --git a/main.c b/main.c
index af55f0f..e91b832 100644
--- a/main.c
+++ b/main.c
@@ -31,6 +31,8 @@ static int last_screenshot = 0;
static uint32_t vsyncs;
static uint32_t renders;
+#define UNDERRUN_THRESHOLD 0.5
+
static void toggle_fast_forward(int force_off)
{
static int frameskip_style_was;
@@ -198,6 +200,7 @@ void set_defaults(void)
show_hud = 1;
limit_frames = 1;
enable_audio = 1;
+ enable_drc = 1;
audio_buffer_size = 5;
scale_size = SCALE_SIZE_NONE;
scale_filter = SCALE_FILTER_NEAREST;
@@ -496,7 +499,10 @@ static void adjust_audio(void) {
if (current_core.retro_audio_buffer_status) {
float occupancy = 1.0 - plat_sound_capacity();
- current_core.retro_audio_buffer_status(true, (int)(occupancy * 100), occupancy < 0.50);
+ if (enable_drc)
+ occupancy = MIN(1.0, occupancy * 2.0);
+
+ current_core.retro_audio_buffer_status(true, (int)(occupancy * 100), occupancy < UNDERRUN_THRESHOLD);
}
}
diff --git a/menu.c b/menu.c
index 52a05fe..2926a86 100644
--- a/menu.c
+++ b/menu.c
@@ -484,6 +484,7 @@ static const char h_restore_def[] = "Switches back to default settings";
static const char h_show_fps[] = "Shows frames and vsyncs per second";
static const char h_show_cpu[] = "Shows CPU usage";
+static const char h_enable_drc[] = "Dynamically adjusts audio rate for smoother video";
static const char h_audio_buffer_size[] =
"The size of the audio buffer, in frames. Higher\n"
@@ -511,6 +512,7 @@ static menu_entry e_menu_video_options[] =
mee_enum_h ("Screen size", 0, scale_size, men_scale_size, h_scale_size),
mee_enum_h ("Filter", 0, scale_filter, men_scale_filter, h_scale_filter),
mee_range_h ("Audio buffer", 0, audio_buffer_size, 1, 15, h_audio_buffer_size),
+ mee_onoff_h ("Audio adjustment", 0, enable_drc, 1, h_enable_drc),
mee_end,
};
@@ -519,7 +521,7 @@ static int menu_loop_video_options(int id, int keys)
static int sel = 0;
me_loop(e_menu_video_options, &sel);
- scale_update_scaler();
+ plat_reinit();
return 0;
}
@@ -676,7 +678,7 @@ void menu_loop(void)
me_enable(e_menu_main, MA_MAIN_SAVE_STATE, state_allowed());
me_enable(e_menu_main, MA_MAIN_LOAD_STATE, state_allowed());
me_enable(e_menu_main, MA_MAIN_CHEATS, cheats != NULL);
-
+
me_enable(e_menu_main, MA_MAIN_DISC_CTRL, needs_disc_ctrl);
if (override)
diff --git a/options.c b/options.c
index 95b4c71..6cf27cd 100644
--- a/options.c
+++ b/options.c
@@ -11,6 +11,7 @@ int show_cpu;
int show_hud;
int limit_frames;
int enable_audio;
+int enable_drc;
unsigned audio_buffer_size;
enum scale_size scale_size;
enum scale_filter scale_filter;
diff --git a/options.h b/options.h
index 268a619..c76e251 100644
--- a/options.h
+++ b/options.h
@@ -8,6 +8,7 @@ extern int show_cpu;
extern int show_hud;
extern int limit_frames;
extern int enable_audio;
+extern int enable_drc;
extern unsigned audio_buffer_size;
extern enum scale_size scale_size;
extern enum scale_filter scale_filter;
diff --git a/plat_sdl.c b/plat_sdl.c
index 0beb924..817a47e 100644
--- a/plat_sdl.c
+++ b/plat_sdl.c
@@ -18,6 +18,8 @@ struct audio_state {
struct audio_frame *buf;
int in_sample_rate;
int out_sample_rate;
+ int sample_rate_adj;
+ int adj_out_sample_rate;
};
struct audio_state audio;
@@ -25,11 +27,16 @@ struct audio_state audio;
static void plat_sound_select_resampler(void);
void (*plat_sound_write)(const struct audio_frame *data, int frames);
+#define DRC_MAX_ADJUSTMENT 0.003
+#define DRC_ADJ_BELOW 0.4
+#define DRC_ADJ_ABOVE 0.6
+
static char msg[HUD_LEN];
static unsigned msg_priority = 0;
static unsigned msg_expire = 0;
static bool frame_dirty = false;
+static int frame_time = 1000000 / 60;
static void video_expire_msg(void)
{
@@ -65,16 +72,16 @@ static int audio_resample_nearest(struct audio_frame data) {
static int diff = 0;
int consumed = 0;
- if (diff < audio.out_sample_rate) {
+ if (diff < audio.adj_out_sample_rate) {
audio.buf[audio.buf_w++] = data;
if (audio.buf_w >= audio.buf_len) audio.buf_w = 0;
diff += audio.in_sample_rate;
}
- if (diff >= audio.out_sample_rate) {
+ if (diff >= audio.adj_out_sample_rate) {
consumed++;
- diff -= audio.out_sample_rate;
+ diff -= audio.adj_out_sample_rate;
}
return consumed;
@@ -233,9 +240,25 @@ void plat_video_process(const void *data, unsigned width, unsigned height, size_
void plat_video_flip(void)
{
- if (frame_dirty)
+ static unsigned int next_frame_time_us = 0;
+
+ if (frame_dirty) {
+ unsigned int time = plat_get_ticks_us();
+
+ if (limit_frames && enable_drc && time < next_frame_time_us) {
+ usleep(next_frame_time_us - time);
+ }
+
+ if (!next_frame_time_us)
+ next_frame_time_us = time;
+
fb_flip();
+ do {
+ next_frame_time_us += frame_time;
+ } while (next_frame_time_us < time);
+ }
+
frame_dirty = false;
}
@@ -324,6 +347,9 @@ static int plat_sound_init(void)
audio.in_sample_rate = sample_rate;
audio.out_sample_rate = received.freq;
+ audio.sample_rate_adj = audio.out_sample_rate * DRC_MAX_ADJUSTMENT;
+ audio.adj_out_sample_rate = audio.out_sample_rate;
+
plat_sound_select_resampler();
plat_sound_resize_buffer();
@@ -347,12 +373,22 @@ float plat_sound_capacity(void)
}
#define BATCH_SIZE 100
-void plat_sound_write_resample(const struct audio_frame *data, int frames, int (*resample)(struct audio_frame data))
+void plat_sound_write_resample(const struct audio_frame *data, int frames, int (*resample)(struct audio_frame data), bool drc)
{
int consumed = 0;
if (audio.buf_len == 0)
return;
+ if (drc) {
+ if (plat_sound_capacity() < DRC_ADJ_BELOW) {
+ audio.adj_out_sample_rate = audio.out_sample_rate - audio.sample_rate_adj;
+ } else if (plat_sound_capacity() > DRC_ADJ_ABOVE) {
+ audio.adj_out_sample_rate = audio.out_sample_rate + audio.sample_rate_adj;
+ } else {
+ audio.adj_out_sample_rate = audio.out_sample_rate;
+ }
+ }
+
SDL_LockAudio();
while (frames > 0) {
@@ -382,12 +418,17 @@ void plat_sound_write_resample(const struct audio_frame *data, int frames, int (
void plat_sound_write_passthrough(const struct audio_frame *data, int frames)
{
- plat_sound_write_resample(data, frames, audio_resample_passthrough);
+ plat_sound_write_resample(data, frames, audio_resample_passthrough, false);
}
void plat_sound_write_nearest(const struct audio_frame *data, int frames)
{
- plat_sound_write_resample(data, frames, audio_resample_nearest);
+ plat_sound_write_resample(data, frames, audio_resample_nearest, false);
+}
+
+void plat_sound_write_drc(const struct audio_frame *data, int frames)
+{
+ plat_sound_write_resample(data, frames, audio_resample_nearest, true);
}
void plat_sound_resize_buffer(void) {
@@ -398,6 +439,10 @@ void plat_sound_resize_buffer(void) {
? current_audio_buffer_size * audio.in_sample_rate / frame_rate
: 0;
+ /* Dynamic adjustment keeps buffer 50% full, need double size */
+ if (enable_drc)
+ audio.buf_len *= 2;
+
if (audio.buf_len == 0) {
SDL_UnlockAudio();
return;
@@ -422,7 +467,10 @@ void plat_sound_resize_buffer(void) {
static void plat_sound_select_resampler(void)
{
- if (audio.in_sample_rate == audio.out_sample_rate) {
+ if (enable_drc) {
+ PA_INFO("Using audio adjustment (in: %d, out: %d-%d)\n", audio.in_sample_rate, audio.out_sample_rate - audio.sample_rate_adj, audio.out_sample_rate + audio.sample_rate_adj);
+ plat_sound_write = plat_sound_write_drc;
+ } else if (audio.in_sample_rate == audio.out_sample_rate) {
PA_INFO("Using passthrough resampler (in: %d, out: %d)\n", audio.in_sample_rate, audio.out_sample_rate);
plat_sound_write = plat_sound_write_passthrough;
} else {
@@ -479,7 +527,14 @@ int plat_reinit(void)
PA_ERROR("SDL sound failed to init: %s\n", SDL_GetError());
return -1;
}
+ } else {
+ plat_sound_resize_buffer();
+ plat_sound_select_resampler();
}
+
+ if (frame_rate != 0)
+ frame_time = 1000000 / frame_rate;
+
scale_update_scaler();
return 0;
}