Merge branch 'master' into flowgraph
diff --git a/.ci_tools/setup_env.sh b/.ci_tools/setup_env.sh
index cb9152b..33c7b42 100644
--- a/.ci_tools/setup_env.sh
+++ b/.ci_tools/setup_env.sh
@@ -41,11 +41,4 @@
 done < $TMP_SETUP_FILENAME
 #echo "Android platforms:"; cat $TMP_SETUP_FILENAME;rm -f $TMP_SETUP_FILENAME
 
-## Retrieve constraint-layout versions
-retrieve_versions "constraint-layout:"  $TMP_SETUP_FILENAME
-while read -r version_; do
-    echo y | $ANDROID_HOME/tools/bin/sdkmanager \
-        "extras;m2repository;com;android;support;constraint;constraint-layout;$version_"
-done < $TMP_SETUP_FILENAME
-#echo "constraint-layout versions:"; cat $TMP_SETUP_FILENAME;
 rm -f $TMP_SETUP_FILENAME
diff --git a/README.md b/README.md
index cd67f54..1ca8cd5 100755
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@
 We would love to receive your pull requests. Before we can though, please read the [contributing](CONTRIBUTING.md) guidelines.
 
 ## Version history
-View the [changelog](docs/ChangeLog.md).
+View the [releases page](../../releases).
 
 ## License
 [LICENSE](LICENSE)
diff --git a/apps/OboeTester/README.md b/apps/OboeTester/README.md
index b2091ae..e5eda61 100644
--- a/apps/OboeTester/README.md
+++ b/apps/OboeTester/README.md
@@ -1,6 +1,9 @@
 # Oboe Tester
 
-Test Oboe using an Interactive App
+Test Oboe using an Interactive App.
+
+Download the top level oboe repository from GitHub
+Then use Android Studio to build the app in this folder.
 
 ## Test Output
 
@@ -8,6 +11,8 @@
 
 ## Test Input
 
+Test input streams. Displays current volume
+
 ## Tap to Tone Latency
 
 Measure touch screen latency plus audio output latency.
diff --git a/apps/OboeTester/app/build.gradle b/apps/OboeTester/app/build.gradle
index ec63c94..c32b8b5 100644
--- a/apps/OboeTester/app/build.gradle
+++ b/apps/OboeTester/app/build.gradle
@@ -7,7 +7,7 @@
         minSdkVersion 26
         targetSdkVersion 26
         versionCode 3
-        versionName "1.2.01"
+        versionName "1.2.02"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         externalNativeBuild {
             cmake {
diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml
index 2465afa..edbfa6e 100644
--- a/apps/OboeTester/app/src/main/AndroidManifest.xml
+++ b/apps/OboeTester/app/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.sample.oboe.manualtest"
     android:versionCode="3"
-    android:versionName="1.2.01">
+    android:versionName="1.2.02">
     <!-- versionCode and versionName also have to be updated in build.gradle -->
 
     <uses-feature android:name="android.hardware.microphone" android:required="true" />
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
index 1e59431..4d389ea 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
@@ -186,7 +186,13 @@
     }
 
     oboe::Result start() {
+
         LOGD("NativeAudioContext: %s() called", __func__);
+
+        if (oboeStream == nullptr) {
+            return oboe::Result::ErrorInvalidState;
+        }
+
         stop();
 
         LOGD("NativeAudioContext: %s() start modules", __func__);
@@ -207,13 +213,13 @@
         oboe::Result result = oboe::Result::OK;
         if (oboeStream != nullptr) {
             result = oboeStream->requestStart();
-        }
 
-        if (!useCallback && result == oboe::Result::OK) {
-            LOGD("OboeAudioStream_start: start thread for blocking I/O");
-            // Instead of using the callback, start a thread that reads or writes the stream.
-            threadEnabled.store(true);
-            dataThread = new std::thread(threadCallback, this);
+            if (!useCallback && result == oboe::Result::OK) {
+                LOGD("OboeAudioStream_start: start thread for blocking I/O");
+                // Instead of using the callback, start a thread that reads or writes the stream.
+                threadEnabled.store(true);
+                dataThread = new std::thread(threadCallback, this);
+            }
         }
         LOGD("OboeAudioStream_start: start returning %d", result);
         return result;
diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
index 14db05d..714defd 100644
--- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
+++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
@@ -326,6 +326,21 @@
     return result;
 }
 
+JNIEXPORT jint JNICALL
+Java_com_google_sample_oboe_manualtest_OboeAudioStream_getXRunCount(
+        JNIEnv *env, jobject) {
+    jint result = (jlong) oboe::Result::ErrorNull;
+    if (engine.oboeStream != nullptr) {
+        auto oboeResult  = engine.oboeStream->getXRunCount();
+        if (!oboeResult) {
+            result = (jint) oboeResult.error();
+        } else {
+            result = oboeResult.value();
+        }
+    }
+    return result;
+}
+
 JNIEXPORT jlong JNICALL
 Java_com_google_sample_oboe_manualtest_OboeAudioStream_getCallbackCount(
         JNIEnv *env, jobject) {
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
index a34765b..2f83d25 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
@@ -31,7 +31,7 @@
     public StreamStatus getStreamStatus() {
         StreamStatus status = new StreamStatus();
         status.bufferSize = getBufferSizeInFrames();
-        status.xRunCount = getUnderrunCount();
+        status.xRunCount = getXRunCount();
         status.framesRead = getFramesRead();
         status.framesWritten = getFramesWritten();
         status.callbackCount = getCallbackCount();
@@ -117,9 +117,7 @@
 
     public void setAmplitude(double amplitude) {}
 
-    public int getUnderrunCount() {
-        return 0;
-    }
+    public abstract int getXRunCount();
 
 //    public boolean isUnderrunCountSupported() {
 //        return false;
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java
index 302a41a..0d62e4a 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java
@@ -169,6 +169,9 @@
     public native long getFramesRead();
 
     @Override
+    public native int getXRunCount();
+
+    @Override
     public native double getLatency();
 
     @Override
diff --git a/docs/FullGuide.md b/docs/FullGuide.md
index 04f64dd..b0cad02 100644
--- a/docs/FullGuide.md
+++ b/docs/FullGuide.md
@@ -348,12 +348,12 @@
 Note that in this example it is assumed the input and output streams have the same number of channels, format and sample rate. The format of the streams can be mismatched - as long as the code handles the translations properly.
 
 #### Callback do's and don'ts 
-These are things the `onAudioReady` method should NOT do:
+You should never perform an operation which could block inside `onAudioReady`. Examples of blocking operations include:
 
 - allocate memory using, for example, malloc() or new
-- any file operations such as opening, closing, reading or writing
-- any network operations such as streaming
-- use any mutexes or other synchronization primitives
+- file operations such as opening, closing, reading or writing
+- network operations such as streaming
+- use mutexes or other synchronization primitives
 - sleep
 - stop or close the stream
 - Call read() or write() on the stream which invoked it
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 2674e24..aec9430 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -70,18 +70,32 @@
     builder.setDirection(oboe::Direction::Output);
     builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
     builder.setSharingMode(oboe::SharingMode::Exclusive);
+    builder.setFormat(oboe::AudioFormat::Float);
+    builder.setChannelCount(oboe::ChannelCount::Mono);
 
 Define an `AudioStreamCallback` class to receive callbacks whenever the stream requires new data.
 
     class MyCallback : public oboe::AudioStreamCallback {
     public:
         oboe::DataCallbackResult
-        onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames){
-            generateSineWave(static_cast<float *>(audioData), numFrames);
+        onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) {
+            
+            // We requested AudioFormat::Float so we assume we got it. For production code always check what format
+	    // the stream has and cast to the appropriate type.
+            auto *outputData = static_cast<float *>(audioData);
+	    
+            // Generate random numbers centered around zero.
+            const float amplitude = 0.2f;
+            for (int i = 0; i < numFrames; ++i){
+                outputData[i] = ((float)drand48() - 0.5f) * 2 * amplitude;
+            }
+	    
             return oboe::DataCallbackResult::Continue;
         }
     };
 
+You can find examples of how to play sound using digital synthesis and pre-recorded audio in the [code samples](../samples). 
+
 Supply this callback class to the builder:
 
     MyCallback myCallback;
@@ -94,7 +108,7 @@
 
 Check the result to make sure the stream was opened successfully. Oboe has a convenience method for converting its types into human-readable strings called `oboe::convertToText`:
 
-    if (result != Result::OK){
+    if (result != oboe::Result::OK) {
         LOGE("Failed to create stream. Error: %s", oboe::convertToText(result));
     }
 
diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h
index 5898b9f..01748d1 100644
--- a/include/oboe/AudioStream.h
+++ b/include/oboe/AudioStream.h
@@ -371,6 +371,11 @@
         return nullptr;
     }
 
+    /**
+     * Launch a thread that will stop the stream.
+     */
+    void launchStopThread();
+
 protected:
 
     /**
@@ -402,7 +407,7 @@
      * @return the result of the callback: stop or continue
      *
      */
-    DataCallbackResult fireCallback(void *audioData, int numFrames);
+    DataCallbackResult fireDataCallback(void *audioData, int numFrames);
 
     /**
      * Update mFramesWritten.
@@ -415,7 +420,22 @@
     virtual void updateFramesRead() = 0;
 
     /**
-     * Number of frames which have been written into the stream.
+     * @return true if callbacks may be called
+     */
+    bool isDataCallbackEnabled() {
+        return mDataCallbackEnabled;
+    }
+
+    /**
+     * This can be set false internally to prevent callbacks
+     * after DataCallbackResult::Stop has been returned.
+     */
+    void setDataCallbackEnabled(bool enabled) {
+        mDataCallbackEnabled = enabled;
+    }
+
+    /**
+     * Number of frames which have been written into the stream
      *
      * This is signed integer to match the counters in AAudio.
      * At audio rates, the counter will overflow in about six million years.
@@ -430,9 +450,13 @@
      */
     std::atomic<int64_t> mFramesRead{};
 
+    std::mutex           mLock; // for synchronizing start/stop/close
+
 private:
     int                  mPreviousScheduler = -1;
 
+    std::atomic<bool>    mDataCallbackEnabled{};
+
 };
 
 } // namespace oboe
diff --git a/samples/MegaDrone/CMakeLists.txt b/samples/MegaDrone/CMakeLists.txt
index 2d51a6d..fd94781 100644
--- a/samples/MegaDrone/CMakeLists.txt
+++ b/samples/MegaDrone/CMakeLists.txt
@@ -18,7 +18,6 @@
 add_library( megadrone SHARED
         src/main/cpp/native-lib.cpp
         src/main/cpp/AudioEngine.cpp
-        ${OBOE_DIR}/samples/shared/Oscillator.cpp
         )
 
 target_link_libraries( megadrone log oboe )
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp b/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
index 2a19fde..94a330f 100644
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
+++ b/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
@@ -37,12 +37,17 @@
         return;
     }
 
-    if (mStream->getFormat() == AudioFormat::Float){
-        mSynth = std::make_unique<Synth<float>>(mStream->getSampleRate(), mStream->getChannelCount());
-    } else {
-        mSynth = std::make_unique<Synth<int16_t>>(mStream->getSampleRate(), mStream->getChannelCount());
+    // 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();
 
@@ -68,7 +73,16 @@
 
     if (!mIsThreadAffinitySet) setThreadAffinity();
 
-    mSynth->renderAudio(audioData, numFrames);
+    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;
 }
 
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.h b/samples/MegaDrone/src/main/cpp/AudioEngine.h
index 98aa0c9..d3a9630 100644
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.h
+++ b/samples/MegaDrone/src/main/cpp/AudioEngine.h
@@ -40,9 +40,10 @@
 
     StabilizedCallback *mStabilizedCallback = nullptr;
     AudioStream *mStream = nullptr;
