diff --git a/src/mixer/plugins/PipeWireMixerPlugin.cxx b/src/mixer/plugins/PipeWireMixerPlugin.cxx
index 2a8bb3a6c..e8c95baee 100644
--- a/src/mixer/plugins/PipeWireMixerPlugin.cxx
+++ b/src/mixer/plugins/PipeWireMixerPlugin.cxx
@@ -37,6 +37,8 @@ public:
 	{
 	}
 
+	~PipeWireMixer() override;
+
 	PipeWireMixer(const PipeWireMixer &) = delete;
 	PipeWireMixer &operator=(const PipeWireMixer &) = delete;
 
@@ -82,7 +84,14 @@ pipewire_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao,
 		    const ConfigBlock &)
 {
 	auto &po = (PipeWireOutput &)ao;
-	return new PipeWireMixer(po, listener);
+	auto *pm = new PipeWireMixer(po, listener);
+	pipewire_output_set_mixer(po, *pm);
+	return pm;
+}
+
+PipeWireMixer::~PipeWireMixer()
+{
+	pipewire_output_clear_mixer(output, *this);
 }
 
 const MixerPlugin pipewire_mixer_plugin = {
diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index db7c36823..0ebb9c802 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -41,6 +41,9 @@
 #include <spa/param/audio/format-utils.h>
 #include <spa/param/props.h>
 
+#include <cmath>
+#include <iostream>
+
 #ifdef __GNUC__
 #pragma GCC diagnostic pop
 #endif
@@ -75,6 +78,9 @@ class PipeWireOutput final : AudioOutput {
 
 	float volume = 1.0;
 
+	PipeWireMixer *mixer = nullptr;
+	int channels;
+
 	/**
 	 * The active sample format, needed for PcmSilence().
 	 */
@@ -124,11 +130,21 @@ public:
 		events.state_changed = StateChanged;
 		events.process = Process;
 		events.drained = Drained;
+		events.control_info = ControlInfo;
+		events.param_changed = ParamChanged;
 		return events;
 	}
 
 	void SetVolume(float volume);
 
+	void SetMixer(PipeWireMixer &_mixer);
+
+	void ClearMixer([[maybe_unused]] PipeWireMixer &old_mixer) {
+		assert(mixer == &old_mixer);
+
+		mixer = nullptr;
+	}
+
 private:
 	void CheckThrowError() {
 		if (disconnected)
@@ -163,6 +179,46 @@ private:
 		o.Drained();
 	}
 
+	void ControlInfo(const struct pw_stream_control *control) {
+		float sum = 0;
+		unsigned c;
+		for (c = 0; c < control->n_values; c++)
+			sum += control->values[c];
+
+		sum /= control->n_values;
+
+		if (mixer != nullptr)
+			pipewire_mixer_on_change(*mixer, std::cbrt(sum));
+
+		pw_thread_loop_signal(thread_loop, false);
+	}
+
+	static void ControlInfo(void *data,
+				[[maybe_unused]] uint32_t id,
+				const struct pw_stream_control *control) noexcept {
+		auto &o = *(PipeWireOutput *)data;
+		if (StringIsEqual(control->name, "Channel Volumes"))
+			o.ControlInfo(control);
+	}
+
+	void ParamChanged() {
+		if (restore_volume) {
+			SetVolume(volume);
+			restore_volume = false;
+		}
+	}
+
+	static void ParamChanged(void *data,
+				uint32_t id,
+				const struct spa_pod *param)
+	{
+		if (id != SPA_PARAM_Format || param == NULL)
+			return;
+
+		auto &o = *(PipeWireOutput *)data;
+		o.ParamChanged();
+	}
+
 	/* virtual methods from class AudioOutput */
 	void Enable() override;
 	void Disable() noexcept override;
@@ -214,11 +270,20 @@ PipeWireOutput::SetVolume(float _volume)
 {
 	const PipeWire::ThreadLoopLock lock(thread_loop);
 
-	if (stream != nullptr && !restore_volume &&
-	    pw_stream_set_control(stream,
-				  SPA_PROP_volume, 1, &_volume,
+	float newvol = _volume*_volume*_volume;
+
+	if (stream != nullptr && !restore_volume) {
+		float vol[SPA_AUDIO_MAX_CHANNELS];
+		int i;
+
+		for (i = 0; i < channels; i++) {
+			vol[i] = newvol;
+		}
+		if (pw_stream_set_control(stream,
+				  SPA_PROP_channelVolumes, channels, vol,
 				  0) != 0)
-		throw std::runtime_error("pw_stream_set_control() failed");
+			throw std::runtime_error("pw_stream_set_control() failed");
+	}
 
 	volume = _volume;
 }
@@ -404,6 +469,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 
 	frame_size = audio_format.GetFrameSize();
 	sample_format = audio_format.format;
+	channels = audio_format.channels;
 	interrupted = false;
 
 	/* allocate a ring buffer of 0.5 seconds */
@@ -451,14 +517,6 @@ PipeWireOutput::StateChanged(enum pw_stream_state state,
 	if (!was_disconnected && disconnected)
 		pw_thread_loop_signal(thread_loop, false);
 
-	if (state == PW_STREAM_STATE_STREAMING && restore_volume) {
-		/* restore the last known volume after creating a new
-		   pw_stream */
-		restore_volume = false;
-		pw_stream_set_control(stream,
-				      SPA_PROP_volume, 1, &volume,
-				      0);
-	}
 }
 
 inline void
@@ -585,6 +643,28 @@ PipeWireOutput::Pause() noexcept
 	return true;
 }
 
+inline void
+PipeWireOutput::SetMixer(PipeWireMixer &_mixer)
+{
+	assert(mixer == nullptr);
+
+	mixer = &_mixer;
+
+	// TODO: Check if context and stream is ready and trigger a volume update...
+}
+
+void
+pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm)
+{
+	po.SetMixer(pm);
+}
+
+void
+pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm)
+{
+	po.ClearMixer(pm);
+}
+
 const struct AudioOutputPlugin pipewire_output_plugin = {
 	"pipewire",
 	nullptr,
diff --git a/src/output/plugins/PipeWireOutputPlugin.hxx b/src/output/plugins/PipeWireOutputPlugin.hxx
index 9e8cd2c23..43125d767 100644
--- a/src/output/plugins/PipeWireOutputPlugin.hxx
+++ b/src/output/plugins/PipeWireOutputPlugin.hxx
@@ -21,9 +21,16 @@
 #define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
 
 class PipeWireOutput;
+class PipeWireMixer;
 
 extern const struct AudioOutputPlugin pipewire_output_plugin;
 
+void
+pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm);
+
+void
+pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm);
+
 void
 pipewire_output_set_volume(PipeWireOutput &output, float volume);