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