From 6f1152bdbe1338998b41c51c5a39dc6dac28ae31 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Thu, 8 Jun 2023 19:46:05 +0200 Subject: [PATCH] Add a `--audio-output-latency` command-line argument This allows optimizing the audio output latency on higher-end CPUs, especially in projects that do not expose a way to override this setting. --- core/config/engine.cpp | 8 ++++++++ core/config/engine.h | 4 ++++ doc/classes/AudioServer.xml | 3 ++- doc/classes/ProjectSettings.xml | 3 ++- drivers/alsa/audio_driver_alsa.cpp | 2 +- drivers/coreaudio/audio_driver_coreaudio.cpp | 2 +- drivers/pulseaudio/audio_driver_pulseaudio.cpp | 2 +- drivers/wasapi/audio_driver_wasapi.cpp | 2 +- drivers/xaudio2/audio_driver_xaudio2.cpp | 2 +- main/main.cpp | 18 ++++++++++++++++++ misc/dist/linux/godot.6 | 3 +++ misc/dist/shell/_godot.zsh-completion | 1 + misc/dist/shell/godot.bash-completion | 1 + misc/dist/shell/godot.fish | 1 + platform/web/audio_driver_web.cpp | 2 +- servers/audio_server.cpp | 2 -- servers/audio_server.h | 2 -- 17 files changed, 46 insertions(+), 12 deletions(-) diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 6727c58fd111..17d3bdb744de 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -74,6 +74,14 @@ int Engine::get_max_fps() const { return _max_fps; } +void Engine::set_audio_output_latency(int p_msec) { + _audio_output_latency = p_msec > 1 ? p_msec : 1; +} + +int Engine::get_audio_output_latency() const { + return _audio_output_latency; +} + uint64_t Engine::get_frames_drawn() { return frames_drawn; } diff --git a/core/config/engine.h b/core/config/engine.h index 73d40d50ae49..ff88fbc787cf 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -61,6 +61,7 @@ private: double physics_jitter_fix = 0.5; double _fps = 1; int _max_fps = 0; + int _audio_output_latency = 0; double _time_scale = 1.0; uint64_t _physics_frames = 0; int max_physics_steps_per_frame = 8; @@ -99,6 +100,9 @@ public: virtual void set_max_fps(int p_fps); virtual int get_max_fps() const; + virtual void set_audio_output_latency(int p_msec); + virtual int get_audio_output_latency() const; + virtual double get_frames_per_second() const { return _fps; } uint64_t get_frames_drawn(); diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index cb4fb8d5ca9b..5d3d7a059118 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -132,7 +132,8 @@ - Returns the audio driver's output latency. This can be expensive, it is not recommended to call this every frame. + Returns the audio driver's effective output latency. This is based on [member ProjectSettings.audio/driver/output_latency], but the exact returned value will differ depending on the operating system and audio driver. + [b]Note:[/b] This can be expensive; it is not recommended to call [method get_output_latency] every frame. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index fcdcdc697e06..f426030f98be 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -378,7 +378,8 @@ Specifies the preferred output latency in milliseconds for audio. Lower values will result in lower audio latency at the cost of increased CPU usage. Low values may result in audible cracking on slower hardware. Audio output latency may be constrained by the host operating system and audio hardware drivers. If the host can not provide the specified audio output latency then Godot will attempt to use the nearest latency allowed by the host. As such you should always use [method AudioServer.get_output_latency] to determine the actual audio output latency. - [b]Note:[/b] This setting is ignored on all versions of Windows prior to Windows 10. + Audio output latency can be overridden using the [code]--audio-output-latency <ms>[/code] command line argument. + [b]Note:[/b] This setting is ignored on Android, and on all versions of Windows prior to Windows 10. Safer override for [member audio/driver/output_latency] in the Web platform, to avoid audio issues especially on mobile devices. diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp index 966137920a9a..764d66c3b8d1 100644 --- a/drivers/alsa/audio_driver_alsa.cpp +++ b/drivers/alsa/audio_driver_alsa.cpp @@ -111,7 +111,7 @@ Error AudioDriverALSA::init_output_device() { // In ALSA the period size seems to be the one that will determine the actual latency // Ref: https://www.alsa-project.org/main/index.php/FramesPeriods unsigned int periods = 2; - int latency = GLOBAL_GET("audio/driver/output_latency"); + int latency = Engine::get_singleton()->get_audio_output_latency(); buffer_frames = closest_power_of_2(latency * mix_rate / 1000); buffer_size = buffer_frames * periods; period_size = buffer_frames; diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp index 7074cc7187c2..f3920863b495 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.cpp +++ b/drivers/coreaudio/audio_driver_coreaudio.cpp @@ -131,7 +131,7 @@ Error AudioDriverCoreAudio::init() { result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &strdesc, sizeof(strdesc)); ERR_FAIL_COND_V(result != noErr, FAILED); - int latency = GLOBAL_GET("audio/driver/output_latency"); + int latency = Engine::get_singleton()->get_audio_output_latency(); // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) buffer_frames = closest_power_of_2(latency * mix_rate / 1000); diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 8ca396af2018..76b1781dd5af 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -222,7 +222,7 @@ Error AudioDriverPulseAudio::init_output_device() { break; } - int tmp_latency = GLOBAL_GET("audio/driver/output_latency"); + int tmp_latency = Engine::get_singleton()->get_audio_output_latency(); buffer_frames = closest_power_of_2(tmp_latency * mix_rate / 1000); pa_buffer_size = buffer_frames * pa_map.channels; diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 6c84aee2b724..e39373e7a04c 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -555,7 +555,7 @@ Error AudioDriverWASAPI::finish_input_device() { Error AudioDriverWASAPI::init() { mix_rate = _get_configured_mix_rate(); - target_latency_ms = GLOBAL_GET("audio/driver/output_latency"); + target_latency_ms = Engine::get_singleton()->get_audio_output_latency(); Error err = init_output_device(); if (err != OK) { diff --git a/drivers/xaudio2/audio_driver_xaudio2.cpp b/drivers/xaudio2/audio_driver_xaudio2.cpp index 22063c52d913..3fe3ea058b3f 100644 --- a/drivers/xaudio2/audio_driver_xaudio2.cpp +++ b/drivers/xaudio2/audio_driver_xaudio2.cpp @@ -45,7 +45,7 @@ Error AudioDriverXAudio2::init() { speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_GET("audio/driver/output_latency"); + int latency = Engine::get_singleton()->get_audio_output_latency(); buffer_size = closest_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_size * channels); diff --git a/main/main.cpp b/main/main.cpp index 60f04df3d554..0a9ebd4c6103 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -212,6 +212,7 @@ static bool debug_avoidance = false; #endif static int max_fps = -1; static int frame_delay = 0; +static int audio_output_latency = 0; static bool disable_render_loop = false; static int fixed_fps = -1; static MovieWriter *movie_writer = nullptr; @@ -424,6 +425,8 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(")"); } OS::get_singleton()->print("].\n"); + OS::get_singleton()->print(" --audio-output-latency Override audio output latency in milliseconds (default is 15 ms).\n"); + OS::get_singleton()->print(" Lower values make sound playback more reactive but increase CPU usage, and may result in audio cracking if the CPU can't keep up.\n"); OS::get_singleton()->print(" --rendering-method Renderer name. Requires driver support.\n"); OS::get_singleton()->print(" --rendering-driver Rendering driver (depends on display driver).\n"); @@ -936,6 +939,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->print("Missing audio driver argument, aborting.\n"); goto error; } + } else if (I->get() == "--audio-output-latency") { + if (I->next()) { + audio_output_latency = I->next()->get().to_int(); + N = I->next()->next(); + } else { + OS::get_singleton()->print("Missing audio output latency argument, aborting.\n"); + goto error; + } } else if (I->get() == "--text-driver") { if (I->next()) { text_driver = I->next()->get(); @@ -1986,6 +1997,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Engine::get_singleton()->set_max_physics_steps_per_frame(GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "physics/common/max_physics_steps_per_frame", PROPERTY_HINT_RANGE, "1,100,1"), 8)); Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); Engine::get_singleton()->set_max_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/max_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0)); + Engine::get_singleton()->set_audio_output_latency(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "audio/driver/output_latency", PROPERTY_HINT_RANGE, "1,100,1"), 15)); + // Use a safer default output_latency for web to avoid audio cracking on low-end devices, especially mobile. + GLOBAL_DEF_RST("audio/driver/output_latency.web", 50); GLOBAL_DEF("debug/settings/stdout/print_fps", false); GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false); @@ -2007,6 +2021,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph frame_delay = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), 0); } + if (audio_output_latency >= 1) { + Engine::get_singleton()->set_audio_output_latency(audio_output_latency); + } + OS::get_singleton()->set_low_processor_usage_mode(GLOBAL_DEF("application/run/low_processor_mode", false)); OS::get_singleton()->set_low_processor_usage_mode_sleep_usec( GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater"), 6900)); // Roughly 144 FPS diff --git a/misc/dist/linux/godot.6 b/misc/dist/linux/godot.6 index a00a69299c3d..beb92c96b574 100644 --- a/misc/dist/linux/godot.6 +++ b/misc/dist/linux/godot.6 @@ -58,6 +58,9 @@ Password for remote filesystem. \fB\-\-audio\-driver\fR Audio driver ('PulseAudio', 'ALSA', 'Dummy'). .TP +\fB\-\-audio\-output\-latency\fR +Override audio output latency in milliseconds (default is 15 ms). Lower values make sound playback more reactive but increase CPU usage, and may result in audio cracking if the CPU can't keep up. +.TP \fB\-\-video\-driver\fR Video driver ('Vulkan', 'GLES2'). .SS "Display options:" diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion index 3b12fdbc22e9..490af0a8a5a0 100644 --- a/misc/dist/shell/_godot.zsh-completion +++ b/misc/dist/shell/_godot.zsh-completion @@ -43,6 +43,7 @@ _arguments \ '--remote-fs[use a remote filesystem]:remote filesystem address' \ '--remote-fs-password[password for remote filesystem]:remote filesystem password' \ '--audio-driver[set the audio driver]:audio driver name' \ + '--audio-output-latency[override audio output latency in milliseconds (default is 15 ms)]:number of milliseconds' \ '--display-driver[set the display driver]:display driver name' \ "--rendering-method[set the renderer]:renderer name:((forward_plus\:'Desktop renderer' mobile\:'Desktop and mobile renderer' gl_compatibility\:'Desktop, mobile and web renderer'))" \ "--rendering-driver[set the rendering driver]:rendering driver name:((vulkan\:'Vulkan renderer' opengl3\:'OpenGL ES 3.0 renderer' dummy\:'Dummy renderer'))" \ diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion index ba4edc4f2553..c78f0a1f336e 100644 --- a/misc/dist/shell/godot.bash-completion +++ b/misc/dist/shell/godot.bash-completion @@ -46,6 +46,7 @@ _complete_godot_options() { --remote-fs --remote-fs-password --audio-driver +--audio-output-latency --display-driver --rendering-method --rendering-driver diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish index bb4ff0b558a4..fbfa7344f11f 100644 --- a/misc/dist/shell/godot.fish +++ b/misc/dist/shell/godot.fish @@ -59,6 +59,7 @@ complete -c godot -l render-thread -d "Set the render thread mode" -x -a "unsafe complete -c godot -l remote-fs -d "Use a remote filesystem ([:] address)" -x complete -c godot -l remote-fs-password -d "Password for remote filesystem" -x complete -c godot -l audio-driver -d "Set the audio driver" -x +complete -c godot -l audio-output-latency -d "Override audio output latency in milliseconds (default is 15 ms)" -x complete -c godot -l display-driver -d "Set the display driver" -x complete -c godot -l rendering-method -d "Set the renderer" -x -a "(godot_rendering_method_args)" complete -c godot -l rendering-driver -d "Set the rendering driver" -x -a "(godot_rendering_driver_args)" diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index c6c67db3de29..1298d28ebf61 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -101,7 +101,7 @@ void AudioDriverWeb::_audio_driver_capture(int p_from, int p_samples) { } Error AudioDriverWeb::init() { - int latency = GLOBAL_GET("audio/driver/output_latency"); + int latency = Engine::get_singleton()->get_audio_output_latency(); if (!audio_context.inited) { audio_context.mix_rate = _get_configured_mix_rate(); audio_context.channel_count = godot_audio_init(&audio_context.mix_rate, latency, &_state_change_callback, &_latency_update_callback); diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 2308fcca9ebd..6a03a67fcd75 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -201,8 +201,6 @@ void AudioDriverManager::initialize(int p_driver) { GLOBAL_DEF_RST("audio/driver/enable_input", false); GLOBAL_DEF_RST("audio/driver/mix_rate", DEFAULT_MIX_RATE); GLOBAL_DEF_RST("audio/driver/mix_rate.web", 0); // Safer default output_latency for web (use browser default). - GLOBAL_DEF_RST("audio/driver/output_latency", DEFAULT_OUTPUT_LATENCY); - GLOBAL_DEF_RST("audio/driver/output_latency.web", 50); // Safer default output_latency for web. int failed_driver = -1; diff --git a/servers/audio_server.h b/servers/audio_server.h index 6585043f6318..9ffa95bc00e4 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -138,8 +138,6 @@ class AudioDriverManager { MAX_DRIVERS = 10 }; - static const int DEFAULT_OUTPUT_LATENCY = 15; - static AudioDriver *drivers[MAX_DRIVERS]; static int driver_count;