Merge "Implement mean and mean absolute deviation over 5 latency tests." into sc-dev
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
index 8a1c63f..bde5c6e 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
@@ -202,6 +202,10 @@
return mPulseLatencyAnalyzer.getMeasuredConfidence();
}
+bool NativeAudioAnalyzer::isLowLatencyStream() {
+ return mIsLowLatencyStream;
+}
+
int NativeAudioAnalyzer::getSampleRate() {
return mOutputSampleRate;
}
@@ -235,6 +239,10 @@
return result;
}
+ // Did we get a low-latency stream?
+ mIsLowLatencyStream =
+ AAudioStream_getPerformanceMode(mOutputStream) == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+
int32_t outputFramesPerBurst = AAudioStream_getFramesPerBurst(mOutputStream);
(void) AAudioStream_setBufferSizeInFrames(mOutputStream, outputFramesPerBurst * kDefaultOutputSizeBursts);
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
index 3367226..080fe4c 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
@@ -92,6 +92,11 @@
*/
double getConfidence();
+ /**
+ * Returns true if the stream was successfully opened in low-latency mode.
+ */
+ bool isLowLatencyStream();
+
aaudio_result_t getError() {
return mInputError ? mInputError : mOutputError;
}
@@ -131,6 +136,7 @@
int32_t mFramesReadTotal = 0;
int32_t mFramesWrittenTotal = 0;
bool mIsDone = false;
+ bool mIsLowLatencyStream = false;
static constexpr int kLogPeriodMillis = 1000;
static constexpr int kNumInputChannels = 1;
diff --git a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
index 58908bd..6cc3be9 100644
--- a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
@@ -77,6 +77,15 @@
return false;
}
+JNIEXPORT jboolean JNICALL Java_com_android_cts_verifier_audio_NativeAnalyzerThread_isLowlatency
+ (JNIEnv *env __unused, jobject obj __unused, jlong pAnalyzer) {
+ NativeAudioAnalyzer * analyzer = (NativeAudioAnalyzer *) pAnalyzer;
+ if (analyzer != nullptr) {
+ return analyzer->isLowLatencyStream();
+ }
+ return false;
+}
+
JNIEXPORT jint JNICALL Java_com_android_cts_verifier_audio_NativeAnalyzerThread_getError
(JNIEnv *env __unused, jobject obj __unused, jlong pAnalyzer) {
NativeAudioAnalyzer * analyzer = (NativeAudioAnalyzer *) pAnalyzer;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
index b61ea25..92ba0e1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -52,7 +52,7 @@
* Base class for testing activitiees that require audio loopback hardware..
*/
public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
- private static final String TAG = "AudioLoopbackActivity";
+ private static final String TAG = "AudioLoopbackBaseActivity";
protected AudioManager mAudioManager;
@@ -80,8 +80,15 @@
// Loopback Logic
NativeAnalyzerThread mNativeAnalyzerThread = null;
- protected double mLatencyMillis = 0.0;
- protected double mConfidence = 0.0;
+ protected static final int NUM_TEST_PHASES = 5;
+ protected int mTestPhase = 0;
+
+ protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
+ protected double[] mConfidence = new double[NUM_TEST_PHASES];
+
+ protected double mMeanLatencyMillis;
+ protected double mMeanAbsoluteDeviation;
+ protected double mMeanConfidence;
protected static final double CONFIDENCE_THRESHOLD = 0.6;
protected static final double PROAUDIO_LATENCY_MS_LIMIT = 20.0;
@@ -276,13 +283,13 @@
ReportLog reportLog = getReportLog();
reportLog.addValue(
"Estimated Latency",
- mLatencyMillis,
+ mMeanLatencyMillis,
ResultType.LOWER_BETTER,
ResultUnit.MS);
reportLog.addValue(
"Confidence",
- mConfidence,
+ mMeanConfidence,
ResultType.HIGHER_BETTER,
ResultUnit.NONE);
@@ -293,6 +300,12 @@
ResultUnit.NONE);
reportLog.addValue(
+ "Low Latency Stream",
+ mNativeAnalyzerThread.isLowLatencyStream(),
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
"Is Peripheral Attached",
mIsPeripheralAttached,
ResultType.NEUTRAL,
@@ -316,18 +329,43 @@
//
// test logic
//
+ private double calculateMeanAbsoluteDeviation(double mean, double[] values) {
+ double sum = 0.0;
+ for (double value : values) {
+ sum += Math.abs(value - mean);
+ }
+ return sum / values.length;
+ }
+
+ private double calculateMean(double[] values) {
+ double sum = 0.0;
+ for (double value : values) {
+ sum += value;
+ }
+ return sum / values.length;
+ }
+
protected void startAudioTest(Handler messageHandler) {
getPassButton().setEnabled(false);
- mLatencyMillis = 0.0;
- mConfidence = 0.0;
+
+ mTestPhase = 0;
+ java.util.Arrays.fill(mLatencyMillis, 0.0);
+ java.util.Arrays.fill(mConfidence, 0.0);
mNativeAnalyzerThread = new NativeAnalyzerThread(this);
if (mNativeAnalyzerThread != null) {
mNativeAnalyzerThread.setMessageHandler(messageHandler);
// This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
+ startTestPhase();
+ }
+ }
+
+ private void startTestPhase() {
+ if (mNativeAnalyzerThread != null) {
mNativeAnalyzerThread.startTest();
+ // what is this for?
try {
Thread.sleep(200);
} catch (InterruptedException e) {
@@ -337,6 +375,21 @@
}
protected void handleTestCompletion() {
+ Log.i(TAG, "handleTestCompletion() ...");
+
+ mMeanLatencyMillis = calculateMean(mLatencyMillis);
+ mMeanAbsoluteDeviation = calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
+ mMeanConfidence = calculateMean(mConfidence);
+
+ String result = String.format(
+ "Test Finished\nMean Latency:%.2f ms\nMean Absolute Deviation: %.2f\n" +
+ " Confidence: %.2f\n" +
+ " Low Latency Path: %s",
+ mMeanLatencyMillis,
+ mMeanAbsoluteDeviation,
+ mMeanConfidence,
+ (mNativeAnalyzerThread.isLowLatencyStream() ? "yes" : "no"));
+
// Make sure the test thread is finished. It should already be done.
if (mNativeAnalyzerThread != null) {
try {
@@ -345,6 +398,40 @@
e.printStackTrace();
}
}
+ Log.i(TAG, result);
+
+ mResultText.setText(result);
+
+ Log.i(TAG, "... Done");
+ }
+
+ protected void handleTestPhaseCompletion() {
+ if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
+ mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
+ mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
+
+ String result = String.format(
+ "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
+ mTestPhase,
+ mLatencyMillis[mTestPhase],
+ mConfidence[mTestPhase]);
+ Log.i(TAG, result);
+
+ mResultText.setText(result);
+ try {
+ mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+ // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ mTestPhase++;
+ if (mTestPhase >= NUM_TEST_PHASES) {
+ handleTestCompletion();
+ } else {
+ startTestPhase();
+ }
+ }
}
/**
@@ -357,7 +444,9 @@
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
Log.v(TAG,"got message native rec started!!");
showWait(true);
- mResultText.setText("Test Running...");
+ mResultText.setText(String.format("Test Running (phase: %d, low-latency: %s)...",
+ (mTestPhase + 1),
+ (mNativeAnalyzerThread.isLowLatencyStream() ? "yes" : "no")));
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
Log.v(TAG,"got message native rec can't start!!");
@@ -373,16 +462,15 @@
mResultText.setText("Test FAILED due to errors.");
handleTestCompletion();
break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
+ Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
+ mResultText.setText(String.format("Analyzing (phase: %d, low-latency: %s)...",
+ mTestPhase + 1,
+ (mNativeAnalyzerThread.isLowLatencyStream() ? "yes" : "no")));
+ break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
- if (mNativeAnalyzerThread != null) {
- mLatencyMillis = mNativeAnalyzerThread.getLatencyMillis();
- mConfidence = mNativeAnalyzerThread.getConfidence();
- }
- mResultText.setText(String.format(
- "Test Finished\nLatency:%.2f ms\nConfidence: %.2f",
- mLatencyMillis,
- mConfidence));
- handleTestCompletion();
+ Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
+ handleTestPhaseCompletion();
break;
default:
break;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
index ded5c1e..d492a4e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -82,14 +82,17 @@
protected void handleTestCompletion() {
super.handleTestCompletion();
- boolean resultValid = mConfidence >= CONFIDENCE_THRESHOLD
- && mLatencyMillis > 1.0;
+ Log.i(TAG, "handleTestCompletion() ...");
+ boolean resultValid = mMeanConfidence >= CONFIDENCE_THRESHOLD
+ && mMeanLatencyMillis > 1.0;
getPassButton().setEnabled(resultValid);
recordTestResults();
showWait(false);
mTestButton.setEnabled(true);
+
+ Log.i(TAG, "... done");
}
/**
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index 12fc9b2..d18f924 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -47,6 +47,7 @@
private volatile double mLatencyMillis = 0.0;
private volatile double mConfidence = 0.0;
private volatile int mSampleRate = 0;
+ private volatile boolean mIsLowLatencyStream = false;
private int mInputPreset = 0;
@@ -55,6 +56,7 @@
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR = 894;
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895;
static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896;
+ static final int NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING = 897;
public NativeAnalyzerThread(Context context) {
mContext = context;
@@ -89,6 +91,7 @@
private native int analyze(long audio_context);
private native double getLatencyMillis(long audio_context);
private native double getConfidence(long audio_context);
+ private native boolean isLowlatency(long audio_context);
private native int getSampleRate(long audio_context);
@@ -102,6 +105,8 @@
public int getSampleRate() { return mSampleRate; }
+ public boolean isLowLatencyStream() { return mIsLowLatencyStream; }
+
public synchronized void startTest() {
if (mThread == null) {
mEnabled = true;
@@ -149,6 +154,7 @@
sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
mEnabled = false;
}
+ mIsLowLatencyStream = isLowlatency(audioContext);
final long timeoutMillis = mSecondsToRun * 1000;
final long startedAtMillis = System.currentTimeMillis();
@@ -164,6 +170,7 @@
// Analyze the recording and measure latency.
mThread.setPriority(Thread.MAX_PRIORITY);
+ sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING);
result = analyze(audioContext);
if (result < 0) {
break;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index b95e9e3..95090f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -164,12 +164,17 @@
}
private boolean calculatePass() {
- boolean hasPassed = !mClaimsProAudio ||
+ boolean flagsCorrect = !mClaimsProAudio ||
(mClaimsLowLatencyAudio && mClaimsMIDI &&
- mClaimsUSBHostMode && mClaimsUSBPeripheralMode &&
- (!mClaimsHDMI || isHDMIValid()) &&
- mOutputDevInfo != null && mInputDevInfo != null &&
- mConfidence >= CONFIDENCE_THRESHOLD && mLatencyMillis <= PROAUDIO_LATENCY_MS_LIMIT);
+ mClaimsUSBHostMode && mClaimsUSBPeripheralMode);
+ boolean hdmiValid = !mClaimsHDMI || isHDMIValid();
+ boolean deviceValid = mOutputDevInfo != null && mInputDevInfo != null;
+
+ boolean hasPassed = flagsCorrect &&
+ hdmiValid &&
+ deviceValid &&
+ mMeanConfidence >= CONFIDENCE_THRESHOLD &&
+ mMeanLatencyMillis <= PROAUDIO_LATENCY_MS_LIMIT;
getPassButton().setEnabled(hasPassed);
return hasPassed;
@@ -181,9 +186,9 @@
Resources strings = getResources();
if (hasPassed) {
mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_pass));
- } else if (mClaimsProAudio && mLatencyMillis > PROAUDIO_LATENCY_MS_LIMIT) {
+ } else if (mClaimsProAudio && mMeanLatencyMillis > PROAUDIO_LATENCY_MS_LIMIT) {
mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_latencytoohigh));
- } else if (mClaimsProAudio && mConfidence < CONFIDENCE_THRESHOLD) {
+ } else if (mClaimsProAudio && mMeanConfidence < CONFIDENCE_THRESHOLD) {
mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_confidencetoolow));
} else if (!mClaimsMIDI) {
mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_midinotreported));