/* * 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. */ #include #include "IntegerRatio.h" #include "LinearResampler.h" #include "MultiChannelResampler.h" #include "PolyphaseResampler.h" #include "PolyphaseResamplerMono.h" #include "PolyphaseResamplerStereo.h" #include "SincResampler.h" #include "SincResamplerStereo.h" using namespace resampler; MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder) : mNumTaps(builder.getNumTaps()) , mX(builder.getChannelCount() * builder.getNumTaps() * 2) , mSingleFrame(builder.getChannelCount()) , mChannelCount(builder.getChannelCount()) { // Reduce sample rates to the smallest ratio. // For example 44100/48000 would become 147/160. IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate()); ratio.reduce(); mNumerator = ratio.getNumerator(); mDenominator = ratio.getDenominator(); mIntegerPhase = mDenominator; } // static factory method MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount, int32_t inputRate, int32_t outputRate, Quality quality) { Builder builder; builder.setInputRate(inputRate); builder.setOutputRate(outputRate); builder.setChannelCount(channelCount); switch (quality) { case Quality::Fastest: builder.setNumTaps(2); break; case Quality::Low: builder.setNumTaps(4); break; case Quality::Medium: default: builder.setNumTaps(8); break; case Quality::High: builder.setNumTaps(16); break; case Quality::Best: builder.setNumTaps(32); break; } // Set the cutoff frequency so that we do not get aliasing when down-sampling. if (inputRate > outputRate) { builder.setNormalizedCutoff(kDefaultNormalizedCutoff); } return builder.build(); } MultiChannelResampler *MultiChannelResampler::Builder::build() { if (getNumTaps() == 2) { // Note that this does not do low pass filteringh. return new LinearResampler(*this); } IntegerRatio ratio(getInputRate(), getOutputRate()); ratio.reduce(); bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients; if (usePolyphase) { if (getChannelCount() == 1) { return new PolyphaseResamplerMono(*this); } else if (getChannelCount() == 2) { return new PolyphaseResamplerStereo(*this); } else { return new PolyphaseResampler(*this); } } else { // Use less optimized resampler that uses a float phaseIncrement. // TODO mono resampler if (getChannelCount() == 2) { return new SincResamplerStereo(*this); } else { return new SincResampler(*this); } } } void MultiChannelResampler::writeFrame(const float *frame) { // Move cursor before write so that cursor points to last written frame in read. if (--mCursor < 0) { mCursor = getNumTaps() - 1; } float *dest = &mX[mCursor * getChannelCount()]; int offset = getNumTaps() * getChannelCount(); for (int channel = 0; channel < getChannelCount(); channel++) { // Write twice so we avoid having to wrap when reading. dest[channel] = dest[channel + offset] = frame[channel]; } } float MultiChannelResampler::sinc(float radians) { if (abs(radians) < 1.0e-9) return 1.0f; // avoid divide by zero return sinf(radians) / radians; // Sinc function } // Generate coefficients in the order they will be used by readFrame(). // This is more complicated but readFrame() is called repeatedly and should be optimized. void MultiChannelResampler::generateCoefficients(int32_t inputRate, int32_t outputRate, int32_t numRows, double phaseIncrement, float normalizedCutoff) { mCoefficients.resize(getNumTaps() * numRows); int coefficientIndex = 0; double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples // Stretch the sinc function for low pass filtering. const float cutoffScaler = normalizedCutoff * ((outputRate < inputRate) ? ((float)outputRate / inputRate) : ((float)inputRate / outputRate)); const int numTapsHalf = getNumTaps() / 2; // numTaps must be even. const float numTapsHalfInverse = 1.0f / numTapsHalf; for (int i = 0; i < numRows; i++) { float tapPhase = phase - numTapsHalf; float gain = 0.0; // sum of raw coefficients int gainCursor = coefficientIndex; for (int tap = 0; tap < getNumTaps(); tap++) { float radians = tapPhase * M_PI; #if MCR_USE_KAISER float window = mKaiserWindow(tapPhase * numTapsHalfInverse); #else float window = mCoshWindow(tapPhase * numTapsHalfInverse); #endif float coefficient = sinc(radians * cutoffScaler) * window; mCoefficients.at(coefficientIndex++) = coefficient; gain += coefficient; tapPhase += 1.0; } phase += phaseIncrement; while (phase >= 1.0) { phase -= 1.0; } // Correct for gain variations. float gainCorrection = 1.0 / gain; // normalize the gain for (int tap = 0; tap < getNumTaps(); tap++) { mCoefficients.at(gainCursor + tap) *= gainCorrection; } } }