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.
This commit is contained in:
parent
97ae594375
commit
33716732a1
1
NEWS
1
NEWS
|
@ -1,6 +1,7 @@
|
||||||
ver 0.20.3 (not yet released)
|
ver 0.20.3 (not yet released)
|
||||||
* protocol
|
* protocol
|
||||||
- "playlistadd" creates new playlist if it does not exist, as documented
|
- "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
|
* use shortcuts such as "dsd64" in log messages
|
||||||
|
|
||||||
ver 0.20.2 (2017/01/15)
|
ver 0.20.2 (2017/01/15)
|
||||||
|
|
|
@ -20,9 +20,14 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "PcmChannels.hxx"
|
#include "PcmChannels.hxx"
|
||||||
#include "PcmBuffer.hxx"
|
#include "PcmBuffer.hxx"
|
||||||
|
#include "Silence.hxx"
|
||||||
#include "Traits.hxx"
|
#include "Traits.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
#include "util/WritableBuffer.hxx"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
@ -90,6 +95,38 @@ NToStereo(typename Traits::pointer_type dest,
|
||||||
return 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>>
|
template<SampleFormat F, class Traits=SampleTraits<F>>
|
||||||
static typename Traits::pointer_type
|
static typename Traits::pointer_type
|
||||||
NToM(typename Traits::pointer_type dest,
|
NToM(typename Traits::pointer_type dest,
|
||||||
|
@ -133,6 +170,9 @@ ConvertChannels(PcmBuffer &buffer,
|
||||||
StereoToMono<F>(dest, src.begin(), src.end());
|
StereoToMono<F>(dest, src.begin(), src.end());
|
||||||
else if (dest_channels == 2)
|
else if (dest_channels == 2)
|
||||||
NToStereo<F>(dest, src_channels, src.begin(), src.end());
|
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
|
else
|
||||||
NToM<F>(dest, dest_channels,
|
NToM<F>(dest, dest_channels,
|
||||||
src_channels, src.begin(), src.end());
|
src_channels, src.begin(), src.end());
|
||||||
|
|
|
@ -50,6 +50,21 @@ PcmChannelsTest::TestChannels16()
|
||||||
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
|
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
|
||||||
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
|
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
|
void
|
||||||
|
@ -78,4 +93,19 @@ PcmChannelsTest::TestChannels32()
|
||||||
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
|
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
|
||||||
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue