Is there an existing issue for this?
- I have searched the existing issues
Is your issue present in the latest beta/pre-release?
The logic is unchanged on master as of 2026-10-23 (src/platform/linux/audio.cpp, server_t::sink_info()); tested with 2026.1008.155609.
Describe the Bug
On Linux, sink_info() is meant to reuse existing sink-sunshine-stereo / sink-sunshine-surround51 / sink-sunshine-surround71 sinks ("Ensure Sunshine won't create a sink that already exists"), but it uses the sink's owner_module as the "exists" flag:
if (!std::strcmp(sink_info->name, stereo)) {
index.stereo = sink_info->owner_module;
++nullcount;
}
...
if (index.stereo == PA_INVALID_INDEX) {
index.stereo = load_null(stereo, ...);
...
++nullcount;
}
...
if (nullcount == 3) {
sink.null = ...;
}A sink that wasn't created by a PulseAudio module has owner_module == PA_INVALID_INDEX. That is the case for every native PipeWire node, e.g. null sinks defined permanently in ~/.config/pipewire/pipewire.conf.d/ with factory.name = support.null-audio-sink (pactl list sinks shows Owner Module: 4294967295). For such sinks Sunshine:
- counts the sink as found (
++nullcount), then - still calls
load_null()because the index isPA_INVALID_INDEX, creating a second sink with the same name, and counts it again; - ends up with
nullcount == 6, sosink.nullstays empty and the virtual sinks are never used. Audio is captured from the host sink's monitor instead, even with "play audio on host" off.
Why someone would want permanent sinks: today every session loads three module-null-sink modules, moves the user's streams onto one by changing the default sink, moves them back and unloads the modules a moment later. On a PipeWire host that churn coincided with a system-wide audio stall that needed a restart of pipewire/wireplumber (reported to PipeWire separately). Pre-defining the sinks so that Sunshine never loads or unloads anything avoids that, but only works if Sunshine reuses them.
Current workaround: give the native nodes a fake pulse.module.id without pipewire-pulse's module flag (e.g. pulse.module.id = 268435455). Sunshine then sees a valid owner index and reuses the sinks, and its unload_null() calls at the end fail with Access denied (logged as an error three times per session).
Expected Behavior
Sinks that already exist by name are reused, and Sunshine only unloads modules it loaded itself. For example, track "found" and "loaded by us" separately:
// in the sink list callback
if (!std::strcmp(sink_info->name, stereo)) {
found.stereo = true; // don't touch index.stereo: we didn't load it
++nullcount;
}
...
if (!found.stereo) {
index.stereo = load_null(stereo, ...);
if (index.stereo != PA_INVALID_INDEX) ++nullcount;
}so that ~server_t() only unloads indexes from load_null(). That also stops Sunshine from unloading sinks that a user or another program created with pactl load-module module-null-sink sink_name=sink-sunshine-stereo ..., which the current code does (it unloads whatever owner module it recorded).
Additional Context
Steps to reproduce:
- Add
~/.config/pipewire/pipewire.conf.d/sunshine-sinks.confwith threesupport.null-audio-sinkadapter objects namedsink-sunshine-stereo(FL FR),sink-sunshine-surround51(FL FR FC LFE RL RR) andsink-sunshine-surround71(FL FR FC LFE RL RR SL SR), and restart PipeWire. - Start a stream with "play audio on host" off.
pactl list modules short | grep null-sinkshows three newmodule-null-sinkmodules,pactl list sinks shortshows two sinks of each name, and the log never showsSetting default sink to: [sink-sunshine-stereo].
Host Operating System
Linux
Operating System Version
CachyOS (rolling), kernel 7.2, PipeWire 1.6.9 with pipewire-pulse, WirePlumber 0.5.18
Architecture
amd64/x86_64
Package
CachyOS repository package (sunshine 2026.1008.155609-1)
GPU Type
AMD (not relevant: audio only)
Activity
0No activity yet.