diff options
author | Max Kellermann <max@musicpd.org> | 2017-01-19 10:53:41 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2017-01-19 10:53:41 +0100 |
commit | 33716732a1024ae27010f2ff4bbf334f12eb78e1 (patch) | |
tree | d23bf55b5892d8038caa7e0e1201f458a557da38 | |
parent | 97ae594375b3e0b1e03aa6e4d224cb8cdf8b6f84 (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-- | NEWS | 1 | ||||
-rw-r--r-- | src/pcm/PcmChannels.cxx | 40 | ||||
-rw-r--r-- | test/test_pcm_channels.cxx | 30 |
3 files changed, 71 insertions, 0 deletions
@@ -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]); + } } |