diff options
Diffstat (limited to 'externals/oboe/samples/LiveEffect/src/main/cpp')
6 files changed, 602 insertions, 0 deletions
diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/CMakeLists.txt b/externals/oboe/samples/LiveEffect/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000..916edc0cf7 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# 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. +# +cmake_minimum_required(VERSION 3.4.1) +project(liveEffect LANGUAGES C CXX) + +get_filename_component(SAMPLE_ROOT_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE) + +### INCLUDE OBOE LIBRARY ### +set (OBOE_DIR ${SAMPLE_ROOT_DIR}/..) +add_subdirectory(${OBOE_DIR} ./oboe-bin) + +add_library(liveEffect + SHARED + LiveEffectEngine.cpp + jni_bridge.cpp + ${SAMPLE_ROOT_DIR}/debug-utils/trace.cpp) +target_include_directories(liveEffect + PRIVATE + ${SAMPLE_ROOT_DIR}/debug-utils + ${OBOE_DIR}/include) +target_link_libraries(liveEffect + PRIVATE + oboe + android + atomic + log) +target_link_options(liveEffect PRIVATE "-Wl,-z,max-page-size=16384") + +# Enable optimization flags: if having problems with source level debugging, +# disable -Ofast ( and debug ), re-enable it after done debugging. +target_compile_options(liveEffect PRIVATE -Wall -Werror "$<$<CONFIG:RELEASE>:-Ofast>") + diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/FullDuplexPass.h b/externals/oboe/samples/LiveEffect/src/main/cpp/FullDuplexPass.h new file mode 100644 index 0000000000..4c8bbd6db4 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/FullDuplexPass.h @@ -0,0 +1,54 @@ +/* + * 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 SAMPLES_FULLDUPLEXPASS_H +#define SAMPLES_FULLDUPLEXPASS_H + +class FullDuplexPass : public oboe::FullDuplexStream { +public: + virtual oboe::DataCallbackResult + onBothStreamsReady( + const void *inputData, + int numInputFrames, + void *outputData, + int numOutputFrames) { + // Copy the input samples to the output with a little arbitrary gain change. + + // This code assumes the data format for both streams is Float. + const float *inputFloats = static_cast<const float *>(inputData); + float *outputFloats = static_cast<float *>(outputData); + + // It also assumes the channel count for each stream is the same. + int32_t samplesPerFrame = getOutputStream()->getChannelCount(); + int32_t numInputSamples = numInputFrames * samplesPerFrame; + int32_t numOutputSamples = numOutputFrames * samplesPerFrame; + + // It is possible that there may be fewer input than output samples. + int32_t samplesToProcess = std::min(numInputSamples, numOutputSamples); + for (int32_t i = 0; i < samplesToProcess; i++) { + *outputFloats++ = *inputFloats++ * 0.95; // do some arbitrary processing + } + + // If there are fewer input samples then clear the rest of the buffer. + int32_t samplesLeft = numOutputSamples - numInputSamples; + for (int32_t i = 0; i < samplesLeft; i++) { + *outputFloats++ = 0.0; // silence + } + + return oboe::DataCallbackResult::Continue; + } +}; +#endif //SAMPLES_FULLDUPLEXPASS_H diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp b/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp new file mode 100644 index 0000000000..63d0a55a47 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp @@ -0,0 +1,245 @@ +/** + * 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. + */ + +#include <cassert> +#include <logging_macros.h> + +#include "LiveEffectEngine.h" + +LiveEffectEngine::LiveEffectEngine() { + assert(mOutputChannelCount == mInputChannelCount); +} + +void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) { + mRecordingDeviceId = deviceId; +} + +void LiveEffectEngine::setPlaybackDeviceId(int32_t deviceId) { + mPlaybackDeviceId = deviceId; +} + +bool LiveEffectEngine::isAAudioRecommended() { + return oboe::AudioStreamBuilder::isAAudioRecommended(); +} + +bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) { + if (mIsEffectOn) return false; + mAudioApi = api; + return true; +} + +bool LiveEffectEngine::setEffectOn(bool isOn) { + bool success = true; + if (isOn != mIsEffectOn) { + if (isOn) { + success = openStreams() == oboe::Result::OK; + if (success) { + mIsEffectOn = isOn; + } + } else { + closeStreams(); + mIsEffectOn = isOn; + } + } + return success; +} + +void LiveEffectEngine::closeStreams() { + /* + * Note: The order of events is important here. + * The playback stream must be closed before the recording stream. If the + * recording stream were to be closed first the playback stream's + * callback may attempt to read from the recording stream + * which would cause the app to crash since the recording stream would be + * null. + */ + mDuplexStream->stop(); + closeStream(mPlayStream); + closeStream(mRecordingStream); + mDuplexStream.reset(); +} + +oboe::Result LiveEffectEngine::openStreams() { + // Note: The order of stream creation is important. We create the playback + // stream first, then use properties from the playback stream + // (e.g. sample rate) to create the recording stream. By matching the + // properties we should get the lowest latency path + oboe::AudioStreamBuilder inBuilder, outBuilder; + setupPlaybackStreamParameters(&outBuilder); + oboe::Result result = outBuilder.openStream(mPlayStream); + if (result != oboe::Result::OK) { + LOGE("Failed to open output stream. Error %s", oboe::convertToText(result)); + mSampleRate = oboe::kUnspecified; + return result; + } else { + // The input stream needs to run at the same sample rate as the output. + mSampleRate = mPlayStream->getSampleRate(); + } + warnIfNotLowLatency(mPlayStream); + + setupRecordingStreamParameters(&inBuilder, mSampleRate); + result = inBuilder.openStream(mRecordingStream); + if (result != oboe::Result::OK) { + LOGE("Failed to open input stream. Error %s", oboe::convertToText(result)); + closeStream(mPlayStream); + return result; + } + warnIfNotLowLatency(mRecordingStream); + + mDuplexStream = std::make_unique<FullDuplexPass>(); + mDuplexStream->setSharedInputStream(mRecordingStream); + mDuplexStream->setSharedOutputStream(mPlayStream); + mDuplexStream->start(); + return result; +} + +/** + * Sets the stream parameters which are specific to recording, + * including the sample rate which is determined from the + * playback stream. + * + * @param builder The recording stream builder + * @param sampleRate The desired sample rate of the recording stream + */ +oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters( + oboe::AudioStreamBuilder *builder, int32_t sampleRate) { + // This sample uses blocking read() because we don't specify a callback + builder->setDeviceId(mRecordingDeviceId) + ->setDirection(oboe::Direction::Input) + ->setSampleRate(sampleRate) + ->setChannelCount(mInputChannelCount); + return setupCommonStreamParameters(builder); +} + +/** + * Sets the stream parameters which are specific to playback, including device + * id and the dataCallback function, which must be set for low latency + * playback. + * @param builder The playback stream builder + */ +oboe::AudioStreamBuilder *LiveEffectEngine::setupPlaybackStreamParameters( + oboe::AudioStreamBuilder *builder) { + builder->setDataCallback(this) + ->setErrorCallback(this) + ->setDeviceId(mPlaybackDeviceId) + ->setDirection(oboe::Direction::Output) + ->setChannelCount(mOutputChannelCount); + + return setupCommonStreamParameters(builder); +} + +/** + * Set the stream parameters which are common to both recording and playback + * streams. + * @param builder The playback or recording stream builder + */ +oboe::AudioStreamBuilder *LiveEffectEngine::setupCommonStreamParameters( + oboe::AudioStreamBuilder *builder) { + // We request EXCLUSIVE mode since this will give us the lowest possible + // latency. + // If EXCLUSIVE mode isn't available the builder will fall back to SHARED + // mode. + builder->setAudioApi(mAudioApi) + ->setFormat(mFormat) + ->setFormatConversionAllowed(true) + ->setSharingMode(oboe::SharingMode::Exclusive) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency); + return builder; +} + +/** + * Close the stream. AudioStream::close() is a blocking call so + * the application does not need to add synchronization between + * onAudioReady() function and the thread calling close(). + * [the closing thread is the UI thread in this sample]. + * @param stream the stream to close + */ +void LiveEffectEngine::closeStream(std::shared_ptr<oboe::AudioStream> &stream) { + if (stream) { + oboe::Result result = stream->stop(); + if (result != oboe::Result::OK) { + LOGW("Error stopping stream: %s", oboe::convertToText(result)); + } + result = stream->close(); + if (result != oboe::Result::OK) { + LOGE("Error closing stream: %s", oboe::convertToText(result)); + } else { + LOGW("Successfully closed streams"); + } + stream.reset(); + } +} + +/** + * Warn in logcat if non-low latency stream is created + * @param stream: newly created stream + * + */ +void LiveEffectEngine::warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream) { + if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) { + LOGW( + "Stream is NOT low latency." + "Check your requested format, sample rate and channel count"); + } +} + +/** + * Handles playback stream's audio request. In this sample, we simply block-read + * from the record stream for the required samples. + * + * @param oboeStream: the playback stream that requesting additional samples + * @param audioData: the buffer to load audio samples for playback stream + * @param numFrames: number of frames to load to audioData buffer + * @return: DataCallbackResult::Continue. + */ +oboe::DataCallbackResult LiveEffectEngine::onAudioReady( + oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) { + return mDuplexStream->onAudioReady(oboeStream, audioData, numFrames); +} + +/** + * Oboe notifies the application for "about to close the stream". + * + * @param oboeStream: the stream to close + * @param error: oboe's reason for closing the stream + */ +void LiveEffectEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream, + oboe::Result error) { + LOGE("%s stream Error before close: %s", + oboe::convertToText(oboeStream->getDirection()), + oboe::convertToText(error)); +} + +/** + * Oboe notifies application that "the stream is closed" + * + * @param oboeStream + * @param error + */ +void LiveEffectEngine::onErrorAfterClose(oboe::AudioStream *oboeStream, + oboe::Result error) { + LOGE("%s stream Error after close: %s", + oboe::convertToText(oboeStream->getDirection()), + oboe::convertToText(error)); + + closeStreams(); + + // Restart the stream if the error is a disconnect. + if (error == oboe::Result::ErrorDisconnected) { + LOGI("Restarting AudioStream"); + openStreams(); + } +} diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h b/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h new file mode 100644 index 0000000000..faea3b07d9 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h @@ -0,0 +1,83 @@ +/* + * 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 OBOE_LIVEEFFECTENGINE_H +#define OBOE_LIVEEFFECTENGINE_H + +#include <jni.h> +#include <oboe/Oboe.h> +#include <string> +#include <thread> +#include "FullDuplexPass.h" + +class LiveEffectEngine : public oboe::AudioStreamCallback { +public: + LiveEffectEngine(); + + void setRecordingDeviceId(int32_t deviceId); + void setPlaybackDeviceId(int32_t deviceId); + + /** + * @param isOn + * @return true if it succeeds + */ + bool setEffectOn(bool isOn); + + /* + * oboe::AudioStreamDataCallback interface implementation + */ + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, + void *audioData, int32_t numFrames) override; + + /* + * oboe::AudioStreamErrorCallback interface implementation + */ + void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error) override; + void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override; + + bool setAudioApi(oboe::AudioApi); + bool isAAudioRecommended(void); + +private: + bool mIsEffectOn = false; + int32_t mRecordingDeviceId = oboe::kUnspecified; + int32_t mPlaybackDeviceId = oboe::kUnspecified; + const oboe::AudioFormat mFormat = oboe::AudioFormat::Float; // for easier processing + oboe::AudioApi mAudioApi = oboe::AudioApi::AAudio; + int32_t mSampleRate = oboe::kUnspecified; + const int32_t mInputChannelCount = oboe::ChannelCount::Stereo; + const int32_t mOutputChannelCount = oboe::ChannelCount::Stereo; + + std::unique_ptr<FullDuplexPass> mDuplexStream; + std::shared_ptr<oboe::AudioStream> mRecordingStream; + std::shared_ptr<oboe::AudioStream> mPlayStream; + + oboe::Result openStreams(); + + void closeStreams(); + + void closeStream(std::shared_ptr<oboe::AudioStream> &stream); + + oboe::AudioStreamBuilder *setupCommonStreamParameters( + oboe::AudioStreamBuilder *builder); + oboe::AudioStreamBuilder *setupRecordingStreamParameters( + oboe::AudioStreamBuilder *builder, int32_t sampleRate); + oboe::AudioStreamBuilder *setupPlaybackStreamParameters( + oboe::AudioStreamBuilder *builder); + void warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream); +}; + +#endif // OBOE_LIVEEFFECTENGINE_H diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/jni_bridge.cpp b/externals/oboe/samples/LiveEffect/src/main/cpp/jni_bridge.cpp new file mode 100644 index 0000000000..ad9172e809 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/jni_bridge.cpp @@ -0,0 +1,134 @@ +/** + * 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. + */ + +#include <jni.h> +#include <logging_macros.h> +#include "LiveEffectEngine.h" + +static const int kOboeApiAAudio = 0; +static const int kOboeApiOpenSLES = 1; + +static LiveEffectEngine *engine = nullptr; + +extern "C" { + +JNIEXPORT jboolean JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_create(JNIEnv *env, + jclass) { + if (engine == nullptr) { + engine = new LiveEffectEngine(); + } + + return (engine != nullptr) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_delete(JNIEnv *env, + jclass) { + if (engine) { + engine->setEffectOn(false); + delete engine; + engine = nullptr; + } +} + +JNIEXPORT jboolean JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setEffectOn( + JNIEnv *env, jclass, jboolean isEffectOn) { + if (engine == nullptr) { + LOGE( + "Engine is null, you must call createEngine before calling this " + "method"); + return JNI_FALSE; + } + + return engine->setEffectOn(isEffectOn) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setRecordingDeviceId( + JNIEnv *env, jclass, jint deviceId) { + if (engine == nullptr) { + LOGE( + "Engine is null, you must call createEngine before calling this " + "method"); + return; + } + + engine->setRecordingDeviceId(deviceId); +} + +JNIEXPORT void JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setPlaybackDeviceId( + JNIEnv *env, jclass, jint deviceId) { + if (engine == nullptr) { + LOGE( + "Engine is null, you must call createEngine before calling this " + "method"); + return; + } + + engine->setPlaybackDeviceId(deviceId); +} + +JNIEXPORT jboolean JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setAPI(JNIEnv *env, + jclass type, + jint apiType) { + if (engine == nullptr) { + LOGE( + "Engine is null, you must call createEngine " + "before calling this method"); + return JNI_FALSE; + } + + oboe::AudioApi audioApi; + switch (apiType) { + case kOboeApiAAudio: + audioApi = oboe::AudioApi::AAudio; + break; + case kOboeApiOpenSLES: + audioApi = oboe::AudioApi::OpenSLES; + break; + default: + LOGE("Unknown API selection to setAPI() %d", apiType); + return JNI_FALSE; + } + + return engine->setAudioApi(audioApi) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_isAAudioRecommended( + JNIEnv *env, jclass type) { + if (engine == nullptr) { + LOGE( + "Engine is null, you must call createEngine " + "before calling this method"); + return JNI_FALSE; + } + return engine->isAAudioRecommended() ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_native_1setDefaultStreamValues(JNIEnv *env, + jclass type, + jint sampleRate, + jint framesPerBurst) { + oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate; + oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst; +} +} // extern "C" diff --git a/externals/oboe/samples/LiveEffect/src/main/cpp/ndk-stl-config.cmake b/externals/oboe/samples/LiveEffect/src/main/cpp/ndk-stl-config.cmake new file mode 100644 index 0000000000..72f0f8d4d4 --- /dev/null +++ b/externals/oboe/samples/LiveEffect/src/main/cpp/ndk-stl-config.cmake @@ -0,0 +1,40 @@ +# Copy shared STL files to Android Studio output directory so they can be +# packaged in the APK. +# Usage: +# +# find_package(ndk-stl REQUIRED) +# +# or +# +# find_package(ndk-stl REQUIRED PATHS ".") + +if(NOT ${ANDROID_STL} MATCHES "_shared") + return() +endif() + +function(configure_shared_stl lib_path so_base) + message("Configuring STL ${so_base} for ${ANDROID_ABI}") + configure_file( + "${ANDROID_NDK}/sources/cxx-stl/${lib_path}/libs/${ANDROID_ABI}/lib${so_base}.so" + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so" + COPYONLY) +endfunction() + +if("${ANDROID_STL}" STREQUAL "libstdc++") + # The default minimal system C++ runtime library. +elseif("${ANDROID_STL}" STREQUAL "gabi++_shared") + # The GAbi++ runtime (shared). + message(FATAL_ERROR "gabi++_shared was not configured by ndk-stl package") +elseif("${ANDROID_STL}" STREQUAL "stlport_shared") + # The STLport runtime (shared). + configure_shared_stl("stlport" "stlport_shared") +elseif("${ANDROID_STL}" STREQUAL "gnustl_shared") + # The GNU STL (shared). + configure_shared_stl("gnu-libstdc++/4.9" "gnustl_shared") +elseif("${ANDROID_STL}" STREQUAL "c++_shared") + # The LLVM libc++ runtime (shared). + configure_shared_stl("llvm-libc++" "c++_shared") +else() + message(FATAL_ERROR "STL configuration ANDROID_STL=${ANDROID_STL} is not supported") +endif() + |
