summaryrefslogtreecommitdiffstats
path: root/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
blob: 17280da08e658e83a7a4937bdac80e812fa1b05b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
From 0c232229f3dc6b0cdf02ef44ae212b256c1828a3 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Tue, 9 Jul 2019 18:06:18 +0300
Subject: [PATCH] alsa: make corrections on the timeout based on how fast ALSA
 consumes samples

This feels a bit hacky, but it actually makes huge difference when pipewire is
running in qemu.

The idea is that it keeps track of how much samples are in the device
(fill level) and calculates how many are consumed when a timeout occurs.
Then it converts that into a time based on the sample rate and compares it to
the system clock time that elapsed since the last write to the device.
The division between the two gives a rate (drift) that can be used to shorten
the timeout window.

So for instance, if the timeout window was 21.3 ms, but the device actually
consumed an equivalent of 28 ms in samples, the drift will be 21.3/28 = 0.76
and the next timeout window will be approximately 21.3 * 0.76 = 16.1 ms

To avoid making things worse, the drift is clamped between 0.6 and 1.0.
Min 0.6 was arbitrarily chosen, but sometimes alsa does report strange numbers,
causing the drift to be very low, which in turn causes an early wakeup.
Max 1.0 basically means that we don't care if the device is consuming samples
slower. In that case, the early wakeup mechanism will throttle pipewire.

Fixes #163

Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/166]
---
 spa/plugins/alsa/alsa-utils.c | 29 +++++++++++++++++++++++++----
 spa/plugins/alsa/alsa-utils.h |  2 ++
 2 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
index 7cf1d0d2..e8548345 100644
--- a/spa/plugins/alsa/alsa-utils.c
+++ b/spa/plugins/alsa/alsa-utils.c
@@ -624,7 +624,21 @@ static int get_status(struct state *state, snd_pcm_uframes_t *delay, snd_pcm_ufr
 static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay,
 		snd_pcm_sframes_t target, bool slave)
 {
-	double err, corr;
+	double err, corr, drift;
+	snd_pcm_sframes_t consumed;
+
+	consumed = state->fill_level - delay;
+	if (state->alsa_started && consumed > 0) {
+		double sysclk_diff = nsec - state->last_time;
+		double devclk_diff = ((double) consumed) * 1e9 / state->rate;
+		drift = sysclk_diff / devclk_diff;
+		drift = SPA_CLAMP(drift, 0.6, 1.0);
+
+		spa_log_trace_fp(state->log, "cons:%ld sclk:%f dclk:%f drift:%f",
+			consumed, sysclk_diff, devclk_diff, drift);
+	} else {
+		drift = 1.0;
+	}
 
 	if (state->stream == SND_PCM_STREAM_PLAYBACK)
 		err = delay - target;
@@ -677,11 +691,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
 		state->clock->next_nsec = state->next_time;
 	}
 
-	spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %d", slave, nsec,
-			corr, delay, err, state->threshold * corr,
+	spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %f %d", slave, nsec,
+			corr, delay, err, state->threshold * corr, drift,
 			state->threshold);
 
-	state->next_time += state->threshold / corr * 1e9 / state->rate;
+	state->next_time += state->threshold / corr * drift * 1e9 / state->rate;
 	state->last_threshold = state->threshold;
 
 	return 0;
@@ -812,6 +826,10 @@ again:
 		goto again;
 
 	state->sample_count += total_written;
+	state->fill_level += total_written;
+
+	clock_gettime(CLOCK_MONOTONIC, &state->now);
+	state->last_time = SPA_TIMESPEC_TO_NSEC (&state->now);
 
 	if (!state->alsa_started && total_written > 0) {
 		spa_log_trace(state->log, "snd_pcm_start %lu", written);
@@ -981,6 +999,8 @@ static int handle_play(struct state *state, uint64_t nsec,
 	if ((res = update_time(state, nsec, delay, target, false)) < 0)
 		return res;
 
+	state->fill_level = delay;
+
 	if (spa_list_is_empty(&state->ready)) {
 		struct spa_io_buffers *io = state->io;
 
@@ -1120,6 +1140,7 @@ int spa_alsa_start(struct state *state)
 
 	state->threshold = (state->duration * state->rate + state->rate_denom-1) / state->rate_denom;
 	state->last_threshold = state->threshold;
+	state->fill_level = 0;
 
 	init_loop(state);
 	state->safety = 0.0;
diff --git a/spa/plugins/alsa/alsa-utils.h b/spa/plugins/alsa/alsa-utils.h
index 110d4204..bab0f67b 100644
--- a/spa/plugins/alsa/alsa-utils.h
+++ b/spa/plugins/alsa/alsa-utils.h
@@ -141,7 +141,9 @@ struct state {
 	int64_t sample_time;
 	uint64_t next_time;
 	uint64_t base_time;
+	uint64_t last_time;
 
+	snd_pcm_uframes_t fill_level;
 	uint64_t underrun;
 	double safety;
 
-- 
2.23.0