1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <math.h>
18 
19 #include "IntegerRatio.h"
20 #include "LinearResampler.h"
21 #include "MultiChannelResampler.h"
22 #include "PolyphaseResampler.h"
23 #include "PolyphaseResamplerMono.h"
24 #include "PolyphaseResamplerStereo.h"
25 #include "SincResampler.h"
26 #include "SincResamplerStereo.h"
27 
28 using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
29 
MultiChannelResampler(const MultiChannelResampler::Builder & builder)30 MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
31         : mNumTaps(builder.getNumTaps())
32         , mX(static_cast<size_t>(builder.getChannelCount())
33                 * static_cast<size_t>(builder.getNumTaps()) * 2)
34         , mSingleFrame(builder.getChannelCount())
35         , mChannelCount(builder.getChannelCount())
36         {
37     // Reduce sample rates to the smallest ratio.
38     // For example 44100/48000 would become 147/160.
39     IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
40     ratio.reduce();
41     mNumerator = ratio.getNumerator();
42     mDenominator = ratio.getDenominator();
43     mIntegerPhase = mDenominator; // so we start with a write needed
44 }
45 
46 // static factory method
make(int32_t channelCount,int32_t inputRate,int32_t outputRate,Quality quality)47 MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
48                                                    int32_t inputRate,
49                                                    int32_t outputRate,
50                                                    Quality quality) {
51     Builder builder;
52     builder.setInputRate(inputRate);
53     builder.setOutputRate(outputRate);
54     builder.setChannelCount(channelCount);
55 
56     switch (quality) {
57         case Quality::Fastest:
58             builder.setNumTaps(2);
59             break;
60         case Quality::Low:
61             builder.setNumTaps(4);
62             break;
63         case Quality::Medium:
64         default:
65             builder.setNumTaps(8);
66             break;
67         case Quality::High:
68             builder.setNumTaps(16);
69             break;
70         case Quality::Best:
71             builder.setNumTaps(32);
72             break;
73     }
74 
75     // Set the cutoff frequency so that we do not get aliasing when down-sampling.
76     if (inputRate > outputRate) {
77         builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
78     }
79     return builder.build();
80 }
81 
build()82 MultiChannelResampler *MultiChannelResampler::Builder::build() {
83     if (getNumTaps() == 2) {
84         // Note that this does not do low pass filteringh.
85         return new LinearResampler(*this);
86     }
87     IntegerRatio ratio(getInputRate(), getOutputRate());
88     ratio.reduce();
89     bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
90     if (usePolyphase) {
91         if (getChannelCount() == 1) {
92             return new PolyphaseResamplerMono(*this);
93         } else if (getChannelCount() == 2) {
94             return new PolyphaseResamplerStereo(*this);
95         } else {
96             return new PolyphaseResampler(*this);
97         }
98     } else {
99         // Use less optimized resampler that uses a float phaseIncrement.
100         // TODO mono resampler
101         if (getChannelCount() == 2) {
102             return new SincResamplerStereo(*this);
103         } else {
104             return new SincResampler(*this);
105         }
106     }
107 }
108 
writeFrame(const float * frame)109 void MultiChannelResampler::writeFrame(const float *frame) {
110     // Move cursor before write so that cursor points to last written frame in read.
111     if (--mCursor < 0) {
112         mCursor = getNumTaps() - 1;
113     }
114     float *dest = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
115     int offset = getNumTaps() * getChannelCount();
116     for (int channel = 0; channel < getChannelCount(); channel++) {
117         // Write twice so we avoid having to wrap when reading.
118         dest[channel] = dest[channel + offset] = frame[channel];
119     }
120 }
121 
sinc(float radians)122 float MultiChannelResampler::sinc(float radians) {
123     if (abs(radians) < 1.0e-9) return 1.0f;   // avoid divide by zero
124     return sinf(radians) / radians;   // Sinc function
125 }
126 
127 // Generate coefficients in the order they will be used by readFrame().
128 // This is more complicated but readFrame() is called repeatedly and should be optimized.
generateCoefficients(int32_t inputRate,int32_t outputRate,int32_t numRows,double phaseIncrement,float normalizedCutoff)129 void MultiChannelResampler::generateCoefficients(int32_t inputRate,
130                                               int32_t outputRate,
131                                               int32_t numRows,
132                                               double phaseIncrement,
133                                               float normalizedCutoff) {
134     mCoefficients.resize(static_cast<size_t>(getNumTaps()) * static_cast<size_t>(numRows));
135     int coefficientIndex = 0;
136     double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
137     // Stretch the sinc function for low pass filtering.
138     const float cutoffScaler = (outputRate < inputRate)
139              ? (normalizedCutoff * (float)outputRate / inputRate)
140              : 1.0f; // Do not filter when upsampling.
141     const int numTapsHalf = getNumTaps() / 2; // numTaps must be even.
142     const float numTapsHalfInverse = 1.0f / numTapsHalf;
143     for (int i = 0; i < numRows; i++) {
144         float tapPhase = phase - numTapsHalf;
145         float gain = 0.0; // sum of raw coefficients
146         int gainCursor = coefficientIndex;
147         for (int tap = 0; tap < getNumTaps(); tap++) {
148             float radians = tapPhase * M_PI;
149 
150 #if MCR_USE_KAISER
151             float window = mKaiserWindow(tapPhase * numTapsHalfInverse);
152 #else
153             float window = mCoshWindow(static_cast<double>(tapPhase) * numTapsHalfInverse);
154 #endif
155             float coefficient = sinc(radians * cutoffScaler) * window;
156             mCoefficients.at(coefficientIndex++) = coefficient;
157             gain += coefficient;
158             tapPhase += 1.0;
159         }
160         phase += phaseIncrement;
161         while (phase >= 1.0) {
162             phase -= 1.0;
163         }
164 
165         // Correct for gain variations.
166         float gainCorrection = 1.0 / gain; // normalize the gain
167         for (int tap = 0; tap < getNumTaps(); tap++) {
168             mCoefficients.at(gainCursor + tap) *= gainCorrection;
169         }
170     }
171 }
172