diff options
author | neonloop | 2022-03-27 16:33:44 +0000 |
---|---|---|
committer | neonloop | 2022-03-27 16:33:44 +0000 |
commit | 8870de89ba1ad9ae08e5f4c7602007b05fab5f3e (patch) | |
tree | 21447aa0ef726eb04317cdd9f0d9481fcb69f297 | |
parent | caa956d120b34e4c0deadb9e61af509a88debd09 (diff) | |
download | picoarch-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.c | 1 | ||||
-rw-r--r-- | main.c | 8 | ||||
-rw-r--r-- | menu.c | 6 | ||||
-rw-r--r-- | options.c | 1 | ||||
-rw-r--r-- | options.h | 1 | ||||
-rw-r--r-- | plat_sdl.c | 71 |
6 files changed, 77 insertions, 11 deletions
@@ -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), @@ -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); } } @@ -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) @@ -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; @@ -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; @@ -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; } |