Commit graph

30 commits

Author SHA1 Message Date
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
c2a766c2ee [client] audio: fix setfault due to failure to properly reset 2022-01-27 19:20:16 +11:00
Geoffrey McRae
a560a610d9 [client] audio: allow building without any audio support 2022-01-27 18:03:11 +11:00
Geoffrey McRae
a7db3d3a0f [client] audio: check for malloc failure 2022-01-27 18:03:11 +11:00
Geoffrey McRae
016001da67 [client] audio: cosmetics 2022-01-27 18:03:11 +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
599fdd6ffd [common] ringbuffer: add unbounded mode
In unbounded mode, the read and write pointers are free to move
independently of one another. This is useful where the input and output
streams are progressing at the same rate on average, and we want to keep
the latency stable in the event than an underrun or overrun occurs.

If an underrun occurs (i.e., there is not enough data in the buffer to
satisfy a read request), the missing values with be filled with zeros. When
the writer catches up, the same number of values will be skipped from the
input.

If an overrun occurs (i.e., there is not enough free space in the buffer to
satisfy a write request), excess values will be discarded. When the reader
catches up, the same number of values will be zeroed in the output.

Unbounded mode is currently unused since our audio input and output
streams are not synchronised. This will be implemented in a later commit.

Also reimplemented as a lock-free queue which is safer for use in audio
device callbacks.
2022-01-27 18:03:11 +11:00
Chris Spencer
b34b253814 [client] audio: stop playback immediately if new playback is started
If a new playback is started while the previous playback is still flushing,
we simply allow the stream to continue playing and effectively cancel the
flush. In general this is not safe because there may not be enough data in
the buffer to avoid underrunning. We could handle this better later by
trying to insert the right number of silent samples into the buffer, but
for now just completely stop the previous stream before starting the new
one.
2022-01-27 18:03:11 +11:00
Geoffrey McRae
9601bc677f [client] audio: report the buffered frames and not the buffer length 2022-01-22 16:06:16 +11:00
Geoffrey McRae
c84879717f [client] audio: fix failure to properly handle restart if draining 2022-01-20 07:02:00 +11:00
Geoffrey McRae
b3c81bcedf [client] audio: fix use after free race 2022-01-19 18:50:39 +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
46da447429 [client] audio: fix latency calculation 2022-01-19 00:58:48 +11:00
Geoffrey McRae
4b080f7610 [client] audio: don't lock when consuming frames from the buffer 2022-01-19 00:25:52 +11:00
Geoffrey McRae
4fadf3a130 [client] audio: tell the audiodev to stop on the last packet, not after 2022-01-18 23:43:12 +11:00
Geoffrey McRae
73dc08e5f9 [client] audio: remove duplicated line 2022-01-18 23:42:02 +11:00
Geoffrey McRae
07c92ec2e8 [client] audio: drain buffers on stop instead of just discarding them 2022-01-18 23:39:05 +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
0ad26b7da7 [client] audio: redraw the graphs if they have been updated 2022-01-17 22:49:19 +11:00
Geoffrey McRae
775ac7ce8b [client] audio: reduce timing graph sample count to 30 seconds 2022-01-17 22:23:37 +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
Geoffrey McRae
32134b33ea [client] audio: remove more debug output spam 2022-01-07 16:35:46 +11:00
Geoffrey McRae
9d894065c8 [client] audio: remove debug spam 2022-01-07 16:27:29 +11:00
Geoffrey McRae
52f06ec332 [client] audio: don't call record.mute if it's not supported 2022-01-07 00:22:35 +11:00
Geoffrey McRae
fe7973ea24 [client] audio: implement record interface and glue 2022-01-06 23:49:20 +11:00
Geoffrey McRae
ff2ca20235 [client] audio: always store the volume & mute state for restore 2022-01-06 23:31:39 +11:00
Geoffrey McRae
a114ea3de4 [client] audio: move audio code into it's own unit 2022-01-06 23:24:13 +11:00