Skip to content

Commit

Permalink
Add a --audio-output-latency command-line argument
Browse files Browse the repository at this point in the history
This allows optimizing the audio output latency on higher-end CPUs,
especially in projects that do not expose a way to override this setting.
  • Loading branch information
Calinou committed Jun 10, 2023
1 parent 577ab3c commit e48c448
Show file tree
Hide file tree
Showing 17 changed files with 45 additions and 12 deletions.
8 changes: 8 additions & 0 deletions core/config/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class Engine {
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;
Expand Down Expand Up @@ -98,6 +99,9 @@ class Engine {
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();
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/AudioServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
<method name="get_output_latency" qualifiers="const">
<return type="float" />
<description>
Returns the audio driver's output latency.
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.
</description>
</method>
<method name="get_speaker_mode" qualifiers="const">
Expand Down
3 changes: 2 additions & 1 deletion doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@
<member name="audio/driver/output_latency" type="int" setter="" getter="" default="15">
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 &lt;ms&gt;[/code] command line argument.
[b]Note:[/b] This setting is ignored on Android, and on all versions of Windows prior to Windows 10.
</member>
<member name="audio/driver/output_latency.web" type="int" setter="" getter="" default="50">
Safer override for [member audio/driver/output_latency] in the Web platform, to avoid audio issues especially on mobile devices.
Expand Down
2 changes: 1 addition & 1 deletion drivers/alsa/audio_driver_alsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion drivers/coreaudio/audio_driver_coreaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion drivers/pulseaudio/audio_driver_pulseaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion drivers/wasapi/audio_driver_wasapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion drivers/xaudio2/audio_driver_xaudio2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ static bool debug_navigation = false;
static bool debug_avoidance = false;
#endif
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;
Expand Down Expand Up @@ -418,6 +419,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 <ms> 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> Renderer name. Requires driver support.\n");
OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n");
Expand Down Expand Up @@ -914,6 +917,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();
Expand Down Expand Up @@ -1942,6 +1953,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);
Expand All @@ -1955,6 +1969,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
Expand Down
3 changes: 3 additions & 0 deletions misc/dist/linux/godot.6
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Password for remote filesystem.
\fB\-\-audio\-driver\fR <driver>
Audio driver ('PulseAudio', 'ALSA', 'Dummy').
.TP
\fB\-\-audio\-output\-latency\fR <ms>
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 <driver>
Video driver ('Vulkan', 'GLES2').
.SS "Display options:"
Expand Down
1 change: 1 addition & 0 deletions misc/dist/shell/_godot.zsh-completion
Original file line number Diff line number Diff line change
Expand Up @@ -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'))" \
Expand Down
1 change: 1 addition & 0 deletions misc/dist/shell/godot.bash-completion
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ _complete_godot_options() {
--remote-fs
--remote-fs-password
--audio-driver
--audio-output-latency
--display-driver
--rendering-method
--rendering-driver
Expand Down
1 change: 1 addition & 0 deletions misc/dist/shell/godot.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<host/IP>[:<port>] 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)"
Expand Down
2 changes: 1 addition & 1 deletion platform/web/audio_driver_web.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 0 additions & 2 deletions servers/audio_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,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;

Expand Down
2 changes: 0 additions & 2 deletions servers/audio_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit e48c448

Please sign in to comment.