The parameter buffer_latency_msec can be used together with latency_msec. If buffer_latency_msec is specified, the resulting latency will be latency_msec + buffer_latency_msec. Latency_msec then refers only to the source/sink latency while buffer_latency_msec specifies the buffer part. This can be used to save a lot of CPU at low latencies, running 10 ms latency with latency_msec=6 buffer_latency_msec=4 gives 8% CPU on my system compared to 12% when I only specify latency_msec=10. Additionally you can go beyond the safe-guard limits that are built in, you can access the range 1 - 3 ms or lower the buffer latency for fixed latency devices. Some of my USB devices run fine at a buffer latency of fragment size + 4 ms instead of the default fragment size + 20 ms. --- src/modules/module-loopback.c | 81 +++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 7474ef2..3cff092 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -47,6 +47,7 @@ PA_MODULE_USAGE( "sink=<sink to connect to> " "adjust_time=<how often to readjust rates in s> " "latency_msec=<latency in ms> " + "buffer_latency_msec=<buffer latency in ms> " "format=<sample format> " "rate=<sample rate> " "channels=<number of channels> " @@ -101,6 +102,7 @@ struct userdata { bool in_pop; bool pop_called; bool source_sink_changed; + bool buffer_latency_set; struct { int64_t send_counter; @@ -120,6 +122,7 @@ static const char* const valid_modargs[] = { "sink", "adjust_time", "latency_msec", + "buffer_latency_msec", "format", "rate", "channels", @@ -270,7 +273,10 @@ static void adjust_rates(struct userdata *u) { source_sink_latency = u->sink_latency_sum / u->sink_adjust_counter + u->source_latency_sum / u->source_adjust_counter; - final_latency = PA_MAX(u->latency, source_sink_latency + u->buffer_latency); + final_latency = u->latency; + if (u->buffer_latency_set) + final_latency += u->initial_buffer_latency; + final_latency = PA_MAX(final_latency, source_sink_latency + u->buffer_latency); latency_difference = (int32_t)(corrected_latency - final_latency); pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms (at the base rate: %0.2f ms, old estimate: %0.2f ms)", @@ -347,8 +353,12 @@ static void update_adjust_timer(struct userdata *u) { } static pa_usec_t get_requested_latency(struct userdata *u) { + pa_usec_t requested_latency; - return PA_MAX(u->configured_sink_latency + u->buffer_latency, u->latency); + requested_latency = u->latency; + if(u->buffer_latency_set) + requested_latency += u->buffer_latency; + return PA_MAX(u->configured_sink_latency + u->buffer_latency, requested_latency); } /* Called from all contexts */ @@ -440,12 +450,15 @@ static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, static void set_source_output_latency(struct userdata *u, pa_source *source) { pa_usec_t min_latency, max_latency, buffer_msec, latency; - /* Set lower limit of source latency to 2.333 ms */ + /* Set lower limit of source latency to 2.333 ms, if buffer + * latency is specified, use latency_msec as source latency */ latency = PA_MAX(u->latency / 3, 2.333 * PA_USEC_PER_MSEC); + if (u->buffer_latency_set) + latency = u->latency; if(source->flags & PA_SOURCE_DYNAMIC_LATENCY) { pa_source_get_latency_range(source, &min_latency, &max_latency); - if (min_latency > latency) { + if (min_latency > latency && !u->buffer_latency_set) { u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(min_latency * 0.75)); pa_log_warn("Cannot set requested source latency, adjusting buffer to %0.2f ms", (double)u->buffer_latency / PA_USEC_PER_MSEC); } @@ -456,7 +469,7 @@ static void set_source_output_latency(struct userdata *u, pa_source *source) { if (latency == 0) latency = pa_source_get_fixed_latency(source); buffer_msec = u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC; - if (u->buffer_latency < buffer_msec * PA_USEC_PER_MSEC) { + if (!u->buffer_latency_set && u->buffer_latency < buffer_msec * PA_USEC_PER_MSEC) { pa_log_warn("Fixed latency device, setting buffer latency to %zd.00 ms", buffer_msec); u->buffer_latency = buffer_msec * PA_USEC_PER_MSEC; } @@ -561,10 +574,12 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { memblockq_adjust(u, 0, true); return; } - if (u->sink_input->sink->flags & PA_SINK_DYNAMIC_LATENCY) - u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(u->configured_sink_latency * 0.75)); - else - u->buffer_latency = PA_MAX(u->buffer_latency, (u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC) * PA_USEC_PER_MSEC); + if (!u->buffer_latency_set) { + if (u->sink_input->sink->flags & PA_SINK_DYNAMIC_LATENCY) + u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(u->configured_sink_latency * 0.75)); + else + u->buffer_latency = PA_MAX(u->buffer_latency, (u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC) * PA_USEC_PER_MSEC); + } pa_sink_input_get_latency(u->sink_input, &sink_latency); if (u->send_counter > u->recv_counter) @@ -612,7 +627,7 @@ static void update_source_requested_latency_cb(pa_source_output *i) { if (source_latency > u->configured_source_latency) { pa_log_warn("Source latency increased to %0.2f ms", (double)source_latency / PA_USEC_PER_MSEC); u->configured_source_latency = source_latency; - if (u->buffer_latency < source_latency * 0.75) + if (!u->buffer_latency_set && u->buffer_latency < source_latency * 0.75) u->buffer_latency = source_latency * 0.75; if (!u->source_sink_changed) { u->source_adjust_counter = 0; @@ -744,12 +759,15 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in static void set_sink_input_latency(struct userdata *u, pa_sink *sink) { pa_usec_t min_latency, max_latency, buffer_msec, latency; - /* Set lower limit of sink latency to 2.333 ms */ + /* Set lower limit of sink latency to 2.333 ms, if buffer + * latency is specified, use latency_msec as sink latency */ latency = PA_MAX(u->latency / 3, 2.333 * PA_USEC_PER_MSEC); + if (u->buffer_latency_set) + latency = u->latency; if(sink->flags & PA_SINK_DYNAMIC_LATENCY) { pa_sink_get_latency_range(sink, &min_latency, &max_latency); - if (min_latency > latency) { + if (min_latency > latency && !u->buffer_latency_set) { u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(min_latency * 0.75)); pa_log_warn("Cannot set requested sink latency, adjusting buffer to %0.2f ms", (double)u->buffer_latency / PA_USEC_PER_MSEC); } @@ -760,7 +778,7 @@ static void set_sink_input_latency(struct userdata *u, pa_sink *sink) { if (latency == 0) latency = pa_sink_get_fixed_latency(sink); buffer_msec = u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC; - if (u->buffer_latency < buffer_msec * PA_USEC_PER_MSEC) { + if (!u->buffer_latency_set && u->buffer_latency < buffer_msec * PA_USEC_PER_MSEC) { pa_log_warn("Fixed latency device, setting buffer latency to %zd.00 ms", buffer_msec); u->buffer_latency = buffer_msec * PA_USEC_PER_MSEC; } @@ -884,10 +902,12 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { memblockq_adjust(u, 0, true); return; } - if (u->source_output->source->flags & PA_SOURCE_DYNAMIC_LATENCY) - u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(u->configured_source_latency * 0.75)); - else - u->buffer_latency = PA_MAX(u->buffer_latency, (u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC) * PA_USEC_PER_MSEC); + if (!u->buffer_latency_set) { + if (u->source_output->source->flags & PA_SOURCE_DYNAMIC_LATENCY) + u->buffer_latency = PA_MAX(u->buffer_latency, (pa_usec_t)(u->configured_source_latency * 0.75)); + else + u->buffer_latency = PA_MAX(u->buffer_latency, (u->core->default_fragment_size_msec + DEFAULT_BUFFER_MARGIN_MSEC) * PA_USEC_PER_MSEC); + } pa_source_output_get_latency(u->source_output, &source_latency); if (u->send_counter > u->recv_counter) @@ -941,7 +961,7 @@ static void update_sink_requested_latency_cb(pa_sink_input *i) { if (sink_latency > u->configured_sink_latency) { pa_log_warn("Sink latency increased to %0.2f ms", (double)sink_latency / PA_USEC_PER_MSEC); u->configured_sink_latency = sink_latency; - if (u->buffer_latency < sink_latency * 0.75) + if (!u->buffer_latency_set && u->buffer_latency < sink_latency * 0.75) u->buffer_latency = sink_latency * 0.75; if (!u->source_sink_changed) { u->sink_adjust_counter = 0; @@ -974,12 +994,13 @@ int pa__init(pa_module *m) { pa_source *source = NULL; pa_source_output_new_data source_output_data; bool source_dont_move; - uint32_t latency_msec; + uint32_t latency_msec, buffer_latency_msec; pa_sample_spec ss; pa_channel_map map; bool format_set = false; bool rate_set = false; bool channels_set = false; + bool buffer_latency_set = false; pa_memchunk silence; uint32_t adjust_time_sec; const char *n; @@ -1053,7 +1074,17 @@ int pa__init(pa_module *m) { if (pa_modargs_get_value(ma, "channels", NULL) || pa_modargs_get_value(ma, "channel_map", NULL)) channels_set = true; - latency_msec = DEFAULT_LATENCY_MSEC; + buffer_latency_msec = 0; + if (pa_modargs_get_value_u32(ma, "buffer_latency_msec", &buffer_latency_msec) < 0 || buffer_latency_msec > 30000) { + pa_log_info("Invalid buffer latency specification"); + goto fail; + } + else if (buffer_latency_msec > 0) + buffer_latency_set = true; + + latency_msec = 0; + if (!buffer_latency_set) + latency_msec = DEFAULT_LATENCY_MSEC; if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec > 30000) { pa_log("Invalid latency specification"); goto fail; @@ -1063,8 +1094,12 @@ int pa__init(pa_module *m) { u->core = m->core; u->module = m; u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC; - u->initial_buffer_latency = PA_MAX(u->latency / 4, 1.667 * PA_USEC_PER_MSEC); + if (buffer_latency_set) + u->initial_buffer_latency = (pa_usec_t) buffer_latency_msec * PA_USEC_PER_MSEC; + else + u->initial_buffer_latency = PA_MAX(u->latency / 4, 1.667 * PA_USEC_PER_MSEC); u->buffer_latency = u->initial_buffer_latency; + u->buffer_latency_set = buffer_latency_set; u->sink_latency_sum = 0; u->source_latency_sum = 0; u->pop_called = false; @@ -1150,8 +1185,8 @@ int pa__init(pa_module *m) { u->sink_input->update_sink_requested_latency = update_sink_requested_latency_cb; u->sink_input->userdata = u; - if (u->latency < 4 * PA_USEC_PER_MSEC) - pa_log_warn("Latency limited to 4 ms"); + if (u->latency < 4 * PA_USEC_PER_MSEC && !buffer_latency_set) + pa_log_warn("Latency limited to 4 ms, try buffer_latency_msec together with latency_msec if you know what you are doing"); set_sink_input_latency(u, u->sink_input->sink); -- 2.1.4