Liveeffect: fix crash on exit

Use std::shared_ptr in FullDuplexStream.
Explicitly process the samples in the callback instead
of just using memcpy. This will be easier to modify.

Fixes #1113
Fixes #852
diff --git a/samples/LiveEffect/src/main/cpp/FullDuplexPass.h b/samples/LiveEffect/src/main/cpp/FullDuplexPass.h
index 11bdd45..11bdc65 100644
--- a/samples/LiveEffect/src/main/cpp/FullDuplexPass.h
+++ b/samples/LiveEffect/src/main/cpp/FullDuplexPass.h
@@ -16,19 +16,36 @@
 
 #ifndef SAMPLES_FULLDUPLEXPASS_H
 #define SAMPLES_FULLDUPLEXPASS_H
+
 #include "FullDuplexStream.h"
 
 class FullDuplexPass : public FullDuplexStream {
 public:
     virtual oboe::DataCallbackResult
-    onBothStreamsReady(const void *inputData, int numInputFrames, void *outputData,
-                       int numOutputFrames) {
-        size_t bytesPerFrame = this->getOutputStream()->getBytesPerFrame();
-        size_t bytesToWrite = numInputFrames * bytesPerFrame;
-        size_t byteDiff = (numOutputFrames - numInputFrames) * bytesPerFrame;
-        size_t bytesToZero = (byteDiff > 0) ? byteDiff : 0;
-        memcpy(outputData, inputData, bytesToWrite);
-        memset((u_char*) outputData + bytesToWrite, 0, bytesToZero);
+    onBothStreamsReady(
+            std::shared_ptr<oboe::AudioStream> inputStream,
+            const void *inputData,
+            int   numInputFrames,
+            std::shared_ptr<oboe::AudioStream> outputStream,
+            void *outputData,
+            int   numOutputFrames) {
+        // Copy the input samples to the output with a little arbitrary gain change.
+        // This code assumes the data format for both streams is Float.
+        const float *inputFloats = static_cast<const float *>(inputData);
+        float *outputFloats = static_cast<float *>(outputData);
+        // It also assume the channel count for each stream is the same.
+        int32_t samplesPerFrame = outputStream->getChannelCount();
+        int32_t numInputSamples = numInputFrames * samplesPerFrame;
+        int32_t numOutputSamples = numInputFrames * samplesPerFrame;
+        // It is possible that there may be fewer input than output samples.
+        int32_t samplesToProcess = std::min(numInputSamples, numOutputSamples);
+        for (int32_t i = 0; i < samplesToProcess; i++) {
+            *outputFloats++ = *inputFloats++ * 0.95; // do some arbitrary processing
+        }
+        int32_t samplesLeft = numOutputSamples - numInputSamples;
+        for (int32_t i = 0; i < samplesLeft; i++) {
+            *outputFloats++ = 0.0; // silence
+        }
         return oboe::DataCallbackResult::Continue;
     }
 };
diff --git a/samples/LiveEffect/src/main/cpp/FullDuplexStream.cpp b/samples/LiveEffect/src/main/cpp/FullDuplexStream.cpp
index 5b2fd41..e38bb5f 100644
--- a/samples/LiveEffect/src/main/cpp/FullDuplexStream.cpp
+++ b/samples/LiveEffect/src/main/cpp/FullDuplexStream.cpp
@@ -31,7 +31,7 @@
         // Drain the input.
         int32_t totalFramesRead = 0;
         do {
-            oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
+            oboe::ResultWithValue<int32_t> result = mInputStream->read(mInputBuffer.get(),
                                                                            numFrames,
                                                                            0 /* timeout */);
             if (!result) {
@@ -52,7 +52,7 @@
 
     } else if (mCountCallbacksToDiscard > 0) {
         // Ignore. Allow the input to reach to equilibrium with the output.
-        oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
+        oboe::ResultWithValue<int32_t> result = mInputStream->read(mInputBuffer.get(),
                                                                        numFrames,
                                                                        0 /* timeout */);
         if (!result) {
@@ -62,7 +62,7 @@
 
     } else {
         // Read data into input buffer.
-        oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
+        oboe::ResultWithValue<int32_t> result = mInputStream->read(mInputBuffer.get(),
                                                                        numFrames,
                                                                        0 /* timeout */);
         if (!result) {
@@ -71,14 +71,14 @@
             int32_t framesRead = result.value();
 
             callbackResult = onBothStreamsReady(
-                    mInputBuffer.get(), framesRead,
-                    audioData, numFrames
+                    mInputStream, mInputBuffer.get(), framesRead,
+                    mOutputStream, audioData, numFrames
             );
         }
     }
 
     if (callbackResult == oboe::DataCallbackResult::Stop) {
-        getInputStream()->requestStop();
+        mInputStream->requestStop();
     }
 
     return callbackResult;
@@ -90,27 +90,27 @@
     mCountCallbacksToDiscard = kNumCallbacksToDiscard;
 
     // Determine maximum size that could possibly be called.
-    int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames()
-            * getOutputStream()->getChannelCount();
+    int32_t bufferSize = mOutputStream->getBufferCapacityInFrames()
+            * mOutputStream->getChannelCount();
     if (bufferSize > mBufferSize) {
         mInputBuffer = std::make_unique<float[]>(bufferSize);
         mBufferSize = bufferSize;
     }
-    oboe::Result result = getInputStream()->requestStart();
+    oboe::Result result = mInputStream->requestStart();
     if (result != oboe::Result::OK) {
         return result;
     }
-    return getOutputStream()->requestStart();
+    return mOutputStream->requestStart();
 }
 
 oboe::Result FullDuplexStream::stop() {
     oboe::Result outputResult = oboe::Result::OK;
     oboe::Result inputResult = oboe::Result::OK;
-    if (getOutputStream()) {
-        outputResult = getOutputStream()->requestStop();
+    if (mOutputStream) {
+        outputResult = mOutputStream->requestStop();
     }
-    if (getInputStream()) {
-        inputResult = getInputStream()->requestStop();
+    if (mInputStream) {
+        inputResult = mInputStream->requestStop();
     }
     if (outputResult != oboe::Result::OK) {
         return outputResult;
diff --git a/samples/LiveEffect/src/main/cpp/FullDuplexStream.h b/samples/LiveEffect/src/main/cpp/FullDuplexStream.h
index 90fa63b..c30bc04 100644
--- a/samples/LiveEffect/src/main/cpp/FullDuplexStream.h
+++ b/samples/LiveEffect/src/main/cpp/FullDuplexStream.h
@@ -27,20 +27,13 @@
     FullDuplexStream() {}
     virtual ~FullDuplexStream() = default;
 
-    void setInputStream(oboe::AudioStream *stream) {
+    void setInputStream(std::shared_ptr<oboe::AudioStream> stream) {
         mInputStream = stream;
     }
 
-    oboe::AudioStream *getInputStream() {
-        return mInputStream;
-    }
-
-    void setOutputStream(oboe::AudioStream *stream) {
+    void setOutputStream(std::shared_ptr<oboe::AudioStream> stream) {
         mOutputStream = stream;
     }
-    oboe::AudioStream *getOutputStream() {
-        return mOutputStream;
-    }
 
     virtual oboe::Result start();
 
@@ -51,8 +44,10 @@
      * App should override this method.
      */
     virtual oboe::DataCallbackResult onBothStreamsReady(
+            std::shared_ptr<oboe::AudioStream> inputStream,
             const void *inputData,
             int   numInputFrames,
+            std::shared_ptr<oboe::AudioStream> outputStream,
             void *outputData,
             int   numOutputFrames
             ) = 0;
@@ -96,8 +91,8 @@
     // Discard some callbacks so the input and output reach equilibrium.
     int32_t              mCountCallbacksToDiscard = kNumCallbacksToDiscard;
 
-    oboe::AudioStream   *mInputStream = nullptr;
-    oboe::AudioStream   *mOutputStream = nullptr;
+    std::shared_ptr<oboe::AudioStream> mInputStream;
+    std::shared_ptr<oboe::AudioStream> mOutputStream;
 
     int32_t              mBufferSize = 0;
     std::unique_ptr<float[]> mInputBuffer;
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
index c2a46c8..e9075db 100644
--- a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
@@ -23,13 +23,6 @@
     assert(mOutputChannelCount == mInputChannelCount);
 }
 
-LiveEffectEngine::~LiveEffectEngine() {
-
-    mFullDuplexPass.stop();
-    closeStream(mPlayStream);
-    closeStream(mRecordingStream);
-}
-
 void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
     mRecordingDeviceId = deviceId;
 }
@@ -61,22 +54,29 @@
             }
         } else {
             mFullDuplexPass.stop();
-            /*
-            * Note: The order of events is important here.
-            * The playback stream must be closed before the recording stream. If the
-            * recording stream were to be closed first the playback stream's
-            * callback may attempt to read from the recording stream
-            * which would cause the app to crash since the recording stream would be
-            * null.
-            */
-            closeStream(mPlayStream);
-            closeStream(mRecordingStream);
+            closeStreams();
             mIsEffectOn = isOn;
        }
     }
     return success;
 }
 
+void LiveEffectEngine::closeStreams() {
+    /*
+    * Note: The order of events is important here.
+    * The playback stream must be closed before the recording stream. If the
+    * recording stream were to be closed first the playback stream's
+    * callback may attempt to read from the recording stream
+    * which would cause the app to crash since the recording stream would be
+    * null.
+    */
+    closeStream(mPlayStream);
+    mFullDuplexPass.setOutputStream(nullptr);
+
+    closeStream(mRecordingStream);
+    mFullDuplexPass.setInputStream(nullptr);
+}
+
 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
@@ -89,11 +89,12 @@
         mSampleRate = oboe::kUnspecified;
         return result;
     } else {
+        // The input stream needs to run at the same sample rate as the output.
         mSampleRate = mPlayStream->getSampleRate();
     }
     warnIfNotLowLatency(mPlayStream);
 
-    setupRecordingStreamParameters(&inBuilder);
+    setupRecordingStreamParameters(&inBuilder, mSampleRate);
     result = inBuilder.openStream(mRecordingStream);
     if (result != oboe::Result::OK) {
         closeStream(mPlayStream);
@@ -101,8 +102,8 @@
     }
     warnIfNotLowLatency(mRecordingStream);
 
-    mFullDuplexPass.setInputStream(mRecordingStream.get());
-    mFullDuplexPass.setOutputStream(mPlayStream.get());
+    mFullDuplexPass.setInputStream(mRecordingStream);
+    mFullDuplexPass.setOutputStream(mPlayStream);
     return result;
 }
 
