Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2015 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 <unistd.h>
#include "FlowGraphNode.h"
#include "ChannelCountConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ChannelCountConverter::ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount)
: input(*this, inputChannelCount)
, output(*this, outputChannelCount) {
}
ChannelCountConverter::~ChannelCountConverter() = default;
int32_t ChannelCountConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t inputChannelCount = input.getSamplesPerFrame();
int32_t outputChannelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
int inputChannel = 0;
for (int outputChannel = 0; outputChannel < outputChannelCount; outputChannel++) {
// Copy input channels to output channels.
// Wrap if we run out of inputs.
// Discard if we run out of outputs.
outputBuffer[outputChannel] = inputBuffer[inputChannel];
inputChannel = (inputChannel == inputChannelCount)
? 0 : inputChannel + 1;
}
inputBuffer += inputChannelCount;
outputBuffer += outputChannelCount;
}
return numFrames;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2015 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_CHANNEL_COUNT_CONVERTER_H
#define FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Change the number of number of channels without mixing.
* When increasing the channel count, duplicate input channels.
* When decreasing the channel count, drop input channels.
*/
class ChannelCountConverter : public FlowGraphNode {
public:
explicit ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount);
virtual ~ChannelCountConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "ChannelCountConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2015 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "ClipToRange.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ClipToRange::ClipToRange(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t ClipToRange::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
for (int32_t i = 0; i < numSamples; i++) {
*outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++));
}
return numFrames;
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2015 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_CLIP_TO_RANGE_H
#define FLOWGRAPH_CLIP_TO_RANGE_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data.
// It is designed to allow occasional transient peaks.
constexpr float kDefaultMaxHeadroom = 1.41253754f;
constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom;
class ClipToRange : public FlowGraphFilter {
public:
explicit ClipToRange(int32_t channelCount);
virtual ~ClipToRange() = default;
int32_t onProcess(int32_t numFrames) override;
void setMinimum(float min) {
mMinimum = min;
}
float getMinimum() const {
return mMinimum;
}
void setMaximum(float min) {
mMaximum = min;
}
float getMaximum() const {
return mMaximum;
}
const char *getName() override {
return "ClipToRange";
}
private:
float mMinimum = kDefaultMinHeadroom;
float mMaximum = kDefaultMaxHeadroom;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_CLIP_TO_RANGE_H

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2015 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 "stdio.h"
#include <algorithm>
#include <sys/types.h>
#include "FlowGraphNode.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
/***************************************************************************/
int32_t FlowGraphNode::pullData(int32_t numFrames, int64_t callCount) {
int32_t frameCount = numFrames;
// Prevent recursion and multiple execution of nodes.
if (callCount > mLastCallCount) {
mLastCallCount = callCount;
if (mDataPulledAutomatically) {
// Pull from all the upstream nodes.
for (auto &port : mInputPorts) {
// TODO fix bug of leaving unused data in some ports if using multiple AudioSource
frameCount = port.get().pullData(callCount, frameCount);
}
}
if (frameCount > 0) {
frameCount = onProcess(frameCount);
}
mLastFrameCount = frameCount;
} else {
frameCount = mLastFrameCount;
}
return frameCount;
}
void FlowGraphNode::pullReset() {
if (!mBlockRecursion) {
mBlockRecursion = true; // for cyclic graphs
// Pull reset from all the upstream nodes.
for (auto &port : mInputPorts) {
port.get().pullReset();
}
mBlockRecursion = false;
reset();
}
}
void FlowGraphNode::reset() {
mLastFrameCount = 0;
mLastCallCount = kInitialCallCount;
}
/***************************************************************************/
FlowGraphPortFloat::FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer)
: FlowGraphPort(parent, samplesPerFrame)
, mFramesPerBuffer(framesPerBuffer)
, mBuffer(nullptr) {
size_t numFloats = static_cast<size_t>(framesPerBuffer) * getSamplesPerFrame();
mBuffer = std::make_unique<float[]>(numFloats);
}
/***************************************************************************/
int32_t FlowGraphPortFloatOutput::pullData(int64_t callCount, int32_t numFrames) {
numFrames = std::min(getFramesPerBuffer(), numFrames);
return mContainingNode.pullData(numFrames, callCount);
}
void FlowGraphPortFloatOutput::pullReset() {
mContainingNode.pullReset();
}
// These need to be in the .cpp file because of forward cross references.
void FlowGraphPortFloatOutput::connect(FlowGraphPortFloatInput *port) {
port->connect(this);
}
void FlowGraphPortFloatOutput::disconnect(FlowGraphPortFloatInput *port) {
port->disconnect(this);
}
/***************************************************************************/
int32_t FlowGraphPortFloatInput::pullData(int64_t callCount, int32_t numFrames) {
return (mConnected == nullptr)
? std::min(getFramesPerBuffer(), numFrames)
: mConnected->pullData(callCount, numFrames);
}
void FlowGraphPortFloatInput::pullReset() {
if (mConnected != nullptr) mConnected->pullReset();
}
float *FlowGraphPortFloatInput::getBuffer() {
if (mConnected == nullptr) {
return FlowGraphPortFloat::getBuffer(); // loaded using setValue()
} else {
return mConnected->getBuffer();
}
}
int32_t FlowGraphSink::pullData(int32_t numFrames) {
return FlowGraphNode::pullData(numFrames, getLastCallCount() + 1);
}

View File

