| /* | |
| * 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; | |
| } |