Audio corruption when connecting one source to multiple sinks, and a sink overwrites memory
- PipeWire version (
pipewire --version
): 0.3.82 - Distribution and distribution version (
PRETTY_NAME
from/etc/os-release
): Arch Linux - Desktop Environment: KDE Wayland
- Kernel version (
uname -r
): 6.5.6-arch2-1
Description of Problem:
When a node's output source is connected to multiple nodes with sinks, and one JACK or PipeWire node overwrites its input buffers in-place, the audio gets corrupted in the other nodes as well.
How Reproducible:
Always (if you launch the corrupting app before other listeners).
Steps to Reproduce:
I've found multiple ways (both through everyday use and synthetic test cases) to trigger this bug.
Minimal corruptor
- Clone and build https://github.com/nyanpasu64/jack-corruptor.
- Run a program (like Audacity or REAPER) which records from the microphone.
- Run
jack-corruptor
to overwrite the default input port (mic?) with -0.5, then watch as audio becomes garbled in the recording program (randomly alternating between -0.5 and actual audio).
Easy Effects corrupting audio
- Enable an Easy Effects chain.
- For the first effect in the chain (listening to "Easy Effects Sink" monitor_FL/FR), set the input gain to any nonzero number (I used negative).
- Begin recording audio in Audacity, then use pavucontrol to have it listen to "Monitor of Easy Effects Sink" as well.
- Replay the audio and note it's garbled (randomly alternating between the gain value and actual audio).
REAPER corrupting audio
- Run
pw-loopback --name "loop1" --capture-props='media.class=Audio/Sink' --playback-props='media.class=Audio/Source'
, and route a microphone into its sink (input). - Launch REAPER (with JACK backend) and have it listen to loop1's source (output).
- Arm a track for recording, then mute it to disable software playthrough. (This also causes REAPER to zero out input buffers.)
- Try recording audio from the microphone in another program (eg. close/reopen Firefox, then open Google Voice and call an echo test number like
(804) 222-1111, press 3
).
Audio remains garbled and mostly silent.
- Closing REAPER but leaving loop1 running does not fix this.
- If you instead disconnect and reconnect the microphone to loop1 (with or without REAPER still listening to loop1), the issue stops (even though the previous and final states are the "same").
Actual Results:
Whether it's legal or not, it's common for apps to overwrite input audio it receives from JACK or PipeWire. This also affects other apps listening to the same source node. I think this is because all sink nodes (and possibly the source node producing audio) share the same audio buffer through shared memory mapping.
Why does the issue not recur when I disconnect and reconnect the corrupting sink? I think the first sink that connects to a source (output) is the first one to get polled when it's ready, and any changes it makes to the audio buffer are likely to be heard by other clients. If you stop and restart the app corrupting memory (eg. reconnect microphone -> loop1), this link gets placed at the end of the list of sinks polled when a source (output) is ready, and other sinks will likely finish reading memory by the time your app gets called and starts corrupting memory.
Why does the microphone get corrupted, even when REAPER is listening to loop1 rather than the microphone? I suppose pw-loopback
shares its input and output buffers? IDK.
Expected Results:
If one app overwrites input audio, it should not corrupt the audio heard by other apps. This could be implemented using copy-on-write shared memory, read-only shared memory (though this will break existing apps dependent on mutating input buffers), or copying memory to each sink rather than merely mapping it in.
I don't know which fixes have acceptable CPU/memory/backwards-compat penalties.
Additional Info (as attachments):
-
pw-dump > pw-dump.log
: pw-dump.log