[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