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