Skip to content

Patch to add a new "pwsink" mixer plugin for the PipeWire output - #2565

Open
gearhead wants to merge 2 commits into
MusicPlayerDaemon:masterfrom
gearhead:master
Open

Patch to add a new "pwsink" mixer plugin for the PipeWire output#2565
gearhead wants to merge 2 commits into
MusicPlayerDaemon:masterfrom
gearhead:master

Conversation

@gearhead

@gearhead gearhead commented Aug 9, 2026

Copy link
Copy Markdown

Unlike the existing pipewire mixer plugin (PipeWireMixerPlugin.cxx), which sets SPA_PROP_channelVolumes on MPD's own pw_stream (a per-client software gain stage applied before the signal reaches the sink, and not shared with other PipeWire clients), this plugin opens its own independent PipeWire client connection and drives a sink node's volume directly:

  • By default it follows the system's current default sink, resolved via the "default" pw_metadata object's "default.audio.sink" property (the same mechanism @DEFAULT_AUDIO_SINK@ uses).
  • If the output's "target" config option names a specific node, this plugin follows that node's volume instead, keeping audio routing and volume control pointed at the same sink without a second config key.

WirePlumber then applies the resulting volume through the ALSA hardware mixer control when the sink's Route reports one (route.hw-volume = true), or through PipeWire's own software volume otherwise -- giving PipeWire outputs the same "real hardware mixer" option that ALSA, OSS and PulseAudio outputs already have via mixer_type "hardware", instead of always paying for a second, client-side software gain stage on top of whatever WirePlumber itself applies.

MPD's own pw_stream is pinned to unity gain via
pipewire_output_set_volume() while this mixer is open, so the sink's own volume is the only gain stage in effect.

WirePlumber/wpctl display and set volume on a cubic scale rather than linear PCM gain; this is replicated here (config option "mixer_volume_curve") so a given MPD volume percentage matches what "wpctl get-volume" shows for the same node.

For a "pipewire" output, mixer_type "hardware" previously fell through to the output's own registered mixer_plugin (PipeWireMixerPlugin.cxx), i.e. the same client-side stream volume used when mixer_type is left unset entirely -- there was no way to ask for a real hardware/sink-backed mixer at all.

Special-case the HARDWARE case for pipewire outputs: if "mixer_type" is explicitly present in that output's config block (regardless of what any global default mixer_type is), use the new PwSinkMixer instead of the output's own plugin. If "mixer_type" is absent from the block, behaviour is unchanged (client-side stream volume via PipeWireMixerPlugin.cxx).

This intentionally inspects the block's raw configuration rather than the already-resolved MixerType, since the resolved value alone cannot distinguish "not configured, inherited the HARDWARE default" from "explicitly configured as hardware" -- and those two cases need different plugins here specifically because pipewire's own default mixer plugin already occupies the HARDWARE slot.

Output types other than "pipewire" are unaffected by this patch.

Also here is the documentation of the new behaviour of mixer_type "hardware" for pipewire outputs a "pipewire" ref label so other sections can link to it, describe the hardware-mixer opt-in and the mixer_volume_curve option in plugins.rst, and mention PipeWire alongside ALSA/OSS/PulseAudio in the mixer_type descriptions in user.rst and mpd.conf.5.rst.

Unlike the existing pipewire mixer plugin (PipeWireMixerPlugin.cxx),
which sets SPA_PROP_channelVolumes on MPD's own pw_stream (a per-client
software gain stage applied before the signal reaches the sink, and
not shared with other PipeWire clients), this plugin opens its own
independent PipeWire client connection and drives a sink node's
volume directly:

 - By default it follows the system's current default sink, resolved
   via the "default" pw_metadata object's "default.audio.sink"
   property (the same mechanism @DEFAULT_AUDIO_SINK@ uses).
 - If the output's "target" config option names a specific node, this
   plugin follows that node's volume instead, keeping audio routing
   and volume control pointed at the same sink without a second
   config key.

WirePlumber then applies the resulting volume through the ALSA
hardware mixer control when the sink's Route reports one
(route.hw-volume = true), or through PipeWire's own software volume
otherwise -- giving PipeWire outputs the same "real hardware mixer"
option that ALSA, OSS and PulseAudio outputs already have via
mixer_type "hardware", instead of always paying for a second,
client-side software gain stage on top of whatever WirePlumber itself
applies.

