/* * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef OBOE_MULTICHANNEL_RESAMPLER_H #define OBOE_MULTICHANNEL_RESAMPLER_H #include #include #include #include #ifndef MCR_USE_KAISER // It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts. // And it is faster to calculate. #define MCR_USE_KAISER 0 #endif #if MCR_USE_KAISER #include "KaiserWindow.h" #else #include "HyperbolicCosineWindow.h" #endif namespace resampler { class MultiChannelResampler { public: enum class Quality : int32_t { Fastest, Low, Medium, High, Best, }; class Builder { public: /** * Construct an optimal resampler based on the specified parameters. * @return address of a resampler */ MultiChannelResampler *build(); /** * The number of taps in the resampling filter. * More taps gives better quality but uses more CPU time. * This typically ranges from 4 to 64. Default is 16. * * For polyphase filters, numTaps must be a multiple of four for loop unrolling. * @param numTaps number of taps for the filter * @return address of this builder for chaining calls */ Builder *setNumTaps(int32_t numTaps) { mNumTaps = numTaps; return this; } /** * Use 1 for mono, 2 for stereo, etc. Default is 1. * * @param channelCount number of channels * @return address of this builder for chaining calls */ Builder *setChannelCount(int32_t channelCount) { mChannelCount = channelCount; return this; } /** * Default is 48000. * * @param inputRate sample rate of the input stream * @return address of this builder for chaining calls */ Builder *setInputRate(int32_t inputRate) { mInputRate = inputRate; return this; } /** * Default is 48000. * * @param outputRate sample rate of the output stream * @return address of this builder for chaining calls */ Builder *setOutputRate(int32_t outputRate) { mOutputRate = outputRate; return this; } /** * Set cutoff frequency relative to the Nyquist rate of the output sample rate. * Set to 1.0 to match the Nyquist frequency. * Set lower to reduce aliasing. * Default is 0.70. * * @param normalizedCutoff anti-aliasing filter cutoff * @return address of this builder for chaining calls */ Builder *setNormalizedCutoff(float normalizedCutoff) { mNormalizedCutoff = normalizedCutoff; return this; } int32_t getNumTaps() const { return mNumTaps; } int32_t getChannelCount() const { return mChannelCount; } int32_t getInputRate() const { return mInputRate; } int32_t getOutputRate() const { return mOutputRate; } float getNormalizedCutoff() const { return mNormalizedCutoff; } protected: int32_t mChannelCount = 1; int32_t mNumTaps = 16; int32_t mInputRate = 48000; int32_t mOutputRate = 48000; float mNormalizedCutoff = kDefaultNormalizedCutoff; }; virtual ~MultiChannelResampler() = default; /** * Factory method for making a resampler that is optimal for the given inputs. * * @param channelCount number of channels, 2 for stereo * @param inputRate sample rate of the input stream * @param outputRate sample rate of the output stream * @param quality higher quality sounds better but uses more CPU * @return an optimal resampler */ static MultiChannelResampler *make(int32_t channelCount, int32_t inputRate, int32_t outputRate, Quality quality); bool isWriteNeeded() const { return mIntegerPhase >= mDenominator; } /** * Write a frame containing N samples. * * @param frame pointer to the first sample in a frame */ void writeNextFrame(const float *frame) { writeFrame(frame); advanceWrite(); } /** * Read a frame containing N samples. * * @param frame pointer to the first sample in a frame */ void readNextFrame(float *frame) { readFrame(frame); advanceRead(); } int getNumTaps() const { return mNumTaps; } int getChannelCount() const { return mChannelCount; } static float hammingWindow(float radians, float spread); static float sinc(float radians); protected: explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder); /** * Write a frame containing N samples. * Call advanceWrite() after calling this. * @param frame pointer to the first sample in a frame */ virtual void writeFrame(const float *frame); /** * Read a frame containing N samples using interpolation. * Call advanceRead() after calling this. * @param frame pointer to the first sample in a frame */ virtual void readFrame(float *frame) = 0; void advanceWrite() { mIntegerPhase -= mDenominator; } void advanceRead() { mIntegerPhase += mNumerator; } /** * Generate the filter coefficients in optimal order. * @param inputRate sample rate of the input stream * @param outputRate sample rate of the output stream * @param numRows number of rows in the array that contain a set of tap coefficients * @param phaseIncrement how much to increment the phase between rows * @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output */ void generateCoefficients(int32_t inputRate, int32_t outputRate, int32_t numRows, double phaseIncrement, float normalizedCutoff); int32_t getIntegerPhase() { return mIntegerPhase; } static constexpr int kMaxCoefficients = 8 * 1024; std::vector mCoefficients; const int mNumTaps; int mCursor = 0; std::vector mX; // delayed input values for the FIR std::vector mSingleFrame; // one frame for temporary use int32_t mIntegerPhase = 0; int32_t mNumerator = 0; int32_t mDenominator = 0; private: #if MCR_USE_KAISER KaiserWindow mKaiserWindow; #else HyperbolicCosineWindow mCoshWindow; #endif static constexpr float kDefaultNormalizedCutoff = 0.70f; const int mChannelCount; }; } #endif //OBOE_MULTICHANNEL_RESAMPLER_H