blob: ac12ef4879e8d8eeeab93b6a850b13951d71506f [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;
void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) {
mSource->setData(buffer, numFrames);
}
static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) {
switch (quality) {
case SampleRateConversionQuality::Low:
return MultiChannelResampler::Quality::Low;
case SampleRateConversionQuality::Medium:
return MultiChannelResampler::Quality::Medium;
default:
case SampleRateConversionQuality::High:
return MultiChannelResampler::Quality::High;
case SampleRateConversionQuality::Best:
return MultiChannelResampler::Quality::Best;
}
}
// Chain together multiple processors.
Result DataConversionFlowGraph::configure(AudioStream *stream,
AudioFormat sourceFormat,
int32_t sourceChannelCount,
int32_t sourceSampleRate,
AudioFormat sinkFormat,
int32_t sinkChannelCount,
int32_t sinkSampleRate
) {
AudioFloatOutputPort *lastOutput = nullptr;
mFilterStream = stream;
LOGD("%s() flowgraph converts format: %d to %d, channels: %d to %d, rate: %d to %d",
__func__, sourceFormat, sinkFormat,
sourceChannelCount, sinkChannelCount,
sourceSampleRate, sinkSampleRate);
// Source
if (stream->getCallback() != nullptr && stream->getDirection() == Direction::Output) {
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
stream->getFramesPerBurst()); // TODO use requested frames per callback
break;
case AudioFormat::I16:
mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
stream->getFramesPerBurst());
break;
default:
LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
mSourceCaller->setStream(stream);
mSourceCaller->setCallback(stream->getCallback());
lastOutput = &mSourceCaller->output;
} else {
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;
}
lastOutput = &mSource->output;
}
if (stream->getCallback() != nullptr && stream->getDirection() == Direction::Input) {
// TODO use requested frames per callback
mBlockWriter.open(stream->getFramesPerBurst() * stream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(kDefaultBufferSize * stream->getBytesPerFrame());
}
// Sample Rate conversion
if (sourceSampleRate != sinkSampleRate) {
mResampler.reset(MultiChannelResampler::make(sourceChannelCount,
sourceSampleRate,
sinkSampleRate,
convertOboeSRQualityToMCR(
stream->getSampleRateConversionType())));
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) {
LOGE("%s() Channel reduction not supported.", __func__);
return Result::ErrorUnimplemented;
}
// 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;
}
// This is only used for OUTPUT streams.
int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames) {
int32_t numRead = mSink->read(mFramePosition, buffer, numFrames);
mFramePosition += numRead;
return numRead;
}
// This is only used for INPUT streams. It is like pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *childBuffer, int32_t numFrames) {
// Put the data from the INPUT callback at the head of the flowgraph.
mSource->setData(childBuffer, numFrames);
while (true) {
// Pull and read some data in app format into a small buffer.
int32_t numRead = mSink->read(mFramePosition, mAppBuffer.get(), flowgraph::kDefaultBufferSize);
mFramePosition += numRead;
if (numRead <= 0) break;
// Write to a block adapter, which will call the app whenever it has enough data.
mBlockWriter.processVariableBlock(mAppBuffer.get(), numRead * mFilterStream->getBytesPerFrame());
}
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 0;
}