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;
29 
MultiChannelResampler(const MultiChannelResampler::Builder & builder)30 MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
31         : mNumTaps(builder.getNumTaps())
32         , mX(builder.getChannelCount() * builder.getNumTaps() * 2)
33         , mSingleFrame(builder.getChannelCount())
34         , mChannelCount(builder.getChannelCount())
35         {
36     // Reduce sample rates to the smallest ratio.
37     // For example 44100/48000 would become 147/160.
38     IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
39     ratio.reduce();
40     mNumerator = ratio.getNumerator();
41     mDenominator = ratio.getDenominator();
42     mIntegerPhase = mDenominator;
43 }
44 
45 // static factory method
make(int32_t channelCount,int32_t inputRate,int32_t outputRate,Quality quality)46 MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
47                                                    int32_t inputRate,
48                                                    int32_t outputRate,
49                                                    Quality quality) {
50     Builder builder;
51     builder.setInputRate(inputRate);
52     builder.setOutputRate(outputRate);
53     builder.setChannelCount(channelCount);
54 
55     switch (quality) {
56         case Quality::Fastest:
57             builder.setNumTaps(2);
58             break;
59         case Quality::Low:
60             builder.setNumTaps(4);
61             break;
62         case Quality::Medium:
63         default:
64             builder.setNumTaps(8);
65             break;
66         case Quality::High:
67             builder.setNumTaps(16);
68             break;
69         case Quality::Best:
70             builder.setNumTaps(32);
71             break;
72     }
73 
74     // Set the cutoff frequency so that we do not get aliasing when down-sampling.
75     if (inputRate > outputRate) {
76         builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
77     }
78     return builder.build();
79 }
80 
build()81 MultiChannelResampler *MultiChannelResampler::Builder::build() {
82     if (getNumTaps() == 2) {
83         // Note that this does not do low pass filteringh.
84         return new LinearResampler(*this);
85     }
86     IntegerRatio ratio(getInputRate(), getOutputRate());
87     ratio.reduce();
88     bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
89     if (usePolyphase) {
90         if (getChannelCount() == 1) {
91             return new PolyphaseResamplerMono(*this);
92         } else if (getChannelCount() == 2) {
93             return new PolyphaseResamplerStereo(*this);
94         } else {
95             return new PolyphaseResampler(*this);
96         }
97     } else {
98         // Use less optimized resampler that uses a float phaseIncrement.
99         // TODO mono resampler
100         if (getChannelCount() == 2) {
101             return new SincResamplerStereo(*this);
102         } else {
103             return new SincResampler(*this);
104         }
105     }
106 }
107 
writeFrame(const float * frame)108 void MultiChannelResampler::writeFrame(const float *frame) {
109     // Move cursor before write so that cursor points to last written frame in read.
110     if (--mCursor < 0) {
111         mCursor = getNumTaps() - 1;
112     }
113     float *dest = &mX[mCursor * getChannelCount()];
114     int offset = getNumTaps() * getChannelCount();
115     for (int channel = 0; channel < getChannelCount(); channel++) {
116         // Write twice so we avoid having to wrap when reading.
117         dest[channel] = dest[channel + offset] = frame[channel];
118     }
119 }
120 
sinc(float radians)121 float MultiChannelResampler::sinc(float radians) {
122     if (abs(radians) < 1.0e-9) return 1.0f;   // avoid divide by zero
123     return sinf(radians) / radians;   // Sinc function
124 }
125 
126 // Generate coefficients in the order they will be used by readFrame().
127 // 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)128 void MultiChannelResampler::generateCoefficients(int32_t inputRate,
129                                               int32_t outputRate,
130                                               int32_t numRows,
131                                               double phaseIncrement,
132                                               float normalizedCutoff) {
133     mCoefficients.resize(getNumTaps() * numRows);
134     int coefficientIndex = 0;
135     double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
136     // Stretch the sinc function for low pass filtering.
137     const float cutoffScaler = normalizedCutoff *
138             ((outputRate < inputRate)
139              ? ((float)outputRate / inputRate)
140              : ((float)inputRate / outputRate));
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(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