OboeTester: add FullDuplexStream and FullDuplexEcho
diff --git a/apps/OboeTester/app/CMakeLists.txt b/apps/OboeTester/app/CMakeLists.txt
index 2390007..2bca76c 100644
--- a/apps/OboeTester/app/CMakeLists.txt
+++ b/apps/OboeTester/app/CMakeLists.txt
@@ -28,6 +28,7 @@
### END OBOE INCLUDE SECTION ###
+
# link to oboe
target_link_libraries(oboetester log oboe atomic)
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp
new file mode 100644
index 0000000..3f37d8a
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 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 "common/OboeDebug.h"
+#include "FullDuplexEcho.h"
+
+oboe::Result FullDuplexEcho::start() {
+ mDelaySizeFrames = 3 * getOutputStream()->getSampleRate();
+ static const int32_t maxDelaySamples = mDelaySizeFrames * getOutputStream()->getChannelCount();
+ mDelayLine = std::make_unique<float[]>(maxDelaySamples);
+ return FullDuplexStream::start();
+}
+
+oboe::DataCallbackResult FullDuplexEcho::onBothStreamsReady(
+ const void *inputData,
+ int numInputFrames,
+ void *outputData,
+ int numOutputFrames) {
+ // FIXME only handles matching stream formats.
+// LOGE("FullDuplexEcho::%s() called, numInputFrames = %d, numOutputFrames = %d",
+// __func__, numInputFrames, numOutputFrames);
+ int32_t framesLeft = std::min(numInputFrames, numOutputFrames);
+ while (framesLeft > 0) {
+ float *delayAddress = mDelayLine.get() + (mCursorFrames * getOutputStream()->getChannelCount());
+ memcpy(outputData, delayAddress, getOutputStream()->getBytesPerFrame());
+ memcpy(delayAddress, inputData, getOutputStream()->getBytesPerFrame());
+ mCursorFrames++;
+ if (mCursorFrames >= mDelaySizeFrames) {
+ mCursorFrames = 0;
+ }
+ inputData = ((uint8_t *)inputData) + getOutputStream()->getBytesPerFrame();
+ outputData = ((uint8_t *)outputData) + getOutputStream()->getBytesPerFrame();
+ framesLeft--;
+ }
+ return oboe::DataCallbackResult::Continue;
+};
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h
new file mode 100644
index 0000000..668bf42
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 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 OBOETESTER_FULL_DUPLEX_ECHO_H
+#define OBOETESTER_FULL_DUPLEX_ECHO_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "oboe/Oboe.h"
+#include "FullDuplexStream.h"
+
+class FullDuplexEcho : public FullDuplexStream {
+public:
+ FullDuplexEcho() {}
+
+ /**
+ * Called when data is available on both streams.
+ * Caller should override this method.
+ */
+ oboe::DataCallbackResult onBothStreamsReady(
+ const void *inputData,
+ int numInputFrames,
+ void *outputData,
+ int numOutputFrames
+ ) override;
+
+ oboe::Result start() override;
+
+private:
+ std::unique_ptr<float[]> mDelayLine;
+ int32_t mCursorFrames = 0;
+ int32_t mDelaySizeFrames = 0;
+};
+
+
+#endif //OBOETESTER_FULL_DUPLEX_ECHO_H
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp
new file mode 100644
index 0000000..4d31226
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 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 "common/OboeDebug.h"
+#include "FullDuplexStream.h"
+
+oboe::DataCallbackResult FullDuplexStream::onAudioReady(
+ oboe::AudioStream *outputStream,
+ void *audioData,
+ int numFrames) {
+ oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
+
+// LOGE("FullDuplexStream::%s() called, numFrames = %d, buf = %p", __func__, numFrames, mInputBuffer.get());
+ // Process in multiple bursts to fit in the buffer.
+ int32_t framesLeft = numFrames;
+ while(framesLeft > 0) {
+ // Read data into input buffer.
+ int32_t framesToRead = std::min(framesLeft, outputStream->getFramesPerBurst());
+ oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
+ framesToRead,
+ 0 /* timeout */);
+
+ if (!result) {
+ LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
+ break;
+ }
+ int32_t framesRead = result.value();
+
+ callbackResult = onBothStreamsReady(
+ mInputBuffer.get(), framesRead,
+ audioData, framesToRead
+ );
+ if (callbackResult != oboe::DataCallbackResult::Continue) {
+ break;
+ }
+
+ framesLeft -= framesToRead;
+ audioData = ((uint8_t *)audioData) +
+ (framesToRead * outputStream->getChannelCount() * outputStream->getBytesPerSample());
+ }
+
+ return callbackResult;
+}
+
+oboe::Result FullDuplexStream::start() {
+ int32_t bufferSize = getOutputStream()->getFramesPerBurst()
+ * getOutputStream()->getChannelCount();
+ if (bufferSize > mBufferSize) {
+ LOGE("FullDuplexStream::%s() allocating bufferSize = %d", __func__, bufferSize);
+ mInputBuffer = std::make_unique<float[]>(bufferSize);
+ mBufferSize = bufferSize;
+ }
+ getInputStream()->requestStart();
+ return getOutputStream()->requestStart();
+}
+
+oboe::Result FullDuplexStream::stop() {
+ getOutputStream()->requestStop(); // TODO result?
+ return getInputStream()->requestStop();
+}
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h
new file mode 100644
index 0000000..d892016
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 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 OBOETESTER_FULL_DUPLEX_STREAM_H
+#define OBOETESTER_FULL_DUPLEX_STREAM_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "oboe/Oboe.h"
+
+class FullDuplexStream : public oboe::AudioStreamCallback {
+public:
+ FullDuplexStream() {}
+ virtual ~FullDuplexStream() = default;
+
+ void setInputStream(oboe::AudioStream *stream) {
+ mInputStream = stream;
+ }
+
+ oboe::AudioStream *getInputStream() {
+ return mInputStream;
+ }
+
+ void setOutputStream(oboe::AudioStream *stream) {
+ mOutputStream = stream;
+ }
+ oboe::AudioStream *getOutputStream() {
+ return mOutputStream;
+ }
+
+ virtual oboe::Result start();
+ virtual oboe::Result stop();
+
+ /**
+ * Called when data is available on both streams.
+ * Caller should override this method.
+ */
+ virtual oboe::DataCallbackResult onBothStreamsReady(
+ const void *inputData,
+ int numInputFrames,
+ void *outputData,
+ int numOutputFrames
+ ) = 0;
+
+ /**
+ * Called by Oboe when the stream is ready to process audio.
+ */
+ oboe::DataCallbackResult onAudioReady(
+ oboe::AudioStream *audioStream,
+ void *audioData,
+ int numFrames) override;
+
+private:
+ oboe::AudioStream *mInputStream = nullptr;
+ oboe::AudioStream *mOutputStream = nullptr;
+ std::unique_ptr<float[]> mInputBuffer;
+ int32_t mBufferSize = 0;
+};
+
+
+#endif //OBOETESTER_FULL_DUPLEX_STREAM_H
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
index a9e3501..de1124c 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
@@ -189,12 +189,22 @@
->setSampleRate(sampleRate)
->setFormat((oboe::AudioFormat) format);
- // We needed the proxy because we did not know the channelCount when we setup the Builder.
- if (useCallback) {
- LOGD("NativeAudioContext::open() set callback to use oboeCallbackProxy, size = %d",
- callbackSize);
- builder.setCallback(&oboeCallbackProxy);
- builder.setFramesPerCallback(callbackSize);
+ if (mActivityType == ActivityType::Echo) {
+ if (mFullDuplexEcho.get() == nullptr) {
+ mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
+ }
+ // only output uses a callback, input is polled
+ if (!isInput) {
+ builder.setCallback(mFullDuplexEcho.get());
+ }
+ } else {
+ // We needed the proxy because we did not know the channelCount when we setup the Builder.
+ if (useCallback) {
+ LOGD("NativeAudioContext::open() set callback to use oboeCallbackProxy, size = %d",
+ callbackSize);
+ builder.setCallback(&oboeCallbackProxy);
+ builder.setFramesPerCallback(callbackSize);
+ }
}
if (audioApi == oboe::AudioApi::OpenSLES) {
@@ -217,6 +227,14 @@
mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
mFramesPerBurst = oboeStream->getFramesPerBurst();
mSampleRate = oboeStream->getSampleRate();
+
+ if (mActivityType == ActivityType::Echo) {
+ if (isInput) {
+ mFullDuplexEcho->setInputStream(oboeStream);
+ } else {
+ mFullDuplexEcho->setOutputStream(oboeStream);
+ }
+ }
}
return ((int)result < 0) ? (int)result : streamIndex;
@@ -377,10 +395,7 @@
break;
case ActivityType::Echo:
- inputStream = getInputStream();
- outputStream = getOutputStream();
- result = inputStream->requestStart(); // FIXME use full duplex object
- result = outputStream->requestStart();
+ result = mFullDuplexEcho->start();
break;
}
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
index 718ae1c..6cec33b 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
@@ -33,6 +33,9 @@
#include "flowgraph/SinkI16.h"
#include "flowgraph/SineOscillator.h"
#include "flowgraph/SawtoothOscillator.h"
+
+#include "FullDuplexEcho.h"
+#include "FullDuplexStream.h"
#include "InputStreamCallbackAnalyzer.h"
#include "MultiChannelRecording.h"
#include "OboeStreamCallbackProxy.h"
@@ -310,6 +313,7 @@
std::shared_ptr<flowgraph::SinkI16> mSinkI16;
std::unique_ptr<AudioStreamGateway> audioStreamGateway{};
std::unique_ptr<MultiChannelRecording> mRecording{};
+ std::unique_ptr<FullDuplexEcho> mFullDuplexEcho{};
PlayRecordingCallback mPlayRecordingCallback;
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp
index 8148195..c35547a 100644
--- a/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp
@@ -23,8 +23,10 @@
/***************************************************************************/
int32_t AudioProcessorBase::pullData(int64_t framePosition, int32_t numFrames) {
int32_t frameCount = numFrames;
+ // Prevent recursion and multiple execution of nodes.
if (framePosition > mLastFramePosition) {
mLastFramePosition = framePosition;
+ // Pull from all the upstream nodes.
for (auto &port : mInputPorts) {
frameCount = port.get().pullData(framePosition, frameCount);
}