aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/common/DataConversionFlowGraph.cpp
diff options
context:
space:
mode:
authorswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
committerswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
commit6c655321e6b04b8648802e296bb5ca11d290ae68 (patch)
tree5c906511e4a2910ce8b806bb9d763b524c28619e /externals/oboe/src/common/DataConversionFlowGraph.cpp
parentc0cceff365f754f8859f4c9a0362ebdba7ea388b (diff)
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/src/common/DataConversionFlowGraph.cpp')
-rw-r--r--externals/oboe/src/common/DataConversionFlowGraph.cpp266
1 files changed, 266 insertions, 0 deletions
diff --git a/externals/oboe/src/common/DataConversionFlowGraph.cpp b/externals/oboe/src/common/DataConversionFlowGraph.cpp
new file mode 100644
index 0000000000..0bb51ae2eb
--- /dev/null
+++ b/externals/oboe/src/common/DataConversionFlowGraph.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 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 <memory>
+
+#include "OboeDebug.h"
+#include "DataConversionFlowGraph.h"
+#include "SourceFloatCaller.h"
+#include "SourceI16Caller.h"
+#include "SourceI24Caller.h"
+#include "SourceI32Caller.h"
+
+#include <flowgraph/MonoToMultiConverter.h>
+#include <flowgraph/MultiToMonoConverter.h>
+#include <flowgraph/RampLinear.h>
+#include <flowgraph/SinkFloat.h>
+#include <flowgraph/SinkI16.h>
+#include <flowgraph/SinkI24.h>
+#include <flowgraph/SinkI32.h>
+#include <flowgraph/SourceFloat.h>
+#include <flowgraph/SourceI16.h>
+#include <flowgraph/SourceI24.h>
+#include <flowgraph/SourceI32.h>
+#include <flowgraph/SampleRateConverter.h>
+
+using namespace oboe;
+using namespace flowgraph;
+using namespace resampler;
+
+void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) {
+ mSource->setData(buffer, numFrames);
+}
+
+static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) {
+ switch (quality) {
+ case SampleRateConversionQuality::Fastest:
+ return MultiChannelResampler::Quality::Fastest;
+ case SampleRateConversionQuality::Low:
+ return MultiChannelResampler::Quality::Low;
+ default:
+ case SampleRateConversionQuality::Medium:
+ return MultiChannelResampler::Quality::Medium;
+ case SampleRateConversionQuality::High:
+ return MultiChannelResampler::Quality::High;
+ case SampleRateConversionQuality::Best:
+ return MultiChannelResampler::Quality::Best;
+ }
+}
+
+// Chain together multiple processors.
+// Callback Output
+// Use SourceCaller that calls original app callback from the flowgraph.
+// The child callback from FilteredAudioStream read()s from the flowgraph.
+// Callback Input
+// Child callback from FilteredAudioStream writes()s to the flowgraph.
+// The output of the flowgraph goes through a BlockWriter to the app callback.
+// Blocking Write
+// Write buffer is set on an AudioSource.
+// Data is pulled through the graph and written to the child stream.
+// Blocking Read
+// Reads in a loop from the flowgraph Sink to fill the read buffer.
+// A SourceCaller then does a blocking read from the child Stream.
+//
+Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream *sinkStream) {
+
+ FlowGraphPortFloatOutput *lastOutput = nullptr;
+
+ bool isOutput = sourceStream->getDirection() == Direction::Output;
+ bool isInput = !isOutput;
+ mFilterStream = isOutput ? sourceStream : sinkStream;
+
+ AudioFormat sourceFormat = sourceStream->getFormat();
+ int32_t sourceChannelCount = sourceStream->getChannelCount();
+ int32_t sourceSampleRate = sourceStream->getSampleRate();
+ int32_t sourceFramesPerCallback = sourceStream->getFramesPerDataCallback();
+
+ AudioFormat sinkFormat = sinkStream->getFormat();
+ int32_t sinkChannelCount = sinkStream->getChannelCount();
+ int32_t sinkSampleRate = sinkStream->getSampleRate();
+ int32_t sinkFramesPerCallback = sinkStream->getFramesPerDataCallback();
+
+ LOGI("%s() flowgraph converts channels: %d to %d, format: %s to %s"
+ ", rate: %d to %d, cbsize: %d to %d, qual = %s",
+ __func__,
+ sourceChannelCount, sinkChannelCount,
+ oboe::convertToText(sourceFormat), oboe::convertToText(sinkFormat),
+ sourceSampleRate, sinkSampleRate,
+ sourceFramesPerCallback, sinkFramesPerCallback,
+ oboe::convertToText(sourceStream->getSampleRateConversionQuality()));
+
+ // Source
+ // IF OUTPUT and using a callback then call back to the app using a SourceCaller.
+ // OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller.
+ bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified();
+ if ((isDataCallbackSpecified && isOutput)
+ || (!isDataCallbackSpecified && isInput)) {
+ int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified)
+ ? sourceStream->getFramesPerBurst()
+ : sourceFramesPerCallback;
+ switch (sourceFormat) {
+ case AudioFormat::Float:
+ mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
+ actualSourceFramesPerCallback);
+ break;
+ case AudioFormat::I16:
+ mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
+ actualSourceFramesPerCallback);
+ break;
+ case AudioFormat::I24:
+ mSourceCaller = std::make_unique<SourceI24Caller>(sourceChannelCount,
+ actualSourceFramesPerCallback);
+ break;
+ case AudioFormat::I32:
+ mSourceCaller = std::make_unique<SourceI32Caller>(sourceChannelCount,
+ actualSourceFramesPerCallback);
+ break;
+ default:
+ LOGE("%s() Unsupported source caller format = %d", __func__, static_cast<int>(sourceFormat));
+ return Result::ErrorIllegalArgument;
+ }
+ mSourceCaller->setStream(sourceStream);
+ lastOutput = &mSourceCaller->output;
+ } else {
+ // IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
+ // OR IF INPUT and using a callback then write to the app using a BlockWriter.
+ switch (sourceFormat) {
+ case AudioFormat::Float:
+ mSource = std::make_unique<SourceFloat>(sourceChannelCount);
+ break;
+ case AudioFormat::I16:
+ mSource = std::make_unique<SourceI16>(sourceChannelCount);
+ break;
+ case AudioFormat::I24:
+ mSource = std::make_unique<SourceI24>(sourceChannelCount);
+ break;
+ case AudioFormat::I32:
+ mSource = std::make_unique<SourceI32>(sourceChannelCount);
+ break;
+ default:
+ LOGE("%s() Unsupported source format = %d", __func__, static_cast<int>(sourceFormat));
+ return Result::ErrorIllegalArgument;
+ }
+ if (isInput) {
+ int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified)
+ ? sinkStream->getFramesPerBurst()
+ : sinkFramesPerCallback;
+ // The BlockWriter is after the Sink so use the SinkStream size.
+ mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame());
+ mAppBuffer = std::make_unique<uint8_t[]>(
+ kDefaultBufferSize * sinkStream->getBytesPerFrame());
+ }
+ lastOutput = &mSource->output;
+ }
+
+ // If we are going to reduce the number of channels then do it before the
+ // sample rate converter.
+ if (sourceChannelCount > sinkChannelCount) {
+ if (sinkChannelCount == 1) {
+ mMultiToMonoConverter = std::make_unique<MultiToMonoConverter>(sourceChannelCount);
+ lastOutput->connect(&mMultiToMonoConverter->input);
+ lastOutput = &mMultiToMonoConverter->output;
+ } else {
+ mChannelCountConverter = std::make_unique<ChannelCountConverter>(
+ sourceChannelCount,
+ sinkChannelCount);
+ lastOutput->connect(&mChannelCountConverter->input);
+ lastOutput = &mChannelCountConverter->output;
+ }
+ }
+
+ // Sample Rate conversion
+ if (sourceSampleRate != sinkSampleRate) {
+ // Create a resampler to do the math.
+ mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(),
+ sourceSampleRate,
+ sinkSampleRate,
+ convertOboeSRQualityToMCR(
+ sourceStream->getSampleRateConversionQuality())));
+ // Make a flowgraph node that uses the resampler.
+ mRateConverter = std::make_unique<SampleRateConverter>(lastOutput->getSamplesPerFrame(),
+ *mResampler.get());
+ lastOutput->connect(&mRateConverter->input);
+ lastOutput = &mRateConverter->output;
+ }
+
+ // Expand the number of channels if required.
+ if (sourceChannelCount < sinkChannelCount) {
+ if (sourceChannelCount == 1) {
+ mMonoToMultiConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
+ lastOutput->connect(&mMonoToMultiConverter->input);
+ lastOutput = &mMonoToMultiConverter->output;
+ } else {
+ mChannelCountConverter = std::make_unique<ChannelCountConverter>(
+ sourceChannelCount,
+ sinkChannelCount);
+ lastOutput->connect(&mChannelCountConverter->input);
+ lastOutput = &mChannelCountConverter->output;
+ }
+ }
+
+ // Sink
+ switch (sinkFormat) {
+ case AudioFormat::Float:
+ mSink = std::make_unique<SinkFloat>(sinkChannelCount);
+ break;
+ case AudioFormat::I16:
+ mSink = std::make_unique<SinkI16>(sinkChannelCount);
+ break;
+ case AudioFormat::I24:
+ mSink = std::make_unique<SinkI24>(sinkChannelCount);
+ break;
+ case AudioFormat::I32:
+ mSink = std::make_unique<SinkI32>(sinkChannelCount);
+ break;
+ default:
+ LOGE("%s() Unsupported sink format = %d", __func__, static_cast<int>(sinkFormat));
+ return Result::ErrorIllegalArgument;;
+ }
+ lastOutput->connect(&mSink->input);
+
+ return Result::OK;
+}
+
+int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) {
+ if (mSourceCaller) {
+ mSourceCaller->setTimeoutNanos(timeoutNanos);
+ }
+ int32_t numRead = mSink->read(buffer, numFrames);
+ return numRead;
+}
+
+// This is similar to pushing data through the flowgraph.
+int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
+ // Put the data from the input at the head of the flowgraph.
+ mSource->setData(inputBuffer, numFrames);
+ while (true) {
+ // Pull and read some data in app format into a small buffer.
+ int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize);
+ if (framesRead <= 0) break;
+ // Write to a block adapter, which will call the destination whenever it has enough data.
+ int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(),
+ framesRead * mFilterStream->getBytesPerFrame());
+ if (bytesRead < 0) return bytesRead; // TODO review
+ }
+ return numFrames;
+}
+
+int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
+ int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame();
+ mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames);
+ // TODO handle STOP from callback, process data remaining in the block adapter
+ return numBytes;
+}