summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2017-01-19 10:53:41 +0100
committerMax Kellermann <max@musicpd.org>2017-01-19 10:53:41 +0100
commit33716732a1024ae27010f2ff4bbf334f12eb78e1 (patch)
treed23bf55b5892d8038caa7e0e1201f458a557da38
parent97ae594375b3e0b1e03aa6e4d224cb8cdf8b6f84 (diff)
pcm/PcmChannels: silence surround channels when converting from stereo
Previously, there was no special code to convert stereo to multi-channel. The generic solution for this was to convert to mono, and then copy the result to all channels. That's a pretty bad solution, but at least something which always renders audio. MPD does something, instead of failing. Now that MPD has proper support for multi-channel (by defining the channel order), we can do better than that. It is a (somewhat) common case to play back stereo music on a DAC which can only do multi-channel. The best approach here is to copy the stereo channels to front-left and front-right, and apply the "silence" pattern to all other channels.
-rw-r--r--NEWS1
-rw-r--r--src/pcm/PcmChannels.cxx40
-rw-r--r--test/test_pcm_channels.cxx30
3 files changed, 71 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index bf6e1a6e3..951914d2e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
ver 0.20.3 (not yet released)
* protocol
- "playlistadd" creates new playlist if it does not exist, as documented
+* silence surround channels when converting from stereo
* use shortcuts such as "dsd64" in log messages
ver 0.20.2 (2017/01/15)
diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx
index 363dde824..1105e7412 100644
--- a/src/pcm/PcmChannels.cxx
+++ b/src/pcm/PcmChannels.cxx
@@ -20,9 +20,14 @@
#include "config.h"
#include "PcmChannels.hxx"
#include "PcmBuffer.hxx"
+#include "Silence.hxx"
#include "Traits.hxx"
#include "AudioFormat.hxx"
#include "util/ConstBuffer.hxx"
+#include "util/WritableBuffer.hxx"
+
+#include <array>
+#include <algorithm>
#include <assert.h>
@@ -90,6 +95,38 @@ NToStereo(typename Traits::pointer_type dest,
return dest;
}
+/**
+ * Convert stereo to N channels (where N > 2). Left and right map to
+ * the first two channels (front left and front right), and the
+ * remaining (surround) channels are filled with silence.
+ */
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+StereoToN(typename Traits::pointer_type dest,
+ unsigned dest_channels,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
+{
+ assert(dest_channels > 2);
+ assert((end - src) % 2 == 0);
+
+ std::array<typename Traits::value_type, MAX_CHANNELS - 2> silence;
+ PcmSilence({&silence.front(), sizeof(silence)}, F);
+
+ while (src != end) {
+ /* copy left/right to front-left/front-right, which is
+ the first two channels in all multi-channel
+ configurations **/
+ *dest++ = *src++;
+ *dest++ = *src++;
+
+ /* all other channels are silent */
+ dest = std::copy_n(silence.begin(), dest_channels - 2, dest);
+ }
+
+ return dest;
+}
+
template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::pointer_type
NToM(typename Traits::pointer_type dest,
@@ -133,6 +170,9 @@ ConvertChannels(PcmBuffer &buffer,
StereoToMono<F>(dest, src.begin(), src.end());
else if (dest_channels == 2)
NToStereo<F>(dest, src_channels, src.begin(), src.end());
+ else if (src_channels == 2 && dest_channels > 2)
+ StereoToN<F, Traits>(dest, dest_channels,
+ src.begin(), src.end());
else
NToM<F>(dest, dest_channels,
src_channels, src.begin(), src.end());
diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx
index d5dacbe43..39f03ab80 100644
--- a/test/test_pcm_channels.cxx
+++ b/test/test_pcm_channels.cxx
@@ -50,6 +50,21 @@ PcmChannelsTest::TestChannels16()
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
}
+
+ /* stereo to 5.1 */
+
+ dest = pcm_convert_channels_16(buffer, 6, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 6, dest.size);
+ constexpr int16_t silence = 0;
+ for (unsigned i = 0; i < N; ++i) {
+ CPPUNIT_ASSERT_EQUAL(src[i * 2], dest[i * 6]);
+ CPPUNIT_ASSERT_EQUAL(src[i * 2 + 1], dest[i * 6+ 1]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 2]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 3]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 4]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 5]);
+ }
}
void
@@ -78,4 +93,19 @@ PcmChannelsTest::TestChannels32()
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
}
+
+ /* stereo to 5.1 */
+
+ dest = pcm_convert_channels_32(buffer, 6, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 6, dest.size);
+ constexpr int32_t silence = 0;
+ for (unsigned i = 0; i < N; ++i) {
+ CPPUNIT_ASSERT_EQUAL(src[i * 2], dest[i * 6]);
+ CPPUNIT_ASSERT_EQUAL(src[i * 2 + 1], dest[i * 6+ 1]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 2]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 3]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 4]);
+ CPPUNIT_ASSERT_EQUAL(silence, dest[i * 6 + 5]);
+ }
}