aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/src/pcm_utils.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--trunk/src/pcm_utils.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/trunk/src/pcm_utils.c b/trunk/src/pcm_utils.c
new file mode 100644
index 000000000..534095620
--- /dev/null
+++ b/trunk/src/pcm_utils.c
@@ -0,0 +1,470 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
+ * This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "pcm_utils.h"
+
+#include "mpd_types.h"
+#include "log.h"
+#include "utils.h"
+#include "conf.h"
+
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+void pcm_volumeChange(char *buffer, int bufferSize, AudioFormat * format,
+ int volume)
+{
+ mpd_sint32 temp32;
+ mpd_sint8 *buffer8 = (mpd_sint8 *) buffer;
+ mpd_sint16 *buffer16 = (mpd_sint16 *) buffer;
+
+ if (volume >= 1000)
+ return;
+
+ if (volume <= 0) {
+ memset(buffer, 0, bufferSize);
+ return;
+ }
+
+ switch (format->bits) {
+ case 16:
+ while (bufferSize > 0) {
+ temp32 = *buffer16;
+ temp32 *= volume;
+ temp32 += rand() & 511;
+ temp32 -= rand() & 511;
+ temp32 += 500;
+ temp32 /= 1000;
+ *buffer16 = temp32 > 32767 ? 32767 :
+ (temp32 < -32768 ? -32768 : temp32);
+ buffer16++;
+ bufferSize -= 2;
+ }
+ break;
+ case 8:
+ while (bufferSize > 0) {
+ temp32 = *buffer8;
+ temp32 *= volume;
+ temp32 += rand() & 511;
+ temp32 -= rand() & 511;
+ temp32 += 500;
+ temp32 /= 1000;
+ *buffer8 = temp32 > 127 ? 127 :
+ (temp32 < -128 ? -128 : temp32);
+ buffer8++;
+ bufferSize--;
+ }
+ break;
+ default:
+ FATAL("%i bits not supported by pcm_volumeChange!\n",
+ format->bits);
+ }
+}
+
+static void pcm_add(char *buffer1, char *buffer2, size_t bufferSize1,
+ size_t bufferSize2, int vol1, int vol2,
+ AudioFormat * format)
+{
+ mpd_sint32 temp32;
+ mpd_sint8 *buffer8_1 = (mpd_sint8 *) buffer1;
+ mpd_sint8 *buffer8_2 = (mpd_sint8 *) buffer2;
+ mpd_sint16 *buffer16_1 = (mpd_sint16 *) buffer1;
+ mpd_sint16 *buffer16_2 = (mpd_sint16 *) buffer2;
+
+ switch (format->bits) {
+ case 16:
+ while (bufferSize1 > 0 && bufferSize2 > 0) {
+ temp32 =
+ (vol1 * (*buffer16_1) +
+ vol2 * (*buffer16_2));
+ temp32 += rand() & 511;
+ temp32 -= rand() & 511;
+ temp32 += 500;
+ temp32 /= 1000;
+ *buffer16_1 =
+ temp32 > 32767 ? 32767 : (temp32 <
+ -32768 ? -32768 : temp32);
+ buffer16_1++;
+ buffer16_2++;
+ bufferSize1 -= 2;
+ bufferSize2 -= 2;
+ }
+ if (bufferSize2 > 0)
+ memcpy(buffer16_1, buffer16_2, bufferSize2);
+ break;
+ case 8:
+ while (bufferSize1 > 0 && bufferSize2 > 0) {
+ temp32 =
+ (vol1 * (*buffer8_1) + vol2 * (*buffer8_2));
+ temp32 += rand() & 511;
+ temp32 -= rand() & 511;
+ temp32 += 500;
+ temp32 /= 1000;
+ *buffer8_1 =
+ temp32 > 127 ? 127 : (temp32 <
+ -128 ? -128 : temp32);
+ buffer8_1++;
+ buffer8_2++;
+ bufferSize1--;
+ bufferSize2--;
+ }
+ if (bufferSize2 > 0)
+ memcpy(buffer8_1, buffer8_2, bufferSize2);
+ break;
+ default:
+ FATAL("%i bits not supported by pcm_add!\n", format->bits);
+ }
+}
+
+void pcm_mix(char *buffer1, char *buffer2, size_t bufferSize1,
+ size_t bufferSize2, AudioFormat * format, float portion1)
+{
+ int vol1;
+ float s = sin(M_PI_2 * portion1);
+ s *= s;
+
+ vol1 = s * 1000 + 0.5;
+ vol1 = vol1 > 1000 ? 1000 : (vol1 < 0 ? 0 : vol1);
+
+ pcm_add(buffer1, buffer2, bufferSize1, bufferSize2, vol1, 1000 - vol1,
+ format);
+}
+
+#ifdef HAVE_LIBSAMPLERATE
+static int pcm_getSampleRateConverter(void)
+{
+ const char *conf = getConfigParamValue(CONF_SAMPLERATE_CONVERTER);
+ long convalgo;
+ char *test;
+ size_t len;
+
+ if (!conf) {
+ convalgo = SRC_SINC_FASTEST;
+ goto out;
+ }
+
+ convalgo = strtol(conf, &test, 10);
+ if (*test == '\0' && src_get_name(convalgo))
+ goto out;
+
+ len = strlen(conf);
+ for (convalgo = 0 ; ; convalgo++) {
+ test = (char *)src_get_name(convalgo);
+ if (!test) {
+ convalgo = SRC_SINC_FASTEST;
+ break;
+ }
+ if (strncasecmp(test, conf, len) == 0)
+ goto out;
+ }
+
+ ERROR("unknown samplerate converter \"%s\"\n", conf);
+out:
+ DEBUG("selecting samplerate converter \"%s\"\n",
+ src_get_name(convalgo));
+
+ return convalgo;
+}
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate,
+ char *inBuffer, size_t inSize,
+ mpd_uint32 outSampleRate, char *outBuffer,
+ size_t outSize, ConvState *convState)
+{
+ static int convalgo = -1;
+ SRC_DATA *data = &convState->data;
+ size_t dataInSize;
+ size_t dataOutSize;
+ int error;
+
+ if (convalgo < 0)
+ convalgo = pcm_getSampleRateConverter();
+
+ /* (re)set the state/ratio if the in or out format changed */
+ if ((channels != convState->lastChannels) ||
+ (inSampleRate != convState->lastInSampleRate) ||
+ (outSampleRate != convState->lastOutSampleRate)) {
+ convState->error = 0;
+ convState->lastChannels = channels;
+ convState->lastInSampleRate = inSampleRate;
+ convState->lastOutSampleRate = outSampleRate;
+
+ if (convState->state)
+ convState->state = src_delete(convState->state);
+
+ convState->state = src_new(convalgo, channels, &error);
+ if (!convState->state) {
+ ERROR("cannot create new libsamplerate state: %s\n",
+ src_strerror(error));
+ convState->error = 1;
+ return 0;
+ }
+
+ data->src_ratio = (double)outSampleRate / (double)inSampleRate;
+ DEBUG("setting samplerate conversion ratio to %.2lf\n",
+ data->src_ratio);
+ src_set_ratio(convState->state, data->src_ratio);
+ }
+
+ /* there was an error previously, and nothing has changed */
+ if (convState->error)
+ return 0;
+
+ data->input_frames = inSize / 2 / channels;
+ dataInSize = data->input_frames * sizeof(float) * channels;
+ if (dataInSize > convState->dataInSize) {
+ convState->dataInSize = dataInSize;
+ data->data_in = xrealloc(data->data_in, dataInSize);
+ }
+
+ data->output_frames = outSize / 2 / channels;
+ dataOutSize = data->output_frames * sizeof(float) * channels;
+ if (dataOutSize > convState->dataOutSize) {
+ convState->dataOutSize = dataOutSize;
+ data->data_out = xrealloc(data->data_out, dataOutSize);
+ }
+
+ src_short_to_float_array((short *)inBuffer, data->data_in,
+ data->input_frames * channels);
+
+ error = src_process(convState->state, data);
+ if (error) {
+ ERROR("error processing samples with libsamplerate: %s\n",
+ src_strerror(error));
+ convState->error = 1;
+ return 0;
+ }
+
+ src_float_to_short_array(data->data_out, (short *)outBuffer,
+ data->output_frames_gen * channels);
+
+ return data->output_frames_gen * 2 * channels;
+}
+#else /* !HAVE_LIBSAMPLERATE */
+/* resampling code blatantly ripped from ESD */
+static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate,
+ char *inBuffer, size_t inSize,
+ mpd_uint32 outSampleRate, char *outBuffer,
+ size_t outSize, ConvState *convState)
+{
+ mpd_uint32 rd_dat = 0;
+ mpd_uint32 wr_dat = 0;
+ mpd_sint16 *in = (mpd_sint16 *)inBuffer;
+ mpd_sint16 *out = (mpd_sint16 *)outBuffer;
+ mpd_uint32 nlen = outSize / 2;
+ mpd_sint16 lsample, rsample;
+
+ switch (channels) {
+ case 1:
+ while (wr_dat < nlen) {
+ rd_dat = wr_dat * inSampleRate / outSampleRate;
+
+ lsample = in[rd_dat++];
+
+ out[wr_dat++] = lsample;
+ }
+ break;
+ case 2:
+ while (wr_dat < nlen) {
+ rd_dat = wr_dat * inSampleRate / outSampleRate;
+ rd_dat &= ~1;
+
+ lsample = in[rd_dat++];
+ rsample = in[rd_dat++];
+
+ out[wr_dat++] = lsample;
+ out[wr_dat++] = rsample;
+ }
+ break;
+ }
+
+ return outSize;
+}
+#endif /* !HAVE_LIBSAMPLERATE */
+
+static char *pcm_convertChannels(mpd_sint8 channels, char *inBuffer,
+ size_t inSize, size_t *outSize)
+{
+ static char *buf;
+ static size_t len;
+ char *outBuffer = NULL;;
+ mpd_sint16 *in;
+ mpd_sint16 *out;
+ int inSamples, i;
+
+ switch (channels) {
+ /* convert from 1 -> 2 channels */
+ case 1:
+ *outSize = (inSize >> 1) << 2;
+ if (*outSize > len) {
+ len = *outSize;
+ buf = xrealloc(buf, len);
+ }
+ outBuffer = buf;
+
+ inSamples = inSize >> 1;
+ in = (mpd_sint16 *)inBuffer;
+ out = (mpd_sint16 *)outBuffer;
+ for (i = 0; i < inSamples; i++) {
+ *out++ = *in;
+ *out++ = *in++;
+ }
+
+ break;
+ /* convert from 2 -> 1 channels */
+ case 2:
+ *outSize = inSize >> 1;
+ if (*outSize > len) {
+ len = *outSize;
+ buf = xrealloc(buf, len);
+ }
+ outBuffer = buf;
+
+ inSamples = inSize >> 2;
+ in = (mpd_sint16 *)inBuffer;
+ out = (mpd_sint16 *)outBuffer;
+ for (i = 0; i < inSamples; i++) {
+ *out = (*in++) / 2;
+ *out++ += (*in++) / 2;
+ }
+
+ break;
+ default:
+ ERROR("only 1 or 2 channels are supported for conversion!\n");
+ }
+
+ return outBuffer;
+}
+
+static char *pcm_convertTo16bit(mpd_sint8 bits, char *inBuffer, size_t inSize,
+ size_t *outSize)
+{
+ static char *buf;
+ static size_t len;
+ char *outBuffer = NULL;
+ mpd_sint8 *in;
+ mpd_sint16 *out;
+ int i;
+
+ switch (bits) {
+ case 8:
+ *outSize = inSize << 1;
+ if (*outSize > len) {
+ len = *outSize;
+ buf = xrealloc(buf, len);
+ }
+ outBuffer = buf;
+
+ in = (mpd_sint8 *)inBuffer;
+ out = (mpd_sint16 *)outBuffer;
+ for (i = 0; i < inSize; i++)
+ *out++ = (*in++) << 8;
+
+ break;
+ case 16:
+ *outSize = inSize;
+ outBuffer = inBuffer;
+ break;
+ case 24:
+ /* put dithering code from mp3_decode here */
+ default:
+ ERROR("only 8 or 16 bits are supported for conversion!\n");
+ }
+
+ return outBuffer;
+}
+
+/* outFormat bits must be 16 and channels must be 1 or 2! */
+size_t pcm_convertAudioFormat(AudioFormat * inFormat, char *inBuffer,
+ size_t inSize, AudioFormat * outFormat,
+ char *outBuffer, ConvState *convState)
+{
+ char *buf;
+ size_t len;
+ size_t outSize = pcm_sizeOfConvBuffer(inFormat, inSize, outFormat);
+
+ assert(outFormat->bits == 16);
+ assert(outFormat->channels == 2 || outFormat->channels == 1);
+
+ /* everything else supports 16 bit only, so convert to that first */
+ buf = pcm_convertTo16bit(inFormat->bits, inBuffer, inSize, &len);
+ if (!buf)
+ exit(EXIT_FAILURE);
+
+ if (inFormat->channels != outFormat->channels) {
+ buf = pcm_convertChannels(inFormat->channels, buf, len, &len);
+ if (!buf)
+ exit(EXIT_FAILURE);
+ }
+
+ if (inFormat->sampleRate == outFormat->sampleRate) {
+ assert(outSize >= len);
+ memcpy(outBuffer, buf, len);
+ } else {
+ len = pcm_convertSampleRate(outFormat->channels,
+ inFormat->sampleRate, buf, len,
+ outFormat->sampleRate, outBuffer,
+ outSize, convState);
+ if (len == 0)
+ exit(EXIT_FAILURE);
+ }
+
+ return len;
+}
+
+size_t pcm_sizeOfConvBuffer(AudioFormat * inFormat, size_t inSize,
+ AudioFormat * outFormat)
+{
+ const double ratio = (double)outFormat->sampleRate /
+ (double)inFormat->sampleRate;
+ const int shift = 2 * outFormat->channels;
+ size_t outSize = inSize;
+
+ switch (inFormat->bits) {
+ case 8:
+ outSize <<= 1;
+ break;
+ case 16:
+ break;
+ default:
+ FATAL("only 8 or 16 bits are supported for conversion!\n");
+ }
+
+ if (inFormat->channels != outFormat->channels) {
+ switch (inFormat->channels) {
+ case 1:
+ outSize = (outSize >> 1) << 2;
+ break;
+ case 2:
+ outSize >>= 1;
+ break;
+ default:
+ FATAL("only 1 or 2 channels are supported "
+ "for conversion!\n");
+ }
+ }
+
+ outSize /= shift;
+ outSize = floor(0.5 + (double)outSize * ratio);
+ outSize *= shift;
+
+ return outSize;
+}