| /* |
| * 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 RESAMPLER_MULTICHANNEL_RESAMPLER_H |
| #define RESAMPLER_MULTICHANNEL_RESAMPLER_H |
| |
| #include <memory> |
| #include <vector> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #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 |
| |
| #include "ResamplerDefinitions.h" |
| |
| namespace RESAMPLER_OUTER_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. |
| * |
| * Note that this value is ignored when upsampling, which is when |
| * the outputRate is higher than the inputRate. |
| * |
| * @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. |
| * |
| * Note that normalizedCutoff is ignored when upsampling, which is when |
| * the outputRate is higher than the inputRate. |
| * |
| * @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<float> mCoefficients; |
| |
| const int mNumTaps; |
| int mCursor = 0; |
| std::vector<float> mX; // delayed input values for the FIR |
| std::vector<float> 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; |
| }; |
| |
| } /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ |
| |
| #endif //RESAMPLER_MULTICHANNEL_RESAMPLER_H |