These are the new smoother functions. The old code is maintained for debugging purposes, in case someone likes to compare old and new code. --- src/modules/alsa/alsa-sink.c | 134 +++++++++++++++++++++++++++++++++++++++-- src/modules/alsa/alsa-source.c | 126 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 251 insertions(+), 9 deletions(-) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index ec867cb..229a3be 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -148,6 +148,19 @@ struct userdata { pa_usec_t smoother_interval; pa_usec_t last_smoother_update; + pa_usec_t start_time; + pa_usec_t first_start_time; + pa_usec_t last_time; + + double start_pos; + int64_t time_offset; + + double drift_filter; + double drift_filter_1; + double time_factor; + + bool first_delayed; + pa_idxset *formats; pa_reserve_wrapper *reserve; @@ -846,6 +859,110 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo return work_done ? 1 : 0; } +static void init_time_estimate(struct userdata *u) { + + /* Reset variables for rate estimation */ + u->drift_filter = 1.0; + u->drift_filter_1 = 1.0; + u->time_factor = 1.0; + u->start_pos = 0; + u->first_delayed = false; + + /* Start time */ + u->start_time = pa_rtclock_now(); + u->first_start_time = u->start_time; + u->last_time = u->start_time; +} + +static void update_time_estimate(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + double byte_count, iteration_time; + int err; + double time_delta_system, time_delta_card, drift, filter_constant, filter_constant_1; + pa_usec_t time_stamp; + snd_pcm_status_t *status; + + snd_pcm_status_alloca(&status); + + /* Nothing has happend yet, initialize and return */ + if PA_UNLIKELY((u->first)) { + init_time_estimate(u); + u->first_delayed = true; + return; + } + + /* Get delay */ + if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, status, &delay, u->hwbuf_size, &u->sink->sample_spec, false)) < 0)) { + pa_log_warn("Failed to query DSP status data: %s", pa_alsa_strerror(err)); + return; + } + + /* Get current time */ + time_stamp = pa_rtclock_now(); + + /* Initial setup */ + if PA_UNLIKELY((u->first_delayed)) { + /* Save number of bytes that have already been played */ + u->start_pos = (double)u->write_count - (int64_t)delay * (int64_t)u->frame_size; + if (u->start_pos <= 0) + /* We are not playing yet */ + return; + + /* Now we are playing. + * Get fresh time stamps */ + u->last_time = time_stamp; + u->start_time = time_stamp; + u->first_start_time = time_stamp; + + u->first_delayed = false; + return; + } + + /* Duration of last iteration */ + iteration_time = time_stamp - u->last_time; + + /* Total number of bytes played */ + byte_count = (double)u->write_count - (int64_t)delay * (int64_t) u->frame_size; + + /* Calculate new start time and corresponding sample count after 15s */ + if (time_stamp - u->first_start_time > 15 * PA_USEC_PER_SEC) { + u->start_pos += (byte_count - u->start_pos) / (time_stamp - u->start_time) * iteration_time; + u->start_time += iteration_time; + + /* Wait at least 100 ms before starting calculations, otherwise the + * impact of the offset error will slow down convergence */ + } else if (time_stamp - u->first_start_time < 100 * PA_USEC_PER_MSEC) + return; + + /* Time difference in system time domain */ + time_delta_system = time_stamp - u->start_time; + + /* Number of bytes played since start_time */ + byte_count = byte_count - u->start_pos; + + /* Time difference in soundcard time domain. Don't use + * pa_bytes_to_usec() here because u->start_pos need not + * be on a sample boundary */ + time_delta_card = byte_count / u->frame_size / u->sink->sample_spec.rate * PA_USEC_PER_SEC; + + /* 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); + + /* Calculate geometric series */ + drift = (u->drift_filter_1 + 1.0) * (1.5 - time_delta_card / time_delta_system); + + /* 2nd order lowpass */ + u->drift_filter = (1 - filter_constant) * u->drift_filter + filter_constant * drift; + u->drift_filter_1 = (1 - filter_constant) * u->drift_filter_1 + filter_constant * u->drift_filter; + + /* Calculate time conversion factor, filter again */ + u->time_factor = (1 - filter_constant_1) * u->time_factor + filter_constant_1 * (u->drift_filter_1 + 3) / (u->drift_filter_1 + 1) / 2; + + /* Save current system time */ + u->last_time = time_stamp; +} + static void update_smoother(struct userdata *u) { snd_pcm_sframes_t delay = 0; int64_t position; @@ -894,14 +1011,20 @@ static void update_smoother(struct userdata *u) { static pa_usec_t sink_get_latency(struct userdata *u, bool raw) { int64_t delay; - pa_usec_t now1, now2; + int64_t now2; pa_assert(u); - now1 = pa_rtclock_now(); +/* now1 = pa_rtclock_now(); now2 = pa_smoother_get(u->smoother, now1); - delay = (int64_t) pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now2; + delay = (int64_t) pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now2; */ + + /* Convert system time difference to soundcard time difference */ + now2 = (pa_rtclock_now() - u->start_time) * 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; if (u->memchunk.memblock) delay += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); @@ -1735,6 +1858,7 @@ static void thread_func(void *userdata) { /* pa_log_debug("work_done = %i", work_done); */ if (work_done) { + update_time_estimate(u); if (u->first) { pa_log_info("Starting playback."); @@ -1774,7 +1898,8 @@ static void thread_func(void *userdata) { /* Convert from the sound card time domain to the * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); +/* cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); */ + cusec = sleep_usec * u->time_factor; #ifdef DEBUG_TIMING pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); @@ -2124,6 +2249,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca u->deferred_volume = deferred_volume; u->fixed_latency_range = fixed_latency_range; u->first = true; + u->time_factor = 1.0; u->rewind_safeguard = rewind_safeguard; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 8f3571c..55fb30f 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -134,6 +134,18 @@ struct userdata { pa_usec_t smoother_interval; pa_usec_t last_smoother_update; + pa_usec_t start_time; + pa_usec_t first_start_time; + pa_usec_t last_time; + + double start_pos; + + double drift_filter; + double drift_filter_1; + double time_factor; + + bool first_delayed; + pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; pa_reserve_monitor_wrapper *monitor; @@ -767,6 +779,98 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo return work_done ? 1 : 0; } +static void init_time_estimate(struct userdata *u) { + /* Reset rate estimation variables */ + u->drift_filter = 1.0; + u->drift_filter_1 = 1.0; + u->time_factor = 1.0; + u->start_pos = 0; + u->first_delayed = true; + + /* Start times */ + u->start_time = pa_rtclock_now(); + u->first_start_time = u->start_time; + u->last_time = u->start_time; +} + +static void update_time_estimate(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + double byte_count, iteration_time; + double time_delta_system, time_delta_card, drift, filter_constant, filter_constant_1; + pa_usec_t time_stamp; + int err; + snd_pcm_status_t *status; + + snd_pcm_status_alloca(&status); + + /* Get delay */ + if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, status, &delay, u->hwbuf_size, &u->source->sample_spec, true)) < 0)) { + pa_log_warn("Failed to get delay: %s", pa_alsa_strerror(err)); + return; + } + + /* Get current time */ + time_stamp = pa_rtclock_now(); + + /* Initial setup */ + if (PA_UNLIKELY(u->first_delayed)) { + /* save number of samples already recorded */ + u->start_pos = (double)u->read_count + (int64_t)delay * (int64_t)u->frame_size; + + /* Time stamps */ + u->last_time = time_stamp; + u->start_time = time_stamp; + u->first_start_time = time_stamp; + + u->first_delayed = false; + return; + } + + /* Total number of bytes recorded */ + byte_count = (double)u->read_count + (int64_t)delay * (int64_t) u->frame_size; + + /* Duration of last iteration */ + iteration_time = time_stamp - u->last_time; + + /* Calculate new start time and corresponding sample count after 15s */ + if (time_stamp - u->first_start_time > 15 * PA_USEC_PER_SEC) { + u->start_pos += (byte_count - u->start_pos) / (time_stamp - u->start_time) * iteration_time; + u->start_time += iteration_time; + + /* Wait at least 100 ms before starting calculations, otherwise the + * impact of the offset error will slow down convergence */ + } else if (time_stamp - u->first_start_time < 100 * PA_USEC_PER_MSEC) + return; + + /* Time difference in system time domain */ + time_delta_system = time_stamp - u->start_time; + + /* Number of bytes recorded since start_time */ + byte_count = byte_count - u->start_pos; + + /* Time difference in soundcard time domain. Don't use + * pa_bytes_to_usec() here because u->start_pos need not + * be on a sample boundary */ + time_delta_card = byte_count / u->frame_size / u->source->sample_spec.rate * PA_USEC_PER_SEC; + + /* 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); + + /* Calculate geometric series */ + drift = (u->drift_filter_1 + 1.0) * (1.5 - time_delta_card / time_delta_system); + + /* 2nd order lowpass */ + u->drift_filter = (1 - filter_constant) * u->drift_filter + filter_constant * drift; + u->drift_filter_1 = (1 - filter_constant) * u->drift_filter_1 + filter_constant * u->drift_filter; + + /* Calculate time conversion factor, filter again */ + u->time_factor = (1 - filter_constant_1) * u->time_factor + filter_constant_1 * (u->drift_filter_1 + 3) / (u->drift_filter_1 + 1) / 2; + + /* Save current system time */ + u->last_time = time_stamp; +} + static void update_smoother(struct userdata *u) { snd_pcm_sframes_t delay = 0; uint64_t position; @@ -811,14 +915,20 @@ static void update_smoother(struct userdata *u) { static pa_usec_t source_get_latency(struct userdata *u, bool raw) { int64_t delay; - pa_usec_t now1, now2; + int64_t now2; pa_assert(u); - now1 = pa_rtclock_now(); +/* now1 = pa_rtclock_now(); now2 = pa_smoother_get(u->smoother, now1); - delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec); + delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec); */ + + /* Convert system time difference to soundcard time difference */ + now2 = (pa_rtclock_now() - u->start_time) * u->time_factor; + + /* Don't use pa_bytes_to_usec(), u->start_pos needs not be on a sample boundary */ + delay = now2 - (int64_t) (PA_CLIP_SUB((double)u->read_count, u->start_pos) / u->frame_size / u->source->sample_spec.rate * PA_USEC_PER_SEC); if (raw) return delay; @@ -1469,6 +1579,8 @@ static void thread_func(void *userdata) { if (u->first) { pa_log_info("Starting capture."); + + init_time_estimate(u); snd_pcm_start(u->pcm_handle); pa_smoother_resume(u->smoother, pa_rtclock_now(), true); @@ -1486,8 +1598,10 @@ static void thread_func(void *userdata) { /* pa_log_debug("work_done = %i", work_done); */ - if (work_done) + if (work_done) { update_smoother(u); + update_time_estimate(u); + } if (u->use_tsched) { pa_usec_t cusec; @@ -1499,7 +1613,8 @@ static void thread_func(void *userdata) { /* Convert from the sound card time domain to the * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); +/* cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); */ + cusec = sleep_usec * u->time_factor; /* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ @@ -1836,6 +1951,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p u->deferred_volume = deferred_volume; u->fixed_latency_range = fixed_latency_range; u->first = true; + u->time_factor = 1.0; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); -- 2.8.1