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