| /* |
| * 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 <flowgraph/ClipToRange.h> |
| #include <flowgraph/MonoToMultiConverter.h> |
| #include <flowgraph/RampLinear.h> |
| #include <flowgraph/SinkFloat.h> |
| #include <flowgraph/SinkI16.h> |
| #include <flowgraph/SinkI24.h> |
| #include <flowgraph/SourceFloat.h> |
| #include <flowgraph/SourceI16.h> |
| #include <flowgraph/SourceI24.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(); |
| |
| AudioFormat sinkFormat = sinkStream->getFormat(); |
| int32_t sinkChannelCount = sinkStream->getChannelCount(); |
| int32_t sinkSampleRate = sinkStream->getSampleRate(); |
| |
| LOGD("%s() flowgraph converts channels: %d to %d, format: %d to %d, rate: %d to %d, qual = %d", |
| __func__, |
| sourceChannelCount, sinkChannelCount, |
| sourceFormat, sinkFormat, |
| sourceSampleRate, sinkSampleRate, |
| sourceStream->getSampleRateConversionQuality()); |
| |
| int32_t framesPerCallback = (sourceStream->getFramesPerCallback() == kUnspecified) |
| ? sourceStream->getFramesPerBurst() |
| : sourceStream->getFramesPerCallback(); |
| // Source |
| // If OUTPUT and using a callback then call back to the app using a SourceCaller. |
| // If INPUT and NOT using a callback then read from the child stream using a SourceCaller. |
| if ((sourceStream->getCallback() != nullptr && isOutput) |
| || (sourceStream->getCallback() == nullptr && isInput)) { |
| switch (sourceFormat) { |
| case AudioFormat::Float: |
| mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount, |
| framesPerCallback); |
| break; |
| case AudioFormat::I16: |
| mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount, |
| framesPerCallback); |
| break; |
| default: |
| LOGE("%s() Unsupported source caller format = %d", __func__, 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. |
| // 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; |
| default: |
| LOGE("%s() Unsupported source format = %d", __func__, sourceFormat); |
| return Result::ErrorIllegalArgument; |
| } |
| if (isInput) { |
| // The BlockWriter is after the Sink so use the SinkStream size. |
| mBlockWriter.open(framesPerCallback * sinkStream->getBytesPerFrame()); |
| mAppBuffer = std::make_unique<uint8_t[]>( |
| kDefaultBufferSize * sinkStream->getBytesPerFrame()); |
| } |
| lastOutput = &mSource->output; |
| } |
| |
| // Sample Rate conversion |
| if (sourceSampleRate != sinkSampleRate) { |
| mResampler.reset(MultiChannelResampler::make(sourceChannelCount, |
| sourceSampleRate, |
| sinkSampleRate, |
| convertOboeSRQualityToMCR( |
| sourceStream->getSampleRateConversionQuality()))); |
| mRateConverter = std::make_unique<SampleRateConverter>(sourceChannelCount, |
| *mResampler.get()); |
| lastOutput->connect(&mRateConverter->input); |
| lastOutput = &mRateConverter->output; |
| } |
| |
| // Expand the number of channels if required. |
| if (sourceChannelCount == 1 && sinkChannelCount > 1) { |
| mChannelConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount); |
| lastOutput->connect(&mChannelConverter->input); |
| lastOutput = &mChannelConverter->output; |
| } else if (sourceChannelCount != sinkChannelCount) { |
| LOGW("%s() Channel reduction not supported.", __func__); |
| return Result::ErrorUnimplemented; // TODO |
| } |
| |
| // Sink |
| switch (sinkFormat) { |
| case AudioFormat::Float: |
| mSink = std::make_unique<SinkFloat>(sinkChannelCount); |
| break; |
| case AudioFormat::I16: |
| mSink = std::make_unique<SinkI16>(sinkChannelCount); |
| break; |
| default: |
| LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat); |
| return Result::ErrorIllegalArgument;; |
| } |
| lastOutput->connect(&mSink->input); |
| |
| mFramePosition = 0; |
| |
| 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(mFramePosition, buffer, numFrames); |
| mFramePosition += numRead; |
| 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(mFramePosition, mAppBuffer.get(), flowgraph::kDefaultBufferSize); |
| mFramePosition += framesRead; |
| 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->getCallback()->onAudioReady(mFilterStream, buffer, numFrames); |
| // TODO handle STOP from callback, process data remaining in the block adapter |
| return numBytes; |
| } |