Commit Graph

39 Commits

Author SHA1 Message Date
Chris Spencer
0d97a51802 [client] audio: increase startup latency
Underruns can still happen quite easily at the beginning of playback,
particularly at very low latency settings. Further increase the startup
latency to avoid this.
2022-02-10 07:50:01 +11:00
Chris Spencer
05ca59ed48 [client] audio/pw: increase startup latency
PipeWire startup latency varies wildly depending on what else is, or was
last using the audio device. In the worst case, PipeWire can request two
full buffers within a very short period of time immediately at the start of
playback, so make sure we've got enough data in the buffer to support this.
2022-02-04 16:27:12 +11:00
Chris Spencer
e1e60fdaa6 [client] audio: tune target latency
The target latency is now based upon the device maximum period size
(which may be configured by setting the `PIPEWIRE_LATENCY` environment
variable if using PipeWire), with some allowance for timing jitter from
Spice and the audio device.

PipeWire can change the period size dynamically at any time which must be
taken into account when selecting the target latency to avoid underruns
when the period size is increased. This is explained in detail within the
commit body.
2022-02-04 16:27:12 +11:00
Geoffrey McRae
5bbc1d44bf [client] audio/pw: get the period size to determine when to start audio
Previously this was hardcoded to 100ms which is far too high in most
instances, instead we get the initial period size and use whichever is
greater out of 50ms or the period size.

The idea is to reduce the amount of time it takes for the latency to
come down after initial stream start.
2022-01-28 12:08:58 +11:00
Geoffrey McRae
a0477466d2 Revert "[client] audio: allow the audiodev to return the periodFrames"
This reverts commit 41884bfcc5.

PipeWire can change it's period size on the fly on us making this
approach invalid.
2022-01-28 10:00:35 +11:00
Geoffrey McRae
41884bfcc5 [client] audio: allow the audiodev to return the periodFrames
This change allows the audiodevs to return the minimum period frames
needed to start playback instead of having to rely on a pull to obtain
these details.

Additionally we are using this information to select an initial start
latency as well as to train the desired latency in order to keep it as
low as possible.
2022-01-27 18:03:11 +11:00
Chris Spencer
dd2d84a080 [client] audio: adjust playback speed to match audio device clock
This change is based on the techniques described in [1] and [2].

The input audio stream from Spice is not synchronised to the audio playback
device. While the input and output may be both nominally running at 48 kHz,
when compared against each other, they will differ by a tiny fraction of a
percent. Given enough time (typically on the order of a few hours), this
will result in the ring buffer becoming completely full or completely
empty. It will stay in this state permanently, periodically resulting in
glitches as the buffer repeatedly underruns or overruns.

To address this, adjust the speed of the received data to match the rate at
which it is being consumed by the audio device. This will result in a
slight pitch shift, but the changes should be small and smooth enough that
this is unnoticeable to the user.

The process works roughly as follows:
1. Every time audio data is received from Spice, or consumed by the audio
   device, sample the current time. These are fed into a pair of delay
   locked loops to produce smoothed approximations of the two clocks.
2. Compute the difference between the two clocks and compare this against
   the target latency to produce an error value. This error value will be
   quite stable during normal operation, but can change quite rapidly due
   to external factors, particularly at the start of playback. To smooth
   out any sudden changes in playback speed, which would be noticeable to
   the user, this value is also filtered through another delay locked loop.
3. Feed this error value into a PI controller to produce a ratio value.
   This is the target playback speed in order to bring the error value
   towards zero.
4. Resample the input audio using the computed ratio to apply the speed
   change. The output of the resampler is what is ultimately inserted into
   the ring buffer for consumption by the audio device.

Since this process targets a specific latency value, rather than simply
trying to rate match the input and output, it also has the effect of
'correcting' latency issues. If a high latency application (such as a media
player) is already running, the time between requesting the start of
playback and the audio device actually starting to consume samples can be
very high, easily in the hundreds of milliseconds. The changes here will
automatically adjust the playback speed over the course of a few minutes to
bring the latency back down to the target value.