MPD's own pw_stream is pinned to unity gain via
pipewire_output_set_volume() while this mixer is open, so the sink's
own volume is the only gain stage in effect.

WirePlumber/wpctl display and set volume on a cubic scale rather than
linear PCM gain; this is replicated here (config option
"mixer_volume_curve") so a given MPD volume percentage matches what
"wpctl get-volume" shows for the same node.

For a "pipewire" output, mixer_type "hardware" previously fell
through to the output's own registered mixer_plugin
(PipeWireMixerPlugin.cxx), i.e. the same client-side stream volume
used when mixer_type is left unset entirely -- there was no way to
ask for a real hardware/sink-backed mixer at all.

Special-case the HARDWARE case for pipewire outputs: if "mixer_type"
is explicitly present in that output's config block (regardless of
what any global default mixer_type is), use the new PwSinkMixer
instead of the output's own plugin. If "mixer_type" is absent from
the block, behaviour is unchanged (client-side stream volume via
PipeWireMixerPlugin.cxx).

This intentionally inspects the block's raw configuration rather than
the already-resolved MixerType, since the resolved value alone cannot
distinguish "not configured, inherited the HARDWARE default" from
"explicitly configured as hardware" -- and those two cases need
different plugins here specifically because pipewire's own default
mixer plugin already occupies the HARDWARE slot.

Output types other than "pipewire" are unaffected by this patch.

Also here is the documentation of the new behaviour of mixer_type
"hardware" for pipewire outputs a "pipewire" ref label so other
sections can link to it, describe the hardware-mixer opt-in and
the mixer_volume_curve option in plugins.rst, and mention PipeWire
alongside ALSA/OSS/PulseAudio in the mixer_type descriptions in
user.rst and mpd.conf.5.rst.
@MaxKellermann

Copy link
Copy Markdown
Member

This is a big wall of text and a big wall of code. Is this LLM-generated code, or did you write every line (of code and text) manually?

Before I look at any this:

  • is it really necessary to have a separate plugin?
  • hard-coding "pipewire" in the MPD core is not acceptable. There must be a better way.
  • if I find any obvious LLM slop, I'll stop looking at the code and will close the PR immediately. You must take full responsibility for everything you submit here. "But Claude/Codex said ..." is the worst possible excuse. Don't be a meat proxy.
  • will you maintain this new plugin indefinitely, and will you promise to handle all bug reports? Unmaintained code (with nobody handling bug reports) will be removed from MPD.

@MaxKellermann

Copy link
Copy Markdown
Member

As I expected, all builds fail. Of course they do!

ld.lld: error: undefined symbol: pw_sink_mixer_plugin

@gearhead

gearhead commented Aug 9, 2026

Copy link
Copy Markdown
Author

Max,

I Apologize for the wall of text I did not have a feel for how much depth we wanted here.

As for coding 'pipewire' into the MPC core. I can work on adding this code to the existing pipewire plugin to avoid that test in the core.

I did have llm help for sure. I have been developing and using this patch for quite a while.

Yes, I'll maintain it.

I built this patch yesterday and am running it currently with no issues on the RPi. It does what it's supposed to do and it falls back to default behavior when configured as such. It appears that MPD had a later commit to when I forked. I just built my fork on Arch Linux, it builds and runs as well.

I do not get exactly why it failed to build here. Please help me to understand what is going on with that. The git has changed since I pulled the repo, but still builds when I apply this as a patch to the current git. I forked gff22bd411. I just tried it again and this patch applies and builds a functional mpd binary on the RPI with the latest git pull: g92cc5e76b

I dev for the RuneAudio and we use MPD as the front end for it. Previously, we used ALSA. In a bare alsa setup, we try to use the native alsa volume control and wanted to basically duplicate that functionality with pipewire.
We are looking at moving to pipewire and want to have the MPD volume control the sink volume as this is a music appliance setup.

I did not want to interfere with the initial audio stream navigated by MPD to pipewire, so opted to have a separate connection to wireplumber to control the sink volume.

I use this to build it on Arch that I got from the mpd-git AUR:

meson setup --prefix /usr \
        --libexecdir lib \
        --sbindir bin \
        --buildtype plain \
        --auto-features enabled \
        --wrap-mode nodownload \
        -D b_lto=true \
        -D b_pie=true \
        -D documentation=enabled \
        -D adplug=disabled \
        -D sndio=disabled \
        -D shine=disabled \
        -D tremor=disabled \
        -D openmpt=disabled \
        -D vgmstream=disabled \
        -D psgplay=disabled \
        build

  ninja -C build

I get this package when I use the mpd-git AUR and substitute my fork...
-rw-r--r-- 1 x x 3957411 Aug 9 16:44 mpd-git-r0.g10403bc-1-x86_64.pkg.tar.zst

If I install it and enable it as a user service I get it to work and it does as I expect.
If I enable mixer_type "hardware" in the conf, It creates a link in wireplumber:
158. mpd-pwsink-mixer [1.6.8, user@desktop, pid:942891]

If I comment that out, it defaults to normal mpd operation and this connection goes away.
When it is enabled, it allows me to control the sink volume.

user@desktop:~$ mpc volume 80
Capital Cities - Safe and Sound - Capital Cities - EP 
[playing] #1/1   2:23/0:00 (0%)
volume: 80%   repeat: off   random: off   single: off   consume: off
user@desktop:~$ wpctl get-volume 61
Volume: 0.80
user@desktop:~$ mpc volume 90
Capital Cities - Safe and Sound - Capital Cities - EP 
[playing] #1/1   2:37/0:00 (0%)
volume: 90%   repeat: off   random: off   single: off   consume: off
user@desktop:~$ wpctl get-volume 61
Volume: 0.90

Added pwsink volume to the pipewire plugin
@MaxKellermann

Copy link
Copy Markdown
Member

I hate reading all these LLM-generated too-verbose texts/code, and most likely I will reject this PR for that reason - reviewing LLM slop code is a DoS attack on maintainer's time. If you didn't bother to take the time to write this piece of code, don't expect me to take the time to read the code.

Your LLM has certainly misunderstood the meaning of mixer_type "hardware". This PR breaks existing configurations, because mixer_type "hardware" is the setting that currently enables the existing pipewire_mixer_plugin.
An output's MixerPlugin is always and only ever used for "hardware" mixers.

@gearhead

Copy link
Copy Markdown
Author

I do apologize for bothering you on this. The attention of hardware was my call. I was trying to mimic how the alsa worked. If you are heard set against this patch, that's fine. I'll apply and use it locally.
I guess my question is how else would you propose we use the mpd volume setting to control the pipewire sink volume? We are using mpd to play music on a pi and want the real hardware sink volume to be controlled by mpd, not the stream volume. I'm all ears and would be interested in helping on any way I can to achieve this mode of volume control.

@gearhead

Copy link
Copy Markdown
Author

Max, I can strip out the comments.
As for your other comment, I understand that 'hardware' is not the same between pipewire and alsa. That was my addition, probably wrongly, because on my card the sink volume is the hardware volume and I was trying to use a previously used parameter. If you want the parameters to be totally separate for pipewire, I can rework this.
How about adding a line to the plugin.rst for pipewire

* - **sink_volume yes|no**
   - Enables MPD to control the sink volume of default sink or target sink.

@MaxKellermann

Copy link
Copy Markdown
Member

Don't strip out comments. Documentation/comments are good, but only if they mean something.
The problem with LLMs is that they can and will generate shitloads of redundant text. Empty shells without meaning. Trying to understand LLM-generate code/text is just a waste of time.
If I take time to review code written by a human, that takes time, too, but that time is not wasted: I can give feedback to that human, and the human can learn from it. The human will over time improve skills and the next submission will be better. That is worth the time.
LLMs, however, by their nature, cannot learn. Sure, you can fix all my complaints, but the next time, you will just shoot more walls of text at me. In the end, the only person who has really spent time is me, not you; I would do all the real work. But for that, I don't need you at all. I can just prompt the LLM myself. You would have contributed nothing.

So please, don't submit LLM garbage here. That is not worth my time.

    • sink_volume yes|no

That would certainly an improvment over your current design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants