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