Merge branch 'refactor_samplebuffer'
diff --git a/apps/OboeTester/app/build.gradle b/apps/OboeTester/app/build.gradle
index 2220dd1..a57de16 100644
--- a/apps/OboeTester/app/build.gradle
+++ b/apps/OboeTester/app/build.gradle
@@ -36,7 +36,7 @@
 
 dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
-    implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta2'
+    implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
 
     testImplementation 'junit:junit:4.13-beta-3'
     implementation 'com.android.support:appcompat-v7:28.0.0'
diff --git a/apps/OboeTester/build.gradle b/apps/OboeTester/build.gradle
index 168851d..d254264 100644
--- a/apps/OboeTester/build.gradle
+++ b/apps/OboeTester/build.gradle
@@ -6,7 +6,7 @@
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
+        classpath 'com.android.tools.build:gradle:3.6.2'
     }
 }
 
diff --git a/apps/OboeTester/gradle/wrapper/gradle-wrapper.properties b/apps/OboeTester/gradle/wrapper/gradle-wrapper.properties
index 589ba30..419af02 100644
--- a/apps/OboeTester/gradle/wrapper/gradle-wrapper.properties
+++ b/apps/OboeTester/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jan 21 16:32:11 MST 2020
+#Wed Mar 25 15:16:43 PDT 2020
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index d693a93..e5a5969 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -1,12 +1,52 @@
-# Getting Started
-The easiest way to start using Oboe is to build it from source by adding a few steps to an existing Android Studio project.
+# Adding Oboe to your project
+There are two ways use Oboe in your Android Studio project: 
 
-## Creating an Android app with native support
-+ Create a new project: `File > New > New Project`
-+ When selecting the project type, select Native C++
-+ Finish configuring project
+1) **Use the Oboe pre-built library binaries and headers** *(Experimental)*. Use this approach if you just want to use a stable version of the Oboe library in your project.
 
-## Adding Oboe to your project
+or
+
+2) **Build Oboe from source.** Use this approach if you would like to debug or make changes to the Oboe source code and contribute back to the project.
+
+## Option 1) Using pre-built binaries and headers
+*This approach is currently experimental as it uses a preview version of Android Studio.*
+
+Oboe is distributed as a [prefab](https://github.com/google/prefab) package via [Google Maven](https://maven.google.com/web/index.html) (search for "oboe"). [Prefab support was added](https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html) to [Android Studio Preview 4.0 Canary 9](https://developer.android.com/studio/preview) so you'll need to be using this version of Android Studio or above. 
+
+Add the oboe dependency to your app's `build.gradle` file. Replace "1.3.0" with the [latest stable version](https://github.com/google/oboe/releases/) of Oboe:
+
+    dependencies {
+        implementation 'com.google.oboe:oboe:1.3.0'
+    }
+
+Prefab isn't enabled by default so enable it by adding following to your `gradle.properties` file, which is in the root folder of your app:
+
+    # Enables Prefab
+    android.enablePrefab=true
+    # Work around https://issuetracker.google.com/149575364
+    android.enableParallelJsonGen=false
+    # 4.0.0 canary 9 defaults to Prefab 1.0.0-alpha3, which is not the latest.
+    android.prefabVersion=1.0.0-alpha5
+
+**Note:** Please check back regularly to see whether these workarounds are still required. This will ensure you don't get stuck on an unecessary Android Studio configuration or outdated Prefab version.
+
+Include and link to oboe by updating your `CMakeLists.txt`: 
+
+    find_package (oboe REQUIRED CONFIG)
+    target_link_libraries(app oboe::oboe) # You may have other libraries here such as `log`.
+
+Configure your app to use the shared STL by updating your `app/build.gradle`: 
+
+    android { 
+        defaultConfig { 
+            externalNativeBuild {
+                cmake {
+                    arguments "-DANDROID_STL=c++_shared"
+                }
+	    }
+	}
+    }
+
+## Option 2) Building from source
 
 ### 1. Clone the github repository
 Start by cloning the [latest stable release](https://github.com/google/oboe/releases/) of the Oboe repository, for example:
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
index 6aabb8f..d5fded9 100644
--- a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "LiveEffectEngine.h"
-#include <assert.h>
+#include <cassert>
 #include <logging_macros.h>
 
+#include "LiveEffectEngine.h"
 
 LiveEffectEngine::LiveEffectEngine() {
     assert(mOutputChannelCount == mInputChannelCount);
@@ -28,7 +28,6 @@
     mFullDuplexPass.stop();
     closeStream(mPlayStream);
     closeStream(mRecordingStream);
-
 }
 
 void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
@@ -43,18 +42,23 @@
     oboe::AudioStreamBuilder builder;
     return builder.isAAudioSupported();
 }
+
 bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
     if (mIsEffectOn) return false;
 
     mAudioApi = api;
     return true;
 }
-void LiveEffectEngine::setEffectOn(bool isOn) {
+
+bool LiveEffectEngine::setEffectOn(bool isOn) {
+    bool success = true;
     if (isOn != mIsEffectOn) {
-        mIsEffectOn = isOn;
         if (isOn) {
-            openStreams();
-            mFullDuplexPass.start();
+            success = openStreams() == oboe::Result::OK;
+            if (success) {
+                mFullDuplexPass.start();
+                mIsEffectOn = isOn;
+            }
         } else {
             mFullDuplexPass.stop();
             /*
@@ -67,25 +71,36 @@
             */
             closeStream(mPlayStream);
             closeStream(mRecordingStream);
+            mIsEffectOn = isOn;
        }
     }
+    return success;
 }
-void LiveEffectEngine::openStreams() {
+
+oboe::Result  LiveEffectEngine::openStreams() {
     // Note: The order of stream creation is important. We create the playback
     // stream first, then use properties from the playback stream
     // (e.g. sample rate) to create the recording stream. By matching the
     // properties we should get the lowest latency path
     oboe::AudioStreamBuilder inBuilder, outBuilder;
     setupPlaybackStreamParameters(&outBuilder);
-    outBuilder.openStream(&mPlayStream);
+    oboe::Result result = outBuilder.openManagedStream(mPlayStream);
+    if (result != oboe::Result::OK) {
+        return result;
+    }
     warnIfNotLowLatency(mPlayStream);
 
     setupRecordingStreamParameters(&inBuilder);
-    inBuilder.openStream(&mRecordingStream);
+    result = inBuilder.openManagedStream(mRecordingStream);
+    if (result != oboe::Result::OK) {
+        closeStream(mPlayStream);
+        return result;
+    }
     warnIfNotLowLatency(mRecordingStream);
 
-    mFullDuplexPass.setInputStream(mRecordingStream);
-    mFullDuplexPass.setOutputStream(mPlayStream);
+    mFullDuplexPass.setInputStream(mRecordingStream.get());
+    mFullDuplexPass.setOutputStream(mPlayStream.get());
+    return result;
 }
 
 /**
@@ -148,13 +163,14 @@
  * [the closing thread is the UI thread in this sample].
  * @param stream the stream to close
  */
-void LiveEffectEngine::closeStream(oboe::AudioStream *stream) {
+void LiveEffectEngine::closeStream(oboe::ManagedStream &stream) {
     if (stream) {
         oboe::Result result = stream->close();
         if (result != oboe::Result::OK) {
             LOGE("Error closing stream. %s", oboe::convertToText(result));
         }
         LOGW("Successfully closed streams");
+        stream.reset();
     }
 }
 
@@ -164,7 +180,7 @@
  * @param stream: newly created stream
  *
  */
-void LiveEffectEngine::warnIfNotLowLatency(oboe::AudioStream *stream) {
+void LiveEffectEngine::warnIfNotLowLatency(oboe::ManagedStream &stream) {
     if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
         LOGW(
             "Stream is NOT low latency."
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
index be62d6f..16813cf 100644
--- a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
@@ -29,16 +29,19 @@
     ~LiveEffectEngine();
     void setRecordingDeviceId(int32_t deviceId);
     void setPlaybackDeviceId(int32_t deviceId);
-    void setEffectOn(bool isOn);
-    void openStreams();
+    /**
+     * @param isOn
+     * @return true if it succeeds
+     */
+    bool setEffectOn(bool isOn);
 
     /*
      * oboe::AudioStreamCallback interface implementation
      */
     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
-                                          void *audioData, int32_t numFrames);
-    void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error);
-    void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error);
+                                          void *audioData, int32_t numFrames) override;
+    void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
+    void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
 
     bool setAudioApi(oboe::AudioApi);
     bool isAAudioSupported(void);
@@ -52,12 +55,14 @@
     int32_t mSampleRate = oboe::kUnspecified;
     int32_t mInputChannelCount = oboe::ChannelCount::Stereo;
     int32_t mOutputChannelCount = oboe::ChannelCount::Stereo;
-    oboe::AudioStream *mRecordingStream = nullptr;
-    oboe::AudioStream *mPlayStream = nullptr;
+
+    oboe::ManagedStream mRecordingStream;
+    oboe::ManagedStream mPlayStream;
+
     oboe::AudioApi mAudioApi = oboe::AudioApi::AAudio;
 
-    void closeStream(oboe::AudioStream *stream);
-
+    oboe::Result openStreams();
+    void closeStream(oboe::ManagedStream &stream);
 
     oboe::AudioStreamBuilder *setupCommonStreamParameters(
         oboe::AudioStreamBuilder *builder);
@@ -65,7 +70,7 @@
         oboe::AudioStreamBuilder *builder);
     oboe::AudioStreamBuilder *setupPlaybackStreamParameters(
         oboe::AudioStreamBuilder *builder);
-    void warnIfNotLowLatency(oboe::AudioStream *stream);
+    void warnIfNotLowLatency(oboe::ManagedStream &stream);
 };
 
 #endif  // OBOE_LIVEEFFECTENGINE_H
