1 From 05a3a926df4906cdf60f7978843c637bbf37714a Mon Sep 17 00:00:00 2001
2 From: George Kiagiadakis <george.kiagiadakis@collabora.com>
3 Date: Tue, 9 Jul 2019 18:06:18 +0300
4 Subject: [PATCH] alsa: make corrections on the timeout based on how fast ALSA
7 This feels a bit hacky, but it actually makes huge difference when pipewire is
10 The idea is that it keeps track of how much samples are in the device
11 (fill level) and calculates how many are consumed when a timeout occurs.
12 Then it converts that into a time based on the sample rate and compares it to
13 the system clock time that elapsed since the last write to the device.
14 The division between the two gives a rate (drift) that can be used to shorten
17 So for instance, if the timeout window was 21.3 ms, but the device actually
18 consumed an equivalent of 28 ms in samples, the drift will be 21.3/28 = 0.76
19 and the next timeout window will be approximately 21.3 * 0.76 = 16.1 ms
21 To avoid making things worse, the drift is clamped between 0.6 and 1.0.
22 Min 0.6 was arbitrarily chosen, but sometimes alsa does report strange numbers,
23 causing the drift to be very low, which in turn causes an early wakeup.
24 Max 1.0 basically means that we don't care if the device is consuming samples
25 slower. In that case, the early wakeup mechanism will throttle pipewire.
29 Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/166]
31 spa/plugins/alsa/alsa-utils.c | 31 ++++++++++++++++++++++++++-----
32 spa/plugins/alsa/alsa-utils.h | 2 ++
33 2 files changed, 28 insertions(+), 5 deletions(-)
35 diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
36 index 87582c1c..872969bf 100644
37 --- a/spa/plugins/alsa/alsa-utils.c
38 +++ b/spa/plugins/alsa/alsa-utils.c
39 @@ -593,7 +593,21 @@ static int get_status(struct state *state, snd_pcm_sframes_t *delay)
41 static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, bool slave)
44 + double err, corr, drift;
45 + snd_pcm_sframes_t consumed;
47 + consumed = state->fill_level - delay;
48 + if (state->alsa_started && consumed > 0) {
49 + double sysclk_diff = nsec - state->last_time;
50 + double devclk_diff = ((double) consumed) * 1e9 / state->rate;
51 + drift = sysclk_diff / devclk_diff;
52 + drift = SPA_CLAMP(drift, 0.6, 1.0);
54 + spa_log_trace_fp(state->log, "cons:%ld sclk:%f dclk:%f drift:%f",
55 + consumed, sysclk_diff, devclk_diff, drift);
60 if (state->stream == SND_PCM_STREAM_PLAYBACK)
61 err = delay - state->last_threshold;
62 @@ -650,11 +664,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
63 state->clock->rate_diff = corr;
66 - spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %d", slave, nsec,
67 - corr, delay, err, state->threshold * corr,
68 + spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %f %d", slave, nsec,
69 + corr, delay, err, state->threshold * corr, drift,
72 - state->next_time += state->threshold / corr * 1e9 / state->rate;
73 + state->next_time += state->threshold / corr * drift * 1e9 / state->rate;
74 state->last_threshold = state->threshold;
77 @@ -783,6 +797,10 @@ again:
80 state->sample_count += total_written;
81 + state->fill_level += total_written;
83 + clock_gettime(CLOCK_MONOTONIC, &state->now);
84 + state->last_time = SPA_TIMESPEC_TO_NSEC (&state->now);
86 if (!state->alsa_started && total_written > 0) {
87 spa_log_trace(state->log, "snd_pcm_start %lu", written);
88 @@ -935,7 +953,7 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
92 - if (delay >= state->last_threshold * 2) {
93 + if (delay > state->last_threshold * 2) {
94 spa_log_trace(state->log, "early wakeup %ld %d", delay, state->threshold);
95 state->next_time = nsec + (delay - state->last_threshold) * SPA_NSEC_PER_SEC / state->rate;
97 @@ -944,6 +962,8 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
98 if ((res = update_time(state, nsec, delay, false)) < 0)
101 + state->fill_level = delay;
103 if (spa_list_is_empty(&state->ready)) {
104 struct spa_io_buffers *io = state->io;
106 @@ -1072,6 +1092,7 @@ int spa_alsa_start(struct state *state)
108 state->slaved = is_slaved(state);
109 state->last_threshold = state->threshold;
110 + state->fill_level = 0;
114 diff --git a/spa/plugins/alsa/alsa-utils.h b/spa/plugins/alsa/alsa-utils.h
115 index a862873f..b53890b5 100644
116 --- a/spa/plugins/alsa/alsa-utils.h
117 +++ b/spa/plugins/alsa/alsa-utils.h
118 @@ -134,7 +134,9 @@ struct state {
122 + uint64_t last_time;
124 + snd_pcm_uframes_t fill_level;