Merge branch 'master' of github.com:google/oboe into 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/src/main/cpp/DrumPlayerJNI.cpp b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
index 232ae22..6fb7425 100644
--- a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
+++ b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
@@ -113,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/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
index a79f374..2d6edbf 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
@@ -86,18 +86,31 @@
 
     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;
     }
 
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;