diff --git a/samples/LiveEffect/src/main/cpp/jni_bridge.cpp b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
index 52ec9ef..757a26b 100644
--- a/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
+++ b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
@@ -25,14 +25,14 @@
 
 extern "C" {
 
-JNIEXPORT bool JNICALL
+JNIEXPORT jboolean JNICALL
 Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_create(JNIEnv *env,
                                                                jclass) {
     if (engine == nullptr) {
         engine = new LiveEffectEngine();
     }
 
-    return (engine != nullptr);
+    return (engine != nullptr) ? JNI_TRUE : JNI_FALSE;
 }
 
 JNIEXPORT void JNICALL
@@ -42,17 +42,17 @@
     engine = nullptr;
 }
 
-JNIEXPORT void JNICALL
+JNIEXPORT jboolean JNICALL
 Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_setEffectOn(
     JNIEnv *env, jclass, jboolean isEffectOn) {
     if (engine == nullptr) {
         LOGE(
             "Engine is null, you must call createEngine before calling this "
             "method");
-        return;
+        return JNI_FALSE;
     }
 
-    engine->setEffectOn(isEffectOn);
+    return engine->setEffectOn(isEffectOn) ? JNI_TRUE : JNI_FALSE;
 }
 
 JNIEXPORT void JNICALL
@@ -105,8 +105,7 @@
             return JNI_FALSE;
     }
 
-    return static_cast<jboolean>(engine->setAudioApi(audioApi) ? JNI_TRUE
-                                                               : JNI_FALSE);
+    return engine->setAudioApi(audioApi) ? JNI_TRUE : JNI_FALSE;
 }
 
 JNIEXPORT jboolean JNICALL
@@ -118,15 +117,14 @@
             "before calling this method");
         return JNI_FALSE;
     }
-    return static_cast<jboolean>(engine->isAAudioSupported() ? JNI_TRUE
-                                                             : JNI_FALSE);
+    return engine->isAAudioSupported() ? JNI_TRUE : JNI_FALSE;
 }
 
 JNIEXPORT void JNICALL
 Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_native_1setDefaultStreamValues(JNIEnv *env,
-                                                                            jclass type,
-                                                                            jint sampleRate,
-                                                                            jint framesPerBurst) {
+                                               jclass type,
+                                               jint sampleRate,
+                                               jint framesPerBurst) {
     oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate;
     oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst;
 }
diff --git a/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
index e2f6840..2d93eaa 100644
--- a/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
+++ b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
@@ -32,7 +32,7 @@
     static native boolean create();
     static native boolean isAAudioSupported();
     static native boolean setAPI(int apiType);
-    static native void setEffectOn(boolean isEffectOn);
+    static native boolean setEffectOn(boolean isEffectOn);
     static native void setRecordingDeviceId(int deviceId);
     static native void setPlaybackDeviceId(int deviceId);
     static native void delete();
diff --git a/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java
index 94f52cc..2057db5 100644
--- a/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java
+++ b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java
@@ -131,7 +131,7 @@
         }
         findViewById(R.id.slesButton).setEnabled(enable);
         if(!aaudioSupported) {
-          findViewById(R.id.aaudioButton).setEnabled(false);
+            findViewById(R.id.aaudioButton).setEnabled(false);
         } else {
             findViewById(R.id.aaudioButton).setEnabled(enable);
         }
@@ -139,6 +139,7 @@
         ((RadioGroup)findViewById(R.id.apiSelectionGroup))
           .check(apiSelection == OBOE_API_AAUDIO ? R.id.aaudioButton : R.id.slesButton);
     }
+
     @Override
     protected void onStart() {
         super.onStart();
@@ -163,9 +164,7 @@
     public void toggleEffect() {
         if (isPlaying) {
             stopEffect();
-            EnableAudioApiUI(true);
         } else {
-            EnableAudioApiUI(false);
             LiveEffectEngine.setAPI(apiSelection);
             startEffect();
         }
@@ -179,11 +178,17 @@
             return;
         }
 
-        setSpinnersEnabled(false);
-        LiveEffectEngine.setEffectOn(true);
-        statusText.setText(R.string.status_playing);
-        toggleEffectButton.setText(R.string.stop_effect);
-        isPlaying = true;
+        boolean success = LiveEffectEngine.setEffectOn(true);
+        if (success) {
+            setSpinnersEnabled(false);
+            statusText.setText(R.string.status_playing);
+            toggleEffectButton.setText(R.string.stop_effect);
+            isPlaying = true;
+            EnableAudioApiUI(false);
+        } else {
+            statusText.setText(R.string.status_open_failed);
+            isPlaying = false;
+        }
     }
 
     private void stopEffect() {
@@ -193,6 +198,7 @@
         toggleEffectButton.setText(R.string.start_effect);
         isPlaying = false;
         setSpinnersEnabled(true);
+        EnableAudioApiUI(true);
     }
 
     private void setSpinnersEnabled(boolean isEnabled){
@@ -219,6 +225,7 @@
                 new String[]{Manifest.permission.RECORD_AUDIO},
                 AUDIO_EFFECT_REQUEST);
     }
+
     private void resetStatusView() {
         statusText.setText(R.string.status_warning);
     }