-    std::unique_ptr<ISynth> mSynth;
+    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
 
     void setThreadAffinity();
 };
diff --git a/samples/MegaDrone/src/main/cpp/Synth.h b/samples/MegaDrone/src/main/cpp/Synth.h
index 36d3415..c8c5548 100644
--- a/samples/MegaDrone/src/main/cpp/Synth.h
+++ b/samples/MegaDrone/src/main/cpp/Synth.h
@@ -28,16 +28,8 @@
 constexpr float kOscDivisor = 33;
 constexpr float kOscAmplitude = 0.009;
 
-class ISynth {
-public:
-    virtual void renderAudio(void *audioData, int32_t numFrames) = 0;
-    virtual void setWaveOn(bool isEnabled) = 0;
-    virtual ~ISynth() {};
-};
 
-
-template <typename T>
-class Synth : public ISynth {
+class Synth : public IRenderableAudio {
 public:
 
     Synth(int32_t sampleRate, int32_t channelCount) {
@@ -47,34 +39,34 @@
             mOscs[i].setFrequency(kOscBaseFrequency+(static_cast<float>(i)/kOscDivisor));
             mOscs[i].setAmplitude(kOscAmplitude);
 
-            std::shared_ptr<RenderableAudio<T>> pOsc(&mOscs[i]);
+            std::shared_ptr<IRenderableAudio> pOsc(&mOscs[i]);
             mMixer.addTrack(pOsc);
         }
 
         if (channelCount == oboe::ChannelCount::Stereo){
-            mOutputStage = std::make_shared<MonoToStereo<T>>(&mMixer);
+            mOutputStage = std::make_shared<MonoToStereo>(&mMixer);
         } else {
             mOutputStage.reset(&mMixer);
         }
     }
 
     // From ISynth
-    void setWaveOn(bool isEnabled) override {
+    void setWaveOn(bool isEnabled) {
         for (auto &osc : mOscs) osc.setWaveOn(isEnabled);
     };
 
-    // From ISynth
-    void renderAudio(void *audioData, int32_t numFrames) override {
-        mOutputStage->renderAudio(static_cast<T*>(audioData), numFrames);
+    // From IRenderableAudio
+    void renderAudio(float *audioData, int32_t numFrames) override {
+        mOutputStage->renderAudio(audioData, numFrames);
     };
 
     virtual ~Synth() {}
 private:
 
     // Rendering objects
-    std::array<Oscillator<T>, kNumOscillators> mOscs;
-    Mixer<T> mMixer;
-    std::shared_ptr<RenderableAudio<T>> mOutputStage;
+    std::array<Oscillator, kNumOscillators> mOscs;
+    Mixer mMixer;
+    std::shared_ptr<IRenderableAudio> mOutputStage;
 };
 
 
diff --git a/samples/RhythmGame/README.md b/samples/RhythmGame/README.md
index 82e1a11..ceb81f8 100644
--- a/samples/RhythmGame/README.md
+++ b/samples/RhythmGame/README.md
@@ -39,9 +39,9 @@
 
 ### Audio rendering
 
-The `RenderableAudio` interface (abstract class) represents objects which can produce frames of audio data. The `SoundRecording` and `Mixer` objects both implement this interface.
+The `IRenderableAudio` interface (abstract class) represents objects which can produce frames of audio data. The `Player` and `Mixer` objects both implement this interface.
 
-Both the clap sound and backing tracks are represented by `SoundRecording` objects which are then mixed together using a `Mixer`.
+Both the clap sound and backing tracks are represented by `Player` objects which are then mixed together using a `Mixer`.
 
 ![Audio rendering](images/4-audio-rendering.png "Audio rendering")
 
diff --git a/samples/RhythmGame/src/main/cpp/Game.cpp b/samples/RhythmGame/src/main/cpp/Game.cpp
index ed82372..1adef91 100644
--- a/samples/RhythmGame/src/main/cpp/Game.cpp
+++ b/samples/RhythmGame/src/main/cpp/Game.cpp
@@ -51,6 +51,7 @@
     // simultaneously using a single audio stream.
     mMixer.addTrack(mClap);
     mMixer.addTrack(mBackingTrack);
+    mMixer.setChannelCount(kChannelCount);
 
     // Add the audio frame numbers on which the clap sound should be played to the clap event queue.
     // The backing track tempo is 120 beats per minute, which is 2 beats per second. At a sample
@@ -68,7 +69,6 @@
 
     // Create a builder
     AudioStreamBuilder builder;
-    builder.setFormat(AudioFormat::I16);
     builder.setChannelCount(kChannelCount);
     builder.setSampleRate(kSampleRateHz);
     builder.setCallback(this);
@@ -78,6 +78,12 @@
     Result result = builder.openStream(&mAudioStream);
     if (result != Result::OK){
         LOGE("Failed to open stream. Error: %s", convertToText(result));
+        return;
+    }
+
+    if (mAudioStream->getFormat() == AudioFormat::I16){
+        mConversionBuffer = std::make_unique<float[]>(mAudioStream->getBufferCapacityInFrames() *
+                kChannelCount);
     }
 
     // Reduce stream latency by setting the buffer size to a multiple of the burst size
@@ -138,6 +144,11 @@
 
 DataCallbackResult Game::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
 
+    // If we're outputting in 16-bit we need to render into a separate buffer then convert that
+    // buffer to int16s
+    bool is16Bit = (oboeStream->getFormat() == AudioFormat::I16);
+    float *outputBuffer = (is16Bit) ? mConversionBuffer.get() : static_cast<float *>(audioData);
+
     int64_t nextClapEvent;
 
     for (int i = 0; i < numFrames; ++i) {
@@ -146,10 +157,16 @@
             mClap->setPlaying(true);
             mClapEvents.pop(nextClapEvent);
         }
-        mMixer.renderAudio(static_cast<int16_t*>(audioData)+(kChannelCount*i), 1);
+        mMixer.renderAudio(outputBuffer+(kChannelCount*i), 1);
         mCurrentFrame++;
     }
 
+    if (is16Bit){
+        oboe::convertFloatToPcm16(outputBuffer,
+                                  static_cast<int16_t*>(audioData),
+                                  numFrames * kChannelCount);
+    }
+
     mLastUpdateTime = nowUptimeMillis();
 
     return DataCallbackResult::Continue;
diff --git a/samples/RhythmGame/src/main/cpp/Game.h b/samples/RhythmGame/src/main/cpp/Game.h
index 3a17658..07d396a 100644
--- a/samples/RhythmGame/src/main/cpp/Game.h
+++ b/samples/RhythmGame/src/main/cpp/Game.h
@@ -52,7 +52,8 @@
     AudioStream *mAudioStream{nullptr};
     std::shared_ptr<Player> mClap;
     std::shared_ptr<Player> mBackingTrack;
-    Mixer<int16_t> mMixer;
+    Mixer mMixer;
+    std::unique_ptr<float[]> mConversionBuffer { nullptr }; // For float->int16 conversion
 
     LockFreeQueue<int64_t, kMaxQueueItems> mClapEvents;
     std::atomic<int64_t> mCurrentFrame { 0 };
diff --git a/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp b/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
index 0d6dbc4..b9b6bbe 100644
--- a/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
+++ b/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
@@ -16,6 +16,7 @@
 
 
 #include <utils/logging.h>
+#include <oboe/Oboe.h>
 #include "AAssetDataSource.h"
 
 
@@ -33,17 +34,23 @@
 
     // Get the length of the track (we assume it is stereo 48kHz)
     off_t trackSizeInBytes = AAsset_getLength(asset);
+    auto numSamples = static_cast<int32_t>(trackSizeInBytes / sizeof(int16_t));
+    auto numFrames = static_cast<int32_t>(numSamples / channelCount);
 
-    // Load it into memory
-    auto *audioBuffer = static_cast<const int16_t*>(AAsset_getBuffer(asset));
+    // Load it into memory (we assume it is 16 bit signed integers)
+    auto *sourceBuffer = static_cast<const int16_t*>(AAsset_getBuffer(asset));
 
-    if (audioBuffer == nullptr){
+    if (sourceBuffer == nullptr){
         LOGE("Could not get buffer for track");
         return nullptr;
     }
 
-    auto numFrames = static_cast<int32_t>(trackSizeInBytes / (sizeof(int16_t) * channelCount));
-    LOGD("Opened audio data source, bytes: %ld frames: %d", trackSizeInBytes, numFrames);
+    auto outputBuffer = std::make_unique<float[]>(numSamples);
+    oboe::convertPcm16ToFloat(sourceBuffer, outputBuffer.get(), numSamples);
 
-    return new AAssetDataSource(asset, audioBuffer, numFrames, channelCount);
+    LOGD("Opened audio data source %s, bytes: %ld samples: %d frames: %d", filename, trackSizeInBytes, numSamples, numFrames);
+
+    AAsset_close(asset);
+
+    return new AAssetDataSource(std::move(outputBuffer), numFrames, channelCount);
 }
diff --git a/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h b/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
index dd64650..c9ee3d1 100644
--- a/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
+++ b/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
@@ -23,31 +23,22 @@
 class AAssetDataSource : public DataSource {
 
 public:
-
-    ~AAssetDataSource(){
-
-        // Note that this will also delete the data at mBuffer
-        AAsset_close(mAsset);
-    }
-
-    int32_t getTotalFrames() const override { return mTotalFrames; } ;
-    int32_t getChannelCount() const override { return mChannelCount; } ;
-    const int16_t* getData() const override { return mBuffer;	};
+    int32_t getTotalFrames() const override { return mTotalFrames; }
+    int32_t getChannelCount() const override { return mChannelCount; }
+    const float* getData() const override { return mBuffer.get();	}
 
     static AAssetDataSource* newFromAssetManager(AAssetManager&, const char *, const int32_t);
 
 private:
 
-    AAssetDataSource(AAsset *asset, const int16_t *data, int32_t frames,
-                     int32_t channelCount)
-            : mAsset(asset)
-            , mBuffer(data)
+    AAssetDataSource(std::unique_ptr<float[]> data, int32_t frames,
+                     const int32_t channelCount)
+            : mBuffer(std::move(data))
             , mTotalFrames(frames)
             , mChannelCount(channelCount) {
-    };
+    }
 
-    AAsset *mAsset = nullptr;
-    const int16_t* mBuffer;
+    const std::unique_ptr<float[]> mBuffer;
     const int32_t mTotalFrames;
     const int32_t mChannelCount;
 
diff --git a/samples/RhythmGame/src/main/cpp/audio/DataSource.h b/samples/RhythmGame/src/main/cpp/audio/DataSource.h
index 6694399..377e045 100644
--- a/samples/RhythmGame/src/main/cpp/audio/DataSource.h
+++ b/samples/RhythmGame/src/main/cpp/audio/DataSource.h
@@ -24,7 +24,7 @@
     virtual ~DataSource(){};
     virtual int32_t getTotalFrames() const = 0;
     virtual int32_t getChannelCount() const  = 0;
-    virtual const int16_t* getData() const = 0;
+    virtual const float* getData() const = 0;
 };
 
 
diff --git a/samples/RhythmGame/src/main/cpp/audio/Player.cpp b/samples/RhythmGame/src/main/cpp/audio/Player.cpp
index 2ecd616..13646fb 100644
--- a/samples/RhythmGame/src/main/cpp/audio/Player.cpp
+++ b/samples/RhythmGame/src/main/cpp/audio/Player.cpp
@@ -17,7 +17,7 @@
 #include "Player.h"
 #include "utils/logging.h"
 
