oboe: add PolyphaseSincResampler
refactor resampler API
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36802a9..5303f26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,10 +22,13 @@
src/fifo/FifoControllerIndirect.cpp
src/flowgraph/AudioProcessorBase.cpp
src/flowgraph/ClipToRange.cpp
+ src/flowgraph/ContinuousResampler.cpp
+ src/flowgraph/IntegerRatio.cpp
src/flowgraph/LinearResampler.cpp
src/flowgraph/ManyToMultiConverter.cpp
src/flowgraph/MonoToMultiConverter.cpp
src/flowgraph/MultiChannelResampler.cpp
+ src/flowgraph/PolyphaseSincResampler.cpp
src/flowgraph/RampLinear.cpp
src/flowgraph/SampleRateConverter.cpp
src/flowgraph/SincResampler.cpp
diff --git a/apps/OboeTester/app/CMakeLists.txt b/apps/OboeTester/app/CMakeLists.txt
index a3accd9..38616c4 100644
--- a/apps/OboeTester/app/CMakeLists.txt
+++ b/apps/OboeTester/app/CMakeLists.txt
@@ -26,6 +26,7 @@
${OBOE_DIR}/src
)
+
### END OBOE INCLUDE SECTION ###
# link to oboe
diff --git a/src/common/DataConversionFlowGraph.cpp b/src/common/DataConversionFlowGraph.cpp
index 31ee8bd..ac12ef4 100644
--- a/src/common/DataConversionFlowGraph.cpp
+++ b/src/common/DataConversionFlowGraph.cpp
@@ -30,6 +30,7 @@
#include <flowgraph/SourceFloat.h>
#include <flowgraph/SourceI16.h>
#include <flowgraph/SourceI24.h>
+#include <flowgraph/SampleRateConverter.h>
using namespace oboe;
using namespace flowgraph;
@@ -112,11 +113,12 @@
if (sourceSampleRate != sinkSampleRate) {
mResampler.reset(MultiChannelResampler::make(sourceChannelCount,
+ sourceSampleRate,
+ sinkSampleRate,
convertOboeSRQualityToMCR(
stream->getSampleRateConversionType())));
mRateConverter = std::make_unique<SampleRateConverter>(sourceChannelCount,
*mResampler.get());
- mRateConverter->setPhaseIncrement((double) sourceSampleRate / sinkSampleRate);
lastOutput->connect(&mRateConverter->input);
lastOutput = &mRateConverter->output;
}
diff --git a/src/flowgraph/ContinuousResampler.cpp b/src/flowgraph/ContinuousResampler.cpp
new file mode 100644
index 0000000..80e140d
--- /dev/null
+++ b/src/flowgraph/ContinuousResampler.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ContinuousResampler.h"
diff --git a/src/flowgraph/ContinuousResampler.h b/src/flowgraph/ContinuousResampler.h
new file mode 100644
index 0000000..c1f97a8
--- /dev/null
+++ b/src/flowgraph/ContinuousResampler.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_CONTINUOUS_RESAMPLER_H
+#define FLOWGRAPH_CONTINUOUS_RESAMPLER_H
+
+#include <sys/types.h>
+#include <unistd.h>
+#include "MultiChannelResampler.h"
+
+namespace flowgraph {
+
+/*
+ * Resampler that uses a double precision phase internally.
+ */
+class ContinuousResampler : public MultiChannelResampler {
+public:
+ explicit ContinuousResampler(int32_t channelCount,
+ int32_t numTaps,
+ int32_t inputRate,
+ int32_t outputRate)
+ : MultiChannelResampler(channelCount, numTaps, inputRate, outputRate) {
+ mPhaseIncrement = (double) inputRate / (double) outputRate;
+ }
+
+ virtual ~ContinuousResampler() = default;
+
+ bool isWriteReady() const override {
+ return mPhase >= 1.0;
+ }
+
+ virtual void advanceWrite() override {
+ mPhase -= 1.0;
+ }
+
+ bool isReadReady() const override {
+ return mPhase < 1.0;
+ }
+
+ virtual void advanceRead() override {
+ mPhase += mPhaseIncrement;
+ }
+
+ float getPhase() {
+ return (float) mPhase;
+ }
+
+private:
+ double mPhase = 1.0;
+ double mPhaseIncrement = 1.0;
+};
+
+}
+#endif //FLOWGRAPH_CONTINUOUS_RESAMPLER_H
diff --git a/src/flowgraph/IntegerRatio.cpp b/src/flowgraph/IntegerRatio.cpp
new file mode 100644
index 0000000..983c006
--- /dev/null
+++ b/src/flowgraph/IntegerRatio.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "IntegerRatio.h"
+
+using namespace flowgraph;
+
+std::vector<int> IntegerRatio::kPrimes{
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
+ 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
+ 101, 103, 107, 109, 113, 127, 131, 137, 139, 149,
+ 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199};
+
+void IntegerRatio::reduce() {
+ int32_t factoredTop = mNumerator;
+ int32_t factoredBottom = mDenominator;
+ bool downToOne = false;
+ for (int prime : kPrimes) {
+ if (mNumerator < prime
+ || mDenominator < prime
+ || downToOne)
+ break;
+ int topCount = 0;
+ while (true) {
+ int top = factoredTop / prime;
+ if (factoredTop == top * prime) {
+ topCount++;
+ factoredTop = top;
+ downToOne |= top == 1;
+ } else {
+ break;
+ }
+ }
+ int bottomCount = 0;
+ while (true) {
+ int bottom = factoredBottom / prime;
+ if (factoredBottom == bottom * prime) {
+ bottomCount++;
+ factoredBottom = bottom;
+ downToOne |= bottom == 1;
+ } else {
+ break;
+ }
+ }
+
+ int commonCount = std::min(topCount, bottomCount);
+ while (commonCount--) {
+ mNumerator /= prime;
+ mDenominator /= prime;
+ }
+ }
+}
diff --git a/src/flowgraph/IntegerRatio.h b/src/flowgraph/IntegerRatio.h
new file mode 100644
index 0000000..5fb8c1b
--- /dev/null
+++ b/src/flowgraph/IntegerRatio.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_INTEGER_RATIO_H
+#define FLOWGRAPH_INTEGER_RATIO_H
+
+#include <sys/types.h>
+#include <vector>
+
+namespace flowgraph {
+
+class IntegerRatio {
+public:
+ IntegerRatio(int32_t numerator, int32_t denominator)
+ : mNumerator(numerator), mDenominator(denominator) {}
+
+ /** Reduce by removing common prime factors.
+ */
+ void reduce();
+
+ int32_t getNumerator() {
+ return mNumerator;
+ }
+
+ int32_t getDenominator() {
+ return mDenominator;
+ }
+
+private:
+ int32_t mNumerator;
+ int32_t mDenominator;
+ static std::vector<int> kPrimes;
+};
+
+}
+
+#endif //FLOWGRAPH_INTEGER_RATIO_H
diff --git a/src/flowgraph/LinearResampler.cpp b/src/flowgraph/LinearResampler.cpp
index 27f6702..2dc1d9b 100644
--- a/src/flowgraph/LinearResampler.cpp
+++ b/src/flowgraph/LinearResampler.cpp
@@ -18,8 +18,10 @@
using namespace flowgraph;
-LinearResampler::LinearResampler(int32_t channelCount)
- : MultiChannelResampler(channelCount) {
+LinearResampler::LinearResampler(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate)
+ : ContinuousResampler(channelCount, 2 /* numTaps */, inputRate,outputRate) {
mPreviousFrame = std::make_unique<float[]>(channelCount);
mCurrentFrame = std::make_unique<float[]>(channelCount);
}
@@ -29,12 +31,13 @@
memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount());
}
-void LinearResampler::readFrame(float *frame, float mPhase) {
+void LinearResampler::readFrame(float *frame) {
float *previous = mPreviousFrame.get();
float *current = mCurrentFrame.get();
+ float phase = (float) getPhase();
for (int channel = 0; channel < getChannelCount(); channel++) {
float f0 = *previous++;
float f1 = *current++;
- *frame++ = f0 + (mPhase * (f1 - f0));
+ *frame++ = f0 + (phase * (f1 - f0));
}
}
diff --git a/src/flowgraph/LinearResampler.h b/src/flowgraph/LinearResampler.h
index e91048e..b294df1 100644
--- a/src/flowgraph/LinearResampler.h
+++ b/src/flowgraph/LinearResampler.h
@@ -20,17 +20,19 @@
#include <memory>
#include <sys/types.h>
#include <unistd.h>
-#include "MultiChannelResampler.h"
+#include "ContinuousResampler.h"
namespace flowgraph {
-class LinearResampler : public MultiChannelResampler{
+class LinearResampler : public ContinuousResampler {
public:
- explicit LinearResampler(int32_t channelCount);
+ LinearResampler(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate);
void writeFrame(const float *frame) override;
- void readFrame(float *frame, float mPhase) override;
+ void readFrame(float *frame) override;
private:
std::unique_ptr<float[]> mPreviousFrame;
diff --git a/src/flowgraph/MultiChannelResampler.cpp b/src/flowgraph/MultiChannelResampler.cpp
index 62af660..a4b15ab 100644
--- a/src/flowgraph/MultiChannelResampler.cpp
+++ b/src/flowgraph/MultiChannelResampler.cpp
@@ -14,25 +14,60 @@
* limitations under the License.
*/
+#include <math.h>
+
#include "MultiChannelResampler.h"
#include "LinearResampler.h"
#include "SincResampler.h"
+#include "PolyphaseSincResampler.h"
#include "SincResamplerStereo.h"
using namespace flowgraph;
-MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount, Quality quality) {
+MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate,
+ Quality quality) {
switch (quality) {
case Quality::Low:
case Quality::Medium: // TODO polynomial
- return new LinearResampler(channelCount);
+ return new LinearResampler(channelCount, inputRate, outputRate);
default:
case Quality::High:
+ return new PolyphaseSincResampler(channelCount, inputRate, outputRate); // TODO
case Quality::Best:
if (channelCount == 2) {
- return new SincResamplerStereo(); // TODO pass spread
+ return new SincResamplerStereo( inputRate, outputRate); // TODO pass spread
} else {
- return new SincResampler(channelCount); // TODO pass spread
+ return new SincResampler(channelCount, inputRate, outputRate); // TODO pass spread
}
}
}
+
+
+void MultiChannelResampler::writeFrame(const float *frame) {
+ // Advance cursor before write so that cursor points to last written frame in read.
+ if (++mCursor >= getNumTaps()) {
+ mCursor = 0;
+ }
+ int xIndex = mCursor * getChannelCount();
+ int offset = getNumTaps() * getChannelCount();
+ float *dest = &mX[xIndex];
+ for (int channel = 0; channel < getChannelCount(); channel++) {
+ // Write twice so we avoid having to wrap when running the FIR.
+ dest[channel] = dest[channel + offset] = frame[channel];
+ }
+}
+
+
+float MultiChannelResampler::calculateWindowedSinc(float phase, int spread) {
+ const float realPhase = phase - spread;
+ if (abs(realPhase) < 0.00000001) return 1.0f; // avoid divide by zero
+ // Hamming window TODO try Kaiser window
+ const float alpha = 0.54f;
+ const float windowPhase = realPhase * M_PI / spread;
+ const float window = (float) (alpha + ((1.0 - alpha) * cosf(windowPhase)));
+ const float sincPhase = realPhase * M_PI;
+ const float sinc = sinf(sincPhase) / sincPhase;
+ return window * sinc;
+}
diff --git a/src/flowgraph/MultiChannelResampler.h b/src/flowgraph/MultiChannelResampler.h
index ec4671b..2286eba 100644
--- a/src/flowgraph/MultiChannelResampler.h
+++ b/src/flowgraph/MultiChannelResampler.h
@@ -18,35 +18,61 @@
#define FLOWGRAPH_MULTICHANNEL_RESAMPLER_H
#include <memory>
+#include <vector>
#include <sys/types.h>
#include <unistd.h>
namespace flowgraph {
class MultiChannelResampler {
+
public:
- explicit MultiChannelResampler(int32_t channelCount)
- : mChannelCount(channelCount) {}
+
+ MultiChannelResampler(int32_t channelCount,
+ int32_t numTaps,
+ int32_t inputRate,
+ int32_t outputRate)
+ : mChannelCount(channelCount)
+ , mNumTaps(numTaps)
+ , mX(channelCount * getNumTaps() * 2)
+ , mSingleFrame(channelCount)
+ {}
virtual ~MultiChannelResampler() = default;
+ virtual bool isReadReady() const = 0;
+ virtual bool isWriteReady() const = 0;
+
+ virtual void advanceWrite() = 0;
+ virtual void advanceRead() = 0;
+
/**
* Write a frame containing N samples.
- * @param frame
+ * @param frame pointer to the first sample in a frame
*/
- virtual void writeFrame(const float *frame) = 0;
+ virtual void writeFrame(const float *frame);
/**
* Read a frame containing N samples using interpolation.
- * @param frame
+ * @param frame pointer to the first sample in a frame
* @param phase phase between 0.0 and 1.0 for interpolation
*/
- virtual void readFrame(float *frame, float phase) = 0;
+ virtual void readFrame(float *frame) = 0;
+
+ int getNumTaps() const {
+ return mNumTaps;
+ }
int getChannelCount() const {
return mChannelCount;
}
+ /**
+ * @param phase between 0.0 and 2*spread
+ * @return windowedSinc
+ */
+ static float calculateWindowedSinc(float phase, int spread);
+
enum class Quality : int32_t {
Low,
Medium,
@@ -54,10 +80,23 @@
Best,
};
- static MultiChannelResampler *make(int32_t channelCount, Quality quality);
+ static MultiChannelResampler *make(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate,
+ Quality quality);
private:
- const int mChannelCount;
+
+ const int mChannelCount;
+ const int mNumTaps;
+
+protected:
+
+ int mCursor = 0;
+ std::vector<float> mX;
+ std::vector<float> mSingleFrame;
+
+
};
}
diff --git a/src/flowgraph/PolyphaseSincResampler.cpp b/src/flowgraph/PolyphaseSincResampler.cpp
new file mode 100644
index 0000000..9ed3212
--- /dev/null
+++ b/src/flowgraph/PolyphaseSincResampler.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IntegerRatio.h"
+#include "PolyphaseSincResampler.h"
+
+using namespace flowgraph;
+
+PolyphaseSincResampler::PolyphaseSincResampler(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate)
+ : MultiChannelResampler(channelCount, kNumTaps, inputRate, outputRate)
+ {
+ generateCoefficients(inputRate, outputRate);
+}
+
+void PolyphaseSincResampler::generateCoefficients(int32_t inputRate, int32_t outputRate) {
+ IntegerRatio ratio(inputRate, outputRate);
+ ratio.reduce();
+ mNumerator = ratio.getNumerator();
+ mDenominator = ratio.getDenominator();
+ mIntegerPhase = mDenominator;
+ mCoefficients.resize(getNumTaps() * ratio.getDenominator());
+ int cursor = 0;
+ double phase = 0.0;
+ double phaseIncrement = (double) inputRate / (double) outputRate;
+ for (int i = 0; i < ratio.getDenominator(); i++) {
+ float tapPhase = phase;
+ for (int tap = 0; tap < getNumTaps(); tap++) {
+ mCoefficients.at(cursor++) = calculateWindowedSinc(tapPhase, kSpread);
+ tapPhase += 1.0;
+ }
+ phase += phaseIncrement;
+ while (phase >= 1.0) {
+ phase -= 1.0;
+ }
+ }
+}
+void PolyphaseSincResampler::readFrame(float *frame) {
+ // Clear accumulator for mix.
+ for (int channel = 0; channel < getChannelCount(); channel++) {
+ mSingleFrame[channel] = 0.0;
+ }
+
+ float *coefficients = &mCoefficients[mCoefficientCursor];
+ // Multiply input times windowed sinc function.
+ int xIndex = (mCursor + kNumTaps) * getChannelCount();
+ for (int i = 0; i < kNumTaps; i++) {
+ float coefficient = *coefficients++;
+ float *xFrame = &mX[xIndex];
+ for (int channel = 0; channel < getChannelCount(); channel++) {
+ mSingleFrame[channel] += coefficient * xFrame[channel];
+ }
+ xIndex -= getChannelCount();
+ }
+
+ mCoefficientCursor += kNumTaps;
+ if (mCoefficientCursor >= mCoefficients.size()) {
+ mCoefficientCursor = 0;
+ }
+
+ // Copy accumulator to output.
+ for (int channel = 0; channel < getChannelCount(); channel++) {
+ frame[channel] = mSingleFrame[channel];
+ }
+}
diff --git a/src/flowgraph/PolyphaseSincResampler.h b/src/flowgraph/PolyphaseSincResampler.h
new file mode 100644
index 0000000..e64966f
--- /dev/null
+++ b/src/flowgraph/PolyphaseSincResampler.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
+#define FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
+
+
+#include <memory>
+#include <vector>
+#include <sys/types.h>
+#include <unistd.h>
+#include "MultiChannelResampler.h"
+
+namespace flowgraph {
+
+class PolyphaseSincResampler : public MultiChannelResampler {
+public:
+ PolyphaseSincResampler(int32_t channelCount, int32_t inputRate, int32_t outputRate);
+
+ virtual ~PolyphaseSincResampler() = default;
+
+ void readFrame(float *frame) override;
+
+ int getSpread() const {
+ return kSpread;
+ }
+
+ bool isWriteReady() const override {
+ return mIntegerPhase >= mDenominator;
+ }
+
+ virtual void advanceWrite() override {
+ mIntegerPhase -= mDenominator;
+ }
+
+ bool isReadReady() const override {
+ return mIntegerPhase < mDenominator;
+ }
+
+ virtual void advanceRead() override {
+ mIntegerPhase += mNumerator;
+ }
+
+protected:
+
+ void generateCoefficients(int32_t inputRate, int32_t outputRate);
+
+ // Number of zero crossings on one side of central lobe.
+ // Higher numbers provide higher quality but use more CPU.
+ // 2 is the minimum one should use.
+ static constexpr int kSpread = 10;
+ static constexpr int kNumTaps = kSpread * 2; // TODO should be odd, not even
+
+ std::vector<float> mCoefficients;
+ int32_t mCoefficientCursor = 0;
+ int32_t mIntegerPhase = 0;
+ int32_t mNumerator = 0;
+ int32_t mDenominator = 0;
+
+};
+
+}
+
+#endif //FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
diff --git a/src/flowgraph/SampleRateConverter.cpp b/src/flowgraph/SampleRateConverter.cpp
index 84c9bda..55d8310 100644
--- a/src/flowgraph/SampleRateConverter.cpp
+++ b/src/flowgraph/SampleRateConverter.cpp
@@ -45,24 +45,21 @@
int framesLeft = numFrames;
while (framesLeft > 0) {
// Gather input samples as needed.
- while(mPhase >= 1.0) {
- if (!isInputAvailable()) break;
- mPhase -= 1.0;
- const float *frame = getNextInputFrame();
- mResampler.writeFrame(frame);
- }
-
- // If phase >= 1.0 then we are waiting for input data.
- if (mPhase < 1.0) {
+ if(mResampler.isWriteReady()) {
+ if (isInputAvailable()) {
+ const float *frame = getNextInputFrame();
+ mResampler.writeFrame(frame);
+ mResampler.advanceWrite();
+ } else {
+ break;
+ }
+ } else {
// Output frame is interpolated from input samples based on phase.
- mResampler.readFrame(outputBuffer, mPhase);
- mPhase += mPhaseIncrement;
+ mResampler.readFrame(outputBuffer);
+ mResampler.advanceRead();
outputBuffer += channelCount;
framesLeft--;
- } else {
- break;
}
}
return numFrames - framesLeft;
}
-
diff --git a/src/flowgraph/SampleRateConverter.h b/src/flowgraph/SampleRateConverter.h
index 046397c..9e2d900 100644
--- a/src/flowgraph/SampleRateConverter.h
+++ b/src/flowgraph/SampleRateConverter.h
@@ -35,14 +35,6 @@
int32_t onProcess(int32_t numFrames) override;
- double getPhaseIncrement() {
- return mPhaseIncrement;
- }
-
- void setPhaseIncrement(double phaseIncrement) {
- mPhaseIncrement = phaseIncrement;
- }
-
const char *getName() override {
return "SampleRateConverter";
}
@@ -56,8 +48,6 @@
MultiChannelResampler &mResampler;
- double mPhase = 1.0;
- double mPhaseIncrement = 1.0;
int32_t mInputCursor = 0;
int32_t mInputValid = 0;
int64_t mInputFramePosition = 0; // monotonic counter of input frames used for pullData
diff --git a/src/flowgraph/SampleRateConverterVariable.cpp b/src/flowgraph/SampleRateConverterVariable.cpp
new file mode 100644
index 0000000..4a3f103
--- /dev/null
+++ b/src/flowgraph/SampleRateConverterVariable.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SampleRateConverterVariable.h"
+
+using namespace flowgraph;
+
+SampleRateConverterVariable::SampleRateConverterVariable(int32_t channelCount, MultiChannelResampler &resampler)
+ : SampleRateConverterVariable(channelCount, resampler) {
+}
+
+int32_t SampleRateConverterVariable::onProcess(int32_t numFrames) {
+ float *outputBuffer = output.getBuffer();
+ int32_t channelCount = output.getSamplesPerFrame();
+ int framesLeft = numFrames;
+ while (framesLeft > 0) {
+ // Gather input samples as needed.
+ if(mResampler.isWriteReady()) {
+ if (!isInputAvailable()) break;
+ const float *frame = getNextInputFrame();
+ mResampler.writeFrame(frame);
+ }
+
+ // If phase >= 1.0 then we are waiting for input data.
+ if (mResampler.isReadReady()) {
+ // Output frame is interpolated from input samples based on phase.
+ mResampler.readFrame(outputBuffer);
+ outputBuffer += channelCount;
+ framesLeft--;
+ } else {
+ break;
+ }
+ }
+ return numFrames - framesLeft;
+}
diff --git a/src/flowgraph/SampleRateConverterVariable.h b/src/flowgraph/SampleRateConverterVariable.h
new file mode 100644
index 0000000..a215ac1
--- /dev/null
+++ b/src/flowgraph/SampleRateConverterVariable.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
+#define OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "AudioProcessorBase.h"
+#include "MultiChannelResampler.h"
+#include "LinearResampler.h"
+#include "SincResampler.h"
+
+namespace flowgraph {
+
+class SampleRateConverterVariable : public SampleRateConverterVariable {
+public:
+ explicit SampleRateConverterVariable(int32_t channelCount, MultiChannelResampler &mResampler);
+
+ double getPhaseIncrement() {
+ return mPhaseIncrement;
+ }
+
+ void setPhaseIncrement(double phaseIncrement) {
+ mPhaseIncrement = phaseIncrement;
+ }
+
+ int32_t onProcess(int32_t numFrames) override;
+
+private:
+ double mPhase = 1.0;
+ double mPhaseIncrement = 1.0;
+};
+
+} /* namespace flowgraph */
+
+#endif //OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
diff --git a/src/flowgraph/SincResampler.cpp b/src/flowgraph/SincResampler.cpp
index f833c32..baaba35 100644
--- a/src/flowgraph/SincResampler.cpp
+++ b/src/flowgraph/SincResampler.cpp
@@ -19,33 +19,21 @@
using namespace flowgraph;
-SincResampler::SincResampler(int32_t channelCount)
- : MultiChannelResampler(channelCount)
- , mX(channelCount * kNumTaps * 2)
- , mSingleFrame(channelCount)
+SincResampler::SincResampler(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate)
+ : ContinuousResampler(channelCount, kNumTaps, inputRate, outputRate)
, mWindowedSinc(kNumPoints + kNumGuardPoints){
generateLookupTable();
}
-void SincResampler::writeFrame(const float *frame) {
- // Advance cursor before write so that cursor points to last written frame in read.
- if (++mCursor >= kNumTaps) {
- mCursor = 0;
- }
- int xIndex = mCursor * getChannelCount();
- int offset = kNumTaps * getChannelCount();
- float *dest = &mX[xIndex];
- for (int channel = 0; channel < getChannelCount(); channel++) {
- // Write twice so we avoid having to wrap when running the FIR.
- dest[channel] = dest[channel + offset] = frame[channel];
- }
-}
-void SincResampler::readFrame(float *frame, float phase) {
+void SincResampler::readFrame(float *frame) {
// Clear accumulator for mix.
for (int channel = 0; channel < getChannelCount(); channel++) {
mSingleFrame[channel] = 0.0;
}
+ float phase = getPhase();
// Multiply input times windowed sinc function.
int xIndex = (mCursor + kNumTaps) * getChannelCount();
for (int i = 0; i < kNumTaps; i++) {
@@ -89,23 +77,11 @@
return low + (fraction * (high - low));
}
-float SincResampler::calculateWindowedSinc(float phase) {
- const float realPhase = phase - kSpread;
- if (abs(realPhase) < 0.00000001) return 1.0f; // avoid divide by zero
- // Hamming window TODO try Kaiser window
- const float alpha = 0.54f;
- const float windowPhase = realPhase * M_PI * kSpreadInverse;
- const float window = (float) (alpha + ((1.0 - alpha) * cosf(windowPhase)));
- const float sincPhase = realPhase * M_PI;
- const float sinc = sinf(sincPhase) / sincPhase;
- return window * sinc;
-}
-
void SincResampler::generateLookupTable() {
// TODO store only half of function and use symmetry
// By iterating over the table size we also set the guard point.
for (int i = 0; i < mWindowedSinc.size(); i++) {
float phase = (i * 2.0 * kSpread) / kNumPoints;
- mWindowedSinc[i] = calculateWindowedSinc(phase);
+ mWindowedSinc[i] = calculateWindowedSinc(phase, kSpread);
}
}
diff --git a/src/flowgraph/SincResampler.h b/src/flowgraph/SincResampler.h
index 20eea51..a887a04 100644
--- a/src/flowgraph/SincResampler.h
+++ b/src/flowgraph/SincResampler.h
@@ -18,22 +18,21 @@
#define OBOE_SINC_RESAMPLER_H
#include <memory>
-#include <vector>
#include <sys/types.h>
#include <unistd.h>
-#include "MultiChannelResampler.h"
+#include "ContinuousResampler.h"
namespace flowgraph {
-class SincResampler : public MultiChannelResampler{
+class SincResampler : public ContinuousResampler {
public:
- explicit SincResampler(int32_t channelCount);
+ SincResampler(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate);
virtual ~SincResampler() = default;
- void writeFrame(const float *frame) override;
-
- void readFrame(float *frame, float mPhase) override;
+ void readFrame(float *frame) override;
int getSpread() const {
return kSpread;
@@ -45,26 +44,18 @@
*/
float interpolateWindowedSinc(float phase);
- /**
- * @param phase between 0.0 and 2*kSpread
- * @return windowedSinc
- */
- float calculateWindowedSinc(float phase);
-
protected:
+
// Number of zero crossings on one side of central lobe.
// Higher numbers provide higher quality but use more CPU.
// 2 is the minimum one should use.
static constexpr int kSpread = 10;
- static constexpr int kNumTaps = kSpread * 2;
- static constexpr float kSpreadInverse = 1.0 / kSpread;
+ static constexpr int kNumTaps = kSpread * 2; // TODO should be odd, not even
- std::vector<float> mX;
- std::vector<float> mSingleFrame;
std::vector<float> mWindowedSinc;
- int mCursor = 0;
private:
+
void generateLookupTable();
// Size of the lookup table.
diff --git a/src/flowgraph/SincResamplerStereo.cpp b/src/flowgraph/SincResamplerStereo.cpp
index 50c028e..0e5568f 100644
--- a/src/flowgraph/SincResamplerStereo.cpp
+++ b/src/flowgraph/SincResamplerStereo.cpp
@@ -32,9 +32,10 @@
}
}
-void SincResamplerStereo::readFrame(float *frame, float phase) {
+void SincResamplerStereo::readFrame(float *frame) {
float left = 0.0;
float right = 0.0;
+ float phase = getPhase();
// Multiply input times windowed sinc function.
int xIndex = (mCursor + kNumTaps) * STEREO;
for (int i = 0; i < kNumTaps; i++) {
diff --git a/src/flowgraph/SincResamplerStereo.h b/src/flowgraph/SincResamplerStereo.h
index 06e8e13..1629858 100644
--- a/src/flowgraph/SincResamplerStereo.h
+++ b/src/flowgraph/SincResamplerStereo.h
@@ -25,13 +25,16 @@
class SincResamplerStereo : public SincResampler {
public:
- explicit SincResamplerStereo() : SincResampler(2) {}
+ SincResamplerStereo(
+ int32_t inputRate,
+ int32_t outputRate)
+ : SincResampler(2, inputRate, outputRate) {}
virtual ~SincResamplerStereo() = default;
void writeFrame(const float *frame) override;
- void readFrame(float *frame, float mPhase) override;
+ void readFrame(float *frame) override;
};
}