From 0e8c21de2a464fc913e0dbb6f7f148a84db95d55 Mon Sep 17 00:00:00 2001 Message-Id: <0e8c21de2a464fc913e0dbb6f7f148a84db95d55.1715425738.git.mdw@distorted.org.uk> From: Mark Wooding Date: Thu, 19 Nov 2009 19:40:17 +0000 Subject: [PATCH] Initial resample.c implementation Organization: Straylight/Edgeware From: Richard Kettlewell --- configure.ac | 3 + lib/Makefile.am | 1 + lib/resample.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/resample.h | 67 +++++++++++ 4 files changed, 380 insertions(+) create mode 100644 lib/resample.c create mode 100644 lib/resample.h diff --git a/configure.ac b/configure.ac index 80cc04f..f527fff 100644 --- a/configure.ac +++ b/configure.ac @@ -438,6 +438,8 @@ if test $want_alsa = yes; then AC_CHECK_LIB([asound], [snd_pcm_open], [AC_SUBST(LIBASOUND,[-lasound])]) fi +AC_CHECK_LIB([samplerate],[src_new], + [AC_SUBST([LIBSAMPLERATE],[-lsamplerate])]) if test $want_server = yes; then RJK_CHECK_LIB(db, db_create, [#include ], [AC_SUBST(LIBDB,[-ldb])], @@ -523,6 +525,7 @@ AC_CHECK_HEADERS([dlfcn.h gcrypt.h \ syslog.h unistd.h],[:],[ missing_headers="$missing_headers $ac_header" ]) +AC_CHECK_HEADERS([samplerate.h]) if test ! -z "$missing_headers"; then AC_MSG_ERROR([missing headers:$missing_headers]) diff --git a/lib/Makefile.am b/lib/Makefile.am index 1ab3773..fa5b389 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -64,6 +64,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ queue.c queue.h \ random.c random.h \ regsub.c regsub.h \ + resample.c resample.h \ rights.c queue-rights.c rights.h \ rtp.h \ selection.c selection.h \ diff --git a/lib/resample.c b/lib/resample.c new file mode 100644 index 0000000..9617db9 --- /dev/null +++ b/lib/resample.c @@ -0,0 +1,309 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2009 Richard Kettlewell + * + * 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 3 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, see . + */ + +/** @file lib/resample.c + * @brief Audio resampling + * + * General purpose audio format conversion. Rate conversion only works if the + * SRC samplerate library is available, but the bitness/channel/endianness + * conversion works regardless. + */ + +#include "common.h" +#include "resample.h" +#include "log.h" +#include "mem.h" + +/** @brief Number of intermediate-format samples */ +#define SAMPLES 1024 + +/** @brief Multiplier for signed formats to allow easy switching */ +#define SIGNED 4 + +/** @brief Initialize a resampler + * @param rs Resampler + * @param input_bits Bits/sample in input + * @param input_channels Number of input channels + * @param input_signed Whether input samples are signed or unsigned + * @param input_rate Frames/second in input + * @param output_bits Bits/sample in output + * @param output_channels Number of output channels + * @param output_rate Frames/second in output + * @param output_signed Whether output samples are signed or unsigned + * + * For formats with more than two channels it's assume that the first + * two channels are left and right. No particular meaning is attached + * to additional channels other than to assume channel N in an input + * means the same as channel N in an output, for N>1. + */ +void resample_init(struct resampler *rs, + int input_bits, int input_channels, + int input_rate, int input_signed, + int input_endian, + int output_bits, int output_channels, + int output_rate, int output_signed, + int output_endian) { + int error_; + memset(rs, 0, sizeof *rs); + assert(input_bits == 8 || input_bits == 16); + assert(output_bits == 8 || output_bits == 16); + assert(input_endian == ENDIAN_BIG || input_endian == ENDIAN_LITTLE); + assert(output_endian == ENDIAN_BIG || output_endian == ENDIAN_LITTLE); + assert(ENDIAN_BIG >= 0 && ENDIAN_BIG < SIGNED); + assert(ENDIAN_LITTLE >= 0 && ENDIAN_LITTLE < SIGNED); + rs->input_bits = input_bits; + rs->input_channels = input_channels; + rs->input_rate = input_rate; + rs->input_signed = SIGNED * !!input_signed; + rs->input_endian = input_endian; + rs->output_bits = output_bits; + rs->output_channels = output_channels; + rs->output_rate = output_rate; + rs->output_signed = SIGNED * !!output_signed; + rs->output_endian = output_endian; + rs->input_bytes_per_sample = (rs->input_bits + 7) / 8; + rs->input_bytes_per_frame = rs->input_channels * rs->input_bytes_per_sample; + if(rs->input_rate != rs->output_rate) { +#if HAVE_SAMPLERATE_H + rs->state = src_new(SRC_SINC_BEST_QUALITY, rs->output_channels, &error_); + if(!rs->state) + fatal(0, "calling src_new: %s", src_strerror(error_)); +#else + fatal(0, "need to resample audio data but libsamplerate not available"); +#endif + } +} + +/** @brief Destroy a resampler + * @param rs Resampler + */ +void resample_close(struct resampler *rs) { +#if HAVE_SAMPLERATE_H + if(rs->state) + src_delete(rs->state); +#endif +} + +/** @brief Get one sample value and normalize it to [-1,1] + * @param rs Resampler state + * @param bytes Pointer to input data + * @param where Where to store result + * @return Number of bytes consumed + */ +static size_t resample_get_sample(struct resampler *rs, + const uint8_t *bytes, + float *where) { + switch(rs->input_bits + rs->input_signed + rs->input_endian) { + case 8+ENDIAN_BIG: + case 8+ENDIAN_LITTLE: + *where = (bytes[0] - 128)/ 128; + return 1; + case 8+SIGNED+ENDIAN_BIG: + case 8+SIGNED+ENDIAN_LITTLE: + *where = (int8_t)bytes[0] / 128; + return 1; + case 16+ENDIAN_BIG: + *where = (bytes[0] * 256 + bytes[1] - 32768)/ 32768.0; + return 2; + break; + case 16+ENDIAN_LITTLE: + *where = (bytes[1] * 256 + bytes[0] - 32768)/ 32768.0; + return 2; + break; + case 16+SIGNED+ENDIAN_BIG: + *where = (int16_t)(bytes[0] * 256 + bytes[1])/ 32768.0; + return 2; + break; + case 16+SIGNED+ENDIAN_LITTLE: + *where = (int16_t)(bytes[1] * 256 + bytes[0])/ 32768.0; + return 2; + break; + default: + assert(!"unsupported sample format"); + } +} + +static inline int clip(int n, int min, int max) { + if(n >= min) { + if(n <= max) + return n; + else + return max; + } else + return min; +} + +/** @brief Store one sample value + * @param rs Resampler state + * @param sample Sample value + * @param bytes Where to store it + * @return Number of bytes stored + * + * The value is clipped naively if it will not fit. + */ +static size_t resample_put_sample(struct resampler *rs, + float sample, + uint8_t *bytes) { + unsigned value; + switch(rs->input_bits + rs->input_signed + rs->input_endian) { + case 8+ENDIAN_BIG: + case 8+ENDIAN_LITTLE: + *bytes = clip(sample * 128.0 + 128, 0, 255); + return 1; + case 8+SIGNED+ENDIAN_BIG: + case 8+SIGNED+ENDIAN_LITTLE: + *bytes = clip((int)(sample * 128.0), -128, 127); + return 1; + case 16+ENDIAN_BIG: /* unsigned */ + value = clip(sample * 32768.0 + 32768, 0, 65535); + *bytes++ = value >> 8; + *bytes++ = value; + return 2; + case 16+ENDIAN_LITTLE: + value = clip(sample * 32768.0 + 32768, 0, 65535); + *bytes++ = value; + *bytes++ = value >> 8; + return 2; + case 16+SIGNED+ENDIAN_BIG: + value = clip(sample * 32768.0, -32768, 32767); + *bytes++ = value >> 8; + *bytes++ = value; + return 2; + case 16+SIGNED+ENDIAN_LITTLE: + value = clip(sample * 32768.0, -32768, 32767); + *bytes++ = value; + *bytes++ = value >> 8; + return 2; + default: + assert(!"unsupported sample format"); + } +} + +/** @brief Convert input samples to floats + * @param rs Resampler state + * @param bytes Input bytes + * @param nbytes Number of input bytes + * @param floats Where to store converted data + * + * @p floats must be big enough. As well as converting to floats this + * also converts to the output's channel format. + * + * Excess input channels are just discarded. If there are insufficient input + * channels the last one is duplicated as often as necessary to make up the + * numbers. This is a rather naff heuristic and may be improved in a future + * version, but mostly in DisOrder the output is pretty much always stereo and + * the input either mono or stereo, so the result isn't actually going to be + * too bad. + */ +static void resample_prepare_input(struct resampler *rs, + const uint8_t *bytes, + size_t nbytes, + float *floats) { + size_t nframes = nbytes / (rs->input_bytes_per_frame); + + while(nframes > 0) { + int n; + + for(n = 0; n < rs->input_channels && n < rs->output_channels; ++n) { + bytes += resample_get_sample(rs, bytes, floats); + ++floats; + } + if(n < rs->input_channels) { + /* More input channels; discard them */ + bytes += (rs->input_channels - n) * rs->input_bytes_per_sample; + } else if(n < rs->output_channels) { + /* More output channels; duplicate the last input channel */ + for(; n < rs->output_channels; ++n) { + *floats = floats[-1]; + ++floats; + } + } + } +} + +/** @brief Convert between sample formats + * @param rs Resampler state + * @param bytes Bytes to convert + * @param nbytes Number of bytes to convert + * @param eof Set an end of input stream + * @param converted Called with converted data (possibly more than once) + * @return Number of bytes consumed + */ +size_t resample_convert(struct resampler *rs, + const uint8_t *bytes, + size_t nbytes, + int eof, + void (*converted)(uint8_t *bytes, + size_t nbytes)) { + size_t nframesin = nbytes / (rs->input_bytes_per_frame); + size_t nsamplesout; + float *input = xcalloc(nframesin * rs->output_channels, sizeof (float)); + float *output = 0; + + resample_prepare_input(rs, bytes, nbytes, input); +#if HAVE_SAMPLERATE_H + if(rs->state) { + /* A sample-rate conversion must be performed */ + SRC_DATA data; + /* Compute how many frames are expected to come out. */ + size_t maxframesout = nframesin * rs->output_rate / rs->input_rate + 1; + output = xcalloc(maxframesout * rs->output_channels, sizeof(float)); + data.data_in = input; + data.data_out = output; + data.input_frames = nframesin; + data.output_frames = maxframesout; + data.end_of_input = eof; + data.src_ratio = rs->output_rate / rs->input_rate; + int error_ = src_process(rs->state, &data); + if(error_) + fatal(0, "calling src_process: %s", src_strerror(error_)); + nframesin = data.input_frames_used; + nsamplesout = data.output_frames_gen * rs->output_channels; + } +#endif + if(!output) { + /* No sample-rate conversion required */ + output = input; + nsamplesout = nframesin * rs->output_channels; + } + const float *op = output; + while(nsamplesout > 0) { + uint8_t buffer[4096]; + size_t bufused = 0; + + while(bufused < sizeof buffer && nsamplesout > 0) { + bufused += resample_put_sample(rs, *op++, buffer + bufused); + --nsamplesout; + } + converted(buffer, bufused); + } + if(output != input) + xfree(output); + xfree(input); + /* Report how many input bytes were actually consumed */ + return nframesin * rs->input_bytes_per_frame; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/resample.h b/lib/resample.h new file mode 100644 index 0000000..e8bfb10 --- /dev/null +++ b/lib/resample.h @@ -0,0 +1,67 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2009 Richard Kettlewell + * + * 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 3 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, see . + */ + +/** @file lib/resample.h + * @brief Audio resampling + */ + +#ifndef RESAMPLE_H +#define RESAMPLE_H + +#if HAVE_SAMPLERATE_H +#include +#endif + +#define ENDIAN_BIG 1 +#define ENDIAN_LITTLE 2 + +struct resampler { + int input_bits, input_channels, input_rate, input_signed, input_endian; + int output_bits, output_channels, output_rate, output_signed, output_endian; + int input_bytes_per_sample; + int input_bytes_per_frame; +#if HAVE_SAMPLERATE_H + SRC_STATE *state; +#endif +}; + +void resample_init(struct resampler *rs, + int input_bits, int input_channels, + int input_rate, int input_signed, + int input_endian, + int output_bits, int output_channels, + int output_rate, int output_signed, + int output_endian); +size_t resample_convert(struct resampler *rs, + const uint8_t *bytes, + size_t nbytes, + int eof, + void (*converted)(uint8_t *bytes, + size_t nbytes)); +void resample_close(struct resampler *rs); + +#endif /* RESAMPLE_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ -- [mdw]