USB sinks exhibit a strange behavior at startup. See comment in the code for more explanation. This hack compensates the issue. --- src/modules/alsa/alsa-sink.c | 47 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index d497200..de51710 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -150,6 +150,9 @@ struct userdata { double time_factor; bool first_delayed; + bool usb_hack; + + double hack_threshold; pa_idxset *formats; @@ -850,6 +853,10 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo } static void init_time_estimate(struct userdata *u) { + const char *id; + snd_pcm_info_t* pcm_info; + + snd_pcm_info_alloca(&pcm_info); /* Reset variables for rate estimation */ u->drift_filter = 1.0; @@ -857,6 +864,21 @@ static void init_time_estimate(struct userdata *u) { u->time_factor = 1.0; u->start_pos = 0; u->first_delayed = false; + u->usb_hack = false; + u->time_offset = 0; + + /* Check if this is an USB device, see alsa-util.c + * USB devices unfortunately need some special handling */ + if (snd_pcm_info(u->pcm_handle, pcm_info) == 0 && + (id = snd_pcm_info_get_id(pcm_info))) { + if (pa_streq(id, "USB Audio")) { + /* USB device, set hack parameter */ + u->usb_hack = true; + u->hack_threshold = 2000; + if (!u->use_tsched) + u->hack_threshold = 1000; + } + } /* Start time */ u->start_time = pa_rtclock_now(); @@ -869,6 +891,7 @@ static void update_time_estimate(struct userdata *u) { double byte_count, iteration_time; int err; double time_delta_system, time_delta_card, drift, filter_constant, filter_constant_1; + double temp; pa_usec_t time_stamp; snd_pcm_status_t *status; @@ -935,6 +958,28 @@ static void update_time_estimate(struct userdata *u) { * be on a sample boundary */ time_delta_card = byte_count / u->frame_size / u->sink->sample_spec.rate * PA_USEC_PER_SEC; + /* This is a horrible hack which is necessary because USB devices seem to fix up + * the reported delay by some millisecondsconds shortly after startup. This is + * an artifact, the real latency does not change on the reported jump. If the + * change is not caught or if the hack is triggered inadvertently, it will lead to + * prolonged convergence time and decreased stability of the reported latency. + * Since the fix up will occur within the first seconds, it is disabled later to + * avoid false triggers. When run as batch device, the threshold for the hack must + * be lower than for timer based scheduling. */ + if (u->usb_hack && time_delta_system < 5 * PA_USEC_PER_SEC) { + if (abs(time_delta_system - time_delta_card / u->time_factor) > u->hack_threshold) { + /* Recalculate initial conditions */ + temp = time_stamp - time_delta_card - u->start_time; + u->start_time += temp; + u->first_start_time += temp; + u->time_offset = -temp; + + pa_log_debug("USB Hack, start time corrected by %0.2f usec", temp); + u->usb_hack = false; + return; + } + } + /* Parameter for lowpass filter with 2s and 15s time constant */ filter_constant = iteration_time / (iteration_time + 318310.0); filter_constant_1 = iteration_time / (iteration_time + 2387324.0); @@ -960,7 +1005,7 @@ static pa_usec_t sink_get_latency(struct userdata *u, bool raw) { pa_assert(u); /* Convert system time difference to soundcard time difference */ - now2 = (pa_rtclock_now() - u->start_time) * u->time_factor; + now2 = (pa_rtclock_now() - u->start_time - u->time_offset) * u->time_factor; /* Don't use pa_bytes_to_usec(), u->start_pos needs not be on a sample boundary */ delay = (int64_t)(((double)u->write_count - u->start_pos) / u->frame_size / u->sink->sample_spec.rate * PA_USEC_PER_SEC) - now2; -- 2.8.1