diff --git a/Makefile.am b/Makefile.am index c846d7411..0bcd9ad9c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -537,6 +537,7 @@ libpcm_a_SOURCES = \ src/pcm/Neon.hxx \ src/pcm/FormatConverter.cxx src/pcm/FormatConverter.hxx \ src/pcm/ChannelsConverter.cxx src/pcm/ChannelsConverter.hxx \ + src/pcm/Order.cxx src/pcm/Order.hxx \ src/pcm/Resampler.hxx \ src/pcm/GlueResampler.cxx src/pcm/GlueResampler.hxx \ src/pcm/FallbackResampler.cxx src/pcm/FallbackResampler.hxx \ diff --git a/NEWS b/NEWS index 3db3c6e2a..d3888be38 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ ver 0.20 (not yet released) - embcue: fix last track - flac: new plugin which reads the "CUESHEET" metadata block * output + - alsa: fix multi-channel order - jack: reduce CPU usage - pulse: set channel map to WAVE-EX - recorder: record tags diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 8a7bb9643..0bc5438f1 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -711,7 +711,7 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, Error &error) pcm_export->Open(audio_format.format, audio_format.channels, - dop2, shift8, packed, reverse_endian); + true, dop2, shift8, packed, reverse_endian); return true; } diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index 7f75f4e31..ba86dc079 100644 --- a/src/output/plugins/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -537,7 +537,7 @@ oss_probe_sample_format(int fd, SampleFormat sample_format, *oss_format_r = oss_format; #ifdef AFMT_S24_PACKED - pcm_export.Open(sample_format, 0, false, false, + pcm_export.Open(sample_format, 0, true, false, false, oss_format == AFMT_S24_PACKED, oss_format == AFMT_S24_PACKED && !IsLittleEndian()); diff --git a/src/pcm/Order.cxx b/src/pcm/Order.cxx new file mode 100644 index 000000000..470f9d119 --- /dev/null +++ b/src/pcm/Order.cxx @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Order.hxx" +#include "PcmBuffer.hxx" +#include "util/ConstBuffer.hxx" + +template +struct TwoPointers { + V *dest; + const V *src; + + TwoPointers &CopyOne() { + *dest++ = *src++; + return *this; + } + + TwoPointers &CopyTwo() { + return CopyOne().CopyOne(); + } + + TwoPointers &SwapTwoPairs() { + *dest++ = src[2]; + *dest++ = src[3]; + *dest++ = src[0]; + *dest++ = src[1]; + src += 4; + return *this; + } + + TwoPointers &ToAlsa51() { + return CopyTwo() // left+right + .SwapTwoPairs(); // center, LFE, surround left+right + } + + TwoPointers &ToAlsa71() { + return ToAlsa51() + .CopyTwo(); // side left+right + } +}; + +template +static void +ToAlsaChannelOrder51(V *dest, const V *src, size_t n) +{ + TwoPointers p{dest, src}; + for (size_t i = 0; i != n; ++i) + p.ToAlsa51(); +} + +template +static inline ConstBuffer +ToAlsaChannelOrder51(PcmBuffer &buffer, ConstBuffer src) +{ + auto dest = buffer.GetT(src.size); + ToAlsaChannelOrder51(dest, src.data, src.size / 6); + return { dest, src.size }; +} + +template +static void +ToAlsaChannelOrder71(V *dest, const V *src, size_t n) +{ + TwoPointers p{dest, src}; + for (size_t i = 0; i != n; ++i) + p.ToAlsa71(); +} + +template +static inline ConstBuffer +ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer src) +{ + auto dest = buffer.GetT(src.size); + ToAlsaChannelOrder71(dest, src.data, src.size / 6); + return { dest, src.size }; +} + +template +static ConstBuffer +ToAlsaChannelOrderT(PcmBuffer &buffer, ConstBuffer src, unsigned channels) +{ + switch (channels) { + case 6: // 5.1 + return ToAlsaChannelOrder51(buffer, src); + + case 8: // 7.1 + return ToAlsaChannelOrder71(buffer, src); + + default: + return src; + } +} + +ConstBuffer +ToAlsaChannelOrder(PcmBuffer &buffer, ConstBuffer src, + SampleFormat sample_format, unsigned channels) +{ + switch (sample_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::S8: + case SampleFormat::DSD: + return src; + + case SampleFormat::S16: + return ToAlsaChannelOrderT(buffer, + ConstBuffer::FromVoid(src), + channels).ToVoid(); + + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + return ToAlsaChannelOrderT(buffer, + ConstBuffer::FromVoid(src), + channels).ToVoid(); + } + + gcc_unreachable(); +} diff --git a/src/pcm/Order.hxx b/src/pcm/Order.hxx new file mode 100644 index 000000000..14bfa2382 --- /dev/null +++ b/src/pcm/Order.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_ORDER_HXX +#define MPD_PCM_ORDER_HXX + +#include "check.h" +#include "AudioFormat.hxx" + +class PcmBuffer; +template struct ConstBuffer; + +/** + * Convert the given buffer from FLAC channel order + * (https://xiph.org/flac/format.html) to ALSA channel order. + */ +ConstBuffer +ToAlsaChannelOrder(PcmBuffer &buffer, ConstBuffer src, + SampleFormat sample_format, unsigned channels); + +#endif diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx index af2eb7d9f..0bf4f8be8 100644 --- a/src/pcm/PcmExport.cxx +++ b/src/pcm/PcmExport.cxx @@ -19,6 +19,7 @@ #include "config.h" #include "PcmExport.hxx" +#include "Order.hxx" #include "PcmDop.hxx" #include "PcmPack.hxx" #include "util/ByteReverse.hxx" @@ -28,12 +29,16 @@ void PcmExport::Open(SampleFormat sample_format, unsigned _channels, + bool _alsa_channel_order, bool _dop, bool _shift8, bool _pack, bool _reverse_endian) { assert(audio_valid_sample_format(sample_format)); assert(!_dop || audio_valid_channel_count(_channels)); channels = _channels; + alsa_channel_order = _alsa_channel_order + ? sample_format + : SampleFormat::UNDEFINED; dop = _dop && sample_format == SampleFormat::DSD; if (dop) /* after the conversion to DoP, the DSD @@ -77,6 +82,10 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const ConstBuffer PcmExport::Export(ConstBuffer data) { + if (alsa_channel_order != SampleFormat::UNDEFINED) + data = ToAlsaChannelOrder(order_buffer, data, + alsa_channel_order, channels); + if (dop) data = pcm_dsd_to_dop(dop_buffer, channels, ConstBuffer::FromVoid(data)) diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx index 7265ca07d..aafa1cea0 100644 --- a/src/pcm/PcmExport.hxx +++ b/src/pcm/PcmExport.hxx @@ -33,6 +33,13 @@ template struct ConstBuffer; * representation which are not supported by the pcm_convert library. */ struct PcmExport { + /** + * This buffer is used to reorder channels. + * + * @see #alsa_channel_order + */ + PcmBuffer order_buffer; + /** * The buffer is used to convert DSD samples to the * DoP format. @@ -60,6 +67,16 @@ struct PcmExport { */ uint8_t channels; + /** + * Convert the given buffer from FLAC channel order to ALSA + * channel order using ToAlsaChannelOrder()? + * + * If this value is SampleFormat::UNDEFINED, then no channel + * reordering is applied, otherwise this is the input sample + * format. + */ + SampleFormat alsa_channel_order; + /** * Convert DSD to DSD-over-PCM (DoP)? Input format must be * SampleFormat::DSD and output format must be @@ -96,6 +113,7 @@ struct PcmExport { * @param channels the number of channels; ignored unless dop is set */ void Open(SampleFormat sample_format, unsigned channels, + bool _alsa_channel_order, bool dop, bool shift8, bool pack, bool reverse_endian); /** diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx index fd08ec451..e63a58211 100644 --- a/test/test_pcm_all.hxx +++ b/test/test_pcm_all.hxx @@ -126,6 +126,7 @@ class PcmExportTest : public CppUnit::TestFixture { CPPUNIT_TEST(TestPack24); CPPUNIT_TEST(TestReverseEndian); CPPUNIT_TEST(TestDop); + CPPUNIT_TEST(TestAlsaChannelOrder); CPPUNIT_TEST_SUITE_END(); public: @@ -133,6 +134,7 @@ public: void TestPack24(); void TestReverseEndian(); void TestDop(); + void TestAlsaChannelOrder(); }; #endif diff --git a/test/test_pcm_export.cxx b/test/test_pcm_export.cxx index 347e2394a..60fd67483 100644 --- a/test/test_pcm_export.cxx +++ b/test/test_pcm_export.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "test_pcm_all.hxx" #include "pcm/PcmExport.hxx" +#include "pcm/Traits.hxx" #include "system/ByteOrder.hxx" #include "util/ConstBuffer.hxx" @@ -32,7 +33,7 @@ PcmExportTest::TestShift8() static constexpr uint32_t expected[] = { 0x0, 0x100, 0x10000, 0x1000000, 0xffffff00 }; PcmExport e; - e.Open(SampleFormat::S24_P32, 2, false, true, false, false); + e.Open(SampleFormat::S24_P32, 2, false, false, true, false, false); auto dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size); @@ -65,7 +66,7 @@ PcmExportTest::TestPack24() ? expected_be : expected_le; PcmExport e; - e.Open(SampleFormat::S24_P32, 2, false, false, true, false); + e.Open(SampleFormat::S24_P32, 2, false, false, false, true, false); auto dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(expected_size, dest.size); @@ -88,18 +89,18 @@ PcmExportTest::TestReverseEndian() }; PcmExport e; - e.Open(SampleFormat::S8, 2, false, false, false, true); + e.Open(SampleFormat::S8, 2, false, false, false, false, true); auto dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(sizeof(src), dest.size); CPPUNIT_ASSERT(memcmp(dest.data, src, dest.size) == 0); - e.Open(SampleFormat::S16, 2, false, false, false, true); + e.Open(SampleFormat::S16, 2, false, false, false, false, true); dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(sizeof(expected2), dest.size); CPPUNIT_ASSERT(memcmp(dest.data, expected2, dest.size) == 0); - e.Open(SampleFormat::S32, 2, false, false, false, true); + e.Open(SampleFormat::S32, 2, false, false, false, false, true); dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(sizeof(expected4), dest.size); CPPUNIT_ASSERT(memcmp(dest.data, expected4, dest.size) == 0); @@ -121,9 +122,66 @@ PcmExportTest::TestDop() }; PcmExport e; - e.Open(SampleFormat::DSD, 2, true, false, false, false); + e.Open(SampleFormat::DSD, 2, false, true, false, false, false); auto dest = e.Export({src, sizeof(src)}); CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size); CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0); } + +template> +static void +TestAlsaChannelOrder51() +{ + typedef typename Traits::value_type value_type; + + static constexpr value_type src[] = { + 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, + }; + + static constexpr value_type expected[] = { + 0, 1, 4, 5, 2, 3, + 6, 7, 10, 11, 8, 9, + }; + + PcmExport e; + e.Open(F, 6, true, false, false, false, false); + + auto dest = e.Export({src, sizeof(src)}); + CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size); + CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0); +} + +template> +static void +TestAlsaChannelOrder71() +{ + typedef typename Traits::value_type value_type; + + static constexpr value_type src[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + }; + + static constexpr value_type expected[] = { + 0, 1, 4, 5, 2, 3, 6, 7, + 8, 9, 12, 13, 10, 11, 14, 15, + }; + + PcmExport e; + e.Open(F, 8, true, false, false, false, false); + + 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::TestAlsaChannelOrder() +{ + TestAlsaChannelOrder51(); + TestAlsaChannelOrder71(); + TestAlsaChannelOrder51(); + TestAlsaChannelOrder71(); +}