diff --git a/samples/LiveEffect/src/main/res/values/strings.xml b/samples/LiveEffect/src/main/res/values/strings.xml
index a1a97b6..ea45319 100644
--- a/samples/LiveEffect/src/main/res/values/strings.xml
+++ b/samples/LiveEffect/src/main/res/values/strings.xml
@@ -5,6 +5,7 @@
     <string name="stop_effect">Stop</string>
     <string name="need_record_audio_permission">"This sample needs RECORD_AUDIO permission"</string>
     <string name="status_playing">Engine Playing ....</string>
+    <string name="status_open_failed">Engine Failed to Open Streams!</string>
     <string name="status_record_audio_denied">Error: Permission for RECORD_AUDIO was denied</string>
     <string name="status_touch_to_begin">RECORD_AUDIO permission granted, touch START to begin</string>
     <string name="status_warning">Warning: If you run this sample using the built-in microphone
diff --git a/samples/drumthumper/build.gradle b/samples/drumthumper/build.gradle
index 28e312a..e4f307d 100644
--- a/samples/drumthumper/build.gradle
+++ b/samples/drumthumper/build.gradle
@@ -7,7 +7,7 @@
 
     defaultConfig {
         applicationId "com.google.oboe.sample.drumthumper"
-        minSdkVersion 23
+        minSdkVersion 26
         targetSdkVersion 29
         versionCode 1
         versionName "1.0"
diff --git a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
index 2b0d2bd..6fb7425 100644
--- a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
+++ b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
@@ -22,7 +22,7 @@
 
 #include <android/log.h>
 
-#include <player/OneShotSampleBuffer.h>
+#include <player/OneShotSampleSource.h>
 #include <player/SimpleMultiPlayer.h>
 
 static const char* TAG = "DrumPlayerJNI";
@@ -32,7 +32,7 @@
 extern "C" {
 #endif
 
-using namespace wavlib;
+using namespace iolib;
 
 static SimpleMultiPlayer sDTPlayer;
 
@@ -40,18 +40,17 @@
  * Native (JNI) implementation of DrumPlayer.setupAudioStreamNative()
  */
 JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_setupAudioStreamNative(
-        JNIEnv* env, jobject, jint numSampleBuffers, jint numChannels, jint sampleRate) {
+        JNIEnv* env, jobject, jint numChannels, jint sampleRate) {
     __android_log_print(ANDROID_LOG_INFO, TAG, "%s", "init()");
 
     // we know in this case that the sample buffers are all 1-channel, 41K
-    sDTPlayer.setupAudioStream(numSampleBuffers, numChannels, sampleRate);
+    sDTPlayer.setupAudioStream(numChannels, sampleRate);
 }
 
 /**
  * Native (JNI) implementation of DrumPlayer.teardownAudioStreamNative()
  */
-JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_teardownAudioStreamNative(
-        JNIEnv* env, jobject, jint numSampleBuffers, jint numChannels, jint sampleRate) {
+JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_teardownAudioStreamNative(JNIEnv* , jobject) {
     __android_log_print(ANDROID_LOG_INFO, TAG, "%s", "deinit()");
 
     // we know in this case that the sample buffers are all 1-channel, 44.1K
@@ -59,6 +58,17 @@
 }
 
 /**
+ * Native (JNI) implementation of DrumPlayer.allocSampleDataNative()
+ */
+JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_allocSampleDataNative(
+        JNIEnv* env, jobject, jint numSampleBuffers) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "%s", "allocSampleDataNative()");
+
+    // we know in this case that the sample buffers are all 1-channel, 44.1K
+    sDTPlayer.allocSampleData(numSampleBuffers);
+}
+
+/**
  * Native (JNI) implementation of DrumPlayer.loadWavAssetNative()
  */
 JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_loadWavAssetNative(JNIEnv* env, jobject, jbyteArray bytearray, jint index) {
@@ -71,6 +81,13 @@
 }
 
 /**
+ * Native (JNI) implementation of DrumPlayer.unloadWavAssetsNative()
+ */
+JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_unloadWavAssetsNative(JNIEnv* env, jobject) {
+    sDTPlayer.unloadSampleData();
+}
+
+/**
  * Native (JNI) implementation of DrumPlayer.trigger()
  */
 JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_trigger(JNIEnv* env, jobject, jint index) {
@@ -96,7 +113,11 @@
  */
 JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_restartStream() {
     sDTPlayer.resetAll();
-    sDTPlayer.openStream();
+    if (sDTPlayer.openStream()){
+        __android_log_print(ANDROID_LOG_INFO, TAG, "openStream successful");
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, TAG, "openStream failed");
+    }
 }
 
 #ifdef __cplusplus
diff --git a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
index da6482d..f171313 100644
--- a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
+++ b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
@@ -41,7 +41,7 @@
     }
 
     fun setupAudioStream() {
-        setupAudioStreamNative(NUM_SAMPLES, NUM_CHANNELS, SAMPLE_RATE)
+        setupAudioStreamNative(NUM_CHANNELS, SAMPLE_RATE)
     }
 
     fun teardownAudioStream() {
@@ -49,6 +49,10 @@
     }
 
     // asset-based samples
+    fun allocSampleData() {
+        allocSampleDataNative(NUM_SAMPLES)
+    }
+
     fun loadWavAssets(assetMgr: AssetManager) {
         loadWavAsset(assetMgr, "KickDrum.wav", BASSDRUM)
         loadWavAsset(assetMgr, "SnareDrum.wav", SNAREDRUM)
@@ -60,6 +64,10 @@
         loadWavAsset(assetMgr, "HiHat_Closed.wav", HIHATCLOSED)
     }
 
+    fun unloadWavAssets() {
+        unloadWavAssetsNative()
+    }
+
     fun loadWavAsset(assetMgr: AssetManager, assetName: String, index: Int) {
         try {
             val assetFD = assetMgr.openFd(assetName)
@@ -74,10 +82,12 @@
         }
     }
 
-    external fun setupAudioStreamNative(numSampleBuffers: Int, numChannels: Int, sampleRate: Int)
+    external fun setupAudioStreamNative(numChannels: Int, sampleRate: Int)
     external fun teardownAudioStreamNative()
 
+    external fun allocSampleDataNative(numSampleBuffers: Int)
     external fun loadWavAssetNative(wavBytes: ByteArray, index: Int)
+    external fun unloadWavAssetsNative()
 
     external fun trigger(drumIndex: Int)
 
diff --git a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
index 9acd0db..3ab87db 100644
--- a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
+++ b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
@@ -27,6 +27,8 @@
 
 import java.util.*
 
+import java.time.LocalDateTime;
+
 import kotlin.concurrent.schedule
 
 class DrumThumperActivity : AppCompatActivity(), TriggerPad.DrumPadTriggerListener {
@@ -36,7 +38,9 @@
 
     private var mDrumPlayer = DrumPlayer()
 
-    private val mUseDeviceChangeFallback = true
+    private val mUseDeviceChangeFallback = false
+    private val mSwitchTimerMs = 500L
+
     private var mDevicesInitialized = false
 
     private var mDeviceListener: DeviceListener = DeviceListener()
@@ -47,9 +51,20 @@
     }
 
     inner class DeviceListener: AudioDeviceCallback() {
+        fun logDevices(label: String, devices: Array<AudioDeviceInfo> ) {
+            Log.i(TAG, label + " " + devices.size)
+            for(device in devices) {
+                Log.i(TAG, "  " + device.getProductName().toString()
+                    + " type:" + device.getType()
+                    + " source:" + device.isSource()
+                    + " sink:" + device.isSink())
+            }
+        }
+
         override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo> ) {
             // Note: This will get called when the callback is installed.
             if (mDevicesInitialized) {
+                logDevices("onAudioDevicesAdded", addedDevices)
                 // This is not the initial callback, so devices have changed
                 Toast.makeText(applicationContext, "Added Device", Toast.LENGTH_LONG).show()
                 resetOutput()
@@ -58,22 +73,26 @@
         }
 
         override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo> ) {
+            logDevices("onAudioDevicesRemoved", removedDevices)
             Toast.makeText(applicationContext, "Removed Device", Toast.LENGTH_LONG).show()
             resetOutput()
         }
 
