blob: ce1d093dc3a37e9646fd9f053d01ac2a34265402 [file] [log] [blame]
/*
* 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;
}