1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.audio; 18 19 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode; 20 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix; 21 22 import android.content.res.Resources; 23 import android.media.AudioDeviceCallback; 24 import android.media.AudioDeviceInfo; 25 import android.media.AudioManager; 26 import android.media.MediaRecorder; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.util.Log; 32 import android.view.View; 33 import android.view.View.OnClickListener; 34 import android.widget.Button; 35 import android.widget.ProgressBar; 36 import android.widget.SeekBar; 37 import android.widget.TextView; 38 39 import com.android.compatibility.common.util.CddTest; 40 import com.android.compatibility.common.util.ResultType; 41 import com.android.compatibility.common.util.ResultUnit; 42 import com.android.cts.verifier.CtsVerifierReportLog; 43 import com.android.cts.verifier.PassFailButtons; 44 import com.android.cts.verifier.R; 45 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags; 46 import com.android.cts.verifier.audio.audiolib.AudioUtils; 47 import com.android.cts.verifier.audio.audiolib.StatUtils; 48 49 /** 50 * CtsVerifier Audio Loopback Latency Test 51 */ 52 @CddTest(requirement = "5.10/C-1-2,C-1-5") 53 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity { 54 private static final String TAG = "AudioLoopbackLatencyActivity"; 55 56 // JNI load 57 static { 58 try { 59 System.loadLibrary("audioloopback_jni"); 60 } catch (UnsatisfiedLinkError e) { 61 Log.e(TAG, "Error loading Audio Loopback JNI library"); 62 Log.e(TAG, "e: " + e); 63 e.printStackTrace(); 64 } 65 66 /* TODO: gracefully fail/notify if the library can't be loaded */ 67 } 68 protected AudioManager mAudioManager; 69 70 // UI 71 TextView[] mResultsText = new TextView[NUM_TEST_ROUTES]; 72 73 TextView mAudioLevelText; 74 SeekBar mAudioLevelSeekbar; 75 76 TextView mTestStatusText; 77 ProgressBar mProgressBar; 78 int mMaxLevel; 79 80 private OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 81 private Button[] mStartButtons = new Button[NUM_TEST_ROUTES]; 82 83 String mYesString; 84 String mNoString; 85 86 String mPassString; 87 String mFailString; 88 String mNotTestedString; 89 String mNotRequiredString; 90 String mRequiredString; 91 92 // These flags determine the maximum allowed latency 93 private boolean mClaimsProAudio; 94 private boolean mClaimsLowLatency; 95 private boolean mClaimsMediaPerformance; 96 private boolean mClaimsOutput; 97 private boolean mClaimsInput; 98 99 // Useful info 100 private boolean mSupportsMMAP = AudioUtils.isMMapSupported(); 101 private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported(); 102 103 // Peripheral(s) 104 private static final int NUM_TEST_ROUTES = 3; 105 private static final int TESTROUTE_DEVICE = 0; // device speaker + mic 106 private static final int TESTROUTE_ANALOG_JACK = 1; 107 private static final int TESTROUTE_USB = 2; 108 private int mTestRoute = TESTROUTE_DEVICE; 109 110 // Loopback Logic 111 private NativeAnalyzerThread mNativeAnalyzerThread = null; 112 113 protected static final int NUM_TEST_PHASES = 5; 114 protected int mTestPhase = 0; 115 116 private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6; 117 private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6; 118 119 public static final double LATENCY_NOT_MEASURED = 0.0; 120 public static final double LATENCY_BASIC = 500.0; 121 public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0; 122 public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0; 123 public static final double LATENCY_PRO_AUDIO_USB = 25.0; 124 public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0; 125 126 // The audio stream callback threads should stop and close 127 // in less than a few hundred msec. This is a generous timeout value. 128 private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000; 129 130 private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES]; 131 class TestSpec { 132 private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec"; 133 // impossibly low latencies (indicating something in the test went wrong). 134 protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0; 135 136 final int mRouteId; 137 138 // runtime assigned device ID 139 static final int DEVICEID_NONE = -1; 140 int mInputDeviceId; 141 int mOutputDeviceId; 142 143 String mDeviceName; 144 145 double[] mLatencyMS = new double[NUM_TEST_PHASES]; 146 double[] mConfidence = new double[NUM_TEST_PHASES]; 147 148 double mMeanLatencyMS; 149 double mMeanAbsoluteDeviation; 150 double mMeanConfidence; 151 double mRequiredConfidence; 152 153 boolean mRouteAvailable; // Have we seen this route/device at any time 154 boolean mRouteConnected; // is the route available NOW 155 boolean mTestRun; 156 TestSpec(int routeId, double requiredConfidence)157 TestSpec(int routeId, double requiredConfidence) { 158 mRouteId = routeId; 159 mRequiredConfidence = requiredConfidence; 160 161 mInputDeviceId = DEVICEID_NONE; 162 mOutputDeviceId = DEVICEID_NONE; 163 } 164 startTest()165 void startTest() { 166 mTestRun = true; 167 168 java.util.Arrays.fill(mLatencyMS, 0.0); 169 java.util.Arrays.fill(mConfidence, 0.0); 170 } 171 recordPhase(int phase, double latencyMS, double confidence)172 void recordPhase(int phase, double latencyMS, double confidence) { 173 mLatencyMS[phase] = latencyMS; 174 mConfidence[phase] = confidence; 175 } 176 handleTestCompletion()177 void handleTestCompletion() { 178 mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS); 179 mMeanAbsoluteDeviation = 180 StatUtils.calculateMeanAbsoluteDeviation( 181 mMeanLatencyMS, mLatencyMS, mLatencyMS.length); 182 mMeanConfidence = StatUtils.calculateMean(mConfidence); 183 } 184 isMeasurementValid()185 boolean isMeasurementValid() { 186 return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence; 187 } 188 getResultString()189 String getResultString() { 190 String result; 191 192 if (!mRouteAvailable) { 193 result = "Route Not Available"; 194 } else if (!mTestRun) { 195 result = "Test Not Run"; 196 } else if (mMeanConfidence < mRequiredConfidence) { 197 result = String.format( 198 "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.", 199 mMeanConfidence, mRequiredConfidence); 200 } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) { 201 result = String.format( 202 "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.", 203 mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS); 204 } else { 205 result = String.format( 206 "Test Finished\nMean Latency:%.2f ms\n" 207 + "Mean Absolute Deviation: %.2f\n" 208 + "Confidence: %.2f\n" 209 + "Low Latency Path: %s", 210 mMeanLatencyMS, 211 mMeanAbsoluteDeviation, 212 mMeanConfidence, 213 mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString); 214 } 215 216 return result; 217 } 218 219 // ReportLog Schema (per route) 220 private static final String KEY_ROUTEINDEX = "route_index"; 221 private static final String KEY_LATENCY = "latency"; 222 private static final String KEY_CONFIDENCE = "confidence"; 223 private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation"; 224 private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached"; 225 private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral"; 226 private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral"; 227 private static final String KEY_TEST_PERIPHERAL = "test_peripheral"; 228 recordTestResults(CtsVerifierReportLog reportLog)229 void recordTestResults(CtsVerifierReportLog reportLog) { 230 reportLog.addValue( 231 KEY_ROUTEINDEX, 232 mRouteId, 233 ResultType.NEUTRAL, 234 ResultUnit.NONE); 235 236 reportLog.addValue( 237 KEY_LATENCY, 238 mMeanLatencyMS, 239 ResultType.LOWER_BETTER, 240 ResultUnit.MS); 241 242 reportLog.addValue( 243 KEY_CONFIDENCE, 244 mMeanConfidence, 245 ResultType.HIGHER_BETTER, 246 ResultUnit.NONE); 247 248 reportLog.addValue( 249 KEY_MEANABSDEVIATION, 250 mMeanAbsoluteDeviation, 251 ResultType.NEUTRAL, 252 ResultUnit.NONE); 253 254 reportLog.addValue( 255 KEY_TEST_PERIPHERAL, 256 mDeviceName, 257 ResultType.NEUTRAL, 258 ResultUnit.NONE); 259 } 260 } 261 262 @Override onCreate(Bundle savedInstanceState)263 protected void onCreate(Bundle savedInstanceState) { 264 super.onCreate(savedInstanceState); 265 266 setContentView(R.layout.audio_loopback_latency_activity); 267 268 setPassFailButtonClickListeners(); 269 getPassButton().setEnabled(false); 270 setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1); 271 272 mRequireReportLogToPass = true; 273 274 mClaimsOutput = AudioSystemFlags.claimsOutput(this); 275 mClaimsInput = AudioSystemFlags.claimsInput(this); 276 mClaimsProAudio = AudioSystemFlags.claimsProAudio(this); 277 mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this); 278 mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0; 279 280 // Setup test specifications 281 double mustLatency; 282 283 // Speaker/Mic Path 284 mTestSpecs[TESTROUTE_DEVICE] = 285 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT); 286 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; // Always 287 288 // Analog Jack Path 289 mTestSpecs[TESTROUTE_ANALOG_JACK] = 290 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED); 291 292 // USB Path 293 mTestSpecs[TESTROUTE_USB] = 294 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED); 295 296 // Setup UI 297 Resources resources = getResources(); 298 mYesString = resources.getString(R.string.audio_general_yes); 299 mNoString = resources.getString(R.string.audio_general_no); 300 mPassString = resources.getString(R.string.audio_general_pass); 301 mFailString = resources.getString(R.string.audio_general_fail); 302 mNotTestedString = resources.getString(R.string.audio_general_not_tested); 303 mNotRequiredString = resources.getString(R.string.audio_general_not_required); 304 mRequiredString = resources.getString(R.string.audio_general_required); 305 306 // Pro Audio 307 ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText( 308 (mClaimsProAudio ? mYesString : mNoString)); 309 310 // Low Latency 311 ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText( 312 (mClaimsLowLatency ? mYesString : mNoString)); 313 314 // Media Performance Class 315 ((TextView) findViewById(R.id.audio_loopback_mpc)).setText( 316 (mClaimsMediaPerformance ? mYesString : mNoString)); 317 318 // MMAP 319 ((TextView) findViewById(R.id.audio_loopback_mmap)).setText( 320 (mSupportsMMAP ? mYesString : mNoString)); 321 ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText( 322 (mSupportsMMAPExclusive ? mYesString : mNoString)); 323 324 // Individual Test Results 325 mResultsText[TESTROUTE_DEVICE] = 326 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info); 327 mResultsText[TESTROUTE_ANALOG_JACK] = 328 (TextView) findViewById(R.id.audio_loopback_headsetpath_info); 329 mResultsText[TESTROUTE_USB] = 330 (TextView) findViewById(R.id.audio_loopback_usbpath_info); 331 332 mStartButtons[TESTROUTE_DEVICE] = 333 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn); 334 mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener); 335 336 mStartButtons[TESTROUTE_ANALOG_JACK] = 337 (Button) findViewById(R.id.audio_loopback_headsetpath_btn); 338 mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener); 339 340 mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn); 341 mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener); 342 343 mAudioManager = getSystemService(AudioManager.class); 344 345 mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler()); 346 347 connectLoopbackUI(); 348 349 enableStartButtons(true); 350 351 handleTestCompletion(false); 352 } 353 354 // 355 // UI State 356 // enableStartButtons(boolean enable)357 private void enableStartButtons(boolean enable) { 358 if (enable) { 359 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 360 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected); 361 } 362 } else { 363 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 364 mStartButtons[routeId].setEnabled(false); 365 } 366 } 367 } 368 connectLoopbackUI()369 private void connectLoopbackUI() { 370 mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text); 371 mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar); 372 mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 373 mAudioLevelSeekbar.setMax(mMaxLevel); 374 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0); 375 refreshLevel(); 376 377 mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 378 @Override 379 public void onStopTrackingTouch(SeekBar seekBar) {} 380 381 @Override 382 public void onStartTrackingTouch(SeekBar seekBar) {} 383 384 @Override 385 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 386 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 387 progress, 0); 388 Log.i(TAG,"Level set to: " + progress); 389 refreshLevel(); 390 } 391 }); 392 393 mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text); 394 mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar); 395 showWait(false); 396 } 397 398 // 399 // Peripheral Connection Logic 400 // clearDeviceIds()401 void clearDeviceIds() { 402 for (TestSpec testSpec : mTestSpecs) { 403 testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE; 404 } 405 } 406 clearDeviceConnected()407 void clearDeviceConnected() { 408 for (TestSpec testSpec : mTestSpecs) { 409 testSpec.mRouteConnected = false; 410 } 411 } 412 scanPeripheralList(AudioDeviceInfo[] devices)413 void scanPeripheralList(AudioDeviceInfo[] devices) { 414 clearDeviceIds(); 415 clearDeviceConnected(); 416 417 for (AudioDeviceInfo devInfo : devices) { 418 switch (devInfo.getType()) { 419 // TESTROUTE_DEVICE (i.e. Speaker & Mic) 420 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: 421 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 422 if (devInfo.isSink()) { 423 mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = devInfo.getId(); 424 } else if (devInfo.isSource()) { 425 mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = devInfo.getId(); 426 } 427 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; 428 mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true; 429 mTestSpecs[TESTROUTE_DEVICE].mDeviceName = devInfo.getProductName().toString(); 430 break; 431 432 // TESTROUTE_ANALOG_JACK 433 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 434 case AudioDeviceInfo.TYPE_AUX_LINE: 435 if (devInfo.isSink()) { 436 mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId(); 437 } else if (devInfo.isSource()) { 438 mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId(); 439 } 440 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true; 441 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true; 442 mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName = 443 devInfo.getProductName().toString(); 444 break; 445 446 // TESTROUTE_USB 447 case AudioDeviceInfo.TYPE_USB_DEVICE: 448 case AudioDeviceInfo.TYPE_USB_HEADSET: 449 if (devInfo.isSink()) { 450 mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId(); 451 } else if (devInfo.isSource()) { 452 mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId(); 453 } 454 mTestSpecs[TESTROUTE_USB].mRouteAvailable = true; 455 mTestSpecs[TESTROUTE_USB].mRouteConnected = true; 456 mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString(); 457 } 458 459 enableStartButtons(true); 460 } 461 } 462 463 private class ConnectListener extends AudioDeviceCallback { ConnectListener()464 ConnectListener() {} 465 466 // 467 // AudioDevicesManager.OnDeviceConnectionListener 468 // 469 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)470 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 471 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 472 } 473 474 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)475 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 476 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 477 } 478 } 479 480 /** 481 * refresh Audio Level seekbar and text 482 */ refreshLevel()483 private void refreshLevel() { 484 int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 485 mAudioLevelSeekbar.setProgress(currentLevel); 486 487 String levelText = String.format("%s: %d/%d", 488 getResources().getString(R.string.audio_loopback_level_text), 489 currentLevel, mMaxLevel); 490 mAudioLevelText.setText(levelText); 491 } 492 493 // 494 // show active progress bar 495 // showWait(boolean show)496 protected void showWait(boolean show) { 497 mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 498 } 499 500 // 501 // Common logging 502 // 503 504 @Override getTestId()505 public String getTestId() { 506 return setTestNameSuffix(sCurrentDisplayMode, getClass().getName()); 507 } 508 509 @Override requiresReportLog()510 public boolean requiresReportLog() { 511 return true; 512 } 513 514 @Override getReportFileName()515 public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; } 516 517 @Override getReportSectionName()518 public final String getReportSectionName() { 519 return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity"); 520 } 521 522 // Test-Schema 523 private static final String KEY_SAMPLE_RATE = "sample_rate"; 524 private static final String KEY_IS_PRO_AUDIO = "is_pro_audio"; 525 private static final String KEY_IS_LOW_LATENCY = "is_low_latency"; 526 private static final String KEY_TEST_MMAP = "supports_mmap"; 527 private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive"; 528 private static final String KEY_LEVEL = "level"; 529 recordRouteResults(int routeIndex)530 private void recordRouteResults(int routeIndex) { 531 if (mTestSpecs[routeIndex].mTestRun) { 532 CtsVerifierReportLog reportLog = getReportLog(); 533 534 int audioLevel = mAudioLevelSeekbar.getProgress(); 535 reportLog.addValue( 536 KEY_LEVEL, 537 audioLevel, 538 ResultType.NEUTRAL, 539 ResultUnit.NONE); 540 541 reportLog.addValue( 542 KEY_IS_PRO_AUDIO, 543 mClaimsProAudio, 544 ResultType.NEUTRAL, 545 ResultUnit.NONE); 546 547 reportLog.addValue( 548 KEY_TEST_MMAP, 549 mSupportsMMAP, 550 ResultType.NEUTRAL, 551 ResultUnit.NONE); 552 553 reportLog.addValue( 554 KEY_TEST_MMAPEXCLUSIVE, 555 mSupportsMMAPExclusive, 556 ResultType.NEUTRAL, 557 ResultUnit.NONE); 558 559 reportLog.addValue( 560 KEY_SAMPLE_RATE, 561 mNativeAnalyzerThread.getSampleRate(), 562 ResultType.NEUTRAL, 563 ResultUnit.NONE); 564 565 reportLog.addValue( 566 KEY_IS_LOW_LATENCY, 567 mNativeAnalyzerThread.isLowLatencyStream(), 568 ResultType.NEUTRAL, 569 ResultUnit.NONE); 570 571 mTestSpecs[routeIndex].recordTestResults(reportLog); 572 573 reportLog.submit(); 574 } 575 } 576 577 @Override recordTestResults()578 public void recordTestResults() { 579 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 580 recordRouteResults(route); 581 } 582 } 583 startAudioTest(Handler messageHandler, int testRouteId)584 private void startAudioTest(Handler messageHandler, int testRouteId) { 585 enableStartButtons(false); 586 mResultsText[testRouteId].setText("Running..."); 587 588 mTestRoute = testRouteId; 589 590 mTestSpecs[mTestRoute].startTest(); 591 592 getPassButton().setEnabled(false); 593 594 mTestPhase = 0; 595 596 mNativeAnalyzerThread = new NativeAnalyzerThread(this); 597 if (mNativeAnalyzerThread != null) { 598 mNativeAnalyzerThread.setMessageHandler(messageHandler); 599 // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 600 mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION); 601 startTestPhase(); 602 } else { 603 Log.e(TAG, "Couldn't allocate native analyzer thread"); 604 mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure)); 605 } 606 } 607 startTestPhase()608 private void startTestPhase() { 609 if (mNativeAnalyzerThread != null) { 610 Log.i(TAG, "mTestRoute: " + mTestRoute 611 + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId 612 + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId); 613 mNativeAnalyzerThread.startTest( 614 mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId); 615 616 // what is this for? 617 try { 618 Thread.sleep(200); 619 } catch (InterruptedException e) { 620 e.printStackTrace(); 621 } 622 } 623 } 624 handleTestPhaseCompletion()625 private void handleTestPhaseCompletion() { 626 if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) { 627 double latency = mNativeAnalyzerThread.getLatencyMillis(); 628 double confidence = mNativeAnalyzerThread.getConfidence(); 629 TestSpec testSpec = mTestSpecs[mTestRoute]; 630 testSpec.recordPhase(mTestPhase, latency, confidence); 631 632 String result = String.format( 633 "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n", 634 mTestPhase, latency, confidence); 635 636 mTestStatusText.setText(result); 637 try { 638 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 639 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500); 640 } catch (InterruptedException e) { 641 e.printStackTrace(); 642 } 643 644 645 mTestPhase++; 646 if (mTestPhase >= NUM_TEST_PHASES) { 647 handleTestCompletion(true); 648 } else { 649 startTestPhase(); 650 } 651 } 652 } 653 handleTestCompletion(boolean showResult)654 private void handleTestCompletion(boolean showResult) { 655 TestSpec testSpec = mTestSpecs[mTestRoute]; 656 testSpec.handleTestCompletion(); 657 658 // Make sure the test thread is finished. It should already be done. 659 if (mNativeAnalyzerThread != null) { 660 try { 661 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 662 } catch (InterruptedException e) { 663 e.printStackTrace(); 664 } 665 } 666 667 mResultsText[mTestRoute].setText(testSpec.getResultString()); 668 669 LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements(); 670 boolean pass = isReportLogOkToPass() 671 && requirements.evaluate(mClaimsProAudio, 672 Build.VERSION.MEDIA_PERFORMANCE_CLASS, 673 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 674 ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0, 675 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 676 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS : 0.0, 677 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 678 ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0); 679 680 getPassButton().setEnabled(pass); 681 682 StringBuilder sb = new StringBuilder(); 683 if (!isReportLogOkToPass()) { 684 sb.append(getResources().getString(R.string.audio_general_reportlogtest) + "\n"); 685 } 686 sb.append(requirements.getResultsString()); 687 if (showResult) { 688 sb.append("\n" + (pass ? mPassString : mFailString)); 689 } 690 mTestStatusText.setText(sb.toString()); 691 692 showWait(false); 693 enableStartButtons(true); 694 } 695 696 /** 697 * handler for messages from audio thread 698 */ 699 private Handler mMessageHandler = new Handler() { 700 public void handleMessage(Message msg) { 701 super.handleMessage(msg); 702 switch(msg.what) { 703 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED: 704 Log.v(TAG,"got message native rec started!!"); 705 showWait(true); 706 mTestStatusText.setText(String.format("[phase: %d] - Test Running...", 707 (mTestPhase + 1))); 708 break; 709 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR: 710 Log.v(TAG,"got message native rec can't start!!"); 711 mTestStatusText.setText("Test Error opening streams."); 712 handleTestCompletion(true); 713 break; 714 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR: 715 Log.v(TAG,"got message native rec can't start!!"); 716 mTestStatusText.setText("Test Error while recording."); 717 handleTestCompletion(true); 718 break; 719 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS: 720 mTestStatusText.setText("Test FAILED due to errors."); 721 handleTestCompletion(true); 722 break; 723 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING: 724 mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...", 725 mTestPhase + 1)); 726 break; 727 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE: 728 handleTestPhaseCompletion(); 729 break; 730 default: 731 break; 732 } 733 } 734 }; 735 736 private class OnBtnClickListener implements OnClickListener { 737 @Override onClick(View v)738 public void onClick(View v) { 739 int id = v.getId(); 740 if (id == R.id.audio_loopback_speakermicpath_btn) { 741 startAudioTest(mMessageHandler, TESTROUTE_DEVICE); 742 } else if (id == R.id.audio_loopback_headsetpath_btn) { 743 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK); 744 } else if (id == R.id.audio_loopback_usbpath_btn) { 745 startAudioTest(mMessageHandler, TESTROUTE_USB); 746 } 747 } 748 } 749 750 class LoopbackLatencyRequirements { 751 public static final int MPC_NONE = 0; 752 public static final int MPC_R = Build.VERSION_CODES.R; 753 public static final int MPC_S = Build.VERSION_CODES.S; 754 public static final int MPC_T = Build.VERSION_CODES.TIRAMISU; 755 756 String mResultsString = new String(); 757 getResultsString()758 String getResultsString() { 759 return mResultsString; 760 } 761 checkLatency(double measured, double limit)762 private boolean checkLatency(double measured, double limit) { 763 return measured == LATENCY_NOT_MEASURED || measured <= limit; 764 } 765 evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency)766 public boolean evaluate(boolean proAudio, 767 int mediaPerformanceClass, 768 double deviceLatency, 769 double analogLatency, 770 double usbLatency) { 771 772 // Required to test the Mic/Speaker path 773 boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED; 774 775 // All devices must be under the basic limit. 776 boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC) 777 && checkLatency(analogLatency, LATENCY_BASIC) 778 && checkLatency(usbLatency, LATENCY_BASIC); 779 780 // For Media Performance Class T the RT latency must be <= 80 msec on one path. 781 boolean mpcAtLeastOnePass = (mediaPerformanceClass < MPC_T) 782 || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE) 783 || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE) 784 || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE); 785 786 // For ProAudio, the RT latency must be <= 25 msec on one path. 787 boolean proAudioAtLeastOnePass = !proAudio 788 || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 789 || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 790 || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE); 791 792 String supplementalText = ""; 793 // For ProAudio, analog and USB have specific limits 794 boolean proAudioLimitsPass = !proAudio; 795 if (proAudio) { 796 if (analogLatency > 0.0) { 797 proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG; 798 } else if (usbLatency > 0.0) { 799 // USB audio must be supported if 3.5mm jack not supported 800 proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB; 801 } 802 } 803 804 boolean pass = 805 internalPathRun && 806 basicPass && 807 mpcAtLeastOnePass && 808 proAudioAtLeastOnePass && 809 proAudioLimitsPass; 810 811 // Build the results explanation 812 StringBuilder sb = new StringBuilder(); 813 if (proAudio) { 814 sb.append("[Pro Audio]"); 815 } else if (mediaPerformanceClass != MPC_NONE) { 816 sb.append("[MPC %d]" + mediaPerformanceClass); 817 } else { 818 sb.append("[Basic Audio]"); 819 } 820 sb.append(" "); 821 822 sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED 823 ? String.format("%.2fms ", deviceLatency) 824 : (mNotTestedString + " - " + mRequiredString))); 825 sb.append("\nHeadset: " + (analogLatency != LATENCY_NOT_MEASURED 826 ? String.format("%.2fms ", analogLatency) 827 : (mNotTestedString + " - " + mNotRequiredString))); 828 sb.append("\nUSB: " + (usbLatency != LATENCY_NOT_MEASURED 829 ? String.format("%.2fms ", usbLatency) 830 : (mNotTestedString + " - " + mNotRequiredString))); 831 832 sb.append(supplementalText); 833 mResultsString = sb.toString(); 834 835 return pass; 836 } 837 } 838 } 839