OboeTester: add glitch test
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp
new file mode 100644
index 0000000..428ce5a
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "FullDuplexAnalyzer.h"
+
+oboe::Result FullDuplexAnalyzer::start() {
+ getLoopbackProcessor()->reset();
+ return FullDuplexStream::start();
+}
+
+oboe::DataCallbackResult FullDuplexAnalyzer::onBothStreamsReady(
+ const void *inputData,
+ int numInputFrames,
+ void *outputData,
+ int numOutputFrames) {
+ // TODO Pull up into superclass
+ // reset analyzer if we miss some input data
+ if (numInputFrames < numOutputFrames) {
+ LOGD("numInputFrames (%4d) < numOutputFrames (%4d) so reset analyzer",
+ numInputFrames, numOutputFrames);
+ getLoopbackProcessor()->reset();
+ } else {
+ float *inputFloat = (float *) inputData;
+ float *outputFloat = (float *) outputData;
+ int32_t outputStride = getOutputStream()->getChannelCount();
+
+ (void) getLoopbackProcessor()->process(inputFloat, getInputStream()->getChannelCount(),
+ outputFloat, outputStride,
+ numOutputFrames);
+
+ // zero out remainder of output array
+ int32_t framesLeft = numOutputFrames - numInputFrames;
+ outputFloat += numOutputFrames * outputStride;
+
+ if (framesLeft > 0) {
+ memset(outputFloat, 0, framesLeft * getOutputStream()->getBytesPerFrame());
+ }
+ }
+ return oboe::DataCallbackResult::Continue;
+};
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h
new file mode 100644
index 0000000..0b7ce9f
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h
@@ -0,0 +1,54 @@
+/*
+ * 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_ANALYZER_H
+#define OBOETESTER_FULL_DUPLEX_ANALYZER_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "oboe/Oboe.h"
+#include "FullDuplexStream.h"
+#include "LatencyAnalyzer.h"
+
+class FullDuplexAnalyzer : public FullDuplexStream {
+public:
+ FullDuplexAnalyzer() {}
+
+ /**
+ * 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;
+
+ bool isDone() {
+ return false;
+ }
+
+ virtual LoopbackProcessor *getLoopbackProcessor() = 0;
+
+};
+
+
+#endif //OBOETESTER_FULL_DUPLEX_ANALYZER_H
+
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.cpp
new file mode 100644
index 0000000..53cfa5b
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.cpp
@@ -0,0 +1,18 @@
+/*
+ * 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 "FullDuplexGlitches.h"
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h
new file mode 100644
index 0000000..38fa057
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.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_GLITCHES_H
+#define OBOETESTER_FULL_DUPLEX_GLITCHES_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "oboe/Oboe.h"
+#include "FullDuplexAnalyzer.h"
+#include "LatencyAnalyzer.h"
+
+class FullDuplexGlitches : public FullDuplexAnalyzer {
+public:
+ FullDuplexGlitches() {}
+
+ bool isDone() {
+ return false;
+ }
+
+ GlitchAnalyzer *getGlitchAnalyzer() {
+ return &mGlitchAnalyzer;
+ }
+
+ LoopbackProcessor *getLoopbackProcessor() override {
+ return (LoopbackProcessor *) &mGlitchAnalyzer;
+ }
+private:
+
+ GlitchAnalyzer mGlitchAnalyzer;
+
+};
+
+
+#endif //OBOETESTER_FULL_DUPLEX_GLITCHES_H
+
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexLatency.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexLatency.cpp
index edeb585..5bd7104 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexLatency.cpp
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexLatency.cpp
@@ -35,10 +35,12 @@
int numOutputFrames) {
// reset analyzer if we miss some input data
if (numInputFrames < numOutputFrames) {
- LOGD("numInputFrames (%4d) < numOutputFrames (%4d) so reset analyzer",
- numInputFrames, numOutputFrames);
+ LOGD("%s() numInputFrames (%4d) < numOutputFrames (%4d) so reset analyzer",
+ __func__, numInputFrames, numOutputFrames);
mEchoAnalyzer.reset();
} else {
+ LOGD("%s() numInputFrames = %4d, numOutputFrames = %4d",
+ __func__, numInputFrames, numOutputFrames);
float *inputFloat = (float *) inputData;
float *outputFloat = (float *) outputData;
int32_t outputStride = getOutputStream()->getChannelCount();
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp
index f3d4c19..6f92cd4 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexStream.cpp
@@ -33,8 +33,8 @@
int32_t totalFramesRead = 0;
do {
oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
- numFrames,
- 0 /* timeout */);
+ numFrames,
+ 0 /* timeout */);
if (!result) {
// Ignore errors because input stream may not be started yet.
break;
@@ -54,8 +54,8 @@
} else if (mCountCallbacksToDiscard > 0) {
// Ignore. Allow the input to reach to equilibrium with the output.
oboe::ResultWithValue<int32_t> result = getInputStream()->read(mInputBuffer.get(),
- numFrames,
- 0 /* timeout */);
+ numFrames,
+ 0 /* timeout */);
if (!result) {
LOGE("%s() read() returned %s\n", __func__, convertToText(result.error()));
callbackResult = oboe::DataCallbackResult::Stop;
@@ -80,6 +80,10 @@
}
}
+ if (callbackResult == oboe::DataCallbackResult::Stop) {
+ getInputStream()->requestStop();
+ }
+
return callbackResult;
}
diff --git a/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h b/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h
index 05f8f45..d64a599 100644
--- a/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h
+++ b/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h
@@ -908,11 +908,39 @@
* Use a cosine transform to measure the predicted magnitude and relative phase of the
* looped back sine wave. Then generate a predicted signal and compare with the actual signal.
*/
-class SineAnalyzer : public LoopbackProcessor {
+class GlitchAnalyzer : public LoopbackProcessor {
public:
+ int32_t getState() {
+ return mState;
+ }
+
+ int32_t getGlitchCount() {
+ return mGlitchCount;
+ }
+
+ double getSignalToNoiseDB() {
+ static const double threshold = 1.0e-10;
+ LOGD(LOOPBACK_RESULT_TAG "mMagnitude = %8f", mMagnitude);
+ LOGD(LOOPBACK_RESULT_TAG "mPeakNoise = %8f", mPeakNoise);
+ if (mMagnitude < threshold || mPeakNoise < threshold) {
+ return 0.0;
+ } else {
+ double amplitudeRatio = mMagnitude / mPeakNoise;
+ double signalToNoise = amplitudeRatio * amplitudeRatio;
+ double signalToNoiseDB = 10.0 * log(signalToNoise);
+ LOGD(LOOPBACK_RESULT_TAG "signalToNoiseDB = %8f", signalToNoiseDB);
+ if (signalToNoiseDB < MIN_SNRATIO_DB) {
+ LOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
+ MIN_SNRATIO_DB);
+ setResult(ERROR_VOLUME_TOO_LOW);
+ }
+ return signalToNoiseDB;
+ }
+ }
+
void analyze() override {
- LOGD("SineAnalyzer ------------------");
+ LOGD("GlitchAnalyzer ------------------");
LOGD(LOOPBACK_RESULT_TAG "peak.amplitude = %8f", mPeakAmplitude);
LOGD(LOOPBACK_RESULT_TAG "sine.magnitude = %8f", mMagnitude);
LOGD(LOOPBACK_RESULT_TAG "peak.noise = %8f", mPeakNoise);
@@ -1051,12 +1079,8 @@
float absDiff = fabs(diff);
mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
if (absDiff > mTolerance) {
- mGlitchCount++;
result = ERROR_GLITCHES;
- //LOGD("%5d: Got a glitch # %d, predicted = %f, actual = %f",
- // mFrameCounter, mGlitchCount, predicted, sample);
- mState = STATE_IMMUNE;
- mDownCounter = mSinePeriod * PERIODS_IMMUNE;
+ addGlitch();
}
// Track incoming signal and slowly adjust magnitude to account
@@ -1072,6 +1096,11 @@
// One pole averaging filter.
mMagnitude = (mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient);
resetAccumulator();
+
+ if (mMagnitude < mThreshold) {
+ result = ERROR_GLITCHES;
+ addGlitch();
+ }
}
} break;
}
@@ -1094,6 +1123,14 @@
return result;
}
+ void addGlitch() {
+ mGlitchCount++;
+// LOGD("%5d: Got a glitch # %d, predicted = %f, actual = %f",
+// mFrameCounter, mGlitchCount, predicted, sample);
+ mState = STATE_IMMUNE;
+ mDownCounter = mSinePeriod * PERIODS_IMMUNE;
+ }
+
void resetAccumulator() {
mFramesAccumulated = 0;
mSinAccumulator = 0.0;
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
index 2db3ffd..2cba670 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
@@ -520,3 +520,24 @@
mFullDuplexLatency->setOutputStream(oboeStream);
}
}
+
+// ======================================================================= ActivityGlitches
+void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
+ ActivityFullDuplex::configureBuilder(isInput, builder);
+
+ if (mFullDuplexGlitches.get() == nullptr) {
+ mFullDuplexGlitches = std::make_unique<FullDuplexGlitches>();
+ }
+ if (!isInput) {
+ // only output uses a callback, input is polled
+ builder.setCallback(mFullDuplexGlitches.get());
+ }
+}
+
+void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
+ if (isInput) {
+ mFullDuplexGlitches->setInputStream(oboeStream);
+ } else {
+ mFullDuplexGlitches->setOutputStream(oboeStream);
+ }
+}
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
index 7983076..24db396 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
@@ -35,6 +35,7 @@
#include "flowgraph/SawtoothOscillator.h"
#include "FullDuplexEcho.h"
+#include "FullDuplexGlitches.h"
#include "FullDuplexLatency.h"
#include "FullDuplexStream.h"
#include "InputStreamCallbackAnalyzer.h"
@@ -335,6 +336,9 @@
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
+ virtual int32_t getState() { return -1; }
+ virtual int32_t getResult() { return -1; }
+ virtual bool isAnalyzerDone() { return false; }
};
/**
@@ -378,7 +382,13 @@
return mFullDuplexLatency->getLatencyAnalyzer();
}
- bool isAnalyzerDone() {
+ int32_t getState() override {
+ return getLatencyAnalyzer()->getState();
+ }
+ int32_t getResult() override {
+ return getLatencyAnalyzer()->getState();
+ }
+ bool isAnalyzerDone() override {
return mFullDuplexLatency->isDone();
}
@@ -390,6 +400,40 @@
};
/**
+ * Measure Glitches
+ */
+class ActivityGlitches : public ActivityFullDuplex {
+public:
+
+ oboe::Result startStreams() override {
+ return mFullDuplexGlitches->start();
+ }
+
+ void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
+
+ GlitchAnalyzer *getGlitchAnalyzer() {
+ LOGD("%s() mFullDuplexGlitches = %p", __func__, mFullDuplexGlitches.get());
+ return mFullDuplexGlitches->getGlitchAnalyzer();
+ }
+
+ int32_t getState() override {
+ return getGlitchAnalyzer()->getState();
+ }
+ int32_t getResult() override {
+ return getGlitchAnalyzer()->getResult();
+ }
+ bool isAnalyzerDone() override {
+ return mFullDuplexGlitches->isDone();
+ }
+
+protected:
+ void finishOpen(bool isInput, oboe::AudioStream *oboeStream) override;
+
+private:
+ std::unique_ptr<FullDuplexGlitches> mFullDuplexGlitches{};
+};
+
+/**
* Switch between various
*/
class NativeAudioContext {
@@ -423,6 +467,9 @@
case ActivityType::RoundTripLatency:
currentActivity = &mActivityRoundTripLatency;
break;
+ case ActivityType::Glitches:
+ currentActivity = &mActivityGlitches;
+ break;
}
}
@@ -436,6 +483,7 @@
ActivityRecording mActivityRecording;
ActivityEcho mActivityEcho;
ActivityRoundTripLatency mActivityRoundTripLatency;
+ ActivityGlitches mActivityGlitches;
private:
@@ -448,6 +496,7 @@
RecordPlay = 3,
Echo = 4,
RoundTripLatency = 5,
+ Glitches = 6,
};
ActivityType mActivityType = ActivityType::Undefined;
diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
index caf7950..ea31462 100644
--- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
+++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
@@ -409,7 +409,7 @@
engine.getCurrentActivity()->setChannelEnabled(channelIndex, enabled);
}
-
+// ==========================================================================
JNIEXPORT void JNICALL
Java_com_google_sample_oboe_manualtest_TestAudioActivity_setActivityType(JNIEnv *env,
jobject instance,
@@ -417,6 +417,7 @@
engine.setActivityType(activityType);
}
+// ==========================================================================
JNIEXPORT void JNICALL
Java_com_google_sample_oboe_manualtest_EchoActivity_setDelayTime(JNIEnv *env,
jobject instance,
@@ -424,24 +425,13 @@
engine.setDelayTime(delayTimeSeconds);
}
+// ==========================================================================
JNIEXPORT jint JNICALL
Java_com_google_sample_oboe_manualtest_RoundTripLatencyActivity_getAnalyzerProgress(JNIEnv *env,
jobject instance) {
return engine.mActivityRoundTripLatency.getLatencyAnalyzer()->getProgress();
}
-JNIEXPORT jint JNICALL
-Java_com_google_sample_oboe_manualtest_RoundTripLatencyActivity_getAnalyzerState(JNIEnv *env,
- jobject instance) {
- return engine.mActivityRoundTripLatency.getLatencyAnalyzer()->getState();
-}
-
-JNIEXPORT jboolean JNICALL
-Java_com_google_sample_oboe_manualtest_RoundTripLatencyActivity_isAnalyzerDone(JNIEnv *env,
- jobject instance) {
- return engine.mActivityRoundTripLatency.isAnalyzerDone();
-}
-
JNIEXPORT jdouble JNICALL
Java_com_google_sample_oboe_manualtest_RoundTripLatencyActivity_getMeasuredLatency(JNIEnv *env,
jobject instance) {
@@ -454,10 +444,41 @@
return engine.mActivityRoundTripLatency.getLatencyAnalyzer()->getMeasuredConfidence();
}
+// ==========================================================================
JNIEXPORT jint JNICALL
-Java_com_google_sample_oboe_manualtest_RoundTripLatencyActivity_getMeasuredResult(JNIEnv *env,
- jobject instance) {
+Java_com_google_sample_oboe_manualtest_AnalyzerActivity_getAnalyzerState(JNIEnv *env,
+ jobject instance) {
+ return ((ActivityFullDuplex *)engine.getCurrentActivity())->getState();
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_google_sample_oboe_manualtest_AnalyzerActivity_isAnalyzerDone(JNIEnv *env,
+ jobject instance) {
+ return ((ActivityFullDuplex *)engine.getCurrentActivity())->isAnalyzerDone();
+}
+
+JNIEXPORT jint JNICALL
+Java_com_google_sample_oboe_manualtest_AnalyzerActivity_getMeasuredResult(JNIEnv *env,
+ jobject instance) {
return engine.mActivityRoundTripLatency.getLatencyAnalyzer()->getResult();
}
+
+// ==========================================================================
+JNIEXPORT jint JNICALL
+Java_com_google_sample_oboe_manualtest_GlitchActivity_getGlitchCount(JNIEnv *env,
+ jobject instance) {
+ LOGD("%s() engine.mActivityGlitches.getGlitchAnalyzer() = %p",
+ __func__, engine.mActivityGlitches.getGlitchAnalyzer());
+ return engine.mActivityGlitches.getGlitchAnalyzer()->getGlitchCount();
}
+
+JNIEXPORT jdouble JNICALL
+Java_com_google_sample_oboe_manualtest_GlitchActivity_getSignalToNoiseDB(JNIEnv *env,
+ jobject instance) {
+ LOGD("%s() engine.mActivityGlitches.getGlitchAnalyzer() = %p",
+ __func__, engine.mActivityGlitches.getGlitchAnalyzer());
+ return engine.mActivityGlitches.getGlitchAnalyzer()->getSignalToNoiseDB();
+}
+
+}
\ No newline at end of file