-        fun resetOutput() {
+        private fun resetOutput() {
+            Log.i(TAG, "resetOutput() time:" + LocalDateTime.now() + " native reset:" + mDrumPlayer.getOutputReset());
             if (mDrumPlayer.getOutputReset()) {
                 // the (native) stream has been reset by the onErrorAfterClose() callback
                 mDrumPlayer.clearOutputReset()
             } else {
                 // give the (native) stream a chance to close it.
-                val timer = Timer("stream restart timer", false)
+                val timer = Timer("stream restart timer time:" + LocalDateTime.now(),
+                        false)
                 // schedule a single event
-                timer.schedule(3000) {
+                timer.schedule(mSwitchTimerMs) {
                     if (!mDrumPlayer.getOutputReset()) {
                         // still didn't get reset, so lets do it ourselves
-                        mDrumPlayer.restartStream();
+                        Log.i(TAG, "restartStream() time:" + LocalDateTime.now())
+                        mDrumPlayer.restartStream()
                     }
                 }
             }
@@ -84,19 +103,23 @@
         super.onCreate(savedInstanceState)
 
         mAudioMgr = getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+        mDrumPlayer.allocSampleData()
+        mDrumPlayer.loadWavAssets(getAssets())
     }
 
     override fun onStart() {
         super.onStart()
 
-    }
-
-    override fun onResume() {
-        super.onResume()
+        mDrumPlayer.setupAudioStream()
 
         if (mUseDeviceChangeFallback) {
             mAudioMgr!!.registerAudioDeviceCallback(mDeviceListener, null)
         }
+    }
+
+    override fun onResume() {
+        super.onResume()
 
         // UI
         setContentView(R.layout.drumthumper_activity)
@@ -142,22 +165,24 @@
             pad.addListener(this)
         }
 
-        mDrumPlayer.setupAudioStream()
-        mDrumPlayer.loadWavAssets(getAssets())
     }
 
     override fun onPause() {
         super.onPause()
-
-        mAudioMgr!!.unregisterAudioDeviceCallback(mDeviceListener)
     }
 
     override fun onStop() {
+        if (mUseDeviceChangeFallback) {
+            mAudioMgr!!.unregisterAudioDeviceCallback(mDeviceListener)
+        }
+
         mDrumPlayer.teardownAudioStream()
+
         super.onStop()
     }
 
     override fun onDestroy() {
+        mDrumPlayer.unloadWavAssets();
         super.onDestroy()
     }
 
diff --git a/samples/iolib/src/main/cpp/CMakeLists.txt b/samples/iolib/src/main/cpp/CMakeLists.txt
index 6192961..2c3d923 100644
--- a/samples/iolib/src/main/cpp/CMakeLists.txt
+++ b/samples/iolib/src/main/cpp/CMakeLists.txt
@@ -44,8 +44,9 @@
         STATIC

 

         # source

+        ${CMAKE_CURRENT_LIST_DIR}/player/SampleSource.cpp

         ${CMAKE_CURRENT_LIST_DIR}/player/SampleBuffer.cpp

-        ${CMAKE_CURRENT_LIST_DIR}/player/OneShotSampleBuffer.cpp

+        ${CMAKE_CURRENT_LIST_DIR}/player/OneShotSampleSource.cpp

         ${CMAKE_CURRENT_LIST_DIR}/player/SimpleMultiPlayer.cpp)

 

 # Specifies libraries CMake should link to your target library. You

diff --git a/samples/iolib/src/main/cpp/player/DataSource.h b/samples/iolib/src/main/cpp/player/DataSource.h
index b455cb8..b5f9c9a 100644
--- a/samples/iolib/src/main/cpp/player/DataSource.h
+++ b/samples/iolib/src/main/cpp/player/DataSource.h
@@ -19,13 +19,7 @@
 
 #include <cstdint>
 
-/*
- * Defines the relevant properties of the audio data being sourced.
- */
-struct AudioProperties {
-    int32_t channelCount;
-    int32_t sampleRate;
-};
+namespace iolib {
 
 /*
  * Defines an interface for audio data sources for the SimpleMultiPlayer class.
@@ -33,8 +27,10 @@
 class DataSource {
 public:
     virtual ~DataSource() {};
-    virtual AudioProperties getProperties() const  = 0;
+
+    virtual void mixAudio(float* outBuff, int numFrames) = 0;
 };
 
+}
 
 #endif //_PLAYER_AUDIOSOURCE_H_
diff --git a/samples/iolib/src/main/cpp/player/OneShotSampleBuffer.cpp b/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
similarity index 61%
rename from samples/iolib/src/main/cpp/player/OneShotSampleBuffer.cpp
rename to samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
index a00afbf..c637dfe 100644
--- a/samples/iolib/src/main/cpp/player/OneShotSampleBuffer.cpp
+++ b/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
@@ -18,32 +18,12 @@
 
 #include "io/wav/WavStreamReader.h"
 
-#include "OneShotSampleBuffer.h"
+#include "OneShotSampleSource.h"
 
-namespace wavlib {
+namespace iolib {
 
-void OneShotSampleBuffer::loadSampleData(WavStreamReader* reader) {
-    mProperties.channelCount = reader->getNumChannels();
-    mProperties.sampleRate = reader->getSampleRate();
-
-    reader->positionToAudio();
-
-    numSampleFrames = reader->getNumSampleFrames() * reader->getNumChannels();
-    mSampleData = new float[numSampleFrames];
-    reader->getDataFloat(mSampleData, reader->getNumSampleFrames());
-}
-
-void OneShotSampleBuffer::unloadSampleData() {
-    delete[] mSampleData;
-    mSampleData = nullptr;
-    numSampleFrames = 0;
-
-    // kinda by definition..
-    mCurFrameIndex = 0;
-    mIsPlaying = false;
-}
-
-void OneShotSampleBuffer::mixAudio(float* outBuff, int32_t numFrames) {
+void OneShotSampleSource::mixAudio(float* outBuff, int32_t numFrames) {
+    int32_t numSampleFrames = mSampleBuffer->getNumSampleFrames();
     int32_t numWriteFrames = mIsPlaying
                          ? std::min(numFrames, numSampleFrames - mCurFrameIndex)
                          : 0;
@@ -51,8 +31,11 @@
     if (numWriteFrames != 0) {
         // Mix in the samples
         int32_t lastIndex = mCurFrameIndex + numWriteFrames;
+
+        // investigate unrolling this loop...
+        const float* data  = mSampleBuffer->getSampleData();
         for(int32_t index = 0; index < numWriteFrames; index++) {
-            outBuff[index] += mSampleData[mCurFrameIndex++];
+            outBuff[index] += data[mCurFrameIndex++];
         }
 
         if (mCurFrameIndex >= numSampleFrames) {
diff --git a/samples/iolib/src/main/cpp/player/OneShotSampleBuffer.h b/samples/iolib/src/main/cpp/player/OneShotSampleSource.h
similarity index 64%
rename from samples/iolib/src/main/cpp/player/OneShotSampleBuffer.h
rename to samples/iolib/src/main/cpp/player/OneShotSampleSource.h
index 10f509f..b78a8b8 100644
--- a/samples/iolib/src/main/cpp/player/OneShotSampleBuffer.h
+++ b/samples/iolib/src/main/cpp/player/OneShotSampleSource.h
@@ -14,29 +14,24 @@
  * limitations under the License.
  */
 
-#ifndef _PLAYER_ONESHOTSAMPLEBUFFER_
-#define _PLAYER_ONESHOTSAMPLEBUFFER_
+#ifndef _PLAYER_ONESHOTSAMPLESOURCE_
+#define _PLAYER_ONESHOTSAMPLESOURCE_
 
-#include "SampleBuffer.h"
+#include "SampleSource.h"
 
-namespace wavlib {
-
-class WavStreamReader;
+namespace iolib {
 
 /**
  * Provides audio data which will play through once when triggered
  */
-class OneShotSampleBuffer: public SampleBuffer {
+class OneShotSampleSource: public SampleSource {
 public:
-    OneShotSampleBuffer() : SampleBuffer() {};
-    virtual ~OneShotSampleBuffer() {};
-
-    void loadSampleData(WavStreamReader* reader);
-    void unloadSampleData();
+    OneShotSampleSource(SampleBuffer *sampleBuffer) : SampleSource(sampleBuffer) {};
+    virtual ~OneShotSampleSource() {};
 
     virtual void mixAudio(float* outBuff, int32_t numFrames);
 };
 
-} // namespace wavlib
+} // namespace iolib
 
-#endif //_PLAYER_ONESHOTSAMPLEBUFFER_
+#endif //_PLAYER_ONESHOTSAMPLESOURCE_
diff --git a/samples/iolib/src/main/cpp/player/SampleBuffer.cpp b/samples/iolib/src/main/cpp/player/SampleBuffer.cpp
index d8f3da0..464c92f 100644
--- a/samples/iolib/src/main/cpp/player/SampleBuffer.cpp
+++ b/samples/iolib/src/main/cpp/player/SampleBuffer.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,7 +16,25 @@
 
 #include "SampleBuffer.h"
 
-namespace wavlib {
-    // for now, all methods of SampleBuffer are either in-line or pure virtual
+#include "io/wav/WavStreamReader.h"
+
+namespace iolib {
+
+void SampleBuffer::loadSampleData(parselib::WavStreamReader* reader) {
+    mAudioProperties.channelCount = reader->getNumChannels();
+    mAudioProperties.sampleRate = reader->getSampleRate();
+
+    reader->positionToAudio();
+
+    mNumSamples = reader->getNumSampleFrames() * reader->getNumChannels();
+    mSampleData = new float[mNumSamples];
+
+    reader->getDataFloat(mSampleData, reader->getNumSampleFrames());
 }
 
+void SampleBuffer::unloadSampleData() {
+    delete[] mSampleData;
+    mNumSamples = 0;
+}
+
+}
diff --git a/samples/iolib/src/main/cpp/player/SampleBuffer.h b/samples/iolib/src/main/cpp/player/SampleBuffer.h
index 4483579..acb2629 100644
--- a/samples/iolib/src/main/cpp/player/SampleBuffer.h
+++ b/samples/iolib/src/main/cpp/player/SampleBuffer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,44 +17,39 @@
 #ifndef _PLAYER_SAMPLEBUFFER_
 #define _PLAYER_SAMPLEBUFFER_
 
-#include <cstdint>
+#include <io/wav/WavStreamReader.h>
 
-#include "DataSource.h"
+namespace iolib {
 
-namespace wavlib {
-
-/**
- * Defines an interface for audio data provided to a player object.
- * Concrete examples include OneShotSampleBuffer. One could imagine a LoopingSampleBuffer.
+/*
+ * Defines the relevant properties of the audio data being sourced.
  */
-class SampleBuffer: public DataSource {
-public:
-    SampleBuffer() : numSampleFrames(0), mCurFrameIndex(0), mIsPlaying(false) {};
-    virtual ~SampleBuffer() {};
-
-    /*
-     * Returns the audio properties of the audio data.
-     */
-    AudioProperties getProperties() const { return mProperties; };
-
-    void setPlayMode() { mCurFrameIndex = 0; mIsPlaying = true; }
-    void setStopMode() { mIsPlaying = false; mCurFrameIndex = 0; }
-
-    bool isPlaying() { return mIsPlaying; }
-
-    virtual void mixAudio(float* outBuff, int numFrames) {}
-
-
-protected:
-    AudioProperties mProperties;
-
-    float*  mSampleData;
-    int32_t numSampleFrames;
-    int32_t mCurFrameIndex;
-
-    bool mIsPlaying;
+struct AudioProperties {
+    int32_t channelCount;
+    int32_t sampleRate;
 };
 
-} // namespace wavlib
+class SampleBuffer {
+public:
+    SampleBuffer() : mNumSamples(0) {};
+    ~SampleBuffer() { unloadSampleData(); }
+
+    // Data load/unload
+    void loadSampleData(parselib::WavStreamReader* reader);
+    void unloadSampleData();
+
+    virtual AudioProperties getProperties() const { return mAudioProperties; }
+
+    float* getSampleData() { return mSampleData; }
+    int32_t getNumSampleFrames() { return mNumSamples; }
+
+protected:
+    AudioProperties mAudioProperties;
+
+    float*  mSampleData;
+    int32_t mNumSamples;
+};
+
+}
 
 #endif //_PLAYER_SAMPLEBUFFER_
diff --git a/samples/iolib/src/main/cpp/player/SampleSource.cpp b/samples/iolib/src/main/cpp/player/SampleSource.cpp
new file mode 100644
index 0000000..7c53708
--- /dev/null
+++ b/samples/iolib/src/main/cpp/player/SampleSource.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SampleSource.h"
+
+namespace iolib {
+    // for now, all methods of SampleSource are either in-line or pure virtual
+}
diff --git a/samples/iolib/src/main/cpp/player/SampleSource.h b/samples/iolib/src/main/cpp/player/SampleSource.h
new file mode 100644
index 0000000..367438d
--- /dev/null
+++ b/samples/iolib/src/main/cpp/player/SampleSource.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _PLAYER_SAMPLESOURCE_
+#define _PLAYER_SAMPLESOURCE_
+
+#include <cstdint>
+
+#include "DataSource.h"
+
+#include "SampleBuffer.h"
+
+namespace iolib {
+
+/**
+ * Defines an interface for audio data provided to a player object.
+ * Concrete examples include OneShotSampleBuffer. One could imagine a LoopingSampleBuffer.
+ */
+class SampleSource: public DataSource {
+public:
+    SampleSource(SampleBuffer *sampleBuffer)
+     : mSampleBuffer(sampleBuffer), mCurFrameIndex(0), mIsPlaying(false) {};
+    virtual ~SampleSource() {};
+
+    void setPlayMode() { mCurFrameIndex = 0; mIsPlaying = true; }
+    void setStopMode() { mIsPlaying = false; mCurFrameIndex = 0; }
+
+    bool isPlaying() { return mIsPlaying; }
+
+protected:
+    SampleBuffer    *mSampleBuffer;
+
+    int32_t mCurFrameIndex;
+
+    bool mIsPlaying;
+};
+
+} // namespace wavlib
+
+#endif //_PLAYER_SAMPLESOURCE_
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
index c95ec15..2d6edbf 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
@@ -16,16 +16,20 @@
 
 #include <android/log.h>
 
-// wavlib includes
+// parselib includes
 #include <io/stream/MemInputStream.h>
 #include <io/wav/WavStreamReader.h>
-#include "OneShotSampleBuffer.h"
 
+// local includes
+#include "OneShotSampleSource.h"
 #include "SimpleMultiPlayer.h"
 
 static const char* TAG = "SimpleMultiPlayer";
 
-using namespace wavlib;
+using namespace oboe;
+using namespace parselib;
+
+namespace iolib {
 
 constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
 
@@ -46,9 +50,10 @@
 
     memset(audioData, 0, numFrames * sizeof(float));
 
+    // OneShotSampleSource* sources = mSampleSources.get();
     for(int32_t index = 0; index < mNumSampleBuffers; index++) {
-        if (mSampleBuffers[index].isPlaying()) {
-            mSampleBuffers[index].mixAudio((float*)audioData, numFrames);
+        if (mSampleSources[index]->isPlaying()) {
+            mSampleSources[index]->mixAudio((float*)audioData, numFrames);
         }
     }
 
@@ -79,79 +84,107 @@
     builder.setSharingMode(SharingMode::Exclusive);
     builder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);
 
-    Result result = builder.openStream(&mAudioStream);
+    Result result = builder.openManagedStream(mAudioStream);
     if (result != Result::OK){
+        __android_log_print(
+                ANDROID_LOG_ERROR,
+                TAG,
+                "openStream failed. Error: %s", convertToText(result));
         return false;
     }
 
     // Reduce stream latency by setting the buffer size to a multiple of the burst size
-    auto setBufferSizeResult = mAudioStream->setBufferSizeInFrames(
+    // Note: this will fail with ErrorUnimplemented if we are using a callback with OpenSL ES
+    // See oboe::AudioStreamBuffered::setBufferSizeInFrames
+    result = mAudioStream->setBufferSizeInFrames(
             mAudioStream->getFramesPerBurst() * kBufferSizeInBursts);
-    if (setBufferSizeResult != Result::OK) {
-        return false;
+    if (result != Result::OK) {
+        __android_log_print(
+                ANDROID_LOG_WARN,
+                TAG,
+                "setBufferSizeInFrames failed. Error: %s", convertToText(result));
     }
 
-    result = mAudioStream->start();
+    result = mAudioStream->requestStart();
     if (result != Result::OK){
+        __android_log_print(
+                ANDROID_LOG_ERROR,
+                TAG,
+                "requestStart failed. Error: %s", convertToText(result));
         return false;
     }
 
     return true;
 }
 
-void SimpleMultiPlayer::setupAudioStream(int32_t numSampleBuffers, int32_t channelCount, int32_t sampleRate) {
-
+void SimpleMultiPlayer::setupAudioStream(int32_t channelCount, int32_t sampleRate) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "setupAudioStream()");
     mChannelCount = channelCount;
     mSampleRate = sampleRate;
+    mSampleRate = sampleRate;
 
-    mSampleBuffers = new OneShotSampleBuffer[mNumSampleBuffers = numSampleBuffers];
     openStream();
 }
 
 void SimpleMultiPlayer::teardownAudioStream() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "teardownAudioStream()");
     // tear down the player
     if (mAudioStream != nullptr) {
         mAudioStream->stop();
-        delete mAudioStream;
-        mAudioStream = nullptr;
     }
+}
 
-    // Unload the samples
-    unloadSampleData();
+void SimpleMultiPlayer::allocSampleData(int32_t numSampleBuffers) {
+    mNumSampleBuffers = numSampleBuffers;
+
+    for(int index = 0; index < numSampleBuffers; index++) {
+        SampleBuffer* buffer = new SampleBuffer();
+        OneShotSampleSource* source = new OneShotSampleSource(buffer);
+        mSampleBuffers.push_back(buffer);
+        mSampleSources.push_back(source);
+    }
 }
 
 void SimpleMultiPlayer::loadSampleDataFromAsset(byte* dataBytes, int32_t dataLen, int32_t index) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "loadSampleDataFromAsset()");
     MemInputStream stream(dataBytes, dataLen);
 
     WavStreamReader reader(&stream);
     reader.parse();
 
-    mSampleBuffers[index].loadSampleData(&reader);
+    mSampleBuffers[index]->loadSampleData(&reader);
 }
 
 void SimpleMultiPlayer::unloadSampleData() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "unloadSampleData()");
     for (int32_t bufferIndex = 0; bufferIndex < mNumSampleBuffers; bufferIndex++) {
-        mSampleBuffers[bufferIndex].unloadSampleData();
+        mSampleBuffers[bufferIndex]->unloadSampleData();
+        delete mSampleBuffers[bufferIndex];
+        delete mSampleSources[bufferIndex];
     }
-    delete[] mSampleBuffers;
-    mSampleBuffers = nullptr;
+
+    mSampleBuffers.clear();
+    mSampleSources.clear();
+
     mNumSampleBuffers = 0;
 }
 
 void SimpleMultiPlayer::triggerDown(int32_t index) {
     if (index < mNumSampleBuffers) {
-        mSampleBuffers[index].setPlayMode();
+        mSampleSources[index]->setPlayMode();
     }
 }
 
 void SimpleMultiPlayer::triggerUp(int32_t index) {
     if (index < mNumSampleBuffers) {
-        mSampleBuffers[index].setStopMode();
+        mSampleSources[index]->setStopMode();
     }
 }
 
 void SimpleMultiPlayer::resetAll() {
     for (int32_t bufferIndex = 0; bufferIndex < mNumSampleBuffers; bufferIndex++) {
-        mSampleBuffers[bufferIndex].setStopMode();
+        mSampleSources[bufferIndex]->setStopMode();
     }
 }
+
+}
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
index 128ff61..8371cb0 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
@@ -17,34 +17,37 @@
 #ifndef _PLAYER_SIMIPLEMULTIPLAYER_H_
 #define _PLAYER_SIMIPLEMULTIPLAYER_H_
 
+#include <vector>
+
 #include <oboe/Oboe.h>
 
-#include <player/OneShotSampleBuffer.h>
+#include "OneShotSampleSource.h"
+#include "SampleBuffer.h"
 
-using namespace oboe;
-using namespace wavlib;
+namespace iolib {
 
 typedef unsigned char byte;     // an 8-bit unsigned value
 
 /**
  * A simple streaming player for multiple SampleBuffers.
  */
-class SimpleMultiPlayer : public AudioStreamCallback  {
+class SimpleMultiPlayer : public oboe::AudioStreamCallback  {
 public:
     SimpleMultiPlayer();
 
     // Inherited from oboe::AudioStreamCallback
-    DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData,
+    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
             int32_t numFrames) override;
-    virtual void onErrorAfterClose(AudioStream *oboeStream, Result error) override;
-    virtual void onErrorBeforeClose(AudioStream * oboeStream, Result error) override;
+    virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
+    virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override;
 
-    void setupAudioStream(int32_t numSampleBuffers, int32_t channelCount, int32_t sampleRate);
+    void setupAudioStream(int32_t channelCount, int32_t sampleRate);
     void teardownAudioStream();
 
     bool openStream();
 
     // Wave Sample Loading...
+    void allocSampleData(int32_t numSampleBuffers);
     void loadSampleDataFromAsset(byte* dataBytes, int32_t dataLen, int32_t index);
     void unloadSampleData();
 
@@ -58,7 +61,7 @@
 
 private:
     // Oboe Audio Stream
-    AudioStream *mAudioStream { nullptr };
+    oboe::ManagedStream mAudioStream;
 
     // Audio attributs
     int32_t mChannelCount;
@@ -66,9 +69,11 @@
 
     // Sample Data
     int32_t mNumSampleBuffers;
-    OneShotSampleBuffer* mSampleBuffers;
+    std::vector<SampleBuffer*> mSampleBuffers;
+    std::vector<OneShotSampleSource*>   mSampleSources;
 
     bool    mOutputReset;
 };
 
+}
 #endif //_PLAYER_SIMIPLEMULTIPLAYER_H_
diff --git a/samples/parselib/src/main/cpp/io/stream/FileInputStream.cpp b/samples/parselib/src/main/cpp/io/stream/FileInputStream.cpp
index 2ab7d77..186b9b1 100644
--- a/samples/parselib/src/main/cpp/io/stream/FileInputStream.cpp
+++ b/samples/parselib/src/main/cpp/io/stream/FileInputStream.cpp
@@ -17,7 +17,7 @@
 

 #include "FileInputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 int32_t FileInputStream::read(void *buff, int32_t numBytes) {

     return ::read(mFH, buff, numBytes);

@@ -45,4 +45,4 @@
     }

 }

 

-} /* namespace wavlib */

+} /* namespace parselib */

diff --git a/samples/parselib/src/main/cpp/io/stream/FileInputStream.h b/samples/parselib/src/main/cpp/io/stream/FileInputStream.h
index 8734181..95c9afa 100644
--- a/samples/parselib/src/main/cpp/io/stream/FileInputStream.h
+++ b/samples/parselib/src/main/cpp/io/stream/FileInputStream.h
@@ -18,7 +18,7 @@
 

 #include "InputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 /**

  * A concrete implementation of InputStream for a file data source

@@ -44,6 +44,6 @@
     int mFH;

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_STREAM_FILEINPUTSTREAM_H_

diff --git a/samples/parselib/src/main/cpp/io/stream/InputStream.cpp b/samples/parselib/src/main/cpp/io/stream/InputStream.cpp
index 4df0772..747a401 100644
--- a/samples/parselib/src/main/cpp/io/stream/InputStream.cpp
+++ b/samples/parselib/src/main/cpp/io/stream/InputStream.cpp
@@ -15,7 +15,7 @@
  */

 #include "InputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 // All of the methods of InputStream are pure virtual

 

diff --git a/samples/parselib/src/main/cpp/io/stream/InputStream.h b/samples/parselib/src/main/cpp/io/stream/InputStream.h
index d3beee1..f9089d8 100644
--- a/samples/parselib/src/main/cpp/io/stream/InputStream.h
+++ b/samples/parselib/src/main/cpp/io/stream/InputStream.h
@@ -18,7 +18,7 @@
 

 #include <cstdint>

 

