summaryrefslogtreecommitdiffstats
path: root/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
blob: 86495014603e694b585bb835733613c1a0560b41 (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
122
123
124
125
126
127
128
129
130
From 05a3a926df4906cdf60f7978843c637bbf37714a 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 | 31 ++++++++++++++++++++++++++-----
 spa/plugins/alsa/alsa-utils.h |  2 ++
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
index 87582c1c..872969bf 100644
--- a/spa/plugins/alsa/alsa-utils.c
+++ b/spa/plugins/alsa/alsa-utils.c
@@ -593,7 +593,21 @@ static int get_status(struct state *state, snd_pcm_sframes_t *delay)
 
 static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, 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 - state->last_threshold;
@@ -650,11 +664,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
 		state->clock->rate_diff = corr;
 	}
 
-	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;
@@ -783,6 +797,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);
@@ -935,7 +953,7 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
 {
 	int res;
 
-	if (delay >= state->last_threshold * 2) {
+	if (delay > state->last_threshold * 2) {
 		spa_log_trace(state->log, "early wakeup %ld %d", delay, state->threshold);
 		state->next_time = nsec + (delay - state->last_threshold) * SPA_NSEC_PER_SEC / state->rate;
 		return -EAGAIN;
@@ -944,6 +962,8 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
 	if ((res = update_time(state, nsec, delay, false)) < 0)
 		return res;
 
+	state->fill_level = delay;
+
 	if (spa_list_is_empty(&state->ready)) {
 		struct spa_io_buffers *io = state->io;
 
@@ -1072,6 +1092,7 @@ int spa_alsa_start(struct state *state)
 
 	state->slaved = is_slaved(state);
 	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 a862873f..b53890b5 100644
--- a/spa/plugins/alsa/alsa-utils.h
+++ b/spa/plugins/alsa/alsa-utils.h
@@ -134,7 +134,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.20.1