[pulseaudio-discuss] [PATCH 1/4] alsa-{source, sink}: Add alternative smoother functions

Georg Chini georg at chini.tk
Sun Jun 5 19:48:55 UTC 2016


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



More information about the pulseaudio-discuss mailing list