OboeTester: add glitch test
diff --git a/apps/OboeTester/app/CMakeLists.txt b/apps/OboeTester/app/CMakeLists.txt
index a3accd9..38616c4 100644
--- a/apps/OboeTester/app/CMakeLists.txt
+++ b/apps/OboeTester/app/CMakeLists.txt
@@ -26,6 +26,7 @@
${OBOE_DIR}/src
)
+
### END OBOE INCLUDE SECTION ###
# link to oboe
diff --git a/apps/OboeTester/app/build.gradle b/apps/OboeTester/app/build.gradle
index fbb79e0..572a0f5 100644
--- a/apps/OboeTester/app/build.gradle
+++ b/apps/OboeTester/app/build.gradle
@@ -6,8 +6,8 @@
applicationId = "com.google.sample.oboe.manualtest"
minSdkVersion 26
targetSdkVersion 26
- versionCode 5
- versionName "1.3.01"
+ versionCode 6
+ versionName "1.3.02"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml
index ee3f838..d632de6 100644
--- a/apps/OboeTester/app/src/main/AndroidManifest.xml
+++ b/apps/OboeTester/app/src/main/AndroidManifest.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.sample.oboe.manualtest"
- android:versionCode="5"
- android:versionName="1.3.01">
+ android:versionCode="6"
+ android:versionName="1.3.02">
<!-- versionCode and versionName also have to be updated in build.gradle -->
<uses-feature android:name="android.hardware.microphone" android:required="true" />
@@ -73,6 +73,12 @@
android:screenOrientation="portrait">
</activity>
+ <activity
+ android:name="com.google.sample.oboe.manualtest.GlitchActivity"
+ android:label="@string/title_activity_glitches"
+ android:screenOrientation="portrait">
+ </activity>
+
<service
android:name="com.google.sample.oboe.manualtest.AudioMidiTester"
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
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
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java
new file mode 100644
index 0000000..1dc3f5e
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.google.sample.oboe.manualtest;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to measure latency on a full duplex stream.
+ */
+public class AnalyzerActivity extends TestInputActivity {
+
+ AudioOutputTester mAudioOutTester;
+
+ // Note that these string must match the enum result_code in LatencyAnalyzer.h
+ String resultCodeToString(int resultCode) {
+ switch (resultCode) {
+ case 0:
+ return "OK";
+ case -99:
+ return "ERROR_NOISY";
+ case -98:
+ return "ERROR_VOLUME_TOO_LOW";
+ case -97:
+ return "ERROR_VOLUME_TOO_HIGH";
+ case -96:
+ return "ERROR_CONFIDENCE";
+ case -95:
+ return "ERROR_INVALID_STATE";
+ case -94:
+ return "ERROR_GLITCHES";
+ case -93:
+ return "ERROR_NO_LOCK";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ public native int getAnalyzerState();
+ public native boolean isAnalyzerDone();
+ public native int getMeasuredResult();
+
+}
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/GlitchActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/GlitchActivity.java
new file mode 100644
index 0000000..10bff7a
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/GlitchActivity.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.google.sample.oboe.manualtest;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to measure the number of glitches.
+ */
+public class GlitchActivity extends AnalyzerActivity {
+ private TextView mAnalyzerView;
+ private Button mStartButton;
+ private Button mStopButton;
+
+ // These must match the values in LatencyAnalyzer.h
+ final static int STATE_IDLE = 0;
+ final static int STATE_MEASURE_NOISE =1;
+ final static int STATE_IMMUNE = 2;
+ final static int STATE_WAITING_FOR_SIGNAL = 3;
+ final static int STATE_WAITING_FOR_LOCK = 4;
+ final static int STATE_LOCKED = 5;
+
+
+ // Note that these string must match the enum result_code in LatencyAnalyzer.h
+ String stateToString(int resultCode) {
+ switch (resultCode) {
+ case STATE_IDLE:
+ return "IDLE";
+ case STATE_MEASURE_NOISE:
+ return "MEASURE_NOISE";
+ case STATE_IMMUNE:
+ return "IMMUNE";
+ case STATE_WAITING_FOR_SIGNAL:
+ return "WAITING_FOR_SIGNAL";
+ case STATE_WAITING_FOR_LOCK:
+ return "WAITING_FOR_LOCK";
+ case STATE_LOCKED:
+ return "RUNNING";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ // Periodically query the status of the stream.
+ protected class GlitchSniffer {
+ public static final int SNIFFER_UPDATE_PERIOD_MSEC = 100;
+ public static final int SNIFFER_UPDATE_DELAY_MSEC = 200;
+ long timeStarted = 0;
+ long timeSinceLock = 0;
+ int lastGlitchCount = 0;
+ int previousState = STATE_IDLE;
+ boolean gotLock = false;
+
+ private Handler mHandler = new Handler(Looper.getMainLooper()); // UI thread
+
+ // Display status info for the stream.
+ private Runnable runnableCode = new Runnable() {
+ @Override
+ public void run() {
+ int state = getAnalyzerState();
+ int glitchCount = getGlitchCount();
+ double signalToNoiseDB = getSignalToNoiseDB();
+
+ boolean locked = (state == STATE_LOCKED);
+ if (locked && (previousState != STATE_LOCKED)) {
+ timeSinceLock = System.currentTimeMillis();
+ }
+ previousState = state;
+ gotLock = gotLock || locked;
+ if (glitchCount > lastGlitchCount) {
+ timeSinceLock = System.currentTimeMillis();
+ lastGlitchCount = glitchCount;
+ }
+
+ long now = System.currentTimeMillis();
+ double totalSeconds = (now - timeStarted) / 1000.0;
+
+ StringBuffer message = new StringBuffer();
+ message.append("state = " + state + " = " + stateToString(state) + "\n");
+ message.append(String.format("signal to noise = %5.1f dB\n", signalToNoiseDB));
+ message.append(String.format("Time total = %8.2f seconds\n", totalSeconds));
+ double goodSeconds = (locked && (timeSinceLock > 0))
+ ? ((now - timeSinceLock) / 1000.0)
+ : 0.0;
+ message.append(String.format("Time without glitches = %8.2f seconds\n",
+ goodSeconds));
+ if (gotLock) {
+ message.append("glitchCount = " + glitchCount+ "\n");
+ }
+ setAnalyzerText(message.toString());
+
+ // Reschedule so this task repeats
+ mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
+ }
+ };
+
+ private void startSniffer() {
+ // Start the initial runnable task by posting through the handler
+ mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
+ timeStarted = System.currentTimeMillis();
+ timeSinceLock = 0;
+ lastGlitchCount = 0;
+ gotLock = false;
+ }
+
+ private void stopSniffer() {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(runnableCode);
+ }
+ }
+ }
+
+ private GlitchSniffer mGlitchSniffer = new GlitchSniffer();
+
+ native int getGlitchCount();
+ native double getSignalToNoiseDB();
+
+ private void setAnalyzerText(String s) {
+ mAnalyzerView.setText(s);
+ }
+
+ @Override
+ protected void inflateActivity() {
+ setContentView(R.layout.activity_glitches);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mStartButton = (Button) findViewById(R.id.button_start);
+ mStopButton = (Button) findViewById(R.id.button_stop);
+ mStopButton.setEnabled(false);
+ mAnalyzerView = (TextView) findViewById(R.id.text_analyzer_result);
+ updateEnabledWidgets();
+ mAudioOutTester = addAudioOutputTester();
+
+ hideSettingsViews();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ setActivityType(ACTIVITY_GLITCHES);
+ }
+
+ @Override
+ protected void onStop() {
+ mGlitchSniffer.stopSniffer();
+ super.onStop();
+ }
+
+ public void onStartAudioTest(View view) {
+ openAudio();
+ startAudio();
+ mStartButton.setEnabled(false);
+ mStopButton.setEnabled(true);
+ mGlitchSniffer.startSniffer();
+ }
+
+ public void onCancel(View view) {
+ stopAudioTest();
+ }
+
+ // Call on UI thread
+ public void onStopAudioTest(View view) {
+ stopAudioTest();
+ }
+
+ public void stopAudioTest() {
+ mGlitchSniffer.stopSniffer();
+ mStartButton.setEnabled(true);
+ mStopButton.setEnabled(false);
+ stopAudio();
+ closeAudio();
+ }
+
+ @Override
+ boolean isOutput() {
+ return false;
+ }
+
+ @Override
+ public void setupEffects(int sessionId) {
+ }
+}
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
index 6b7f0cb..5528b93 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
@@ -125,6 +125,12 @@
startActivity(intent);
}
+ public void onLaunchGlitchTest(View view) {
+ updateCallbackSize();
+ Intent intent = new Intent(this, GlitchActivity.class);
+ startActivity(intent);
+ }
+
public void onUseCallbackClicked(View view) {
CheckBox checkBox = (CheckBox) view;
OboeAudioStream.setUseCallback(checkBox.isChecked());
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
index a66a8bd..cba159b 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
@@ -27,39 +27,14 @@
/**
* Activity to measure latency on a full duplex stream.
*/
-public class RoundTripLatencyActivity extends TestInputActivity {
+public class RoundTripLatencyActivity extends AnalyzerActivity {
private static final int STATE_GOT_DATA = 5; // Defined in LatencyAnalyzer.h
-
- AudioOutputTester mAudioOutTester;
+
private TextView mAnalyzerView;
private Button mMeasureButton;
private Button mCancelButton;
- // Note that these string must match the enum result_code in LatencyAnalyzer.h
- String resultCodeToString(int resultCode) {
- switch (resultCode) {
- case 0:
- return "OK";
- case -99:
- return "ERROR_NOISY";
- case -98:
- return "ERROR_VOLUME_TOO_LOW";
- case -97:
- return "ERROR_VOLUME_TOO_HIGH";
- case -96:
- return "ERROR_CONFIDENCE";
- case -95:
- return "ERROR_INVALID_STATE";
- case -94:
- return "ERROR_GLITCHES";
- case -93:
- return "ERROR_NO_LOCK";
- default:
- return "UNKNOWN";
- }
- }
-
// Periodically query the status of the stream.
protected class LatencySniffer {
public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150;
@@ -122,10 +97,7 @@
private LatencySniffer mLatencySniffer = new LatencySniffer();
- native int getAnalyzerState();
native int getAnalyzerProgress();
- native boolean isAnalyzerDone();
- native int getMeasuredResult();
native double getMeasuredLatency();
native double getMeasuredConfidence();
@@ -146,6 +118,8 @@
mAnalyzerView = (TextView) findViewById(R.id.text_analyzer_result);
updateEnabledWidgets();
mAudioOutTester = addAudioOutputTester();
+
+ hideSettingsViews();
}
@Override
@@ -175,10 +149,10 @@
// Call on UI thread
public void stopAudioTest() {
mLatencySniffer.stopSniffer();
- stopAudio();
- closeAudio();
mMeasureButton.setEnabled(true);
mCancelButton.setEnabled(false);
+ stopAudio();
+ closeAudio();
}
@Override
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
index 5d108c6..d4cc863 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
@@ -72,15 +72,23 @@
private View.OnClickListener mToggleListener = new View.OnClickListener() {
public void onClick(View v) {
if (mOptionTable.isShown()) {
- mOptionTable.setVisibility(View.GONE);
- mOptionExpander.setText(mShowSettingsText);
+ hideSettingsView();
} else {
- mOptionTable.setVisibility(View.VISIBLE);
- mOptionExpander.setText(mHideSettingsText);
+ showSettingsView();
}
}
};
+ public void showSettingsView() {
+ mOptionTable.setVisibility(View.VISIBLE);
+ mOptionExpander.setText(mHideSettingsText);
+ }
+
+ public void hideSettingsView() {
+ mOptionTable.setVisibility(View.GONE);
+ mOptionExpander.setText(mShowSettingsText);
+ }
+
public StreamConfigurationView(Context context) {
super(context);
initializeViews(context);
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
index 119ad1c..16dc642 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
@@ -54,6 +54,7 @@
public static final int ACTIVITY_RECORD_PLAY = 3;
public static final int ACTIVITY_ECHO = 4;
public static final int ACTIVITY_RT_LATENCY = 5;
+ public static final int ACTIVITY_GLITCHES = 6;
private int mState = STATE_CLOSED;
protected String audioManagerSampleRate;
@@ -130,6 +131,12 @@
findAudioCommon();
}
+ public void hideSettingsViews() {
+ for (StreamContext streamContext : mStreamContexts) {
+ streamContext.configurationView.hideSettingsView();
+ }
+ }
+
@Override
protected void onStop() {
Log.i(TAG, "onStop() called so stopping audio =========================");
diff --git a/apps/OboeTester/app/src/main/res/layout/activity_glitches.xml b/apps/OboeTester/app/src/main/res/layout/activity_glitches.xml
new file mode 100644
index 0000000..4b15153
--- /dev/null
+++ b/apps/OboeTester/app/src/main/res/layout/activity_glitches.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="com.google.sample.oboe.manualtest.GlitchActivity">
+
+ <com.google.sample.oboe.manualtest.StreamConfigurationView
+ android:id="@+id/inputStreamConfiguration"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <com.google.sample.oboe.manualtest.StreamConfigurationView
+ android:id="@+id/outputStreamConfiguration"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button_start"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:onClick="onStartAudioTest"
+ android:text="@string/startAudio" />
+
+ <Button
+ android:id="@+id/button_stop"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:onClick="onStopAudioTest"
+ android:text="@string/stopAudio" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/text_analyzer_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lines="8"
+ android:text="@string/use_loopback"
+ android:textSize="18sp"
+ android:textStyle="bold" />
+
+</LinearLayout>
diff --git a/apps/OboeTester/app/src/main/res/layout/activity_main.xml b/apps/OboeTester/app/src/main/res/layout/activity_main.xml
index fcd2864..a5392a8 100644
--- a/apps/OboeTester/app/src/main/res/layout/activity_main.xml
+++ b/apps/OboeTester/app/src/main/res/layout/activity_main.xml
@@ -58,6 +58,13 @@
android:onClick="onLaunchRoundTripLatency"
android:text="Round Trip Latency" />
+ <Button
+ android:id="@+id/button_glitches"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:onClick="onLaunchGlitchTest"
+ android:text="Test Glitches" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/apps/OboeTester/app/src/main/res/values/strings.xml b/apps/OboeTester/app/src/main/res/values/strings.xml
index 9f5929f..f415920 100644
--- a/apps/OboeTester/app/src/main/res/values/strings.xml
+++ b/apps/OboeTester/app/src/main/res/values/strings.xml
@@ -19,7 +19,7 @@
<string name="auto_select">Auto select</string>
<string name="hint_hide_settings">Hide Settings</string>
<string name="hint_show_settings">Show Settings</string>
- <string name="use_loopback">Hide Settings to make more room.\nRequires a loopback adapter!</string>
+ <string name="use_loopback">Click Show Settings to configure stream.\nRequires a loopback adapter!</string>
<string name="output_prompt">Choose an Output Mode</string>
<string-array name="output_modes">
@@ -98,6 +98,7 @@
<string name="title_activity_recorder">Recorder</string>
<string name="title_activity_echo">Echo Input to Output</string>
<string name="title_activity_rt_latency">Round Trip Latency</string>
+ <string name="title_activity_glitches">Test Glitches</string>
<string name="need_record_audio_permission">"This app needs RECORD_AUDIO permission"</string>