diff options
Diffstat (limited to 'src/PcmConvert.cxx')
-rw-r--r-- | src/PcmConvert.cxx | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/PcmConvert.cxx b/src/PcmConvert.cxx new file mode 100644 index 000000000..9618b9642 --- /dev/null +++ b/src/PcmConvert.cxx @@ -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; + } +} |