diff options
Diffstat (limited to 'externals/oboe/src/common/DataConversionFlowGraph.cpp')
| -rw-r--r-- | externals/oboe/src/common/DataConversionFlowGraph.cpp | 266 |
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; +} |