-namespace wavlib {

+namespace parselib {

 

 /**

  * An interface declaration for a stream of bytes. Concrete implements for File and Memory Buffers

@@ -58,6 +58,6 @@
     virtual void setPos(int32_t pos) = 0;

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_STREAM_INPUTSTREAM_H_

diff --git a/samples/parselib/src/main/cpp/io/stream/MemInputStream.cpp b/samples/parselib/src/main/cpp/io/stream/MemInputStream.cpp
index 0967704..37d5a15 100644
--- a/samples/parselib/src/main/cpp/io/stream/MemInputStream.cpp
+++ b/samples/parselib/src/main/cpp/io/stream/MemInputStream.cpp
@@ -18,7 +18,7 @@
 

 #include "MemInputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 int32_t MemInputStream::read(void *buff, int32_t numBytes) {

     int32_t numAvail = mBufferLen - mPos;

@@ -57,4 +57,4 @@
     }

 }

 

-} // namespace wavelib

+} // namespace parselib

diff --git a/samples/parselib/src/main/cpp/io/stream/MemInputStream.h b/samples/parselib/src/main/cpp/io/stream/MemInputStream.h
index 904f43e..5999249 100644
--- a/samples/parselib/src/main/cpp/io/stream/MemInputStream.h
+++ b/samples/parselib/src/main/cpp/io/stream/MemInputStream.h
@@ -18,7 +18,7 @@
 

 #include "InputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 /**

  * A concrete implementation of InputStream for a memory buffer data source

@@ -50,6 +50,6 @@
     int32_t mPos;

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_STREAM_MEMINPUTSTREAM_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/AudioEncoding.cpp b/samples/parselib/src/main/cpp/io/wav/AudioEncoding.cpp
index 5bf5618..3da041a 100644
--- a/samples/parselib/src/main/cpp/io/wav/AudioEncoding.cpp
+++ b/samples/parselib/src/main/cpp/io/wav/AudioEncoding.cpp
@@ -15,6 +15,6 @@
  */

 #include "AudioEncoding.h"

 

-namespace wavlib {

+namespace parselib {

 

 } // namespace wavlib

diff --git a/samples/parselib/src/main/cpp/io/wav/AudioEncoding.h b/samples/parselib/src/main/cpp/io/wav/AudioEncoding.h
index c6da407..2b9703a 100644
--- a/samples/parselib/src/main/cpp/io/wav/AudioEncoding.h
+++ b/samples/parselib/src/main/cpp/io/wav/AudioEncoding.h
@@ -16,7 +16,7 @@
 #ifndef _IO_WAV_AUDIOFORMAT_H_

 #define _IO_WAV_AUDIOFORMAT_H_

 

-namespace wavlib {

+namespace parselib {

 

 /**

  * Definitions for Audio Encodings in WAV files.

@@ -29,6 +29,6 @@
     static const int PCM_IEEEFLOAT = 2;

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_WAV_AUDIOFORMAT_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.cpp b/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.cpp
index 4d5148d..f691a1f 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.cpp
+++ b/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.cpp
@@ -17,7 +17,7 @@
 

 #include "WavChunkHeader.h"

 

-namespace wavlib {

+namespace parselib {

 

 const RiffID WavChunkHeader::RIFFID_DATA = makeRiffID('d', 'a', 't', 'a');

 

@@ -26,4 +26,4 @@
     stream->read(&mChunkSize, sizeof(mChunkSize));

 }

 

-} // namespace wavlib

+} // namespace parselib

diff --git a/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.h b/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.h
index 0035646..07f6333 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.h
+++ b/samples/parselib/src/main/cpp/io/wav/WavChunkHeader.h
@@ -18,7 +18,7 @@
 

 #include "WavTypes.h"

 

-namespace wavlib {

+namespace parselib {

 

 class InputStream;

 

@@ -45,6 +45,6 @@
     virtual void read(InputStream *stream);

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_WAV_WAVCHUNKHEADER_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.cpp b/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.cpp
index ba650ba..86d8cc3 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.cpp
+++ b/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.cpp
@@ -21,7 +21,7 @@
 

 static const char *TAG = "WavFmtChunkHeader";

 

-namespace wavlib {

+namespace parselib {

 

 const RiffID WavFmtChunkHeader::RIFFID_FMT = makeRiffID('f', 'm', 't', ' ');

 

@@ -72,4 +72,4 @@
     }

 }

 

-} // namespace wavlib

+} // namespace parselib

diff --git a/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.h b/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.h
index 5cdd92b..ab4d478 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.h
+++ b/samples/parselib/src/main/cpp/io/wav/WavFmtChunkHeader.h
@@ -20,7 +20,7 @@
 

 class InputStream;

 

-namespace wavlib {

+namespace parselib {

 

 /**

  * Encapsulates a WAV file 'fmt ' chunk.

@@ -51,6 +51,6 @@
     void read(InputStream *stream);

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_WAV_WAVFMTCHUNKHEADER_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.cpp b/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.cpp
index 9881094..e2333e2 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.cpp
+++ b/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.cpp
@@ -16,7 +16,7 @@
 #include "WavRIFFChunkHeader.h"

 #include "io/stream/InputStream.h"

 

-namespace wavlib {

+namespace parselib {

 

 const RiffID WavRIFFChunkHeader::RIFFID_RIFF = makeRiffID('R', 'I', 'F', 'F');

 const RiffID WavRIFFChunkHeader::RIFFID_WAVE = makeRiffID('W', 'A', 'V', 'E');

@@ -34,4 +34,4 @@
     stream->read(&mFormatId, sizeof(mFormatId));

 }

 

-} // namespace wavlib

+} // namespace parselib

diff --git a/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.h b/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.h
index e26c1df..f9e6af0 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.h
+++ b/samples/parselib/src/main/cpp/io/wav/WavRIFFChunkHeader.h
@@ -18,7 +18,7 @@
 

 #include "WavChunkHeader.h"

 

-namespace wavlib {

+namespace parselib {

 

 class InputStream;

 

@@ -37,6 +37,6 @@
     virtual void read(InputStream *stream);

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_WAV_WAVRIFFCHUNKHEADER_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/WavStreamReader.cpp b/samples/parselib/src/main/cpp/io/wav/WavStreamReader.cpp
index e820190..0931a3c 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavStreamReader.cpp
+++ b/samples/parselib/src/main/cpp/io/wav/WavStreamReader.cpp
@@ -28,7 +28,7 @@
 

 static const char *TAG = "WavStreamReader";

 

-namespace wavlib {

+namespace parselib {

 

 WavStreamReader::WavStreamReader(InputStream *stream) {

     mStream = stream;

@@ -156,4 +156,4 @@
     return 0;

 }

 

-} // namespace wavlib

+} // namespace parselib

diff --git a/samples/parselib/src/main/cpp/io/wav/WavStreamReader.h b/samples/parselib/src/main/cpp/io/wav/WavStreamReader.h
index fe004cb..9a0ab98 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavStreamReader.h
+++ b/samples/parselib/src/main/cpp/io/wav/WavStreamReader.h
@@ -27,7 +27,7 @@
  * http://soundfile.sapp.org/doc/WaveFormat/

  * https://web.archive.org/web/20090417165828/http://www.kk.iij4u.or.jp/~kondo/wave/mpidata.txt

  */

-namespace wavlib {

+namespace parselib {

 

 class InputStream;

 

@@ -68,6 +68,6 @@
     std::map<RiffID, WavChunkHeader *> *mChunkMap;

 };

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif // _IO_WAV_WAVSTREAMREADER_H_

diff --git a/samples/parselib/src/main/cpp/io/wav/WavTypes.h b/samples/parselib/src/main/cpp/io/wav/WavTypes.h
index af7a60a..89af7cd 100644
--- a/samples/parselib/src/main/cpp/io/wav/WavTypes.h
+++ b/samples/parselib/src/main/cpp/io/wav/WavTypes.h
@@ -16,7 +16,7 @@
 #ifndef __WAVTYPES_H__

 #define __WAVTYPES_H__

 

-namespace wavlib {

+namespace parselib {

 

 /*

  * Declarations for various (cross-platform) WAV-specific data types.

@@ -33,6 +33,6 @@
     return ((RiffID)d << 24) | ((RiffID)c << 16) | ((RiffID)b << 8) | (RiffID)a;

 }

 

-} // namespace wavlib

+} // namespace parselib

 

 #endif /* __WAVTYPES_H__ */

diff --git a/src/flowgraph/resampler/README.md b/src/flowgraph/resampler/README.md
index 68d2a36..2633b6a 100644
--- a/src/flowgraph/resampler/README.md
+++ b/src/flowgraph/resampler/README.md
@@ -2,7 +2,7 @@
 
 This folder contains a sample rate converter, or "resampler".
 It is part of [Oboe](https://github.com/google/oboe) but has no dependencies on Oboe.
-So it can be used outside of Oboe.
+So the contents of this folder can be used outside of Oboe.
 
 The converter is based on a sinc function that has been windowed by a hyperbolic cosine.
 We found this had fewer artifacts than the more traditional Kaiser window.
@@ -14,8 +14,9 @@
     #include "resampler/MultiChannelResampler.h"
 
 Here is an example of creating a stereo resampler that will convert from 44100 to 48000 Hz.
+Only do this once, when you open your stream. Then use the sample resampler to process multiple buffers.
 
-    MultiChannelResampler *mResampler = MultiChannelResampler::make(
+    MultiChannelResampler *resampler = MultiChannelResampler::make(
             2, // channel count
             44100, // input sampleRate
             48000, // output sampleRate
@@ -24,24 +25,69 @@
 Possible values for quality include { Fastest, Low, Medium, High, Best }.
 Higher quality levels will sound better but consume more CPU because they have more taps in the filter.
 
-## Calling the Resampler
+## Fractional Frame Counts
+
+Note that the number of output frames generated for a given number of input frames can vary.
+
+For example, suppose you are converting from 44100 Hz to 48000 Hz and using an input buffer with 940 frames. If you calculate the number of output frames you get:
+
+    940 * 48000 * 44100 = 1023.1292517...
+    
+You cannot generate a fractional number of frames. So the resampler will sometimes generate 1023 frames and sometimes 1024 frames. On average it will generate 1023.1292517 frames. The resampler stores the fraction internally and keeps track of when to consume or generate a frame.
+
+You can either use a fixed number of input frames or a fixed number of output frames. The other frame count will vary.
+
+## Calling the Resampler with a fixed number of OUTPUT frames
+
+In this example, suppose we have a fixed number of output frames and a variable number of input frames.
 
 Assume you start with these variables and a method that returns the next input frame:
 
-    float *outputBuffer;  // multi-channel buffer to be filled
-    int numFrames;        // number of frames of output
-    int32_t channelCount; // 1 for mono, 2 for stereo, etc.
+    float *outputBuffer;     // multi-channel buffer to be filled
+    int    numOutputFrames;  // number of frames of output
     
 The resampler has a method isWriteNeeded() that tells you whether to write to or read from the resampler.
 
-    while (numFrames > 0) {
-        if(mResampler->isWriteNeeded()) {
+    int outputFramesLeft = numOutputFrames;
+    while (outputFramesLeft > 0) {
+        if(resampler->isWriteNeeded()) {
             const float *frame = getNextInputFrame(); // you provide this
-            mResampler->writeNextFrame(frame);
+            resampler->writeNextFrame(frame);
         } else {
-            mResampler->readNextFrame(outputBuffer);
+            resampler->readNextFrame(outputBuffer);
             outputBuffer += channelCount;
-            numFrames--;
+            outputFramesLeft--;
         }
     }
+
+## Calling the Resampler with a fixed number of INPUT frames
+
+In this example, suppose we have a fixed number of input frames and a variable number of output frames.
+
+Assume you start with these variables:
+
+    float *inputBuffer;     // multi-channel buffer to be consumed
+    float *outputBuffer;    // multi-channel buffer to be filled
+    int    numInputFrames;  // number of frames of input
+    int    numOutputFrames = 0;
+    int    channelCount;    // 1 for mono, 2 for stereo
+
+    int inputFramesLeft = numInputFrames;
+    while (inputFramesLeft > 0) {
+        if(resampler->isWriteNeeded()) {
+            resampler->writeNextFrame(inputBuffer);
+            inputBuffer += channelCount;
+            inputFramesLeft--;
+        } else {
+            resampler->readNextFrame(outputBuffer);
+            outputBuffer += channelCount;
+            numOutputFrames++;
+        }
+    }
+
+## Deleting the Resampler
+
+When you are done, you should delete the Resampler to avoid a memory leak.
+
+    delete resampler;