-void Player::renderAudio(int16_t *targetData, int32_t numFrames){
+void Player::renderAudio(float *targetData, int32_t numFrames){
 
     const int32_t channelCount = mSource->getChannelCount();
 
@@ -25,7 +25,7 @@
 
         int32_t framesToRenderFromData = numFrames;
         int32_t totalSourceFrames = mSource->getTotalFrames();
-        const int16_t *data = mSource->getData();
+        const float *data = mSource->getData();
 
         // Check whether we're about to reach the end of the recording
         if (!mIsLooping && mReadFrameIndex + numFrames >= totalSourceFrames){
@@ -52,7 +52,7 @@
     }
 }
 
-void Player::renderSilence(int16_t *start, int32_t numSamples){
+void Player::renderSilence(float *start, int32_t numSamples){
     for (int i = 0; i < numSamples; ++i) {
         start[i] = 0;
     }
diff --git a/samples/RhythmGame/src/main/cpp/audio/Player.h b/samples/RhythmGame/src/main/cpp/audio/Player.h
index 1899ed0..9c7eeba 100644
--- a/samples/RhythmGame/src/main/cpp/audio/Player.h
+++ b/samples/RhythmGame/src/main/cpp/audio/Player.h
@@ -26,10 +26,10 @@
 
 #include <android/asset_manager.h>
 
-#include "shared/RenderableAudio.h"
+#include "shared/IRenderableAudio.h"
 #include "DataSource.h"
 
-class Player : public RenderableAudio<int16_t>{
+class Player : public IRenderableAudio{
 
 public:
     /**
@@ -43,7 +43,7 @@
         : mSource(source)
     {};
 
-    void renderAudio(int16_t *targetData, int32_t numFrames);
+    void renderAudio(float *targetData, int32_t numFrames);
     void resetPlayHead() { mReadFrameIndex = 0; };
     void setPlaying(bool isPlaying) { mIsPlaying = isPlaying; resetPlayHead(); };
     void setLooping(bool isLooping) { mIsLooping = isLooping; };
@@ -54,7 +54,7 @@
     std::atomic<bool> mIsLooping { false };
     std::shared_ptr<DataSource> mSource;
 
-    void renderSilence(int16_t*, int32_t);
+    void renderSilence(float*, int32_t);
 };
 
 #endif //RHYTHMGAME_SOUNDRECORDING_H
diff --git a/samples/debug-utils/trace.cpp b/samples/debug-utils/trace.cpp
index 85d531f..b2d1766 100644
--- a/samples/debug-utils/trace.cpp
+++ b/samples/debug-utils/trace.cpp
@@ -26,31 +26,36 @@
 
 static void *(*ATrace_endSection)(void);
 
+static bool *(*ATrace_isEnabled)(void);
+
 typedef void *(*fp_ATrace_beginSection)(const char *sectionName);
 
 typedef void *(*fp_ATrace_endSection)(void);
 
-bool Trace::is_tracing_supported_ = false;
+typedef bool *(*fp_ATrace_isEnabled)(void);
 
-void Trace::beginSection(const char *fmt, ...){
+bool Trace::is_enabled_ = false;
+bool Trace::has_error_been_shown_ = false;
 
-  static char buff[TRACE_MAX_SECTION_NAME_LENGTH];
-  va_list args;
-  va_start(args, fmt);
-  vsprintf(buff, fmt, args);
-  va_end(args);
+void Trace::beginSection(const char *fmt, ...) {
 
-  if (is_tracing_supported_) {
+  if (is_enabled_) {
+    static char buff[TRACE_MAX_SECTION_NAME_LENGTH];
+    va_list args;
+    va_start(args, fmt);
+    vsprintf(buff, fmt, args);
+    va_end(args);
     ATrace_beginSection(buff);
-  } else {
+  } else if (!has_error_been_shown_) {
     LOGE("Tracing is either not initialized (call Trace::initialize()) "
-             "or not supported on this device");
+         "or not supported on this device");
+    has_error_been_shown_ = true;
   }
 }
 
 void Trace::endSection() {
 
-  if (is_tracing_supported_) {
+  if (is_enabled_) {
     ATrace_endSection();
   }
 }
@@ -69,9 +74,12 @@
     ATrace_endSection =
         reinterpret_cast<fp_ATrace_endSection >(
             dlsym(lib, "ATrace_endSection"));
+    ATrace_isEnabled =
+        reinterpret_cast<fp_ATrace_isEnabled >(
+            dlsym(lib, "ATrace_isEnabled"));
 
-    if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr){
-      is_tracing_supported_ = true;
+    if (ATrace_isEnabled != nullptr && ATrace_isEnabled()) {
+      is_enabled_ = true;
     }
   }
 }
diff --git a/samples/debug-utils/trace.h b/samples/debug-utils/trace.h
index 88d5cfc..fa13920 100644
--- a/samples/debug-utils/trace.h
+++ b/samples/debug-utils/trace.h
@@ -22,10 +22,12 @@
 public:
   static void beginSection(const char *format, ...);
   static void endSection();
+  static bool isEnabled() { return is_enabled_; }
   static void initialize();
 
 private:
-  static bool is_tracing_supported_;
+  static bool is_enabled_;
+  static bool has_error_been_shown_;
 };
 
 #endif //SIMPLESYNTH_TRACE_H
diff --git a/samples/hello-oboe/CMakeLists.txt b/samples/hello-oboe/CMakeLists.txt
index b194577..5541dce 100644
--- a/samples/hello-oboe/CMakeLists.txt
+++ b/samples/hello-oboe/CMakeLists.txt
@@ -25,8 +25,8 @@
 # specify a binary directory
 add_subdirectory(${OBOE_DIR} ./oboe-bin)
 
-# Include the Oboe headers
-include_directories(${OBOE_DIR}/include)
+# Include the Oboe headers and shared sample code
+include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples)
 
 ### END OBOE INCLUDE SECTION ###
 
@@ -40,7 +40,7 @@
 file (GLOB_RECURSE APP_SOURCES
     ${APP_DIR}/jni_bridge.cpp
     ${APP_DIR}/PlayAudioEngine.cpp
-    ${APP_DIR}/SineGenerator.cpp
+    ${APP_DIR}/SoundGenerator.cpp
 )
 
 # Build the libhello-oboe library
diff --git a/samples/hello-oboe/build.gradle b/samples/hello-oboe/build.gradle
index 4c0161a..cdd975d 100644
--- a/samples/hello-oboe/build.gradle
+++ b/samples/hello-oboe/build.gradle
@@ -11,8 +11,8 @@
         versionName '1.0'
         externalNativeBuild {
             cmake {
+                cppFlags "-std=c++14"
                 arguments '-DANDROID_STL=c++_static'
-
                 // armeabi and mips are deprecated in NDK r16 so we don't want to build for them
                 abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
             }
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
index df404d2..ef84638 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
@@ -16,20 +16,22 @@
 
 #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
-constexpr int32_t kDefaultChannelCount = 2; // Stereo
+
 
 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();
-
-    mChannelCount = kDefaultChannelCount;
     createPlaybackStream();
 }
 
@@ -80,10 +82,22 @@
 
         // TODO: Implement Oboe_convertStreamToText
         // PrintAudioStreamInfo(mPlayStream);
-        prepareOscillators();
+        if (mPlayStream->getFormat() == oboe::AudioFormat::I16){
+            // create a buffer of floats which we can render our audio data into
+            int conversionBufferSamples = mPlayStream->getBufferCapacityInFrames() * channelCount;
+            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::unique_ptr<oboe::LatencyTuner>(new oboe::LatencyTuner(*mPlayStream));
+        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) {
@@ -98,18 +112,6 @@
     }
 }
 
-void PlayAudioEngine::prepareOscillators() {
-
-    double frequency = 440.0;
-    constexpr double interval = 110.0;
-    constexpr float amplitude = 0.25;
-
-    for (SineGenerator &osc : mOscillators){
-        osc.setup(frequency, mSampleRate, amplitude);
-        frequency += interval;
-    }
-}
-
 /**
  * Sets the stream parameters which are specific to playback, including device id and the
  * callback class, which must be set for low latency playback.
@@ -143,7 +145,7 @@
 }
 
 void PlayAudioEngine::setToneOn(bool isToneOn) {
-    mIsToneOn = isToneOn;
+    mSoundGenerator->setTonesOn(isToneOn);
 }
 
 /**
@@ -175,37 +177,28 @@
      */
     auto underrunCountResult = audioStream->getXRunCount();
 
-    Trace::beginSection("numFrames %d, Underruns %d, buffer size %d",
+    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 tone is on we need to use our synthesizer to render the audio data for the sine waves
-    if (audioStream->getFormat() == oboe::AudioFormat::Float) {
-        if (mIsToneOn) {
-            for (int i = 0; i < channelCount; ++i) {
-                mOscillators[i].render(static_cast<float *>(audioData) + i, channelCount, numFrames);
-            }
-        } else {
-            memset(static_cast<uint8_t *>(audioData), 0,
-                   sizeof(float) * channelCount * numFrames);
-        }
-    } else {
-        if (mIsToneOn) {
-            for (int i = 0; i < channelCount; ++i) {
-                mOscillators[i].render(static_cast<int16_t *>(audioData) + i, channelCount, numFrames);
-            }
-        } else {
-            memset(static_cast<uint8_t *>(audioData), 0,
-                   sizeof(int16_t) * channelCount * numFrames);
-        }
+    // 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);
     }
 
-    Trace::endSection();
+    if (Trace::isEnabled()) Trace::endSection();
     return oboe::DataCallbackResult::Continue;
 }
 
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
index ff4bce7..3e3b853 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
@@ -20,10 +20,12 @@
 #include <thread>
 #include <array>
 #include <oboe/Oboe.h>
-#include "SineGenerator.h"
+
+#include "shared/Mixer.h"
+
+#include "SoundGenerator.h"
 
 constexpr int32_t kBufferSizeAutomatic = 0;
-constexpr int32_t kMaximumChannelCount = 8;
 
 class PlayAudioEngine : oboe::AudioStreamCallback {
 
@@ -56,8 +58,7 @@
     oboe::AudioApi mAudioApi = oboe::AudioApi::Unspecified;
     int32_t mPlaybackDeviceId = oboe::kUnspecified;
     int32_t mSampleRate;
-    int32_t mChannelCount;
-    bool mIsToneOn = false;
+    int32_t mChannelCount = 2; // Stereo
     int32_t mFramesPerBurst;
     double mCurrentOutputLatencyMillis = 0;
     int32_t mBufferSizeSelection = kBufferSizeAutomatic;
@@ -65,9 +66,8 @@
     oboe::AudioStream *mPlayStream;
     std::unique_ptr<oboe::LatencyTuner> mLatencyTuner;
     std::mutex mRestartingLock;
-
-    // The SineGenerators generate audio data, feel free to replace with your own audio generators
-    std::array<SineGenerator, kMaximumChannelCount> mOscillators;
+    std::unique_ptr<SoundGenerator> mSoundGenerator;
+    std::unique_ptr<float[]> mConversionBuffer { nullptr };
 
     void createPlaybackStream();
 
@@ -77,8 +77,6 @@
 
     void setupPlaybackStreamParameters(oboe::AudioStreamBuilder *builder);
 
-    void prepareOscillators();
-
     oboe::Result calculateCurrentOutputLatencyMillis(oboe::AudioStream *stream, double *latencyMillis);
 
 };
diff --git a/samples/hello-oboe/src/main/cpp/SineGenerator.cpp b/samples/hello-oboe/src/main/cpp/SineGenerator.cpp
deleted file mode 100644
index 2d2f4a3..0000000
--- a/samples/hello-oboe/src/main/cpp/SineGenerator.cpp
+++ /dev/null
@@ -1,88 +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 "SineGenerator.h"
-
-constexpr int kDefaultFrameRate = 48000;
-constexpr float kDefaultAmplitude = 0.01;
-constexpr float kDefaultFrequency = 440.0;
-constexpr double kTwoPi = M_PI * 2;
-
-SineGenerator::SineGenerator() {
-    setup(kDefaultFrequency, kDefaultFrameRate, kDefaultAmplitude);
-}
-
-void SineGenerator::setup(double frequency, int32_t frameRate) {
-    mFrameRate = frameRate;
-    mPhaseIncrement = getPhaseIncremement(frequency);
-}
-
-void SineGenerator::setup(double frequency, int32_t frameRate, float amplitude) {
-    setup(frequency, frameRate);
-    mAmplitude = amplitude;
-}
-
-void SineGenerator::setSweep(double frequencyLow, double frequencyHigh, double seconds) {
-    mPhaseIncrementLow = getPhaseIncremement(frequencyLow);
-    mPhaseIncrementHigh = getPhaseIncremement(frequencyHigh);
-
-    double numFrames = seconds * mFrameRate;
-    mUpScaler = pow((frequencyHigh / frequencyLow), (1.0 / numFrames));
-    mDownScaler = 1.0 / mUpScaler;
-    mGoingUp = true;
-    mSweeping = true;
-}
-
-void SineGenerator::render(int16_t *buffer, int32_t channelStride, int32_t numFrames) {
-    int sampleIndex = 0;
-    for (int i = 0; i < numFrames; i++) {
-        buffer[sampleIndex] = static_cast<int16_t>(INT16_MAX * sinf(mPhase) * mAmplitude);
-        sampleIndex += channelStride;
-        advancePhase();
-    }
-}
-
-void SineGenerator::render(float *buffer, int32_t channelStride, int32_t numFrames) {
-    for (int i = 0, sampleIndex = 0; i < numFrames; i++) {
-        buffer[sampleIndex] = static_cast<float>(sinf(mPhase) * mAmplitude);
-        sampleIndex += channelStride;
-        advancePhase();
-    }
-}
-
-void SineGenerator::advancePhase() {
-    mPhase += mPhaseIncrement;
-    while (mPhase >= kTwoPi) {
-        mPhase -= kTwoPi;
-    }
-    if (mSweeping) {
-        if (mGoingUp) {
-            mPhaseIncrement *= mUpScaler;
-            if (mPhaseIncrement > mPhaseIncrementHigh) {
-                mGoingUp = false;
-            }
-        } else {
-            mPhaseIncrement *= mDownScaler;
-            if (mPhaseIncrement < mPhaseIncrementLow) {
-                mGoingUp = true;
-            }
-        }
-    }
-}
-
-double SineGenerator::getPhaseIncremement(double frequency) {
-    return frequency * kTwoPi / mFrameRate;
-}
\ No newline at end of file
diff --git a/samples/hello-oboe/src/main/cpp/SineGenerator.h b/samples/hello-oboe/src/main/cpp/SineGenerator.h
deleted file mode 100644
index c7a3f1a..0000000
--- a/samples/hello-oboe/src/main/cpp/SineGenerator.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 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_SINE_GENERATOR_H
-#define OBOE_HELLOOBOE_SINE_GENERATOR_H
-
-#include <math.h>
-#include <cstdint>
-
-class SineGenerator
-{
-public:
-    SineGenerator();
-    ~SineGenerator() = default;
-
-    void setup(double frequency, int32_t frameRate);
-
-    void setup(double frequency, int32_t frameRate, float amplitude);
-
-    void setSweep(double frequencyLow, double frequencyHigh, double seconds);
-
-    void render(int16_t *buffer, int32_t channelStride, int32_t numFrames);
-
-    void render(float *buffer, int32_t channelStride, int32_t numFrames);
-
-private:
-    double mAmplitude;
-    double mPhase = 0.0;
-    int32_t mFrameRate;
-    double mPhaseIncrement;
-    double mPhaseIncrementLow;
-    double mPhaseIncrementHigh;
-    double mUpScaler = 1.0;
-    double mDownScaler = 1.0;
-    bool   mGoingUp = false;
-    bool   mSweeping = false;
-
-    void advancePhase();
-
-    double getPhaseIncremement(double frequency);
-};
-
-#endif /* OBOE_HELLOOBOE_SINE_GENERATOR_H */
diff --git a/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
new file mode 100644
index 0000000..690bf37
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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 "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)){
+
+    double frequency = 440.0;
+    constexpr double interval = 110.0;
+    constexpr float amplitude = 1.0;
+
+    // Set up the oscillators
+    for (int i = 0; i < mChannelCount; ++i) {
+        mOscillators[i].setFrequency(frequency);
+        mOscillators[i].setSampleRate(mSampleRate);
+        mOscillators[i].setAmplitude(amplitude);
+        frequency += interval;
+    }
+}
+
+void SoundGenerator::renderAudio(float *audioData, int32_t numFrames) {
+
+    // Render each oscillator into its own channel
+    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];
+        }
+    }
+}
+
+void SoundGenerator::setTonesOn(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
new file mode 100644
index 0000000..7577cdc
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.h
@@ -0,0 +1,56 @@
+/*
+ * 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_SOUNDGENERATOR_H
+#define SAMPLES_SOUNDGENERATOR_H
+
+
+#include <shared/IRenderableAudio.h>
+#include <shared/Oscillator.h>
+
+/**
+ * Generates a fixed frequency tone for each channel.
+ */
+class SoundGenerator : public IRenderableAudio {
+public:
+    /**
+     * Create a new SoundGenerator object.
+     *
+     * @param sampleRate - The output sample rate.
+     * @param maxFrames - The maximum number of audio frames which will be rendered, this is used to
+     * calculate this object's internal buffer size.
+     * @param channelCount - The number of channels in the output, one tone will be created for each
+     * channel, the output will be interlaced.
+     *
+     */
+    SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t channelCount);
+    ~SoundGenerator() = default;
+
+    // Switch the tones on
+    void setTonesOn(bool isOn);
+
+    // 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;
+};
+
+
+#endif //SAMPLES_SOUNDGENERATOR_H
diff --git a/samples/shared/RenderableAudio.h b/samples/shared/IRenderableAudio.h
similarity index 83%
rename from samples/shared/RenderableAudio.h
rename to samples/shared/IRenderableAudio.h
index 756ed72..eee9872 100644
--- a/samples/shared/RenderableAudio.h
+++ b/samples/shared/IRenderableAudio.h
@@ -17,15 +17,14 @@
 #ifndef MEGADRONE_RENDERABLEAUDIO_H
 #define MEGADRONE_RENDERABLEAUDIO_H
 
-
 #include <cstdint>
+#include <string>
 
-template <typename T>
-class RenderableAudio {
+class IRenderableAudio {
 
 public:
-    virtual ~RenderableAudio() = default;
-    virtual void renderAudio(T *audioData, int32_t numFrames) = 0;
+    virtual ~IRenderableAudio() = default;
+    virtual void renderAudio(float *audioData, int32_t numFrames) = 0;
 };
 
 
diff --git a/samples/shared/Mixer.h b/samples/shared/Mixer.h
index 6969832..52ccf37 100644
--- a/samples/shared/Mixer.h
+++ b/samples/shared/Mixer.h
@@ -14,41 +14,49 @@
  * limitations under the License.
  */
 
-#ifndef RHYTHMGAME_MIXER_H
-#define RHYTHMGAME_MIXER_H
+#ifndef SHARED_MIXER_H
+#define SHARED_MIXER_H
 
-#include "RenderableAudio.h"
+#include <array>
+#include "IRenderableAudio.h"
 
 constexpr int32_t kBufferSize = 192*10;  // Temporary buffer is used for mixing
 constexpr uint8_t kMaxTracks = 100;
 
-template <typename T>
-class Mixer : public RenderableAudio<T> {
+/**
+ * A Mixer object which sums the output from multiple tracks into a single output. The number of
+ * input channels on each track must match the number of output channels (default 1=mono). This can
+ * be changed by calling `setChannelCount`.
+ */
+class Mixer : public IRenderableAudio {
 
 public:
-    void renderAudio(T *audioData, int32_t numFrames) {
+    void renderAudio(float *audioData, int32_t numFrames) {
 
         // Zero out the incoming container array
-        memset(audioData, 0, sizeof(T) * numFrames);
+        memset(audioData, 0, sizeof(float) * numFrames * mChannelCount);
 
         for (int i = 0; i < mNextFreeTrackIndex; ++i) {
             mTracks[i]->renderAudio(mixingBuffer, numFrames);
 
-            for (int j = 0; j < numFrames; ++j) {
+            for (int j = 0; j < numFrames * mChannelCount; ++j) {
                 audioData[j] += mixingBuffer[j];
             }
         }
     }
 
-    void addTrack(std::shared_ptr<RenderableAudio<T>> renderer){
+    void addTrack(std::shared_ptr<IRenderableAudio> renderer){
         mTracks[mNextFreeTrackIndex++] = renderer;
     }
 
+    void setChannelCount(int32_t channelCount){ mChannelCount = channelCount; }
+
 private:
-    T mixingBuffer[kBufferSize];
-    std::array<std::shared_ptr<RenderableAudio<T>>, kMaxTracks> mTracks;
+    float mixingBuffer[kBufferSize];
+    std::array<std::shared_ptr<IRenderableAudio>, kMaxTracks> mTracks;
     uint8_t mNextFreeTrackIndex = 0;
+    int32_t mChannelCount = 1; // Default to mono
 };
 
 
-#endif //RHYTHMGAME_MIXER_H
+#endif //SHARED_MIXER_H
diff --git a/samples/shared/MonoToStereo.h b/samples/shared/MonoToStereo.h
index 80ff9a3..97ba349 100644
--- a/samples/shared/MonoToStereo.h
+++ b/samples/shared/MonoToStereo.h
@@ -14,19 +14,21 @@
  * limitations under the License.
  */
 
-#ifndef MEGADRONE_MONOTOSTEREO_H
-#define MEGADRONE_MONOTOSTEREO_H
+#ifndef SHARED_MONOTOSTEREO_H
+#define SHARED_MONOTOSTEREO_H
 
-#include "RenderableAudio.h"
+#include "IRenderableAudio.h"
 
-template <typename T>
-class MonoToStereo : public RenderableAudio<T> {
+
+class MonoToStereo : public IRenderableAudio {
 
 public:
 
-    MonoToStereo(RenderableAudio<T> *input) : mInput(input){};
+    MonoToStereo(IRenderableAudio *input) : mInput(input){};
 
-    void renderAudio(T *audioData, int32_t numFrames) override {
+    void renderAudio(float *audioData, int32_t numFrames) override {
+
+        constexpr int kChannelCountStereo = 2;
 
         mInput->renderAudio(audioData, numFrames);
 
@@ -35,13 +37,13 @@
         // e.g. 123 => 112233
         for (int i = numFrames - 1; i >= 0; --i) {
 
-            audioData[i * oboe::ChannelCount::Stereo] = audioData[i];
-            audioData[i * oboe::ChannelCount::Stereo + 1] = audioData[i];
+            audioData[i * kChannelCountStereo] = audioData[i];
+            audioData[i * kChannelCountStereo + 1] = audioData[i];
         }
     }
 
-    RenderableAudio<T> *mInput;
+    IRenderableAudio *mInput;
 };
 
 
-#endif //MEGADRONE_MONOTOSTEREO_H
+#endif //SHARED_MONOTOSTEREO_H
diff --git a/samples/shared/Oscillator.cpp b/samples/shared/Oscillator.cpp
deleted file mode 100644
index fe590de..0000000
--- a/samples/shared/Oscillator.cpp
+++ /dev/null
@@ -1,28 +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 "Oscillator.h"
-
-// We need to calculate the amplitude value differently for each supported output format
-template<>
-void Oscillator<float>::setAmplitude(float amplitude){
-    mAmplitude = amplitude;
-};
-
-template<>
-void Oscillator<int16_t>::setAmplitude(float amplitude){
-    mAmplitude = amplitude * INT16_MAX;
-};
\ No newline at end of file
diff --git a/samples/shared/Oscillator.h b/samples/shared/Oscillator.h
index 33b2772..4847274 100644
--- a/samples/shared/Oscillator.h
+++ b/samples/shared/Oscillator.h
@@ -15,31 +15,32 @@
  */
 
 
-#ifndef MEGADRONE_OSCILLATOR_H
-#define MEGADRONE_OSCILLATOR_H
+#ifndef SHARED_OSCILLATOR_H
+#define SHARED_OSCILLATOR_H
 
 
 #include <cstdint>
 #include <atomic>
 #include <math.h>
 #include <memory>
-#include "RenderableAudio.h"
+#include "IRenderableAudio.h"
 
 constexpr double kDefaultFrequency = 440.0;
 constexpr int32_t kDefaultSampleRate = 48000;
 constexpr double kPi = M_PI;
 constexpr double kTwoPi = kPi * 2;
 
-template <typename T>
-class Oscillator : public RenderableAudio<T> {
+class Oscillator : public IRenderableAudio {
 
 public:
 
-    void setWaveOn(bool isWaveOn){
+    ~Oscillator() = default;
+
+    void setWaveOn(bool isWaveOn) {
         mIsWaveOn.store(isWaveOn);
     };
 
-    void setSampleRate(int32_t sampleRate){
+    void setSampleRate(int32_t sampleRate) {
         mSampleRate = sampleRate;
         updatePhaseIncrement();
     };
@@ -49,16 +50,18 @@
         updatePhaseIncrement();
     };
 
-    void setAmplitude(float amplitude);
+    inline void setAmplitude(float amplitude) {
+        mAmplitude = amplitude;
+    };
 
-    // From RenderableAudio<T>
-    void renderAudio(T *audioData, int32_t numFrames) {
+    // From IRenderableAudio
+    void renderAudio(float *audioData, int32_t numFrames) override {
 
         if (mIsWaveOn){
             for (int i = 0; i < numFrames; ++i) {
 
                 // Sine wave (sinf)
-                //audioData[i*kChannelCount] = sinf(mPhase) * mAmplitude;
+                //data[i*kChannelCount] = sinf(mPhase) * mAmplitude;
 
                 // Square wave
                 if (mPhase <= kPi){
@@ -71,14 +74,14 @@
                 if (mPhase > kTwoPi) mPhase -= kTwoPi;
             }
         } else {
-            memset(audioData, 0, sizeof(T) * numFrames);
+            memset(audioData, 0, sizeof(float) * numFrames);
         }
     };
 
 private:
     std::atomic<bool> mIsWaveOn { false };
     float mPhase = 0.0;
-    std::atomic<T> mAmplitude { 0 };
+    std::atomic<float> mAmplitude { 0 };
     std::atomic<double> mPhaseIncrement { 0.0 };
     double mFrequency = kDefaultFrequency;
     int32_t mSampleRate = kDefaultSampleRate;
@@ -88,4 +91,4 @@
     };
 };
 
-#endif //MEGADRONE_OSCILLATOR_H
+#endif //SHARED_OSCILLATOR_H
diff --git a/src/aaudio/AAudioLoader.cpp b/src/aaudio/AAudioLoader.cpp
index 9a863c2..4c33453 100644
--- a/src/aaudio/AAudioLoader.cpp
+++ b/src/aaudio/AAudioLoader.cpp
@@ -38,7 +38,10 @@
     if (mLibHandle != nullptr) {
         return 0;
     }
-    mLibHandle = dlopen(LIB_AAUDIO_NAME, 0);
+
+    // Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause.
+    // Also resolving all the links now will prevent a run-time penalty later.
+    mLibHandle = dlopen(LIB_AAUDIO_NAME, RTLD_NOW);
     if (mLibHandle == nullptr) {
         LOGI("AAudioLoader::open() could not find " LIB_AAUDIO_NAME);
         return -1; // TODO review return code
@@ -47,17 +50,13 @@
     }
 
     // Load all the function pointers.
-    createStreamBuilder = reinterpret_cast<aaudio_result_t (*)(AAudioStreamBuilder **builder)>
-            (dlsym(mLibHandle, "AAudio_createStreamBuilder"));
-
-    builder_openStream = reinterpret_cast<aaudio_result_t (*)(AAudioStreamBuilder *builder,
-                                              AAudioStream **stream)>
-            (dlsym(mLibHandle, "AAudioStreamBuilder_openStream"));
+    createStreamBuilder        = load_I_PPB("AAudio_createStreamBuilder");
+    builder_openStream         = load_I_PBPPS("AAudioStreamBuilder_openStream");
 
     builder_setChannelCount    = load_V_PBI("AAudioStreamBuilder_setChannelCount");
     if (builder_setChannelCount == nullptr) {
-        // Use old alias if needed.
-        builder_setChannelCount    = load_V_PBI("AAudioStreamBuilder_setSamplesPerFrame");
+        // Use old deprecated alias if needed.
+        builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setSamplesPerFrame");
     }
 
     builder_setBufferCapacityInFrames = load_V_PBI("AAudioStreamBuilder_setBufferCapacityInFrames");
@@ -76,78 +75,54 @@
 
     builder_delete             = load_I_PB("AAudioStreamBuilder_delete");
 
-    stream_getFormat = reinterpret_cast<aaudio_format_t (*)(AAudioStream *stream)>
-            (dlsym(mLibHandle, "AAudioStream_getFormat"));
 
-    builder_setDataCallback = reinterpret_cast<void (*)(AAudioStreamBuilder *builder,
-                                        AAudioStream_dataCallback callback,
-                                        void *userData)>
-            (dlsym(mLibHandle, "AAudioStreamBuilder_setDataCallback"));
+    builder_setDataCallback    = load_V_PBPDPV("AAudioStreamBuilder_setDataCallback");
+    builder_setErrorCallback   = load_V_PBPEPV("AAudioStreamBuilder_setErrorCallback");
 
-    builder_setErrorCallback = reinterpret_cast<void (*)(AAudioStreamBuilder *builder,
-                                        AAudioStream_errorCallback callback,
-                                        void *userData)>
-            (dlsym(mLibHandle, "AAudioStreamBuilder_setErrorCallback"));
+    stream_read                = load_I_PSPVIL("AAudioStream_read");
 
-    stream_read = reinterpret_cast<aaudio_result_t (*)(AAudioStream *stream,
-                                       void *buffer,
-                                       int32_t numFrames,
-                                       int64_t timeoutNanoseconds)>
-            (dlsym(mLibHandle, "AAudioStream_read"));
+    stream_write               = load_I_PSCPVIL("AAudioStream_write");
 
-    stream_write = reinterpret_cast<aaudio_result_t (*)(AAudioStream *stream,
-                                        const void *buffer,
-                                        int32_t numFrames,
-                                        int64_t timeoutNanoseconds)>
-            (dlsym(mLibHandle, "AAudioStream_write"));
+    stream_waitForStateChange  = load_I_PSTPTL("AAudioStream_waitForStateChange");
 
+    stream_getTimestamp        = load_I_PSKPLPL("AAudioStream_getTimestamp");
 
-    stream_waitForStateChange = reinterpret_cast<aaudio_result_t (*)(AAudioStream *stream,
-                                                 aaudio_stream_state_t inputState,
-                                                 aaudio_stream_state_t *nextState,
-                                                 int64_t timeoutNanoseconds)>
-            (dlsym(mLibHandle, "AAudioStream_waitForStateChange"));
+    stream_isMMapUsed          = load_B_PS("AAudioStream_isMMapUsed");
 
-
-    stream_getTimestamp = reinterpret_cast<aaudio_result_t (*)(AAudioStream *stream,
-                                           clockid_t clockid,
-                                           int64_t *framePosition,
-                                           int64_t *timeNanoseconds)>
-            (dlsym(mLibHandle, "AAudioStream_getTimestamp"));
-
-    stream_getChannelCount    = load_I_PS("AAudioStream_getChannelCount");
+    stream_getChannelCount     = load_I_PS("AAudioStream_getChannelCount");
     if (stream_getChannelCount == nullptr) {
         // Use old alias if needed.
         stream_getChannelCount    = load_I_PS("AAudioStream_getSamplesPerFrame");
     }
 
-    stream_close              = load_I_PS("AAudioStream_close");
+    stream_close               = load_I_PS("AAudioStream_close");
 
-    stream_getBufferSize      = load_I_PS("AAudioStream_getBufferSizeInFrames");
-    stream_getDeviceId        = load_I_PS("AAudioStream_getDeviceId");
-    stream_getBufferCapacity  = load_I_PS("AAudioStream_getBufferCapacityInFrames");
-    stream_getFramesPerBurst  = load_I_PS("AAudioStream_getFramesPerBurst");
-    stream_getFramesRead      = load_L_PS("AAudioStream_getFramesRead");
-    stream_getFramesWritten   = load_L_PS("AAudioStream_getFramesWritten");
-    stream_getPerformanceMode = load_I_PS("AAudioStream_getPerformanceMode");
-    stream_getSampleRate      = load_I_PS("AAudioStream_getSampleRate");
-    stream_getSharingMode     = load_I_PS("AAudioStream_getSharingMode");
-    stream_getState           = load_I_PS("AAudioStream_getState");
-    stream_getXRunCount       = load_I_PS("AAudioStream_getXRunCount");
+    stream_getBufferSize       = load_I_PS("AAudioStream_getBufferSizeInFrames");
+    stream_getDeviceId         = load_I_PS("AAudioStream_getDeviceId");
+    stream_getBufferCapacity   = load_I_PS("AAudioStream_getBufferCapacityInFrames");
+    stream_getFormat           = load_F_PS("AAudioStream_getFormat");
+    stream_getFramesPerBurst   = load_I_PS("AAudioStream_getFramesPerBurst");
+    stream_getFramesRead       = load_L_PS("AAudioStream_getFramesRead");
+    stream_getFramesWritten    = load_L_PS("AAudioStream_getFramesWritten");
+    stream_getPerformanceMode  = load_I_PS("AAudioStream_getPerformanceMode");
+    stream_getSampleRate       = load_I_PS("AAudioStream_getSampleRate");
+    stream_getSharingMode      = load_I_PS("AAudioStream_getSharingMode");
+    stream_getState            = load_I_PS("AAudioStream_getState");
+    stream_getXRunCount        = load_I_PS("AAudioStream_getXRunCount");
 
-    stream_requestStart       = load_I_PS("AAudioStream_requestStart");
-    stream_requestPause       = load_I_PS("AAudioStream_requestPause");
-    stream_requestFlush       = load_I_PS("AAudioStream_requestFlush");
-    stream_requestStop        = load_I_PS("AAudioStream_requestStop");
+    stream_requestStart        = load_I_PS("AAudioStream_requestStart");
+    stream_requestPause        = load_I_PS("AAudioStream_requestPause");
+    stream_requestFlush        = load_I_PS("AAudioStream_requestFlush");
+    stream_requestStop         = load_I_PS("AAudioStream_requestStop");
 
-    stream_setBufferSize      = load_I_PSI("AAudioStream_setBufferSizeInFrames");
+    stream_setBufferSize       = load_I_PSI("AAudioStream_setBufferSizeInFrames");
 
-    convertResultToText       = load_PC_I("AAudio_convertResultToText");
+    convertResultToText        = load_CPH_I("AAudio_convertResultToText");
 
-    stream_getUsage           = load_I_PS("AAudioStream_getUsage");
-    stream_getContentType     = load_I_PS("AAudioStream_getContentType");
-    stream_getInputPreset     = load_I_PS("AAudioStream_getInputPreset");
-    stream_getSessionId       = load_I_PS("AAudioStream_getSessionId");
+    stream_getUsage            = load_I_PS("AAudioStream_getUsage");
+    stream_getContentType      = load_I_PS("AAudioStream_getContentType");
+    stream_getInputPreset      = load_I_PS("AAudioStream_getInputPreset");
+    stream_getSessionId        = load_I_PS("AAudioStream_getSessionId");
 
     return 0;
 }
@@ -158,10 +133,16 @@
     }
 }
 
-AAudioLoader::signature_PC_I AAudioLoader::load_PC_I(const char *functionName) {
+AAudioLoader::signature_I_PPB AAudioLoader::load_I_PPB(const char *functionName) {
     void *proc = dlsym(mLibHandle, functionName);
     AAudioLoader_check(proc, functionName);
-    return reinterpret_cast<signature_PC_I>(proc);
+    return reinterpret_cast<signature_I_PPB>(proc);
+}
+
+AAudioLoader::signature_CPH_I AAudioLoader::load_CPH_I(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_CPH_I>(proc);
 }
 
 AAudioLoader::signature_V_PBI AAudioLoader::load_V_PBI(const char *functionName) {
@@ -170,6 +151,18 @@
     return reinterpret_cast<signature_V_PBI>(proc);
 }
 
+AAudioLoader::signature_V_PBPDPV AAudioLoader::load_V_PBPDPV(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_V_PBPDPV>(proc);
+}
+
+AAudioLoader::signature_V_PBPEPV AAudioLoader::load_V_PBPEPV(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_V_PBPEPV>(proc);
+}
+
 AAudioLoader::signature_I_PSI AAudioLoader::load_I_PSI(const char *functionName) {
     void *proc = dlsym(mLibHandle, functionName);
     AAudioLoader_check(proc, functionName);
@@ -188,10 +181,52 @@
     return reinterpret_cast<signature_L_PS>(proc);
 }
 
+AAudioLoader::signature_F_PS AAudioLoader::load_F_PS(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_F_PS>(proc);
+}
+
+AAudioLoader::signature_B_PS AAudioLoader::load_B_PS(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_B_PS>(proc);
+}
+
 AAudioLoader::signature_I_PB AAudioLoader::load_I_PB(const char *functionName) {
     void *proc = dlsym(mLibHandle, functionName);
     AAudioLoader_check(proc, functionName);
     return reinterpret_cast<signature_I_PB>(proc);
 }
 
-} // namespace oboe
\ No newline at end of file
+AAudioLoader::signature_I_PBPPS AAudioLoader::load_I_PBPPS(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_I_PBPPS>(proc);
+}
+
+AAudioLoader::signature_I_PSCPVIL AAudioLoader::load_I_PSCPVIL(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_I_PSCPVIL>(proc);
+}
+
+AAudioLoader::signature_I_PSPVIL AAudioLoader::load_I_PSPVIL(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_I_PSPVIL>(proc);
+}
+
+AAudioLoader::signature_I_PSTPTL AAudioLoader::load_I_PSTPTL(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_I_PSTPTL>(proc);
+}
+
+AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *functionName) {
+    void *proc = dlsym(mLibHandle, functionName);
+    AAudioLoader_check(proc, functionName);
+    return reinterpret_cast<signature_I_PSKPLPL>(proc);
+}
+
+} // namespace oboe
diff --git a/src/aaudio/AAudioLoader.h b/src/aaudio/AAudioLoader.h
index e590b1c..4c41157 100644
--- a/src/aaudio/AAudioLoader.h
+++ b/src/aaudio/AAudioLoader.h
@@ -32,11 +32,22 @@
 class AAudioLoader {
   public:
     // Use signatures for common functions.
-    typedef const char * (*signature_PC_I)(int32_t);
-    typedef int32_t (*signature_I_I)(int32_t);
-    typedef int32_t (*signature_I_II)(int32_t, int32_t);
-    typedef int32_t (*signature_I_IPI)(int32_t, int32_t *);
-    typedef int32_t (*signature_I_IIPI)(int32_t, int32_t, int32_t *);
+    // Key to letter abbreviations.
+    // S = Stream
+    // B = Builder
+    // I = int32_t
+    // L = int64_t
+    // T = sTate
+    // K = clocKid_t
+    // P = Pointer to following data type
+    // C = Const prefix
+    // H = cHar
+    typedef int32_t  (*signature_I_PPB)(AAudioStreamBuilder **builder);
+
+    typedef const char * (*signature_CPH_I)(int32_t);
+
+    typedef int32_t (*signature_I_PBPPS)(AAudioStreamBuilder *,
+                                      AAudioStream **stream);  // AAudioStreamBuilder_open()
 
     typedef int32_t (*signature_I_PB)(AAudioStreamBuilder *);  // AAudioStreamBuilder_delete()
     // AAudioStreamBuilder_setSampleRate()
@@ -47,6 +58,28 @@
     // AAudioStream_setBufferSizeInFrames()
     typedef int32_t (*signature_I_PSI)(AAudioStream *, int32_t);
 
+    typedef void    (*signature_V_PBPDPV)(AAudioStreamBuilder *,
+                                          AAudioStream_dataCallback,
+                                          void *);
+
+    typedef void    (*signature_V_PBPEPV)(AAudioStreamBuilder *,
+                                          AAudioStream_errorCallback,
+                                          void *);
+
+    typedef aaudio_format_t (*signature_F_PS)(AAudioStream *stream);
+
+    typedef int32_t (*signature_I_PSPVIL)(AAudioStream *, void *, int32_t, int64_t);
+    typedef int32_t (*signature_I_PSCPVIL)(AAudioStream *, const void *, int32_t, int64_t);
+
+    typedef int32_t (*signature_I_PSTPTL)(AAudioStream *,
+                                          aaudio_stream_state_t,
+                                          aaudio_stream_state_t *,
+                                          int64_t);
+
+    typedef int32_t (*signature_I_PSKPLPL)(AAudioStream *, clockid_t, int64_t *, int64_t *);
+
+    typedef bool    (*signature_B_PS)(AAudioStream *);
+
     static AAudioLoader* getInstance(); // singleton
 
     /**
@@ -61,10 +94,9 @@
     int open();
 
     // Function pointers into the AAudio shared library.
-    aaudio_result_t (*createStreamBuilder)(AAudioStreamBuilder **builder) = nullptr;
+    signature_I_PPB   createStreamBuilder = nullptr;
 
-    aaudio_result_t  (*builder_openStream)(AAudioStreamBuilder *builder,
-                                           AAudioStream **stream) = nullptr;
+    signature_I_PBPPS builder_openStream = nullptr;
 
     signature_V_PBI builder_setBufferCapacityInFrames = nullptr;
     signature_V_PBI builder_setChannelCount = nullptr;
@@ -81,37 +113,21 @@
     signature_V_PBI builder_setInputPreset = nullptr;
     signature_V_PBI builder_setSessionId = nullptr;
 
-    void (*builder_setDataCallback)(AAudioStreamBuilder *builder,
-                                    AAudioStream_dataCallback callback,
-                                    void *userData) = nullptr;
+    signature_V_PBPDPV  builder_setDataCallback = nullptr;
+    signature_V_PBPEPV  builder_setErrorCallback = nullptr;
 
-    void (*builder_setErrorCallback)(AAudioStreamBuilder *builder,
-                                    AAudioStream_errorCallback callback,
-                                    void *userData) = nullptr;
+    signature_I_PB      builder_delete = nullptr;
 
-    signature_I_PB  builder_delete = nullptr;
+    signature_F_PS      stream_getFormat = nullptr;
 
-    aaudio_format_t (*stream_getFormat)(AAudioStream *stream) = nullptr;
+    signature_I_PSPVIL  stream_read = nullptr;
+    signature_I_PSCPVIL stream_write = nullptr;
 
-    aaudio_result_t (*stream_read)(AAudioStream* stream,
-                                   void *buffer,
-                                   int32_t numFrames,
-                                   int64_t timeoutNanoseconds) = nullptr;
+    signature_I_PSTPTL  stream_waitForStateChange = nullptr;
 
-    aaudio_result_t (*stream_write)(AAudioStream *stream,
-                                   const void *buffer,
-                                   int32_t numFrames,
-                                   int64_t timeoutNanoseconds) = nullptr;
+    signature_I_PSKPLPL stream_getTimestamp = nullptr;
 
-    aaudio_result_t (*stream_waitForStateChange)(AAudioStream *stream,
-                                                 aaudio_stream_state_t inputState,
-                                                 aaudio_stream_state_t *nextState,
-                                                 int64_t timeoutNanoseconds) = nullptr;
-
-    aaudio_result_t (*stream_getTimestamp)(AAudioStream *stream,
-                                          clockid_t clockid,
-                                          int64_t *framePosition,
-                                          int64_t *timeNanoseconds) = nullptr;
+    signature_B_PS      stream_isMMapUsed = nullptr;
 
     signature_I_PS   stream_close = nullptr;
 
@@ -136,7 +152,7 @@
     signature_L_PS   stream_getFramesRead = nullptr;
     signature_L_PS   stream_getFramesWritten = nullptr;
 
-    signature_PC_I   convertResultToText = nullptr;
+    signature_CPH_I  convertResultToText = nullptr;
 
     signature_I_PS   stream_getUsage = nullptr;
     signature_I_PS   stream_getContentType = nullptr;
@@ -148,13 +164,22 @@
     ~AAudioLoader();
 
     // Load function pointers for specific signatures.
-    signature_PC_I   load_PC_I(const char *name);
-
-    signature_V_PBI  load_V_PBI(const char *name);
-    signature_I_PB   load_I_PB(const char *name);
-    signature_I_PS   load_I_PS(const char *name);
-    signature_L_PS   load_L_PS(const char *name);
-    signature_I_PSI  load_I_PSI(const char *name);
+    signature_I_PPB     load_I_PPB(const char *name);
+    signature_CPH_I     load_CPH_I(const char *name);
+    signature_V_PBI     load_V_PBI(const char *name);
+    signature_V_PBPDPV  load_V_PBPDPV(const char *name);
+    signature_V_PBPEPV  load_V_PBPEPV(const char *name);
+    signature_I_PB      load_I_PB(const char *name);
+    signature_I_PBPPS   load_I_PBPPS(const char *name);
+    signature_I_PS      load_I_PS(const char *name);
+    signature_L_PS      load_L_PS(const char *name);
+    signature_F_PS      load_F_PS(const char *name);
+    signature_B_PS      load_B_PS(const char *name);
+    signature_I_PSI     load_I_PSI(const char *name);
+    signature_I_PSPVIL  load_I_PSPVIL(const char *name);
+    signature_I_PSCPVIL load_I_PSCPVIL(const char *name);
+    signature_I_PSTPTL  load_I_PSTPTL(const char *name);
+    signature_I_PSKPLPL load_I_PSKPLPL(const char *name);
 
     void *mLibHandle = nullptr;
 };
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index a8a5086..b8c4394 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -38,9 +38,10 @@
         int32_t numFrames) {
 
     AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
-    if (oboeStream != NULL) {
+    if (oboeStream != nullptr) {
         return static_cast<aaudio_data_callback_result_t>(
                 oboeStream->callOnAudioReady(stream, audioData, numFrames));
+
     } else {
         return static_cast<aaudio_data_callback_result_t>(DataCallbackResult::Stop);
     }
@@ -49,7 +50,7 @@
 static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream,
                                           AAudioStream *stream,
                                           Result error) {
-    if (oboeStream != NULL) {
+    if (oboeStream != nullptr) {
         oboeStream->onErrorInThread(stream, error);
     }
 }
@@ -157,8 +158,10 @@
     if (mStreamCallback != nullptr) {
         mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this);
         mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerCallback());
+        // If the data callback is not being used then the write method will return an error
+        // and the app can stop and close the stream.
+        mLibLoader->builder_setErrorCallback(aaudioBuilder, oboe_aaudio_error_callback_proc, this);
     }
-    mLibLoader->builder_setErrorCallback(aaudioBuilder, oboe_aaudio_error_callback_proc, this);
 
     // ============= OPEN THE STREAM ================
     {
@@ -231,10 +234,28 @@
 DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream *stream,
                                                                  void *audioData,
                                                                  int32_t numFrames) {
-    return mStreamCallback->onAudioReady(
-            this,
-            audioData,
-            numFrames);
+    DataCallbackResult result = fireDataCallback(audioData, numFrames);
+    if (result == DataCallbackResult::Continue) {
+        return result;
+    } else {
+        if (result == DataCallbackResult::Stop) {
+            LOGE("Oboe callback returned DataCallbackResult::Stop");
+        } else {
+            LOGE("Oboe callback returned unexpected value = %d", result);
+        }
+
+        if (getSdkVersion() <= __ANDROID_API_P__) {
+            launchStopThread();
+            if (isMMapUsed()) {
+                return DataCallbackResult::Stop;
+            } else {
+                // Legacy stream <= API_P cannot be restarted after returning Stop.
+                return DataCallbackResult::Continue;
+            }
+        } else {
+            return DataCallbackResult::Stop; // OK >= API_Q
+        }
+    }
 }
 
 void AudioStreamAAudio::onErrorInThread(AAudioStream *stream, Result error) {
@@ -263,6 +284,9 @@
                 return Result::OK;
             }
         }
+        if (mStreamCallback != nullptr) { // Was a callback requested?
+            setDataCallbackEnabled(true);
+        }
         return static_cast<Result>(mLibLoader->stream_requestStart(stream));
     } else {
         return Result::ErrorClosed;
@@ -511,4 +535,13 @@
     return ResultWithValue<double>(latencyMillis);
 }
 
+bool AudioStreamAAudio::isMMapUsed() {
+    AAudioStream *stream = mAAudioStream.load();
+    if (stream != nullptr) {
+        return mLibLoader->stream_isMMapUsed(stream);
+    } else {
+        return false;
+    }
+}
+
 } // namespace oboe
diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h
index ccabe05..8316d62 100644
--- a/src/aaudio/AudioStreamAAudio.h
+++ b/src/aaudio/AudioStreamAAudio.h
@@ -109,9 +109,10 @@
 
 private:
 
+    bool                 isMMapUsed();
+
     std::atomic<bool>    mCallbackThreadEnabled;
 
-    std::mutex           mLock; // for synchronizing start/stop/close
     std::atomic<AAudioStream *> mAAudioStream{nullptr};
 
     static AAudioLoader *mLibLoader;
diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp
index 966bc36..5dfafb1 100644
--- a/src/common/AudioStream.cpp
+++ b/src/common/AudioStream.cpp
@@ -16,6 +16,7 @@
 
 #include <sys/types.h>
 #include <pthread.h>
+#include <thread>
 #include <oboe/AudioStream.h>
 #include "OboeDebug.h"
 #include <oboe/Utilities.h>
@@ -41,10 +42,15 @@
     return Result::OK;
 }
 
-DataCallbackResult AudioStream::fireCallback(void *audioData, int32_t numFrames) {
+DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFrames) {
+    if (!isDataCallbackEnabled()) {
+        LOGW("AudioStream::%s() called with data callback disabled!", __func__);
+        return DataCallbackResult::Stop; // We should not be getting called any more.
+    }
+
     int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread
     if (scheduler != mPreviousScheduler) {
-        LOGD("AudioStream::fireCallback() scheduler = %s",
+        LOGD("AudioStream::%s() scheduler = %s", __func__,
              ((scheduler == SCHED_FIFO) ? "SCHED_FIFO" :
              ((scheduler == SCHED_OTHER) ? "SCHED_OTHER" :
              ((scheduler == SCHED_RR) ? "SCHED_RR" : "UNKNOWN")))
@@ -58,6 +64,9 @@
     } else {
         result = mStreamCallback->onAudioReady(this, audioData, numFrames);
     }
+    // On Oreo, we might get called after returning stop.
+    // So block there here.
+    setDataCallbackEnabled(result == DataCallbackResult::Continue);
 
     return result;
 }
@@ -131,4 +140,19 @@
     return mFramesWritten;
 }
 
+static void oboe_stop_thread_proc(AudioStream *oboeStream) {
+    LOGD("%s() called ----)))))", __func__);
+    if (oboeStream != nullptr) {
+        oboeStream->requestStop();
+    }
+    LOGD("%s() returning (((((----", __func__);
+}
+
+void AudioStream::launchStopThread() {
+    // Stop this stream on a separate thread
+    // std::thread t(requestStop);
+    std::thread t(oboe_stop_thread_proc, this);
+    t.detach();
+}
+
 } // namespace oboe
diff --git a/src/common/AudioStreamBuilder.cpp b/src/common/AudioStreamBuilder.cpp
index 4b909af..66e3b7d 100644
--- a/src/common/AudioStreamBuilder.cpp
+++ b/src/common/AudioStreamBuilder.cpp
@@ -39,6 +39,8 @@
 int32_t DefaultStreamValues::FramesPerBurst = 192; // 4 msec at 48000 Hz
 int32_t DefaultStreamValues::ChannelCount = 2; // Stereo
 
+constexpr int kBufferSizeInBurstsForLowLatencyStreams = 2;
+
 #ifndef OBOE_ENABLE_AAUDIO
 // Set OBOE_ENABLE_AAUDIO to 0 if you want to disable the AAudio API.
 // This might be useful if you want to force all the unit tests to use OpenSL ES.
@@ -88,6 +90,18 @@
     }
     Result result = streamP->open(); // TODO review API
     if (result == Result::OK) {
+
+        // Use a reasonable default buffer size for low latency streams.
+        if (streamP->getPerformanceMode() == PerformanceMode::LowLatency){
+            int32_t optimalBufferSize = streamP->getFramesPerBurst() *
+                                        kBufferSizeInBurstsForLowLatencyStreams;
+            auto setBufferResult = streamP->setBufferSizeInFrames(optimalBufferSize);
+            if (!setBufferResult){
+                LOGW("Failed to set buffer size to %d. Error was %s",
+                     optimalBufferSize,
+                     convertToText(setBufferResult.error()));
+            }
+        }
         *streamPP = streamP;
     } else {
         delete streamP;
diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp
index 1606da3..30a5128 100644
--- a/src/opensles/AudioInputStreamOpenSLES.cpp
+++ b/src/opensles/AudioInputStreamOpenSLES.cpp
@@ -206,44 +206,63 @@
 }
 
 Result AudioInputStreamOpenSLES::close() {
-
+    LOGD("AudioInputStreamOpenSLES::%s()", __func__);
+    mLock.lock();
+    Result result = Result::OK;
     if (mState == StreamState::Closed){
-        return Result::ErrorClosed;
+        result = Result::ErrorClosed;
     } else {
+        mLock.unlock(); // avoid recursive lock
         requestStop();
+        mLock.lock();
+        // invalidate any interfaces
         mRecordInterface = NULL;
-        return AudioStreamOpenSLES::close();
+        result = AudioStreamOpenSLES::close();
     }
+    mLock.unlock(); // avoid recursive lock
+    return result;
 }
 
-Result AudioInputStreamOpenSLES::setRecordState(SLuint32 newState) {
-
-    LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState);
+Result AudioInputStreamOpenSLES::setRecordState_l(SLuint32 newState) {
+    LOGD("AudioInputStreamOpenSLES::%s(%d)", __func__, newState);
     Result result = Result::OK;
 
     if (mRecordInterface == nullptr) {
-        LOGE("AudioInputStreamOpenSLES::SetRecordState() mRecordInterface is null");
+        LOGE("AudioInputStreamOpenSLES::%s() mRecordInterface is null", __func__);
         return Result::ErrorInvalidState;
     }
     SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
     if (SL_RESULT_SUCCESS != slResult) {
-        LOGE("AudioInputStreamOpenSLES::SetRecordState() returned %s", getSLErrStr(slResult));
+        LOGE("AudioInputStreamOpenSLES::%s() returned %s", __func__, getSLErrStr(slResult));
         result = Result::ErrorInternal; // TODO review
     }
     return result;
 }
 
 Result AudioInputStreamOpenSLES::requestStart() {
-
-    LOGD("AudioInputStreamOpenSLES::requestStart()");
+    LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
+    std::lock_guard<std::mutex> lock(mLock);
     StreamState initialState = getState();
-    if (initialState == StreamState::Closed) return Result::ErrorClosed;
+    switch (initialState) {
+        case StreamState::Starting:
+        case StreamState::Started:
+            return Result::OK;
+        case StreamState::Closed:
+            return Result::ErrorClosed;
+        default:
+            break;
+    }
+
+    // We use a callback if the user requests one
+    // OR if we have an internal callback to fill the blocking IO buffer.
+    setDataCallbackEnabled(true);
 
     setState(StreamState::Starting);
-    Result result = setRecordState(SL_RECORDSTATE_RECORDING);
+    Result result = setRecordState_l(SL_RECORDSTATE_RECORDING);
     if (result == Result::OK) {
-        // Enqueue the first buffer so that we have data ready in the callback.
         setState(StreamState::Started);
+        // Enqueue the first buffer to start the streaming.
+        // This does not call the callback function.
         enqueueCallbackBuffer(mSimpleBufferQueueInterface);
     } else {
         setState(initialState);
@@ -253,26 +272,35 @@
 
 
 Result AudioInputStreamOpenSLES::requestPause() {
-    LOGW("AudioInputStreamOpenSLES::requestPause() is intentionally not implemented for input "
-         "streams");
+    LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
+         "streams", __func__);
     return Result::ErrorUnimplemented; // Matches AAudio behavior.
 }
 
 Result AudioInputStreamOpenSLES::requestFlush() {
-    LOGW("AudioInputStreamOpenSLES::requestFlush() is intentionally not implemented for input "
-         "streams");
+    LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
+         "streams", __func__);
     return Result::ErrorUnimplemented; // Matches AAudio behavior.
 }
 
 Result AudioInputStreamOpenSLES::requestStop() {
+    LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
 
-    LOGD("AudioInputStreamOpenSLES::requestStop()");
+    std::lock_guard<std::mutex> lock(mLock);
     StreamState initialState = getState();
-    if (initialState == StreamState::Closed) return Result::ErrorClosed;
+    switch (initialState) {
+        case StreamState::Stopping:
+        case StreamState::Stopped:
+            return Result::OK;
+        case StreamState::Closed:
+            return Result::ErrorClosed;
+        default:
+            break;
+    }
 
     setState(StreamState::Stopping);
 
-    Result result = setRecordState(SL_RECORDSTATE_STOPPED);
+    Result result = setRecordState_l(SL_RECORDSTATE_STOPPED);
     if (result == Result::OK) {
         mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
         setState(StreamState::Stopped);
diff --git a/src/opensles/AudioInputStreamOpenSLES.h b/src/opensles/AudioInputStreamOpenSLES.h
index bf88ad9..2f81a8b 100644
--- a/src/opensles/AudioInputStreamOpenSLES.h
+++ b/src/opensles/AudioInputStreamOpenSLES.h
@@ -55,7 +55,7 @@
 
     SLuint32 channelCountToChannelMask(int chanCount);
 
-    Result setRecordState(SLuint32 newState);
+    Result setRecordState_l(SLuint32 newState);
 
     SLRecordItf mRecordInterface = nullptr;
 };
diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp
index adcf2d4..4bd73fd 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.cpp
+++ b/src/opensles/AudioOutputStreamOpenSLES.cpp
@@ -234,65 +234,98 @@
 
 Result AudioOutputStreamOpenSLES::onAfterDestroy() {
     OutputMixerOpenSL::getInstance().close();
-
     return Result::OK;
 }
 
 Result AudioOutputStreamOpenSLES::close() {
-
+    mLock.lock();
+    Result result = Result::OK;
     if (mState == StreamState::Closed){
-        return Result::ErrorClosed;
+        result = Result::ErrorClosed;
     } else {
+        mLock.unlock(); // avoid recursive lock
         requestPause();
+        mLock.lock();
         // invalidate any interfaces
         mPlayInterface = NULL;
-        return AudioStreamOpenSLES::close();
+        result = AudioStreamOpenSLES::close();
     }
+    mLock.unlock(); // avoid recursive lock
+    return result;
 }
 
-Result AudioOutputStreamOpenSLES::setPlayState(SLuint32 newState) {
+Result AudioOutputStreamOpenSLES::setPlayState_l(SLuint32 newState) {
 
-    LOGD("AudioOutputStreamOpenSLES(): setPlayState()");
+    LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
     Result result = Result::OK;
 
     if (mPlayInterface == nullptr){
-        LOGE("AudioOutputStreamOpenSLES::setPlayState() mPlayInterface is null");
+        LOGE("AudioOutputStreamOpenSLES::%s() mPlayInterface is null", __func__);
         return Result::ErrorInvalidState;
     }
 
     SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
     if (SL_RESULT_SUCCESS != slResult) {
-        LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult));
+        LOGD("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult));
         result = Result::ErrorInternal; // TODO convert slResult to Result::Error
     }
     return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestStart() {
+    LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
 
-    LOGD("AudioOutputStreamOpenSLES(): requestStart()");
+    mLock.lock();
     StreamState initialState = getState();
-    if (initialState == StreamState::Closed) return Result::ErrorClosed;
+    switch (initialState) {
+        case StreamState::Starting:
+        case StreamState::Started:
+            mLock.unlock();
+            return Result::OK;
+        case StreamState::Closed:
+            mLock.unlock();
+            return Result::ErrorClosed;
+        default:
+            break;
+    }
+
+    // We use a callback if the user requests one
+    // OR if we have an internal callback to read the blocking IO buffer.
+    setDataCallbackEnabled(true);
 
     setState(StreamState::Starting);
-    Result result = setPlayState(SL_PLAYSTATE_PLAYING);
+    Result result = setPlayState_l(SL_PLAYSTATE_PLAYING);
     if (result == Result::OK) {
         setState(StreamState::Started);
+        mLock.unlock();
+        // Enqueue the first buffer to start the streaming.
+        // This might call requestStop() so try to avoid a recursive lock.
         processBufferCallback(mSimpleBufferQueueInterface);
     } else {
         setState(initialState);
+        mLock.unlock();
     }
+    LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
     return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestPause() {
+    LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
 
-    LOGD("AudioOutputStreamOpenSLES::requestPause()");
+    std::lock_guard<std::mutex> lock(mLock);
     StreamState initialState = getState();
-    if (initialState == StreamState::Closed) return Result::ErrorClosed;
+    switch (initialState) {
+        case StreamState::Pausing:
+        case StreamState::Paused:
+            return Result::OK;
+        case StreamState::Closed:
+            return Result::ErrorClosed;
+        default:
+            break;
+    }
 
     setState(StreamState::Pausing);
-    Result result = setPlayState(SL_PLAYSTATE_PAUSED);
+    Result result = setPlayState_l(SL_PLAYSTATE_PAUSED);
     if (result == Result::OK) {
         // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
         int64_t framesWritten = getFramesWritten();
@@ -303,6 +336,7 @@
     } else {
         setState(initialState);
     }
+    LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
     return result;
 }
 
@@ -310,36 +344,50 @@
  * Flush/clear the queue buffers
  */
 Result AudioOutputStreamOpenSLES::requestFlush() {
+    std::lock_guard<std::mutex> lock(mLock);
+    return requestFlush_l();
+}
 
-    LOGD("AudioOutputStreamOpenSLES(): requestFlush()");
+Result AudioOutputStreamOpenSLES::requestFlush_l() {
+    LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
     if (getState() == StreamState::Closed) return Result::ErrorClosed;
-
+    Result result = Result::OK;
     if (mPlayInterface == NULL || mSimpleBufferQueueInterface == NULL) {
-        return Result::ErrorInvalidState;
+        result = Result::ErrorInvalidState;
     } else {
-        SLresult result = (*mSimpleBufferQueueInterface)->Clear(mSimpleBufferQueueInterface);
-        if (result == SL_RESULT_SUCCESS){
-            return Result::OK;
-        } else {
+        SLresult slResult = (*mSimpleBufferQueueInterface)->Clear(mSimpleBufferQueueInterface);
+        if (slResult != SL_RESULT_SUCCESS){
             LOGW("Failed to clear buffer queue. OpenSLES error: %d", result);
-            return Result::ErrorInternal;
+            result = Result::ErrorInternal;
         }
     }
+    LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
+    return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestStop() {
+    LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
 
-    LOGD("AudioOutputStreamOpenSLES(): requestStop()");
+    std::lock_guard<std::mutex> lock(mLock);
     StreamState initialState = getState();
-    if (initialState == StreamState::Closed) return Result::ErrorClosed;
+    switch (initialState) {
+        case StreamState::Stopping:
+        case StreamState::Stopped:
+            return Result::OK;
+        case StreamState::Closed:
+            return Result::ErrorClosed;
+        default:
+            break;
+    }
 
     setState(StreamState::Stopping);
 
-    Result result = setPlayState(SL_PLAYSTATE_STOPPED);
+    Result result = setPlayState_l(SL_PLAYSTATE_STOPPED);
     if (result == Result::OK) {
 
-        // Also clear the buffer queue so the old data won't be played if the stream is restarted
-        if (requestFlush() != Result::OK){
+        // Also clear the buffer queue so the old data won't be played if the stream is restarted.
+        // Call the _l function that expects to already be under a lock.
+        if (requestFlush_l() != Result::OK) {
             LOGW("Failed to flush the stream. Error %s", convertToText(flush()));
         }
 
@@ -352,8 +400,8 @@
     } else {
         setState(initialState);
     }
+    LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
     return result;
-
 }
 
 void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
diff --git a/src/opensles/AudioOutputStreamOpenSLES.h b/src/opensles/AudioOutputStreamOpenSLES.h
index ea0ba8c..999a80c 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.h
+++ b/src/opensles/AudioOutputStreamOpenSLES.h
@@ -58,13 +58,15 @@
 
     Result onAfterDestroy() override;
 
+    Result requestFlush_l();
+
     /**
      * Set OpenSL ES PLAYSTATE.
      *
      * @param newState SL_PLAYSTATE_PAUSED, SL_PLAYSTATE_PLAYING, SL_PLAYSTATE_STOPPED
      * @return
      */
-    Result setPlayState(SLuint32 newState);
+    Result setPlayState_l(SLuint32 newState);
 
     SLPlayItf      mPlayInterface = nullptr;
 
diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp
index 93dceb7..93ad431 100644
--- a/src/opensles/AudioStreamOpenSLES.cpp
+++ b/src/opensles/AudioStreamOpenSLES.cpp
@@ -243,13 +243,11 @@
     return (*bq)->Enqueue(bq, mCallbackBuffer, mBytesPerCallback);
 }
 
-SLresult AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
+void AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
+    bool stopStream = false;
     // Ask the callback to fill the output buffer with data.
-    DataCallbackResult result = fireCallback(mCallbackBuffer, mFramesPerCallback);
-    if (result != DataCallbackResult::Continue) {
-        LOGE("Oboe callback returned %d", result);
-        return SL_RESULT_INTERNAL_ERROR; // TODO How should we stop OpenSL ES.
-    } else {
+    DataCallbackResult result = fireDataCallback(mCallbackBuffer, mFramesPerCallback);
+    if (result == DataCallbackResult::Continue) {
         // Update Oboe service position based on OpenSL ES position.
         updateServiceFrameCounter();
         // Update Oboe client position with frames handled by the callback.
@@ -259,7 +257,20 @@
             mFramesWritten += mFramesPerCallback;
         }
         // Pass the data to OpenSLES.
-        return enqueueCallbackBuffer(bq);
+        SLresult enqueueResult = enqueueCallbackBuffer(bq);
+        if (enqueueResult != SL_RESULT_SUCCESS) {
+            LOGE("enqueueCallbackBuffer() returned %d", enqueueResult);
+            stopStream = true;
+        }
+    } else if (result == DataCallbackResult::Stop) {
+        LOGD("Oboe callback returned Stop");
+        stopStream = true;
+    } else {
+        LOGW("Oboe callback returned unexpected value = %d", result);
+        stopStream = true;
+    }
+    if (stopStream) {
+        requestStop();
     }
 }
 
diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h
index 0b6fb96..c49fd35 100644
--- a/src/opensles/AudioStreamOpenSLES.h
+++ b/src/opensles/AudioStreamOpenSLES.h
@@ -70,13 +70,12 @@
      *
      * This is public, but don't call it directly.
      */
-    SLresult processBufferCallback(SLAndroidSimpleBufferQueueItf bq);
+    void processBufferCallback(SLAndroidSimpleBufferQueueItf bq);
 
     Result waitForStateChange(StreamState currentState,
                               StreamState *nextState,
                               int64_t timeoutNanoseconds) override;
 
-
 protected:
 
     SLuint32 channelCountToChannelMaskDefault(int channelCount);
diff --git a/tests/testStreamOpen.cpp b/tests/testStreamOpen.cpp
index 8cc4023..4dc79e2 100644
--- a/tests/testStreamOpen.cpp
+++ b/tests/testStreamOpen.cpp
@@ -198,3 +198,14 @@
     ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
     closeStream();
 }
+
+TEST_F(StreamOpen, LowLatencyStreamHasBufferSizeOfTwoBursts){
+
+    if (mBuilder.isAAudioRecommended()){
+        mBuilder.setDirection(Direction::Output);
+        mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
+        openStream();
+        ASSERT_EQ(mStream->getBufferSizeInFrames(), mStream->getFramesPerBurst() * 2);
+        closeStream();
+    }
+}