diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx
index b40129ad1..5a9bf4558 100644
--- a/src/pcm/PcmExport.cxx
+++ b/src/pcm/PcmExport.cxx
@@ -25,6 +25,7 @@
 #include "util/ConstBuffer.hxx"
 
 #ifdef ENABLE_DSD
+#include "PcmDsd.hxx"
 #include "PcmDop.hxx"
 #endif
 
@@ -42,7 +43,15 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
 		: SampleFormat::UNDEFINED;
 
 #ifdef ENABLE_DSD
+	assert(!params.dsd_u32 || !params.dop);
 	assert(!params.dop || audio_valid_channel_count(_channels));
+
+	dsd_u32 = params.dsd_u32 && sample_format == SampleFormat::DSD;
+	if (dsd_u32)
+		/* after the conversion to DSD_U32, the DSD samples
+		   are stuffed inside fake 32 bit samples */
+		sample_format = SampleFormat::S32;
+
 	dop = params.dop && sample_format == SampleFormat::DSD;
 	if (dop)
 		/* after the conversion to DoP, the DSD
@@ -77,6 +86,9 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
 		return audio_format.channels * 3;
 
 #ifdef ENABLE_DSD
+	if (dsd_u32)
+		return channels * 4;
+
 	if (dop)
 		/* the DSD-over-USB draft says that DSD 1-bit samples
 		   are enclosed within 24 bit samples, and MPD's
@@ -96,6 +108,11 @@ PcmExport::Export(ConstBuffer<void> data)
 					  alsa_channel_order, channels);
 
 #ifdef ENABLE_DSD
+	if (dsd_u32)
+		data = Dsd8To32(dop_buffer, channels,
+				ConstBuffer<uint8_t>::FromVoid(data))
+			.ToVoid();
+
 	if (dop)
 		data = pcm_dsd_to_dop(dop_buffer, channels,
 				      ConstBuffer<uint8_t>::FromVoid(data))
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
index a03cdc903..b33d7b800 100644
--- a/src/pcm/PcmExport.hxx
+++ b/src/pcm/PcmExport.hxx
@@ -35,6 +35,7 @@ template<typename T> struct ConstBuffer;
 struct PcmExport {
 	struct Params {
 		bool alsa_channel_order = false;
+		bool dsd_u32 = false;
 		bool dop = false;
 		bool shift8 = false;
 		bool pack24 = false;
@@ -88,6 +89,11 @@ struct PcmExport {
 	SampleFormat alsa_channel_order;
 
 #ifdef ENABLE_DSD
+	/**
+	 * Convert DSD (U8) to DSD_U32?
+	 */
+	bool dsd_u32;
+
 	/**
 	 * Convert DSD to DSD-over-PCM (DoP)?  Input format must be
 	 * SampleFormat::DSD and output format must be
diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx
index ad0d245c3..c59d2e1f2 100644
--- a/test/test_pcm_all.hxx
+++ b/test/test_pcm_all.hxx
@@ -125,6 +125,7 @@ class PcmExportTest : public CppUnit::TestFixture {
 	CPPUNIT_TEST(TestShift8);
 	CPPUNIT_TEST(TestPack24);
 	CPPUNIT_TEST(TestReverseEndian);
+	CPPUNIT_TEST(TestDsdU32);
 	CPPUNIT_TEST(TestDop);
 	CPPUNIT_TEST(TestAlsaChannelOrder);
 	CPPUNIT_TEST_SUITE_END();
@@ -133,6 +134,7 @@ public:
 	void TestShift8();
 	void TestPack24();
 	void TestReverseEndian();
+	void TestDsdU32();
 	void TestDop();
 	void TestAlsaChannelOrder();
 };
diff --git a/test/test_pcm_export.cxx b/test/test_pcm_export.cxx
index cea0de634..bd4deb42a 100644
--- a/test/test_pcm_export.cxx
+++ b/test/test_pcm_export.cxx
@@ -115,6 +115,30 @@ PcmExportTest::TestReverseEndian()
 	CPPUNIT_ASSERT(memcmp(dest.data, expected4, dest.size) == 0);
 }
 
+void
+PcmExportTest::TestDsdU32()
+{
+	static constexpr uint8_t src[] = {
+		0x01, 0x23, 0x45, 0x67,
+		0x89, 0xab, 0xcd, 0xef,
+	};
+
+	static constexpr uint32_t expected[] = {
+		0xcd894501,
+		0xefab6723,
+	};
+
+	PcmExport::Params params;
+	params.dsd_u32 = true;
+
+	PcmExport e;
+	e.Open(SampleFormat::DSD, 2, params);
+
+	auto dest = e.Export({src, sizeof(src)});
+	CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size);
+	CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0);
+}
+
 void
 PcmExportTest::TestDop()
 {