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(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-3"}) 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 TextView mTestInstructions; 81 82 private OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 83 private Button[] mStartButtons = new Button[NUM_TEST_ROUTES]; 84 85 String mYesString; 86 String mNoString; 87 88 String mPassString; 89 String mFailString; 90 String mNotTestedString; 91 String mNotRequiredString; 92 String mRequiredString; 93 94 // These flags determine the maximum allowed latency 95 private boolean mClaimsProAudio; 96 private boolean mClaimsLowLatency; 97 private boolean mClaimsMediaPerformance; 98 private boolean mClaimsOutput; 99 private boolean mClaimsInput; 100 101 // Useful info 102 private boolean mSupportsMMAP = AudioUtils.isMMapSupported(); 103 private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported(); 104 105 private boolean mIsWatch; 106 private boolean mIsTV; 107 private boolean mIsAutomobile; 108 private boolean mIsHandheld; 109 private int mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 110 private int mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 111 112 // Peripheral(s) 113 private static final int NUM_TEST_ROUTES = 3; 114 private static final int TESTROUTE_DEVICE = 0; // device speaker + mic 115 private static final int TESTROUTE_ANALOG_JACK = 1; 116 private static final int TESTROUTE_USB = 2; 117 private int mTestRoute = TESTROUTE_DEVICE; 118 119 // Loopback Logic 120 private NativeAnalyzerThread mNativeAnalyzerThread = null; 121 122 protected static final int NUM_TEST_PHASES = 5; 123 protected int mTestPhase = 0; 124 125 private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6; 126 private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6; 127 128 public static final double LATENCY_NOT_MEASURED = 0.0; 129 public static final double LATENCY_BASIC = 300.0; 130 public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0; 131 public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0; 132 public static final double LATENCY_PRO_AUDIO_USB = 25.0; 133 public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0; 134 135 // The audio stream callback threads should stop and close 136 // in less than a few hundred msec. This is a generous timeout value. 137 private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000; 138 139 private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES]; 140 class TestSpec { 141 private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec"; 142 // impossibly low latencies (indicating something in the test went wrong). 143 protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0; 144 145 final int mRouteId; 146 147 // runtime assigned device ID 148 static final int DEVICEID_NONE = -1; 149 int mInputDeviceId; 150 int mOutputDeviceId; 151 152 String mDeviceName; 153 154 double[] mLatencyMS = new double[NUM_TEST_PHASES]; 155 double[] mConfidence = new double[NUM_TEST_PHASES]; 156 157 double mMeanLatencyMS; 158 double mMeanAbsoluteDeviation; 159 double mMeanConfidence; 160 double mRequiredConfidence; 161 162 boolean mRouteAvailable; // Have we seen this route/device at any time 163 boolean mRouteConnected; // is the route available NOW 164 boolean mTestRun; 165 TestSpec(int routeId, double requiredConfidence)166 TestSpec(int routeId, double requiredConfidence) { 167 mRouteId = routeId; 168 mRequiredConfidence = requiredConfidence; 169 170 mInputDeviceId = DEVICEID_NONE; 171 mOutputDeviceId = DEVICEID_NONE; 172 } 173 startTest()174 void startTest() { 175 mTestRun = true; 176 177 java.util.Arrays.fill(mLatencyMS, 0.0); 178 java.util.Arrays.fill(mConfidence, 0.0); 179 } 180 recordPhase(int phase, double latencyMS, double confidence)181 void recordPhase(int phase, double latencyMS, double confidence) { 182 mLatencyMS[phase] = latencyMS; 183 mConfidence[phase] = confidence; 184 } 185 handleTestCompletion()186 void handleTestCompletion() { 187 mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS); 188 mMeanAbsoluteDeviation = 189 StatUtils.calculateMeanAbsoluteDeviation( 190 mMeanLatencyMS, mLatencyMS, mLatencyMS.length); 191 mMeanConfidence = StatUtils.calculateMean(mConfidence); 192 } 193 isMeasurementValid()194 boolean isMeasurementValid() { 195 return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence; 196 } 197 has24BitHardwareSupport()198 boolean has24BitHardwareSupport() { 199 return (mNativeAnalyzerThread != null) 200 && mNativeAnalyzerThread.has24BitHardwareSupport(); 201 } 202 getResultString()203 String getResultString() { 204 String result; 205 206 if (!mRouteAvailable) { 207 result = "Route Not Available"; 208 } else if (!mTestRun) { 209 result = "Test Not Run"; 210 } else if (mMeanConfidence < mRequiredConfidence) { 211 result = String.format( 212 "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.", 213 mMeanConfidence, mRequiredConfidence); 214 } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) { 215 result = String.format( 216 "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.", 217 mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS); 218 } else { 219 result = String.format( 220 "Test Finished\nMean Latency:%.2f ms\n" 221 + "Mean Absolute Deviation: %.2f\n" 222 + "Confidence: %.2f\n" 223 + "Low Latency Path: %s\n" 224 + "24 Bit Hardware Support: %s", 225 mMeanLatencyMS, 226 mMeanAbsoluteDeviation, 227 mMeanConfidence, 228 mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString, 229 mNativeAnalyzerThread.has24BitHardwareSupport() ? mYesString : mNoString); 230 } 231 232 return result; 233 } 234 235 // ReportLog Schema (per route) 236 private static final String KEY_ROUTEINDEX = "route_index"; 237 private static final String KEY_LATENCY = "latency"; 238 private static final String KEY_CONFIDENCE = "confidence"; 239 private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation"; 240 private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached"; 241 private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral"; 242 private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral"; 243 private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name"; 244 recordTestResults(CtsVerifierReportLog reportLog)245 void recordTestResults(CtsVerifierReportLog reportLog) { 246 reportLog.addValue( 247 KEY_ROUTEINDEX, 248 mRouteId, 249 ResultType.NEUTRAL, 250 ResultUnit.NONE); 251 252 reportLog.addValue( 253 KEY_LATENCY, 254 mMeanLatencyMS, 255 ResultType.LOWER_BETTER, 256 ResultUnit.MS); 257 258 reportLog.addValue( 259 KEY_CONFIDENCE, 260 mMeanConfidence, 261 ResultType.HIGHER_BETTER, 262 ResultUnit.NONE); 263 264 reportLog.addValue( 265 KEY_MEANABSDEVIATION, 266 mMeanAbsoluteDeviation, 267 ResultType.NEUTRAL, 268 ResultUnit.NONE); 269 270 reportLog.addValue( 271 KEY_TEST_PERIPHERAL_NAME, 272 mDeviceName, 273 ResultType.NEUTRAL, 274 ResultUnit.NONE); 275 } 276 } 277 278 @Override onCreate(Bundle savedInstanceState)279 protected void onCreate(Bundle savedInstanceState) { 280 super.onCreate(savedInstanceState); 281 282 setContentView(R.layout.audio_loopback_latency_activity); 283 284 setPassFailButtonClickListeners(); 285 setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1); 286 287 mRequireReportLogToPass = true; 288 289 mClaimsOutput = AudioSystemFlags.claimsOutput(this); 290 mClaimsInput = AudioSystemFlags.claimsInput(this); 291 mClaimsProAudio = AudioSystemFlags.claimsProAudio(this); 292 mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this); 293 mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0; 294 mIsWatch = AudioSystemFlags.isWatch(this); 295 mIsTV = AudioSystemFlags.isTV(this); 296 mIsAutomobile = AudioSystemFlags.isAutomobile(this); 297 mIsHandheld = AudioSystemFlags.isHandheld(this); 298 299 // Setup test specifications 300 double mustLatency; 301 302 // Speaker/Mic Path 303 mTestSpecs[TESTROUTE_DEVICE] = 304 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT); 305 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; // Always 306 307 // Analog Jack Path 308 mTestSpecs[TESTROUTE_ANALOG_JACK] = 309 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED); 310 311 // USB Path 312 mTestSpecs[TESTROUTE_USB] = 313 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED); 314 315 // Setup UI 316 Resources resources = getResources(); 317 mYesString = resources.getString(R.string.audio_general_yes); 318 mNoString = resources.getString(R.string.audio_general_no); 319 mPassString = resources.getString(R.string.audio_general_teststatus_pass); 320 mFailString = resources.getString(R.string.audio_general_teststatus_fail); 321 mNotTestedString = resources.getString(R.string.audio_general_not_tested); 322 mNotRequiredString = resources.getString(R.string.audio_general_not_required); 323 mRequiredString = resources.getString(R.string.audio_general_required); 324 325 // Pro Audio 326 ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText( 327 (mClaimsProAudio ? mYesString : mNoString)); 328 329 // Low Latency 330 ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText( 331 (mClaimsLowLatency ? mYesString : mNoString)); 332 333 // Media Performance Class 334 ((TextView) findViewById(R.id.audio_loopback_mpc)).setText( 335 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS) 336 : mNoString)); 337 338 // MMAP 339 ((TextView) findViewById(R.id.audio_loopback_mmap)).setText( 340 (mSupportsMMAP ? mYesString : mNoString)); 341 ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText( 342 (mSupportsMMAPExclusive ? mYesString : mNoString)); 343 344 // Device Type 345 ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText( 346 (mIsWatch ? mYesString : mNoString)); 347 ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText( 348 (mIsTV ? mYesString : mNoString)); 349 ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText( 350 (mIsAutomobile ? mYesString : mNoString)); 351 ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText( 352 (mIsHandheld ? mYesString : mNoString)); 353 354 // Individual Test Results 355 mResultsText[TESTROUTE_DEVICE] = 356 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info); 357 mResultsText[TESTROUTE_ANALOG_JACK] = 358 (TextView) findViewById(R.id.audio_loopback_headsetpath_info); 359 mResultsText[TESTROUTE_USB] = 360 (TextView) findViewById(R.id.audio_loopback_usbpath_info); 361 362 mStartButtons[TESTROUTE_DEVICE] = 363 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn); 364 mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener); 365 366 mStartButtons[TESTROUTE_ANALOG_JACK] = 367 (Button) findViewById(R.id.audio_loopback_headsetpath_btn); 368 mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener); 369 370 mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn); 371 mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener); 372 373 mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions); 374 375 mAudioManager = getSystemService(AudioManager.class); 376 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 377 378 connectLoopbackUI(); 379 380 if (mustRunTest()) { 381 getPassButton().setEnabled(false); 382 enableStartButtons(true); 383 } else { 384 getPassButton().setEnabled(isReportLogOkToPass()); 385 enableStartButtons(false); 386 } 387 388 mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler()); 389 390 showTestInstructions(); 391 handleTestCompletion(false); 392 } 393 394 // 395 // UI State 396 // showTestInstructions()397 private void showTestInstructions() { 398 if (mustRunTest()) { 399 mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths)); 400 } else { 401 mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required)); 402 } 403 } 404 enableStartButtons(boolean enable)405 private void enableStartButtons(boolean enable) { 406 if (enable) { 407 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 408 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected); 409 } 410 } else { 411 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 412 mStartButtons[routeId].setEnabled(false); 413 } 414 } 415 } 416 connectLoopbackUI()417 private void connectLoopbackUI() { 418 mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text); 419 mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar); 420 mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 421 mAudioLevelSeekbar.setMax(mMaxLevel); 422 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0); 423 refreshLevel(); 424 425 mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 426 @Override 427 public void onStopTrackingTouch(SeekBar seekBar) {} 428 429 @Override 430 public void onStartTrackingTouch(SeekBar seekBar) {} 431 432 @Override 433 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 434 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 435 progress, 0); 436 Log.i(TAG,"Level set to: " + progress); 437 refreshLevel(); 438 } 439 }); 440 441 mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text); 442 mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar); 443 showWait(false); 444 } 445 446 // 447 // Peripheral Connection Logic 448 // clearDeviceIds()449 void clearDeviceIds() { 450 for (TestSpec testSpec : mTestSpecs) { 451 testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE; 452 } 453 } 454 clearDeviceConnected()455 void clearDeviceConnected() { 456 for (TestSpec testSpec : mTestSpecs) { 457 testSpec.mRouteConnected = false; 458 } 459 } 460 scanPeripheralList(AudioDeviceInfo[] devices)461 void scanPeripheralList(AudioDeviceInfo[] devices) { 462 clearDeviceIds(); 463 clearDeviceConnected(); 464 465 mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 466 mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 467 for (AudioDeviceInfo devInfo : devices) { 468 switch (devInfo.getType()) { 469 // TESTROUTE_DEVICE (i.e. Speaker & Mic) 470 // This needs to be handled differently. The other devices can be assumed 471 // to contain both input & output devices in the same type. 472 // For built-in we need to see both TYPES to be sure to have both input & output. 473 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: 474 mSpeakerDeviceId = devInfo.getId(); 475 break; 476 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 477 mMicDeviceId = devInfo.getId(); 478 break; 479 480 // TESTROUTE_ANALOG_JACK 481 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 482 case AudioDeviceInfo.TYPE_AUX_LINE: 483 if (devInfo.isSink()) { 484 mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId(); 485 } else if (devInfo.isSource()) { 486 mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId(); 487 } 488 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true; 489 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true; 490 mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName = 491 devInfo.getProductName().toString(); 492 break; 493 494 // TESTROUTE_USB 495 case AudioDeviceInfo.TYPE_USB_DEVICE: 496 case AudioDeviceInfo.TYPE_USB_HEADSET: 497 if (devInfo.isSink()) { 498 mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId(); 499 } else if (devInfo.isSource()) { 500 mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId(); 501 } 502 mTestSpecs[TESTROUTE_USB].mRouteAvailable = true; 503 mTestSpecs[TESTROUTE_USB].mRouteConnected = true; 504 mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString(); 505 } 506 } 507 508 // do we have BOTH a Speaker and Mic? 509 if (hasInternalPath()) { 510 mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId; 511 mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId; 512 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; 513 mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true; 514 mTestSpecs[TESTROUTE_DEVICE].mDeviceName = 515 getResources().getString(R.string.audio_loopback_test_internal_devices); 516 } 517 518 enableStartButtons(mustRunTest()); 519 } 520 521 private class ConnectListener extends AudioDeviceCallback { ConnectListener()522 ConnectListener() {} 523 524 // 525 // AudioDevicesManager.OnDeviceConnectionListener 526 // 527 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)528 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 529 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 530 } 531 532 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)533 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 534 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 535 } 536 } 537 538 /** 539 * refresh Audio Level seekbar and text 540 */ refreshLevel()541 private void refreshLevel() { 542 int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 543 mAudioLevelSeekbar.setProgress(currentLevel); 544 545 String levelText = String.format("%s: %d/%d", 546 getResources().getString(R.string.audio_loopback_level_text), 547 currentLevel, mMaxLevel); 548 mAudioLevelText.setText(levelText); 549 } 550 551 // 552 // show active progress bar 553 // showWait(boolean show)554 protected void showWait(boolean show) { 555 mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 556 } 557 558 // 559 // Common logging 560 // 561 562 @Override getTestId()563 public String getTestId() { 564 return setTestNameSuffix(sCurrentDisplayMode, getClass().getName()); 565 } 566 567 @Override requiresReportLog()568 public boolean requiresReportLog() { 569 return true; 570 } 571 572 @Override getReportFileName()573 public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; } 574 575 @Override getReportSectionName()576 public final String getReportSectionName() { 577 return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity"); 578 } 579 580 // Test-Schema 581 private static final String KEY_SAMPLE_RATE = "sample_rate"; 582 private static final String KEY_IS_PRO_AUDIO = "is_pro_audio"; 583 private static final String KEY_IS_LOW_LATENCY = "is_low_latency"; 584 private static final String KEY_TEST_MMAP = "supports_mmap"; 585 private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive"; 586 private static final String KEY_LEVEL = "level"; 587 private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT = 588 "has_24_bit_hardware_support"; 589 recordRouteResults(int routeIndex)590 private void recordRouteResults(int routeIndex) { 591 if (mTestSpecs[routeIndex].mTestRun) { 592 CtsVerifierReportLog reportLog = newReportLog(); 593 594 int audioLevel = mAudioLevelSeekbar.getProgress(); 595 reportLog.addValue( 596 KEY_LEVEL, 597 audioLevel, 598 ResultType.NEUTRAL, 599 ResultUnit.NONE); 600 601 reportLog.addValue( 602 KEY_IS_PRO_AUDIO, 603 mClaimsProAudio, 604 ResultType.NEUTRAL, 605 ResultUnit.NONE); 606 607 reportLog.addValue( 608 KEY_TEST_MMAP, 609 mSupportsMMAP, 610 ResultType.NEUTRAL, 611 ResultUnit.NONE); 612 613 reportLog.addValue( 614 KEY_TEST_MMAPEXCLUSIVE, 615 mSupportsMMAPExclusive, 616 ResultType.NEUTRAL, 617 ResultUnit.NONE); 618 619 reportLog.addValue( 620 KEY_SAMPLE_RATE, 621 mNativeAnalyzerThread.getSampleRate(), 622 ResultType.NEUTRAL, 623 ResultUnit.NONE); 624 625 reportLog.addValue( 626 KEY_IS_LOW_LATENCY, 627 mNativeAnalyzerThread.isLowLatencyStream(), 628 ResultType.NEUTRAL, 629 ResultUnit.NONE); 630 631 reportLog.addValue( 632 KEY_HAS_24_BIT_HARDWARE_SUPPORT, 633 mNativeAnalyzerThread.has24BitHardwareSupport(), 634 ResultType.NEUTRAL, 635 ResultUnit.NONE); 636 637 mTestSpecs[routeIndex].recordTestResults(reportLog); 638 639 reportLog.submit(); 640 } 641 } 642 643 @Override recordTestResults()644 public void recordTestResults() { 645 // Look for a valid route with the minimum latency. 646 int bestRoute = -1; 647 double minLatency = Double.MAX_VALUE; 648 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 649 if (mTestSpecs[route].isMeasurementValid()) { 650 if (mTestSpecs[route].mMeanLatencyMS < minLatency) { 651 bestRoute = route; 652 minLatency = mTestSpecs[route].mMeanLatencyMS; 653 } 654 } 655 } 656 // Record a single result. 657 if (bestRoute >= 0) { 658 recordRouteResults(bestRoute); 659 } 660 } 661 startAudioTest(Handler messageHandler, int testRouteId)662 private void startAudioTest(Handler messageHandler, int testRouteId) { 663 enableStartButtons(false); 664 mResultsText[testRouteId].setText("Running..."); 665 666 mTestRoute = testRouteId; 667 668 mTestSpecs[mTestRoute].startTest(); 669 670 getPassButton().setEnabled(false); 671 672 mTestPhase = 0; 673 674 mNativeAnalyzerThread = new NativeAnalyzerThread(this); 675 if (mNativeAnalyzerThread != null) { 676 mNativeAnalyzerThread.setMessageHandler(messageHandler); 677 // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 678 mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION); 679 startTestPhase(); 680 } else { 681 Log.e(TAG, "Couldn't allocate native analyzer thread"); 682 mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure)); 683 } 684 } 685 startTestPhase()686 private void startTestPhase() { 687 if (mNativeAnalyzerThread != null) { 688 Log.i(TAG, "mTestRoute: " + mTestRoute 689 + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId 690 + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId); 691 mNativeAnalyzerThread.startTest( 692 mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId); 693 694 // what is this for? 695 try { 696 Thread.sleep(200); 697 } catch (InterruptedException e) { 698 e.printStackTrace(); 699 } 700 } 701 } 702 handleTestPhaseCompletion()703 private void handleTestPhaseCompletion() { 704 if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) { 705 double latency = mNativeAnalyzerThread.getLatencyMillis(); 706 double confidence = mNativeAnalyzerThread.getConfidence(); 707 TestSpec testSpec = mTestSpecs[mTestRoute]; 708 testSpec.recordPhase(mTestPhase, latency, confidence); 709 710 String result = String.format( 711 "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n", 712 mTestPhase, latency, confidence); 713 714 mTestStatusText.setText(result); 715 try { 716 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 717 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500); 718 } catch (InterruptedException e) { 719 e.printStackTrace(); 720 } 721 722 723 mTestPhase++; 724 if (mTestPhase >= NUM_TEST_PHASES) { 725 handleTestCompletion(true); 726 } else { 727 startTestPhase(); 728 } 729 } 730 } 731 generateStatusString( LoopbackLatencyRequirements requirements, boolean showResult)732 private String generateStatusString( 733 LoopbackLatencyRequirements requirements, boolean showResult) { 734 735 if (!isReportLogOkToPass()) { 736 return getResources().getString(R.string.audio_general_reportlogtest); 737 } 738 739 if (!mustRunTest()) { 740 return getResources().getString(R.string.audio_loopback_test_non_handheld); 741 } 742 743 boolean pass = calcPass(requirements); 744 StringBuilder sb = new StringBuilder(); 745 sb.append(requirements.getResultsString()); 746 if (showResult) { 747 sb.append("\n" + (pass ? mPassString : mFailString)); 748 } 749 return sb.toString(); 750 } 751 mustRunTest()752 private boolean mustRunTest() { 753 return mIsHandheld && hasInternalPath(); 754 } 755 hasInternalPath()756 boolean hasInternalPath() { 757 return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN 758 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN; 759 } 760 calcPass(LoopbackLatencyRequirements requirements)761 private boolean calcPass(LoopbackLatencyRequirements requirements) { 762 if (!isReportLogOkToPass()) { 763 // Can't pass if we can't write the ReportLog 764 return false; 765 } 766 if (!mustRunTest()) { 767 // just grant a pass on non-handheld devices 768 return true; 769 } 770 boolean pass = requirements.evaluate(mClaimsProAudio, 771 Build.VERSION.MEDIA_PERFORMANCE_CLASS, 772 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 773 ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0, 774 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 775 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS : 0.0, 776 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 777 ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0, 778 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(), 779 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport()); 780 781 return pass; 782 } 783 handleTestCompletion(boolean showResult)784 private void handleTestCompletion(boolean showResult) { 785 TestSpec testSpec = mTestSpecs[mTestRoute]; 786 testSpec.handleTestCompletion(); 787 788 // Make sure the test thread is finished. It should already be done. 789 if (mNativeAnalyzerThread != null) { 790 try { 791 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 792 } catch (InterruptedException e) { 793 e.printStackTrace(); 794 } 795 } 796 797 mResultsText[mTestRoute].setText(testSpec.getResultString()); 798 799 LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements(); 800 boolean pass = calcPass(requirements); 801 802 getPassButton().setEnabled(pass); 803 804 mTestStatusText.setText(generateStatusString(requirements, showResult)); 805 806 showWait(false); 807 enableStartButtons(mustRunTest()); 808 } 809 810 /** 811 * handler for messages from audio thread 812 */ 813 private Handler mMessageHandler = new Handler() { 814 public void handleMessage(Message msg) { 815 super.handleMessage(msg); 816 switch(msg.what) { 817 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED: 818 Log.v(TAG,"got message native rec started!!"); 819 showWait(true); 820 mTestStatusText.setText(String.format("[phase: %d] - Test Running...", 821 (mTestPhase + 1))); 822 break; 823 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR: 824 Log.v(TAG,"got message native rec can't start!!"); 825 mTestStatusText.setText("Test Error opening streams."); 826 handleTestCompletion(true); 827 break; 828 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR: 829 Log.v(TAG,"got message native rec can't start!!"); 830 mTestStatusText.setText("Test Error while recording."); 831 handleTestCompletion(true); 832 break; 833 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS: 834 mTestStatusText.setText("Test FAILED due to errors."); 835 handleTestCompletion(true); 836 break; 837 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING: 838 mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...", 839 mTestPhase + 1)); 840 break; 841 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE: 842 handleTestPhaseCompletion(); 843 break; 844 default: 845 break; 846 } 847 } 848 }; 849 850 private class OnBtnClickListener implements OnClickListener { 851 @Override onClick(View v)852 public void onClick(View v) { 853 int id = v.getId(); 854 if (id == R.id.audio_loopback_speakermicpath_btn) { 855 startAudioTest(mMessageHandler, TESTROUTE_DEVICE); 856 } else if (id == R.id.audio_loopback_headsetpath_btn) { 857 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK); 858 } else if (id == R.id.audio_loopback_usbpath_btn) { 859 startAudioTest(mMessageHandler, TESTROUTE_USB); 860 } 861 } 862 } 863 864 class LoopbackLatencyRequirements { 865 public static final int MPC_NONE = 0; 866 public static final int MPC_R = Build.VERSION_CODES.R; 867 public static final int MPC_S = Build.VERSION_CODES.S; 868 public static final int MPC_T = Build.VERSION_CODES.TIRAMISU; 869 870 String mResultsString = new String(); 871 getResultsString()872 String getResultsString() { 873 return mResultsString; 874 } 875 876 static final int RESULTCODE_NONE = 0; 877 static final int RESULTCODE_PASS = 1; 878 static final int RESULTCODE_FAIL_NOINTERNAL = 2; 879 static final int RESULTCODE_FAIL_BASIC = 3; 880 static final int RESULTCODE_FAIL_MPC = 4; 881 static final int RESULTCODE_FAIL_PROONEPATH = 5; 882 static final int RESULTCODE_FAIL_PROLIMITS_ANALOG = 6; 883 static final int RESULTCODE_FAIL_PROLIMITS_USB = 7; 884 static final int RESULTCODE_FAIL_24BIT = 8; 885 static final int RESULTCODE_FAIL_PRO_NOWIRED = 9; 886 int mResultCode = 0; 887 checkLatency(double measured, double limit)888 private boolean checkLatency(double measured, double limit) { 889 return measured == LATENCY_NOT_MEASURED || measured <= limit; 890 } 891 setResultCode(boolean pass, int code)892 private void setResultCode(boolean pass, int code) { 893 // only set the first non-none result code 894 Log.i(TAG, "setResultCode(" + pass + ", " + code + ")"); 895 if (!pass && mResultCode == RESULTCODE_NONE) { 896 mResultCode = code; 897 } 898 } 899 getResultCodeText()900 public String getResultCodeText() { 901 Resources resources = getResources(); 902 switch (mResultCode) { 903 case RESULTCODE_NONE: 904 return resources.getString(R.string.audio_loopback_resultcode_none); 905 case RESULTCODE_PASS: 906 return resources.getString(R.string.audio_loopback_resultcode_pass); 907 case RESULTCODE_FAIL_NOINTERNAL: 908 return resources.getString( 909 R.string.audio_loopback_resultcode_nointernal); 910 case RESULTCODE_FAIL_BASIC: 911 return resources.getString( 912 R.string.audio_loopback_resultcode_failbasic); 913 case RESULTCODE_FAIL_MPC: 914 return resources.getString(R.string.audio_loopback_resultcode_failmpc); 915 case RESULTCODE_FAIL_PROONEPATH: 916 return resources.getString(R.string.audio_loopback_resultcode_failpro); 917 case RESULTCODE_FAIL_PROLIMITS_ANALOG: 918 return resources.getString(R.string.audio_loopback_resultcode_failproanalog); 919 case RESULTCODE_FAIL_PROLIMITS_USB: 920 return resources.getString(R.string.audio_loopback_resultcode_failprousb); 921 case RESULTCODE_FAIL_24BIT: 922 return resources.getString(R.string.audio_loopback_resultcode_fail24bit); 923 case RESULTCODE_FAIL_PRO_NOWIRED: 924 return resources.getString( 925 R.string.audio_loopback_resultcode_failproaudiowired); 926 default: 927 // this should never happen 928 return resources.getString(R.string.audio_loopback_resultcode_invalid); 929 } 930 } 931 evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport)932 public boolean evaluate(boolean proAudio, 933 int mediaPerformanceClass, 934 double deviceLatency, 935 double analogLatency, 936 double usbLatency, 937 boolean analog24BitHardwareSupport, 938 boolean usb24BitHardwareSupport) { 939 940 Log.i(TAG, "evaluate()"); 941 mResultCode = RESULTCODE_NONE; 942 943 // Required to test the Mic/Speaker path 944 boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED; 945 Log.i(TAG, " internalPathRun:" + internalPathRun); 946 setResultCode(internalPathRun, RESULTCODE_FAIL_NOINTERNAL); 947 948 // All devices must be under the basic limit. 949 boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC) 950 && checkLatency(analogLatency, LATENCY_BASIC) 951 && checkLatency(usbLatency, LATENCY_BASIC); 952 Log.i(TAG, " basicPass:" + basicPass); 953 setResultCode(basicPass, RESULTCODE_FAIL_BASIC); 954 955 // For Media Performance Class T the RT latency must be <= 80 msec on one path. 956 boolean mpcAtLeastOnePass = (mediaPerformanceClass < MPC_T) 957 || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE) 958 || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE) 959 || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE); 960 Log.i(TAG, " mpcAtLeastOnePass:" + mpcAtLeastOnePass); 961 setResultCode(mpcAtLeastOnePass, RESULTCODE_FAIL_MPC); 962 963 // For ProAudio, the RT latency must be <= 25 msec on one path. 964 boolean proAudioAtLeastOnePass = !proAudio 965 || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 966 || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 967 || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE); 968 Log.i(TAG, " proAudioAtLeastOnePass:" + proAudioAtLeastOnePass); 969 setResultCode(proAudioAtLeastOnePass, RESULTCODE_FAIL_PROONEPATH); 970 971 // For ProAudio, analog and USB have specific limits 972 boolean proAudioLimitsPass = !proAudio; 973 if (proAudio) { 974 if (analogLatency > 0.0) { 975 proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG; 976 setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_ANALOG); 977 } else if (usbLatency > 0.0) { 978 // USB audio must be supported if 3.5mm jack not supported 979 proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB; 980 setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_USB); 981 } 982 } 983 984 if (!proAudioLimitsPass) { 985 setResultCode(false, RESULTCODE_FAIL_PRO_NOWIRED); 986 } 987 988 // For Media Performance Class T, usb and analog should support >=24 bit audio. 989 boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T) 990 || analog24BitHardwareSupport || usb24BitHardwareSupport; 991 Log.i(TAG, " has24BitHardwareSupportPass:" + has24BitHardwareSupportPass); 992 setResultCode(has24BitHardwareSupportPass, RESULTCODE_FAIL_24BIT); 993 994 boolean pass = 995 internalPathRun 996 && basicPass 997 && mpcAtLeastOnePass 998 && proAudioAtLeastOnePass 999 && proAudioLimitsPass 1000 && has24BitHardwareSupportPass; 1001 Log.i(TAG, " pass:" + pass); 1002 1003 // Build the results explanation 1004 StringBuilder sb = new StringBuilder(); 1005 if (proAudio) { 1006 sb.append("[Pro Audio]"); 1007 } else if (mediaPerformanceClass != MPC_NONE) { 1008 sb.append("[MPC " + mediaPerformanceClass + "]"); 1009 } else { 1010 sb.append("[Basic Audio]"); 1011 } 1012 sb.append(" "); 1013 1014 sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED 1015 ? String.format("%.2fms ", deviceLatency) 1016 : (mNotTestedString + " - " + mRequiredString))); 1017 sb.append("\nHeadset: " + (analogLatency != LATENCY_NOT_MEASURED 1018 ? String.format("%.2fms ", analogLatency) 1019 : (mNotTestedString + " - " + mNotRequiredString))); 1020 sb.append("\nUSB: " + (usbLatency != LATENCY_NOT_MEASURED 1021 ? String.format("%.2fms ", usbLatency) 1022 : (mNotTestedString + " - " + mNotRequiredString))); 1023 1024 mResultsString = sb.toString(); 1025 if (mResultCode > RESULTCODE_PASS) { 1026 mResultsString = mResultsString + "\n" + getResultCodeText(); 1027 } 1028 1029 return pass; 1030 } 1031 } 1032 } 1033