blob: a6bd4add994015c2d617a86be7a20970f676c5ed [file] [log] [blame]
/*
* Copyright (C) 2015 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.android.cts.verifier.audio;
import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
import android.content.res.Resources;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
import com.android.cts.verifier.audio.audiolib.AudioUtils;
import com.android.cts.verifier.audio.audiolib.StatUtils;
/**
* CtsVerifier Audio Loopback Latency Test
*/
@CddTest(requirement = "5.10/C-1-2,C-1-5")
public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
private static final String TAG = "AudioLoopbackLatencyActivity";
// JNI load
static {
try {
System.loadLibrary("audioloopback_jni");
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Error loading Audio Loopback JNI library");
Log.e(TAG, "e: " + e);
e.printStackTrace();
}
/* TODO: gracefully fail/notify if the library can't be loaded */
}
protected AudioManager mAudioManager;
// UI
TextView[] mResultsText = new TextView[NUM_TEST_ROUTES];
TextView mAudioLevelText;
SeekBar mAudioLevelSeekbar;
TextView mTestStatusText;
ProgressBar mProgressBar;
int mMaxLevel;
private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
String mYesString;
String mNoString;
String mPassString;
String mFailString;
String mNotTestedString;
// These flags determine the maximum allowed latency
private boolean mClaimsProAudio;
private boolean mClaimsLowLatency;
private boolean mClaimsMediaPerformance;
private boolean mClaimsOutput;
private boolean mClaimsInput;
// Useful info
private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
// Peripheral(s)
private static final int NUM_TEST_ROUTES = 3;
private static final int TESTROUTE_DEVICE = 0; // device speaker + mic
private static final int TESTROUTE_ANALOG_JACK = 1;
private static final int TESTROUTE_USB = 2;
private int mTestRoute = TESTROUTE_DEVICE;
// Loopback Logic
private NativeAnalyzerThread mNativeAnalyzerThread = null;
protected static final int NUM_TEST_PHASES = 5;
protected int mTestPhase = 0;
private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6;
private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6;
public static final double LATENCY_NOT_MEASURED = 0.0;
public static final double LATENCY_BASIC = 500.0;
public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0;
public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0;
public static final double LATENCY_PRO_AUDIO_USB = 25.0;
public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0;
// The audio stream callback threads should stop and close
// in less than a few hundred msec. This is a generous timeout value.
private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
class TestSpec {
// impossibly low latencies (indicating something in the test went wrong).
protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0;
final int mRouteId;
// final double mMustLatencyMS;
// runtime assigned device ID
static final int DEVICEID_NONE = -1;
int mInputDeviceId;
int mOutputDeviceId;
String mDeviceName;
double[] mLatencyMS = new double[NUM_TEST_PHASES];
double[] mConfidence = new double[NUM_TEST_PHASES];
double mMeanLatencyMS;
double mMeanAbsoluteDeviation;
double mMeanConfidence;
double mRequiredConfidence;
boolean mRouteAvailable; // Have we seen this route/device at any time
boolean mRouteConnected; // is the route available NOW
boolean mTestRun;
// boolean mTestPass;
TestSpec(int routeId, double requiredConfidence) {
mRouteId = routeId;
mRequiredConfidence = requiredConfidence;
mInputDeviceId = DEVICEID_NONE;
mOutputDeviceId = DEVICEID_NONE;
}
void startTest() {
mTestRun = true;
java.util.Arrays.fill(mLatencyMS, 0.0);
java.util.Arrays.fill(mConfidence, 0.0);
}
void recordPhase(int phase, double latencyMS, double confidence) {
mLatencyMS[phase] = latencyMS;
mConfidence[phase] = confidence;
}
void handleTestCompletion() {
mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS);
mMeanAbsoluteDeviation =
StatUtils.calculateMeanAbsoluteDeviation(
mMeanLatencyMS, mLatencyMS, mLatencyMS.length);
mMeanConfidence = StatUtils.calculateMean(mConfidence);
}
boolean getRouteAvailable() {
return mRouteAvailable;
}
boolean getTestRun() {
return mTestRun;
}
boolean isMeasurementValid() {
return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence;
}
String getResultString() {
String result;
if (!mRouteAvailable) {
result = "Route Not Available";
} else if (!mTestRun) {
result = "Test Not Run";
} else if (mMeanConfidence < mRequiredConfidence) {
result = String.format(
"Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
mMeanConfidence, mRequiredConfidence);
} else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
result = String.format(
"Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.",
mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
} else {
result = String.format(
"Test Finished\nMean Latency:%.2f ms\n"
+ "Mean Absolute Deviation: %.2f\n"
+ "Confidence: %.2f\n"
+ "Low Latency Path: %s",
mMeanLatencyMS,
mMeanAbsoluteDeviation,
mMeanConfidence,
mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
}
return result;
}
// ReportLog Schema (per route)
private static final String KEY_ROUTEAVAILABLE = "route_available";
private static final String KEY_ROUTECONNECTED = "route_connected";
private static final String KEY_ROUTERUN = "route_run";
private static final String KEY_LATENCY = "route_latency";
private static final String KEY_CONFIDENCE = "route_confidence";
private static final String KEY_MEANABSDEVIATION = "route_mean_absolute_deviation";
private static final String KEY_IS_PERIPHERAL_ATTACHED = "route_is_peripheral_attached";
private static final String KEY_INPUT_PERIPHERAL_NAME = "route_input_peripheral";
private static final String KEY_OUTPUT_PERIPHERAL_NAME = "route_output_peripheral";
private static final String KEY_TEST_PERIPHERAL = "route_test_peripheral";
String makeSectionKey(String key) {
return Integer.toString(mRouteId) + "_" + key;
}
void recordTestResults(CtsVerifierReportLog reportLog) {
reportLog.addValue(
makeSectionKey(KEY_ROUTEAVAILABLE),
mRouteAvailable ? 1 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
makeSectionKey(KEY_ROUTECONNECTED),
mRouteConnected ? 1 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
makeSectionKey(KEY_ROUTERUN),
mTestRun ? 1 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
makeSectionKey(KEY_LATENCY),
mMeanLatencyMS,
ResultType.LOWER_BETTER,
ResultUnit.MS);
reportLog.addValue(
makeSectionKey(KEY_CONFIDENCE),
mMeanConfidence,
ResultType.HIGHER_BETTER,
ResultUnit.NONE);
reportLog.addValue(
makeSectionKey(KEY_MEANABSDEVIATION),
mMeanAbsoluteDeviation,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
makeSectionKey(KEY_TEST_PERIPHERAL),
mDeviceName,
ResultType.NEUTRAL,
ResultUnit.NONE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.audio_loopback_latency_activity);
setPassFailButtonClickListeners();
getPassButton().setEnabled(false);
setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
mClaimsOutput = AudioSystemFlags.claimsOutput(this);
mClaimsInput = AudioSystemFlags.claimsInput(this);
mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this);
mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
// Setup test specifications
double mustLatency;
// Speaker/Mic Path
mTestSpecs[TESTROUTE_DEVICE] =
new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT);
// Analog Jack Path
mTestSpecs[TESTROUTE_ANALOG_JACK] =
new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED);
// USB Path
mTestSpecs[TESTROUTE_USB] =
new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED);
// Setup UI
Resources resources = getResources();
mYesString = resources.getString(R.string.audio_general_yes);
mNoString = resources.getString(R.string.audio_general_no);
mPassString = resources.getString(R.string.audio_general_pass);
mFailString = resources.getString(R.string.audio_general_fail);
mNotTestedString = resources.getString(R.string.audio_general_not_tested) + " ";
// Pro Audio
((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText(
(mClaimsProAudio ? mYesString : mNoString));
// Low Latency
((TextView) findViewById(R.id.audio_loopback_low_latency)).setText(
(mClaimsLowLatency ? mYesString : mNoString));
// Media Performance Class
((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
(mClaimsMediaPerformance ? mYesString : mNoString));
// MMAP
((TextView) findViewById(R.id.audio_loopback_mmap)).setText(
(mSupportsMMAP ? mYesString : mNoString));
((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
(mSupportsMMAPExclusive ? mYesString : mNoString));
// Individual Test Results
mResultsText[TESTROUTE_DEVICE] =
(TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
mResultsText[TESTROUTE_ANALOG_JACK] =
(TextView) findViewById(R.id.audio_loopback_headsetpath_info);
mResultsText[TESTROUTE_USB] =
(TextView) findViewById(R.id.audio_loopback_usbpath_info);
mStartButtons[TESTROUTE_DEVICE] =
(Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
mStartButtons[TESTROUTE_ANALOG_JACK] =
(Button) findViewById(R.id.audio_loopback_headsetpath_btn);
mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
mAudioManager = getSystemService(AudioManager.class);
mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
connectLoopbackUI();
enableStartButtons(true);
}
//
// UI State
//
private void enableStartButtons(boolean enable) {
if (enable) {
for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
}
} else {
for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
mStartButtons[routeId].setEnabled(false);
}
}
}
private void connectLoopbackUI() {
mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mAudioLevelSeekbar.setMax(mMaxLevel);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
refreshLevel();
mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
progress, 0);
Log.i(TAG,"Level set to: " + progress);
refreshLevel();
}
});
mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text);
mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar);
showWait(false);
}
//
// Peripheral Connection Logic
//
void clearDeviceIds() {
for (TestSpec testSpec : mTestSpecs) {
testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
}
}
void clearDeviceConnected() {
for (TestSpec testSpec : mTestSpecs) {
testSpec.mRouteConnected = false;
}
}
void scanPeripheralList(AudioDeviceInfo[] devices) {
clearDeviceIds();
clearDeviceConnected();
for (AudioDeviceInfo devInfo : devices) {
switch (devInfo.getType()) {
// TESTROUTE_DEVICE (i.e. Speaker & Mic)
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
case AudioDeviceInfo.TYPE_BUILTIN_MIC:
if (devInfo.isSink()) {
mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = devInfo.getId();
} else if (devInfo.isSource()) {
mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = devInfo.getId();
}
mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
mTestSpecs[TESTROUTE_DEVICE].mDeviceName = devInfo.getProductName().toString();
break;
// TESTROUTE_ANALOG_JACK
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case AudioDeviceInfo.TYPE_AUX_LINE:
if (devInfo.isSink()) {
mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
} else if (devInfo.isSource()) {
mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
}
mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
devInfo.getProductName().toString();
break;
// TESTROUTE_USB
case AudioDeviceInfo.TYPE_USB_DEVICE:
case AudioDeviceInfo.TYPE_USB_HEADSET:
if (devInfo.isSink()) {
mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
} else if (devInfo.isSource()) {
mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
}
mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
}
// setTestButtonsState();
enableStartButtons(true);
}
}
private class ConnectListener extends AudioDeviceCallback {
ConnectListener() {}
//
// AudioDevicesManager.OnDeviceConnectionListener
//
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
}
}
/**
* refresh Audio Level seekbar and text
*/
private void refreshLevel() {
int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mAudioLevelSeekbar.setProgress(currentLevel);
String levelText = String.format("%s: %d/%d",
getResources().getString(R.string.audio_loopback_level_text),
currentLevel, mMaxLevel);
mAudioLevelText.setText(levelText);
}
//
// show active progress bar
//
protected void showWait(boolean show) {
mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
//
// Common logging
//
@Override
public String getTestId() {
return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
}
@Override
public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
@Override
public final String getReportSectionName() {
return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
}
// Schema
private static final String KEY_SAMPLE_RATE = "sample_rate";
private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
private static final String KEY_TEST_MMAP = "supports_mmap";
private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
private static final String KEY_LEVEL = "level";
//
// Subclasses should call this explicitly. SubClasses should call submit() after their logs
//
@Override
public void recordTestResults() {
Log.i(TAG, "recordTestResults() mNativeAnalyzerThread:" + mNativeAnalyzerThread);
// We need to rework that
CtsVerifierReportLog reportLog = getReportLog();
int audioLevel = mAudioLevelSeekbar.getProgress();
reportLog.addValue(
KEY_LEVEL,
audioLevel,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
KEY_IS_PRO_AUDIO,
mClaimsProAudio,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
KEY_TEST_MMAP,
mSupportsMMAP,
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
KEY_TEST_MMAPEXCLUSIVE ,
mSupportsMMAPExclusive,
ResultType.NEUTRAL,
ResultUnit.NONE);
if (mNativeAnalyzerThread == null) {
return; // no test results to report
}
reportLog.addValue(
KEY_SAMPLE_RATE,
mNativeAnalyzerThread.getSampleRate(),
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
KEY_IS_LOW_LATENCY,
mNativeAnalyzerThread.isLowLatencyStream(),
ResultType.NEUTRAL,
ResultUnit.NONE);
for (TestSpec testSpec : mTestSpecs) {
testSpec.recordTestResults(reportLog);
}
reportLog.submit();
}
private void startAudioTest(Handler messageHandler, int testRouteId) {
enableStartButtons(false);
mResultsText[testRouteId].setText("Running...");
mTestRoute = testRouteId;
mTestSpecs[mTestRoute].startTest();
getPassButton().setEnabled(false);
mTestPhase = 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();
} else {
Log.e(TAG, "Couldn't allocate native analyzer thread");
mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure));
}
}
private void startTestPhase() {
if (mNativeAnalyzerThread != null) {
Log.i(TAG, "mTestRoute: " + mTestRoute
+ " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
+ " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
mNativeAnalyzerThread.startTest(
mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
// what is this for?
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void handleTestPhaseCompletion() {
if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
double latency = mNativeAnalyzerThread.getLatencyMillis();
double confidence = mNativeAnalyzerThread.getConfidence();
TestSpec testSpec = mTestSpecs[mTestRoute];
testSpec.recordPhase(mTestPhase, latency, confidence);
String result = String.format(
"Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
mTestPhase, latency, confidence);
mTestStatusText.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();
}
}
}
private void handleTestCompletion() {
TestSpec testSpec = mTestSpecs[mTestRoute];
testSpec.handleTestCompletion();
// Make sure the test thread is finished. It should already be done.
if (mNativeAnalyzerThread != null) {
try {
mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mResultsText[mTestRoute].setText(testSpec.getResultString());
LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements();
boolean pass = requirements.evaluate(mClaimsProAudio,
Build.VERSION.MEDIA_PERFORMANCE_CLASS,
mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0,
mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS : 0.0,
mTestSpecs[TESTROUTE_USB].isMeasurementValid()
? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0);
getPassButton().setEnabled(pass);
String resultText = requirements.getResultsString();
mTestStatusText.setText(resultText);
showWait(false);
enableStartButtons(true);
}
/**
* handler for messages from audio thread
*/
private Handler mMessageHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what) {
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
Log.v(TAG,"got message native rec started!!");
showWait(true);
mTestStatusText.setText(String.format("[phase: %d] - Test Running...",
(mTestPhase + 1)));
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
Log.v(TAG,"got message native rec can't start!!");
mTestStatusText.setText("Test Error opening streams.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
Log.v(TAG,"got message native rec can't start!!");
mTestStatusText.setText("Test Error while recording.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
mTestStatusText.setText("Test FAILED due to errors.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...",
mTestPhase + 1));
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
handleTestPhaseCompletion();
break;
default:
break;
}
}
};
private class OnBtnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.audio_loopback_speakermicpath_btn) {
startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
} else if (id == R.id.audio_loopback_headsetpath_btn) {
startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
} else if (id == R.id.audio_loopback_usbpath_btn) {
startAudioTest(mMessageHandler, TESTROUTE_USB);
}
}
}
class LoopbackLatencyRequirements {
public static final int MPC_NONE = 0;
public static final int MPC_R = 1;
public static final int MPC_S = 2;
public static final int MPC_T = 3;
String mResultsString = new String();
String getResultsString() {
return mResultsString;
}
private boolean checkLatency(double measured, double limit) {
return measured == LATENCY_NOT_MEASURED || measured <= limit;
}
public boolean evaluate(boolean proAudio,
int mediaPerformanceClass,
double deviceLatency,
double analogLatency,
double usbLatency) {
// All devices must be under the basic limit.
boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC)
&& checkLatency(analogLatency, LATENCY_BASIC)
&& checkLatency(usbLatency, LATENCY_BASIC);
// For Media Performance Class T the RT latency must be <= 80 msec on one path.
boolean mpcAtLeastOnePass = (mediaPerformanceClass < MPC_T)
|| checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE)
|| checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)
|| checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE);
// For ProAudio, the RT latency must be <= 25 msec on one path.
boolean proAudioAtLeastOnePass = !proAudio
|| checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
|| checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
|| checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE);
String supplementalText = "";
// For ProAudio, analog and USB have specific limits
boolean proAudioLimitsPass = !proAudio;
if (proAudio) {
if (analogLatency > 0.0) {
proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG;
} else if (usbLatency > 0.0) {
// USB audio must be supported if 3.5mm jack not supported
proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB;
}
}
boolean pass =
basicPass && mpcAtLeastOnePass && proAudioAtLeastOnePass && proAudioLimitsPass;
// Build the results explanation
StringBuilder sb = new StringBuilder();
if (proAudio) {
sb.append("[Pro Audio]");
} else if (mediaPerformanceClass != MPC_NONE) {
sb.append("[MPC %d]" + mediaPerformanceClass);
} else {
sb.append("[Basic Audio]");
}
sb.append(" ");
sb.append("Speaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED
? String.format("%.2fms ", deviceLatency)
: mNotTestedString));
sb.append("Headset: " + (analogLatency != LATENCY_NOT_MEASURED
? String.format("%.2fms ", analogLatency)
: mNotTestedString));
sb.append("USB: " + (usbLatency != LATENCY_NOT_MEASURED
? String.format("%.2fms ", usbLatency)
: mNotTestedString));
sb.append(supplementalText);
sb.append(pass ? mPassString : mFailString);
mResultsString = sb.toString();
return pass;
}
}
}