From 3ddc35f7aecbcd094de7338c045b08e407c9772c Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 21 Oct 2020 15:05:14 +0100 Subject: [PATCH] Resampler code pulled back in from bqresample, with safer api --- src/StretcherImpl.cpp | 18 +- src/StretcherProcess.cpp | 10 +- src/dsp/Resampler.cpp | 670 ++++++++++++++++++++------------------- src/dsp/Resampler.h | 100 ++++-- src/system/Allocators.h | 4 +- 5 files changed, 443 insertions(+), 359 deletions(-) diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index cbf8b7b..2863ef8 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -672,9 +672,12 @@ RubberBandStretcher::Impl::configure() if (m_channelData[c]->resampler) continue; - m_channelData[c]->resampler = - new Resampler(Resampler::FastestTolerable, 1, 4096 * 16, - m_debugLevel); + Resampler::Parameters params; + params.quality = Resampler::FastestTolerable; + params.maxBufferSize = 4096 * 16; + params.debugLevel = m_debugLevel; + + m_channelData[c]->resampler = new Resampler(params, 1); // rbs is the amount of buffer space we think we'll need // for resampling; but allocate a sensible amount in case @@ -813,9 +816,12 @@ RubberBandStretcher::Impl::reconfigure() std::cerr << "WARNING: reconfigure(): resampler construction required in RT mode" << std::endl; - m_channelData[c]->resampler = - new Resampler(Resampler::FastestTolerable, 1, m_sWindowSize, - m_debugLevel); + Resampler::Parameters params; + params.quality = Resampler::FastestTolerable; + params.maxBufferSize = m_sWindowSize; + params.debugLevel = m_debugLevel; + + m_channelData[c]->resampler = new Resampler(params, 1); size_t rbs = lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale)); diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index d8c912e..b0bafad 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -217,8 +217,9 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, input = inputs[c] + offset; } - toWrite = cd.resampler->resample(&input, - &cd.resamplebuf, + toWrite = cd.resampler->resample(&cd.resamplebuf, + cd.resamplebufSize, + &input, samples, 1.0 / m_pitchScale, final); @@ -1090,8 +1091,9 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo #endif #endif - size_t outframes = cd.resampler->resample(&cd.accumulator, - &cd.resamplebuf, + size_t outframes = cd.resampler->resample(&cd.resamplebuf, + cd.resamplebufSize, + &cd.accumulator, si, 1.0 / m_pitchScale, last); diff --git a/src/dsp/Resampler.cpp b/src/dsp/Resampler.cpp index 383c5a1..280e817 100644 --- a/src/dsp/Resampler.cpp +++ b/src/dsp/Resampler.cpp @@ -31,6 +31,7 @@ #include #include "system/Allocators.h" +#include "system/VectorOps.h" #ifdef HAVE_IPP #include @@ -65,23 +66,27 @@ #endif #endif +using namespace std; + namespace RubberBand { -class ResamplerImpl +class Resampler::Impl { public: - virtual ~ResamplerImpl() { } + virtual ~Impl() { } - virtual int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, + virtual int resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final) = 0; - virtual int resampleInterleaved(const float *const R__ in, - float *const R__ out, + virtual int resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final) = 0; virtual int getChannelCount() const = 0; @@ -93,23 +98,25 @@ namespace Resamplers { #ifdef HAVE_IPP -class D_IPP : public ResamplerImpl +class D_IPP : public Resampler::Impl { public: - D_IPP(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel); + D_IPP(Resampler::Quality quality, int channels, double initialSampleRate, + int maxBufferSize, int debugLevel); ~D_IPP(); - int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, + int resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final); - int resampleInterleaved(const float *const R__ in, - float *const R__ out, + int resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final = false); int getChannelCount() const { return m_channels; } @@ -119,8 +126,9 @@ public: protected: // to m_outbuf int doResample(int outcount, double ratio, bool final); - + IppsResamplingPolyphase_32f **m_state; + double m_initialSampleRate; float **m_inbuf; size_t m_inbufsz; float **m_outbuf; @@ -137,20 +145,23 @@ protected: void setBufSize(int); }; -D_IPP::D_IPP(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel) : +D_IPP::D_IPP(Resampler::Quality quality, int channels, double initialSampleRate, + int maxBufferSize, int debugLevel) : m_state(0), + m_initialSampleRate(initialSampleRate), m_channels(channels), m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - std::cerr << "Resampler::Resampler: using IPP implementation" - << std::endl; + cerr << "Resampler::Resampler: using IPP implementation" + << endl; } int nStep = 16; IppHintAlgorithm hint = ippAlgHintFast; + //!!! todo: make use of initialSampleRate to calculate parameters + switch (quality) { case Resampler::Best: @@ -173,7 +184,22 @@ D_IPP::D_IPP(Resampler::Quality quality, int channels, int maxBufferSize, } m_factor = 8; // initial upper bound on m_ratio, may be amended later - m_history = int(m_window * 0.5 * std::max(1.0, 1.0 / m_factor)) + 1; + + // This is largely based on the IPP docs and examples. Adapted + // from the docs: + // + // m_time defines the time value for which the first output + // sample is calculated. The input vector with indices less + // than m_time [whose initial value is m_history below] + // contains the history data of filters. + // + // The history length is [(1/2) window * max(1, 1/factor) ]+1 + // where window is the size of the ideal lowpass filter + // window. The input vector must contain the same number of + // elements with indices greater than m_time + length for the + // right filter wing for the last element. + + m_history = int(m_window * 0.5 * max(1.0, 1.0 / m_factor)) + 1; m_state = new IppsResamplingPolyphase_32f *[m_channels]; @@ -185,11 +211,11 @@ D_IPP::D_IPP(Resampler::Quality quality, int channels, int maxBufferSize, m_inbuf = 0; m_outbuf = 0; m_bufsize = 0; - + setBufSize(maxBufferSize + m_history); if (m_debugLevel > 1) { - std::cerr << "bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << std::endl; + cerr << "bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << endl; } #if (IPP_VERSION_MAJOR >= 7) @@ -230,7 +256,7 @@ D_IPP::D_IPP(Resampler::Quality quality, int channels, int maxBufferSize, } if (m_debugLevel > 1) { - std::cerr << "Resampler init done" << std::endl; + cerr << "Resampler init done" << endl; } } @@ -258,20 +284,29 @@ void D_IPP::setBufSize(int sz) { if (m_debugLevel > 1) { - std::cerr << "resize bufsize " << m_bufsize << " -> "; + if (m_bufsize > 0) { + cerr << "resize bufsize " << m_bufsize << " -> "; + } else { + cerr << "initialise bufsize to "; + } } m_bufsize = sz; if (m_debugLevel > 1) { - std::cerr << m_bufsize << std::endl; + cerr << m_bufsize << endl; } int n1 = m_bufsize + m_history + 2; - int n2 = lrintf(ceil((m_bufsize - m_history) * m_factor + 2)); if (m_debugLevel > 1) { - std::cerr << "(outbufsize = " << n2 << ")" << std::endl; + cerr << "inbuf allocating " << m_bufsize << " + " << m_history << " + 2 = " << n1 << endl; + } + + int n2 = (int)lrintf(ceil((m_bufsize - m_history) * m_factor + 2)); + + if (m_debugLevel > 1) { + cerr << "outbuf allocating (" << m_bufsize << " - " << m_history << ") * " << m_factor << " + 2 = " << n2 << endl; } m_inbuf = reallocate_and_zero_extend_channels @@ -285,15 +320,20 @@ D_IPP::setBufSize(int sz) } int -D_IPP::resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, +D_IPP::resample(float *const R__ *const R__ out, + int outspace, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final) { if (ratio > m_factor) { m_factor = ratio; - m_history = int(m_window * 0.5 * std::max(1.0, 1.0 / m_factor)) + 1; + m_history = int(m_window * 0.5 * max(1.0, 1.0 / m_factor)) + 1; + } + + if (m_debugLevel > 2) { + cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; } for (int c = 0; c < m_channels; ++c) { @@ -309,8 +349,12 @@ D_IPP::resample(const float *const R__ *const R__ in, m_lastread[c] += incount; } - int got = doResample(int(round(incount * ratio)), ratio, final); - + if (m_debugLevel > 2) { + cerr << "lastread advanced to " << m_lastread[0] << endl; + } + + int got = doResample(outspace, ratio, final); + for (int c = 0; c < m_channels; ++c) { v_copy(out[c], m_outbuf[c], got); } @@ -319,15 +363,20 @@ D_IPP::resample(const float *const R__ *const R__ in, } int -D_IPP::resampleInterleaved(const float *const R__ in, - float *const R__ out, +D_IPP::resampleInterleaved(float *const R__ out, + int outspace, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final) { if (ratio > m_factor) { m_factor = ratio; - m_history = int(m_window * 0.5 * std::max(1.0, 1.0 / m_factor)) + 1; + m_history = int(m_window * 0.5 * max(1.0, 1.0 / m_factor)) + 1; + } + + if (m_debugLevel > 2) { + cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; } for (int c = 0; c < m_channels; ++c) { @@ -343,7 +392,12 @@ D_IPP::resampleInterleaved(const float *const R__ in, m_lastread[c] += incount; } - int got = doResample(int(round(incount * ratio)), ratio, final); + if (m_debugLevel > 2) { + cerr << "lastread advanced to " << m_lastread[0] << " after injection of " + << incount << " samples" << endl; + } + + int got = doResample(outspace, ratio, final); v_interleave(out, m_outbuf, m_channels, got); @@ -360,20 +414,20 @@ D_IPP::doResample(int outspace, double ratio, bool final) int n = m_lastread[c] - m_history - int(m_time[c]); if (c == 0 && m_debugLevel > 2) { - std::cerr << "at start, lastread = " << m_lastread[c] << ", history = " + cerr << "at start, lastread = " << m_lastread[c] << ", history = " << m_history << ", time = " << m_time[c] << ", therefore n = " - << n << std::endl; + << n << endl; } if (n <= 0) { if (c == 0 && m_debugLevel > 1) { - std::cerr << "not enough input samples to do anything" << std::endl; + cerr << "not enough input samples to do anything" << endl; } continue; } if (c == 0 && m_debugLevel > 2) { - std::cerr << "before resample call, time = " << m_time[c] << std::endl; + cerr << "before resample call, time = " << m_time[c] << endl; } // We're committed to not overrunning outspace, so we need to @@ -382,9 +436,9 @@ D_IPP::doResample(int outspace, double ratio, bool final) int limit = int(floor(outspace / ratio)); if (n > limit) { if (c == 0 && m_debugLevel > 1) { - std::cerr << "trimming input samples from " << n << " to " << limit + cerr << "trimming input samples from " << n << " to " << limit << " to avoid overrunning " << outspace << " at output" - << std::endl; + << endl; } n = limit; } @@ -412,11 +466,11 @@ D_IPP::doResample(int outspace, double ratio, bool final) int t = int(round(m_time[c])); if (c == 0 && m_debugLevel > 2) { - std::cerr << "converted " << n << " samples to " << outcount - << ", time advanced to " << t << std::endl; - std::cerr << "will move " << m_lastread[c] + m_history - t + cerr << "converted " << n << " samples to " << outcount + << ", time advanced to " << t << endl; + cerr << "will move " << m_lastread[c] + m_history - t << " unconverted samples back from index " << t - m_history - << " to 0" << std::endl; + << " to 0" << endl; } v_move(m_inbuf[c], @@ -427,9 +481,9 @@ D_IPP::doResample(int outspace, double ratio, bool final) m_time[c] -= t - m_history; if (c == 0 && m_debugLevel > 2) { - std::cerr << "lastread reduced to " << m_lastread[c] + cerr << "lastread reduced to " << m_lastread[c] << ", time reduced to " << m_time[c] - << std::endl; + << endl; } if (final && n < limit) { @@ -446,8 +500,8 @@ D_IPP::doResample(int outspace, double ratio, bool final) int additionalcount = 0; if (c == 0 && m_debugLevel > 2) { - std::cerr << "final call, padding input with " << m_history - << " zeros (symmetrical with m_history)" << std::endl; + cerr << "final call, padding input with " << m_history + << " zeros (symmetrical with m_history)" << endl; } for (int i = 0; i < m_history; ++i) { @@ -455,17 +509,17 @@ D_IPP::doResample(int outspace, double ratio, bool final) } if (c == 0 && m_debugLevel > 2) { - std::cerr << "before resample call, time = " << m_time[c] << std::endl; + cerr << "before resample call, time = " << m_time[c] << endl; } int nAdditional = m_lastread[c] - int(m_time[c]); if (n + nAdditional > limit) { if (c == 0 && m_debugLevel > 1) { - std::cerr << "trimming final input samples from " << nAdditional + cerr << "trimming final input samples from " << nAdditional << " to " << (limit - n) << " to avoid overrunning " << outspace << " at output" - << std::endl; + << endl; } nAdditional = limit - n; } @@ -491,9 +545,9 @@ D_IPP::doResample(int outspace, double ratio, bool final) #endif if (c == 0 && m_debugLevel > 2) { - std::cerr << "converted " << n << " samples to " << additionalcount - << ", time advanced to " << m_time[c] << std::endl; - std::cerr << "outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << std::endl; + cerr << "converted " << n << " samples to " << additionalcount + << ", time advanced to " << m_time[c] << endl; + cerr << "outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << endl; } if (c == 0) { @@ -503,7 +557,7 @@ D_IPP::doResample(int outspace, double ratio, bool final) } if (m_debugLevel > 2) { - std::cerr << "returning " << outcount << " samples" << std::endl; + cerr << "returning " << outcount << " samples" << endl; } return outcount; @@ -519,23 +573,25 @@ D_IPP::reset() #ifdef HAVE_LIBSAMPLERATE -class D_SRC : public ResamplerImpl +class D_SRC : public Resampler::Impl { public: - D_SRC(Resampler::Quality quality, int channels, int maxBufferSize, - int m_debugLevel); + D_SRC(Resampler::Quality quality, int channels, double initialSampleRate, + int maxBufferSize, int m_debugLevel); ~D_SRC(); - int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, + int resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final); - int resampleInterleaved(const float *const R__ in, - float *const R__ out, + int resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final = false); int getChannelCount() const { return m_channels; } @@ -546,27 +602,25 @@ protected: SRC_STATE *m_src; float *m_iin; float *m_iout; - float m_lastRatio; int m_channels; int m_iinsize; int m_ioutsize; int m_debugLevel; }; -D_SRC::D_SRC(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel) : +D_SRC::D_SRC(Resampler::Quality quality, int channels, double, + int maxBufferSize, int debugLevel) : m_src(0), m_iin(0), m_iout(0), - m_lastRatio(1.f), m_channels(channels), m_iinsize(0), m_ioutsize(0), m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - std::cerr << "Resampler::Resampler: using libsamplerate implementation" - << std::endl; + cerr << "Resampler::Resampler: using libsamplerate implementation" + << endl; } int err = 0; @@ -576,8 +630,8 @@ D_SRC::D_SRC(Resampler::Quality quality, int channels, int maxBufferSize, channels, &err); if (err) { - std::cerr << "Resampler::Resampler: failed to create libsamplerate resampler: " - << src_strerror(err) << std::endl; + cerr << "Resampler::Resampler: failed to create libsamplerate resampler: " + << src_strerror(err) << endl; #ifndef NO_EXCEPTIONS throw Resampler::ImplementationError; #endif @@ -601,125 +655,45 @@ D_SRC::~D_SRC() } int -D_SRC::resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, +D_SRC::resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final) { - SRC_DATA data; - - static size_t n_in = 0, n_out = 0; - - std::cerr << "Resampler::process: at input sample " << n_in << ", output sample " << n_out << ", incount = " << incount << ", ratio = " << ratio << ", incount * ratio = " << incount * ratio << std::endl; - - int outcount = lrintf(ceilf(incount * ratio) + 10); - if (m_channels == 1) { - data.data_in = const_cast(*in); //!!!??? - data.data_out = *out; - } else { - if (incount * m_channels > m_iinsize) { - m_iin = reallocate(m_iin, m_iinsize, incount * m_channels); - m_iinsize = incount * m_channels; - } - if (outcount * m_channels > m_ioutsize) { - m_iout = reallocate(m_iout, m_ioutsize, outcount * m_channels); - m_ioutsize = outcount * m_channels; - } - v_interleave(m_iin, in, m_channels, incount); - data.data_in = m_iin; - data.data_out = m_iout; + return resampleInterleaved(*out, outcount, *in, incount, ratio, final); } - data.src_ratio = ratio; - - int err = 0; - -#ifdef PERFORM_LIBSAMPLERATE_XFADE - const int xfade = 10; - if (ratio != m_lastRatio && outcount > xfade) { - int xin = lrintf(ceilf(xfade / ratio)); - SRC_STATE *xsrc = src_clone(m_src, &err); - float *xbuf = 0; - if (err) { - std::cerr << "Resampler::process: libsamplerate error: " - << src_strerror(err) << ", skipping xfade" << std::endl; - } else { - - data.input_frames = xin; - data.output_frames = xfade; - data.end_of_input = false; - - err = src_process(xsrc, &data); - xbuf = allocate(xfade * m_channels); - v_copy(xbuf, data.data_out, xfade * m_channels); - src_delete(xsrc); - } - - data.input_frames = incount; - data.output_frames = outcount; - data.end_of_input = (final ? 1 : 0); - - src_set_ratio(m_src, ratio); - err = src_process(m_src, &data); - - if (xbuf) { - for (int i = 0; i < xfade; ++i) { - for (int c = 0; c < m_channels; ++c) { - float g = float(i+1) / float(xfade); - float f = data.data_out[i * m_channels + c] * g + - xbuf[i * m_channels + c] * (1.f - g); - data.data_out[i * m_channels + c] = f; - } - } - deallocate(xbuf); - } - } else { -#endif - data.input_frames = incount; - data.output_frames = outcount; - data.end_of_input = (final ? 1 : 0); - err = src_process(m_src, &data); -#ifdef PERFORM_LIBSAMPLERATE_XFADE + if (incount * m_channels > m_iinsize) { + m_iin = reallocate(m_iin, m_iinsize, incount * m_channels); + m_iinsize = incount * m_channels; } -#endif - - if (err) { - std::cerr << "Resampler::process: libsamplerate error: " - << src_strerror(err) << std::endl; -#ifndef NO_EXCEPTIONS - throw Resampler::ImplementationError; -#endif + if (outcount * m_channels > m_ioutsize) { + m_iout = reallocate(m_iout, m_ioutsize, outcount * m_channels); + m_ioutsize = outcount * m_channels; } - - if (m_channels > 1) { - v_deinterleave(out, m_iout, m_channels, data.output_frames_gen); - } - - m_lastRatio = ratio; - - n_in += incount; - n_out += data.output_frames_gen; - - std::cerr << "outcount = " << data.output_frames_gen << std::endl; - -// out[0][0] = 1.0; - return data.output_frames_gen; + v_interleave(m_iin, in, m_channels, incount); + + int n = resampleInterleaved(m_iout, outcount, m_iin, incount, ratio, final); + + v_deinterleave(out, m_iout, m_channels, n); + + return n; } int -D_SRC::resampleInterleaved(const float *const R__ in, - float *const R__ out, +D_SRC::resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final) { SRC_DATA data; - int outcount = lrintf(ceilf(incount * ratio) + 10); - data.data_in = const_cast(in); data.data_out = out; @@ -727,20 +701,18 @@ D_SRC::resampleInterleaved(const float *const R__ in, data.output_frames = outcount; data.src_ratio = ratio; data.end_of_input = (final ? 1 : 0); - + int err = src_process(m_src, &data); if (err) { - std::cerr << "Resampler::process: libsamplerate error: " - << src_strerror(err) << std::endl; + cerr << "Resampler::process: libsamplerate error: " + << src_strerror(err) << endl; #ifndef NO_EXCEPTIONS throw Resampler::ImplementationError; #endif } - m_lastRatio = ratio; - - return data.output_frames_gen; + return (int)data.output_frames_gen; } void @@ -753,23 +725,25 @@ D_SRC::reset() #ifdef HAVE_LIBRESAMPLE -class D_Resample : public ResamplerImpl +class D_Resample : public Resampler::Impl { public: - D_Resample(Resampler::Quality quality, int channels, int maxBufferSize, - int m_debugLevel); + D_Resample(Resampler::Quality quality, int channels, double initialSampleRate, + int maxBufferSize, int m_debugLevel); ~D_Resample(); - int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, + int resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final); - int resampleInterleaved(const float *const R__ in, - float *const R__ out, + int resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final); int getChannelCount() const { return m_channels; } @@ -780,27 +754,26 @@ protected: void *m_src; float *m_iin; float *m_iout; - float m_lastRatio; + double m_lastRatio; int m_channels; int m_iinsize; int m_ioutsize; int m_debugLevel; }; -D_Resample::D_Resample(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel) : +D_Resample::D_Resample(Resampler::Quality quality, + int channels, double, int maxBufferSize, int debugLevel) : m_src(0), m_iin(0), m_iout(0), - m_lastRatio(1.f), m_channels(channels), m_iinsize(0), m_ioutsize(0), m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - std::cerr << "Resampler::Resampler: using libresample implementation" - << std::endl; + cerr << "Resampler::Resampler: using libresample implementation" + << endl; } float min_factor = 0.125f; @@ -809,8 +782,8 @@ D_Resample::D_Resample(Resampler::Quality quality, int channels, int maxBufferSi m_src = resample_open(quality == Resampler::Best ? 1 : 0, min_factor, max_factor); if (!m_src) { - std::cerr << "Resampler::Resampler: failed to create libresample resampler: " - << std::endl; + cerr << "Resampler::Resampler: failed to create libresample resampler: " + << endl; throw Resampler::ImplementationError; //!!! of course, need to catch this! } @@ -836,10 +809,11 @@ D_Resample::~D_Resample() } int -D_Resample::resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, +D_Resample::resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final) { float *data_in; @@ -847,7 +821,7 @@ D_Resample::resample(const float *const R__ *const R__ in, int input_frames, output_frames, end_of_input, source_used; float src_ratio; - int outcount = lrintf(ceilf(incount * ratio)); + int outcount = (int)lrint(ceil(incount * ratio)); if (m_channels == 1) { data_in = const_cast(*in); //!!!??? @@ -881,8 +855,8 @@ D_Resample::resample(const float *const R__ *const R__ in, output_frames); if (output_frames_gen < 0) { - std::cerr << "Resampler::process: libresample error: " - << std::endl; + cerr << "Resampler::process: libresample error: " + << endl; throw Resampler::ImplementationError; //!!! of course, need to catch this! } @@ -890,22 +864,21 @@ D_Resample::resample(const float *const R__ *const R__ in, v_deinterleave(out, m_iout, m_channels, output_frames_gen); } - m_lastRatio = ratio; - return output_frames_gen; } int -D_Resample::resampleInterleaved(const float *const R__ in, - float *const R__ out, +D_Resample::resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final) { int input_frames, output_frames, end_of_input, source_used; float src_ratio; - int outcount = lrintf(ceilf(incount * ratio)); + int outcount = (int)lrint(ceil(incount * ratio)); input_frames = incount; output_frames = outcount; @@ -922,13 +895,11 @@ D_Resample::resampleInterleaved(const float *const R__ in, output_frames); if (output_frames_gen < 0) { - std::cerr << "Resampler::process: libresample error: " - << std::endl; + cerr << "Resampler::process: libresample error: " + << endl; throw Resampler::ImplementationError; //!!! of course, need to catch this! } - m_lastRatio = ratio; - return output_frames_gen; } @@ -941,23 +912,25 @@ D_Resample::reset() #ifdef USE_SPEEX -class D_Speex : public ResamplerImpl +class D_Speex : public Resampler::Impl { public: - D_Speex(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel); + D_Speex(Resampler::Quality quality, int channels, double initialSampleRate, + int maxBufferSize, int debugLevel); ~D_Speex(); - int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, + int resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final); - int resampleInterleaved(const float *const R__ in, - float *const R__ out, + int resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final = false); int getChannelCount() const { return m_channels; } @@ -966,27 +939,33 @@ public: protected: SpeexResamplerState *m_resampler; + double m_initialSampleRate; float *m_iin; float *m_iout; int m_channels; int m_iinsize; int m_ioutsize; - float m_lastratio; + double m_lastratio; bool m_initial; int m_debugLevel; - void setRatio(float); + void setRatio(double); + void doResample(const float *in, unsigned int &incount, + float *out, unsigned int &outcount, + double ratio, bool final); }; -D_Speex::D_Speex(Resampler::Quality quality, int channels, int maxBufferSize, - int debugLevel) : +D_Speex::D_Speex(Resampler::Quality quality, + int channels, double initialSampleRate, + int maxBufferSize, int debugLevel) : m_resampler(0), + m_initialSampleRate(initialSampleRate), m_iin(0), m_iout(0), m_channels(channels), m_iinsize(0), m_ioutsize(0), - m_lastratio(1), + m_lastratio(-1.0), m_initial(true), m_debugLevel(debugLevel) { @@ -994,24 +973,23 @@ D_Speex::D_Speex(Resampler::Quality quality, int channels, int maxBufferSize, quality == Resampler::Fastest ? 0 : 4); if (m_debugLevel > 0) { - std::cerr << "Resampler::Resampler: using Speex implementation with q = " - << q - << std::endl; + cerr << "Resampler::Resampler: using Speex implementation with q = " + << q << endl; } - q = 10; //!!! + int rrate = int(round(m_initialSampleRate)); int err = 0; m_resampler = speex_resampler_init_frac(m_channels, 1, 1, - 48000, 48000, // irrelevant + rrate, rrate, q, &err); if (err) { - std::cerr << "Resampler::Resampler: failed to create Speex resampler" - << std::endl; + cerr << "Resampler::Resampler: failed to create Speex resampler" + << endl; #ifndef NO_EXCEPTIONS throw Resampler::ImplementationError; #endif @@ -1033,7 +1011,7 @@ D_Speex::~D_Speex() } void -D_Speex::setRatio(float ratio) +D_Speex::setRatio(double ratio) { // Speex wants a ratio of two unsigned integers, not a single // float. Let's do that. @@ -1052,21 +1030,31 @@ D_Speex::setRatio(float ratio) } if (m_debugLevel > 1) { - std::cerr << "D_Speex: Desired ratio " << ratio << ", requesting ratio " - << num << "/" << denom << " = " << float(double(num)/double(denom)) - << std::endl; + cerr << "D_Speex: Desired ratio " << ratio << ", requesting ratio " + << num << "/" << denom << " = " << float(double(num)/double(denom)) + << endl; } + + int fromRate = int(round(m_initialSampleRate)); + int toRate = int(round(m_initialSampleRate * ratio)); int err = speex_resampler_set_rate_frac - (m_resampler, denom, num, 48000, 48000); - //!!! check err + (m_resampler, denom, num, fromRate, toRate); + + if (err) { + cerr << "Resampler::Resampler: failed to set rate on Speex resampler" + << endl; +#ifndef NO_EXCEPTIONS + throw Resampler::ImplementationError; +#endif + } speex_resampler_get_ratio(m_resampler, &denom, &num); if (m_debugLevel > 1) { - std::cerr << "D_Speex: Desired ratio " << ratio << ", got ratio " - << num << "/" << denom << " = " << float(double(num)/double(denom)) - << std::endl; + cerr << "D_Speex: Desired ratio " << ratio << ", got ratio " + << num << "/" << denom << " = " << float(double(num)/double(denom)) + << endl; } m_lastratio = ratio; @@ -1078,10 +1066,11 @@ D_Speex::setRatio(float ratio) } int -D_Speex::resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, +D_Speex::resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final) { if (ratio != m_lastratio) { @@ -1089,10 +1078,7 @@ D_Speex::resample(const float *const R__ *const R__ in, } unsigned int uincount = incount; - - // This doesn't have to be exact, but it does have to be - // sufficient, hence going over by one - unsigned int outcount = lrintf(ceilf((incount + 1) * ratio)); + unsigned int uoutcount = outcount; float *data_in, *data_out; @@ -1100,11 +1086,11 @@ D_Speex::resample(const float *const R__ *const R__ in, data_in = const_cast(*in); data_out = *out; } else { - if (incount * m_channels > m_iinsize) { + if (int(incount * m_channels) > m_iinsize) { m_iin = reallocate(m_iin, m_iinsize, incount * m_channels); m_iinsize = incount * m_channels; } - if (outcount * m_channels > m_ioutsize) { + if (int(outcount * m_channels) > m_ioutsize) { m_iout = reallocate(m_iout, m_ioutsize, outcount * m_channels); m_ioutsize = outcount * m_channels; } @@ -1113,38 +1099,21 @@ D_Speex::resample(const float *const R__ *const R__ in, data_out = m_iout; } - int err = speex_resampler_process_interleaved_float(m_resampler, - data_in, - &uincount, - data_out, - &outcount); - - if (incount != int(uincount)) { - std::cerr << "Resampler: NOTE: Consumed " << uincount - << " of " << incount << " frames" << std::endl; - } - - if (outcount != lrintf(ceilf(incount * ratio))) { - std::cerr << "Resampler: NOTE: Obtained " << outcount - << " of " << lrintf(ceilf(incount * ratio)) << " frames" - << std::endl; - } - - //!!! check err, respond appropriately - + doResample(data_in, uincount, data_out, uoutcount, ratio, final); if (m_channels > 1) { - v_deinterleave(out, m_iout, m_channels, outcount); + v_deinterleave(out, m_iout, m_channels, uoutcount); } - return outcount; + return uoutcount; } int -D_Speex::resampleInterleaved(const float *const R__ in, - float *const R__ out, +D_Speex::resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final) { if (ratio != m_lastratio) { @@ -1152,20 +1121,60 @@ D_Speex::resampleInterleaved(const float *const R__ in, } unsigned int uincount = incount; - unsigned int outcount = lrintf(ceilf(incount * ratio)); //!!! inexact now + unsigned int uoutcount = outcount; float *data_in = const_cast(in); float *data_out = out; - int err = speex_resampler_process_interleaved_float(m_resampler, - data_in, - &uincount, - data_out, - &outcount); + doResample(data_in, uincount, data_out, uoutcount, ratio, final); + + return uoutcount; +} -// std::cerr << "D_SPEEX: incount " << incount << " ratio " << ratio << " req " << lrintf(ceilf(incount * ratio)) << " final " << final << " output_frames_gen " << outcount << std::endl; +void +D_Speex::doResample(const float *data_in, unsigned int &uincount, + float *data_out, unsigned int &uoutcount, + double ratio, bool final) +{ + int initial_outcount = int(uoutcount); + + int err = speex_resampler_process_interleaved_float + (m_resampler, + data_in, &uincount, + data_out, &uoutcount); + + if (err) { + cerr << "Resampler::Resampler: Speex resampler returned error " + << err << endl; +#ifndef NO_EXCEPTIONS + throw Resampler::ImplementationError; +#endif + } - return outcount; + if (final) { + int actual = int(uoutcount); + int expected = std::min(initial_outcount, int(round(uincount * ratio))); + if (actual < expected) { + unsigned int final_out = expected - actual; + unsigned int final_in = (unsigned int)(round(final_out / ratio)); + if (final_in > 0) { + float *pad = allocate_and_zero(final_in * m_channels); + err = speex_resampler_process_interleaved_float + (m_resampler, + pad, &final_in, + data_out + actual * m_channels, &final_out); + deallocate(pad); + uoutcount += final_out; + if (err) { + cerr << "Resampler::Resampler: Speex resampler returned error " + << err << endl; +#ifndef NO_EXCEPTIONS + throw Resampler::ImplementationError; +#endif + } + } + } + } } void @@ -1180,12 +1189,15 @@ D_Speex::reset() } /* end namespace Resamplers */ -Resampler::Resampler(Resampler::Quality quality, int channels, - int maxBufferSize, int debugLevel) +Resampler::Resampler(Resampler::Parameters params, int channels) { m_method = -1; + + if (params.initialSampleRate == 0) { + params.initialSampleRate = 44100; + } - switch (quality) { + switch (params.quality) { case Resampler::Best: #ifdef HAVE_IPP @@ -1234,63 +1246,63 @@ Resampler::Resampler(Resampler::Quality quality, int channels, } if (m_method == -1) { - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize << "): No implementation available!" - << std::endl; + cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); } switch (m_method) { case 0: #ifdef HAVE_IPP - d = new Resamplers::D_IPP(quality, channels, maxBufferSize, debugLevel); + d = new Resamplers::D_IPP + (params.quality, + channels, + params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize << "): No implementation available!" - << std::endl; + cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); #endif break; case 1: #ifdef HAVE_LIBSAMPLERATE - d = new Resamplers::D_SRC(quality, channels, maxBufferSize, debugLevel); + d = new Resamplers::D_SRC + (params.quality, + channels, + params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize << "): No implementation available!" - << std::endl; + cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); #endif break; case 2: #ifdef USE_SPEEX - d = new Resamplers::D_Speex(quality, channels, maxBufferSize, debugLevel); + d = new Resamplers::D_Speex + (params.quality, + channels, + params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize << "): No implementation available!" - << std::endl; + cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); #endif break; case 3: #ifdef HAVE_LIBRESAMPLE - d = new Resamplers::D_Resample(quality, channels, maxBufferSize, debugLevel); + d = new Resamplers::D_Resample + (params.quality, + channels, + params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize << "): No implementation available!" - << std::endl; + cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); #endif break; } if (!d) { - std::cerr << "Resampler::Resampler(" << quality << ", " << channels - << ", " << maxBufferSize - << "): Internal error: No implementation selected" - << std::endl; + cerr << "Resampler::Resampler: Internal error: No implementation selected" + << endl; abort(); } } @@ -1301,21 +1313,27 @@ Resampler::~Resampler() } int -Resampler::resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, - int incount, float ratio, bool final) +Resampler::resample(float *const R__ *const R__ out, + int outcount, + const float *const R__ *const R__ in, + int incount, + double ratio, + bool final) { Profiler profiler("Resampler::resample"); - return d->resample(in, out, incount, ratio, final); + return d->resample(out, outcount, in, incount, ratio, final); } int -Resampler::resampleInterleaved(const float *const R__ in, - float *const R__ out, - int incount, float ratio, bool final) +Resampler::resampleInterleaved(float *const R__ out, + int outcount, + const float *const R__ in, + int incount, + double ratio, + bool final) { - Profiler profiler("Resampler::resample"); - return d->resampleInterleaved(in, out, incount, ratio, final); + Profiler profiler("Resampler::resampleInterleaved"); + return d->resampleInterleaved(out, outcount, in, incount, ratio, final); } int diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index 5b51400..16b0d06 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -21,60 +21,118 @@ you must obtain a valid commercial licence before doing so. */ -#ifndef _RUBBERBAND_RESAMPLER_H_ -#define _RUBBERBAND_RESAMPLER_H_ +#ifndef RUBBERBAND_RESAMPLER_H +#define RUBBERBAND_RESAMPLER_H #include "system/sysutils.h" namespace RubberBand { -class ResamplerImpl; - class Resampler { public: enum Quality { Best, FastestTolerable, Fastest }; enum Exception { ImplementationError }; + struct Parameters { + + /** + * Resampler filter quality level. + */ + Quality quality; + + /** + * Rate of expected input prior to resampling: may be used to + * determine the filter bandwidth for the quality setting. If + * you don't know what this will be, you can provide an + * arbitrary rate (such as the default) and the resampler will + * work fine, but quality may not be as designed. + */ + double initialSampleRate; + + /** + * Bound on the maximum incount size that may be passed to the + * resample function before the resampler needs to reallocate + * its internal buffers. Default is zero, so that buffer + * allocation will happen on the first call and any subsequent + * call with a greater incount. + */ + int maxBufferSize; + + /** + * Debug output level, from 0 to 3. Controls the amount of + * debug information printed to stderr. + */ + int debugLevel; + + Parameters() : + quality(FastestTolerable), + initialSampleRate(44100), + maxBufferSize(0), + debugLevel(0) { } + }; + /** - * Construct a resampler with the given quality level and channel - * count. maxBufferSize gives a bound on the maximum incount size - * that may be passed to the resample function before the - * resampler needs to reallocate its internal buffers. + * Construct a resampler to process the given number of channels, + * with the given quality level, initial sample rate, and other + * parameters. */ - Resampler(Quality quality, int channels, int maxBufferSize = 0, - int debugLevel = 0); + Resampler(Parameters parameters, int channels); + ~Resampler(); /** * Resample the given multi-channel buffers, where incount is the - * number of frames in the input buffers. Returns the number of - * frames written to the output buffers. + * number of frames in the input buffers and outspace is the space + * available in the output buffers. Generally you want outspace to + * be at least ceil(incount * ratio). + * + * Returns the number of frames written to the output + * buffers. This may be smaller than outspace even where the ratio + * suggests otherwise, particularly at the start of processing + * where there may be a filter tail to allow for. */ - int resample(const float *const R__ *const R__ in, - float *const R__ *const R__ out, +#ifdef __GNUC__ + __attribute__((warn_unused_result)) +#endif + int resample(float *const R__ *const R__ out, + int outspace, + const float *const R__ *const R__ in, int incount, - float ratio, + double ratio, bool final = false); /** * Resample the given interleaved buffer, where incount is the * number of frames in the input buffer (i.e. it has incount * - * getChannelCount() samples). Returns the number of frames - * written to the output buffer. + * getChannelCount() samples) and outspace is the space available + * in frames in the output buffer (i.e. it has space for at least + * outspace * getChannelCount() samples). Generally you want + * outspace to be at least ceil(incount * ratio). + * + * Returns the number of frames written to the output buffer. This + * may be smaller than outspace even where the ratio suggests + * otherwise, particularly at the start of processing where there + * may be a filter tail to allow for. */ - int resampleInterleaved(const float *const R__ in, - float *const R__ out, +#ifdef __GNUC__ + __attribute__((warn_unused_result)) +#endif + int resampleInterleaved(float *const R__ out, + int outspace, + const float *const R__ in, int incount, - float ratio, + double ratio, bool final = false); int getChannelCount() const; void reset(); + class Impl; + protected: - ResamplerImpl *d; + Impl *d; int m_method; }; diff --git a/src/system/Allocators.h b/src/system/Allocators.h index 2f1fbbf..0783bdc 100644 --- a/src/system/Allocators.h +++ b/src/system/Allocators.h @@ -21,8 +21,8 @@ you must obtain a valid commercial licence before doing so. */ -#ifndef _RUBBERBAND_ALLOCATORS_H_ -#define _RUBBERBAND_ALLOCATORS_H_ +#ifndef RUBBERBAND_ALLOCATORS_H +#define RUBBERBAND_ALLOCATORS_H #include "VectorOps.h"