aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAtneya Nair <atneya.nair@gmail.com>2019-08-13 09:07:06 -0700
committerDon Turner <dturner@users.noreply.github.com>2019-08-13 17:07:06 +0100
commit738663903f73ecb2aec6c62cb1bafdf03657d7ed (patch)
treebadd4dfb41ab9f8377f1976885bdb65662b47b21
parente09fd98e1482a1eacda2bc77427e9403717722f6 (diff)
downloadoboe-738663903f73ecb2aec6c62cb1bafdf03657d7ed.tar.gz
Refactor samples (#541)
Refactoring the hello-oboe and MegaDrone samples. Changes include: - Each sample's audio engine now owns a separate callback object, rather than it _being_ a callback. This decouples the callback from the owning class. - A new `DefaultAudioStreamCallback` object implements common useful callback functionality. - In hello-oboe a customised `LatencyTuningCallback` is used which automatically adjusts the buffer size of the audio stream based on the number of underruns (more underruns = bigger buffer). - `ManagedStream` is used in both samples which simplifies the ownership of an `AudioStream` object. - Both samples now have a `TappableAudioSource` which is an object which can be tapped and renders audio data. - Duplicated code has been refactored into shared objects, these can be found in the `samples/shared` folder. Thanks to @atneya who implemented the vast majority of these changes.
-rw-r--r--samples/MegaDrone/build.gradle2
-rw-r--r--samples/MegaDrone/src/main/cpp/AudioEngine.cpp121
-rw-r--r--samples/MegaDrone/src/main/cpp/CMakeLists.txt (renamed from samples/MegaDrone/CMakeLists.txt)9
-rw-r--r--samples/MegaDrone/src/main/cpp/MegaDroneEngine.cpp79
-rw-r--r--samples/MegaDrone/src/main/cpp/MegaDroneEngine.h (renamed from samples/MegaDrone/src/main/cpp/AudioEngine.h)36
-rw-r--r--samples/MegaDrone/src/main/cpp/Synth.h22
-rw-r--r--samples/MegaDrone/src/main/cpp/native-lib.cpp55
-rw-r--r--samples/hello-oboe/build.gradle2
-rw-r--r--samples/hello-oboe/src/main/cpp/CMakeLists.txt (renamed from samples/hello-oboe/CMakeLists.txt)22
-rw-r--r--samples/hello-oboe/src/main/cpp/HelloOboeEngine.cpp148
-rw-r--r--samples/hello-oboe/src/main/cpp/HelloOboeEngine.h89
-rw-r--r--samples/hello-oboe/src/main/cpp/LatencyTuningCallback.cpp40
-rw-r--r--samples/hello-oboe/src/main/cpp/LatencyTuningCallback.h64
-rw-r--r--samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp255
-rw-r--r--samples/hello-oboe/src/main/cpp/PlayAudioEngine.h72
-rw-r--r--samples/hello-oboe/src/main/cpp/SoundGenerator.cpp16
-rw-r--r--samples/hello-oboe/src/main/cpp/SoundGenerator.h22
-rw-r--r--samples/hello-oboe/src/main/cpp/jni_bridge.cpp22
-rw-r--r--samples/hello-oboe/src/main/cpp/ndk-stl-config.cmake39
-rw-r--r--samples/shared/DefaultAudioStreamCallback.h139
-rw-r--r--samples/shared/IRenderableAudio.h6
-rw-r--r--samples/shared/IRestartable.h28
-rw-r--r--samples/shared/ITappable.h25
-rw-r--r--samples/shared/TappableAudioSource.h38
24 files changed, 748 insertions, 603 deletions
diff --git a/samples/MegaDrone/build.gradle b/samples/MegaDrone/build.gradle
index 564993f3..02ab26ea 100644
--- a/samples/MegaDrone/build.gradle
+++ b/samples/MegaDrone/build.gradle
@@ -35,7 +35,7 @@ android {
}
externalNativeBuild {
cmake {
- path "CMakeLists.txt"
+ path "src/main/cpp/CMakeLists.txt"
}
}
}
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp b/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
deleted file mode 100644
index 94a330f4..00000000
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 <memory>
-#include "AudioEngine.h"
-#include "../../../../../src/common/OboeDebug.h"
-
-void AudioEngine::start(std::vector<int> cpuIds) {
-
- //LOGD("In start()");
-
- mCpuIds = cpuIds;
- AudioStreamBuilder builder;
-
- mStabilizedCallback = new StabilizedCallback(this);
- builder.setCallback(mStabilizedCallback);
- builder.setPerformanceMode(PerformanceMode::LowLatency);
- builder.setSharingMode(SharingMode::Exclusive);
-
- Result result = builder.openStream(&mStream);
- if (result != Result::OK){
- LOGE("Failed to open stream. Error: %s", convertToText(result));
- return;
- }
-
- // If the output is 16-bit ints then create a separate float buffer to render into.
- // This buffer will then be converted to 16-bit ints in onAudioReady
- if (mStream->getFormat() == AudioFormat::I16){
-
- // We use the audio stream's capacity as the maximum size since this is feasibly the
- // largest number of frames we'd be required to render inside the audio callback
- mConversionBuffer = std::make_unique<float[]>(mStream->getBufferCapacityInFrames() *
- mStream->getChannelCount());
- }
-
- mSynth = std::make_unique<Synth>(mStream->getSampleRate(), mStream->getChannelCount());
- mStream->setBufferSizeInFrames(mStream->getFramesPerBurst() * 2);
- mStream->requestStart();
-
- //LOGD("Finished start()");
-}
-
-void AudioEngine::stop() {
-
- //LOGD("In stop()");
-
- if (mStream != nullptr){
- mStream->close();
- }
- //LOGD("Finished stop()");
-}
-
-void AudioEngine::tap(bool isOn) {
- mSynth->setWaveOn(isOn);
-}
-
-DataCallbackResult
-AudioEngine::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
-
- if (!mIsThreadAffinitySet) setThreadAffinity();
-
- bool is16Bit = (oboeStream->getFormat() == AudioFormat::I16);
- float *outputBuffer = (is16Bit) ? mConversionBuffer.get() : static_cast<float*>(audioData);
- mSynth->renderAudio(outputBuffer, numFrames);
-
- if (is16Bit) {
- oboe::convertFloatToPcm16(outputBuffer,
- static_cast<int16_t *>(audioData),
- numFrames * oboeStream->getChannelCount());
- }
-
- return DataCallbackResult::Continue;
-}
-
-/**
- * Set the thread affinity for the current thread to mCpuIds. This can be useful to call on the
- * audio thread to avoid underruns caused by CPU core migrations to slower CPU cores.
- */
-void AudioEngine::setThreadAffinity() {
-
- pid_t current_thread_id = gettid();
- cpu_set_t cpu_set;
- CPU_ZERO(&cpu_set);
-
- // If the callback cpu ids aren't specified then bind to the current cpu
- if (mCpuIds.empty()) {
- int current_cpu_id = sched_getcpu();
- LOGV("Current CPU ID is %d", current_cpu_id);
- CPU_SET(current_cpu_id, &cpu_set);
- } else {
-
- for (size_t i = 0; i < mCpuIds.size(); i++) {
- int cpu_id = mCpuIds.at(i);
- LOGV("CPU ID %d added to cores set", cpu_id);
- CPU_SET(cpu_id, &cpu_set);
- }
- }
-
- int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set);
- if (result == 0) {
- LOGV("Thread affinity set");
- } else {
- LOGW("Error setting thread affinity. Error no: %d", result);
- }
-
- mIsThreadAffinitySet = true;
-}
diff --git a/samples/MegaDrone/CMakeLists.txt b/samples/MegaDrone/src/main/cpp/CMakeLists.txt
index fd94781d..ca6c2b39 100644
--- a/samples/MegaDrone/CMakeLists.txt
+++ b/samples/MegaDrone/src/main/cpp/CMakeLists.txt
@@ -4,20 +4,21 @@ cmake_minimum_required(VERSION 3.4.1)
### INCLUDE OBOE LIBRARY ###
# Set the path to the Oboe library directory
-set (OBOE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
+set (OBOE_DIR ../../../../..)
# Add the Oboe library as a subproject. Since Oboe is an out-of-tree source library we must also
# specify a binary directory
add_subdirectory(${OBOE_DIR} ./oboe-bin)
# Include the Oboe headers
-include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples)
+include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples/shared ${OBOE_DIR}/samples/debug-utils)
+
### END OBOE INCLUDE SECTION ###
add_library( megadrone SHARED
- src/main/cpp/native-lib.cpp
- src/main/cpp/AudioEngine.cpp
+ native-lib.cpp
+ MegaDroneEngine.cpp
)
target_link_libraries( megadrone log oboe )
diff --git a/samples/MegaDrone/src/main/cpp/MegaDroneEngine.cpp b/samples/MegaDrone/src/main/cpp/MegaDroneEngine.cpp
new file mode 100644
index 00000000..8e569f94
--- /dev/null
+++ b/samples/MegaDrone/src/main/cpp/MegaDroneEngine.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 <memory>
+#include "MegaDroneEngine.h"
+
+/**
+ * Main audio engine for the MegaDrone sample. It is responsible for:
+ *
+ * - Creating the callback object which will be supplied when constructing the audio stream
+ * - Setting the CPU core IDs to which the callback thread should bind to
+ * - Creating the playback stream, including setting the callback object
+ * - Creating `Synth` which will render the audio inside the callback
+ * - Starting the playback stream
+ * - Restarting the playback stream when `restart()` is called by the callback object
+ *
+ * @param cpuIds
+ */
+MegaDroneEngine::MegaDroneEngine(std::vector<int> cpuIds) {
+
+ createCallback(cpuIds);
+ start();
+}
+
+void MegaDroneEngine::tap(bool isDown) {
+ mAudioSource->tap(isDown);
+}
+
+void MegaDroneEngine::restart() {
+ start();
+}
+
+// Create the playback stream
+oboe::Result MegaDroneEngine::createPlaybackStream() {
+ oboe::AudioStreamBuilder builder;
+ return builder.setSharingMode(oboe::SharingMode::Exclusive)
+ ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+ ->setFormat(oboe::AudioFormat::Float)
+ ->setCallback(mCallback.get())
+ ->openManagedStream(mStream);
+}
+
+// Create the callback and set its thread affinity to the supplied CPU core IDs
+void MegaDroneEngine::createCallback(std::vector<int> cpuIds){
+ // Create the callback, we supply ourselves as the parent so that we can restart the stream
+ // when it's disconnected
+ mCallback = std::make_unique<DefaultAudioStreamCallback>(*this);
+
+ // Bind the audio callback to specific CPU cores as this can help avoid underruns caused by
+ // core migrations
+ mCallback->setCpuIds(cpuIds);
+ mCallback->setThreadAffinityEnabled(true);
+}
+
+void MegaDroneEngine::start(){
+ auto result = createPlaybackStream();
+ if (result == Result::OK){
+ // Create our synthesizer audio source using the properties of the stream
+ mAudioSource = std::make_shared<Synth>(mStream->getSampleRate(), mStream->getChannelCount());
+ mCallback->setSource(std::dynamic_pointer_cast<IRenderableAudio>(mAudioSource));
+ mStream->start();
+ } else {
+ LOGE("Failed to create the playback stream. Error: %s", convertToText(result));
+ }
+}
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.h b/samples/MegaDrone/src/main/cpp/MegaDroneEngine.h
index d3a9630a..dbe1ae1a 100644
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.h
+++ b/samples/MegaDrone/src/main/cpp/MegaDroneEngine.h
@@ -14,39 +14,41 @@
* limitations under the License.
*/
-#ifndef MEGADRONE_AUDIOENGINE_H
-#define MEGADRONE_AUDIOENGINE_H
+#ifndef MEGADRONE_ENGINE_H
+#define MEGADRONE_ENGINE_H
#include <oboe/Oboe.h>
#include <vector>
#include "Synth.h"
+#include <DefaultAudioStreamCallback.h>
+#include <TappableAudioSource.h>
+#include <IRestartable.h>
using namespace oboe;
-class AudioEngine : public AudioStreamCallback {
+class MegaDroneEngine : public IRestartable {
public:
- void start(std::vector<int> cpuIds);
- void tap(bool isOn);
+ MegaDroneEngine(std::vector<int> cpuIds);
- DataCallbackResult
- onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
+ virtual ~MegaDroneEngine() = default;
- void stop();
+ void tap(bool isDown);
-private:
+ // from IRestartable
+ virtual void restart() override;
- StabilizedCallback *mStabilizedCallback = nullptr;
- AudioStream *mStream = nullptr;
- std::unique_ptr<Synth> mSynth;
- std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to
- bool mIsThreadAffinitySet = false;
- std::unique_ptr<float[]> mConversionBuffer; // Used for float->int16 conversion
+private:
+ oboe::ManagedStream mStream;
+ std::shared_ptr<TappableAudioSource> mAudioSource;
+ std::unique_ptr<DefaultAudioStreamCallback> mCallback;
- void setThreadAffinity();
+ oboe::Result createPlaybackStream();
+ void createCallback(std::vector<int> cpuIds);
+ void start();
};
-#endif //MEGADRONE_AUDIOENGINE_H
+#endif //MEGADRONE_ENGINE_H
diff --git a/samples/MegaDrone/src/main/cpp/Synth.h b/samples/MegaDrone/src/main/cpp/Synth.h
index 4d935a9b..93e723cc 100644
--- a/samples/MegaDrone/src/main/cpp/Synth.h
+++ b/samples/MegaDrone/src/main/cpp/Synth.h
@@ -18,10 +18,11 @@
#define MEGADRONE_SYNTH_H
#include <array>
+#include <TappableAudioSource.h>
-#include "shared/Oscillator.h"
-#include "shared/Mixer.h"
-#include "shared/MonoToStereo.h"
+#include <Oscillator.h>
+#include <Mixer.h>
+#include <MonoToStereo.h>
constexpr int kNumOscillators = 100;
constexpr float kOscBaseFrequency = 116.0;
@@ -29,25 +30,26 @@ constexpr float kOscDivisor = 33;
constexpr float kOscAmplitude = 0.009;
-class Synth : public IRenderableAudio {
+class Synth : public TappableAudioSource {
public:
- Synth(int32_t sampleRate, int32_t channelCount) {
+ Synth(int32_t sampleRate, int32_t channelCount) :
+ TappableAudioSource(sampleRate, channelCount) {
for (int i = 0; i < kNumOscillators; ++i) {
- mOscs[i].setSampleRate(sampleRate);
- mOscs[i].setFrequency(kOscBaseFrequency+(static_cast<float>(i)/kOscDivisor));
+ mOscs[i].setSampleRate(mSampleRate);
+ mOscs[i].setFrequency(kOscBaseFrequency + (static_cast<float>(i) / kOscDivisor));
mOscs[i].setAmplitude(kOscAmplitude);
mMixer.addTrack(&mOscs[i]);
}
- if (channelCount == oboe::ChannelCount::Stereo) {
+ if (mChannelCount == oboe::ChannelCount::Stereo) {
mOutputStage = &mConverter;
} else {
mOutputStage = &mMixer;
}
}
- void setWaveOn(bool isEnabled) {
- for (auto &osc : mOscs) osc.setWaveOn(isEnabled);
+ void tap(bool isOn) override {
+ for (auto &osc : mOscs) osc.setWaveOn(isOn);
};
// From IRenderableAudio
diff --git a/samples/MegaDrone/src/main/cpp/native-lib.cpp b/samples/MegaDrone/src/main/cpp/native-lib.cpp
index 7567af4e..b29861f9 100644
--- a/samples/MegaDrone/src/main/cpp/native-lib.cpp
+++ b/samples/MegaDrone/src/main/cpp/native-lib.cpp
@@ -18,8 +18,8 @@
#include <string>
#include <vector>
-#include "AudioEngine.h"
-#include "../../../../../src/common/OboeDebug.h"
+#include "MegaDroneEngine.h"
+
std::vector<int> convertJavaArrayToVector(JNIEnv *env, jintArray intArray){
@@ -35,7 +35,6 @@ std::vector<int> convertJavaArrayToVector(JNIEnv *env, jintArray intArray){
}
return v;
}
-
extern "C" {
/**
* Start the audio engine
@@ -48,53 +47,31 @@ extern "C" {
JNIEXPORT jlong JNICALL
Java_com_example_oboe_megadrone_MainActivity_startEngine(JNIEnv *env, jobject /*unused*/,
jintArray jCpuIds) {
- // We use std::nothrow so `new` returns a nullptr if the engine creation fails
- AudioEngine *engine = new(std::nothrow) AudioEngine();
- if (engine) {
- std::vector<int> cpuIds = convertJavaArrayToVector(env, jCpuIds);
- engine->start(cpuIds);
- LOGD("Engine started");
- } else {
- LOGE("Failed to create audio engine");
- }
+ std::vector<int> cpuIds = convertJavaArrayToVector(env, jCpuIds);
+ LOGD("cpu ids size: %d", static_cast<int>(cpuIds.size()));
+ MegaDroneEngine *engine = new MegaDroneEngine(std::move(cpuIds));
+ LOGD("Engine Started");
return reinterpret_cast<jlong>(engine);
}
-/**
- * Stop the audio engine
- *
- * @param env
- * @param instance
- * @param jEngineHandle - pointer to the audio engine
- */
JNIEXPORT void JNICALL
-Java_com_example_oboe_megadrone_MainActivity_stopEngine( JNIEnv * /*unused*/, jobject /*unused*/,
- jlong jEngineHandle) {
- auto *engine = reinterpret_cast<AudioEngine *>(jEngineHandle);
+Java_com_example_oboe_megadrone_MainActivity_stopEngine(JNIEnv *env, jobject instance,
+ jlong jEngineHandle) {
+ auto engine = reinterpret_cast<MegaDroneEngine*>(jEngineHandle);
if (engine) {
- engine->stop();
delete engine;
- LOGD("Engine stopped");
} else {
- LOGE("Engine handle is invalid, call startEngine() to create a new one");
- return;
+ LOGD("Engine invalid, call startEngine() to create");
}
}
-/**
- * Send a tap event to the audio engine
- *
- * @param env
- * @param instance
- * @param jEngineHandle - pointer to audio engine
- * @param isDown - true if user is tapping down on screen, false user is lifting finger off screen
- */
+
JNIEXPORT void JNICALL
-Java_com_example_oboe_megadrone_MainActivity_tap(JNIEnv * /*unused*/, jobject /*unused*/,
- jlong jEngineHandle,
- jboolean isDown) {
- auto *engine = reinterpret_cast<AudioEngine *>(jEngineHandle);
- if (engine){
+Java_com_example_oboe_megadrone_MainActivity_tap(JNIEnv *env, jobject instance,
+ jlong jEngineHandle, jboolean isDown) {
+
+ auto *engine = reinterpret_cast<MegaDroneEngine*>(jEngineHandle);
+ if (engine) {
engine->tap(isDown);
} else {
LOGE("Engine handle is invalid, call createEngine() to create a new one");
diff --git a/samples/hello-oboe/build.gradle b/samples/hello-oboe/build.gradle
index cdd975de..26d3ccd3 100644
--- a/samples/hello-oboe/build.gradle
+++ b/samples/hello-oboe/build.gradle
@@ -27,7 +27,7 @@ android {
}
externalNativeBuild {
cmake {
- path 'CMakeLists.txt'
+ path 'src/main/cpp/CMakeLists.txt'
}
}
}
diff --git a/samples/hello-oboe/CMakeLists.txt b/samples/hello-oboe/src/main/cpp/CMakeLists.txt
index 5541dcef..77e898e1 100644
--- a/samples/hello-oboe/CMakeLists.txt
+++ b/samples/hello-oboe/src/main/cpp/CMakeLists.txt
@@ -19,28 +19,30 @@ cmake_minimum_required(VERSION 3.4.1)
### INCLUDE OBOE LIBRARY ###
# Set the path to the Oboe library directory
-set (OBOE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
+set (OBOE_DIR ../../../../../)
# Add the Oboe library as a subproject. Since Oboe is an out-of-tree source library we must also
# specify a binary directory
add_subdirectory(${OBOE_DIR} ./oboe-bin)
# Include the Oboe headers and shared sample code
-include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples)
-
-### END OBOE INCLUDE SECTION ###
+include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples/shared)
# Debug utilities
-set (DEBUG_UTILS_PATH "../debug-utils")
+set (DEBUG_UTILS_PATH "${OBOE_DIR}/samples/debug-utils")
set (DEBUG_UTILS_SOURCES ${DEBUG_UTILS_PATH}/trace.cpp)
include_directories(${DEBUG_UTILS_PATH})
+
+### END OBOE INCLUDE SECTION ###
+
+
# App specific sources
-set (APP_DIR src/main/cpp)
-file (GLOB_RECURSE APP_SOURCES
- ${APP_DIR}/jni_bridge.cpp
- ${APP_DIR}/PlayAudioEngine.cpp
- ${APP_DIR}/SoundGenerator.cpp
+set (APP_SOURCES
+ jni_bridge.cpp
+ HelloOboeEngine.cpp
+ SoundGenerator.cpp
+ LatencyTuningCallback.cpp
)
# Build the libhello-oboe library
diff --git a/samples/hello-oboe/src/main/cpp/HelloOboeEngine.cpp b/samples/hello-oboe/src/main/cpp/HelloOboeEngine.cpp
new file mode 100644
index 00000000..de3e3bac
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/HelloOboeEngine.cpp
@@ -0,0 +1,148 @@
+/**
+ * Copyright 2017 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 <inttypes.h>
+#include <memory>
+
+#include <Oscillator.h>
+
+#include "HelloOboeEngine.h"
+#include "SoundGenerator.h"
+
+
+/**
+ * Main audio engine for the HelloOboe sample. It is responsible for:
+ *
+ * - Creating a callback object which is supplied when constructing the audio stream, and will be
+ * called when the stream starts
+ * - Restarting the stream when user-controllable properties (Audio API, channel count etc) are
+ * changed, and when the stream is disconnected (e.g. when headphones are attached)
+ * - Calculating the audio latency of the stream
+ *
+ */
+HelloOboeEngine::HelloOboeEngine(): mLatencyCallback(std::make_unique<LatencyTuningCallback>(*this)) {
+ start();
+ updateLatencyDetection();
+}
+
+double HelloOboeEngine::getCurrentOutputLatencyMillis() {
+ if (!mIsLatencyDetectionSupported) return -1;
+ // Get the time that a known audio frame was presented for playing
+ auto result = mStream->getTimestamp(CLOCK_MONOTONIC);
+ double outputLatencyMillis = -1;
+ const int64_t kNanosPerMillisecond = 1000000;
+ if (result == oboe::Result::OK) {
+ oboe::FrameTimestamp playedFrame = result.value();
+ // Get the write index for the next audio frame
+ int64_t writeIndex = mStream->getFramesWritten();
+ // Calculate the number of frames between our known frame and the write index
+ int64_t frameIndexDelta = writeIndex - playedFrame.position;
+ // Calculate the time which the next frame will be presented
+ int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / (mStream->getSampleRate());
+ int64_t nextFramePresentationTime = playedFrame.timestamp + frameTimeDelta;
+ // Assume that the next frame will be written at the current time
+ using namespace std::chrono;
+ int64_t nextFrameWriteTime =
+ duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
+ // Calculate the latency
+ outputLatencyMillis = static_cast<double>(nextFramePresentationTime - nextFrameWriteTime)
+ / kNanosPerMillisecond;
+ } else {
+ LOGE("Error calculating latency: %s", oboe::convertToText(result.error()));
+ }
+ return outputLatencyMillis;
+}
+
+void HelloOboeEngine::setBufferSizeInBursts(int32_t numBursts) {
+ mIsLatencyDetectionSupported = false;
+ mLatencyCallback->setBufferTuneEnabled(numBursts == kBufferSizeAutomatic);
+ auto result = mStream->setBufferSizeInFrames(
+ numBursts * mStream->getFramesPerBurst());
+ updateLatencyDetection();
+ if (result) {
+ LOGD("Buffer size successfully changed to %d", result.value());
+ } else {
+ LOGW("Buffer size could not be changed, %d", result.error());
+ }
+}
+
+void HelloOboeEngine::setAudioApi(oboe::AudioApi audioApi) {
+ mIsLatencyDetectionSupported = false;
+ createPlaybackStream(*oboe::AudioStreamBuilder(*mStream)
+ .setAudioApi(audioApi));
+ updateAudioSource();
+ LOGD("AudioAPI is now %d", mStream->getAudioApi());
+}
+
+void HelloOboeEngine::setChannelCount(int channelCount) {
+ mIsLatencyDetectionSupported = false;
+ createPlaybackStream(*oboe::AudioStreamBuilder(*mStream)
+ .setChannelCount(channelCount));
+ updateAudioSource();
+ LOGD("Channel count is now %d", mStream->getChannelCount());
+}
+
+void HelloOboeEngine::setDeviceId(int32_t deviceId) {
+ mIsLatencyDetectionSupported = false;
+ createPlaybackStream(*oboe::AudioStreamBuilder(*mStream).
+ setDeviceId(deviceId));
+ updateAudioSource();
+ LOGD("Device ID is now %d", mStream->getDeviceId());
+}
+
+bool HelloOboeEngine::isLatencyDetectionSupported() {
+ return mIsLatencyDetectionSupported;
+}
+
+void HelloOboeEngine::updateLatencyDetection() {
+ mIsLatencyDetectionSupported = (mStream->getTimestamp((CLOCK_MONOTONIC)) !=
+ oboe::Result::ErrorUnimplemented);
+}
+
+void HelloOboeEngine::tap(bool isDown) {
+ mAudioSource->tap(isDown);
+}
+
+void HelloOboeEngine::updateAudioSource() {
+ *mAudioSource = SoundGenerator(mStream->getSampleRate(), mStream->getChannelCount());
+ mStream->start();
+ updateLatencyDetection();
+}
+
+oboe::Result HelloOboeEngine::createPlaybackStream(oboe::AudioStreamBuilder builder) {
+ return builder.setSharingMode(oboe::SharingMode::Exclusive)
+ ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+ ->setFormat(oboe::AudioFormat::Float)
+ ->setCallback(mLatencyCallback.get())
+ ->openManagedStream(mStream);
+}
+
+void HelloOboeEngine::restart() {
+ start();
+}
+
+void HelloOboeEngine::start() {
+ auto result = createPlaybackStream(oboe::AudioStreamBuilder());
+ if (result == oboe::Result::OK){
+ mAudioSource = std::make_shared<SoundGenerator>(mStream->getSampleRate(), mStream->getChannelCount());
+ mLatencyCallback->setSource(std::dynamic_pointer_cast<IRenderableAudio>(mAudioSource));
+ mStream->start();
+ } else {
+ LOGE("Error creating playback stream. Error: %s", oboe::convertToText(result));
+ }
+}
+
diff --git a/samples/hello-oboe/src/main/cpp/HelloOboeEngine.h b/samples/hello-oboe/src/main/cpp/HelloOboeEngine.h
new file mode 100644
index 00000000..bf1d92b8
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/HelloOboeEngine.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 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_HELLO_OBOE_ENGINE_H
+#define OBOE_HELLO_OBOE_ENGINE_H
+
+#include <oboe/Oboe.h>
+
+#include "SoundGenerator.h"
+#include "LatencyTuningCallback.h"
+#include "IRestartable.h"
+
+constexpr int32_t kBufferSizeAutomatic = 0;
+
+class HelloOboeEngine : public IRestartable {
+
+public:
+ HelloOboeEngine();
+
+ virtual ~HelloOboeEngine() = default;
+
+ void tap(bool isDown);
+
+ // From IRestartable
+ void restart() override;
+
+ // These methods reset the underlying stream with new properties
+
+ /**
+ * Set the audio device which should be used for playback. Can be set to oboe::kUnspecified if
+ * you want to use the default playback device (which is usually the built-in speaker if
+ * no other audio devices, such as headphones, are attached).
+ *
+ * @param deviceId the audio device id, can be obtained through an {@link AudioDeviceInfo} object
+ * using Java/JNI.
+ */
+ void setDeviceId(int32_t deviceId);
+
+ void setChannelCount(int channelCount);
+
+ void setAudioApi(oboe::AudioApi audioApi);
+
+ void setBufferSizeInBursts(int32_t numBursts);
+
+ /**
+ * Calculate the current latency between writing a frame to the output stream and
+ * the same frame being presented to the audio hardware.
+ *
+ * Here's how the calculation works:
+ *
+ * 1) Get the time a particular frame was presented to the audio hardware
+ * @see AudioStream::getTimestamp
+ * 2) From this extrapolate the time which the *next* audio frame written to the stream
+ * will be presented
+ * 3) Assume that the next audio frame is written at the current time
+ * 4) currentLatency = nextFramePresentationTime - nextFrameWriteTime
+ *
+ * @return Output Latency in Milliseconds
+ */
+ double getCurrentOutputLatencyMillis();
+
+ bool isLatencyDetectionSupported();
+
+private:
+ oboe::ManagedStream mStream;
+ std::unique_ptr<LatencyTuningCallback> mLatencyCallback;
+ std::shared_ptr<SoundGenerator> mAudioSource;
+ bool mIsLatencyDetectionSupported = false;
+
+ oboe::Result createPlaybackStream(oboe::AudioStreamBuilder builder);
+ void updateLatencyDetection();
+ void updateAudioSource();
+ void start();
+};
+
+#endif //OBOE_HELLO_OBOE_ENGINE_H
diff --git a/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.cpp b/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.cpp
new file mode 100644
index 00000000..0cc4a1d9
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2017 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 "LatencyTuningCallback.h"
+
+oboe::DataCallbackResult LatencyTuningCallback::onAudioReady(
+ oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
+ if (!mLatencyTuner) mLatencyTuner = std::make_unique<oboe::LatencyTuner>(*oboeStream);
+ if (mBufferTuneEnabled && oboeStream->getAudioApi() == oboe::AudioApi::AAudio) {
+ mLatencyTuner->tune();
+ }
+ auto underrunCountResult = oboeStream->getXRunCount();
+ int bufferSize = oboeStream->getBufferSizeInFrames();
+ /**
+ * The following output can be seen by running a systrace. Tracing is preferable to logging
+ * inside the callback since tracing does not block.
+ *
+ * See https://developer.android.com/studio/profile/systrace-commandline.html
+ */
+ if (Trace::isEnabled()) Trace::beginSection("numFrames %d, Underruns %d, buffer size %d",
+ numFrames, underrunCountResult.value(), bufferSize);
+ auto result = DefaultAudioStreamCallback::onAudioReady(oboeStream, audioData, numFrames);
+ if (Trace::isEnabled()) Trace::endSection();
+ return result;
+}
+
+
diff --git a/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.h b/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.h
new file mode 100644
index 00000000..42cbe4c2
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/LatencyTuningCallback.h
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2017 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_LATENCY_TUNING_CALLBACK_H
+#define SAMPLES_LATENCY_TUNING_CALLBACK_H
+
+#include <oboe/Oboe.h>
+#include <oboe/LatencyTuner.h>
+
+#include <TappableAudioSource.h>
+#include <DefaultAudioStreamCallback.h>
+#include <trace.h>
+
+/**
+ * This callback object extends the functionality of `DefaultAudioStreamCallback` by automatically
+ * tuning the latency of the audio stream. @see onAudioReady for more details on this.
+ *
+ * It also demonstrates how to use tracing functions for logging inside the audio callback without
+ * blocking.
+ */
+class LatencyTuningCallback: public DefaultAudioStreamCallback {
+public:
+ LatencyTuningCallback(IRestartable &mParent) : DefaultAudioStreamCallback(mParent) {
+
+ // Initialize the trace functions, this enables you to output trace statements without
+ // blocking. See https://developer.android.com/studio/profile/systrace-commandline.html
+ Trace::initialize();
+ }
+
+ /**
+ * Every time the playback stream requires data this method will be called.
+ *
+ * @param audioStream the audio stream which is requesting data, this is the mPlayStream object
+ * @param audioData an empty buffer into which we can write our audio data
+ * @param numFrames the number of audio frames which are required
+ * @return Either oboe::DataCallbackResult::Continue if the stream should continue requesting data
+ * or oboe::DataCallbackResult::Stop if the stream should stop.
+ */
+ oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
+
+ void setBufferTuneEnabled(bool enabled) {mBufferTuneEnabled = enabled;}
+
+private:
+ bool mBufferTuneEnabled = true;
+
+ // This will be used to automatically tune the buffer size of the stream, obtaining optimal latency
+ std::unique_ptr<oboe::LatencyTuner> mLatencyTuner;
+};
+
+#endif //SAMPLES_LATENCY_TUNING_CALLBACK_H
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
deleted file mode 100644
index a03a9317..00000000
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
+++ /dev/null
@@ -1,255 +0,0 @@
-/**
- * Copyright 2017 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 <trace.h>
-#include <inttypes.h>
-#include <memory>
-
-#include "shared/Oscillator.h"
-
-#include "PlayAudioEngine.h"
-#include "logging_macros.h"
-#include "SoundGenerator.h"
-
-constexpr int64_t kNanosPerMillisecond = 1000000; // Use int64_t to avoid overflows in calculations
-
-
-PlayAudioEngine::PlayAudioEngine() {
-
- // Initialize the trace functions, this enables you to output trace statements without
- // blocking. See https://developer.android.com/studio/profile/systrace-commandline.html
- Trace::initialize();
- oboe::AudioStreamBuilder builder = oboe::AudioStreamBuilder();
- createPlaybackStream(&builder);
-}
-
-/**
- * Creates an audio stream for playback. Takes in a builder pointer which contains stream params
- */
-void PlayAudioEngine::createPlaybackStream(oboe::AudioStreamBuilder *builder) {
- oboe::Result result = builder->setSharingMode(oboe::SharingMode::Exclusive)
- ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
- ->setCallback(this)
- ->openManagedStream(mPlayStream);
- if (result == oboe::Result::OK && mPlayStream.get() != nullptr) {
- // Set the buffer size to the burst size - this will give us the minimum possible latency
- mPlayStream->setBufferSizeInFrames(mPlayStream->getFramesPerBurst());
-
- // TODO: Implement Oboe_convertStreamToText
- // PrintAudioStreamInfo(mPlayStream);
- if (mPlayStream->getFormat() == oboe::AudioFormat::I16){
- // create a buffer of floats which we can render our audio data into
- int conversionBufferSamples = mPlayStream->getBufferCapacityInFrames() * mPlayStream->getChannelCount();
- LOGD("Stream format is 16-bit integers, creating a temporary buffer of %d samples"
- " for float->int16 conversion", conversionBufferSamples);
- mConversionBuffer = std::make_unique<float[]>(conversionBufferSamples);
- }
-
- mSoundGenerator = std::make_unique<SoundGenerator>(
- mPlayStream->getSampleRate(),
- mPlayStream->getBufferCapacityInFrames(),
- mPlayStream->getChannelCount()
- );
-
- // Create a latency tuner which will automatically tune our buffer size.
- mLatencyTuner = std::make_unique<oboe::LatencyTuner>(*mPlayStream);
- // Start the stream - the dataCallback function will start being called
- result = mPlayStream->requestStart();
- if (result != oboe::Result::OK) {
- LOGE("Error starting stream. %s", oboe::convertToText(result));
- }
-
- mIsLatencyDetectionSupported = (mPlayStream->getTimestamp(CLOCK_MONOTONIC, 0, 0) !=
- oboe::Result::ErrorUnimplemented);
-
- } else {
- LOGE("Failed to create stream. Error: %s", oboe::convertToText(result));
- }
-}
-
-void PlayAudioEngine::setToneOn(bool isToneOn) {
- mSoundGenerator->setTonesOn(isToneOn);
-}
-
-/**
- * Every time the playback stream requires data this method will be called.
- *
- * @param audioStream the audio stream which is requesting data, this is the mPlayStream object
- * @param audioData an empty buffer into which we can write our audio data
- * @param numFrames the number of audio frames which are required
- * @return Either oboe::DataCallbackResult::Continue if the stream should continue requesting data
- * or oboe::DataCallbackResult::Stop if the stream should stop.
- */
-oboe::DataCallbackResult
-PlayAudioEngine::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) {
-
- if (mBufferSizeSelection == kBufferSizeAutomatic) mLatencyTuner->tune();
- int32_t bufferSize = audioStream->getBufferSizeInFrames();
-
- /**
- * The following output can be seen by running a systrace. Tracing is preferable to logging
- * inside the callback since tracing does not block.
- *
- * See https://developer.android.com/studio/profile/systrace-commandline.html
- */
- auto underrunCountResult = audioStream->getXRunCount();
-
- if (Trace::isEnabled()) Trace::beginSection("numFrames %d, Underruns %d, buffer size %d",
- numFrames, underrunCountResult.value(), bufferSize);
-
- bool is16BitFormat = (audioStream->getFormat() == oboe::AudioFormat::I16);
- int32_t channelCount = audioStream->getChannelCount();
-
- // If the stream is 16-bit render into a float buffer then convert that buffer to 16-bit ints
- float *outputBuffer = (is16BitFormat) ? mConversionBuffer.get() : static_cast<float *>(audioData);
- mSoundGenerator->renderAudio(outputBuffer, numFrames);
-
- if (is16BitFormat){
- oboe::convertFloatToPcm16(outputBuffer,
- static_cast<int16_t *>(audioData),
- numFrames * channelCount);
- }
-
- if (mIsLatencyDetectionSupported) {
- calculateCurrentOutputLatencyMillis(audioStream, &mCurrentOutputLatencyMillis);
- }
-
- if (Trace::isEnabled()) Trace::endSection();
- return oboe::DataCallbackResult::Continue;
-}
-
-/**
- * Calculate the current latency between writing a frame to the output stream and
- * the same frame being presented to the audio hardware.
- *
- * Here's how the calculation works:
- *
- * 1) Get the time a particular frame was presented to the audio hardware
- * @see AudioStream::getTimestamp
- * 2) From this extrapolate the time which the *next* audio frame written to the stream
- * will be presented
- * 3) Assume that the next audio frame is written at the current time
- * 4) currentLatency = nextFramePresentationTime - nextFrameWriteTime
- *
- * @param stream The stream being written to
- * @param latencyMillis pointer to a variable to receive the latency in milliseconds between
- * writing a frame to the stream and that frame being presented to the audio hardware.
- * @return oboe::Result::OK or a oboe::Result::Error* value. It is normal to receive an error soon
- * after a stream has started because the timestamps are not yet available.
- */
-oboe::Result
-PlayAudioEngine::calculateCurrentOutputLatencyMillis(oboe::AudioStream *stream,
- double *latencyMillis) {
-
- // Get the time that a known audio frame was presented for playing
- auto result = stream->getTimestamp(CLOCK_MONOTONIC);
-
- if (result == oboe::Result::OK) {
-
- oboe::FrameTimestamp playedFrame = result.value();
-
- // Get the write index for the next audio frame
- int64_t writeIndex = stream->getFramesWritten();
-
- // Calculate the number of frames between our known frame and the write index
- int64_t frameIndexDelta = writeIndex - playedFrame.position;
-
- // Calculate the time which the next frame will be presented
- int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / (stream->getSampleRate());
- int64_t nextFramePresentationTime = playedFrame.timestamp + frameTimeDelta;
-
- // Assume that the next frame will be written at the current time
- using namespace std::chrono;
- int64_t nextFrameWriteTime =
- duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
-
- // Calculate the latency
- *latencyMillis = static_cast<double>(nextFramePresentationTime - nextFrameWriteTime)
- / kNanosPerMillisecond;
- } else {
- LOGE("Error calculating latency: %s", oboe::convertToText(result.error()));
- }
-
- return result;
-}
-
-/**
- * If there is an error with a stream this function will be called. A common example of an error
- * is when an audio device (such as headphones) is disconnected. It is safe to restart the stream
- * in this method. There is no need to create a new thread.
- *
- * @param audioStream the stream with the error
- * @param error the error which occured, a human readable string can be obtained using
- * oboe::convertToText(error);
- *
- * @see oboe::StreamCallback
- */
-void PlayAudioEngine::onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) {
- if (error == oboe::Result::ErrorDisconnected) {
- oboe::AudioStreamBuilder builder_ = oboe::AudioStreamBuilder(*oboeStream);
- restartStream(&builder_);
- }
-}
-
-void PlayAudioEngine::restartStream(oboe::AudioStreamBuilder *builder) {
- LOGI("Restarting stream");
- createPlaybackStream(builder);
-}
-
-double PlayAudioEngine::getCurrentOutputLatencyMillis() {
- return mCurrentOutputLatencyMillis;
-}
-
-void PlayAudioEngine::setBufferSizeInBursts(int32_t numBursts) {
- mBufferSizeSelection = numBursts;
- auto result = mPlayStream->setBufferSizeInFrames(
- mBufferSizeSelection * mPlayStream->getFramesPerBurst());
- if (result) {
- LOGD("Buffer size successfully changed to %d", result.value());
- } else {
- LOGW("Buffer size could not be changed, %d", result.error());
- }
-}
-
-bool PlayAudioEngine::isLatencyDetectionSupported() {
- return mIsLatencyDetectionSupported;
-}
-void PlayAudioEngine::setAudioApi(oboe::AudioApi audioApi) {
- oboe::AudioStreamBuilder *builder = oboe::AudioStreamBuilder(*mPlayStream).setAudioApi(audioApi);
- restartStream(builder);
- LOGD("AudioAPI is now %d", mPlayStream->getAudioApi());
-}
-
-void PlayAudioEngine::setChannelCount(int channelCount) {
- oboe::AudioStreamBuilder *builder = oboe::AudioStreamBuilder(*mPlayStream).setChannelCount(channelCount);
- restartStream(builder);
- LOGD("Channel count is now %d", mPlayStream->getChannelCount());
-}
-
-/**
- * Set the audio device which should be used for playback. Can be set to oboe::kUnspecified if
- * you want to use the default playback device (which is usually the built-in speaker if
- * no other audio devices, such as headphones, are attached).
- *
- * @param deviceId the audio device id, can be obtained through an {@link AudioDeviceInfo} object
- * using Java/JNI.
- */
-void PlayAudioEngine::setDeviceId(int32_t deviceId) {
- oboe::AudioStreamBuilder *builder = oboe::AudioStreamBuilder(*mPlayStream).setDeviceId(deviceId);
- restartStream(builder);
- LOGD("Device ID is now %d", mPlayStream->getDeviceId());
-}
-
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
deleted file mode 100644
index 4f1ec098..00000000
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2017 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_HELLOOBOE_PLAYAUDIOENGINE_H
-#define OBOE_HELLOOBOE_PLAYAUDIOENGINE_H
-
-#include <oboe/Oboe.h>
-
-#include "SoundGenerator.h"
-
-constexpr int32_t kBufferSizeAutomatic = 0;
-
-class PlayAudioEngine : oboe::AudioStreamCallback {
-
-public:
- PlayAudioEngine();
-
-
- void setAudioApi(oboe::AudioApi audioApi);
-
- void setDeviceId(int32_t deviceId);
-
- void setChannelCount(int channelCount);
-
- void setBufferSizeInBursts(int32_t numBursts);
-
- void setToneOn(bool isToneOn);
-
- double getCurrentOutputLatencyMillis();
-
- bool isLatencyDetectionSupported();
-
- // oboe::StreamCallback methods
- oboe::DataCallbackResult
- onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
-
- void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error);
-
-
-
-private:
- oboe::ManagedStream mPlayStream;
- double mCurrentOutputLatencyMillis = 0;
- int32_t mBufferSizeSelection = kBufferSizeAutomatic; // Used to keep track if we are auto tuning
- bool mIsLatencyDetectionSupported = false;
- std::unique_ptr<oboe::LatencyTuner> mLatencyTuner;
- std::unique_ptr<SoundGenerator> mSoundGenerator;
- std::unique_ptr<float[]> mConversionBuffer { nullptr };
- // We will handle conversion to avoid getting kicked off the fast track as penalty
-
- void createPlaybackStream(oboe::AudioStreamBuilder *builder);
-
- void restartStream(oboe::AudioStreamBuilder *builder);
-
- oboe::Result calculateCurrentOutputLatencyMillis(oboe::AudioStream *stream, double *latencyMillis);
-
-};
-
-#endif //OBOE_HELLOOBOE_PLAYAUDIOENGINE_H
diff --git a/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
index 690bf37e..1de9030f 100644
--- a/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-#include <memory>
#include "SoundGenerator.h"
-SoundGenerator::SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t channelCount)
- : mSampleRate(sampleRate)
- , mChannelCount(channelCount)
- , mOscillators(std::make_unique<Oscillator[]>(channelCount))
- , mBuffer(std::make_unique<float[]>(maxFrames)){
+SoundGenerator::SoundGenerator(int32_t sampleRate, int32_t channelCount) :
+ TappableAudioSource(sampleRate, channelCount)
+ , mOscillators(std::make_unique<Oscillator[]>(channelCount)){
double frequency = 440.0;
constexpr double interval = 110.0;
@@ -37,18 +34,17 @@ SoundGenerator::SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t ch
}
void SoundGenerator::renderAudio(float *audioData, int32_t numFrames) {
-
// Render each oscillator into its own channel
+ std::fill_n(mBuffer.get(), kSharedBufferSize, 0);
for (int i = 0; i < mChannelCount; ++i) {
-
mOscillators[i].renderAudio(mBuffer.get(), numFrames);
for (int j = 0; j < numFrames; ++j) {
- audioData[(j*mChannelCount)+i] = mBuffer[j];
+ audioData[(j * mChannelCount) + i] = mBuffer[j];
}
}
}
-void SoundGenerator::setTonesOn(bool isOn) {
+void SoundGenerator::tap(bool isOn) {
for (int i = 0; i < mChannelCount; ++i) {
mOscillators[i].setWaveOn(isOn);
}
diff --git a/samples/hello-oboe/src/main/cpp/SoundGenerator.h b/samples/hello-oboe/src/main/cpp/SoundGenerator.h
index 7577cdca..1ffae37f 100644
--- a/samples/hello-oboe/src/main/cpp/SoundGenerator.h
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.h
@@ -18,13 +18,15 @@
#define SAMPLES_SOUNDGENERATOR_H
-#include <shared/IRenderableAudio.h>
-#include <shared/Oscillator.h>
+#include <Oscillator.h>
+#include <TappableAudioSource.h>
/**
* Generates a fixed frequency tone for each channel.
+ * Implements RenderableTap (sound source with toggle) which is required for AudioEngines.
*/
-class SoundGenerator : public IRenderableAudio {
+class SoundGenerator : public TappableAudioSource {
+ static constexpr size_t kSharedBufferSize = 1024;
public:
/**
* Create a new SoundGenerator object.
@@ -36,20 +38,20 @@ public:
* channel, the output will be interlaced.
*
*/
- SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t channelCount);
+ SoundGenerator(int32_t sampleRate, int32_t channelCount);
~SoundGenerator() = default;
+ SoundGenerator(SoundGenerator&& other) = default;
+ SoundGenerator& operator= (SoundGenerator&& other) = default;
+
// Switch the tones on
- void setTonesOn(bool isOn);
+ void tap(bool isOn) override;
- // From IRenderableAudio
void renderAudio(float *audioData, int32_t numFrames) override;
private:
- const int32_t mSampleRate;
- const int32_t mChannelCount;
- const std::unique_ptr<Oscillator[]> mOscillators;
- const std::unique_ptr<float[]> mBuffer;
+ std::unique_ptr<Oscillator[]> mOscillators;
+ std::unique_ptr<float[]> mBuffer = std::make_unique<float[]>(kSharedBufferSize);
};
diff --git a/samples/hello-oboe/src/main/cpp/jni_bridge.cpp b/samples/hello-oboe/src/main/cpp/jni_bridge.cpp
index 512ed306..b248ff03 100644
--- a/samples/hello-oboe/src/main/cpp/jni_bridge.cpp
+++ b/samples/hello-oboe/src/main/cpp/jni_bridge.cpp
@@ -16,7 +16,7 @@
#include <jni.h>
#include <oboe/Oboe.h>
-#include "PlayAudioEngine.h"
+#include "HelloOboeEngine.h"
#include "logging_macros.h"
extern "C" {
@@ -31,7 +31,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1createEngine(
JNIEnv *env,
jclass /*unused*/) {
// We use std::nothrow so `new` returns a nullptr if the engine creation fails
- PlayAudioEngine *engine = new(std::nothrow) PlayAudioEngine();
+ HelloOboeEngine *engine = new(std::nothrow) HelloOboeEngine();
return reinterpret_cast<jlong>(engine);
}
@@ -41,7 +41,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1deleteEngine(
jclass,
jlong engineHandle) {
- delete reinterpret_cast<PlayAudioEngine *>(engineHandle);
+ delete reinterpret_cast<HelloOboeEngine *>(engineHandle);
}
JNIEXPORT void JNICALL
@@ -51,12 +51,12 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1setToneOn(
jlong engineHandle,
jboolean isToneOn) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine *>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine *>(engineHandle);
if (engine == nullptr) {
LOGE("Engine handle is invalid, call createHandle() to create a new one");
return;
}
- engine->setToneOn(isToneOn);
+ engine->tap(isToneOn);
}
JNIEXPORT void JNICALL
@@ -66,7 +66,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1setAudioApi(
jlong engineHandle,
jint audioApi) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine handle is invalid, call createHandle() to create a new one");
return;
@@ -83,7 +83,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1setAudioDeviceId(
jlong engineHandle,
jint deviceId) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine handle is invalid, call createHandle() to create a new one");
return;
@@ -98,7 +98,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1setChannelCount(
jlong engineHandle,
jint channelCount) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine handle is invalid, call createHandle() to create a new one");
return;
@@ -113,7 +113,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1setBufferSizeInBurs
jlong engineHandle,
jint bufferSizeInBursts) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine handle is invalid, call createHandle() to create a new one");
return;
@@ -128,7 +128,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1getCurrentOutputLat
jclass,
jlong engineHandle) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine is null, you must call createEngine before calling this method");
return static_cast<jdouble>(-1.0);
@@ -142,7 +142,7 @@ Java_com_google_sample_oboe_hellooboe_PlaybackEngine_native_1isLatencyDetectionS
jclass type,
jlong engineHandle) {
- PlayAudioEngine *engine = reinterpret_cast<PlayAudioEngine*>(engineHandle);
+ HelloOboeEngine *engine = reinterpret_cast<HelloOboeEngine*>(engineHandle);
if (engine == nullptr) {
LOGE("Engine is null, you must call createEngine before calling this method");
return JNI_FALSE;
diff --git a/samples/hello-oboe/src/main/cpp/ndk-stl-config.cmake b/samples/hello-oboe/src/main/cpp/ndk-stl-config.cmake
deleted file mode 100644
index 3133faf3..00000000
--- a/samples/hello-oboe/src/main/cpp/ndk-stl-config.cmake
+++ /dev/null
@@ -1,39 +0,0 @@
-# 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()
diff --git a/samples/shared/DefaultAudioStreamCallback.h b/samples/shared/DefaultAudioStreamCallback.h
new file mode 100644
index 00000000..64b84748
--- /dev/null
+++ b/samples/shared/DefaultAudioStreamCallback.h
@@ -0,0 +1,139 @@
+/*
+ * 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 SAMPLES_DEFAULT_AUDIO_STREAM_CALLBACK_H
+#define SAMPLES_DEFAULT_AUDIO_STREAM_CALLBACK_H
+
+
+#include <vector>
+#include <oboe/AudioStreamCallback.h>
+#include <logging_macros.h>
+
+#include "IRenderableAudio.h"
+#include "IRestartable.h"
+
+/**
+ * This is a callback object which will render data from an `IRenderableAudio` source. It is
+ * constructed using an `IRestartable` which allows it to automatically restart the parent object
+ * if the stream is disconnected (for example, when headphones are attached).
+ *
+ * @param IRestartable - the object which should be restarted when the stream is disconnected
+ */
+class DefaultAudioStreamCallback : public oboe::AudioStreamCallback {
+public:
+ DefaultAudioStreamCallback(IRestartable &parent): mParent(parent) {}
+ virtual ~DefaultAudioStreamCallback() = default;
+
+ virtual oboe::DataCallbackResult
+ onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
+
+ if (mIsThreadAffinityEnabled && !mIsThreadAffinitySet) {
+ setThreadAffinity();
+ mIsThreadAffinitySet = true;
+ }
+
+ float *outputBuffer = static_cast<float *>(audioData);
+ if (!mRenderable) {
+ LOGE("Renderable source not set!");
+ return oboe::DataCallbackResult::Stop;
+ }
+ mRenderable->renderAudio(outputBuffer, numFrames);
+ return oboe::DataCallbackResult::Continue;
+ }
+ virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override {
+ // Restart the stream when it errors out with disconnect
+ if (error == oboe::Result::ErrorDisconnected) {
+ LOGE("Restarting AudioStream after disconnect");
+ mParent.restart();
+ } else {
+ LOGE("Unknown error");
+ }
+ mIsThreadAffinitySet = false;
+ }
+
+ void setSource(std::shared_ptr<IRenderableAudio> renderable) {
+ mRenderable = renderable;
+ }
+
+ std::shared_ptr<IRenderableAudio> getSource() {
+ return mRenderable;
+ }
+
+ /**
+ * Set the CPU IDs to bind the audio callback thread to
+ *
+ * @param mCpuIds - the CPU IDs to bind to
+ */
+ void setCpuIds(std::vector<int> cpuIds){
+ mCpuIds = std::move(cpuIds);
+ }
+
+ /**
+ * Enable or disable binding the audio callback thread to specific CPU cores. The CPU core IDs
+ * can be specified using @see setCpuIds. If no CPU IDs are specified the initial core which the
+ * audio thread is called on will be used.
+ *
+ * @param isEnabled - whether the audio callback thread should be bound to specific CPU core(s)
+ */
+ void setThreadAffinityEnabled(bool isEnabled){
+ mIsThreadAffinityEnabled = isEnabled;
+ LOGD("Thread affinity enabled: %s", (isEnabled) ? "true" : "false");
+ }
+
+private:
+ std::shared_ptr<IRenderableAudio> mRenderable;
+ IRestartable &mParent;
+ std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to
+ std::atomic<bool> mIsThreadAffinityEnabled { false };
+ std::atomic<bool> mIsThreadAffinitySet { false };
+
+ /**
+ * Set the thread affinity for the current thread to mCpuIds. This can be useful to call on the
+ * audio thread to avoid underruns caused by CPU core migrations to slower CPU cores.
+ */
+ void setThreadAffinity() {
+
+ pid_t current_thread_id = gettid();
+ cpu_set_t cpu_set;
+ CPU_ZERO(&cpu_set);
+
+ // If the callback cpu ids aren't specified then bind to the current cpu
+ if (mCpuIds.empty()) {
+ int current_cpu_id = sched_getcpu();
+ LOGD("Binding to current CPU ID %d", current_cpu_id);
+ CPU_SET(current_cpu_id, &cpu_set);
+ } else {
+ LOGD("Binding to %d CPU IDs", static_cast<int>(mCpuIds.size()));
+ for (size_t i = 0; i < mCpuIds.size(); i++) {
+ int cpu_id = mCpuIds.at(i);
+ LOGD("CPU ID %d added to cores set", cpu_id);
+ CPU_SET(cpu_id, &cpu_set);
+ }
+ }
+
+ int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set);
+ if (result == 0) {
+ LOGV("Thread affinity set");
+ } else {
+ LOGW("Error setting thread affinity. Error no: %d", result);
+ }
+
+ mIsThreadAffinitySet = true;
+ }
+
+};
+
+#endif //SAMPLES_DEFAULT_AUDIO_STREAM_CALLBACK_H
diff --git a/samples/shared/IRenderableAudio.h b/samples/shared/IRenderableAudio.h
index eee9872e..da0b8cbc 100644
--- a/samples/shared/IRenderableAudio.h
+++ b/samples/shared/IRenderableAudio.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef MEGADRONE_RENDERABLEAUDIO_H
-#define MEGADRONE_RENDERABLEAUDIO_H
+#ifndef SAMPLES_IRENDERABLEAUDIO_H
+#define SAMPLES_IRENDERABLEAUDIO_H
#include <cstdint>
#include <string>
@@ -28,4 +28,4 @@ public:
};
-#endif //MEGADRONE_RENDERABLEAUDIO_H
+#endif //SAMPLES_IRENDERABLEAUDIO_H
diff --git a/samples/shared/IRestartable.h b/samples/shared/IRestartable.h
new file mode 100644
index 00000000..855fff3a
--- /dev/null
+++ b/samples/shared/IRestartable.h
@@ -0,0 +1,28 @@
+/*
+ * 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_IRESTARTABLE_H
+#define SAMPLES_IRESTARTABLE_H
+
+/**
+ * Represents an object which can be restarted. For example an audio engine which has one or more
+ * streams which can be restarted following a change in audio device configuration. For example,
+ * headphones being connected.
+ */
+class IRestartable {
+public:
+ virtual void restart() = 0;
+};
+#endif //SAMPLES_IRESTARTABLE_H
diff --git a/samples/shared/ITappable.h b/samples/shared/ITappable.h
new file mode 100644
index 00000000..40cb7167
--- /dev/null
+++ b/samples/shared/ITappable.h
@@ -0,0 +1,25 @@
+/*
+ * 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 SAMPLES_ITAPPABLE_H
+#define SAMPLES_ITAPPABLE_H
+
+class ITappable {
+public:
+ virtual ~ITappable() = default;
+ virtual void tap(bool isDown) = 0;
+};
+#endif //SAMPLES_ITAPPABLE_H
diff --git a/samples/shared/TappableAudioSource.h b/samples/shared/TappableAudioSource.h
new file mode 100644
index 00000000..fde3917b
--- /dev/null
+++ b/samples/shared/TappableAudioSource.h
@@ -0,0 +1,38 @@
+/*
+ * 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 SAMPLES_RENDERABLE_TAP_H
+#define SAMPLES_RENDERABLE_TAP_H
+
+#include <stdint.h>
+
+#include "IRenderableAudio.h"
+#include "ITappable.h"
+
+/**
+ * This class renders Float audio, but can be tapped to control.
+ * It also contains members for sample rate and channel count
+ */
+class TappableAudioSource : public IRenderableAudio, public ITappable {
+public:
+ TappableAudioSource(int32_t sampleRate, int32_t channelCount) :
+ mSampleRate(sampleRate), mChannelCount(channelCount) { }
+
+ int32_t mSampleRate;
+ int32_t mChannelCount;
+};
+
+#endif //SAMPLES_RENDERABLE_TAP_H \ No newline at end of file