From e1fe9ebcd63ebfe6fcb680fa2a5a4cbef9742439 Mon Sep 17 00:00:00 2001
From: Shen-Ta Hsieh <ibmibmibm.tw@gmail.com>
Date: Wed, 2 Dec 2020 06:26:46 +0800
Subject: [PATCH] output/wasapi: Add dop support for WASAPI

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1102
---
 NEWS                                          |  1 +
 doc/plugins.rst                               |  2 +
 .../plugins/wasapi/WasapiOutputPlugin.cxx     | 46 +++++++++++++++++--
 3 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index e139092bf..a95f44537 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ ver 0.22.7 (not yet released)
 * output
   - wasapi: add algorithm for finding usable audio format
   - wasapi: use default device only if none was configured
+  - wasapi: add DoP support
 
 ver 0.22.6 (2021/02/16)
 * fix missing tags on songs in queue
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 571654df6..71b9ad004 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -1171,6 +1171,8 @@ The `Windows Audio Session API <https://docs.microsoft.com/en-us/windows/win32/c
      - Enumerate all devices in log while playing started. Useful for device configuration. The default value is "no".
    * - **exclusive yes|no**
      - Exclusive mode blocks all other audio source, and get best audio quality without resampling. Stopping playing release the exclusive control of the output device. The default value is "no".
+   * - **dop yes|no**
+     - Enable DSD over PCM. Require exclusive mode. The default value is "no".
 
 
 .. _filter_plugins:
diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
index 9a3616817..3882474f1 100644
--- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
+++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
@@ -107,6 +107,17 @@ inline bool SafeSilenceTry(Functor &&functor) {
 }
 
 std::vector<WAVEFORMATEXTENSIBLE> GetFormats(const AudioFormat &audio_format) noexcept {
+#ifdef ENABLE_DSD
+	if (audio_format.format == SampleFormat::DSD) {
+		AudioFormat dop_format = audio_format;
+		PcmExport::Params params;
+		params.dsd_mode = PcmExport::DsdMode::DOP;
+		dop_format.sample_rate =
+			params.CalcOutputSampleRate(audio_format.sample_rate);
+		dop_format.format = SampleFormat::S24_P32;
+		return GetFormats(dop_format);
+	}
+#endif
 	std::vector<WAVEFORMATEXTENSIBLE> Result;
 	if (audio_format.format == SampleFormat::S24_P32) {
 		Result.resize(2);
@@ -238,6 +249,9 @@ private:
 	bool is_started = false;
 	bool is_exclusive;
 	bool enumerate_devices;
+#ifdef ENABLE_DSD
+	bool dop_setting;
+#endif
 	std::string device_config;
 	ComPtr<IMMDeviceEnumerator> enumerator;
 	ComPtr<IMMDevice> device;
@@ -344,7 +358,11 @@ WasapiOutput::WasapiOutput(const ConfigBlock &block)
 : AudioOutput(FLAG_ENABLE_DISABLE | FLAG_PAUSE),
   is_exclusive(block.GetBlockValue("exclusive", false)),
   enumerate_devices(block.GetBlockValue("enumerate", false)),
-  device_config(block.GetBlockValue("device", "")) {}
+#ifdef ENABLE_DSD
+  dop_setting(block.GetBlockValue("dop", false)),
+#endif
+  device_config(block.GetBlockValue("device", "")) {
+}
 
 /// run inside COMWorkerThread
 void WasapiOutput::DoDisable() noexcept {
@@ -379,20 +397,27 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
 	}
 
 #ifdef ENABLE_DSD
-	if (audio_format.format == SampleFormat::DSD) {
+	if (!dop_setting && audio_format.format == SampleFormat::DSD) {
 		SetDSDFallback(audio_format);
 	}
 #endif
-
 	if (Exclusive()) {
 		FindExclusiveFormatSupported(audio_format);
 	} else {
 		FindSharedFormatSupported(audio_format);
 	}
 	bool require_export = audio_format.format == SampleFormat::S24_P32;
+#ifdef ENABLE_DSD
+	require_export |= audio_format.format == SampleFormat::DSD;
+#endif
 	if (require_export) {
 		PcmExport::Params params;
 		params.dsd_mode = PcmExport::DsdMode::NONE;
+#ifdef ENABLE_DSD
+		if (audio_format.format == SampleFormat::DSD) {
+			params.dsd_mode = PcmExport::DsdMode::DOP;
+		}
+#endif
 		params.shift8 = false;
 		params.pack24 = false;
 		if (device_format.Format.wBitsPerSample == 32 &&
@@ -659,6 +684,16 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
 			channels = audio_format.channels;
 		}
 		auto old_channels = std::exchange(audio_format.channels, channels);
+#ifdef ENABLE_DSD
+		bool was_dsd = false;
+		if (audio_format.format == SampleFormat::DSD) {
+			if (dop_setting && TryFormatExclusive(audio_format)) {
+				return;
+			}
+			was_dsd = true;
+			SetDSDFallback(audio_format);
+		}
+#endif
 		for (uint32_t rate : {0, 384000, 352800, 192000, 176400, 96000, 88200,
 				      48000, 44100, 32000, 22050, 16000, 11025, 8000}) {
 			if (audio_format.sample_rate <= rate) {
@@ -690,6 +725,11 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
 			}
 			audio_format.sample_rate = old_rate;
 		}
+#ifdef ENABLE_DSD
+		if (was_dsd) {
+			audio_format.format = SampleFormat::DSD;
+		}
+#endif
 		audio_format.channels = old_channels;
 	}
 }