pcm_*: move to src/pcm/
This commit is contained in:
290
src/pcm/PcmChannels.cxx
Normal file
290
src/pcm/PcmChannels.cxx
Normal file
@@ -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 "pcm_buffer.h"
|
||||
#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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, 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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, 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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, 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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, 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;
|
||||
}
|
97
src/pcm/PcmChannels.hxx
Normal file
97
src/pcm/PcmChannels.hxx
Normal file
@@ -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>
|
||||
|
||||
struct pcm_buffer;
|
||||
|
||||
/**
|
||||
* 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(struct pcm_buffer *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(struct pcm_buffer *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(struct pcm_buffer *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(struct pcm_buffer *buffer,
|
||||
unsigned dest_channels,
|
||||
unsigned src_channels, const float *src,
|
||||
size_t src_size, size_t *dest_size_r);
|
||||
|
||||
#endif
|
326
src/pcm/PcmConvert.cxx
Normal file
326
src/pcm/PcmConvert.cxx
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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 "audio_format.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <glib.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "pcm"
|
||||
|
||||
PcmConvert::PcmConvert()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
|
||||
pcm_dsd_init(&dsd);
|
||||
pcm_resample_init(&resample);
|
||||
|
||||
pcm_buffer_init(&format_buffer);
|
||||
pcm_buffer_init(&channels_buffer);
|
||||
}
|
||||
|
||||
PcmConvert::~PcmConvert()
|
||||
{
|
||||
pcm_dsd_deinit(&dsd);
|
||||
pcm_resample_deinit(&resample);
|
||||
|
||||
pcm_buffer_deinit(&format_buffer);
|
||||
pcm_buffer_deinit(&channels_buffer);
|
||||
}
|
||||
|
||||
void
|
||||
PcmConvert::Reset()
|
||||
{
|
||||
pcm_dsd_reset(&dsd);
|
||||
pcm_resample_reset(&resample);
|
||||
}
|
||||
|
||||
inline const int16_t *
|
||||
PcmConvert::Convert16(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
const int16_t *buf;
|
||||
size_t len;
|
||||
|
||||
assert(dest_format->format == SAMPLE_FORMAT_S16);
|
||||
|
||||
buf = pcm_convert_to_16(&format_buffer, dither,
|
||||
sample_format(src_format->format),
|
||||
src_buffer, src_size,
|
||||
&len);
|
||||
if (buf == NULL) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"Conversion from %s to 16 bit is not implemented",
|
||||
sample_format_to_string(sample_format(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) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"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 = pcm_resample_16(&resample,
|
||||
dest_format->channels,
|
||||
src_format->sample_rate, buf, len,
|
||||
dest_format->sample_rate, &len,
|
||||
error_r);
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*dest_size_r = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline const int32_t *
|
||||
PcmConvert::Convert24(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
const int32_t *buf;
|
||||
size_t len;
|
||||
|
||||
assert(dest_format->format == SAMPLE_FORMAT_S24_P32);
|
||||
|
||||
buf = pcm_convert_to_24(&format_buffer,
|
||||
sample_format(src_format->format),
|
||||
src_buffer, src_size, &len);
|
||||
if (buf == NULL) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"Conversion from %s to 24 bit is not implemented",
|
||||
sample_format_to_string(sample_format(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) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"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 = pcm_resample_24(&resample,
|
||||
dest_format->channels,
|
||||
src_format->sample_rate, buf, len,
|
||||
dest_format->sample_rate, &len,
|
||||
error_r);
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*dest_size_r = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline const int32_t *
|
||||
PcmConvert::Convert32(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
const int32_t *buf;
|
||||
size_t len;
|
||||
|
||||
assert(dest_format->format == SAMPLE_FORMAT_S32);
|
||||
|
||||
buf = pcm_convert_to_32(&format_buffer,
|
||||
sample_format(src_format->format),
|
||||
src_buffer, src_size, &len);
|
||||
if (buf == NULL) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"Conversion from %s to 32 bit is not implemented",
|
||||
sample_format_to_string(sample_format(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) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"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 = pcm_resample_32(&resample,
|
||||
dest_format->channels,
|
||||
src_format->sample_rate, buf, len,
|
||||
dest_format->sample_rate, &len,
|
||||
error_r);
|
||||
if (buf == NULL)
|
||||
return buf;
|
||||
}
|
||||
|
||||
*dest_size_r = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline const float *
|
||||
PcmConvert::ConvertFloat(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
const float *buffer = (const float *)src_buffer;
|
||||
size_t size = src_size;
|
||||
|
||||
assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
|
||||
|
||||
/* convert to float now */
|
||||
|
||||
buffer = pcm_convert_to_float(&format_buffer,
|
||||
sample_format(src_format->format),
|
||||
buffer, size, &size);
|
||||
if (buffer == NULL) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"Conversion from %s to float is not implemented",
|
||||
sample_format_to_string(sample_format(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) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"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 = pcm_resample_float(&resample,
|
||||
dest_format->channels,
|
||||
src_format->sample_rate,
|
||||
buffer, size,
|
||||
dest_format->sample_rate, &size,
|
||||
error_r);
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*dest_size_r = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const void *
|
||||
PcmConvert::Convert(const audio_format *src_format,
|
||||
const void *src, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
struct audio_format float_format;
|
||||
if (src_format->format == SAMPLE_FORMAT_DSD) {
|
||||
size_t f_size;
|
||||
const float *f = pcm_dsd_to_float(&dsd,
|
||||
src_format->channels,
|
||||
false, (const uint8_t *)src,
|
||||
src_size, &f_size);
|
||||
if (f == NULL) {
|
||||
g_set_error_literal(error_r, pcm_convert_quark(), 0,
|
||||
"DSD to PCM conversion failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float_format = *src_format;
|
||||
float_format.format = SAMPLE_FORMAT_FLOAT;
|
||||
|
||||
src_format = &float_format;
|
||||
src = f;
|
||||
src_size = f_size;
|
||||
}
|
||||
|
||||
switch (sample_format(dest_format->format)) {
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return Convert16(src_format, src, src_size,
|
||||
dest_format, dest_size_r,
|
||||
error_r);
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
return Convert24(src_format, src, src_size,
|
||||
dest_format, dest_size_r,
|
||||
error_r);
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return Convert32(src_format, src, src_size,
|
||||
dest_format, dest_size_r,
|
||||
error_r);
|
||||
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
return ConvertFloat(src_format, src, src_size,
|
||||
dest_format, dest_size_r,
|
||||
error_r);
|
||||
|
||||
default:
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"PCM conversion to %s is not implemented",
|
||||
sample_format_to_string(sample_format(dest_format->format)));
|
||||
return NULL;
|
||||
}
|
||||
}
|
115
src/pcm/PcmConvert.hxx
Normal file
115
src/pcm/PcmConvert.hxx
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
extern "C" {
|
||||
#include "pcm_dsd.h"
|
||||
#include "pcm_resample.h"
|
||||
#include "pcm_buffer.h"
|
||||
}
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
struct audio_format;
|
||||
|
||||
/**
|
||||
* This object is statically allocated (within another struct), and
|
||||
* holds buffer allocations and the state for all kinds of PCM
|
||||
* conversions.
|
||||
*/
|
||||
class PcmConvert {
|
||||
struct pcm_dsd dsd;
|
||||
|
||||
struct pcm_resample_state resample;
|
||||
|
||||
PcmDither dither;
|
||||
|
||||
/** the buffer for converting the sample format */
|
||||
struct pcm_buffer format_buffer;
|
||||
|
||||
/** the buffer for converting the channel count */
|
||||
struct pcm_buffer 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(const audio_format *src_format,
|
||||
const void *src, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
private:
|
||||
const int16_t *Convert16(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
const int32_t *Convert24(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
const int32_t *Convert32(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
const float *ConvertFloat(const audio_format *src_format,
|
||||
const void *src_buffer, size_t src_size,
|
||||
const audio_format *dest_format,
|
||||
size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
};
|
||||
|
||||
static inline GQuark
|
||||
pcm_convert_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("pcm_convert");
|
||||
}
|
||||
|
||||
#endif
|
89
src/pcm/PcmDither.cxx
Normal file
89
src/pcm/PcmDither.cxx
Normal file
@@ -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++);
|
||||
}
|
44
src/pcm/PcmDither.hxx
Normal file
44
src/pcm/PcmDither.hxx
Normal file
@@ -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
|
500
src/pcm/PcmFormat.cxx
Normal file
500
src/pcm/PcmFormat.cxx
Normal file
@@ -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 "pcm_buffer.h"
|
||||
#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(pcm_buffer &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 *)pcm_buffer_get(&buffer, *dest_size_r);
|
||||
ConvertFromFloat<S *, bits>(dest, src, src_size);
|
||||
return dest;
|
||||
}
|
||||
|
||||
static int16_t *
|
||||
pcm_allocate_8_to_16(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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(struct pcm_buffer *buffer, PcmDither &dither,
|
||||
enum sample_format 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 SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return pcm_allocate_8_to_16(buffer,
|
||||
(const int8_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
*dest_size_r = src_size;
|
||||
return (const int16_t *)src;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
return pcm_allocate_24p32_to_16(buffer, dither,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return pcm_allocate_32_to_16(buffer, dither,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *buffer,
|
||||
const int32_t *src, size_t src_size, size_t *dest_size_r)
|
||||
{
|
||||
*dest_size_r = src_size;
|
||||
int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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(struct pcm_buffer *buffer,
|
||||
enum sample_format 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 SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return pcm_allocate_8_to_24(buffer,
|
||||
(const int8_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return pcm_allocate_16_to_24(buffer,
|
||||
(const int16_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
*dest_size_r = src_size;
|
||||
return (const int32_t *)src;
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return pcm_allocate_32_to_24(buffer,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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 *)pcm_buffer_get(buffer, *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(struct pcm_buffer *buffer,
|
||||
const int32_t *src, size_t src_size,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
*dest_size_r = src_size;
|
||||
int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *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(struct pcm_buffer *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(struct pcm_buffer *buffer,
|
||||
enum sample_format 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 SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return pcm_allocate_8_to_32(buffer,
|
||||
(const int8_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return pcm_allocate_16_to_32(buffer,
|
||||
(const int16_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
return pcm_allocate_24p32_to_32(buffer,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
*dest_size_r = src_size;
|
||||
return (const int32_t *)src;
|
||||
|
||||
case SAMPLE_FORMAT_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(pcm_buffer &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 *)pcm_buffer_get(&buffer, *dest_size_r);
|
||||
ConvertToFloat<S, bits>(dest, src, src_size);
|
||||
return dest;
|
||||
}
|
||||
|
||||
static float *
|
||||
pcm_allocate_8_to_float(struct pcm_buffer *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(struct pcm_buffer *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(struct pcm_buffer *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(struct pcm_buffer *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(struct pcm_buffer *buffer,
|
||||
enum sample_format src_format, const void *src,
|
||||
size_t src_size, size_t *dest_size_r)
|
||||
{
|
||||
switch (src_format) {
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return pcm_allocate_8_to_float(buffer,
|
||||
(const int8_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return pcm_allocate_16_to_float(buffer,
|
||||
(const int16_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
return pcm_allocate_24p32_to_float(buffer,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return pcm_allocate_32_to_float(buffer,
|
||||
(const int32_t *)src, src_size,
|
||||
dest_size_r);
|
||||
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
*dest_size_r = src_size;
|
||||
return (const float *)src;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
93
src/pcm/PcmFormat.hxx
Normal file
93
src/pcm/PcmFormat.hxx
Normal file
@@ -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 "audio_format.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct pcm_buffer;
|
||||
class PcmDither;
|
||||
|
||||
/**
|
||||
* Converts PCM samples to 16 bit. If the source format is 24 bit,
|
||||
* then dithering is applied.
|
||||
*
|
||||
* @param buffer a pcm_buffer 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(struct pcm_buffer *buffer, PcmDither &dither,
|
||||
enum sample_format 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 pcm_buffer 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(struct pcm_buffer *buffer,
|
||||
enum sample_format src_format, const void *src,
|
||||
size_t src_size, size_t *dest_size_r);
|
||||
|
||||
/**
|
||||
* Converts PCM samples to 32 bit.
|
||||
*
|
||||
* @param buffer a pcm_buffer 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(struct pcm_buffer *buffer,
|
||||
enum sample_format 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 pcm_buffer 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(struct pcm_buffer *buffer,
|
||||
enum sample_format src_format, const void *src,
|
||||
size_t src_size, size_t *dest_size_r);
|
||||
|
||||
#endif
|
211
src/pcm/PcmMix.cxx
Normal file
211
src/pcm/PcmMix.cxx
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 "audio_format.h"
|
||||
|
||||
#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,
|
||||
enum sample_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
/* not implemented */
|
||||
return false;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
|
||||
vol1, vol2);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
|
||||
vol1, vol2);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
|
||||
vol1, vol2);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
|
||||
vol1, vol2);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
pcm_add_vol_float((float *)buffer1, (const float *)buffer2,
|
||||
size / 4,
|
||||
pcm_volume_to_float(vol1),
|
||||
pcm_volume_to_float(vol2));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
enum sample_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
/* not implemented */
|
||||
return false;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
pcm_add_float((float *)buffer1, (const float *)buffer2,
|
||||
size / 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
pcm_mix(void *buffer1, const void *buffer2, size_t size,
|
||||
enum sample_format 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);
|
||||
}
|
49
src/pcm/PcmMix.hxx
Normal file
49
src/pcm/PcmMix.hxx
Normal file
@@ -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 "audio_format.h"
|
||||
#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,
|
||||
enum sample_format format, float portion1);
|
||||
|
||||
#endif
|
33
src/pcm/PcmPrng.hxx
Normal file
33
src/pcm/PcmPrng.hxx
Normal file
@@ -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
|
66
src/pcm/PcmUtils.hxx
Normal file
66
src/pcm/PcmUtils.hxx
Normal file
@@ -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
|
194
src/pcm/PcmVolume.cxx
Normal file
194
src/pcm/PcmVolume.cxx
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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 "audio_format.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "pcm_volume"
|
||||
|
||||
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, G_GNUC_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,
|
||||
enum sample_format 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 SAMPLE_FORMAT_UNDEFINED:
|
||||
case SAMPLE_FORMAT_DSD:
|
||||
/* not implemented */
|
||||
return false;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end,
|
||||
volume);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end,
|
||||
volume);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end,
|
||||
volume);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end,
|
||||
volume);
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
pcm_volume_change_float((float *)buffer, (const float *)end,
|
||||
pcm_volume_to_float(volume));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
82
src/pcm/PcmVolume.hxx
Normal file
82
src/pcm/PcmVolume.hxx
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
#ifndef MPD_PCM_VOLUME_HXX
|
||||
#define MPD_PCM_VOLUME_HXX
|
||||
|
||||
#include "PcmPrng.hxx"
|
||||
#include "audio_format.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
enum {
|
||||
/** this value means "100% volume" */
|
||||
PCM_VOLUME_1 = 1024,
|
||||
};
|
||||
|
||||
struct audio_format;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
enum sample_format format,
|
||||
int volume);
|
||||
|
||||
#endif
|
184
src/pcm/dsd2pcm/dsd2pcm.c
Normal file
184
src/pcm/dsd2pcm/dsd2pcm.c
Normal file
@@ -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;
|
||||
}
|
||||
|
64
src/pcm/dsd2pcm/dsd2pcm.h
Normal file
64
src/pcm/dsd2pcm/dsd2pcm.h
Normal file
@@ -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 */
|
||||
|
39
src/pcm/dsd2pcm/dsd2pcm.hpp
Normal file
39
src/pcm/dsd2pcm/dsd2pcm.hpp
Normal file
@@ -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
|
||||
|
38
src/pcm/dsd2pcm/info.txt
Normal file
38
src/pcm/dsd2pcm/info.txt
Normal file
@@ -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
|
||||
|
120
src/pcm/dsd2pcm/main.cpp
Normal file
120
src/pcm/dsd2pcm/main.cpp
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
83
src/pcm/dsd2pcm/noiseshape.c
Normal file
83
src/pcm/dsd2pcm/noiseshape.c
Normal file
@@ -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;
|
||||
}
|
||||
|
57
src/pcm/dsd2pcm/noiseshape.h
Normal file
57
src/pcm/dsd2pcm/noiseshape.h
Normal file
@@ -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 */
|
||||
|
43
src/pcm/dsd2pcm/noiseshape.hpp
Normal file
43
src/pcm/dsd2pcm/noiseshape.hpp
Normal file
@@ -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 */
|
||||
|
58
src/pcm/pcm_buffer.c
Normal file
58
src/pcm/pcm_buffer.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 "pcm_buffer.h"
|
||||
#include "poison.h"
|
||||
|
||||
/**
|
||||
* Align the specified size to the next 8k boundary.
|
||||
*/
|
||||
G_GNUC_CONST
|
||||
static size_t
|
||||
align_8k(size_t size)
|
||||
{
|
||||
return ((size - 1) | 0x1fff) + 1;
|
||||
}
|
||||
|
||||
void *
|
||||
pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
if (size == 0)
|
||||
/* never return NULL, because NULL would be assumed to
|
||||
be an error condition */
|
||||
size = 1;
|
||||
|
||||
if (buffer->size < size) {
|
||||
/* free the old buffer */
|
||||
g_free(buffer->buffer);
|
||||
|
||||
buffer->size = align_8k(size);
|
||||
buffer->buffer = g_malloc(buffer->size);
|
||||
} else {
|
||||
/* discard old buffer contents */
|
||||
poison_undefined(buffer->buffer, buffer->size);
|
||||
}
|
||||
|
||||
assert(buffer->size >= size);
|
||||
|
||||
return buffer->buffer;
|
||||
}
|
85
src/pcm/pcm_buffer.h
Normal file
85
src/pcm/pcm_buffer.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 PCM_BUFFER_H
|
||||
#define PCM_BUFFER_H
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.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.
|
||||
*/
|
||||
struct pcm_buffer {
|
||||
void *buffer;
|
||||
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the buffer, but don't allocate anything yet.
|
||||
*/
|
||||
static inline void
|
||||
pcm_buffer_init(struct pcm_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
buffer->buffer = NULL;
|
||||
buffer->size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free resources. This function may be called more than once.
|
||||
*/
|
||||
static inline void
|
||||
pcm_buffer_deinit(struct pcm_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
g_free(buffer->buffer);
|
||||
|
||||
buffer->buffer = NULL;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
G_GNUC_MALLOC
|
||||
void *
|
||||
pcm_buffer_get(struct pcm_buffer *buffer, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
85
src/pcm/pcm_dsd.c
Normal file
85
src/pcm/pcm_dsd.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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 "pcm_dsd.h"
|
||||
#include "dsd2pcm/dsd2pcm.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
pcm_dsd_init(struct pcm_dsd *dsd)
|
||||
{
|
||||
pcm_buffer_init(&dsd->buffer);
|
||||
|
||||
memset(dsd->dsd2pcm, 0, sizeof(dsd->dsd2pcm));
|
||||
}
|
||||
|
||||
void
|
||||
pcm_dsd_deinit(struct pcm_dsd *dsd)
|
||||
{
|
||||
pcm_buffer_deinit(&dsd->buffer);
|
||||
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
|
||||
if (dsd->dsd2pcm[i] != NULL)
|
||||
dsd2pcm_destroy(dsd->dsd2pcm[i]);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_dsd_reset(struct pcm_dsd *dsd)
|
||||
{
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
|
||||
if (dsd->dsd2pcm[i] != NULL)
|
||||
dsd2pcm_reset(dsd->dsd2pcm[i]);
|
||||
}
|
||||
|
||||
const float *
|
||||
pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
|
||||
const uint8_t *src, size_t src_size,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
assert(dsd != NULL);
|
||||
assert(src != NULL);
|
||||
assert(src_size > 0);
|
||||
assert(src_size % channels == 0);
|
||||
assert(channels <= G_N_ELEMENTS(dsd->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 = pcm_buffer_get(&dsd->buffer, dest_size);
|
||||
|
||||
for (unsigned c = 0; c < channels; ++c) {
|
||||
if (dsd->dsd2pcm[c] == NULL) {
|
||||
dsd->dsd2pcm[c] = dsd2pcm_init();
|
||||
if (dsd->dsd2pcm[c] == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dsd2pcm_translate(dsd->dsd2pcm[c], num_frames,
|
||||
src + c, channels,
|
||||
lsbfirst, dest + c, channels);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
52
src/pcm/pcm_dsd.h
Normal file
52
src/pcm/pcm_dsd.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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_H
|
||||
#define MPD_PCM_DSD_H
|
||||
|
||||
#include "check.h"
|
||||
#include "pcm_buffer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Wrapper for the dsd2pcm library.
|
||||
*/
|
||||
struct pcm_dsd {
|
||||
struct pcm_buffer buffer;
|
||||
|
||||
struct dsd2pcm_ctx_s *dsd2pcm[32];
|
||||
};
|
||||
|
||||
void
|
||||
pcm_dsd_init(struct pcm_dsd *dsd);
|
||||
|
||||
void
|
||||
pcm_dsd_deinit(struct pcm_dsd *dsd);
|
||||
|
||||
void
|
||||
pcm_dsd_reset(struct pcm_dsd *dsd);
|
||||
|
||||
const float *
|
||||
pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
|
||||
const uint8_t *src, size_t src_size,
|
||||
size_t *dest_size_r);
|
||||
|
||||
#endif
|
97
src/pcm/pcm_dsd_usb.c
Normal file
97
src/pcm/pcm_dsd_usb.c
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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 "pcm_dsd_usb.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "audio_format.h"
|
||||
|
||||
G_GNUC_CONST
|
||||
static inline uint32_t
|
||||
pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
|
||||
{
|
||||
return 0xff050000 | (a << 8) | b;
|
||||
}
|
||||
|
||||
G_GNUC_CONST
|
||||
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(struct pcm_buffer *buffer, unsigned channels,
|
||||
const uint8_t *src, size_t src_size,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
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 = pcm_buffer_get(buffer, 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;
|
||||
}
|
42
src/pcm/pcm_dsd_usb.h
Normal file
42
src/pcm/pcm_dsd_usb.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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_H
|
||||
#define MPD_PCM_DSD_USB_H
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct pcm_buffer;
|
||||
|
||||
/**
|
||||
* 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(struct pcm_buffer *buffer, unsigned channels,
|
||||
const uint8_t *src, size_t src_size,
|
||||
size_t *dest_size_r);
|
||||
|
||||
#endif
|
160
src/pcm/pcm_export.c
Normal file
160
src/pcm/pcm_export.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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 "pcm_export.h"
|
||||
#include "pcm_dsd_usb.h"
|
||||
#include "pcm_pack.h"
|
||||
#include "util/byte_reverse.h"
|
||||
|
||||
void
|
||||
pcm_export_init(struct pcm_export_state *state)
|
||||
{
|
||||
pcm_buffer_init(&state->reverse_buffer);
|
||||
pcm_buffer_init(&state->pack_buffer);
|
||||
pcm_buffer_init(&state->dsd_buffer);
|
||||
}
|
||||
|
||||
void pcm_export_deinit(struct pcm_export_state *state)
|
||||
{
|
||||
pcm_buffer_deinit(&state->reverse_buffer);
|
||||
pcm_buffer_deinit(&state->pack_buffer);
|
||||
pcm_buffer_deinit(&state->dsd_buffer);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_export_open(struct pcm_export_state *state,
|
||||
enum sample_format 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));
|
||||
|
||||
state->channels = channels;
|
||||
state->dsd_usb = dsd_usb && sample_format == SAMPLE_FORMAT_DSD;
|
||||
if (state->dsd_usb)
|
||||
/* after the conversion to DSD-over-USB, the DSD
|
||||
samples are stuffed inside fake 24 bit samples */
|
||||
sample_format = SAMPLE_FORMAT_S24_P32;
|
||||
|
||||
state->shift8 = shift8 && sample_format == SAMPLE_FORMAT_S24_P32;
|
||||
state->pack24 = pack && sample_format == SAMPLE_FORMAT_S24_P32;
|
||||
|
||||
assert(!state->shift8 || !state->pack24);
|
||||
|
||||
state->reverse_endian = 0;
|
||||
if (reverse_endian) {
|
||||
size_t sample_size = state->pack24
|
||||
? 3
|
||||
: sample_format_size(sample_format);
|
||||
assert(sample_size <= 0xff);
|
||||
|
||||
if (sample_size > 1)
|
||||
state->reverse_endian = sample_size;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
pcm_export_frame_size(const struct pcm_export_state *state,
|
||||
const struct audio_format *audio_format)
|
||||
{
|
||||
assert(state != NULL);
|
||||
assert(audio_format != NULL);
|
||||
|
||||
if (state->pack24)
|
||||
/* packed 24 bit samples (3 bytes per sample) */
|
||||
return audio_format->channels * 3;
|
||||
|
||||
if (state->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 audio_format->channels * 4;
|
||||
|
||||
return audio_format_frame_size(audio_format);
|
||||
}
|
||||
|
||||
const void *
|
||||
pcm_export(struct pcm_export_state *state, const void *data, size_t size,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
if (state->dsd_usb)
|
||||
data = pcm_dsd_to_usb(&state->dsd_buffer, state->channels,
|
||||
data, size, &size);
|
||||
|
||||
if (state->pack24) {
|
||||
assert(size % 4 == 0);
|
||||
|
||||
const size_t num_samples = size / 4;
|
||||
const size_t dest_size = num_samples * 3;
|
||||
|
||||
const uint8_t *src8 = data, *src_end8 = src8 + size;
|
||||
uint8_t *dest = pcm_buffer_get(&state->pack_buffer, 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 (state->shift8) {
|
||||
assert(size % 4 == 0);
|
||||
|
||||
const uint8_t *src8 = data, *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 = pcm_buffer_get(&state->pack_buffer, size);
|
||||
data = dest;
|
||||
|
||||
while (src < src_end)
|
||||
*dest++ = *src++ << 8;
|
||||
}
|
||||
|
||||
|
||||
if (state->reverse_endian > 0) {
|
||||
assert(state->reverse_endian >= 2);
|
||||
|
||||
void *dest = pcm_buffer_get(&state->reverse_buffer, size);
|
||||
assert(dest != NULL);
|
||||
|
||||
const uint8_t *src = data, *src_end = src + size;
|
||||
reverse_bytes(dest, src, src_end, state->reverse_endian);
|
||||
|
||||
data = dest;
|
||||
}
|
||||
|
||||
*dest_size_r = size;
|
||||
return data;
|
||||
}
|
||||
|
||||
size_t
|
||||
pcm_export_source_size(const struct pcm_export_state *state, size_t size)
|
||||
{
|
||||
if (state->pack24)
|
||||
/* 32 bit to 24 bit conversion (4 to 3 bytes) */
|
||||
size = (size / 3) * 4;
|
||||
|
||||
if (state->dsd_usb)
|
||||
/* DSD over USB doubles the transport size */
|
||||
size /= 2;
|
||||
|
||||
return size;
|
||||
}
|
155
src/pcm/pcm_export.h
Normal file
155
src/pcm/pcm_export.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 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_H
|
||||
#define PCM_EXPORT_H
|
||||
|
||||
#include "check.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "audio_format.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct audio_format;
|
||||
|
||||
/**
|
||||
* 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 pcm_export_state {
|
||||
/**
|
||||
* The buffer is used to convert DSD samples to the
|
||||
* DSD-over-USB format.
|
||||
*
|
||||
* @see #dsd_usb
|
||||
*/
|
||||
struct pcm_buffer dsd_buffer;
|
||||
|
||||
/**
|
||||
* The buffer is used to pack samples, removing padding.
|
||||
*
|
||||
* @see #pack24
|
||||
*/
|
||||
struct pcm_buffer pack_buffer;
|
||||
|
||||
/**
|
||||
* The buffer is used to reverse the byte order.
|
||||
*
|
||||
* @see #reverse_endian
|
||||
*/
|
||||
struct pcm_buffer reverse_buffer;
|
||||
|
||||
/**
|
||||
* The number of channels.
|
||||
*/
|
||||
uint8_t channels;
|
||||
|
||||
/**
|
||||
* Convert DSD to DSD-over-USB? Input format must be
|
||||
* SAMPLE_FORMAT_DSD and output format must be
|
||||
* SAMPLE_FORMAT_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;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize a #pcm_export_state object.
|
||||
*/
|
||||
void
|
||||
pcm_export_init(struct pcm_export_state *state);
|
||||
|
||||
/**
|
||||
* Deinitialize a #pcm_export_state object and free allocated memory.
|
||||
*/
|
||||
void
|
||||
pcm_export_deinit(struct pcm_export_state *state);
|
||||
|
||||
/**
|
||||
* 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
|
||||
pcm_export_open(struct pcm_export_state *state,
|
||||
enum sample_format sample_format, unsigned channels,
|
||||
bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
|
||||
|
||||
/**
|
||||
* Calculate the size of one output frame.
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
size_t
|
||||
pcm_export_frame_size(const struct pcm_export_state *state,
|
||||
const struct audio_format *audio_format);
|
||||
|
||||
/**
|
||||
* 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 *
|
||||
pcm_export(struct pcm_export_state *state, 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.
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
size_t
|
||||
pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
77
src/pcm/pcm_pack.c
Normal file
77
src/pcm/pcm_pack.c
Normal file
@@ -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;
|
||||
}
|
||||
}
|
55
src/pcm/pcm_pack.h
Normal file
55
src/pcm/pcm_pack.h
Normal file
@@ -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
|
159
src/pcm/pcm_resample.c
Normal file
159
src/pcm/pcm_resample.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 "pcm_resample_internal.h"
|
||||
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
#include "conf.h"
|
||||
#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(GError **error_r)
|
||||
{
|
||||
#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_r);
|
||||
else
|
||||
return true;
|
||||
#else
|
||||
(void)error_r;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void pcm_resample_init(struct pcm_resample_state *state)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
pcm_resample_lsr_init(state);
|
||||
else
|
||||
#endif
|
||||
pcm_resample_fallback_init(state);
|
||||
}
|
||||
|
||||
void pcm_resample_deinit(struct pcm_resample_state *state)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
pcm_resample_lsr_deinit(state);
|
||||
else
|
||||
#endif
|
||||
pcm_resample_fallback_deinit(state);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_resample_reset(struct pcm_resample_state *state)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
pcm_resample_lsr_reset(state);
|
||||
#else
|
||||
(void)state;
|
||||
#endif
|
||||
}
|
||||
|
||||
const float *
|
||||
pcm_resample_float(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const float *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
return pcm_resample_lsr_float(state, 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(state, channels,
|
||||
src_rate, (const int32_t *)src_buffer,
|
||||
src_size,
|
||||
dest_rate, dest_size_r);
|
||||
}
|
||||
|
||||
const int16_t *
|
||||
pcm_resample_16(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate, const int16_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
return pcm_resample_lsr_16(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r,
|
||||
error_r);
|
||||
#else
|
||||
(void)error_r;
|
||||
#endif
|
||||
|
||||
return pcm_resample_fallback_16(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r);
|
||||
}
|
||||
|
||||
const int32_t *
|
||||
pcm_resample_32(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate, const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
return pcm_resample_lsr_32(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r,
|
||||
error_r);
|
||||
#else
|
||||
(void)error_r;
|
||||
#endif
|
||||
|
||||
return pcm_resample_fallback_32(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r);
|
||||
}
|
164
src/pcm/pcm_resample.h
Normal file
164
src/pcm/pcm_resample.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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_RESAMPLE_H
|
||||
#define MPD_PCM_RESAMPLE_H
|
||||
|
||||
#include "check.h"
|
||||
#include "pcm_buffer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This object is statically allocated (within another struct), and
|
||||
* holds buffer allocations and the state for the resampler.
|
||||
*/
|
||||
struct pcm_resample_state {
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
SRC_STATE *state;
|
||||
SRC_DATA data;
|
||||
|
||||
struct pcm_buffer in, out;
|
||||
|
||||
struct {
|
||||
unsigned src_rate;
|
||||
unsigned dest_rate;
|
||||
unsigned channels;
|
||||
} prev;
|
||||
|
||||
int error;
|
||||
#endif
|
||||
|
||||
struct pcm_buffer buffer;
|
||||
};
|
||||
|
||||
bool
|
||||
pcm_resample_global_init(GError **error_r);
|
||||
|
||||
/**
|
||||
* Initializes a pcm_resample_state object.
|
||||
*/
|
||||
void pcm_resample_init(struct pcm_resample_state *state);
|
||||
|
||||
/**
|
||||
* Deinitializes a pcm_resample_state object and frees allocated
|
||||
* memory.
|
||||
*/
|
||||
void pcm_resample_deinit(struct pcm_resample_state *state);
|
||||
|
||||
/**
|
||||
* @see pcm_convert_reset()
|
||||
*/
|
||||
void
|
||||
pcm_resample_reset(struct pcm_resample_state *state);
|
||||
|
||||
/**
|
||||
* Resamples 32 bit float data.
|
||||
*
|
||||
* @param state an initialized pcm_resample_state object
|
||||
* @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 *
|
||||
pcm_resample_float(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const float *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
/**
|
||||
* Resamples 16 bit PCM data.
|
||||
*
|
||||
* @param state an initialized pcm_resample_state object
|
||||
* @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 *
|
||||
pcm_resample_16(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int16_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
/**
|
||||
* Resamples 32 bit PCM data.
|
||||
*
|
||||
* @param state an initialized pcm_resample_state object
|
||||
* @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 *
|
||||
pcm_resample_32(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
/**
|
||||
* Resamples 24 bit PCM data.
|
||||
*
|
||||
* @param state an initialized pcm_resample_state object
|
||||
* @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
|
||||
*/
|
||||
static inline const int32_t *
|
||||
pcm_resample_24(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
/* reuse the 32 bit code - the resampler code doesn't care if
|
||||
the upper 8 bits are actually used */
|
||||
return pcm_resample_32(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r, error_r);
|
||||
}
|
||||
|
||||
#endif
|
118
src/pcm/pcm_resample_fallback.c
Normal file
118
src/pcm/pcm_resample_fallback.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 "pcm_resample_internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
void
|
||||
pcm_resample_fallback_init(struct pcm_resample_state *state)
|
||||
{
|
||||
pcm_buffer_init(&state->buffer);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
|
||||
{
|
||||
pcm_buffer_deinit(&state->buffer);
|
||||
}
|
||||
|
||||
/* resampling code blatantly ripped from ESD */
|
||||
const int16_t *
|
||||
pcm_resample_fallback_16(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int16_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
unsigned src_pos, 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 = pcm_buffer_get(&state->buffer, dest_size);
|
||||
|
||||
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
while (dest_pos < dest_samples) {
|
||||
src_pos = dest_pos * src_rate / dest_rate;
|
||||
|
||||
dest_buffer[dest_pos++] = src_buffer[src_pos];
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
while (dest_pos < dest_samples) {
|
||||
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(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate,
|
||||
size_t *dest_size_r)
|
||||
{
|
||||
unsigned src_pos, 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 = pcm_buffer_get(&state->buffer, dest_size);
|
||||
|
||||
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
while (dest_pos < dest_samples) {
|
||||
src_pos = dest_pos * src_rate / dest_rate;
|
||||
|
||||
dest_buffer[dest_pos++] = src_buffer[src_pos];
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
while (dest_pos < dest_samples) {
|
||||
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;
|
||||
}
|
97
src/pcm/pcm_resample_internal.h
Normal file
97
src/pcm/pcm_resample_internal.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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_H
|
||||
#define MPD_PCM_RESAMPLE_INTERNAL_H
|
||||
|
||||
#include "check.h"
|
||||
#include "pcm_resample.h"
|
||||
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
|
||||
bool
|
||||
pcm_resample_lsr_global_init(const char *converter, GError **error_r);
|
||||
|
||||
void
|
||||
pcm_resample_lsr_init(struct pcm_resample_state *state);
|
||||
|
||||
void
|
||||
pcm_resample_lsr_deinit(struct pcm_resample_state *state);
|
||||
|
||||
void
|
||||
pcm_resample_lsr_reset(struct pcm_resample_state *state);
|
||||
|
||||
const float *
|
||||
pcm_resample_lsr_float(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const float *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
const int16_t *
|
||||
pcm_resample_lsr_16(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int16_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
const int32_t *
|
||||
pcm_resample_lsr_32(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer,
|
||||
G_GNUC_UNUSED size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r);
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
pcm_resample_fallback_init(struct pcm_resample_state *state);
|
||||
|
||||
void
|
||||
pcm_resample_fallback_deinit(struct pcm_resample_state *state);
|
||||
|
||||
const int16_t *
|
||||
pcm_resample_fallback_16(struct pcm_resample_state *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(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer,
|
||||
G_GNUC_UNUSED size_t src_size,
|
||||
unsigned dest_rate,
|
||||
size_t *dest_size_r);
|
||||
|
||||
#endif
|
312
src/pcm/pcm_resample_libsamplerate.c
Normal file
312
src/pcm/pcm_resample_libsamplerate.c
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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 "pcm_resample_internal.h"
|
||||
#include "conf.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "pcm"
|
||||
|
||||
static int lsr_converter = SRC_SINC_FASTEST;
|
||||
|
||||
static inline GQuark
|
||||
libsamplerate_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("libsamplerate");
|
||||
}
|
||||
|
||||
static bool
|
||||
lsr_parse_converter(const char *s)
|
||||
{
|
||||
assert(s != NULL);
|
||||
|
||||
if (*s == 0)
|
||||
return true;
|
||||
|
||||
char *endptr;
|
||||
long l = strtol(s, &endptr, 10);
|
||||
if (*endptr == 0 && src_get_name(l) != NULL) {
|
||||
lsr_converter = l;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = strlen(s);
|
||||
for (int i = 0;; ++i) {
|
||||
const char *name = src_get_name(i);
|
||||
if (name == NULL)
|
||||
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, GError **error_r)
|
||||
{
|
||||
if (!lsr_parse_converter(converter)) {
|
||||
g_set_error(error_r, libsamplerate_quark(), 0,
|
||||
"unknown samplerate converter '%s'", converter);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_debug("libsamplerate converter '%s'",
|
||||
src_get_name(lsr_converter));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
pcm_resample_lsr_init(struct pcm_resample_state *state)
|
||||
{
|
||||
memset(state, 0, sizeof(*state));
|
||||
|
||||
pcm_buffer_init(&state->in);
|
||||
pcm_buffer_init(&state->out);
|
||||
pcm_buffer_init(&state->buffer);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
|
||||
{
|
||||
if (state->state != NULL)
|
||||
state->state = src_delete(state->state);
|
||||
|
||||
pcm_buffer_deinit(&state->in);
|
||||
pcm_buffer_deinit(&state->out);
|
||||
pcm_buffer_deinit(&state->buffer);
|
||||
}
|
||||
|
||||
void
|
||||
pcm_resample_lsr_reset(struct pcm_resample_state *state)
|
||||
{
|
||||
if (state->state != NULL)
|
||||
src_reset(state->state);
|
||||
}
|
||||
|
||||
static bool
|
||||
pcm_resample_set(struct pcm_resample_state *state,
|
||||
unsigned channels, unsigned src_rate, unsigned dest_rate,
|
||||
GError **error_r)
|
||||
{
|
||||
int error;
|
||||
SRC_DATA *data = &state->data;
|
||||
|
||||
/* (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);
|
||||
|
||||
state->state = src_new(lsr_converter, channels, &error);
|
||||
if (!state->state) {
|
||||
g_set_error(error_r, libsamplerate_quark(), state->error,
|
||||
"libsamplerate initialization has failed: %s",
|
||||
src_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
data->src_ratio = (double)dest_rate / (double)src_rate;
|
||||
g_debug("setting samplerate conversion ratio to %.2lf",
|
||||
data->src_ratio);
|
||||
src_set_ratio(state->state, data->src_ratio);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
lsr_process(struct pcm_resample_state *state, GError **error_r)
|
||||
{
|
||||
if (state->error == 0)
|
||||
state->error = src_process(state->state, &state->data);
|
||||
if (state->error) {
|
||||
g_set_error(error_r, libsamplerate_quark(), state->error,
|
||||
"libsamplerate has failed: %s",
|
||||
src_strerror(state->error));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static float *
|
||||
deconst_float_buffer(const float *in)
|
||||
{
|
||||
union {
|
||||
const float *in;
|
||||
float *out;
|
||||
} u = { .in = in };
|
||||
return u.out;
|
||||
}
|
||||
|
||||
const float *
|
||||
pcm_resample_lsr_float(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const float *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
|
||||
|
||||
if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r))
|
||||
return NULL;
|
||||
|
||||
SRC_DATA *data = &state->data;
|
||||
data->input_frames = src_size / sizeof(*src_buffer) / channels;
|
||||
data->data_in = deconst_float_buffer(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 = pcm_buffer_get(&state->out, data_out_size);
|
||||
|
||||
if (!lsr_process(state, error_r))
|
||||
return NULL;
|
||||
|
||||
*dest_size_r = data->output_frames_gen *
|
||||
sizeof(*data->data_out) * channels;
|
||||
return data->data_out;
|
||||
}
|
||||
|
||||
const int16_t *
|
||||
pcm_resample_lsr_16(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int16_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
bool success;
|
||||
SRC_DATA *data = &state->data;
|
||||
size_t data_in_size;
|
||||
size_t data_out_size;
|
||||
int16_t *dest_buffer;
|
||||
|
||||
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
|
||||
|
||||
success = pcm_resample_set(state, channels, src_rate, dest_rate,
|
||||
error_r);
|
||||
if (!success)
|
||||
return NULL;
|
||||
|
||||
data->input_frames = src_size / sizeof(*src_buffer) / channels;
|
||||
data_in_size = data->input_frames * sizeof(float) * channels;
|
||||
data->data_in = pcm_buffer_get(&state->in, data_in_size);
|
||||
|
||||
data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
|
||||
data_out_size = data->output_frames * sizeof(float) * channels;
|
||||
data->data_out = pcm_buffer_get(&state->out, data_out_size);
|
||||
|
||||
src_short_to_float_array(src_buffer, data->data_in,
|
||||
data->input_frames * channels);
|
||||
|
||||
if (!lsr_process(state, error_r))
|
||||
return NULL;
|
||||
|
||||
*dest_size_r = data->output_frames_gen *
|
||||
sizeof(*dest_buffer) * channels;
|
||||
dest_buffer = pcm_buffer_get(&state->buffer, *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(struct pcm_resample_state *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
GError **error_r)
|
||||
{
|
||||
bool success;
|
||||
SRC_DATA *data = &state->data;
|
||||
size_t data_in_size;
|
||||
size_t data_out_size;
|
||||
int32_t *dest_buffer;
|
||||
|
||||
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
|
||||
|
||||
success = pcm_resample_set(state, channels, src_rate, dest_rate,
|
||||
error_r);
|
||||
if (!success)
|
||||
return NULL;
|
||||
|
||||
data->input_frames = src_size / sizeof(*src_buffer) / channels;
|
||||
data_in_size = data->input_frames * sizeof(float) * channels;
|
||||
data->data_in = pcm_buffer_get(&state->in, data_in_size);
|
||||
|
||||
data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
|
||||
data_out_size = data->output_frames * sizeof(float) * channels;
|
||||
data->data_out = pcm_buffer_get(&state->out, data_out_size);
|
||||
|
||||
src_int_to_float_array(src_buffer, data->data_in,
|
||||
data->input_frames * channels);
|
||||
|
||||
if (!lsr_process(state, error_r))
|
||||
return NULL;
|
||||
|
||||
*dest_size_r = data->output_frames_gen *
|
||||
sizeof(*dest_buffer) * channels;
|
||||
dest_buffer = pcm_buffer_get(&state->buffer, *dest_size_r);
|
||||
src_float_to_int_array(data->data_out, dest_buffer,
|
||||
data->output_frames_gen * channels);
|
||||
|
||||
return dest_buffer;
|
||||
}
|
Reference in New Issue
Block a user