@@ -114,11 +115,11 @@
  * @param builder The recording stream builder
  */
 oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters(
-    oboe::AudioStreamBuilder *builder) {
+    oboe::AudioStreamBuilder *builder, int32_t sampleRate) {
     // This sample uses blocking read() because we don't specify a callback
     builder->setDeviceId(mRecordingDeviceId)
         ->setDirection(oboe::Direction::Input)
-        ->setSampleRate(mSampleRate)
+        ->setSampleRate(sampleRate)
         ->setChannelCount(mInputChannelCount);
     return setupCommonStreamParameters(builder);
 }
@@ -158,7 +159,6 @@
     return builder;
 }
 
-
 /**
  * Close the stream. AudioStream::close() is a blocking call so
  * the application does not need to add synchronization between
@@ -182,7 +182,6 @@
     }
 }
 
-
 /**
  * Warn in logcat if non-low latency stream is created
  * @param stream: newly created stream
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
index f79d596..c12cbdb 100644
--- a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
@@ -24,11 +24,12 @@
 #include "FullDuplexPass.h"
 
 class LiveEffectEngine : public oboe::AudioStreamCallback {
-   public:
+public:
     LiveEffectEngine();
-    ~LiveEffectEngine();
+
     void setRecordingDeviceId(int32_t deviceId);
     void setPlaybackDeviceId(int32_t deviceId);
+
     /**
      * @param isOn
      * @return true if it succeeds
@@ -50,28 +51,30 @@
     bool setAudioApi(oboe::AudioApi);
     bool isAAudioSupported(void);
 
-   private:
-    FullDuplexPass mFullDuplexPass;
-    bool mIsEffectOn = false;
-    int32_t mRecordingDeviceId = oboe::kUnspecified;
-    int32_t mPlaybackDeviceId = oboe::kUnspecified;
-    oboe::AudioFormat mFormat = oboe::AudioFormat::I16;
-    int32_t mSampleRate = oboe::kUnspecified;
-    int32_t mInputChannelCount = oboe::ChannelCount::Stereo;
-    int32_t mOutputChannelCount = oboe::ChannelCount::Stereo;
+private:
+    FullDuplexPass    mFullDuplexPass;
+    bool              mIsEffectOn = false;
+    int32_t           mRecordingDeviceId = oboe::kUnspecified;
+    int32_t           mPlaybackDeviceId = oboe::kUnspecified;
+    const oboe::AudioFormat mFormat = oboe::AudioFormat::Float; // for easier processing
+    oboe::AudioApi    mAudioApi = oboe::AudioApi::AAudio;
+    int32_t           mSampleRate = oboe::kUnspecified;
+    const int32_t     mInputChannelCount = oboe::ChannelCount::Stereo;
+    const int32_t     mOutputChannelCount = oboe::ChannelCount::Stereo;
 
     std::shared_ptr<oboe::AudioStream> mRecordingStream;
     std::shared_ptr<oboe::AudioStream> mPlayStream;
 
-    oboe::AudioApi mAudioApi = oboe::AudioApi::AAudio;
-
     oboe::Result openStreams();
+
+    void closeStreams();
+
     void closeStream(std::shared_ptr<oboe::AudioStream> &stream);
 
     oboe::AudioStreamBuilder *setupCommonStreamParameters(
         oboe::AudioStreamBuilder *builder);
     oboe::AudioStreamBuilder *setupRecordingStreamParameters(
-        oboe::AudioStreamBuilder *builder);
+        oboe::AudioStreamBuilder *builder, int32_t sampleRate);
     oboe::AudioStreamBuilder *setupPlaybackStreamParameters(
         oboe::AudioStreamBuilder *builder);
     void warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream);
diff --git a/samples/LiveEffect/src/main/cpp/jni_bridge.cpp b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
index aa0abd0..eb70e6d 100644
--- a/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
+++ b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
@@ -38,8 +38,11 @@
 JNIEXPORT void JNICALL
 Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_delete(JNIEnv *env,
                                                                jclass) {
-    delete engine;
-    engine = nullptr;
+    if (engine) {
+        engine->setEffectOn(false);
+        delete engine;
+        engine = nullptr;
+    }
 }
 
 JNIEXPORT jboolean JNICALL