aboutsummaryrefslogtreecommitdiffstats
path: root/src/pcm
diff options
context:
space:
mode:
Diffstat (limited to 'src/pcm')
-rw-r--r--src/pcm/PcmBuffer.cxx33
-rw-r--r--src/pcm/PcmBuffer.hxx53
-rw-r--r--src/pcm/PcmChannels.cxx290
-rw-r--r--src/pcm/PcmChannels.hxx97
-rw-r--r--src/pcm/PcmConvert.cxx310
-rw-r--r--src/pcm/PcmConvert.hxx109
-rw-r--r--src/pcm/PcmDither.cxx89
-rw-r--r--src/pcm/PcmDither.hxx44
-rw-r--r--src/pcm/PcmDsd.cxx82
-rw-r--r--src/pcm/PcmDsd.hxx46
-rw-r--r--src/pcm/PcmDsdUsb.cxx96
-rw-r--r--src/pcm/PcmDsdUsb.hxx41
-rw-r--r--src/pcm/PcmExport.cxx145
-rw-r--r--src/pcm/PcmExport.hxx127
-rw-r--r--src/pcm/PcmFormat.cxx500
-rw-r--r--src/pcm/PcmFormat.hxx93
-rw-r--r--src/pcm/PcmMix.cxx209
-rw-r--r--src/pcm/PcmMix.hxx49
-rw-r--r--src/pcm/PcmPrng.hxx33
-rw-r--r--src/pcm/PcmResample.cxx150
-rw-r--r--src/pcm/PcmResample.hxx139
-rw-r--r--src/pcm/PcmResampleFallback.cxx106
-rw-r--r--src/pcm/PcmResampleInternal.hxx91
-rw-r--r--src/pcm/PcmResampleLibsamplerate.cxx286
-rw-r--r--src/pcm/PcmUtils.hxx66
-rw-r--r--src/pcm/PcmVolume.cxx190
-rw-r--r--src/pcm/PcmVolume.hxx81
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.c184
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.h64
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.hpp39
-rw-r--r--src/pcm/dsd2pcm/info.txt38
-rw-r--r--src/pcm/dsd2pcm/main.cpp120
-rw-r--r--src/pcm/dsd2pcm/noiseshape.c83
-rw-r--r--src/pcm/dsd2pcm/noiseshape.h57
-rw-r--r--src/pcm/dsd2pcm/noiseshape.hpp43
-rw-r--r--src/pcm/pcm_pack.c77
-rw-r--r--src/pcm/pcm_pack.h55
37 files changed, 4315 insertions, 0 deletions
diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx
new file mode 100644
index 000000000..6ace399f3
--- /dev/null
+++ b/src/pcm/PcmBuffer.cxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmBuffer.hxx"
+#include "poison.h"
+
+void *
+PcmBuffer::Get(size_t new_size)
+{
+ if (new_size == 0)
+ /* never return NULL, because NULL would be assumed to
+ be an error condition */
+ new_size = 1;
+
+ return buffer.Get(new_size);
+}
diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx
new file mode 100644
index 000000000..2eddfb7f9
--- /dev/null
+++ b/src/pcm/PcmBuffer.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2013 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 PCM_BUFFER_HXX
+#define PCM_BUFFER_HXX
+
+#include "util/ReusableArray.hxx"
+#include "gcc.h"
+
+#include <stdint.h>
+
+/**
+ * Manager for a temporary buffer which grows as needed. We could
+ * allocate a new buffer every time pcm_convert() is called, but that
+ * would put too much stress on the allocator.
+ */
+class PcmBuffer {
+ ReusableArray<uint8_t, 8192> buffer;
+
+public:
+ void Clear() {
+ buffer.Clear();
+ }
+
+ /**
+ * Get the buffer, and guarantee a minimum size. This buffer becomes
+ * invalid with the next pcm_buffer_get() call.
+ *
+ * This function will never return NULL, even if size is zero, because
+ * the PCM library uses the NULL return value to signal "error". An
+ * empty destination buffer is not always an error.
+ */
+ gcc_malloc
+ void *Get(size_t size);
+};
+
+#endif
diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx
new file mode 100644
index 000000000..27a155063
--- /dev/null
+++ b/src/pcm/PcmChannels.cxx
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmChannels.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmUtils.hxx"
+
+#include <assert.h>
+
+template<typename D, typename S>
+static void
+MonoToStereo(D dest, S src, S end)
+{
+ while (src != end) {
+ const auto value = *src++;
+
+ *dest++ = value;
+ *dest++ = value;
+ }
+
+}
+
+static void
+pcm_convert_channels_16_2_to_1(int16_t *restrict dest,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
+{
+ while (src < src_end) {
+ int32_t a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_16_n_to_2(int16_t *restrict dest,
+ unsigned src_channels,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ int32_t sum = 0;
+ int16_t value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (int)src_channels;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+const int16_t *
+pcm_convert_channels_16(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int16_t *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+
+ size_t dest_size = src_size / src_channels * dest_channels;
+ *dest_size_r = dest_size;
+
+ int16_t *dest = (int16_t *)buffer.Get(dest_size);
+ const int16_t *src_end = pcm_end_pointer(src, src_size);
+
+ if (src_channels == 1 && dest_channels == 2)
+ MonoToStereo(dest, src, src_end);
+ else if (src_channels == 2 && dest_channels == 1)
+ pcm_convert_channels_16_2_to_1(dest, src, src_end);
+ else if (dest_channels == 2)
+ pcm_convert_channels_16_n_to_2(dest, src_channels, src,
+ src_end);
+ else
+ return NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_24_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
+{
+ while (src < src_end) {
+ int32_t a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_24_n_to_2(int32_t *restrict dest,
+ unsigned src_channels,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ int32_t sum = 0;
+ int32_t value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (int)src_channels;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+const int32_t *
+pcm_convert_channels_24(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+
+ size_t dest_size = src_size / src_channels * dest_channels;
+ *dest_size_r = dest_size;
+
+ int32_t *dest = (int32_t *)buffer.Get(dest_size);
+ const int32_t *src_end = (const int32_t *)
+ pcm_end_pointer(src, src_size);
+
+ if (src_channels == 1 && dest_channels == 2)
+ MonoToStereo(dest, src, src_end);
+ else if (src_channels == 2 && dest_channels == 1)
+ pcm_convert_channels_24_2_to_1(dest, src, src_end);
+ else if (dest_channels == 2)
+ pcm_convert_channels_24_n_to_2(dest, src_channels, src,
+ src_end);
+ else
+ return NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_32_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
+{
+ while (src < src_end) {
+ int64_t a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_32_n_to_2(int32_t *dest,
+ unsigned src_channels, const int32_t *src,
+ const int32_t *src_end)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ int64_t sum = 0;
+ int32_t value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (int64_t)src_channels;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+const int32_t *
+pcm_convert_channels_32(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+
+ size_t dest_size = src_size / src_channels * dest_channels;
+ *dest_size_r = dest_size;
+
+ int32_t *dest = (int32_t *)buffer.Get(dest_size);
+ const int32_t *src_end = (const int32_t *)
+ pcm_end_pointer(src, src_size);
+
+ if (src_channels == 1 && dest_channels == 2)
+ MonoToStereo(dest, src, src_end);
+ else if (src_channels == 2 && dest_channels == 1)
+ pcm_convert_channels_32_2_to_1(dest, src, src_end);
+ else if (dest_channels == 2)
+ pcm_convert_channels_32_n_to_2(dest, src_channels, src,
+ src_end);
+ else
+ return NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_float_2_to_1(float *restrict dest,
+ const float *restrict src,
+ const float *restrict src_end)
+{
+ while (src < src_end) {
+ double a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_float_n_to_2(float *dest,
+ unsigned src_channels, const float *src,
+ const float *src_end)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ double sum = 0;
+ float value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (double)src_channels;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+const float *
+pcm_convert_channels_float(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const float *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+
+ size_t dest_size = src_size / src_channels * dest_channels;
+ *dest_size_r = dest_size;
+
+ float *dest = (float *)buffer.Get(dest_size);
+ const float *src_end = (const float *)pcm_end_pointer(src, src_size);
+
+ if (src_channels == 1 && dest_channels == 2)
+ MonoToStereo(dest, src, src_end);
+ else if (src_channels == 2 && dest_channels == 1)
+ pcm_convert_channels_float_2_to_1(dest, src, src_end);
+ else if (dest_channels == 2)
+ pcm_convert_channels_float_n_to_2(dest, src_channels, src,
+ src_end);
+ else
+ return NULL;
+
+ return dest;
+}
diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx
new file mode 100644
index 000000000..c67822825
--- /dev/null
+++ b/src/pcm/PcmChannels.hxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2013 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_CHANNELS_HXX
+#define MPD_PCM_CHANNELS_HXX
+
+#include <stdint.h>
+#include <stddef.h>
+
+class PcmBuffer;
+
+/**
+ * Changes the number of channels in 16 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param dest_channels the number of channels requested
+ * @param src_channels the number of channels in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int16_t *
+pcm_convert_channels_16(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int16_t *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Changes the number of channels in 24 bit PCM data (aligned at 32
+ * bit boundaries).
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param dest_channels the number of channels requested
+ * @param src_channels the number of channels in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int32_t *
+pcm_convert_channels_24(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Changes the number of channels in 32 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param dest_channels the number of channels requested
+ * @param src_channels the number of channels in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int32_t *
+pcm_convert_channels_32(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Changes the number of channels in 32 bit float PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param dest_channels the number of channels requested
+ * @param src_channels the number of channels in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const float *
+pcm_convert_channels_float(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const float *src,
+ size_t src_size, size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
new file mode 100644
index 000000000..4260ccb0f
--- /dev/null
+++ b/src/pcm/PcmConvert.cxx
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2003-2011 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 "PcmConvert.hxx"
+#include "PcmChannels.hxx"
+#include "PcmFormat.hxx"
+#include "pcm_pack.h"
+#include "AudioFormat.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <math.h>
+
+const Domain pcm_convert_domain("pcm_convert");
+
+PcmConvert::PcmConvert()
+{
+}
+
+PcmConvert::~PcmConvert()
+{
+}
+
+void
+PcmConvert::Reset()
+{
+ dsd.Reset();
+ resampler.Reset();
+}
+
+inline const int16_t *
+PcmConvert::Convert16(const AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ const AudioFormat dest_format, size_t *dest_size_r,
+ Error &error)
+{
+ const int16_t *buf;
+ size_t len;
+
+ assert(dest_format.format == SampleFormat::S16);
+
+ buf = pcm_convert_to_16(format_buffer, dither,
+ src_format.format,
+ src_buffer, src_size,
+ &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %s to 16 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ if (src_format.channels != dest_format.channels) {
+ buf = pcm_convert_channels_16(channels_buffer,
+ dest_format.channels,
+ src_format.channels,
+ buf, len, &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ if (src_format.sample_rate != dest_format.sample_rate) {
+ buf = resampler.Resample16(dest_format.channels,
+ src_format.sample_rate, buf, len,
+ dest_format.sample_rate, &len,
+ error);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = len;
+ return buf;
+}
+
+inline const int32_t *
+PcmConvert::Convert24(const AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ const AudioFormat dest_format, size_t *dest_size_r,
+ Error &error)
+{
+ const int32_t *buf;
+ size_t len;
+
+ assert(dest_format.format == SampleFormat::S24_P32);
+
+ buf = pcm_convert_to_24(format_buffer,
+ src_format.format,
+ src_buffer, src_size, &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ if (src_format.channels != dest_format.channels) {
+ buf = pcm_convert_channels_24(channels_buffer,
+ dest_format.channels,
+ src_format.channels,
+ buf, len, &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ if (src_format.sample_rate != dest_format.sample_rate) {
+ buf = resampler.Resample24(dest_format.channels,
+ src_format.sample_rate, buf, len,
+ dest_format.sample_rate, &len,
+ error);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = len;
+ return buf;
+}
+
+inline const int32_t *
+PcmConvert::Convert32(const AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ const AudioFormat dest_format, size_t *dest_size_r,
+ Error &error)
+{
+ const int32_t *buf;
+ size_t len;
+
+ assert(dest_format.format == SampleFormat::S32);
+
+ buf = pcm_convert_to_32(format_buffer,
+ src_format.format,
+ src_buffer, src_size, &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %s to 32 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ if (src_format.channels != dest_format.channels) {
+ buf = pcm_convert_channels_32(channels_buffer,
+ dest_format.channels,
+ src_format.channels,
+ buf, len, &len);
+ if (buf == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ if (src_format.sample_rate != dest_format.sample_rate) {
+ buf = resampler.Resample32(dest_format.channels,
+ src_format.sample_rate, buf, len,
+ dest_format.sample_rate, &len,
+ error);
+ if (buf == NULL)
+ return buf;
+ }
+
+ *dest_size_r = len;
+ return buf;
+}
+
+inline const float *
+PcmConvert::ConvertFloat(const AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ const AudioFormat dest_format, size_t *dest_size_r,
+ Error &error)
+{
+ const float *buffer = (const float *)src_buffer;
+ size_t size = src_size;
+
+ assert(dest_format.format == SampleFormat::FLOAT);
+
+ /* convert to float now */
+
+ buffer = pcm_convert_to_float(format_buffer,
+ src_format.format,
+ buffer, size, &size);
+ if (buffer == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %s to float is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ /* convert channels */
+
+ if (src_format.channels != dest_format.channels) {
+ buffer = pcm_convert_channels_float(channels_buffer,
+ dest_format.channels,
+ src_format.channels,
+ buffer, size, &size);
+ if (buffer == NULL) {
+ error.Format(pcm_convert_domain,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ /* resample with float, because this is the best format for
+ libsamplerate */
+
+ if (src_format.sample_rate != dest_format.sample_rate) {
+ buffer = resampler.ResampleFloat(dest_format.channels,
+ src_format.sample_rate,
+ buffer, size,
+ dest_format.sample_rate,
+ &size, error);
+ if (buffer == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = size;
+ return buffer;
+}
+
+const void *
+PcmConvert::Convert(AudioFormat src_format,
+ const void *src, size_t src_size,
+ const AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error)
+{
+ AudioFormat float_format;
+ if (src_format.format == SampleFormat::DSD) {
+ size_t f_size;
+ const float *f = dsd.ToFloat(src_format.channels,
+ false, (const uint8_t *)src,
+ src_size, &f_size);
+ if (f == NULL) {
+ error.Set(pcm_convert_domain,
+ "DSD to PCM conversion failed");
+ return NULL;
+ }
+
+ float_format = src_format;
+ float_format.format = SampleFormat::FLOAT;
+
+ src_format = float_format;
+ src = f;
+ src_size = f_size;
+ }
+
+ switch (dest_format.format) {
+ case SampleFormat::S16:
+ return Convert16(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error);
+
+ case SampleFormat::S24_P32:
+ return Convert24(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error);
+
+ case SampleFormat::S32:
+ return Convert32(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error);
+
+ case SampleFormat::FLOAT:
+ return ConvertFloat(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error);
+
+ default:
+ error.Format(pcm_convert_domain,
+ "PCM conversion to %s is not implemented",
+ sample_format_to_string(dest_format.format));
+ return NULL;
+ }
+}
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
new file mode 100644
index 000000000..40f785179
--- /dev/null
+++ b/src/pcm/PcmConvert.hxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2013 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 PCM_CONVERT_HXX
+#define PCM_CONVERT_HXX
+
+#include "PcmDither.hxx"
+#include "PcmDsd.hxx"
+#include "PcmResample.hxx"
+#include "PcmBuffer.hxx"
+
+#include <stddef.h>
+
+struct AudioFormat;
+class Error;
+
+/**
+ * This object is statically allocated (within another struct), and
+ * holds buffer allocations and the state for all kinds of PCM
+ * conversions.
+ */
+class PcmConvert {
+ PcmDsd dsd;
+
+ PcmResampler resampler;
+
+ PcmDither dither;
+
+ /** the buffer for converting the sample format */
+ PcmBuffer format_buffer;
+
+ /** the buffer for converting the channel count */
+ PcmBuffer channels_buffer;
+
+public:
+ PcmConvert();
+ ~PcmConvert();
+
+
+ /**
+ * Reset the pcm_convert_state object. Use this at the
+ * boundary between two distinct songs and each time the
+ * format changes.
+ */
+ void Reset();
+
+ /**
+ * Converts PCM data between two audio formats.
+ *
+ * @param src_format the source audio format
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_format the requested destination audio format
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return the destination buffer, or NULL on error
+ */
+ const void *Convert(AudioFormat src_format,
+ const void *src, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error);
+
+private:
+ const int16_t *Convert16(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error);
+
+ const int32_t *Convert24(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error);
+
+ const int32_t *Convert32(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error);
+
+ const float *ConvertFloat(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ Error &error);
+};
+
+extern const class Domain pcm_convert_domain;
+
+#endif
diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx
new file mode 100644
index 000000000..98d0d443e
--- /dev/null
+++ b/src/pcm/PcmDither.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmDither.hxx"
+#include "PcmPrng.hxx"
+
+inline int16_t
+PcmDither::Dither24To16(int_fast32_t sample)
+{
+ constexpr unsigned from_bits = 24;
+ constexpr unsigned to_bits = 16;
+ constexpr unsigned scale_bits = from_bits - to_bits;
+ constexpr int_fast32_t round = 1 << (scale_bits - 1);
+ constexpr int_fast32_t mask = (1 << scale_bits) - 1;
+ constexpr int_fast32_t ONE = 1 << (from_bits - 1);
+ constexpr int_fast32_t MIN = -ONE;
+ constexpr int_fast32_t MAX = ONE - 1;
+
+ sample += error[0] - error[1] + error[2];
+
+ error[2] = error[1];
+ error[1] = error[0] / 2;
+
+ /* round */
+ int_fast32_t output = sample + round;
+
+ int_fast32_t rnd = pcm_prng(random);
+ output += (rnd & mask) - (random & mask);
+
+ random = rnd;
+
+ /* clip */
+ if (output > MAX) {
+ output = MAX;
+
+ if (sample > MAX)
+ sample = MAX;
+ } else if (output < MIN) {
+ output = MIN;
+
+ if (sample < MIN)
+ sample = MIN;
+ }
+
+ output &= ~mask;
+
+ error[0] = sample - output;
+
+ return (int16_t)(output >> scale_bits);
+}
+
+void
+PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
+{
+ while (src < src_end)
+ *dest++ = Dither24To16(*src++);
+}
+
+inline int16_t
+PcmDither::Dither32To16(int_fast32_t sample)
+{
+ return Dither24To16(sample >> 8);
+}
+
+void
+PcmDither::Dither32To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
+{
+ while (src < src_end)
+ *dest++ = Dither32To16(*src++);
+}
diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx
new file mode 100644
index 000000000..106382307
--- /dev/null
+++ b/src/pcm/PcmDither.hxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2013 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_DITHER_HXX
+#define MPD_PCM_DITHER_HXX
+
+#include <stdint.h>
+
+class PcmDither {
+ int32_t error[3];
+ int32_t random;
+
+public:
+ constexpr PcmDither()
+ :error{0, 0, 0}, random(0) {}
+
+ void Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end);
+
+ void Dither32To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end);
+
+private:
+ int16_t Dither24To16(int_fast32_t sample);
+ int16_t Dither32To16(int_fast32_t sample);
+};
+
+#endif
diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx
new file mode 100644
index 000000000..096c5464a
--- /dev/null
+++ b/src/pcm/PcmDsd.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmDsd.hxx"
+#include "dsd2pcm/dsd2pcm.h"
+
+#include <glib.h>
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+
+PcmDsd::PcmDsd()
+{
+ std::fill_n(dsd2pcm, G_N_ELEMENTS(dsd2pcm), nullptr);
+}
+
+PcmDsd::~PcmDsd()
+{
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i)
+ if (dsd2pcm[i] != nullptr)
+ dsd2pcm_destroy(dsd2pcm[i]);
+}
+
+void
+PcmDsd::Reset()
+{
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i)
+ if (dsd2pcm[i] != nullptr)
+ dsd2pcm_reset(dsd2pcm[i]);
+}
+
+const float *
+PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(src != nullptr);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+ assert(channels <= G_N_ELEMENTS(dsd2pcm));
+
+ const unsigned num_samples = src_size;
+ const unsigned num_frames = src_size / channels;
+
+ float *dest;
+ const size_t dest_size = num_samples * sizeof(*dest);
+ *dest_size_r = dest_size;
+ dest = (float *)buffer.Get(dest_size);
+
+ for (unsigned c = 0; c < channels; ++c) {
+ if (dsd2pcm[c] == nullptr) {
+ dsd2pcm[c] = dsd2pcm_init();
+ if (dsd2pcm[c] == nullptr)
+ return nullptr;
+ }
+
+ dsd2pcm_translate(dsd2pcm[c], num_frames,
+ src + c, channels,
+ lsbfirst, dest + c, channels);
+ }
+
+ return dest;
+}
diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx
new file mode 100644
index 000000000..26ee11b13
--- /dev/null
+++ b/src/pcm/PcmDsd.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2013 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_DSD_HXX
+#define MPD_PCM_DSD_HXX
+
+#include "check.h"
+#include "PcmBuffer.hxx"
+
+#include <stdint.h>
+
+/**
+ * Wrapper for the dsd2pcm library.
+ */
+struct PcmDsd {
+ PcmBuffer buffer;
+
+ struct dsd2pcm_ctx_s *dsd2pcm[32];
+
+ PcmDsd();
+ ~PcmDsd();
+
+ void Reset();
+
+ const float *ToFloat(unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
+};
+
+#endif
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
new file mode 100644
index 000000000..2d0f33a15
--- /dev/null
+++ b/src/pcm/PcmDsdUsb.cxx
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmDsdUsb.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+constexpr
+static inline uint32_t
+pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
+{
+ return 0xff050000 | (a << 8) | b;
+}
+
+constexpr
+static inline uint32_t
+pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b)
+{
+ return 0xfffa0000 | (a << 8) | b;
+}
+
+
+const uint32_t *
+pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(audio_valid_channel_count(channels));
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+
+ const unsigned num_src_samples = src_size;
+ const unsigned num_src_frames = num_src_samples / channels;
+
+ /* this rounds down and discards the last odd frame; not
+ elegant, but good enough for now */
+ const unsigned num_frames = num_src_frames / 2;
+ const unsigned num_samples = num_frames * channels;
+
+ const size_t dest_size = num_samples * 4;
+ *dest_size_r = dest_size;
+ uint32_t *const dest0 = (uint32_t *)buffer.Get(dest_size),
+ *dest = dest0;
+
+ for (unsigned i = num_frames / 2; i > 0; --i) {
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0x05 marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0xfa marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+ }
+
+ return dest0;
+}
diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx
new file mode 100644
index 000000000..3b7121465
--- /dev/null
+++ b/src/pcm/PcmDsdUsb.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2013 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_DSD_USB_HXX
+#define MPD_PCM_DSD_USB_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+class PcmBuffer;
+
+/**
+ * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for
+ * playback over USB, according to the proposed standard by
+ * dCS and others:
+ * http://www.sonore.us/DoP_openStandard_1v1.pdf
+ */
+const uint32_t *
+pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx
new file mode 100644
index 000000000..762411f59
--- /dev/null
+++ b/src/pcm/PcmExport.cxx
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmExport.hxx"
+#include "PcmDsdUsb.hxx"
+
+extern "C" {
+#include "pcm_pack.h"
+#include "util/byte_reverse.h"
+}
+
+void
+PcmExport::Open(SampleFormat sample_format, unsigned _channels,
+ bool _dsd_usb, bool _shift8, bool _pack, bool _reverse_endian)
+{
+ assert(audio_valid_sample_format(sample_format));
+ assert(!_dsd_usb || audio_valid_channel_count(_channels));
+
+ channels = _channels;
+ dsd_usb = _dsd_usb && sample_format == SampleFormat::DSD;
+ if (dsd_usb)
+ /* after the conversion to DSD-over-USB, the DSD
+ samples are stuffed inside fake 24 bit samples */
+ sample_format = SampleFormat::S24_P32;
+
+ shift8 = _shift8 && sample_format == SampleFormat::S24_P32;
+ pack24 = _pack && sample_format == SampleFormat::S24_P32;
+
+ assert(!shift8 || !pack24);
+
+ reverse_endian = 0;
+ if (_reverse_endian) {
+ size_t sample_size = pack24
+ ? 3
+ : sample_format_size(sample_format);
+ assert(sample_size <= 0xff);
+
+ if (sample_size > 1)
+ reverse_endian = sample_size;
+ }
+}
+
+size_t
+PcmExport::GetFrameSize(const AudioFormat &audio_format) const
+{
+ if (pack24)
+ /* packed 24 bit samples (3 bytes per sample) */
+ return audio_format.channels * 3;
+
+ if (dsd_usb)
+ /* the DSD-over-USB draft says that DSD 1-bit samples
+ are enclosed within 24 bit samples, and MPD's
+ representation of 24 bit is padded to 32 bit (4
+ bytes per sample) */
+ return channels * 4;
+
+ return audio_format.GetFrameSize();
+}
+
+const void *
+PcmExport::Export(const void *data, size_t size, size_t &dest_size_r)
+{
+ if (dsd_usb)
+ data = pcm_dsd_to_usb(dsd_buffer, channels,
+ (const uint8_t *)data, size, &size);
+
+ if (pack24) {
+ assert(size % 4 == 0);
+
+ const size_t num_samples = size / 4;
+ const size_t dest_size = num_samples * 3;
+
+ const uint8_t *src8 = (const uint8_t *)data;
+ const uint8_t *src_end8 = src8 + size;
+ uint8_t *dest = (uint8_t *)pack_buffer.Get(dest_size);
+ assert(dest != NULL);
+
+ pcm_pack_24(dest, (const int32_t *)src8,
+ (const int32_t *)src_end8);
+
+ data = dest;
+ size = dest_size;
+ } else if (shift8) {
+ assert(size % 4 == 0);
+
+ const uint8_t *src8 = (const uint8_t *)data;
+ const uint8_t *src_end8 = src8 + size;
+ const uint32_t *src = (const uint32_t *)src8;
+ const uint32_t *const src_end = (const uint32_t *)src_end8;
+
+ uint32_t *dest = (uint32_t *)pack_buffer.Get(size);
+ data = dest;
+
+ while (src < src_end)
+ *dest++ = *src++ << 8;
+ }
+
+
+ if (reverse_endian > 0) {
+ assert(reverse_endian >= 2);
+
+ uint8_t *dest = (uint8_t *)reverse_buffer.Get(size);
+ assert(dest != NULL);
+
+ const uint8_t *src = (const uint8_t *)data;
+ const uint8_t *src_end = src + size;
+ reverse_bytes(dest, src, src_end, reverse_endian);
+
+ data = dest;
+ }
+
+ dest_size_r = size;
+ return data;
+}
+
+size_t
+PcmExport::CalcSourceSize(size_t size) const
+{
+ if (pack24)
+ /* 32 bit to 24 bit conversion (4 to 3 bytes) */
+ size = (size / 3) * 4;
+
+ if (dsd_usb)
+ /* DSD over USB doubles the transport size */
+ size /= 2;
+
+ return size;
+}
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
new file mode 100644
index 000000000..bd18c0534
--- /dev/null
+++ b/src/pcm/PcmExport.hxx
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2003-2013 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 PCM_EXPORT_HXX
+#define PCM_EXPORT_HXX
+
+#include "check.h"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+struct AudioFormat;
+
+/**
+ * An object that handles export of PCM samples to some instance
+ * outside of MPD. It has a few more options to tweak the binary
+ * representation which are not supported by the pcm_convert library.
+ */
+struct PcmExport {
+ /**
+ * The buffer is used to convert DSD samples to the
+ * DSD-over-USB format.
+ *
+ * @see #dsd_usb
+ */
+ PcmBuffer dsd_buffer;
+
+ /**
+ * The buffer is used to pack samples, removing padding.
+ *
+ * @see #pack24
+ */
+ PcmBuffer pack_buffer;
+
+ /**
+ * The buffer is used to reverse the byte order.
+ *
+ * @see #reverse_endian
+ */
+ PcmBuffer reverse_buffer;
+
+ /**
+ * The number of channels.
+ */
+ uint8_t channels;
+
+ /**
+ * Convert DSD to DSD-over-USB? Input format must be
+ * SampleFormat::DSD and output format must be
+ * SampleFormat::S24_P32.
+ */
+ bool dsd_usb;
+
+ /**
+ * Convert (padded) 24 bit samples to 32 bit by shifting 8
+ * bits to the left?
+ */
+ bool shift8;
+
+ /**
+ * Pack 24 bit samples?
+ */
+ bool pack24;
+
+ /**
+ * Export the samples in reverse byte order? A non-zero value
+ * means the option is enabled and represents the size of each
+ * sample (2 or bigger).
+ */
+ uint8_t reverse_endian;
+
+ /**
+ * Open the #pcm_export_state object.
+ *
+ * There is no "close" method. This function may be called multiple
+ * times to reuse the object, until pcm_export_deinit() is called.
+ *
+ * This function cannot fail.
+ *
+ * @param channels the number of channels; ignored unless dsd_usb is set
+ */
+ void Open(SampleFormat sample_format, unsigned channels,
+ bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
+
+ /**
+ * Calculate the size of one output frame.
+ */
+ gcc_pure
+ size_t GetFrameSize(const AudioFormat &audio_format) const;
+
+ /**
+ * Export a PCM buffer.
+ *
+ * @param state an initialized and open pcm_export_state object
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer (may be a pointer to the source buffer)
+ */
+ const void *Export(const void *src, size_t src_size,
+ size_t &dest_size_r);
+
+ /**
+ * Converts the number of consumed bytes from the pcm_export()
+ * destination buffer to the according number of bytes from the
+ * pcm_export() source buffer.
+ */
+ gcc_pure
+ size_t CalcSourceSize(size_t dest_size) const;
+};
+
+#endif
diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx
new file mode 100644
index 000000000..6425c7cfd
--- /dev/null
+++ b/src/pcm/PcmFormat.cxx
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2003-2011 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 "PcmFormat.hxx"
+#include "PcmDither.hxx"
+#include "PcmBuffer.hxx"
+#include "pcm_pack.h"
+#include "PcmUtils.hxx"
+
+#include <type_traits>
+
+template<typename S>
+struct DefaultSampleBits {
+ typedef decltype(*S()) T;
+ typedef typename std::remove_reference<T>::type U;
+
+ static constexpr auto value = sizeof(U) * 8;
+};
+
+static void
+pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end) {
+ *out++ = *in++ << 8;
+ }
+}
+
+static void
+pcm_convert_24_to_16(PcmDither &dither,
+ int16_t *out, const int32_t *in, const int32_t *in_end)
+{
+ dither.Dither24To16(out, in, in_end);
+}
+
+static void
+pcm_convert_32_to_16(PcmDither &dither,
+ int16_t *out, const int32_t *in, const int32_t *in_end)
+{
+ dither.Dither32To16(out, in, in_end);
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertFromFloat(S dest, const float *src, const float *end)
+{
+ typedef decltype(*S()) T;
+ typedef typename std::remove_reference<T>::type U;
+
+ const float factor = 1 << (bits - 1);
+
+ while (src != end) {
+ int sample(*src++ * factor);
+ *dest++ = PcmClamp<U, int, bits>(sample);
+ }
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertFromFloat(S dest, const float *src, size_t size)
+{
+ ConvertFromFloat<S, bits>(dest, src, pcm_end_pointer(src, size));
+}
+
+template<typename S, unsigned bits=sizeof(S)*8>
+static S *
+AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ constexpr size_t src_sample_size = sizeof(*src);
+ assert(src_size % src_sample_size == 0);
+
+ const size_t num_samples = src_size / src_sample_size;
+ *dest_size_r = num_samples * sizeof(S);
+ S *dest = (S *)buffer.Get(*dest_size_r);
+ ConvertFromFloat<S *, bits>(dest, src, src_size);
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_8_to_16(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = (int16_t *)buffer.Get(*dest_size_r);
+ pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_24p32_to_16(PcmBuffer &buffer, PcmDither &dither,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = (int16_t *)buffer.Get(*dest_size_r);
+ pcm_convert_24_to_16(dither, dest, src,
+ pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_32_to_16(PcmBuffer &buffer, PcmDither &dither,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = (int16_t *)buffer.Get(*dest_size_r);
+ pcm_convert_32_to_16(dither, dest, src,
+ pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_float_to_16(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateFromFloat<int16_t>(buffer, src, src_size, dest_size_r);
+}
+
+const int16_t *
+pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_16(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ *dest_size_r = src_size;
+ return (const int16_t *)src;
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_16(buffer, dither,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_16(buffer, dither,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_16(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+static void
+pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 16;
+}
+
+static void
+pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 8;
+}
+
+static void
+pcm_convert_32_to_24(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ >> 8;
+}
+
+static int32_t *
+pcm_allocate_8_to_24(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_16_to_24(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size * 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_32_to_24(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size, size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_float_to_24(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateFromFloat<int32_t, 24>(buffer, src, src_size,
+ dest_size_r);
+}
+
+const int32_t *
+pcm_convert_to_24(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_24(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ return pcm_allocate_16_to_24(buffer,
+ (const int16_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S24_P32:
+ *dest_size_r = src_size;
+ return (const int32_t *)src;
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_24(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_24(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+static void
+pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 24;
+}
+
+static void
+pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 16;
+}
+
+static void
+pcm_convert_24_to_32(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 8;
+}
+
+static int32_t *
+pcm_allocate_8_to_32(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_16_to_32(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size * 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_24p32_to_32(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
+ pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_float_to_32(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ /* convert to S24_P32 first */
+ int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
+ dest_size_r);
+
+ /* convert to 32 bit in-place */
+ pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
+ return dest;
+}
+
+const int32_t *
+pcm_convert_to_32(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_32(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ return pcm_allocate_16_to_32(buffer,
+ (const int16_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_32(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S32:
+ *dest_size_r = src_size;
+ return (const int32_t *)src;
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_32(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertToFloat(float *dest, S src, S end)
+{
+ constexpr float factor = 0.5 / (1 << (bits - 2));
+ while (src != end)
+ *dest++ = float(*src++) * factor;
+
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertToFloat(float *dest, S src, size_t size)
+{
+ ConvertToFloat<S, bits>(dest, src, pcm_end_pointer(src, size));
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static float *
+AllocateToFloat(PcmBuffer &buffer, S src, size_t src_size,
+ size_t *dest_size_r)
+{
+ constexpr size_t src_sample_size = sizeof(*S());
+ assert(src_size % src_sample_size == 0);
+
+ const size_t num_samples = src_size / src_sample_size;
+ *dest_size_r = num_samples * sizeof(float);
+ float *dest = (float *)buffer.Get(*dest_size_r);
+ ConvertToFloat<S, bits>(dest, src, src_size);
+ return dest;
+}
+
+static float *
+pcm_allocate_8_to_float(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_16_to_float(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_24p32_to_float(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat<decltype(src), 24>
+ (buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_32_to_float(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+const float *
+pcm_convert_to_float(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_float(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ return pcm_allocate_16_to_float(buffer,
+ (const int16_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_float(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_float(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ *dest_size_r = src_size;
+ return (const float *)src;
+ }
+
+ return NULL;
+}
diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx
new file mode 100644
index 000000000..cc44d6dd5
--- /dev/null
+++ b/src/pcm/PcmFormat.hxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2013 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_FORMAT_HXX
+#define MPD_PCM_FORMAT_HXX
+
+#include "AudioFormat.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+class PcmBuffer;
+class PcmDither;
+
+/**
+ * Converts PCM samples to 16 bit. If the source format is 24 bit,
+ * then dithering is applied.
+ *
+ * @param buffer a PcmBuffer object
+ * @param dither a pcm_dither object for 24-to-16 conversion
+ * @param bits the number of in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int16_t *
+pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 24 bit (32 bit alignment).
+ *
+ * @param buffer a PcmBuffer object
+ * @param bits the number of in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int32_t *
+pcm_convert_to_24(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 32 bit.
+ *
+ * @param buffer a PcmBuffer object
+ * @param bits the number of in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const int32_t *
+pcm_convert_to_32(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 32 bit floating point.
+ *
+ * @param buffer a PcmBuffer object
+ * @param bits the number of in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const float *
+pcm_convert_to_float(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx
new file mode 100644
index 000000000..f4a02fc47
--- /dev/null
+++ b/src/pcm/PcmMix.cxx
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2003-2011 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 "PcmMix.hxx"
+#include "PcmVolume.hxx"
+#include "PcmUtils.hxx"
+#include "AudioFormat.hxx"
+
+#include <math.h>
+
+template<typename T, typename U, unsigned bits>
+static T
+PcmAddVolume(T _a, T _b, int volume1, int volume2)
+{
+ U a(_a), b(_b);
+
+ U c = ((a * volume1 + b * volume2) +
+ pcm_volume_dither() + PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+
+ return PcmClamp<T, U, bits>(c);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2)
+{
+ for (size_t i = 0; i != n; ++i)
+ a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2)
+{
+ constexpr size_t sample_size = sizeof(T);
+ assert(size % sample_size == 0);
+
+ PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size,
+ volume1, volume2);
+}
+
+static void
+pcm_add_vol_float(float *buffer1, const float *buffer2,
+ unsigned num_samples, float volume1, float volume2)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+
+ sample1 = (sample1 * volume1 + sample2 * volume2);
+ *buffer1++ = sample1;
+ --num_samples;
+ }
+}
+
+static bool
+pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
+ int vol1, int vol2,
+ SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ /* not implemented */
+ return false;
+
+ case SampleFormat::S8:
+ PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S16:
+ PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S24_P32:
+ PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S32:
+ PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::FLOAT:
+ pcm_add_vol_float((float *)buffer1, (const float *)buffer2,
+ size / 4,
+ pcm_volume_to_float(vol1),
+ pcm_volume_to_float(vol2));
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+template<typename T, typename U, unsigned bits>
+static T
+PcmAdd(T _a, T _b)
+{
+ U a(_a), b(_b);
+ return PcmClamp<T, U, bits>(a + b);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAdd(T *a, const T *b, unsigned n)
+{
+ for (size_t i = 0; i != n; ++i)
+ a[i] = PcmAdd<T, U, bits>(a[i], b[i]);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVoid(void *a, const void *b, size_t size)
+{
+ constexpr size_t sample_size = sizeof(T);
+ assert(size % sample_size == 0);
+
+ PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size);
+}
+
+static void
+pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+ *buffer1++ = sample1 + sample2;
+ --num_samples;
+ }
+}
+
+static bool
+pcm_add(void *buffer1, const void *buffer2, size_t size,
+ SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ /* not implemented */
+ return false;
+
+ case SampleFormat::S8:
+ PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S16:
+ PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S24_P32:
+ PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S32:
+ PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::FLOAT:
+ pcm_add_float((float *)buffer1, (const float *)buffer2,
+ size / 4);
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+pcm_mix(void *buffer1, const void *buffer2, size_t size,
+ SampleFormat format, float portion1)
+{
+ int vol1;
+ float s;
+
+ /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN
+ * to signal mixing rather than fading */
+ if (isnan(portion1))
+ return pcm_add(buffer1, buffer2, size, format);
+
+ s = sin(M_PI_2 * portion1);
+ s *= s;
+
+ vol1 = s * PCM_VOLUME_1 + 0.5;
+ vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
+
+ return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+}
diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx
new file mode 100644
index 000000000..b50a163fd
--- /dev/null
+++ b/src/pcm/PcmMix.hxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2013 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_MIX_HXX
+#define MPD_PCM_MIX_HXX
+
+#include "AudioFormat.hxx"
+#include "gcc.h"
+
+#include <stddef.h>
+
+/*
+ * Linearly mixes two PCM buffers. Both must have the same length and
+ * the same audio format. The formula is:
+ *
+ * s1 := s1 * portion1 + s2 * (1 - portion1)
+ *
+ * @param buffer1 the first PCM buffer, and the destination buffer
+ * @param buffer2 the second PCM buffer
+ * @param size the size of both buffers in bytes
+ * @param format the sample format of both buffers
+ * @param portion1 a number between 0.0 and 1.0 specifying the portion
+ * of the first buffer in the mix; portion2 = (1.0 - portion1). The value
+ * NaN is used by the MixRamp code to specify that simple addition is required.
+ *
+ * @return true on success, false if the format is not supported
+ */
+gcc_warn_unused_result
+bool
+pcm_mix(void *buffer1, const void *buffer2, size_t size,
+ SampleFormat format, float portion1);
+
+#endif
diff --git a/src/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx
new file mode 100644
index 000000000..0c823250d
--- /dev/null
+++ b/src/pcm/PcmPrng.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2013 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_PRNG_HXX
+#define MPD_PCM_PRNG_HXX
+
+/**
+ * A very simple linear congruential PRNG. It's good enough for PCM
+ * dithering.
+ */
+static unsigned long
+pcm_prng(unsigned long state)
+{
+ return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
+}
+
+#endif
diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx
new file mode 100644
index 000000000..e2ce095d1
--- /dev/null
+++ b/src/pcm/PcmResample.cxx
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
+
+#ifdef HAVE_LIBSAMPLERATE
+#include "ConfigGlobal.hxx"
+#include "ConfigOption.hxx"
+#endif
+
+#include <string.h>
+
+#ifdef HAVE_LIBSAMPLERATE
+static bool lsr_enabled;
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+static bool
+pcm_resample_lsr_enabled(void)
+{
+ return lsr_enabled;
+}
+#endif
+
+bool
+pcm_resample_global_init(Error &error)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ const char *converter =
+ config_get_string(CONF_SAMPLERATE_CONVERTER, "");
+
+ lsr_enabled = strcmp(converter, "internal") != 0;
+ if (lsr_enabled)
+ return pcm_resample_lsr_global_init(converter, error);
+ else
+ return true;
+#else
+ (void)error;
+ return true;
+#endif
+}
+
+PcmResampler::PcmResampler()
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ pcm_resample_lsr_init(this);
+#endif
+}
+
+PcmResampler::~PcmResampler()
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ pcm_resample_lsr_deinit(this);
+#endif
+}
+
+void
+PcmResampler::Reset()
+{
+#ifdef HAVE_LIBSAMPLERATE
+ pcm_resample_lsr_reset(this);
+#endif
+}
+
+const float *
+PcmResampler::ResampleFloat(unsigned channels, unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ return pcm_resample_lsr_float(this, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
+#endif
+
+ /* sizeof(float)==sizeof(int32_t); the fallback resampler does
+ not do any math on the sample values, so this hack is
+ possible: */
+ return (const float *)
+ pcm_resample_fallback_32(this, channels,
+ src_rate, (const int32_t *)src_buffer,
+ src_size,
+ dest_rate, dest_size_r);
+}
+
+const int16_t *
+PcmResampler::Resample16(unsigned channels,
+ unsigned src_rate, const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ return pcm_resample_lsr_16(this, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
+#endif
+
+ return pcm_resample_fallback_16(this, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r);
+}
+
+const int32_t *
+PcmResampler::Resample32(unsigned channels, unsigned src_rate,
+ const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ return pcm_resample_lsr_32(this, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
+#endif
+
+ return pcm_resample_fallback_32(this, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r);
+}
diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx
new file mode 100644
index 000000000..8a740744a
--- /dev/null
+++ b/src/pcm/PcmResample.hxx
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2003-2013 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_RESAMPLE_HXX
+#define MPD_PCM_RESAMPLE_HXX
+
+#include "check.h"
+#include "PcmBuffer.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#endif
+
+class Error;
+
+/**
+ * This object is statically allocated (within another struct), and
+ * holds buffer allocations and the state for the resampler.
+ */
+struct PcmResampler {
+#ifdef HAVE_LIBSAMPLERATE
+ SRC_STATE *state;
+ SRC_DATA data;
+
+ PcmBuffer in, out;
+
+ struct {
+ unsigned src_rate;
+ unsigned dest_rate;
+ unsigned channels;
+ } prev;
+
+ int error;
+#endif
+
+ PcmBuffer buffer;
+
+ PcmResampler();
+ ~PcmResampler();
+
+ /**
+ * @see pcm_convert_reset()
+ */
+ void Reset();
+
+ /**
+ * Resamples 32 bit float data.
+ *
+ * @param channels the number of channels
+ * @param src_rate the source sample rate
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_rate the requested destination sample rate
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+ const float *ResampleFloat(unsigned channels, unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r);
+
+ /**
+ * Resamples 16 bit PCM data.
+ *
+ * @param channels the number of channels
+ * @param src_rate the source sample rate
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_rate the requested destination sample rate
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+ const int16_t *Resample16(unsigned channels, unsigned src_rate,
+ const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r);
+
+ /**
+ * Resamples 32 bit PCM data.
+ *
+ * @param channels the number of channels
+ * @param src_rate the source sample rate
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_rate the requested destination sample rate
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+ const int32_t *Resample32(unsigned channels, unsigned src_rate,
+ const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r);
+
+ /**
+ * Resamples 24 bit PCM data.
+ *
+ * @param channels the number of channels
+ * @param src_rate the source sample rate
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_rate the requested destination sample rate
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+ const int32_t *Resample24(unsigned channels, unsigned src_rate,
+ const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error_r)
+ {
+ /* reuse the 32 bit code - the resampler code doesn't care if
+ the upper 8 bits are actually used */
+ return Resample32(channels, src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r, error_r);
+ }
+};
+
+bool
+pcm_resample_global_init(Error &error);
+
+#endif
diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx
new file mode 100644
index 000000000..a62cd64f7
--- /dev/null
+++ b/src/pcm/PcmResampleFallback.cxx
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
+
+#include <assert.h>
+
+/* resampling code blatantly ripped from ESD */
+const int16_t *
+pcm_resample_fallback_16(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate,
+ size_t *dest_size_r)
+{
+ unsigned dest_pos = 0;
+ unsigned src_frames = src_size / channels / sizeof(*src_buffer);
+ unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ unsigned dest_samples = dest_frames * channels;
+ size_t dest_size = dest_samples * sizeof(*src_buffer);
+ int16_t *dest_buffer = (int16_t *)state->buffer.Get(dest_size);
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ switch (channels) {
+ case 1:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+
+ dest_buffer[dest_pos++] = src_buffer[src_pos];
+ }
+ break;
+ case 2:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+ src_pos &= ~1;
+
+ dest_buffer[dest_pos++] = src_buffer[src_pos];
+ dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
+ }
+ break;
+ }
+
+ *dest_size_r = dest_size;
+ return dest_buffer;
+}
+
+const int32_t *
+pcm_resample_fallback_32(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate,
+ size_t *dest_size_r)
+{
+ unsigned dest_pos = 0;
+ unsigned src_frames = src_size / channels / sizeof(*src_buffer);
+ unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ unsigned dest_samples = dest_frames * channels;
+ size_t dest_size = dest_samples * sizeof(*src_buffer);
+ int32_t *dest_buffer = (int32_t *)state->buffer.Get(dest_size);
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ switch (channels) {
+ case 1:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+
+ dest_buffer[dest_pos++] = src_buffer[src_pos];
+ }
+ break;
+ case 2:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+ src_pos &= ~1;
+
+ dest_buffer[dest_pos++] = src_buffer[src_pos];
+ dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
+ }
+ break;
+ }
+
+ *dest_size_r = dest_size;
+ return dest_buffer;
+}
diff --git a/src/pcm/PcmResampleInternal.hxx b/src/pcm/PcmResampleInternal.hxx
new file mode 100644
index 000000000..59bb2f5df
--- /dev/null
+++ b/src/pcm/PcmResampleInternal.hxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2003-2013 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.
+ */
+
+/** \file
+ *
+ * Internal declarations for the pcm_resample library. The "internal"
+ * resampler is called "fallback" in the MPD source, so the file name
+ * of this header is somewhat unrelated to it.
+ */
+
+#ifndef MPD_PCM_RESAMPLE_INTERNAL_HXX
+#define MPD_PCM_RESAMPLE_INTERNAL_HXX
+
+#include "check.h"
+#include "PcmResample.hxx"
+
+#ifdef HAVE_LIBSAMPLERATE
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error);
+
+void
+pcm_resample_lsr_init(PcmResampler *state);
+
+void
+pcm_resample_lsr_deinit(PcmResampler *state);
+
+void
+pcm_resample_lsr_reset(PcmResampler *state);
+
+const float *
+pcm_resample_lsr_float(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error);
+
+const int16_t *
+pcm_resample_lsr_16(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error);
+
+const int32_t *
+pcm_resample_lsr_32(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int32_t *src_buffer,
+ size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error);
+
+#endif
+
+const int16_t *
+pcm_resample_fallback_16(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate,
+ size_t *dest_size_r);
+
+const int32_t *
+pcm_resample_fallback_32(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int32_t *src_buffer,
+ size_t src_size,
+ unsigned dest_rate,
+ size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx
new file mode 100644
index 000000000..2ffe4b8c4
--- /dev/null
+++ b/src/pcm/PcmResampleLibsamplerate.cxx
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int lsr_converter = SRC_SINC_FASTEST;
+
+static constexpr Domain libsamplerate_domain("libsamplerate");
+
+static bool
+lsr_parse_converter(const char *s)
+{
+ assert(s != nullptr);
+
+ if (*s == 0)
+ return true;
+
+ char *endptr;
+ long l = strtol(s, &endptr, 10);
+ if (*endptr == 0 && src_get_name(l) != nullptr) {
+ lsr_converter = l;
+ return true;
+ }
+
+ size_t length = strlen(s);
+ for (int i = 0;; ++i) {
+ const char *name = src_get_name(i);
+ if (name == nullptr)
+ break;
+
+ if (g_ascii_strncasecmp(s, name, length) == 0) {
+ lsr_converter = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error)
+{
+ if (!lsr_parse_converter(converter)) {
+ error.Format(libsamplerate_domain,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ FormatDebug(libsamplerate_domain,
+ "libsamplerate converter '%s'",
+ src_get_name(lsr_converter));
+
+ return true;
+}
+
+void
+pcm_resample_lsr_init(PcmResampler *state)
+{
+ state->state = nullptr;
+ memset(&state->data, 0, sizeof(state->data));
+ memset(&state->prev, 0, sizeof(state->prev));
+ state->error = 0;
+}
+
+void
+pcm_resample_lsr_deinit(PcmResampler *state)
+{
+ if (state->state != nullptr)
+ state->state = src_delete(state->state);
+}
+
+void
+pcm_resample_lsr_reset(PcmResampler *state)
+{
+ if (state->state != nullptr)
+ src_reset(state->state);
+}
+
+static bool
+pcm_resample_set(PcmResampler *state,
+ unsigned channels, unsigned src_rate, unsigned dest_rate,
+ Error &error_r)
+{
+ /* (re)set the state/ratio if the in or out format changed */
+ if (channels == state->prev.channels &&
+ src_rate == state->prev.src_rate &&
+ dest_rate == state->prev.dest_rate)
+ return true;
+
+ state->error = 0;
+ state->prev.channels = channels;
+ state->prev.src_rate = src_rate;
+ state->prev.dest_rate = dest_rate;
+
+ if (state->state)
+ state->state = src_delete(state->state);
+
+ int error;
+ state->state = src_new(lsr_converter, channels, &error);
+ if (!state->state) {
+ error_r.Format(libsamplerate_domain, error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(error));
+ return false;
+ }
+
+ SRC_DATA *data = &state->data;
+ data->src_ratio = (double)dest_rate / (double)src_rate;
+ FormatDebug(libsamplerate_domain,
+ "setting samplerate conversion ratio to %.2lf",
+ data->src_ratio);
+ src_set_ratio(state->state, data->src_ratio);
+
+ return true;
+}
+
+static bool
+lsr_process(PcmResampler *state, Error &error)
+{
+ if (state->error == 0)
+ state->error = src_process(state->state, &state->data);
+ if (state->error) {
+ error.Format(libsamplerate_domain, state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
+ return false;
+ }
+
+ return true;
+}
+
+const float *
+pcm_resample_lsr_float(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate, error))
+ return nullptr;
+
+ data->input_frames = src_size / sizeof(*src_buffer) / channels;
+ data->data_in = const_cast<float *>(src_buffer);
+
+ data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = data->output_frames * sizeof(float) * channels;
+ data->data_out = (float *)state->out.Get(data_out_size);
+
+ if (!lsr_process(state, error))
+ return nullptr;
+
+ *dest_size_r = data->output_frames_gen *
+ sizeof(*data->data_out) * channels;
+ return data->data_out;
+}
+
+const int16_t *
+pcm_resample_lsr_16(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate,
+ error))
+ return nullptr;
+
+ data->input_frames = src_size / sizeof(*src_buffer) / channels;
+ size_t data_in_size = data->input_frames * sizeof(float) * channels;
+ data->data_in = (float *)state->in.Get(data_in_size);
+
+ data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = data->output_frames * sizeof(float) * channels;
+ data->data_out = (float *)state->out.Get(data_out_size);
+
+ src_short_to_float_array(src_buffer, data->data_in,
+ data->input_frames * channels);
+
+ if (!lsr_process(state, error))
+ return nullptr;
+
+ int16_t *dest_buffer;
+ *dest_size_r = data->output_frames_gen *
+ sizeof(*dest_buffer) * channels;
+ dest_buffer = (int16_t *)state->buffer.Get(*dest_size_r);
+ src_float_to_short_array(data->data_out, dest_buffer,
+ data->output_frames_gen * channels);
+
+ return dest_buffer;
+}
+
+#ifdef HAVE_LIBSAMPLERATE_NOINT
+
+/* libsamplerate introduced these functions in v0.1.3 */
+
+static void
+src_int_to_float_array(const int *in, float *out, int len)
+{
+ while (len-- > 0)
+ *out++ = *in++ / (float)(1 << (24 - 1));
+}
+
+static void
+src_float_to_int_array (const float *in, int *out, int len)
+{
+ while (len-- > 0)
+ *out++ = *in++ * (float)(1 << (24 - 1));
+}
+
+#endif
+
+const int32_t *
+pcm_resample_lsr_32(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ Error &error)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate,
+ error))
+ return nullptr;
+
+ data->input_frames = src_size / sizeof(*src_buffer) / channels;
+ size_t data_in_size = data->input_frames * sizeof(float) * channels;
+ data->data_in = (float *)state->in.Get(data_in_size);
+
+ data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = data->output_frames * sizeof(float) * channels;
+ data->data_out = (float *)state->out.Get(data_out_size);
+
+ src_int_to_float_array(src_buffer, data->data_in,
+ data->input_frames * channels);
+
+ if (!lsr_process(state, error))
+ return nullptr;
+
+ int32_t *dest_buffer;
+ *dest_size_r = data->output_frames_gen *
+ sizeof(*dest_buffer) * channels;
+ dest_buffer = (int32_t *)state->buffer.Get(*dest_size_r);
+ src_float_to_int_array(data->data_out, dest_buffer,
+ data->output_frames_gen * channels);
+
+ return dest_buffer;
+}
diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx
new file mode 100644
index 000000000..d77c4194a
--- /dev/null
+++ b/src/pcm/PcmUtils.hxx
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2011 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_UTILS_H
+#define MPD_PCM_UTILS_H
+
+#include "gcc.h"
+
+#include <limits>
+
+#include <stdint.h>
+
+/**
+ * Add a byte count to the specified pointer. This is a utility
+ * function to convert a source pointer and a byte count to an "end"
+ * pointer for use in loops.
+ */
+template<typename T>
+static inline const T *
+pcm_end_pointer(const T *p, size_t size)
+{
+ return (const T *)((const uint8_t *)p + size);
+}
+
+/**
+ * Check if the value is within the range of the provided bit size,
+ * and caps it if necessary.
+ */
+template<typename T, typename U, unsigned bits>
+gcc_const
+static inline T
+PcmClamp(U x)
+{
+ constexpr U MIN_VALUE = -(U(1) << (bits - 1));
+ constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1;
+
+ typedef std::numeric_limits<T> limits;
+ static_assert(MIN_VALUE >= limits::min(), "out of range");
+ static_assert(MAX_VALUE <= limits::max(), "out of range");
+
+ if (gcc_unlikely(x < MIN_VALUE))
+ return T(MIN_VALUE);
+
+ if (gcc_unlikely(x > MAX_VALUE))
+ return T(MAX_VALUE);
+
+ return T(x);
+}
+
+#endif
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
new file mode 100644
index 000000000..05ab73c68
--- /dev/null
+++ b/src/pcm/PcmVolume.cxx
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2003-2011 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 "PcmVolume.hxx"
+#include "PcmUtils.hxx"
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+#include <stdint.h>
+#include <string.h>
+
+static void
+pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
+{
+ while (buffer < end) {
+ int32_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+
+ *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample);
+ }
+}
+
+static void
+pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume)
+{
+ while (buffer < end) {
+ int32_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+
+ *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample);
+ }
+}
+
+#ifdef __i386__
+/**
+ * Optimized volume function for i386. Use the EDX:EAX 2*32 bit
+ * multiplication result instead of emulating 64 bit multiplication.
+ */
+static inline int32_t
+pcm_volume_sample_24(int32_t sample, int32_t volume, gcc_unused int32_t dither)
+{
+ int32_t result;
+
+ asm(/* edx:eax = sample * volume */
+ "imul %2\n"
+
+ /* "add %3, %1\n" dithering disabled for now, because we
+ have no overflow check - is dithering really important
+ here? */
+
+ /* eax = edx:eax / PCM_VOLUME_1 */
+ "sal $22, %%edx\n"
+ "shr $10, %1\n"
+ "or %%edx, %1\n"
+
+ : "=a"(result)
+ : "0"(sample), "r"(volume) /* , "r"(dither) */
+ : "edx"
+ );
+
+ return result;
+}
+#endif
+
+static void
+pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume)
+{
+ while (buffer < end) {
+#ifdef __i386__
+ /* assembly version for i386 */
+ int32_t sample = *buffer;
+
+ sample = pcm_volume_sample_24(sample, volume,
+ pcm_volume_dither());
+#else
+ /* portable version */
+ int64_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+#endif
+ *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample);
+ }
+}
+
+static void
+pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume)
+{
+ while (buffer < end) {
+#ifdef __i386__
+ /* assembly version for i386 */
+ int32_t sample = *buffer;
+
+ *buffer++ = pcm_volume_sample_24(sample, volume, 0);
+#else
+ /* portable version */
+ int64_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+ *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample);
+#endif
+ }
+}
+
+static void
+pcm_volume_change_float(float *buffer, const float *end, float volume)
+{
+ while (buffer < end) {
+ float sample = *buffer;
+ sample *= volume;
+ *buffer++ = sample;
+ }
+}
+
+bool
+pcm_volume(void *buffer, size_t length,
+ SampleFormat format,
+ int volume)
+{
+ if (volume == PCM_VOLUME_1)
+ return true;
+
+ if (volume <= 0) {
+ memset(buffer, 0, length);
+ return true;
+ }
+
+ const void *end = pcm_end_pointer(buffer, length);
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ /* not implemented */
+ return false;
+
+ case SampleFormat::S8:
+ pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end,
+ volume);
+ return true;
+
+ case SampleFormat::S16:
+ pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end,
+ volume);
+ return true;
+
+ case SampleFormat::S24_P32:
+ pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end,
+ volume);
+ return true;
+
+ case SampleFormat::S32:
+ pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end,
+ volume);
+ return true;
+
+ case SampleFormat::FLOAT:
+ pcm_volume_change_float((float *)buffer, (const float *)end,
+ pcm_volume_to_float(volume));
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx
new file mode 100644
index 000000000..8cd82acf7
--- /dev/null
+++ b/src/pcm/PcmVolume.hxx
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2003-2013 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_VOLUME_HXX
+#define MPD_PCM_VOLUME_HXX
+
+#include "PcmPrng.hxx"
+#include "AudioFormat.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum {
+ /** this value means "100% volume" */
+ PCM_VOLUME_1 = 1024,
+};
+
+struct AudioFormat;
+
+/**
+ * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
+ * integer volume value (1000 = 100%).
+ */
+static inline int
+pcm_float_to_volume(float volume)
+{
+ return volume * PCM_VOLUME_1 + 0.5;
+}
+
+static inline float
+pcm_volume_to_float(int volume)
+{
+ return (float)volume / (float)PCM_VOLUME_1;
+}
+
+/**
+ * Returns the next volume dithering number, between -511 and +511.
+ * This number is taken from a global PRNG, see pcm_prng().
+ */
+static inline int
+pcm_volume_dither(void)
+{
+ static unsigned long state;
+ uint32_t r;
+
+ r = state = pcm_prng(state);
+
+ return (r & 511) - ((r >> 9) & 511);
+}
+
+/**
+ * Adjust the volume of the specified PCM buffer.
+ *
+ * @param buffer the PCM buffer
+ * @param length the length of the PCM buffer
+ * @param format the sample format of the PCM buffer
+ * @param volume the volume between 0 and #PCM_VOLUME_1
+ * @return true on success, false if the audio format is not supported
+ */
+bool
+pcm_volume(void *buffer, size_t length,
+ SampleFormat format,
+ int volume);
+
+#endif
diff --git a/src/pcm/dsd2pcm/dsd2pcm.c b/src/pcm/dsd2pcm/dsd2pcm.c
new file mode 100644
index 000000000..4c7640853
--- /dev/null
+++ b/src/pcm/dsd2pcm/dsd2pcm.c
@@ -0,0 +1,184 @@
+#include "util/bit_reverse.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "dsd2pcm.h"
+
+#define HTAPS 48 /* number of FIR constants */
+#define FIFOSIZE 16 /* must be a power of two */
+#define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */
+#define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */
+
+#if FIFOSIZE*8 < HTAPS*2
+#error "FIFOSIZE too small"
+#endif
+
+/*
+ * Properties of this 96-tap lowpass filter when applied on a signal
+ * with sampling rate of 44100*64 Hz:
+ *
+ * () has a delay of 17 microseconds.
+ *
+ * () flat response up to 48 kHz
+ *
+ * () if you downsample afterwards by a factor of 8, the
+ * spectrum below 70 kHz is practically alias-free.
+ *
+ * () stopband rejection is about 160 dB
+ *
+ * The coefficient tables ("ctables") take only 6 Kibi Bytes and
+ * should fit into a modern processor's fast cache.
+ */
+
+/*
+ * The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter
+ */
+static const double htaps[HTAPS] = {
+ 0.09950731974056658,
+ 0.09562845727714668,
+ 0.08819647126516944,
+ 0.07782552527068175,
+ 0.06534876523171299,
+ 0.05172629311427257,
+ 0.0379429484910187,
+ 0.02490921351762261,
+ 0.0133774746265897,
+ 0.003883043418804416,
+ -0.003284703416210726,
+ -0.008080250212687497,
+ -0.01067241812471033,
+ -0.01139427235000863,
+ -0.0106813877974587,
+ -0.009007905078766049,
+ -0.006828859761015335,
+ -0.004535184322001496,
+ -0.002425035959059578,
+ -0.0006922187080790708,
+ 0.0005700762133516592,
+ 0.001353838005269448,
+ 0.001713709169690937,
+ 0.001742046839472948,
+ 0.001545601648013235,
+ 0.001226696225277855,
+ 0.0008704322683580222,
+ 0.0005381636200535649,
+ 0.000266446345425276,
+ 7.002968738383528e-05,
+ -5.279407053811266e-05,
+ -0.0001140625650874684,
+ -0.0001304796361231895,
+ -0.0001189970287491285,
+ -9.396247155265073e-05,
+ -6.577634378272832e-05,
+ -4.07492895872535e-05,
+ -2.17407957554587e-05,
+ -9.163058931391722e-06,
+ -2.017460145032201e-06,
+ 1.249721855219005e-06,
+ 2.166655190537392e-06,
+ 1.930520892991082e-06,
+ 1.319400334374195e-06,
+ 7.410039764949091e-07,
+ 3.423230509967409e-07,
+ 1.244182214744588e-07,
+ 3.130441005359396e-08
+};
+
+static float ctables[CTABLES][256];
+static int precalculated = 0;
+
+static void precalc(void)
+{
+ int t, e, m, k;
+ double acc;
+ if (precalculated) return;
+ for (t=0; t<CTABLES; ++t) {
+ k = HTAPS - t*8;
+ if (k>8) k=8;
+ for (e=0; e<256; ++e) {
+ acc = 0.0;
+ for (m=0; m<k; ++m) {
+ acc += (((e >> (7-m)) & 1)*2-1) * htaps[t*8+m];
+ }
+ ctables[CTABLES-1-t][e] = (float)acc;
+ }
+ }
+ precalculated = 1;
+}
+
+struct dsd2pcm_ctx_s
+{
+ unsigned char fifo[FIFOSIZE];
+ unsigned fifopos;
+};
+
+extern dsd2pcm_ctx* dsd2pcm_init(void)
+{
+ dsd2pcm_ctx* ptr;
+ if (!precalculated) precalc();
+ ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
+ if (ptr) dsd2pcm_reset(ptr);
+ return ptr;
+}
+
+extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr)
+{
+ free(ptr);
+}
+
+extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr)
+{
+ dsd2pcm_ctx* p2;
+ p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
+ if (p2) {
+ memcpy(p2,ptr,sizeof(dsd2pcm_ctx));
+ }
+ return p2;
+}
+
+extern void dsd2pcm_reset(dsd2pcm_ctx* ptr)
+{
+ int i;
+ for (i=0; i<FIFOSIZE; ++i)
+ ptr->fifo[i] = 0x69; /* my favorite silence pattern */
+ ptr->fifopos = 0;
+ /* 0x69 = 01101001
+ * This pattern "on repeat" makes a low energy 352.8 kHz tone
+ * and a high energy 1.0584 MHz tone which should be filtered
+ * out completely by any playback system --> silence
+ */
+}
+
+extern void dsd2pcm_translate(
+ dsd2pcm_ctx* ptr,
+ size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ int lsbf,
+ float *dst, ptrdiff_t dst_stride)
+{
+ unsigned ffp;
+ unsigned i;
+ unsigned bite1, bite2;
+ unsigned char* p;
+ double acc;
+ ffp = ptr->fifopos;
+ lsbf = lsbf ? 1 : 0;
+ while (samples-- > 0) {
+ bite1 = *src & 0xFFu;
+ if (lsbf) bite1 = bit_reverse(bite1);
+ ptr->fifo[ffp] = bite1; src += src_stride;
+ p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK);
+ *p = bit_reverse(*p);
+ acc = 0;
+ for (i=0; i<CTABLES; ++i) {
+ bite1 = ptr->fifo[(ffp -i) & FIFOMASK] & 0xFF;
+ bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF;
+ acc += ctables[i][bite1] + ctables[i][bite2];
+ }
+ *dst = (float)acc; dst += dst_stride;
+ ffp = (ffp + 1) & FIFOMASK;
+ }
+ ptr->fifopos = ffp;
+}
+
diff --git a/src/pcm/dsd2pcm/dsd2pcm.h b/src/pcm/dsd2pcm/dsd2pcm.h
new file mode 100644
index 000000000..80e8ce0cc
--- /dev/null
+++ b/src/pcm/dsd2pcm/dsd2pcm.h
@@ -0,0 +1,64 @@
+#ifndef DSD2PCM_H_INCLUDED
+#define DSD2PCM_H_INCLUDED
+
+#include <stddef.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dsd2pcm_ctx_s;
+
+typedef struct dsd2pcm_ctx_s dsd2pcm_ctx;
+
+/**
+ * initializes a "dsd2pcm engine" for one channel
+ * (precomputes tables and allocates memory)
+ *
+ * This is the only function that is not thread-safe in terms of the
+ * POSIX thread-safety definition because it modifies global state
+ * (lookup tables are computed during the first call)
+ */
+extern dsd2pcm_ctx* dsd2pcm_init(void);
+
+/**
+ * deinitializes a "dsd2pcm engine"
+ * (releases memory, don't forget!)
+ */
+extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx);
+
+/**
+ * clones the context and returns a pointer to the
+ * newly allocated copy
+ */
+extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx);
+
+/**
+ * resets the internal state for a fresh new stream
+ */
+extern void dsd2pcm_reset(dsd2pcm_ctx *ctx);
+
+/**
+ * "translates" a stream of octets to a stream of floats
+ * (8:1 decimation)
+ * @param ctx -- pointer to abstract context (buffers)
+ * @param samples -- number of octets/samples to "translate"
+ * @param src -- pointer to first octet (input)
+ * @param src_stride -- src pointer increment
+ * @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst
+ * @param dst -- pointer to first float (output)
+ * @param dst_stride -- dst pointer increment
+ */
+extern void dsd2pcm_translate(dsd2pcm_ctx *ctx,
+ size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ int lsbitfirst,
+ float *dst, ptrdiff_t dst_stride);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* include guard DSD2PCM_H_INCLUDED */
+
diff --git a/src/pcm/dsd2pcm/dsd2pcm.hpp b/src/pcm/dsd2pcm/dsd2pcm.hpp
new file mode 100644
index 000000000..8f3f55197
--- /dev/null
+++ b/src/pcm/dsd2pcm/dsd2pcm.hpp
@@ -0,0 +1,39 @@
+#ifndef DSD2PCM_HXX_INCLUDED
+#define DSD2PCM_HXX_INCLUDED
+
+#include <algorithm>
+#include <stdexcept>
+#include "dsd2pcm.h"
+
+/**
+ * C++ PImpl Wrapper for the dsd2pcm C library
+ */
+
+class dxd
+{
+ dsd2pcm_ctx *handle;
+public:
+ dxd() : handle(dsd2pcm_init()) {}
+
+ dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) {}
+
+ ~dxd() { dsd2pcm_destroy(handle); }
+
+ friend void swap(dxd & a, dxd & b)
+ { std::swap(a.handle,b.handle); }
+
+ dxd& operator=(dxd x)
+ { swap(*this,x); return *this; }
+
+ void translate(size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ bool lsbitfirst,
+ float *dst, ptrdiff_t dst_stride)
+ {
+ dsd2pcm_translate(handle,samples,src,src_stride,
+ lsbitfirst,dst,dst_stride);
+ }
+};
+
+#endif // DSD2PCM_HXX_INCLUDED
+
diff --git a/src/pcm/dsd2pcm/info.txt b/src/pcm/dsd2pcm/info.txt
new file mode 100644
index 000000000..15ff29245
--- /dev/null
+++ b/src/pcm/dsd2pcm/info.txt
@@ -0,0 +1,38 @@
+You downloaded the source code for "dsd2pcm" which is a simple little
+"filter" program, that takes a DSD data stream on stdin and converts
+it to a PCM stream (352.8 kHz, either 16 or 24 bits) and writes it to
+stdout. The code is split into three modules:
+
+ (1) dsd2pcm
+
+ This is where the 8:1 decimation magic happens. It's an
+ implementation of a symmetric 96-taps FIR lowpass filter
+ optimized for DSD inputs. If you feed this converter with
+ DSD64 you get a PCM stream at 352.8 kHz and floating point
+ samples. This module is independent and can be reused.
+
+ (2) noiseshape
+
+ A module for applying generic noise shaping filters. It's
+ used for the 16-bit output mode in "main" to preserve the
+ dynamic range. This module is independent and can be reused.
+
+ (3) main.cpp (file contains the main function and handles I/O)
+
+The first two modules are pure C for maximum portability. In addition,
+there are C++ wrapper headers for convenient use of these modules in
+C++. The main application is a C++ application and makes use of the
+C++ headers to access the functionality of the first two modules.
+
+
+Under Linux this program is easily compiled by typing
+
+ g++ *.c *.cpp -O3 -o dsd2pcm
+
+provided you have GCC installed. That's why I didn't bother writing
+any makefiles. :-p
+
+
+Cheers!
+SG
+
diff --git a/src/pcm/dsd2pcm/main.cpp b/src/pcm/dsd2pcm/main.cpp
new file mode 100644
index 000000000..0b58888a8
--- /dev/null
+++ b/src/pcm/dsd2pcm/main.cpp
@@ -0,0 +1,120 @@
+#include <iostream>
+#include <vector>
+#include <cstring>
+
+#include "dsd2pcm.hpp"
+#include "noiseshape.hpp"
+
+namespace {
+
+const float my_ns_coeffs[] = {
+// b1 b2 a1 a2
+ -1.62666423, 0.79410094, 0.61367127, 0.23311013, // section 1
+ -1.44870017, 0.54196219, 0.03373857, 0.70316556 // section 2
+};
+
+const int my_ns_soscount = sizeof(my_ns_coeffs)/(sizeof(my_ns_coeffs[0])*4);
+
+inline long myround(float x)
+{
+ return static_cast<long>(x + (x>=0 ? 0.5f : -0.5f));
+}
+
+template<typename T>
+struct id { typedef T type; };
+
+template<typename T>
+inline T clip(
+ typename id<T>::type min,
+ T v,
+ typename id<T>::type max)
+{
+ if (v<min) return min;
+ if (v>max) return max;
+ return v;
+}
+
+inline void write_intel16(unsigned char * ptr, unsigned word)
+{
+ ptr[0] = word & 0xFF;
+ ptr[1] = (word >> 8) & 0xFF;
+}
+
+inline void write_intel24(unsigned char * ptr, unsigned long word)
+{
+ ptr[0] = word & 0xFF;
+ ptr[1] = (word >> 8) & 0xFF;
+ ptr[2] = (word >> 16) & 0xFF;
+}
+
+} // anonymous namespace
+
+using std::vector;
+using std::cin;
+using std::cout;
+using std::cerr;
+
+int main(int argc, char *argv[])
+{
+ const int block = 16384;
+ int channels = -1;
+ int lsbitfirst = -1;
+ int bits = -1;
+ if (argc==4) {
+ if ('1'<=argv[1][0] && argv[1][0]<='9') channels = 1 + (argv[1][0]-'1');
+ if (argv[2][0]=='m' || argv[2][0]=='M') lsbitfirst=0;
+ if (argv[2][0]=='l' || argv[2][0]=='L') lsbitfirst=1;
+ if (!strcmp(argv[3],"16")) bits = 16;
+ if (!strcmp(argv[3],"24")) bits = 24;
+ }
+ if (channels<1 || lsbitfirst<0 || bits<0) {
+ cerr << "\n"
+ "DSD2PCM filter (raw DSD64 --> 352 kHz raw PCM)\n"
+ "(c) 2009 Sebastian Gesemann\n\n"
+ "(filter as in \"reads data from stdin and writes to stdout\")\n\n"
+ "Syntax: dsd2pcm <channels> <bitorder> <bitdepth>\n"
+ "channels = 1,2,3,...,9 (number of channels in DSD stream)\n"
+ "bitorder = L (lsb first), M (msb first) (DSD stream option)\n"
+ "bitdepth = 16 or 24 (intel byte order, output option)\n\n"
+ "Note: At 16 bits/sample a noise shaper kicks in that can preserve\n"
+ "a dynamic range of 135 dB below 30 kHz.\n\n";
+ return 1;
+ }
+ int bytespersample = bits/8;
+ vector<dxd> dxds (channels);
+ vector<noise_shaper> ns;
+ if (bits==16) {
+ ns.resize(channels, noise_shaper(my_ns_soscount, my_ns_coeffs) );
+ }
+ vector<unsigned char> dsd_data (block * channels);
+ vector<float> float_data (block);
+ vector<unsigned char> pcm_data (block * channels * bytespersample);
+ char * const dsd_in = reinterpret_cast<char*>(&dsd_data[0]);
+ char * const pcm_out = reinterpret_cast<char*>(&pcm_data[0]);
+ while (cin.read(dsd_in,block * channels)) {
+ for (int c=0; c<channels; ++c) {
+ dxds[c].translate(block,&dsd_data[0]+c,channels,
+ lsbitfirst,
+ &float_data[0],1);
+ unsigned char * out = &pcm_data[0] + c*bytespersample;
+ if (bits==16) {
+ for (int s=0; s<block; ++s) {
+ float r = float_data[s]*32768 + ns[c].get();
+ long smp = clip(-32768,myround(r),32767);
+ ns[c].update( clip(-1,smp-r,1) );
+ write_intel16(out,smp);
+ out += channels*bytespersample;
+ }
+ } else {
+ for (int s=0; s<block; ++s) {
+ float r = float_data[s]*8388608;
+ long smp = clip(-8388608,myround(r),8388607);
+ write_intel24(out,smp);
+ out += channels*bytespersample;
+ }
+ }
+ }
+ cout.write(pcm_out,block*channels*bytespersample);
+ }
+}
+
diff --git a/src/pcm/dsd2pcm/noiseshape.c b/src/pcm/dsd2pcm/noiseshape.c
new file mode 100644
index 000000000..ecd2f251d
--- /dev/null
+++ b/src/pcm/dsd2pcm/noiseshape.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "noiseshape.h"
+
+extern int noise_shape_init(
+ noise_shape_ctx *ctx,
+ int sos_count,
+ const float *coeffs)
+{
+ int i;
+ ctx->sos_count = sos_count;
+ ctx->bbaa = coeffs;
+ ctx->t1 = (float*) malloc(sizeof(float)*sos_count);
+ if (!ctx->t1) goto escape1;
+ ctx->t2 = (float*) malloc(sizeof(float)*sos_count);
+ if (!ctx->t2) goto escape2;
+ for (i=0; i<sos_count; ++i) {
+ ctx->t1[i] = 0.f;
+ ctx->t2[i] = 0.f;
+ }
+ return 0;
+escape2:
+ free(ctx->t1);
+escape1:
+ return -1;
+}
+
+extern void noise_shape_destroy(
+ noise_shape_ctx *ctx)
+{
+ free(ctx->t1);
+ free(ctx->t2);
+}
+
+extern int noise_shape_clone(
+ const noise_shape_ctx *from,
+ noise_shape_ctx *to)
+{
+ to->sos_count = from->sos_count;
+ to->bbaa = from->bbaa;
+ to->t1 = (float*) malloc(sizeof(float)*to->sos_count);
+ if (!to->t1) goto error1;
+ to->t2 = (float*) malloc(sizeof(float)*to->sos_count);
+ if (!to->t2) goto error2;
+ memcpy(to->t1,from->t1,sizeof(float)*to->sos_count);
+ memcpy(to->t2,from->t2,sizeof(float)*to->sos_count);
+ return 0;
+error2:
+ free(to->t1);
+error1:
+ return -1;
+}
+
+extern float noise_shape_get(noise_shape_ctx *ctx)
+{
+ int i;
+ float acc;
+ const float *c;
+ acc = 0.0;
+ c = ctx->bbaa;
+ for (i=0; i<ctx->sos_count; ++i) {
+ float t1i = ctx->t1[i];
+ float t2i = ctx->t2[i];
+ ctx->t2[i] = acc -= t1i * c[2] + t2i * c[3];
+ acc += t1i * c[0] + t2i * c[1];
+ c += 4;
+ }
+ return acc;
+}
+
+extern void noise_shape_update(noise_shape_ctx *ctx, float qerror)
+{
+ float *p;
+ int i;
+ for (i=0; i<ctx->sos_count; ++i) {
+ ctx->t2[i] += qerror;
+ }
+ p = ctx->t1;
+ ctx->t1 = ctx->t2;
+ ctx->t2 = p;
+}
+
diff --git a/src/pcm/dsd2pcm/noiseshape.h b/src/pcm/dsd2pcm/noiseshape.h
new file mode 100644
index 000000000..6075f0d88
--- /dev/null
+++ b/src/pcm/dsd2pcm/noiseshape.h
@@ -0,0 +1,57 @@
+#ifndef NOISE_SHAPE_H_INCLUDED
+#define NOISE_SHAPE_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct noise_shape_ctx_s {
+ int sos_count; /* number of second order sections */
+ const float *bbaa; /* filter coefficients, owned by user */
+ float *t1, *t2; /* filter state, owned by ns library */
+} noise_shape_ctx;
+
+/**
+ * initializes a noise_shaper context
+ * returns an error code or 0
+ */
+extern int noise_shape_init(
+ noise_shape_ctx *ctx,
+ int sos_count,
+ const float *coeffs);
+
+/**
+ * destroys a noise_shaper context
+ */
+extern void noise_shape_destroy(
+ noise_shape_ctx *ctx);
+
+/**
+ * initializes a noise_shaper context so that its state
+ * is a copy of a given context
+ * returns an error code or 0
+ */
+extern int noise_shape_clone(
+ const noise_shape_ctx *from, noise_shape_ctx *to);
+
+/**
+ * computes the next "noise shaping sample". Note: This call
+ * alters the internal state. xxx_get and xxx_update must be
+ * called in an alternating manner.
+ */
+extern float noise_shape_get(
+ noise_shape_ctx *ctx);
+
+/**
+ * updates the noise shaper's state with the
+ * last quantization error
+ */
+extern void noise_shape_update(
+ noise_shape_ctx *ctx, float qerror);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* NOISE_SHAPE_H_INCLUDED */
+
diff --git a/src/pcm/dsd2pcm/noiseshape.hpp b/src/pcm/dsd2pcm/noiseshape.hpp
new file mode 100644
index 000000000..1fc698b36
--- /dev/null
+++ b/src/pcm/dsd2pcm/noiseshape.hpp
@@ -0,0 +1,43 @@
+#ifndef NOISE_SHAPE_HXX_INCLUDED
+#define NOISE_SHAPE_HXX_INCLUDED
+
+#include <stdexcept>
+#include "noiseshape.h"
+
+/**
+ * C++ wrapper for the noiseshape C library
+ */
+
+class noise_shaper
+{
+ noise_shape_ctx ctx;
+public:
+ noise_shaper(int sos_count, const float *bbaa)
+ {
+ noise_shape_init(&ctx, sos_count, bbaa);
+ }
+
+ noise_shaper(noise_shaper const& x)
+ {
+ noise_shape_clone(&x.ctx,&ctx);
+ }
+
+ ~noise_shaper()
+ { noise_shape_destroy(&ctx); }
+
+ noise_shaper& operator=(noise_shaper const& x)
+ {
+ if (this != &x) {
+ noise_shape_destroy(&ctx);
+ noise_shape_clone(&x.ctx,&ctx);
+ }
+ return *this;
+ }
+
+ float get() { return noise_shape_get(&ctx); }
+
+ void update(float error) { noise_shape_update(&ctx,error); }
+};
+
+#endif /* NOISE_SHAPE_HXX_INCLUDED */
+
diff --git a/src/pcm/pcm_pack.c b/src/pcm/pcm_pack.c
new file mode 100644
index 000000000..921d880c0
--- /dev/null
+++ b/src/pcm/pcm_pack.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2003-2011 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 "pcm_pack.h"
+
+#include <glib.h>
+
+static void
+pack_sample(uint8_t *dest, const int32_t *src0)
+{
+ const uint8_t *src = (const uint8_t *)src0;
+
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ ++src;
+
+ *dest++ = *src++;
+ *dest++ = *src++;
+ *dest++ = *src++;
+}
+
+void
+pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end)
+{
+ /* duplicate loop to help the compiler's optimizer (constant
+ parameter to the pack_sample() inline function) */
+
+ while (src < src_end) {
+ pack_sample(dest, src++);
+ dest += 3;
+ }
+}
+
+static void
+unpack_sample(int32_t *dest0, const uint8_t *src)
+{
+ uint8_t *dest = (uint8_t *)dest0;
+
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ /* extend the sign bit to the most fourth byte */
+ *dest++ = *src & 0x80 ? 0xff : 0x00;
+
+ *dest++ = *src++;
+ *dest++ = *src++;
+ *dest++ = *src;
+
+ if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ /* extend the sign bit to the most fourth byte */
+ *dest++ = *src & 0x80 ? 0xff : 0x00;
+}
+
+void
+pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end)
+{
+ /* duplicate loop to help the compiler's optimizer (constant
+ parameter to the unpack_sample() inline function) */
+
+ while (src < src_end) {
+ unpack_sample(dest++, src);
+ src += 3;
+ }
+}
diff --git a/src/pcm/pcm_pack.h b/src/pcm/pcm_pack.h
new file mode 100644
index 000000000..f3184b403
--- /dev/null
+++ b/src/pcm/pcm_pack.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2011 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.
+ */
+
+/** \file
+ *
+ * Library for working with packed 24 bit samples.
+ */
+
+#ifndef PCM_PACK_H
+#define PCM_PACK_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Converts padded 24 bit samples (4 bytes per sample) to packed 24
+ * bit samples (3 bytes per sample).
+ *
+ * This function can be used to convert a buffer in-place.
+ *
+ * @param dest the destination buffer (array of triples)
+ * @param src the source buffer
+ * @param num_samples the number of samples to convert
+ */
+void
+pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end);
+
+/**
+ * Converts packed 24 bit samples (3 bytes per sample) to padded 24
+ * bit samples (4 bytes per sample).
+ *
+ * @param dest the destination buffer
+ * @param src the source buffer (array of triples)
+ * @param num_samples the number of samples to convert
+ */
+void
+pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end);
+
+#endif