[1] https://kokkinizita.linuxaudio.org/papers/adapt-resamp.pdf
[2] https://kokkinizita.linuxaudio.org/papers/usingdll.pdf
2022-01-27 18:03:11 +11:00
Chris Spencer
68b42e1c1a [client] audio/pw: drop restarting state
Automatically restarting playback once draining has completed could result
in playback starting too early (i.e., before there is enough data in the
ring buffer to avoid underrunning). `audio_playbackData` will keep invoking
`start` until it returns true anyway, so we can just allow draining to
complete normally and wait for `start` to be called again.
2022-01-27 18:03:11 +11:00
Chris Spencer
8580978321 [client] audio/pw: drop redundant flushing state
We do not stop the audio device until after the internal buffer has already
been flushed, so this state does nothing useful.
2022-01-27 18:03:11 +11:00
Geoffrey McRae
15f76339c8 [client] audio: move the memory copy into the pull function 2022-01-19 10:29:49 +11:00
Geoffrey McRae
04ae9217e8 [client] audio: allow the audiodev to determine the start fill level 2022-01-19 01:52:19 +11:00
Geoffrey McRae
d6bbc4f89c [client] audio/pw: return the actual playback latency 2022-01-19 00:03:16 +11:00
Geoffrey McRae
b334f22223 [client] audio: rework audiodevs to be pull model from a common buffer 2022-01-18 09:02:44 +11:00
Geoffrey McRae
689cc53255 [client] audio: add audio playback latency interface and graph 2022-01-17 22:13:41 +11:00
Geoffrey McRae
5629655f74 [client] audio/pw: fix memory leak and gracefully shutdown 2022-01-17 22:10:41 +11:00
Chris Spencer
8a61c8ebc2 [client] audio/pw: use rate matching
This can prevent glitches when the PipeWire quantum size changes.
2022-01-11 09:45:30 +11:00
Chris Spencer
ef9b2958ec [client] audio/pw: set maximum node latency
This prevents severe buffer underruns if the PipeWire quantum is bigger
than the ring buffer size. This could happen if a media player is running
at the same time as Looking Glass if it requests a very large quantum size,
for example.
2022-01-11 09:45:30 +11:00
Chris Spencer
e72e138267 [client] audio/pw: delay playback to avoid glitches 2022-01-11 09:45:30 +11:00
Chris Spencer
4c389a9274 [client] audio/pw: flush playback buffers before stopping
This stops the end of the playback from being truncated. It also prevents
an audible glitch when playback next starts due to the truncated data being
left behind in the ring buffer.
2022-01-11 09:45:30 +11:00
Chris Spencer
b9c646074d [client] audio/pw: don't discard playback data
This can cause significant glitching, particularly around the start of
playback.
2022-01-11 09:45:30 +11:00
Geoffrey McRae
7263159428 [client] audio/pw: implement record support 2022-01-07 00:54:44 +11:00
Geoffrey McRae
7f93bbd675 [client] audio/pw: fixed another search/replace mistake 2022-01-07 00:09:34 +11:00
Geoffrey McRae
5c20a851c6 [client] audio/pw: fix search/replace error 2022-01-06 23:58:02 +11:00
Geoffrey McRae
11acaa2957 [client] audio/pw: refactor to use playback for playback methods 2022-01-06 23:56:12 +11:00
Geoffrey McRae
fe7973ea24 [client] audio: implement record interface and glue 2022-01-06 23:49:20 +11:00
Geoffrey McRae
e6bd36ec7c [client] audio: refactor audio to playback and add record funcs 2022-01-06 22:47:22 +11:00
Geoffrey McRae
952ebea2c5 [all] refresh copyright dates 2022-01-05 19:42:46 +11:00
Geoffrey McRae
02ec25b008 [client] audio/pw: it's Looking Glass, not LookingGlass 2021-12-26 18:49:35 +11:00
Geoffrey McRae
90dd1f3913 [client] audio/pa: added initial pulseaudio implementation 2021-12-26 18:22:51 +11:00
Geoffrey McRae
9fa643484c [client] audio/pw: use scaling forumula provided by quantum 2021-12-26 11:09:42 +11:00
Geoffrey McRae
64b64b61be [client] audio/pw: implement volume and mute control 2021-12-26 11:09:42 +11:00
Geoffrey McRae
e408ea51e2 [client] audio/pw: gracefully shutdown when asked to 2021-12-26 11:09:42 +11:00
Geoffrey McRae
ebdc847ef1 [client] audio/pw: don't re-create an idle stream with matching format 2021-12-26 11:09:42 +11:00
Geoffrey McRae
2ea24516d2 [client] audio/pw: limit how much data gets buffered to reduce latency 2021-12-26 11:09:42 +11:00
Geoffrey McRae
dd04a46403 [client] audio/pw: make use of the new ringbuffer consume/append funcs 2021-12-26 11:09:42 +11:00
Geoffrey McRae
f403033ab1 [client] audio/pw: properly manage the stream state 2021-12-26 11:09:42 +11:00
Geoffrey McRae
11ef94c134 [client] audio/pw: set the node name 2021-12-26 11:09:42 +11:00
Geoffrey McRae
75e46128d4 [client] audio/pw: don't actually stop when SPICE signals a stop 2021-12-26 11:09:42 +11:00
Geoffrey McRae
e810577317 [client] audio: initial addition of PipeWire audio support via SPICE 2021-12-26 11:09:42 +11:00