From da6b6c2dd790a1a6f1702efe1b075b09ec76fb48 Mon Sep 17 00:00:00 2001 From: lonesurvivor Date: Tue, 3 Jan 2017 19:00:31 +0100 Subject: [PATCH] Fix for the huge audio latency of the SamplePlayer (>200 ms) - fixes PulseAudio, ALSA and RtAudio driver - cleans up the driver files for better readability (mostly whitespace-related stuff) - makes ALSA and Pulseaudio actually use the global setting "audio/mix_rate" for the sample rate instead of a fixed value (RtAudio did this already) --- drivers/alsa/audio_driver_alsa.cpp | 50 +++----- drivers/alsa/audio_driver_alsa.h | 1 + .../pulseaudio/audio_driver_pulseaudio.cpp | 121 +++++++++--------- drivers/rtaudio/audio_driver_rtaudio.cpp | 104 +++++++++------ 4 files changed, 145 insertions(+), 131 deletions(-) diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp index 9895a10007f..b026241579c 100644 --- a/drivers/alsa/audio_driver_alsa.cpp +++ b/drivers/alsa/audio_driver_alsa.cpp @@ -45,7 +45,7 @@ Error AudioDriverALSA::init() { samples_in = NULL; samples_out = NULL; - mix_rate = 44100; + mix_rate = GLOBAL_DEF("audio/mix_rate",44100); output_format = OUTPUT_STEREO; channels = 2; @@ -70,67 +70,62 @@ Error AudioDriverALSA::init() { ERR_FAIL_COND_V( status<0, ERR_CANT_OPEN ); snd_pcm_hw_params_alloca(&hwparams); - status = snd_pcm_hw_params_any(pcm_handle, hwparams); + status = snd_pcm_hw_params_any(pcm_handle, hwparams); CHECK_FAIL( status<0 ); status = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); - CHECK_FAIL( status<0 ); //not interested in anything else status = snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE); - CHECK_FAIL( status<0 ); //todo: support 4 and 6 status = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2); - CHECK_FAIL( status<0 ); status = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &mix_rate, NULL); - - CHECK_FAIL( status<0 ); int latency = GLOBAL_DEF("audio/output_latency",25); buffer_size = nearest_power_of_2( latency * mix_rate / 1000 ); - status = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &buffer_size, NULL); - + // set buffer size from project settings + status = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size); + CHECK_FAIL( status<0 ); + // make period size 1/8 + period_size = buffer_size >> 3; + status = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &period_size, NULL); CHECK_FAIL( status<0 ); unsigned int periods=2; status = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &periods, NULL); - CHECK_FAIL( status<0 ); status = snd_pcm_hw_params(pcm_handle,hwparams); - CHECK_FAIL( status<0 ); //snd_pcm_hw_params_free(&hwparams); snd_pcm_sw_params_alloca(&swparams); + status = snd_pcm_sw_params_current(pcm_handle, swparams); CHECK_FAIL( status<0 ); - status = snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, buffer_size); - + status = snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, period_size); CHECK_FAIL( status<0 ); status = snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1); - CHECK_FAIL( status<0 ); status = snd_pcm_sw_params(pcm_handle, swparams); - CHECK_FAIL( status<0 ); - samples_in = memnew_arr(int32_t, buffer_size*channels); - samples_out = memnew_arr(int16_t, buffer_size*channels); + samples_in = memnew_arr(int32_t, period_size*channels); + samples_out = memnew_arr(int16_t, period_size*channels); snd_pcm_nonblock(pcm_handle, 0); @@ -144,36 +139,28 @@ void AudioDriverALSA::thread_func(void* p_udata) { AudioDriverALSA* ad = (AudioDriverALSA*)p_udata; - while (!ad->exit_thread) { - - if (!ad->active) { - - for (unsigned int i=0; i < ad->buffer_size*ad->channels; i++) { - + for (unsigned int i=0; i < ad->period_size*ad->channels; i++) { ad->samples_out[i] = 0; }; } else { - ad->lock(); - ad->audio_server_process(ad->buffer_size, ad->samples_in); + ad->audio_server_process(ad->period_size, ad->samples_in); ad->unlock(); - for(unsigned int i=0;ibuffer_size*ad->channels;i++) { - + for(unsigned int i=0;iperiod_size*ad->channels;i++) { ad->samples_out[i]=ad->samples_in[i]>>16; } }; - int todo = ad->buffer_size; // * ad->channels * 2; + int todo = ad->period_size; int total = 0; while (todo) { - if (ad->exit_thread) break; uint8_t* src = (uint8_t*)ad->samples_out; @@ -184,7 +171,8 @@ void AudioDriverALSA::thread_func(void* p_udata) { break; if ( wrote == -EAGAIN ) { - usleep(1000); //can't write yet (though this is blocking..) + //can't write yet (though this is blocking..) + usleep(1000); continue; } wrote = snd_pcm_recover(ad->pcm_handle, wrote, 0); @@ -197,9 +185,9 @@ void AudioDriverALSA::thread_func(void* p_udata) { } continue; }; + total += wrote; todo -= wrote; - }; }; diff --git a/drivers/alsa/audio_driver_alsa.h b/drivers/alsa/audio_driver_alsa.h index 487f0175848..df28294f56d 100644 --- a/drivers/alsa/audio_driver_alsa.h +++ b/drivers/alsa/audio_driver_alsa.h @@ -51,6 +51,7 @@ class AudioDriverALSA : public AudioDriverSW { OutputFormat output_format; snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; int channels; bool active; diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 521df2e5217..3a1317cbf63 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -36,48 +36,56 @@ Error AudioDriverPulseAudio::init() { - active = false; - thread_exited = false; - exit_thread = false; + active = false; + thread_exited = false; + exit_thread = false; pcm_open = false; samples_in = NULL; samples_out = NULL; - mix_rate = 44100; + mix_rate = GLOBAL_DEF("audio/mix_rate",44100); output_format = OUTPUT_STEREO; channels = 2; - pa_sample_spec spec; - spec.format = PA_SAMPLE_S16LE; - spec.channels = channels; - spec.rate = mix_rate; + pa_sample_spec spec; + spec.format = PA_SAMPLE_S16LE; + spec.channels = channels; + spec.rate = mix_rate; - int error_code; - pulse = pa_simple_new(NULL, // default server - "Godot", // application name - PA_STREAM_PLAYBACK, - NULL, // default device - "Sound", // stream description - &spec, - NULL, // use default channel map - NULL, // use default buffering attributes - &error_code - ); + int latency = GLOBAL_DEF("audio/output_latency", 25); + buffer_size = nearest_power_of_2(latency * mix_rate / 1000); - if (pulse == NULL) { + pa_buffer_attr attr; + // set to appropriate buffer size from global settings + attr.tlength = buffer_size; + // set them to be automatically chosen + attr.prebuf = (uint32_t)-1; + attr.maxlength = (uint32_t)-1; + attr.minreq = (uint32_t)-1; - fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));\ - ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN); - } + int error_code; + pulse = pa_simple_new( NULL, // default server + "Godot", // application name + PA_STREAM_PLAYBACK, + NULL, // default device + "Sound", // stream description + &spec, + NULL, // use default channel map + &attr, // use buffering attributes from above + &error_code + ); - int latency = GLOBAL_DEF("audio/output_latency", 25); - buffer_size = nearest_power_of_2(latency * mix_rate / 1000); + if (pulse == NULL) { + fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));\ + ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN); + } - samples_in = memnew_arr(int32_t, buffer_size * channels); - samples_out = memnew_arr(int16_t, buffer_size * channels); - mutex = Mutex::create(); - thread = Thread::create(AudioDriverPulseAudio::thread_func, this); + samples_in = memnew_arr(int32_t, buffer_size * channels); + samples_out = memnew_arr(int16_t, buffer_size * channels); + + mutex = Mutex::create(); + thread = Thread::create(AudioDriverPulseAudio::thread_func, this); return OK; } @@ -95,47 +103,40 @@ float AudioDriverPulseAudio::get_latency() { void AudioDriverPulseAudio::thread_func(void* p_udata) { - AudioDriverPulseAudio* ad = (AudioDriverPulseAudio*)p_udata; + AudioDriverPulseAudio* ad = (AudioDriverPulseAudio*)p_udata; while (!ad->exit_thread) { - if (!ad->active) { - - for (unsigned int i=0; i < ad->buffer_size * ad->channels; i++) { - + for (unsigned int i=0; i < ad->buffer_size * ad->channels; i++) { ad->samples_out[i] = 0; - } + } } else { - ad->lock(); ad->audio_server_process(ad->buffer_size, ad->samples_in); ad->unlock(); - for (unsigned int i=0; i < ad->buffer_size * ad->channels;i ++) { - - ad->samples_out[i] = ad->samples_in[i] >> 16; + for (unsigned int i=0; i < ad->buffer_size * ad->channels;i ++) { + ad->samples_out[i] = ad->samples_in[i] >> 16; } - } + } - // pa_simple_write always consumes the entire buffer + // pa_simple_write always consumes the entire buffer - int error_code; - int byte_size = ad->buffer_size * sizeof(int16_t) * ad->channels; - if (pa_simple_write(ad->pulse, ad->samples_out, byte_size, &error_code) < 0) { + int error_code; + int byte_size = ad->buffer_size * sizeof(int16_t) * ad->channels; + if (pa_simple_write(ad->pulse, ad->samples_out, byte_size, &error_code) < 0) { + // can't recover here + fprintf(stderr, "PulseAudio failed and can't recover: %s\n", pa_strerror(error_code)); + ad->active = false; + ad->exit_thread = true; + break; + } + } - // can't recover here - fprintf(stderr, "PulseAudio failed and can't recover: %s\n", pa_strerror(error_code)); - ad->active = false; - ad->exit_thread = true; - break; - } - - } - - ad->thread_exited = true; + ad->thread_exited = true; } void AudioDriverPulseAudio::start() { @@ -184,10 +185,10 @@ void AudioDriverPulseAudio::finish() { }; memdelete(thread); - if (mutex) { + if (mutex) { memdelete(mutex); - mutex = NULL; - } + mutex = NULL; + } thread = NULL; } @@ -195,9 +196,9 @@ void AudioDriverPulseAudio::finish() { AudioDriverPulseAudio::AudioDriverPulseAudio() { mutex = NULL; - thread = NULL; - pulse = NULL; - latency=0; + thread = NULL; + pulse = NULL; + latency=0; } AudioDriverPulseAudio::~AudioDriverPulseAudio() { diff --git a/drivers/rtaudio/audio_driver_rtaudio.cpp b/drivers/rtaudio/audio_driver_rtaudio.cpp index 4b145c09fb7..a798990449b 100644 --- a/drivers/rtaudio/audio_driver_rtaudio.cpp +++ b/drivers/rtaudio/audio_driver_rtaudio.cpp @@ -47,9 +47,7 @@ const char* AudioDriverRtAudio::get_name() const { } -// Two-channel sawtooth wave generator. -int AudioDriverRtAudio::callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, - double streamTime, RtAudioStreamStatus status, void *userData ) { +int AudioDriverRtAudio::callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData ) { if (status) { if (status & RTAUDIO_INPUT_OVERFLOW) { @@ -64,8 +62,6 @@ int AudioDriverRtAudio::callback( void *outputBuffer, void *inputBuffer, unsigne AudioDriverRtAudio *self = (AudioDriverRtAudio*)userData; if (self->mutex->try_lock()!=OK) { - - // what should i do.. for(unsigned int i=0;igetDefaultOutputDevice(); RtAudio::StreamOptions options; + + // set the desired numberOfBuffers + unsigned int target_number_of_buffers = 4; + options.numberOfBuffers = target_number_of_buffers; + // options. // RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE). */// // unsigned int numberOfBuffers; /*!< Number of stream buffers. */ // std::string streamName; /*!< A stream name (currently used only in Jack). */ // int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ - parameters.firstChannel = 0; mix_rate = GLOBAL_DEF("audio/mix_rate",44100); int latency = GLOBAL_DEF("audio/output_latency",25); - unsigned int buffer_size = nearest_power_of_2( latency * mix_rate / 1000 ); + // calculate desired buffer_size, taking the desired numberOfBuffers into account (latency depends on numberOfBuffers*buffer_size) + unsigned int buffer_size = nearest_power_of_2( latency * mix_rate / 1000 / target_number_of_buffers); + if (OS::get_singleton()->is_stdout_verbose()) { print_line("audio buffer size: "+itos(buffer_size)); } -// bool success=false; - - while( true) { - - switch(output_format) { - - case OUTPUT_MONO: parameters.nChannels = 1; break; - case OUTPUT_STEREO: parameters.nChannels = 2; break; - case OUTPUT_QUAD: parameters.nChannels = 4; break; - case OUTPUT_5_1: parameters.nChannels = 6; break; - }; - - - try { - dac->openStream( ¶meters, NULL, RTAUDIO_SINT32, - mix_rate, &buffer_size, &callback, this,&options ); - mutex = Mutex::create(true); - active=true; - - break; - } catch ( RtAudioError& e ) { - // try with less channels - - ERR_PRINT("Unable to open audio, retrying with fewer channels.."); + short int tries = 2; + while(true) { + while( true) { switch(output_format) { - - case OUTPUT_MONO: ERR_EXPLAIN("Unable to open audio."); ERR_FAIL_V( ERR_UNAVAILABLE ); break; - case OUTPUT_STEREO: output_format=OUTPUT_MONO; break; - case OUTPUT_QUAD: output_format=OUTPUT_STEREO; break; - case OUTPUT_5_1: output_format=OUTPUT_QUAD; break; + case OUTPUT_MONO: parameters.nChannels = 1; break; + case OUTPUT_STEREO: parameters.nChannels = 2; break; + case OUTPUT_QUAD: parameters.nChannels = 4; break; + case OUTPUT_5_1: parameters.nChannels = 6; break; }; - } - } + try { + dac->openStream( ¶meters, NULL, RTAUDIO_SINT32, mix_rate, &buffer_size, &callback, this,&options ); + mutex = Mutex::create(true); + active=true; + + break; + } catch ( RtAudioError& e ) { + // try with less channels + ERR_PRINT("Unable to open audio, retrying with fewer channels.."); + + switch(output_format) { + case OUTPUT_MONO: ERR_EXPLAIN("Unable to open audio."); ERR_FAIL_V( ERR_UNAVAILABLE ); break; + case OUTPUT_STEREO: output_format=OUTPUT_MONO; break; + case OUTPUT_QUAD: output_format=OUTPUT_STEREO; break; + case OUTPUT_5_1: output_format=OUTPUT_QUAD; break; + }; + } + } + + // compare actual numberOfBuffers with the desired one. If not equal, close and reopen the stream with adjusted buffer size, so the desired output_latency is still correct + if(target_number_of_buffers != options.numberOfBuffers) { + if(tries <= 0) { + ERR_EXPLAIN("RtAudio: Unable to set correct number of buffers."); + ERR_FAIL_V( ERR_UNAVAILABLE ); + break; + } + + try { + dac->closeStream(); + } catch ( RtAudioError& e ) { + ERR_PRINT(e.what()); + ERR_FAIL_V( ERR_UNAVAILABLE ); + break; + } + if (OS::get_singleton()->is_stdout_verbose()) + print_line("RtAudio: Desired number of buffers (" + itos(target_number_of_buffers) + ") not available. Using " + itos(options.numberOfBuffers) + " instead. Reopening stream with adjusted buffer_size."); + + // new buffer size dependent on the ratio between set and actual numberOfBuffers + buffer_size = buffer_size / (options.numberOfBuffers / target_number_of_buffers); + target_number_of_buffers = options.numberOfBuffers; + tries--; + } else { + break; + } + + + } return OK; } @@ -190,7 +214,6 @@ void AudioDriverRtAudio::unlock() { void AudioDriverRtAudio::finish() { - if ( active && dac->isStreamOpen() ) dac->closeStream(); if (mutex) @@ -203,6 +226,7 @@ void AudioDriverRtAudio::finish() { AudioDriverRtAudio::AudioDriverRtAudio() { + mutex=NULL; mix_rate=44100; output_format=OUTPUT_STEREO;