@@ -0,0 +1,450 @@
/*
* Copyright 2015 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.
*/
/*
* FlowGraph.h
*
* Processing node and ports that can be used in a simple data flow graph.
* This was designed to work with audio but could be used for other
* types of data.
*/
#ifndef FLOWGRAPH_FLOW_GRAPH_NODE_H
#define FLOWGRAPH_FLOW_GRAPH_NODE_H
#include <cassert>
#include <cstring>
#include <math.h>
#include <memory>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vector>
// TODO Move these classes into separate files.
// TODO Review use of raw pointers for connect(). Maybe use smart pointers but need to avoid
// run-time deallocation in audio thread.
// Set flags FLOWGRAPH_ANDROID_INTERNAL and FLOWGRAPH_OUTER_NAMESPACE based on whether compiler
// flag __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe and not aaudio.
#ifndef FLOWGRAPH_ANDROID_INTERNAL
#ifdef __ANDROID_NDK__
#define FLOWGRAPH_ANDROID_INTERNAL 0
#else
#define FLOWGRAPH_ANDROID_INTERNAL 1
#endif // __ANDROID_NDK__
#endif // FLOWGRAPH_ANDROID_INTERNAL
#ifndef FLOWGRAPH_OUTER_NAMESPACE
#ifdef __ANDROID_NDK__
#define FLOWGRAPH_OUTER_NAMESPACE oboe
#else
#define FLOWGRAPH_OUTER_NAMESPACE aaudio
#endif // __ANDROID_NDK__
#endif // FLOWGRAPH_OUTER_NAMESPACE
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
// Default block size that can be overridden when the FlowGraphPortFloat is created.
// If it is too small then we will have too much overhead from switching between nodes.
// If it is too high then we will thrash the caches.
constexpr int kDefaultBufferSize = 8; // arbitrary
class FlowGraphPort;
class FlowGraphPortFloatInput;
/***************************************************************************/
/**
* Base class for all nodes in the flowgraph.
*/
class FlowGraphNode {
public:
FlowGraphNode() = default;
virtual ~FlowGraphNode() = default;
/**
* Read from the input ports,
* generate multiple frames of data then write the results to the output ports.
*
* @param numFrames maximum number of frames requested for processing
* @return number of frames actually processed
*/
virtual int32_t onProcess(int32_t numFrames) = 0;
/**
* If the callCount is at or after the previous callCount then call
* pullData on all of the upstreamNodes.
* Then call onProcess().
* This prevents infinite recursion in case of cyclic graphs.
* It also prevents nodes upstream from a branch from being executed twice.
*
* @param callCount
* @param numFrames
* @return number of frames valid
*/
int32_t pullData(int32_t numFrames, int64_t callCount);
/**
* Recursively reset all the nodes in the graph, starting from a Sink.
*
* This must not be called at the same time as pullData!
*/
void pullReset();
/**
* Reset framePosition counters.
*/
virtual void reset();
void addInputPort(FlowGraphPort &port) {
mInputPorts.emplace_back(port);
}
bool isDataPulledAutomatically() const {
return mDataPulledAutomatically;
}
/**
* Set true if you want the data pulled through the graph automatically.
* This is the default.
*
* Set false if you want to pull the data from the input ports in the onProcess() method.
* You might do this, for example, in a sample rate converting node.
*
* @param automatic
*/
void setDataPulledAutomatically(bool automatic) {
mDataPulledAutomatically = automatic;
}
virtual const char *getName() {
return "FlowGraph";
}
int64_t getLastCallCount() {
return mLastCallCount;
}
protected:
static constexpr int64_t kInitialCallCount = -1;
int64_t mLastCallCount = kInitialCallCount;
std::vector<std::reference_wrapper<FlowGraphPort>> mInputPorts;
private:
bool mDataPulledAutomatically = true;
bool mBlockRecursion = false;
int32_t mLastFrameCount = 0;
};
/***************************************************************************/
/**
* This is a connector that allows data to flow between modules.
*
* The ports are the primary means of interacting with a module.
* So they are generally declared as public.
*
*/
class FlowGraphPort {
public:
FlowGraphPort(FlowGraphNode &parent, int32_t samplesPerFrame)
: mContainingNode(parent)
, mSamplesPerFrame(samplesPerFrame) {
}
virtual ~FlowGraphPort() = default;
// Ports are often declared public. So let's make them non-copyable.
FlowGraphPort(const FlowGraphPort&) = delete;
FlowGraphPort& operator=(const FlowGraphPort&) = delete;
int32_t getSamplesPerFrame() const {
return mSamplesPerFrame;
}
virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0;
virtual void pullReset() {}
protected:
FlowGraphNode &mContainingNode;
private:
const int32_t mSamplesPerFrame = 1;
};
/***************************************************************************/
/**
* This port contains a 32-bit float buffer that can contain several frames of data.
* Processing the data in a block improves performance.
*
* The size is framesPerBuffer * samplesPerFrame).
*/
class FlowGraphPortFloat : public FlowGraphPort {
public:
FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer = kDefaultBufferSize
);
virtual ~FlowGraphPortFloat() = default;
int32_t getFramesPerBuffer() const {
return mFramesPerBuffer;
}
protected:
/**
* @return buffer internal to the port or from a connected port
*/
virtual float *getBuffer() {
return mBuffer.get();
}
private:
const int32_t mFramesPerBuffer = 1;
std::unique_ptr<float[]> mBuffer; // allocated in constructor
};
/***************************************************************************/
/**
* The results of a node's processing are stored in the buffers of the output ports.
*/
class FlowGraphPortFloatOutput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatOutput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
}
virtual ~FlowGraphPortFloatOutput() = default;
using FlowGraphPortFloat::getBuffer;
/**
* Connect to the input of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* If you connect a second output port to an input port
* then it overwrites the previous connection.
*
* This not thread safe. Do not modify the graph topology from another thread while running.
* Also do not delete a module while it is connected to another port if the graph is running.
*/
void connect(FlowGraphPortFloatInput *port);
/**
* Disconnect from the input of another module.
* This not thread safe.
*/
void disconnect(FlowGraphPortFloatInput *port);
/**
* Call the parent module's onProcess() method.
* That may pull data from its inputs and recursively
* process the entire graph.
* @return number of frames actually pulled
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
};
/***************************************************************************/
/**
* An input port for streaming audio data.
* You can set a value that will be used for processing.
* If you connect an output port to this port then its value will be used instead.
*/
class FlowGraphPortFloatInput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatInput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
// Add to parent so it can pull data from each input.
parent.addInputPort(*this);
}
virtual ~FlowGraphPortFloatInput() = default;
/**
* If connected to an output port then this will return
* that output ports buffers.
* If not connected then it returns the input ports own buffer
* which can be loaded using setValue().
*/
float *getBuffer() override;
/**
* Write every value of the float buffer.
* This value will be ignored if an output port is connected
* to this port.
*/
void setValue(float value) {
int numFloats = kDefaultBufferSize * getSamplesPerFrame();
float *buffer = getBuffer();
for (int i = 0; i < numFloats; i++) {
*buffer++ = value;
}
}
/**
* Connect to the output of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* This not thread safe.
*/
void connect(FlowGraphPortFloatOutput *port) {
assert(getSamplesPerFrame() == port->getSamplesPerFrame());
mConnected = port;
}
void disconnect(FlowGraphPortFloatOutput *port) {
assert(mConnected == port);
(void) port;
mConnected = nullptr;
}
void disconnect() {
mConnected = nullptr;
}
/**
* Pull data from any output port that is connected.
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
private:
FlowGraphPortFloatOutput *mConnected = nullptr;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSource : public FlowGraphNode {
public:
explicit FlowGraphSource(int32_t channelCount)
: output(*this, channelCount) {
}
virtual ~FlowGraphSource() = default;
FlowGraphPortFloatOutput output;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSourceBuffered : public FlowGraphSource {
public:
explicit FlowGraphSourceBuffered(int32_t channelCount)
: FlowGraphSource(channelCount) {}
virtual ~FlowGraphSourceBuffered() = default;
/**
* Specify buffer that the node will read from.
*
* @param data TODO Consider using std::shared_ptr.
* @param numFrames
*/
void setData(const void *data, int32_t numFrames) {
mData = data;
mSizeInFrames = numFrames;
mFrameIndex = 0;
}
protected:
const void *mData = nullptr;
int32_t mSizeInFrames = 0; // number of frames in mData
int32_t mFrameIndex = 0; // index of next frame to be processed
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no downstream nodes.
* It consumes data but does not output data.
* This graph will be executed when data is read() from this node
* by pulling data from upstream nodes.
*/
class FlowGraphSink : public FlowGraphNode {
public:
explicit FlowGraphSink(int32_t channelCount)
: input(*this, channelCount) {
}
virtual ~FlowGraphSink() = default;
FlowGraphPortFloatInput input;
/**
* Do nothing. The work happens in the read() method.
*
* @param numFrames
* @return number of frames actually processed
*/
int32_t onProcess(int32_t numFrames) override {
return numFrames;
}
virtual int32_t read(void *data, int32_t numFrames) = 0;
protected:
/**
* Pull data through the graph using this nodes last callCount.
* @param numFrames
* @return
*/
int32_t pullData(int32_t numFrames);
};
/***************************************************************************/
/**
* Base class for a node that has an input and an output with the same number of channels.
* This may include traditional filters, eg. FIR, but also include
* any processing node that converts input to output.
*/
class FlowGraphFilter : public FlowGraphNode {
public:
explicit FlowGraphFilter(int32_t channelCount)
: input(*this, channelCount)
, output(*this, channelCount) {
}
virtual ~FlowGraphFilter() = default;
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 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_UTILITIES_H
#define FLOWGRAPH_UTILITIES_H
#include <math.h>
#include <unistd.h>
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
class FlowgraphUtilities {
public:
// This was copied from audio_utils/primitives.h
/**
* Convert a single-precision floating point value to a Q0.31 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647,
* including -Inf and +Inf. NaN values are considered undefined, and behavior may change
* depending on hardware and future implementation of this function.
*/
static int32_t clamp32FromFloat(float f)
{
static const float scale = (float)(1UL << 31);
static const float limpos = 1.;
static const float limneg = -1.;
if (f <= limneg) {
return INT32_MIN;
} else if (f >= limpos) {
return INT32_MAX;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
/**
* Convert a single-precision floating point value to a Q0.23 integer value, stored in a
* 32 bit signed integer (technically stored as Q8.23, but clamped to Q0.23).
*
* Values outside the range [-1.0, 1.0) are properly clamped to -8388608 and 8388607,
* including -Inf and +Inf. NaN values are considered undefined, and behavior may change
* depending on hardware and future implementation of this function.
*/
static int32_t clamp24FromFloat(float f)
{
static const float scale = 1 << 23;
return (int32_t) lroundf(fmaxf(fminf(f * scale, scale - 1.f), -scale));
}
};
#endif // FLOWGRAPH_UTILITIES_H

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2022 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 <algorithm>
#include <math.h>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "Limiter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
Limiter::Limiter(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t Limiter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
// Cache the last valid output to reduce memory read/write
float lastValidOutput = mLastValidOutput;
for (int32_t i = 0; i < numSamples; i++) {
// Use the previous output if the input is NaN
if (!isnan(*inputBuffer)) {
lastValidOutput = processFloat(*inputBuffer);
}
inputBuffer++;
*outputBuffer++ = lastValidOutput;
}
mLastValidOutput = lastValidOutput;
return numFrames;
}
float Limiter::processFloat(float in)
{
float in_abs = fabsf(in);
if (in_abs <= 1) {
return in;
}
float out;
if (in_abs < kXWhenYis3Decibels) {
out = (kPolynomialSplineA * in_abs + kPolynomialSplineB) * in_abs + kPolynomialSplineC;
} else {
out = M_SQRT2;
}
if (in < 0) {
out = -out;
}
return out;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2022 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_LIMITER_H
#define FLOWGRAPH_LIMITER_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class Limiter : public FlowGraphFilter {
public:
explicit Limiter(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "Limiter";
}
private:
// These numbers are based on a polynomial spline for a quadratic solution Ax^2 + Bx + C
// The range is up to 3 dB, (10^(3/20)), to match AudioTrack for float data.
static constexpr float kPolynomialSplineA = -0.6035533905; // -(1+sqrt(2))/4
static constexpr float kPolynomialSplineB = 2.2071067811; // (3+sqrt(2))/2
static constexpr float kPolynomialSplineC = -0.6035533905; // -(1+sqrt(2))/4
static constexpr float kXWhenYis3Decibels = 1.8284271247; // -1+2sqrt(2)
/**
* Process an input based on the following:
* If between -1 and 1, return the input value.
* If above kXWhenYis3Decibels, return sqrt(2).
* If below -kXWhenYis3Decibels, return -sqrt(2).
* If between 1 and kXWhenYis3Decibels, use a quadratic spline (Ax^2 + Bx + C).
* If between -kXWhenYis3Decibels and -1, use the absolute value for the spline and flip it.
* The derivative of the spline is 1 at 1 and 0 at kXWhenYis3Decibels.
* This way, the graph is both continuous and differentiable.
*/
float processFloat(float in);
// Use the previous valid output for NaN inputs
float mLastValidOutput = 0.0f;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_LIMITER_H

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2018 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 <unistd.h>
#include "ManyToMultiConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ManyToMultiConverter::ManyToMultiConverter(int32_t channelCount)
: inputs(channelCount)
, output(*this, channelCount) {
for (int i = 0; i < channelCount; i++) {
inputs[i] = std::make_unique<FlowGraphPortFloatInput>(*this, 1);
}
}
int32_t ManyToMultiConverter::onProcess(int32_t numFrames) {
int32_t channelCount = output.getSamplesPerFrame();
for (int ch = 0; ch < channelCount; ch++) {
const float *inputBuffer = inputs[ch]->getBuffer();
float *outputBuffer = output.getBuffer() + ch;
for (int i = 0; i < numFrames; i++) {
// read one, write into the proper interleaved output channel
float sample = *inputBuffer++;
*outputBuffer = sample;
outputBuffer += channelCount; // advance to next multichannel frame
}
}
return numFrames;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2018 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_MANY_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Combine multiple mono inputs into one interleaved multi-channel output.
*/
class ManyToMultiConverter : public flowgraph::FlowGraphNode {
public:
explicit ManyToMultiConverter(int32_t channelCount);
virtual ~ManyToMultiConverter() = default;
int32_t onProcess(int numFrames) override;
void setEnabled(bool /*enabled*/) {}
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatInput>> inputs;
flowgraph::FlowGraphPortFloatOutput output;
const char *getName() override {
return "ManyToMultiConverter";
}
private:
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2021 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 <unistd.h>
#include "MonoBlend.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoBlend::MonoBlend(int32_t channelCount)
: FlowGraphFilter(channelCount)
, mInvChannelCount(1. / channelCount)
{
}
int32_t MonoBlend::onProcess(int32_t numFrames) {
int32_t channelCount = output.getSamplesPerFrame();
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
for (size_t i = 0; i < numFrames; ++i) {
float accum = 0;
for (size_t j = 0; j < channelCount; ++j) {
accum += *inputBuffer++;
}
accum *= mInvChannelCount;
for (size_t j = 0; j < channelCount; ++j) {
*outputBuffer++ = accum;
}
}
return numFrames;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2021 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_MONO_BLEND_H
#define FLOWGRAPH_MONO_BLEND_H
#include <sys/types.h>
#include <unistd.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Combine data between multiple channels so each channel is an average
* of all channels.
*/
class MonoBlend : public FlowGraphFilter {
public:
explicit MonoBlend(int32_t channelCount);
virtual ~MonoBlend() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoBlend";
}
private:
const float mInvChannelCount;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MONO_BLEND

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2015 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 <unistd.h>
#include "FlowGraphNode.h"
#include "MonoToMultiConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount)
: input(*this, 1)
, output(*this, outputChannelCount) {
}
int32_t MonoToMultiConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read one, write many
float sample = *inputBuffer++;
for (int channel = 0; channel < channelCount; channel++) {
*outputBuffer++ = sample;
}
}
return numFrames;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2015 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_MONO_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Convert a monophonic stream to a multi-channel interleaved stream
* with the same signal on each channel.
*/
class MonoToMultiConverter : public FlowGraphNode {
public:
explicit MonoToMultiConverter(int32_t outputChannelCount);
virtual ~MonoToMultiConverter() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoToMultiConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2021 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 <unistd.h>
#include "FlowGraphNode.h"
#include "MultiToManyConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MultiToManyConverter::MultiToManyConverter(int32_t channelCount)
: outputs(channelCount)
, input(*this, channelCount) {
for (int i = 0; i < channelCount; i++) {
outputs[i] = std::make_unique<FlowGraphPortFloatOutput>(*this, 1);
}
}
MultiToManyConverter::~MultiToManyConverter() = default;
int32_t MultiToManyConverter::onProcess(int32_t numFrames) {
int32_t channelCount = input.getSamplesPerFrame();
for (int ch = 0; ch < channelCount; ch++) {
const float *inputBuffer = input.getBuffer() + ch;
float *outputBuffer = outputs[ch]->getBuffer();
for (int i = 0; i < numFrames; i++) {
*outputBuffer++ = *inputBuffer;
inputBuffer += channelCount;
}
}
return numFrames;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2021 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_MULTI_TO_MANY_CONVERTER_H
#define FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Convert a multi-channel interleaved stream to multiple mono-channel
* outputs
*/
class MultiToManyConverter : public FlowGraphNode {
public:
explicit MultiToManyConverter(int32_t channelCount);
virtual ~MultiToManyConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MultiToManyConverter";
}
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatOutput>> outputs;
flowgraph::FlowGraphPortFloatInput input;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2015 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 <unistd.h>
#include "FlowGraphNode.h"
#include "MultiToMonoConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MultiToMonoConverter::MultiToMonoConverter(int32_t inputChannelCount)
: input(*this, inputChannelCount)
, output(*this, 1) {
}
MultiToMonoConverter::~MultiToMonoConverter() = default;
int32_t MultiToMonoConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = input.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read first channel of multi stream, write many
*outputBuffer++ = *inputBuffer;
inputBuffer += channelCount;
}
return numFrames;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2015 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_MULTI_TO_MONO_CONVERTER_H
#define FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Convert a multi-channel interleaved stream to a monophonic stream
* by extracting channel[0].
*/
class MultiToMonoConverter : public FlowGraphNode {
public:
explicit MultiToMonoConverter(int32_t inputChannelCount);
virtual ~MultiToMonoConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MultiToMonoConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2015 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "RampLinear.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
RampLinear::RampLinear(int32_t channelCount)
: FlowGraphFilter(channelCount) {
mTarget.store(1.0f);
}
void RampLinear::setLengthInFrames(int32_t frames) {
mLengthInFrames = frames;
}
void RampLinear::setTarget(float target) {
mTarget.store(target);
// If the ramp has not been used then start immediately at this level.
if (mLastCallCount == kInitialCallCount) {
forceCurrent(target);
}
}
float RampLinear::interpolateCurrent() {
return mLevelTo - (mRemaining * mScaler);
}
int32_t RampLinear::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
float target = getTarget();
if (target != mLevelTo) {
// Start new ramp. Continue from previous level.
mLevelFrom = interpolateCurrent();
mLevelTo = target;
mRemaining = mLengthInFrames;
mScaler = (mLevelTo - mLevelFrom) / mLengthInFrames; // for interpolation
}
int32_t framesLeft = numFrames;
if (mRemaining > 0) { // Ramping? This doesn't happen very often.
int32_t framesToRamp = std::min(framesLeft, mRemaining);
framesLeft -= framesToRamp;
while (framesToRamp > 0) {
float currentLevel = interpolateCurrent();
for (int ch = 0; ch < channelCount; ch++) {
*outputBuffer++ = *inputBuffer++ * currentLevel;
}
mRemaining--;
framesToRamp--;
}
}
// Process any frames after the ramp.
int32_t samplesLeft = framesLeft * channelCount;
for (int i = 0; i < samplesLeft; i++) {
*outputBuffer++ = *inputBuffer++ * mLevelTo;
}
return numFrames;
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2015 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_RAMP_LINEAR_H
#define FLOWGRAPH_RAMP_LINEAR_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* When the target is modified then the output will ramp smoothly
* between the original and the new target value.
* This can be used to smooth out control values and reduce pops.
*
* The target may be updated while a ramp is in progress, which will trigger
* a new ramp from the current value.
*/
class RampLinear : public FlowGraphFilter {
public:
explicit RampLinear(int32_t channelCount);
virtual ~RampLinear() = default;
int32_t onProcess(int32_t numFrames) override;
/**
* This is used for the next ramp.
* Calling this does not affect a ramp that is in progress.
*/
void setLengthInFrames(int32_t frames);
int32_t getLengthInFrames() const {
return mLengthInFrames;
}
/**
* This may be safely called by another thread.
* @param target
*/
void setTarget(float target);
float getTarget() const {
return mTarget.load();
}
/**
* Force the nextSegment to start from this level.
*
* WARNING: this can cause a discontinuity if called while the ramp is being used.
* Only call this when setting the initial ramp.
*
* @param level
*/
void forceCurrent(float level) {
mLevelFrom = level;
mLevelTo = level;
}
const char *getName() override {
return "RampLinear";
}
private:
float interpolateCurrent();
std::atomic<float> mTarget;
int32_t mLengthInFrames = 48000.0f / 100.0f ; // 10 msec at 48000 Hz;
int32_t mRemaining = 0;
float mScaler = 0.0f;
float mLevelFrom = 0.0f;
float mLevelTo = 0.0f;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_RAMP_LINEAR_H

View File

@@ -0,0 +1,71 @@
/*
* 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 "SampleRateConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
SampleRateConverter::SampleRateConverter(int32_t channelCount,
MultiChannelResampler &resampler)
: FlowGraphFilter(channelCount)
, mResampler(resampler) {
setDataPulledAutomatically(false);
}
void SampleRateConverter::reset() {
FlowGraphNode::reset();
mInputCursor = kInitialCallCount;
}
// Return true if there is a sample available.
bool SampleRateConverter::isInputAvailable() {
// If we have consumed all of the input data then go out and get some more.
if (mInputCursor >= mNumValidInputFrames) {
mInputCallCount++;
mNumValidInputFrames = input.pullData(mInputCallCount, input.getFramesPerBuffer());
mInputCursor = 0;
}
return (mInputCursor < mNumValidInputFrames);
}
const float *SampleRateConverter::getNextInputFrame() {
const float *inputBuffer = input.getBuffer();
return &inputBuffer[mInputCursor++ * input.getSamplesPerFrame()];
}
int32_t SampleRateConverter::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.isWriteNeeded()) {
if (isInputAvailable()) {
const float *frame = getNextInputFrame();
mResampler.writeNextFrame(frame);
} else {
break;
}
} else {
// Output frame is interpolated from input samples.
mResampler.readNextFrame(outputBuffer);
outputBuffer += channelCount;
framesLeft--;
}
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,63 @@
/*
* 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_SAMPLE_RATE_CONVERTER_H
#define FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
#include "resampler/MultiChannelResampler.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SampleRateConverter : public FlowGraphFilter {
public:
explicit SampleRateConverter(int32_t channelCount,
resampler::MultiChannelResampler &mResampler);
virtual ~SampleRateConverter() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SampleRateConverter";
}
void reset() override;
private:
// Return true if there is a sample available.
bool isInputAvailable();
// This assumes data is available. Only call after calling isInputAvailable().
const float *getNextInputFrame();
resampler::MultiChannelResampler &mResampler;
int32_t mInputCursor = 0; // offset into the input port buffer
int32_t mNumValidInputFrames = 0; // number of valid frames currently in the input port buffer
// We need our own callCount for upstream calls because calls occur at a different rate.
// This means we cannot have cyclic graphs or merges that contain an SRC.
int64_t mInputCallCount = 0;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SAMPLE_RATE_CONVERTER_H

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SinkFloat.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkFloat::SinkFloat(int32_t channelCount)
: FlowGraphSink(channelCount) {
}
int32_t SinkFloat::read(void *data, int32_t numFrames) {
float *floatData = (float *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesPulled = pullData(framesLeft);
if (framesPulled <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesPulled * channelCount;
memcpy(floatData, signal, numSamples * sizeof(float));
floatData += numSamples;
framesLeft -= framesPulled;
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2018 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_SINK_FLOAT_H
#define FLOWGRAPH_SINK_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSink that lets you read data as 32-bit floats.
*/
class SinkFloat : public FlowGraphSink {
public:
explicit SinkFloat(int32_t channelCount);
~SinkFloat() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkFloat";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_FLOAT_H

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "SinkI16.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI16::SinkI16(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI16::read(void *data, int32_t numFrames) {
int16_t *shortData = (int16_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i16_from_float(shortData, signal, numSamples);
shortData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*signal++ * 32768.0f);
*shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2018 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_SINK_I16_H
#define FLOWGRAPH_SINK_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSink that lets you read data as 16-bit signed integers.
*/
class SinkI16 : public FlowGraphSink {
public:
explicit SinkI16(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI16";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I16_H

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SinkI24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI24::SinkI24(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI24::read(void *data, int32_t numFrames) {
uint8_t *byteData = (uint8_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *floatData = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_p24_from_float(byteData, floatData, numSamples);
static const int kBytesPerI24Packed = 3;
byteData += numSamples * kBytesPerI24Packed;
floatData += numSamples;
#else
const int32_t kI24PackedMax = 0x007FFFFF;
const int32_t kI24PackedMin = 0xFF800000;
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*floatData++ * 0x00800000);
n = std::min(kI24PackedMax, std::max(kI24PackedMin, n)); // clip
// Write as a packed 24-bit integer in Little Endian format.
*byteData++ = (uint8_t) n;
*byteData++ = (uint8_t) (n >> 8);
*byteData++ = (uint8_t) (n >> 16);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2018 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_SINK_I24_H
#define FLOWGRAPH_SINK_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSink that lets you read data as packed 24-bit signed integers.
* The sample size is 3 bytes.
*/
class SinkI24 : public FlowGraphSink {
public:
explicit SinkI24(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI24";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I24_H

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020 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 "FlowGraphNode.h"
#include "FlowgraphUtilities.h"
#include "SinkI32.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI32::SinkI32(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI32::read(void *data, int32_t numFrames) {
int32_t *intData = (int32_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i32_from_float(intData, signal, numSamples);
intData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
*intData++ = FlowgraphUtilities::clamp32FromFloat(*signal++);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 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_SINK_I32_H
#define FLOWGRAPH_SINK_I32_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SinkI32 : public FlowGraphSink {
public:
explicit SinkI32(int32_t channelCount);
~SinkI32() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI32";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I32_H

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2023 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 "FlowGraphNode.h"
#include "FlowgraphUtilities.h"
#include "SinkI8_24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI8_24::SinkI8_24(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI8_24::read(void *data, int32_t numFrames) {
int32_t *intData = (int32_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_q8_23_from_float_with_clamp(intData, signal, numSamples);
intData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
*intData++ = FlowgraphUtilities::clamp24FromFloat(*signal++);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2023 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_SINK_I8_24_H
#define FLOWGRAPH_SINK_I8_24_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SinkI8_24 : public FlowGraphSink {
public:
explicit SinkI8_24(int32_t channelCount);
~SinkI8_24() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI8_24";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I8_24_H

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceFloat.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceFloat::SourceFloat(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceFloat::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const float *floatBase = (float *) mData;
const float *floatData = &floatBase[mFrameIndex * channelCount];
memcpy(outputBuffer, floatData, numSamples * sizeof(float));
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2018 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_SOURCE_FLOAT_H
#define FLOWGRAPH_SOURCE_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSource that reads a block of pre-defined float data.
*/
class SourceFloat : public FlowGraphSourceBuffered {
public:
explicit SourceFloat(int32_t channelCount);
~SourceFloat() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloat";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_FLOAT_H

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceI16.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI16::SourceI16(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI16::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const int16_t *shortBase = static_cast<const int16_t *>(mData);
const int16_t *shortData = &shortBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2018 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_SOURCE_I16_H
#define FLOWGRAPH_SOURCE_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSource that reads a block of pre-defined 16-bit integer data.
*/
class SourceI16 : public FlowGraphSourceBuffered {
public:
explicit SourceI16(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I16_H

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2018 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceI24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
constexpr int kBytesPerI24Packed = 3;
SourceI24::SourceI24(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI24::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const uint8_t *byteBase = (uint8_t *) mData;
const uint8_t *byteData = &byteBase[mFrameIndex * channelCount * kBytesPerI24Packed];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2018 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_SOURCE_I24_H
#define FLOWGRAPH_SOURCE_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* AudioSource that reads a block of pre-defined 24-bit packed integer data.
*/
class SourceI24 : public FlowGraphSourceBuffered {
public:
explicit SourceI24(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I24_H

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2020 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceI32.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI32::SourceI32(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI32::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const int32_t *intBase = static_cast<const int32_t *>(mData);
const int32_t *intData = &intBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, intData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2018 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_SOURCE_I32_H
#define FLOWGRAPH_SOURCE_I32_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SourceI32 : public FlowGraphSourceBuffered {
public:
explicit SourceI32(int32_t channelCount);
~SourceI32() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32";
}
private:
static constexpr float kScale = 1.0 / (1UL << 31);
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I32_H

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2023 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 <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceI8_24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI8_24::SourceI8_24(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI8_24::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const int32_t *intBase = static_cast<const int32_t *>(mData);
const int32_t *intData = &intBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_q8_23(floatData, intData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2023 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_SOURCE_I8_24_H
#define FLOWGRAPH_SOURCE_I8_24_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SourceI8_24 : public FlowGraphSourceBuffered {
public:
explicit SourceI8_24(int32_t channelCount);
~SourceI8_24() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI8_24";
}
private:
static constexpr float kScale = 1.0 / (1UL << 23);
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I8_24_H

View File

@@ -0,0 +1,71 @@
/*
* 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 RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#define RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#include <math.h>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Calculate a HyperbolicCosineWindow window centered at 0.
* This can be used in place of a Kaiser window.
*
* The code is based on an anonymous contribution by "a concerned citizen":
* https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
*/
class HyperbolicCosineWindow {
public:
HyperbolicCosineWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double alpha = ((-325.1e-6 * attenuation + 0.1677) * attenuation) - 3.149;
setAlpha(alpha);
return alpha;
}
void setAlpha(double alpha) {
mAlpha = alpha;
mInverseCoshAlpha = 1.0 / cosh(alpha);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mAlpha * sqrt(1.0 - x2);
return cosh(w) * mInverseCoshAlpha;
}
private:
double mAlpha = 0.0;
double mInverseCoshAlpha = 1.0;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H

View File

@@ -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.
*/
#include "IntegerRatio.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
// Enough primes to cover the common sample rates.
static const int 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() {
for (int prime : kPrimes) {
if (mNumerator < prime || mDenominator < prime) {
break;
}
// Find biggest prime factor for numerator.
while (true) {
int top = mNumerator / prime;
int bottom = mDenominator / prime;
if ((top >= 1)
&& (bottom >= 1)
&& (top * prime == mNumerator) // divided evenly?
&& (bottom * prime == mDenominator)) {
mNumerator = top;
mDenominator = bottom;
} else {
break;
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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 RESAMPLER_INTEGER_RATIO_H
#define RESAMPLER_INTEGER_RATIO_H
#include <sys/types.h>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Represent the ratio of two integers.
*/
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;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_INTEGER_RATIO_H

View File

@@ -0,0 +1,90 @@
/*
* 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 RESAMPLER_KAISER_WINDOW_H
#define RESAMPLER_KAISER_WINDOW_H
#include <math.h>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Calculate a Kaiser window centered at 0.
*/
class KaiserWindow {
public:
KaiserWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double beta = 0.0;
if (attenuation > 50) {
beta = 0.1102 * (attenuation - 8.7);
} else if (attenuation >= 21) {
double a21 = attenuation - 21;
beta = 0.5842 * pow(a21, 0.4) + (0.07886 * a21);
}
setBeta(beta);
return beta;
}
void setBeta(double beta) {
mBeta = beta;
mInverseBesselBeta = 1.0 / bessel(beta);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mBeta * sqrt(1.0 - x2);
return bessel(w) * mInverseBesselBeta;
}
// Approximation of a
// modified zero order Bessel function of the first kind.
// Based on a discussion at:
// https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
static double bessel(double x) {
double y = cosh(0.970941817426052 * x);
y += cosh(0.8854560256532099 * x);
y += cosh(0.7485107481711011 * x);
y += cosh(0.5680647467311558 * x);
y += cosh(0.3546048870425356 * x);
y += cosh(0.120536680255323 * x);
y *= 2;
y += cosh(x);
y /= 13;
return y;
}
private:
double mBeta = 0.0;
double mInverseBesselBeta = 1.0;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_KAISER_WINDOW_H

View File

@@ -0,0 +1,42 @@
/*
* 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 "LinearResampler.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
LinearResampler::LinearResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder) {
mPreviousFrame = std::make_unique<float[]>(getChannelCount());
mCurrentFrame = std::make_unique<float[]>(getChannelCount());
}
void LinearResampler::writeFrame(const float *frame) {
memcpy(mPreviousFrame.get(), mCurrentFrame.get(), sizeof(float) * getChannelCount());
memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount());
}
void LinearResampler::readFrame(float *frame) {
float *previous = mPreviousFrame.get();
float *current = mCurrentFrame.get();
float phase = (float) getIntegerPhase() / mDenominator;
// iterate across samples in the frame
for (int channel = 0; channel < getChannelCount(); channel++) {
float f0 = *previous++;
float f1 = *current++;
*frame++ = f0 + (phase * (f1 - f0));
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 RESAMPLER_LINEAR_RESAMPLER_H
#define RESAMPLER_LINEAR_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Simple resampler that uses bi-linear interpolation.
*/
class LinearResampler : public MultiChannelResampler {
public:
explicit LinearResampler(const MultiChannelResampler::Builder &builder);
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
private:
std::unique_ptr<float[]> mPreviousFrame;
std::unique_ptr<float[]> mCurrentFrame;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_LINEAR_RESAMPLER_H

View File

@@ -0,0 +1,171 @@
/*
* 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 <math.h>
#include "IntegerRatio.h"
#include "LinearResampler.h"
#include "MultiChannelResampler.h"
#include "PolyphaseResampler.h"
#include "PolyphaseResamplerMono.h"
#include "PolyphaseResamplerStereo.h"
#include "SincResampler.h"
#include "SincResamplerStereo.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
: mNumTaps(builder.getNumTaps())
, mX(static_cast<size_t>(builder.getChannelCount())
* static_cast<size_t>(builder.getNumTaps()) * 2)
, mSingleFrame(builder.getChannelCount())
, mChannelCount(builder.getChannelCount())
{
// Reduce sample rates to the smallest ratio.
// For example 44100/48000 would become 147/160.
IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
ratio.reduce();
mNumerator = ratio.getNumerator();
mDenominator = ratio.getDenominator();
mIntegerPhase = mDenominator; // so we start with a write needed
}
// static factory method
MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality) {
Builder builder;
builder.setInputRate(inputRate);
builder.setOutputRate(outputRate);
builder.setChannelCount(channelCount);
switch (quality) {
case Quality::Fastest:
builder.setNumTaps(2);
break;
case Quality::Low:
builder.setNumTaps(4);
break;
case Quality::Medium:
default:
builder.setNumTaps(8);
break;
case Quality::High:
builder.setNumTaps(16);
break;
case Quality::Best:
builder.setNumTaps(32);
break;
}
// Set the cutoff frequency so that we do not get aliasing when down-sampling.
if (inputRate > outputRate) {
builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
}
return builder.build();
}
MultiChannelResampler *MultiChannelResampler::Builder::build() {
if (getNumTaps() == 2) {
// Note that this does not do low pass filteringh.
return new LinearResampler(*this);
}
IntegerRatio ratio(getInputRate(), getOutputRate());
ratio.reduce();
bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
if (usePolyphase) {
if (getChannelCount() == 1) {
return new PolyphaseResamplerMono(*this);
} else if (getChannelCount() == 2) {
return new PolyphaseResamplerStereo(*this);
} else {
return new PolyphaseResampler(*this);
}
} else {
// Use less optimized resampler that uses a float phaseIncrement.
// TODO mono resampler
if (getChannelCount() == 2) {
return new SincResamplerStereo(*this);
} else {
return new SincResampler(*this);
}
}
}
void MultiChannelResampler::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
int offset = getNumTaps() * getChannelCount();
for (int channel = 0; channel < getChannelCount(); channel++) {
// Write twice so we avoid having to wrap when reading.
dest[channel] = dest[channel + offset] = frame[channel];
}
}
float MultiChannelResampler::sinc(float radians) {
if (fabsf(radians) < 1.0e-9f) return 1.0f; // avoid divide by zero
return sinf(radians) / radians; // Sinc function
}
// Generate coefficients in the order they will be used by readFrame().
// This is more complicated but readFrame() is called repeatedly and should be optimized.
void MultiChannelResampler::generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff) {
mCoefficients.resize(static_cast<size_t>(getNumTaps()) * static_cast<size_t>(numRows));
int coefficientIndex = 0;
double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
// Stretch the sinc function for low pass filtering.
const float cutoffScaler = (outputRate < inputRate)
? (normalizedCutoff * (float)outputRate / inputRate)
: 1.0f; // Do not filter when upsampling.
const int numTapsHalf = getNumTaps() / 2; // numTaps must be even.
const float numTapsHalfInverse = 1.0f / numTapsHalf;
for (int i = 0; i < numRows; i++) {
float tapPhase = phase - numTapsHalf;
float gain = 0.0; // sum of raw coefficients
int gainCursor = coefficientIndex;
for (int tap = 0; tap < getNumTaps(); tap++) {
float radians = tapPhase * M_PI;
#if MCR_USE_KAISER
float window = mKaiserWindow(tapPhase * numTapsHalfInverse);
#else
float window = mCoshWindow(static_cast<double>(tapPhase) * numTapsHalfInverse);
#endif
float coefficient = sinc(radians * cutoffScaler) * window;
mCoefficients.at(coefficientIndex++) = coefficient;
gain += coefficient;
tapPhase += 1.0;
}
phase += phaseIncrement;
while (phase >= 1.0) {
phase -= 1.0;
}
// Correct for gain variations.
float gainCorrection = 1.0 / gain; // normalize the gain
for (int tap = 0; tap < getNumTaps(); tap++) {
mCoefficients.at(gainCursor + tap) *= gainCorrection;
}
}
}

View File

@@ -0,0 +1,281 @@
/*
* 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 RESAMPLER_MULTICHANNEL_RESAMPLER_H
#define RESAMPLER_MULTICHANNEL_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#ifndef MCR_USE_KAISER
// It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts.
// And it is faster to calculate.
#define MCR_USE_KAISER 0
#endif
#if MCR_USE_KAISER
#include "KaiserWindow.h"
#else
#include "HyperbolicCosineWindow.h"
#endif
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
class MultiChannelResampler {
public:
enum class Quality : int32_t {
Fastest,
Low,
Medium,
High,
Best,
};
class Builder {
public:
/**
* Construct an optimal resampler based on the specified parameters.
* @return address of a resampler
*/
MultiChannelResampler *build();
/**
* The number of taps in the resampling filter.
* More taps gives better quality but uses more CPU time.
* This typically ranges from 4 to 64. Default is 16.
*
* For polyphase filters, numTaps must be a multiple of four for loop unrolling.
* @param numTaps number of taps for the filter
* @return address of this builder for chaining calls
*/
Builder *setNumTaps(int32_t numTaps) {
mNumTaps = numTaps;
return this;
}
/**
* Use 1 for mono, 2 for stereo, etc. Default is 1.
*
* @param channelCount number of channels
* @return address of this builder for chaining calls
*/
Builder *setChannelCount(int32_t channelCount) {
mChannelCount = channelCount;
return this;
}
/**
* Default is 48000.
*
* @param inputRate sample rate of the input stream
* @return address of this builder for chaining calls
*/
Builder *setInputRate(int32_t inputRate) {
mInputRate = inputRate;
return this;
}
/**
* Default is 48000.
*
* @param outputRate sample rate of the output stream
* @return address of this builder for chaining calls
*/
Builder *setOutputRate(int32_t outputRate) {
mOutputRate = outputRate;
return this;
}
/**
* Set cutoff frequency relative to the Nyquist rate of the output sample rate.
* Set to 1.0 to match the Nyquist frequency.
* Set lower to reduce aliasing.
* Default is 0.70.
*
* Note that this value is ignored when upsampling, which is when
* the outputRate is higher than the inputRate.
*
* @param normalizedCutoff anti-aliasing filter cutoff
* @return address of this builder for chaining calls
*/
Builder *setNormalizedCutoff(float normalizedCutoff) {
mNormalizedCutoff = normalizedCutoff;
return this;
}
int32_t getNumTaps() const {
return mNumTaps;
}
int32_t getChannelCount() const {
return mChannelCount;
}
int32_t getInputRate() const {
return mInputRate;
}
int32_t getOutputRate() const {
return mOutputRate;
}
float getNormalizedCutoff() const {
return mNormalizedCutoff;
}
protected:
int32_t mChannelCount = 1;
int32_t mNumTaps = 16;
int32_t mInputRate = 48000;
int32_t mOutputRate = 48000;
float mNormalizedCutoff = kDefaultNormalizedCutoff;
};
virtual ~MultiChannelResampler() = default;
/**
* Factory method for making a resampler that is optimal for the given inputs.
*
* @param channelCount number of channels, 2 for stereo
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param quality higher quality sounds better but uses more CPU
* @return an optimal resampler
*/
static MultiChannelResampler *make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality);
bool isWriteNeeded() const {
return mIntegerPhase >= mDenominator;
}
/**
* Write a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void writeNextFrame(const float *frame) {
writeFrame(frame);
advanceWrite();
}
/**
* Read a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void readNextFrame(float *frame) {
readFrame(frame);
advanceRead();
}
int getNumTaps() const {
return mNumTaps;
}
int getChannelCount() const {
return mChannelCount;
}
static float hammingWindow(float radians, float spread);
static float sinc(float radians);
protected:
explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder);
/**
* Write a frame containing N samples.
* Call advanceWrite() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void writeFrame(const float *frame);
/**
* Read a frame containing N samples using interpolation.
* Call advanceRead() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void readFrame(float *frame) = 0;
void advanceWrite() {
mIntegerPhase -= mDenominator;
}
void advanceRead() {
mIntegerPhase += mNumerator;
}
/**
* Generate the filter coefficients in optimal order.
*
* Note that normalizedCutoff is ignored when upsampling, which is when
* the outputRate is higher than the inputRate.
*
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param numRows number of rows in the array that contain a set of tap coefficients
* @param phaseIncrement how much to increment the phase between rows
* @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output
*/
void generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff);
int32_t getIntegerPhase() {
return mIntegerPhase;
}
static constexpr int kMaxCoefficients = 8 * 1024;
std::vector<float> mCoefficients;
const int mNumTaps;
int mCursor = 0;
std::vector<float> mX; // delayed input values for the FIR
std::vector<float> mSingleFrame; // one frame for temporary use
int32_t mIntegerPhase = 0;
int32_t mNumerator = 0;
int32_t mDenominator = 0;
private:
#if MCR_USE_KAISER
KaiserWindow mKaiserWindow;
#else
HyperbolicCosineWindow mCoshWindow;
#endif
static constexpr float kDefaultNormalizedCutoff = 0.70f;
const int mChannelCount;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_MULTICHANNEL_RESAMPLER_H

View File

@@ -0,0 +1,61 @@
/*
* 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 <algorithm> // Do NOT delete. Needed for LLVM. See #1746
#include <cassert>
#include <math.h>
#include "IntegerRatio.h"
#include "PolyphaseResampler.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
PolyphaseResampler::PolyphaseResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder)
{
assert((getNumTaps() % 4) == 0); // Required for loop unrolling.
int32_t inputRate = builder.getInputRate();
int32_t outputRate = builder.getOutputRate();
int32_t numRows = mDenominator;
double phaseIncrement = (double) inputRate / (double) outputRate;
generateCoefficients(inputRate, outputRate,
numRows, phaseIncrement,
builder.getNormalizedCutoff());
}
void PolyphaseResampler::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
// Multiply input times windowed sinc function.
float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
for (int i = 0; i < mNumTaps; i++) {
float coefficient = *coefficients++;
for (int channel = 0; channel < getChannelCount(); channel++) {
mSingleFrame[channel] += *xFrame++ * coefficient;
}
}
// Advance and wrap through coefficients.
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulator to output.
for (int channel = 0; channel < getChannelCount(); channel++) {
frame[channel] = mSingleFrame[channel];
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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 RESAMPLER_POLYPHASE_RESAMPLER_H
#define RESAMPLER_POLYPHASE_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Resampler that is optimized for a reduced ratio of sample rates.
* All of the coefficients for each possible phase value are pre-calculated.
*/
class PolyphaseResampler : public MultiChannelResampler {
public:
/**
*
* @param builder containing lots of parameters
*/
explicit PolyphaseResampler(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResampler() = default;
void readFrame(float *frame) override;
protected:
int32_t mCoefficientCursor = 0;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_POLYPHASE_RESAMPLER_H

View File

@@ -0,0 +1,63 @@
/*
* 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 <cassert>
#include "PolyphaseResamplerMono.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
#define MONO 1
PolyphaseResamplerMono::PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == MONO);
}
void PolyphaseResamplerMono::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * MONO];
const int offset = mNumTaps * MONO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float sample = frame[0];
// Put ordered writes together.
dest[0] = sample;
dest[offset] = sample;
}
void PolyphaseResamplerMono::readFrame(float *frame) {
// Clear accumulator.
float sum = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * MONO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulator to output.
frame[0] = sum;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 RESAMPLER_POLYPHASE_RESAMPLER_MONO_H
#define RESAMPLER_POLYPHASE_RESAMPLER_MONO_H
#include <sys/types.h>
#include <unistd.h>
#include "PolyphaseResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
class PolyphaseResamplerMono : public PolyphaseResampler {
public:
explicit PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerMono() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_POLYPHASE_RESAMPLER_MONO_H

View File

@@ -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 <cassert>
#include "PolyphaseResamplerStereo.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
#define STEREO 2
PolyphaseResamplerStereo::PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == STEREO);
}
void PolyphaseResamplerStereo::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * STEREO];
const int offset = mNumTaps * STEREO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float left = frame[0];
const float right = frame[1];
// Put ordered writes together.
dest[0] = left;
dest[1] = right;
dest[offset] = left;
dest[1 + offset] = right;
}
void PolyphaseResamplerStereo::readFrame(float *frame) {
// Clear accumulators.
float left = 0.0;
float right = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * STEREO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
float coefficient = *coefficients++;
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulators to output.
frame[0] = left;
frame[1] = right;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H
#define RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H
#include <sys/types.h>
#include <unistd.h>
#include "PolyphaseResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
class PolyphaseResamplerStereo : public PolyphaseResampler {
public:
explicit PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerStereo() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H

View File

@@ -0,0 +1,101 @@
# Sample Rate Converter
This folder contains a sample rate converter, or "resampler".
The converter is based on a sinc function that has been windowed by a hyperbolic cosine.
We found this had fewer artifacts than the more traditional Kaiser window.
## Building the Resampler
It is part of [Oboe](https://github.com/google/oboe) but has no dependencies on Oboe.
So the contents of this folder can be used outside of Oboe.
To build it for use outside of Oboe:
1. Copy the "resampler" folder to a folder in your project that is in the include path.
2. Add all of the \*.cpp files in the resampler folder to your project IDE or Makefile.
3. In ResamplerDefinitions.h, define RESAMPLER_OUTER_NAMESPACE with your own project name. Alternatively, use -DRESAMPLER_OUTER_NAMESPACE=mynamespace when compiling to avoid modifying the resampler code.
## Creating a Resampler
Include the [main header](MultiChannelResampler.h) for the resampler.
#include "resampler/MultiChannelResampler.h"
Here is an example of creating a stereo resampler that will convert from 44100 to 48000 Hz.
Only do this once, when you open your stream. Then use the sample resampler to process multiple buffers.
MultiChannelResampler *resampler = MultiChannelResampler::make(
2, // channel count
44100, // input sampleRate
48000, // output sampleRate
MultiChannelResampler::Quality::Medium); // conversion quality
Possible values for quality include { Fastest, Low, Medium, High, Best }.
Higher quality levels will sound better but consume more CPU because they have more taps in the filter.
## Fractional Frame Counts
Note that the number of output frames generated for a given number of input frames can vary.
For example, suppose you are converting from 44100 Hz to 48000 Hz and using an input buffer with 960 frames. If you calculate the number of output frames you get:
960.0 * 48000 / 44100 = 1044.897959...
You cannot generate a fractional number of frames. So the resampler will sometimes generate 1044 frames and sometimes 1045 frames. On average it will generate 1044.897959 frames. The resampler stores the fraction internally and keeps track of when to consume or generate a frame.
You can either use a fixed number of input frames or a fixed number of output frames. The other frame count will vary.
## Calling the Resampler with a fixed number of OUTPUT frames
In this example, suppose we have a fixed number of output frames and a variable number of input frames.
Assume you start with these variables and a method that returns the next input frame:
float *outputBuffer; // multi-channel buffer to be filled
int numOutputFrames; // number of frames of output
The resampler has a method isWriteNeeded() that tells you whether to write to or read from the resampler.
int outputFramesLeft = numOutputFrames;
while (outputFramesLeft > 0) {
if(resampler->isWriteNeeded()) {
const float *frame = getNextInputFrame(); // you provide this
resampler->writeNextFrame(frame);
} else {
resampler->readNextFrame(outputBuffer);
outputBuffer += channelCount;
outputFramesLeft--;
}
}
## Calling the Resampler with a fixed number of INPUT frames
In this example, suppose we have a fixed number of input frames and a variable number of output frames.
Assume you start with these variables:
float *inputBuffer; // multi-channel buffer to be consumed
float *outputBuffer; // multi-channel buffer to be filled
int numInputFrames; // number of frames of input
int numOutputFrames = 0;
int channelCount; // 1 for mono, 2 for stereo
int inputFramesLeft = numInputFrames;
while (inputFramesLeft > 0) {
if(resampler->isWriteNeeded()) {
resampler->writeNextFrame(inputBuffer);
inputBuffer += channelCount;
inputFramesLeft--;
} else {
resampler->readNextFrame(outputBuffer);
outputBuffer += channelCount;
numOutputFrames++;
}
}
## Deleting the Resampler
When you are done, you should delete the Resampler to avoid a memory leak.
delete resampler;

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2022 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.
*/
// Set flag RESAMPLER_OUTER_NAMESPACE based on whether compiler flag
// __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe
// but not in android.
#ifndef RESAMPLER_OUTER_NAMESPACE
#ifdef __ANDROID_NDK__
#define RESAMPLER_OUTER_NAMESPACE oboe
#else
#define RESAMPLER_OUTER_NAMESPACE aaudio
#endif // __ANDROID_NDK__
#endif // RESAMPLER_OUTER_NAMESPACE

View File

@@ -0,0 +1,72 @@
/*
* 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 <algorithm> // Do NOT delete. Needed for LLVM. See #1746
#include <cassert>
#include <math.h>
#include "SincResampler.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
SincResampler::SincResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder)
, mSingleFrame2(builder.getChannelCount()) {
assert((getNumTaps() % 4) == 0); // Required for loop unrolling.
mNumRows = kMaxCoefficients / getNumTaps(); // includes guard row
const int32_t numRowsNoGuard = mNumRows - 1;
mPhaseScaler = (double) numRowsNoGuard / mDenominator;
const double phaseIncrement = 1.0 / numRowsNoGuard;
generateCoefficients(builder.getInputRate(),
builder.getOutputRate(),
mNumRows,
phaseIncrement,
builder.getNormalizedCutoff());
}
void SincResampler::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0);
// Determine indices into coefficients table.
const double tablePhase = getIntegerPhase() * mPhaseScaler;
const int indexLow = static_cast<int>(floor(tablePhase));
const int indexHigh = indexLow + 1; // OK because using a guard row.
assert (indexHigh < mNumRows);
float *coefficientsLow = &mCoefficients[static_cast<size_t>(indexLow)
* static_cast<size_t>(getNumTaps())];
float *coefficientsHigh = &mCoefficients[static_cast<size_t>(indexHigh)
* static_cast<size_t>(getNumTaps())];
float *xFrame = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
for (int tap = 0; tap < mNumTaps; tap++) {
const float coefficientLow = *coefficientsLow++;
const float coefficientHigh = *coefficientsHigh++;
for (int channel = 0; channel < getChannelCount(); channel++) {
const float sample = *xFrame++;
mSingleFrame[channel] += sample * coefficientLow;
mSingleFrame2[channel] += sample * coefficientHigh;
}
}
// Interpolate and copy to output.
const float fraction = tablePhase - indexLow;
for (int channel = 0; channel < getChannelCount(); channel++) {
const float low = mSingleFrame[channel];
const float high = mSingleFrame2[channel];
frame[channel] = low + (fraction * (high - low));
}
}

View File

@@ -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 RESAMPLER_SINC_RESAMPLER_H
#define RESAMPLER_SINC_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Resampler that can interpolate between coefficients.
* This can be used to support arbitrary ratios.
*/
class SincResampler : public MultiChannelResampler {
public:
explicit SincResampler(const MultiChannelResampler::Builder &builder);
virtual ~SincResampler() = default;
void readFrame(float *frame) override;
protected:
std::vector<float> mSingleFrame2; // for interpolation
int32_t mNumRows = 0;
double mPhaseScaler = 1.0;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_SINC_RESAMPLER_H

View File

@@ -0,0 +1,81 @@
/*
* 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 <algorithm> // Do NOT delete. Needed for LLVM. See #1746
#include <cassert>
#include <math.h>
#include "SincResamplerStereo.h"
using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
#define STEREO 2
SincResamplerStereo::SincResamplerStereo(const MultiChannelResampler::Builder &builder)
: SincResampler(builder) {
assert(builder.getChannelCount() == STEREO);
}
void SincResamplerStereo::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * STEREO];
const int offset = mNumTaps * STEREO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float left = frame[0];
const float right = frame[1];
// Put ordered writes together.
dest[0] = left;
dest[1] = right;
dest[offset] = left;
dest[1 + offset] = right;
}
// Multiply input times windowed sinc function.
void SincResamplerStereo::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0);
// Determine indices into coefficients table.
double tablePhase = getIntegerPhase() * mPhaseScaler;
int index1 = static_cast<int>(floor(tablePhase));
float *coefficients1 = &mCoefficients[static_cast<size_t>(index1)
* static_cast<size_t>(getNumTaps())];
int index2 = (index1 + 1);
float *coefficients2 = &mCoefficients[static_cast<size_t>(index2)
* static_cast<size_t>(getNumTaps())];
float *xFrame = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
for (int i = 0; i < mNumTaps; i++) {
float coefficient1 = *coefficients1++;
float coefficient2 = *coefficients2++;
for (int channel = 0; channel < getChannelCount(); channel++) {
float sample = *xFrame++;
mSingleFrame[channel] += sample * coefficient1;
mSingleFrame2[channel] += sample * coefficient2;
}
}
// Interpolate and copy to output.
float fraction = tablePhase - index1;
for (int channel = 0; channel < getChannelCount(); channel++) {
float low = mSingleFrame[channel];
float high = mSingleFrame2[channel];
frame[channel] = low + (fraction * (high - low));
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 RESAMPLER_SINC_RESAMPLER_STEREO_H
#define RESAMPLER_SINC_RESAMPLER_STEREO_H
#include <sys/types.h>
#include <unistd.h>
#include "SincResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
class SincResamplerStereo : public SincResampler {
public:
explicit SincResamplerStereo(const MultiChannelResampler::Builder &builder);
virtual ~SincResamplerStereo() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_SINC_RESAMPLER_STEREO_H