diff --git a/CMakeLists.txt b/CMakeLists.txt index 4df5a25f8..211e6b78b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ set (oboe_sources src/common/LatencyTuner.cpp src/common/SourceFloatCaller.cpp src/common/SourceI16Caller.cpp + src/common/SourceI24Caller.cpp + src/common/SourceI32Caller.cpp src/common/Utilities.cpp src/common/QuirksManager.cpp src/fifo/FifoBuffer.cpp @@ -37,9 +39,11 @@ set (oboe_sources src/flowgraph/SinkFloat.cpp src/flowgraph/SinkI16.cpp src/flowgraph/SinkI24.cpp + src/flowgraph/SinkI32.cpp src/flowgraph/SourceFloat.cpp src/flowgraph/SourceI16.cpp src/flowgraph/SourceI24.cpp + src/flowgraph/SourceI32.cpp src/flowgraph/resampler/IntegerRatio.cpp src/flowgraph/resampler/LinearResampler.cpp src/flowgraph/resampler/MultiChannelResampler.cpp diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml index f01c14724..3390d0f00 100644 --- a/apps/OboeTester/app/src/main/AndroidManifest.xml +++ b/apps/OboeTester/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="46" + android:versionName="1.6.4"> diff --git a/apps/OboeTester/app/src/main/cpp/FormatConverterBox.cpp b/apps/OboeTester/app/src/main/cpp/FormatConverterBox.cpp new file mode 100644 index 000000000..dd9f324f9 --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/FormatConverterBox.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2021 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 "FormatConverterBox.h" + +FormatConverterBox::FormatConverterBox(int32_t numSamples, + oboe::AudioFormat inputFormat, + oboe::AudioFormat outputFormat) { + mInputFormat = inputFormat; + mOutputFormat = outputFormat; + + mInputBuffer = std::make_unique(numSamples * sizeof(int32_t)); + mOutputBuffer = std::make_unique(numSamples * sizeof(int32_t)); + + mSource.reset(); + switch (mInputFormat) { + case oboe::AudioFormat::I16: + mSource = std::make_unique(1); + break; + case oboe::AudioFormat::I24: + mSource = std::make_unique(1); + break; + case oboe::AudioFormat::I32: + mSource = std::make_unique(1); + break; + case oboe::AudioFormat::Float: + case oboe::AudioFormat::Invalid: + case oboe::AudioFormat::Unspecified: + mSource = std::make_unique(1); + break; + } + + mSink.reset(); + switch (mOutputFormat) { + case oboe::AudioFormat::I16: + mSink = std::make_unique(1); + break; + case oboe::AudioFormat::I24: + mSink = std::make_unique(1); + break; + case oboe::AudioFormat::I32: + mSink = std::make_unique(1); + break; + case oboe::AudioFormat::Float: + case oboe::AudioFormat::Invalid: + case oboe::AudioFormat::Unspecified: + mSink = std::make_unique(1); + break; + } + + if (mSource && mSink) { + mSource->output.connect(&mSink->input); + mSink->pullReset(); + } +} + +int32_t FormatConverterBox::convertInternalBuffers(int32_t numSamples) { + return convert(getOutputBuffer(), numSamples, getInputBuffer()); +} + +int32_t FormatConverterBox::convertToInternalOutput(int32_t numSamples, const void *inputBuffer) { + return convert(getOutputBuffer(), numSamples, inputBuffer); +} + +int32_t FormatConverterBox::convertFromInternalInput(void *outputBuffer, int32_t numSamples) { + return convert(outputBuffer, numSamples, getInputBuffer()); +} + +int32_t FormatConverterBox::convert(void *outputBuffer, int32_t numSamples, const void *inputBuffer) { + mSource->setData(inputBuffer, numSamples); + return mSink->read(outputBuffer, numSamples); +} diff --git a/apps/OboeTester/app/src/main/cpp/FormatConverterBox.h b/apps/OboeTester/app/src/main/cpp/FormatConverterBox.h new file mode 100644 index 000000000..782f9e0c2 --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/FormatConverterBox.h @@ -0,0 +1,101 @@ +/* + * Copyright 2021 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. + */ + +#ifndef OBOETESTER_FORMAT_CONVERTER_BOX_H +#define OBOETESTER_FORMAT_CONVERTER_BOX_H + +#include +#include + +#include "oboe/Oboe.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" + +/** + * Use flowgraph modules to convert between the various data formats. + * + * Note that this does not do channel conversions. + */ + +class FormatConverterBox { +public: + FormatConverterBox(int32_t numSamples, + oboe::AudioFormat inputFormat, + oboe::AudioFormat outputFormat); + + /** + * @return internal buffer used to store input data + */ + void *getOutputBuffer() { + return (void *) mOutputBuffer.get(); + }; + /** + * @return internal buffer used to store output data + */ + void *getInputBuffer() { + return (void *) mInputBuffer.get(); + }; + + /** Convert the data from inputFormat to outputFormat + * using both internal buffers. + */ + int32_t convertInternalBuffers(int32_t numSamples); + + /** + * Convert data from external buffer into internal output buffer. + * @param numSamples + * @param inputBuffer + * @return + */ + int32_t convertToInternalOutput(int32_t numSamples, const void *inputBuffer); + + /** + * + * Convert data from internal input buffer into external output buffer. + * @param outputBuffer + * @param numSamples + * @return + */ + int32_t convertFromInternalInput(void *outputBuffer, int32_t numSamples); + + /** + * Convert data formats between the specified external buffers. + * @param outputBuffer + * @param numSamples + * @param inputBuffer + * @return + */ + int32_t convert(void *outputBuffer, int32_t numSamples, const void *inputBuffer); + +private: + oboe::AudioFormat mInputFormat{oboe::AudioFormat::Invalid}; + oboe::AudioFormat mOutputFormat{oboe::AudioFormat::Invalid}; + + std::unique_ptr mInputBuffer; + std::unique_ptr mOutputBuffer; + + std::unique_ptr mSource; + std::unique_ptr mSink; +}; + + +#endif //OBOETESTER_FORMAT_CONVERTER_BOX_H diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp index 2da2dfd4e..e56d09b10 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp @@ -24,15 +24,15 @@ oboe::Result FullDuplexAnalyzer::start() { } oboe::DataCallbackResult FullDuplexAnalyzer::onBothStreamsReady( - const void *inputData, + const float *inputData, int numInputFrames, - void *outputData, + float *outputData, int numOutputFrames) { int32_t inputStride = getInputStream()->getChannelCount(); int32_t outputStride = getOutputStream()->getChannelCount(); - float *inputFloat = (float *) inputData; - float *outputFloat = (float *) outputData; + const float *inputFloat = inputData; + float *outputFloat = outputData; (void) getLoopbackProcessor()->process(inputFloat, inputStride, numInputFrames, outputFloat, outputStride, numOutputFrames); diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h index 022713096..3182ef925 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h @@ -37,9 +37,9 @@ class FullDuplexAnalyzer : public FullDuplexStream { * Caller should override this method. */ oboe::DataCallbackResult onBothStreamsReady( - const void *inputData, + const float *inputData, int numInputFrames, - void *outputData, + float *outputData, int numOutputFrames ) override; diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp index 0da2d0029..e17c75142 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp @@ -24,13 +24,10 @@ oboe::Result FullDuplexEcho::start() { } oboe::DataCallbackResult FullDuplexEcho::onBothStreamsReady( - const void *inputData, + const float *inputData, int numInputFrames, - void *outputData, + float *outputData, int numOutputFrames) { - // FIXME only handles matching stream formats. - // TODO Add delay node - // TODO use flowgraph to handle format conversion int32_t framesToEcho = std::min(numInputFrames, numOutputFrames); float *inputFloat = (float *)inputData; float *outputFloat = (float *)outputData; diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h index 47f7d5ff7..cbb69d7e1 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h @@ -35,9 +35,9 @@ class FullDuplexEcho : public FullDuplexStream { * Caller should override this method. */ oboe::DataCallbackResult onBothStreamsReady( - const void *inputData, + const float *inputData, int numInputFrames, - void *outputData, + float *outputData, int numOutputFrames ) override; diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp index af233aafb..52e762e60 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp @@ -17,6 +17,18 @@ #include "common/OboeDebug.h" #include "FullDuplexStream.h" +oboe::ResultWithValue FullDuplexStream::readInput(int32_t numFrames) { + oboe::ResultWithValue result = getInputStream()->read( + mInputConverter->getInputBuffer(), + numFrames, + 0 /* timeout */); + if (result == oboe::Result::OK) { + int32_t numSamples = result.value() * getInputStream()->getChannelCount(); + mInputConverter->convertInternalBuffers(numSamples); + } + return result; +} + oboe::DataCallbackResult FullDuplexStream::onAudioReady( oboe::AudioStream *outputStream, void *audioData, @@ -32,9 +44,7 @@ oboe::DataCallbackResult FullDuplexStream::onAudioReady( // Drain the input. int32_t totalFramesRead = 0; do { - oboe::ResultWithValue result = getInputStream()->read(mInputBuffer.get(), - numFrames, - 0 /* timeout */); + oboe::ResultWithValue result = readInput(numFrames); if (!result) { // Ignore errors because input stream may not be started yet. break; @@ -62,7 +72,7 @@ oboe::DataCallbackResult FullDuplexStream::onAudioReady( } else { int32_t framesAvailable = resultAvailable.value(); if (framesAvailable >= mMinimumFramesBeforeRead) { - oboe::ResultWithValue resultRead = getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */); + oboe::ResultWithValue resultRead = readInput(numFrames); if (!resultRead) { LOGE("%s() read() returned %s\n", __func__, convertToText(resultRead.error())); callbackResult = oboe::DataCallbackResult::Stop; @@ -79,7 +89,7 @@ oboe::DataCallbackResult FullDuplexStream::onAudioReady( int32_t framesAvailable = resultAvailable.value(); if (framesAvailable >= mMinimumFramesBeforeRead) { // Read data into input buffer. - oboe::ResultWithValue resultRead = getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */); + oboe::ResultWithValue resultRead = readInput(numFrames); if (!resultRead) { LOGE("%s() read() returned %s\n", __func__, convertToText(resultRead.error())); callbackResult = oboe::DataCallbackResult::Stop; @@ -91,9 +101,11 @@ oboe::DataCallbackResult FullDuplexStream::onAudioReady( if (callbackResult == oboe::DataCallbackResult::Continue) { callbackResult = onBothStreamsReady( - mInputBuffer.get(), framesRead, - audioData, numFrames); - + (const float *) mInputConverter->getOutputBuffer(), + framesRead, + (float *) mOutputConverter->getInputBuffer(), numFrames); + mOutputConverter->convertFromInternalInput( audioData, + numFrames * getOutputStream()->getChannelCount()); } } @@ -112,10 +124,13 @@ oboe::Result FullDuplexStream::start() { // Determine maximum size that could possibly be called. int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames() * getOutputStream()->getChannelCount(); - if (bufferSize > mBufferSize) { - mInputBuffer = std::make_unique(bufferSize); - mBufferSize = bufferSize; - } + mInputConverter = std::make_unique(bufferSize, + getInputStream()->getFormat(), + oboe::AudioFormat::Float); + mOutputConverter = std::make_unique(bufferSize, + oboe::AudioFormat::Float, + getOutputStream()->getFormat()); + oboe::Result result = getInputStream()->requestStart(); if (result != oboe::Result::OK) { return result; diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h index ccb521775..dcfb6ea44 100644 --- a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h +++ b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h @@ -22,6 +22,8 @@ #include "oboe/Oboe.h" +#include "FormatConverterBox.h" + class FullDuplexStream : public oboe::AudioStreamCallback { public: FullDuplexStream() {} @@ -46,14 +48,16 @@ class FullDuplexStream : public oboe::AudioStreamCallback { virtual oboe::Result stop(); + oboe::ResultWithValue readInput(int32_t numFrames); + /** * Called when data is available on both streams. * Caller should override this method. */ virtual oboe::DataCallbackResult onBothStreamsReady( - const void *inputData, + const float *inputData, int numInputFrames, - void *outputData, + float *outputData, int numOutputFrames ) = 0; @@ -107,8 +111,8 @@ class FullDuplexStream : public oboe::AudioStreamCallback { oboe::AudioStream *mInputStream = nullptr; oboe::AudioStream *mOutputStream = nullptr; - int32_t mBufferSize = 0; - std::unique_ptr mInputBuffer; + std::unique_ptr mInputConverter; + std::unique_ptr mOutputConverter; }; diff --git a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.cpp b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.cpp index f9290e12e..b16b46666 100644 --- a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.cpp +++ b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.cpp @@ -22,34 +22,17 @@ oboe::DataCallbackResult InputStreamCallbackAnalyzer::onAudioReady( void *audioData, int numFrames) { int32_t channelCount = audioStream->getChannelCount(); - printScheduler(); - - if (audioStream->getFormat() == oboe::AudioFormat::I16) { - int16_t *shortData = (int16_t *) audioData; - if (mRecording != nullptr) { - mRecording->write(shortData, numFrames); - } - int16_t *frameData = shortData; - for (int iFrame = 0; iFrame < numFrames; iFrame++) { - for (int iChannel = 0; iChannel < channelCount; iChannel++) { - float sample = frameData[iChannel] / 32768.0f; - mPeakDetectors[iChannel].process(sample); - } - frameData += channelCount; - } - } else if (audioStream->getFormat() == oboe::AudioFormat::Float) { - float *floatData = (float *) audioData; - if (mRecording != nullptr) { - mRecording->write(floatData, numFrames); - } - float *frameData = floatData; - for (int iFrame = 0; iFrame < numFrames; iFrame++) { - for (int iChannel = 0; iChannel < channelCount; iChannel++) { - float sample = frameData[iChannel]; - mPeakDetectors[iChannel].process(sample); - } - frameData += channelCount; + mInputConverter->convertToInternalOutput(numFrames * channelCount, audioData); + float *floatData = (float *) mInputConverter->getOutputBuffer(); + if (mRecording != nullptr) { + mRecording->write(floatData, numFrames); + } + int32_t sampleIndex = 0; + for (int iFrame = 0; iFrame < numFrames; iFrame++) { + for (int iChannel = 0; iChannel < channelCount; iChannel++) { + float sample = floatData[sampleIndex++]; + mPeakDetectors[iChannel].process(sample); } } diff --git a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h index fc26b1f8c..7dda1d6ce 100644 --- a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h @@ -23,9 +23,11 @@ // TODO #include "flowgraph/FlowGraph.h" #include "oboe/Oboe.h" + +#include "analyzer/PeakDetector.h" +#include "FormatConverterBox.h" #include "MultiChannelRecording.h" #include "OboeTesterStreamCallback.h" -#include "analyzer/PeakDetector.h" constexpr int kMaxInputChannels = 8; @@ -39,6 +41,15 @@ class InputStreamCallbackAnalyzer : public OboeTesterStreamCallback { OboeTesterStreamCallback::reset(); } + void setup(int32_t maxFramesPerCallback, + int32_t channelCount, + oboe::AudioFormat inputFormat) { + int32_t bufferSize = maxFramesPerCallback * channelCount; + mInputConverter = std::make_unique(bufferSize, + inputFormat, + oboe::AudioFormat::Float); + } + /** * Called by Oboe when the stream is ready to process audio. */ @@ -68,6 +79,8 @@ class InputStreamCallbackAnalyzer : public OboeTesterStreamCallback { MultiChannelRecording *mRecording = nullptr; private: + + std::unique_ptr mInputConverter; int32_t mMinimumFramesBeforeRead = 0; }; diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp index c2cc955c6..bc46878b0 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp @@ -327,6 +327,8 @@ void ActivityTestOutput::close(int32_t streamIndex) { monoToMulti.reset(nullptr); mSinkFloat.reset(); mSinkI16.reset(); + mSinkI24.reset(); + mSinkI32.reset(); } void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) { @@ -361,8 +363,10 @@ void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) { void ActivityTestOutput::configureForStart() { manyToMulti = std::make_unique(mChannelCount); - mSinkFloat = std::make_unique(mChannelCount); - mSinkI16 = std::make_unique(mChannelCount); + mSinkFloat = std::make_shared(mChannelCount); + mSinkI16 = std::make_shared(mChannelCount); + mSinkI24 = std::make_shared(mChannelCount); + mSinkI32 = std::make_shared(mChannelCount); std::shared_ptr outputStream = getOutputStream(); @@ -392,9 +396,13 @@ void ActivityTestOutput::configureForStart() { manyToMulti->output.connect(&(mSinkFloat.get()->input)); manyToMulti->output.connect(&(mSinkI16.get()->input)); + manyToMulti->output.connect(&(mSinkI24.get()->input)); + manyToMulti->output.connect(&(mSinkI32.get()->input)); mSinkFloat->pullReset(); mSinkI16->pullReset(); + mSinkI24->pullReset(); + mSinkI32->pullReset(); configureStreamGateway(); } @@ -403,6 +411,10 @@ void ActivityTestOutput::configureStreamGateway() { std::shared_ptr outputStream = getOutputStream(); if (outputStream->getFormat() == oboe::AudioFormat::I16) { audioStreamGateway.setAudioSink(mSinkI16); + } else if (outputStream->getFormat() == oboe::AudioFormat::I24) { + audioStreamGateway.setAudioSink(mSinkI24); + } else if (outputStream->getFormat() == oboe::AudioFormat::I32) { + audioStreamGateway.setAudioSink(mSinkI32); } else if (outputStream->getFormat() == oboe::AudioFormat::Float) { audioStreamGateway.setAudioSink(mSinkFloat); } @@ -530,8 +542,10 @@ oboe::Result ActivityRecording::startPlayback() { void ActivityTapToTone::configureForStart() { monoToMulti = std::make_unique(mChannelCount); - mSinkFloat = std::make_unique(mChannelCount); - mSinkI16 = std::make_unique(mChannelCount); + mSinkFloat = std::make_shared(mChannelCount); + mSinkI16 = std::make_shared(mChannelCount); + mSinkI24 = std::make_shared(mChannelCount); + mSinkI32 = std::make_shared(mChannelCount); std::shared_ptr outputStream = getOutputStream(); sawPingGenerator.setSampleRate(outputStream->getSampleRate()); @@ -541,9 +555,13 @@ void ActivityTapToTone::configureForStart() { sawPingGenerator.output.connect(&(monoToMulti->input)); monoToMulti->output.connect(&(mSinkFloat.get()->input)); monoToMulti->output.connect(&(mSinkI16.get()->input)); + monoToMulti->output.connect(&(mSinkI24.get()->input)); + monoToMulti->output.connect(&(mSinkI32.get()->input)); mSinkFloat->pullReset(); mSinkI16->pullReset(); + mSinkI24->pullReset(); + mSinkI32->pullReset(); configureStreamGateway(); } diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h index 45c38ee85..380822203 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h @@ -34,6 +34,8 @@ #include "flowgraph/MonoToMultiConverter.h" #include "flowgraph/SinkFloat.h" #include "flowgraph/SinkI16.h" +#include "flowgraph/SinkI24.h" +#include "flowgraph/SinkI32.h" #include "flowunits/ExponentialShape.h" #include "flowunits/LinearShape.h" #include "flowunits/SineOscillator.h" @@ -322,8 +324,6 @@ class ActivityTestInput : public ActivityContext { void runBlockingIO() override; - InputStreamCallbackAnalyzer mInputAnalyzer; - void setMinimumFramesBeforeRead(int32_t numFrames) override { mInputAnalyzer.setMinimumFramesBeforeRead(numFrames); mMinimumFramesBeforeRead = numFrames; @@ -337,9 +337,13 @@ class ActivityTestInput : public ActivityContext { oboe::Result startStreams() override { mInputAnalyzer.reset(); + mInputAnalyzer.setup(getInputStream()->getFramesPerBurst(), + getInputStream()->getChannelCount(), + getInputStream()->getFormat()); return getInputStream()->requestStart(); } + InputStreamCallbackAnalyzer mInputAnalyzer; int32_t mMinimumFramesBeforeRead = 0; }; @@ -423,6 +427,8 @@ class ActivityTestOutput : public ActivityContext { std::unique_ptr monoToMulti; std::shared_ptr mSinkFloat; std::shared_ptr mSinkI16; + std::shared_ptr mSinkI24; + std::shared_ptr mSinkI32; }; /** diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h index 31369b4d9..9d42be57d 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h @@ -45,7 +45,7 @@ class DataPathAnalyzer : public BaseSineAnalyzer { * @param frameData contains microphone data with sine signal feedback * @param channelCount */ - result_code processInputFrame(float *frameData, int /* channelCount */) override { + result_code processInputFrame(const float *frameData, int /* channelCount */) override { result_code result = RESULT_OK; float sample = frameData[getInputChannel()]; diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h index 249bdd1a2..d005b9a9c 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h @@ -116,7 +116,7 @@ class GlitchAnalyzer : public BaseSineAnalyzer { * @param frameData contains microphone data with sine signal feedback * @param channelCount */ - result_code processInputFrame(float *frameData, int /* channelCount */) override { + result_code processInputFrame(const float *frameData, int /* channelCount */) override { result_code result = RESULT_OK; float sample = frameData[0]; diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h index d78e4855a..45169794f 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h @@ -108,7 +108,7 @@ class AudioRecording } // Write SHORT data from the first channel. - int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) { + int32_t write(const int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) { // stop at end of buffer if ((mFrameCounter + numFrames) > mMaxFrames) { numFrames = mMaxFrames - mFrameCounter; @@ -120,7 +120,7 @@ class AudioRecording } // Write FLOAT data from the first channel. - int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) { + int32_t write(const float *inputData, int32_t inputChannelCount, int32_t numFrames) { // stop at end of buffer if ((mFrameCounter + numFrames) > mMaxFrames) { numFrames = mMaxFrames - mFrameCounter; @@ -278,10 +278,10 @@ class LoopbackProcessor { mResetCount++; } - virtual result_code processInputFrame(float *frameData, int channelCount) = 0; + virtual result_code processInputFrame(const float *frameData, int channelCount) = 0; virtual result_code processOutputFrame(float *frameData, int channelCount) = 0; - void process(float *inputData, int inputChannelCount, int numInputFrames, + void process(const float *inputData, int inputChannelCount, int numInputFrames, float *outputData, int outputChannelCount, int numOutputFrames) { int numBoth = std::min(numInputFrames, numOutputFrames); // Process one frame at a time. @@ -512,7 +512,7 @@ class PulseLatencyAnalyzer : public LatencyAnalyzer { ALOGD("latency: st = %d = %s", mState, convertStateToText(mState)); } - result_code processInputFrame(float *frameData, int channelCount) override { + result_code processInputFrame(const float *frameData, int channelCount) override { echo_state nextState = mState; mLoopCounter++; diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfiguration.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfiguration.java index 8ad101eb8..ae63f1a3d 100644 --- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfiguration.java +++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfiguration.java @@ -36,6 +36,8 @@ public class StreamConfiguration { public static final int AUDIO_FORMAT_PCM_16 = 1; // must match AAUDIO public static final int AUDIO_FORMAT_PCM_FLOAT = 2; // must match AAUDIO + public static final int AUDIO_FORMAT_PCM_24 = 3; // must match AAUDIO + public static final int AUDIO_FORMAT_PCM_32 = 4; // must match AAUDIO public static final int DIRECTION_OUTPUT = 0; // must match AAUDIO public static final int DIRECTION_INPUT = 1; // must match AAUDIO @@ -192,6 +194,10 @@ public static String convertFormatToText(int format) { return "Unspecified"; case AUDIO_FORMAT_PCM_16: return "I16"; + case AUDIO_FORMAT_PCM_24: + return "I24"; + case AUDIO_FORMAT_PCM_32: + return "I32"; case AUDIO_FORMAT_PCM_FLOAT: return "Float"; default: diff --git a/apps/OboeTester/app/src/main/res/values/strings.xml b/apps/OboeTester/app/src/main/res/values/strings.xml index 6ad153729..bc249fe09 100644 --- a/apps/OboeTester/app/src/main/res/values/strings.xml +++ b/apps/OboeTester/app/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ Unspecified PCM_I16 PCM_FLOAT + PCM_I24 + PCM_I32 InPreset: diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h index 27c8aed6f..dbb127fc7 100644 --- a/include/oboe/AudioStreamBase.h +++ b/include/oboe/AudioStreamBase.h @@ -230,6 +230,8 @@ class AudioStreamBase { case AudioFormat::Unspecified: case AudioFormat::I16: case AudioFormat::Float: + case AudioFormat::I24: + case AudioFormat::I32: break; default: diff --git a/include/oboe/Definitions.h b/include/oboe/Definitions.h index 8063a83c9..e8975407f 100644 --- a/include/oboe/Definitions.h +++ b/include/oboe/Definitions.h @@ -17,7 +17,6 @@ #ifndef OBOE_DEFINITIONS_H #define OBOE_DEFINITIONS_H - #include #include @@ -108,9 +107,36 @@ namespace oboe { I16 = 1, // AAUDIO_FORMAT_PCM_I16, /** - * Single precision floating points. + * Single precision floating point. + * + * This is the recommended format for most applications. + * But note that the use of Float may prevent the opening of + * a low-latency input path on OpenSL ES or Legacy AAudio streams. */ Float = 2, // AAUDIO_FORMAT_PCM_FLOAT, + + /** + * Signed 24-bit integers, packed into 3 bytes. + * + * Note that the use of this format does not guarantee that + * the full precision will be provided. The underlying device may + * be using I16 format. + * + * Added in API 31 (S). + */ + I24 = 3, // AAUDIO_FORMAT_PCM_I24_PACKED + + /** + * Signed 32-bit integers. + * + * Note that the use of this format does not guarantee that + * the full precision will be provided. The underlying device may + * be using I16 format. + * + * Added in API 31 (S). + */ + I32 = 4, // AAUDIO_FORMAT_PCM_I32 + }; /** diff --git a/src/common/AudioStreamBuilder.cpp b/src/common/AudioStreamBuilder.cpp index b9f04b851..5dbe38cc8 100644 --- a/src/common/AudioStreamBuilder.cpp +++ b/src/common/AudioStreamBuilder.cpp @@ -91,6 +91,7 @@ bool AudioStreamBuilder::isCompatible(AudioStreamBase &other) { Result AudioStreamBuilder::openStream(AudioStream **streamPP) { auto result = isValidConfig(); if (result != Result::OK) { + LOGW("%s() invalid config %d", __func__, result); return result; } @@ -202,24 +203,16 @@ Result AudioStreamBuilder::openStream(AudioStream **streamPP) { Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) { stream.reset(); - auto result = isValidConfig(); - if (result != Result::OK) { - return result; - } AudioStream *streamptr; - result = openStream(&streamptr); + auto result = openStream(&streamptr); stream.reset(streamptr); return result; } Result AudioStreamBuilder::openStream(std::shared_ptr &sharedStream) { sharedStream.reset(); - auto result = isValidConfig(); - if (result != Result::OK) { - return result; - } AudioStream *streamptr; - result = openStream(&streamptr); + auto result = openStream(&streamptr); if (result == Result::OK) { sharedStream.reset(streamptr); // Save a weak_ptr in the stream for use with callbacks. diff --git a/src/common/DataConversionFlowGraph.cpp b/src/common/DataConversionFlowGraph.cpp index 2829e6f54..2177685c5 100644 --- a/src/common/DataConversionFlowGraph.cpp +++ b/src/common/DataConversionFlowGraph.cpp @@ -20,6 +20,8 @@ #include "DataConversionFlowGraph.h" #include "SourceFloatCaller.h" #include "SourceI16Caller.h" +#include "SourceI24Caller.h" +#include "SourceI32Caller.h" #include #include @@ -28,9 +30,11 @@ #include #include #include +#include #include #include #include +#include #include using namespace oboe; @@ -116,6 +120,14 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream mSourceCaller = std::make_unique(sourceChannelCount, actualSourceFramesPerCallback); break; + case AudioFormat::I24: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; + case AudioFormat::I32: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; default: LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat); return Result::ErrorIllegalArgument; @@ -132,6 +144,12 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream case AudioFormat::I16: mSource = std::make_unique(sourceChannelCount); break; + case AudioFormat::I24: + mSource = std::make_unique(sourceChannelCount); + break; + case AudioFormat::I32: + mSource = std::make_unique(sourceChannelCount); + break; default: LOGE("%s() Unsupported source format = %d", __func__, sourceFormat); return Result::ErrorIllegalArgument; @@ -202,6 +220,12 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream case AudioFormat::I16: mSink = std::make_unique(sinkChannelCount); break; + case AudioFormat::I24: + mSink = std::make_unique(sinkChannelCount); + break; + case AudioFormat::I32: + mSink = std::make_unique(sinkChannelCount); + break; default: LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat); return Result::ErrorIllegalArgument;; diff --git a/src/common/SourceI24Caller.cpp b/src/common/SourceI24Caller.cpp new file mode 100644 index 000000000..2e44c3d2f --- /dev/null +++ b/src/common/SourceI24Caller.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 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 +#include +#include "flowgraph/FlowGraphNode.h" +#include "SourceI24Caller.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceI24Caller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + + float *floatData = output.getBuffer(); + const uint8_t *byteData = mConversionBuffer.get(); + int32_t numSamples = framesRead * output.getSamplesPerFrame(); + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_p24(floatData, byteData, numSamples); +#else + static const float scale = 1. / (float)(1UL << 31); + for (int i = 0; i < numSamples; i++) { + // Assemble the data assuming Little Endian format. + int32_t pad = byteData[2]; + pad <<= 8; + pad |= byteData[1]; + pad <<= 8; + pad |= byteData[0]; + pad <<= 8; // Shift to 32 bit data so the sign is correct. + byteData += kBytesPerI24Packed; + *floatData++ = pad * scale; // scale to range -1.0 to 1.0 + } +#endif + + return framesRead; +} diff --git a/src/common/SourceI24Caller.h b/src/common/SourceI24Caller.h new file mode 100644 index 000000000..f8c8e115f --- /dev/null +++ b/src/common/SourceI24Caller.h @@ -0,0 +1,52 @@ +/* + * Copyright 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. + */ + +#ifndef OBOE_SOURCE_I24_CALLER_H +#define OBOE_SOURCE_I24_CALLER_H + +#include +#include + +#include "flowgraph/FlowGraphNode.h" +#include "AudioSourceCaller.h" +#include "FixedBlockReader.h" + +namespace oboe { + +/** + * AudioSource that uses callback to get more data. + */ +class SourceI24Caller : public AudioSourceCaller { +public: + SourceI24Caller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, kBytesPerI24Packed) { + mConversionBuffer = std::make_unique( + kBytesPerI24Packed * channelCount * output.getFramesPerBuffer()); + } + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI24Caller"; + } + +private: + std::unique_ptr mConversionBuffer; + static constexpr int kBytesPerI24Packed = 3; +}; + +} +#endif //OBOE_SOURCE_I16_CALLER_H diff --git a/src/common/SourceI32Caller.cpp b/src/common/SourceI32Caller.cpp new file mode 100644 index 000000000..188bd47ca --- /dev/null +++ b/src/common/SourceI32Caller.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 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 +#include +#include "flowgraph/FlowGraphNode.h" +#include "SourceI32Caller.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceI32Caller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + + float *floatData = output.getBuffer(); + const int32_t *intData = mConversionBuffer.get(); + int32_t numSamples = framesRead * output.getSamplesPerFrame(); + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i32(floatData, shortData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *intData++ * kScale; + } +#endif + + return framesRead; +} diff --git a/src/common/SourceI32Caller.h b/src/common/SourceI32Caller.h new file mode 100644 index 000000000..d802ccc7a --- /dev/null +++ b/src/common/SourceI32Caller.h @@ -0,0 +1,52 @@ +/* + * Copyright 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. + */ + +#ifndef OBOE_SOURCE_I32_CALLER_H +#define OBOE_SOURCE_I32_CALLER_H + +#include +#include +#include + +#include "flowgraph/FlowGraphNode.h" +#include "AudioSourceCaller.h" +#include "FixedBlockReader.h" + +namespace oboe { + +/** + * AudioSource that uses callback to get more data. + */ +class SourceI32Caller : public AudioSourceCaller { +public: + SourceI32Caller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, sizeof(int32_t)) { + mConversionBuffer = std::make_unique(channelCount * output.getFramesPerBuffer()); + } + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI32Caller"; + } + +private: + std::unique_ptr mConversionBuffer; + static constexpr float kScale = 1.0 / (1UL << 31); +}; + +} +#endif //OBOE_SOURCE_I32_CALLER_H diff --git a/src/common/Utilities.cpp b/src/common/Utilities.cpp index d38874b53..177c544b3 100644 --- a/src/common/Utilities.cpp +++ b/src/common/Utilities.cpp @@ -60,6 +60,12 @@ int32_t convertFormatToSizeInBytes(AudioFormat format) { case AudioFormat::Float: size = sizeof(float); break; + case AudioFormat::I24: + size = 3; // packed 24-bit data + break; + case AudioFormat::I32: + size = sizeof(int32_t); + break; default: break; } @@ -98,6 +104,8 @@ const char *convertToText(AudioFormat format) { case AudioFormat::Unspecified: return "Unspecified"; case AudioFormat::I16: return "I16"; case AudioFormat::Float: return "Float"; + case AudioFormat::I24: return "I24"; + case AudioFormat::I32: return "I32"; default: return "Unrecognized format"; } } diff --git a/src/flowgraph/FlowgraphUtilities.h b/src/flowgraph/FlowgraphUtilities.h new file mode 100644 index 000000000..ce2bc82fa --- /dev/null +++ b/src/flowgraph/FlowgraphUtilities.h @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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. + */ + +#ifndef FLOWGRAPH_UTILITIES_H +#define FLOWGRAPH_UTILITIES_H + +#include + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +class FlowgraphUtilities { +public: +// This was copied from audio_utils/primitives.h +/** + * Convert a single-precision floating point value to a Q0.31 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647, + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static int32_t clamp32FromFloat(float f) +{ + static const float scale = (float)(1UL << 31); + static const float limpos = 1.; + static const float limneg = -1.; + + if (f <= limneg) { + return -0x80000000; /* or 0x80000000 */ + } else if (f >= limpos) { + return 0x7fffffff; + } + f *= scale; + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f > 0 ? f + 0.5 : f - 0.5; +} + +}; + +#endif // FLOWGRAPH_UTILITIES_H diff --git a/src/flowgraph/SinkFloat.cpp b/src/flowgraph/SinkFloat.cpp index e0ac6e931..1242f5f2f 100644 --- a/src/flowgraph/SinkFloat.cpp +++ b/src/flowgraph/SinkFloat.cpp @@ -28,7 +28,7 @@ SinkFloat::SinkFloat(int32_t channelCount) int32_t SinkFloat::read(void *data, int32_t numFrames) { // printf("SinkFloat::read(,,%d)\n", numFrames); float *floatData = (float *) data; - int32_t channelCount = input.getSamplesPerFrame(); + const int32_t channelCount = input.getSamplesPerFrame(); int32_t framesLeft = numFrames; while (framesLeft > 0) { diff --git a/src/flowgraph/SinkFloat.h b/src/flowgraph/SinkFloat.h index 98d218207..c3f26bdfb 100644 --- a/src/flowgraph/SinkFloat.h +++ b/src/flowgraph/SinkFloat.h @@ -32,6 +32,7 @@ namespace flowgraph { class SinkFloat : public FlowGraphSink { public: explicit SinkFloat(int32_t channelCount); + ~SinkFloat() override = default; int32_t read(void *data, int32_t numFrames) override; diff --git a/src/flowgraph/SinkI32.cpp b/src/flowgraph/SinkI32.cpp new file mode 100644 index 000000000..504ae4715 --- /dev/null +++ b/src/flowgraph/SinkI32.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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. + */ + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +#include "FlowGraphNode.h" +#include "FlowgraphUtilities.h" +#include "SinkI32.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SinkI32::SinkI32(int32_t channelCount) + : FlowGraphSink(channelCount) {} + +int32_t SinkI32::read(void *data, int32_t numFrames) { + int32_t *intData = (int32_t *) data; + const int32_t channelCount = input.getSamplesPerFrame(); + + int32_t framesLeft = numFrames; + while (framesLeft > 0) { + // Run the graph and pull data through the input port. + int32_t framesRead = pullData(framesLeft); + if (framesRead <= 0) { + break; + } + const float *signal = input.getBuffer(); + int32_t numSamples = framesRead * channelCount; +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_i32_from_float(intData, signal, numSamples); + intData += numSamples; + signal += numSamples; +#else + for (int i = 0; i < numSamples; i++) { + *intData++ = FlowgraphUtilities::clamp32FromFloat(*signal++); + } +#endif + framesLeft -= framesRead; + } + return numFrames - framesLeft; +} diff --git a/src/flowgraph/SinkI32.h b/src/flowgraph/SinkI32.h new file mode 100644 index 000000000..6850b40dc --- /dev/null +++ b/src/flowgraph/SinkI32.h @@ -0,0 +1,42 @@ +/* + * Copyright 2020 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. + */ + +#ifndef FLOWGRAPH_SINK_I32_H +#define FLOWGRAPH_SINK_I32_H + +#include + +#include "FlowGraphNode.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE { +namespace flowgraph { + +class SinkI32 : public FlowGraphSink { +public: + explicit SinkI32(int32_t channelCount); + ~SinkI32() override = default; + + int32_t read(void *data, int32_t numFrames) override; + + const char *getName() override { + return "SinkI32"; + } +}; + +} /* namespace flowgraph */ +} /* namespace FLOWGRAPH_OUTER_NAMESPACE */ + +#endif //FLOWGRAPH_SINK_I32_H diff --git a/src/flowgraph/SourceFloat.cpp b/src/flowgraph/SourceFloat.cpp index 3161ec184..a27d43b7f 100644 --- a/src/flowgraph/SourceFloat.cpp +++ b/src/flowgraph/SourceFloat.cpp @@ -28,11 +28,11 @@ SourceFloat::SourceFloat(int32_t channelCount) int32_t SourceFloat::onProcess(int32_t numFrames) { float *outputBuffer = output.getBuffer(); - int32_t channelCount = output.getSamplesPerFrame(); + const int32_t channelCount = output.getSamplesPerFrame(); - int32_t framesLeft = mSizeInFrames - mFrameIndex; - int32_t framesToProcess = std::min(numFrames, framesLeft); - int32_t numSamples = framesToProcess * channelCount; + const int32_t framesLeft = mSizeInFrames - mFrameIndex; + const int32_t framesToProcess = std::min(numFrames, framesLeft); + const int32_t numSamples = framesToProcess * channelCount; const float *floatBase = (float *) mData; const float *floatData = &floatBase[mFrameIndex * channelCount]; diff --git a/src/flowgraph/SourceFloat.h b/src/flowgraph/SourceFloat.h index d191c4c62..cba67581c 100644 --- a/src/flowgraph/SourceFloat.h +++ b/src/flowgraph/SourceFloat.h @@ -31,6 +31,7 @@ namespace flowgraph { class SourceFloat : public FlowGraphSourceBuffered { public: explicit SourceFloat(int32_t channelCount); + ~SourceFloat() override = default; int32_t onProcess(int32_t numFrames) override; diff --git a/src/flowgraph/SourceI32.cpp b/src/flowgraph/SourceI32.cpp new file mode 100644 index 000000000..e4b804184 --- /dev/null +++ b/src/flowgraph/SourceI32.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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 +#include + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +#include "FlowGraphNode.h" +#include "SourceI32.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SourceI32::SourceI32(int32_t channelCount) + : FlowGraphSourceBuffered(channelCount) { +} + +int32_t SourceI32::onProcess(int32_t numFrames) { + float *floatData = output.getBuffer(); + const int32_t channelCount = output.getSamplesPerFrame(); + + const int32_t framesLeft = mSizeInFrames - mFrameIndex; + const int32_t framesToProcess = std::min(numFrames, framesLeft); + const int32_t numSamples = framesToProcess * channelCount; + + const int32_t *intBase = static_cast(mData); + const int32_t *intData = &intBase[mFrameIndex * channelCount]; + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i32(floatData, intData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *intData++ * kScale; + } +#endif + + mFrameIndex += framesToProcess; + return framesToProcess; +} diff --git a/src/flowgraph/SourceI32.h b/src/flowgraph/SourceI32.h new file mode 100644 index 000000000..7ba29e9b2 --- /dev/null +++ b/src/flowgraph/SourceI32.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FLOWGRAPH_SOURCE_I32_H +#define FLOWGRAPH_SOURCE_I32_H + +#include + +#include "FlowGraphNode.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE { +namespace flowgraph { + +class SourceI32 : public FlowGraphSourceBuffered { +public: + explicit SourceI32(int32_t channelCount); + ~SourceI32() override = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI32"; + } +private: + static constexpr float kScale = 1.0 / (1UL << 31); +}; + +} /* namespace flowgraph */ +} /* namespace FLOWGRAPH_OUTER_NAMESPACE */ + +#endif //FLOWGRAPH_SOURCE_I32_H diff --git a/src/opensles/OpenSLESUtilities.cpp b/src/opensles/OpenSLESUtilities.cpp index 4be23b378..6d25e79fd 100644 --- a/src/opensles/OpenSLESUtilities.cpp +++ b/src/opensles/OpenSLESUtilities.cpp @@ -83,6 +83,8 @@ SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format) { return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; case AudioFormat::Float: return SL_ANDROID_PCM_REPRESENTATION_FLOAT; + case AudioFormat::I24: + case AudioFormat::I32: case AudioFormat::Invalid: case AudioFormat::Unspecified: default: