From 32851d1bc7d8a93445ceff13bb0335542a409841 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Wed, 20 Oct 2021 09:54:05 +0200
Subject: [PATCH] output/pipewire: DSD support

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1297
---
 NEWS                                        |  1 +
 doc/plugins.rst                             |  2 ++
 src/output/plugins/PipeWireOutputPlugin.cxx | 39 +++++++++++++++++++--
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 3712f6fed..b4689cf39 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ ver 0.23.2 (not yet released)
   - nfs: fix playback bug
 * output
   - pipewire: send artist and title to PipeWire
+  - pipewire: DSD support
 * neighbor
   - mention failed plugin name in error message
 * fix crash with libfmt versions older than 7
diff --git a/doc/plugins.rst b/doc/plugins.rst
index cb00877b2..18b176fd2 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -1094,6 +1094,8 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server.  Requires
    * - **remote NAME**
      - The name of the remote to connect to.  The default is
        ``pipewire-0``.
+   * - **dsd yes|no**
+     - Enable DSD playback.  This requires PipeWire 0.38.
 
 .. _pulse_plugin:
 
diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index 5f3ab5620..4d2717d1f 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -31,6 +31,7 @@
 #include "util/WritableBuffer.hxx"
 #include "Log.hxx"
 #include "tag/Format.hxx"
+#include "config.h" // for ENABLE_DSD
 
 #ifdef __GNUC__
 #pragma GCC diagnostic push
@@ -91,6 +92,10 @@ class PipeWireOutput final : AudioOutput {
 	 */
 	SampleFormat sample_format;
 
+#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
+	const bool enable_dsd;
+#endif
+
 	bool disconnected;
 
 	/**
@@ -262,6 +267,9 @@ PipeWireOutput::PipeWireOutput(const ConfigBlock &block)
 	 name(block.GetBlockValue("name", "pipewire")),
 	 remote(block.GetBlockValue("remote", nullptr)),
 	 target(block.GetBlockValue("target", nullptr))
+#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
+	, enable_dsd(block.GetBlockValue("dsd", false))
+#endif
 {
 	if (target != nullptr) {
 		if (StringIsEmpty(target))
@@ -477,6 +485,13 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 	if (stream == nullptr)
 		throw MakeErrno("pw_stream_new_simple() failed");
 
+#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
+	/* this needs to be determined before ToPipeWireAudioFormat()
+	   switches DSD to S16 */
+	const bool use_dsd = enable_dsd &&
+		audio_format.format == SampleFormat::DSD;
+#endif
+
 	auto raw = ToPipeWireAudioFormat(audio_format);
 
 	frame_size = audio_format.GetFrameSize();
@@ -494,8 +509,28 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 	pod_builder = {};
 	pod_builder.data = buffer;
 	pod_builder.size = sizeof(buffer);
-	params[0] = spa_format_audio_raw_build(&pod_builder,
-					       SPA_PARAM_EnumFormat, &raw);
+
+#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
+	struct spa_audio_info_dsd dsd;
+	if (use_dsd) {
+		dsd = {};
+
+		/* copy all relevant settings from the
+		   ToPipeWireAudioFormat() return value */
+		dsd.flags = raw.flags;
+		dsd.rate = raw.rate;
+		dsd.channels = raw.channels;
+		if ((dsd.flags & SPA_AUDIO_FLAG_UNPOSITIONED) == 0)
+			std::copy_n(raw.position, dsd.channels, dsd.position);
+
+		params[0] = spa_format_audio_dsd_build(&pod_builder,
+						       SPA_PARAM_EnumFormat,
+						       &dsd);
+	} else
+#endif
+		params[0] = spa_format_audio_raw_build(&pod_builder,
+						       SPA_PARAM_EnumFormat,
+						       &raw);
 
 	int error =
 		pw_stream_connect(stream,