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.Context; 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.TextView; 37 38 import com.android.compatibility.common.util.CddTest; 39 import com.android.compatibility.common.util.ResultType; 40 import com.android.compatibility.common.util.ResultUnit; 41 import com.android.cts.verifier.CtsVerifierReportLog; 42 import com.android.cts.verifier.PassFailButtons; 43 import com.android.cts.verifier.R; 44 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils; 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.DisplayUtils; 48 import com.android.cts.verifier.audio.audiolib.StatUtils; 49 50 import org.hyphonate.megaaudio.common.Globals; 51 import org.hyphonate.megaaudio.common.StreamBase; 52 import org.json.JSONArray; 53 import org.json.JSONException; 54 import org.json.JSONObject; 55 56 import java.util.Locale; 57 58 /** 59 * CtsVerifier Audio Loopback Latency Test 60 */ 61 @CddTest(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-3"}) 62 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity { 63 private static final String TAG = "AudioLoopbackLatencyActivity"; 64 private static final boolean LOG = false; 65 66 // JNI load 67 static { 68 try { 69 System.loadLibrary("audioloopback_jni"); 70 } catch (UnsatisfiedLinkError e) { 71 Log.e(TAG, "Error loading Audio Loopback JNI library"); 72 Log.e(TAG, "e: " + e); 73 e.printStackTrace(); 74 } 75 76 /* TODO: gracefully fail/notify if the library can't be loaded */ 77 } 78 79 Context mContext; 80 protected AudioManager mAudioManager; 81 82 // UI 83 TextView[] mRouteStatus = new TextView[NUM_TEST_ROUTES]; 84 85 TextView mTestStatusText; 86 ProgressBar mProgressBar; 87 int mMaxLevel; 88 89 TextView mTestInstructions; 90 91 private OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 92 private Button[] mStartButtons = new Button[NUM_TEST_ROUTES]; 93 94 private Button mCalibrateAudioButton; 95 private Button mAudioDevicesButton; 96 97 String mYesString; 98 String mNoString; 99 100 String mPassString; 101 String mFailString; 102 String mNotTestedString; 103 String mRequiredString; 104 String mNoHardwareString; 105 String mUnknownHardwareString; 106 107 // These flags determine the maximum allowed latency 108 private boolean mClaimsProAudio; 109 private boolean mClaimsLowLatency; 110 private boolean mClaimsMediaPerformance; 111 private boolean mClaimsOutput; 112 private boolean mClaimsInput; 113 114 // Useful info 115 private boolean mSupportsMMAP = AudioUtils.isMMapSupported(); 116 private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported(); 117 118 private boolean mIsWatch; 119 private boolean mIsTV; 120 private boolean mIsAutomobile; 121 private boolean mIsHandheld; 122 private int mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 123 private int mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 124 125 private int mUSBAudioSupport; 126 private int mAnalogJackSupport; 127 128 // Peripheral(s) 129 private static final int NUM_TEST_ROUTES = 3; 130 private static final int TESTROUTE_DEVICE = 0; // device speaker + mic 131 private static final int TESTROUTE_ANALOG_JACK = 1; 132 private static final int TESTROUTE_USB = 2; 133 private int mTestRoute = TESTROUTE_DEVICE; 134 135 // Loopback Logic 136 private NativeAnalyzerThread mNativeAnalyzerThread = null; 137 138 protected static final int NUM_TEST_PHASES = 5; 139 protected int mTestPhase = 0; 140 141 private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6; 142 private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6; 143 144 public static final double LATENCY_NOT_MEASURED = 0.0; 145 public static final double LATENCY_BASIC = 250.0; // chaned from 300 in CDD 15 for VIC 146 public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0; 147 public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0; 148 public static final double LATENCY_PRO_AUDIO_USB = 25.0; 149 public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0; 150 151 public static final double TIMESTAMP_ACCURACY_MS = 30.0; 152 153 // The audio stream callback threads should stop and close 154 // in less than a few hundred msec. This is a generous timeout value. 155 private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000; 156 157 private static final String LOG_ERROR_STR = "Could not log metric."; 158 159 private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES]; 160 class TestSpec { 161 private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec"; 162 // impossibly low latencies (indicating something in the test went wrong). 163 protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0; 164 165 final int mRouteId; 166 167 // runtime assigned device ID 168 static final int DEVICEID_NONE = -1; 169 int mInputDeviceId; 170 int mOutputDeviceId; 171 172 String mDeviceName; 173 174 double[] mLatencyMS = new double[NUM_TEST_PHASES]; 175 double[] mConfidence = new double[NUM_TEST_PHASES]; 176 177 double[] mTimestampLatencyMS = new double[NUM_TEST_PHASES]; 178 179 double mMeanLatencyMS; 180 double mMeanAbsoluteDeviation; 181 double mMeanConfidence; 182 double mRequiredConfidence; 183 double mMeanTimestampLatencyMS; 184 int mSampleRate; 185 boolean mIsLowLatencyStream; 186 boolean mHas24BitHardwareSupport; 187 int mHardwareFormat; 188 189 boolean mRouteAvailable; // Have we seen this route/device at any time 190 boolean mRouteConnected; // is the route available NOW 191 boolean mTestRun; 192 TestSpec(int routeId, double requiredConfidence)193 TestSpec(int routeId, double requiredConfidence) { 194 mRouteId = routeId; 195 mRequiredConfidence = requiredConfidence; 196 197 mInputDeviceId = DEVICEID_NONE; 198 mOutputDeviceId = DEVICEID_NONE; 199 200 // Default to true if test not run. 201 mHas24BitHardwareSupport = true; 202 } 203 startTest()204 void startTest() { 205 mTestRun = true; 206 207 java.util.Arrays.fill(mLatencyMS, 0.0); 208 java.util.Arrays.fill(mConfidence, 0.0); 209 java.util.Arrays.fill(mTimestampLatencyMS, 0.0); 210 } 211 recordPhase(int phase, double latencyMS, double confidence, double timestampLatencyMS)212 void recordPhase(int phase, double latencyMS, double confidence, 213 double timestampLatencyMS) { 214 mLatencyMS[phase] = latencyMS; 215 mConfidence[phase] = confidence; 216 mTimestampLatencyMS[phase] = timestampLatencyMS; 217 } 218 handleTestCompletion()219 void handleTestCompletion() { 220 mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS); 221 mMeanAbsoluteDeviation = 222 StatUtils.calculateMeanAbsoluteDeviation( 223 mMeanLatencyMS, mLatencyMS, mLatencyMS.length); 224 mMeanConfidence = StatUtils.calculateMean(mConfidence); 225 mMeanTimestampLatencyMS = StatUtils.calculateMean(mTimestampLatencyMS); 226 if (mNativeAnalyzerThread != null) { 227 mSampleRate = mNativeAnalyzerThread.getSampleRate(); 228 mIsLowLatencyStream = mNativeAnalyzerThread.isLowLatencyStream(); 229 mHas24BitHardwareSupport = mNativeAnalyzerThread.has24BitHardwareSupport(); 230 mHardwareFormat = mNativeAnalyzerThread.getHardwareFormat(); 231 } 232 } 233 isMeasurementValid()234 boolean isMeasurementValid() { 235 return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence; 236 } 237 has24BitHardwareSupport()238 boolean has24BitHardwareSupport() { 239 return mHas24BitHardwareSupport; 240 } 241 getResultString()242 String getResultString() { 243 String result; 244 245 if (!mRouteAvailable) { 246 result = "Route Not Available"; 247 } else if (!mTestRun) { 248 result = "Test Not Run"; 249 } else if (mMeanConfidence < mRequiredConfidence) { 250 result = String.format( 251 "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.", 252 mMeanConfidence, mRequiredConfidence); 253 } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) { 254 result = String.format( 255 "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.", 256 mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS); 257 } else { 258 result = String.format( 259 "Test Finished\nMean Latency:%.2f ms\n" 260 + "Mean Absolute Deviation: %.2f\n" 261 + "Confidence: %.2f\n" 262 + "Low Latency Path: %s\n" 263 + "24 Bit Hardware Support: %s\n" 264 + "Timestamp Latency:%.2f ms", 265 mMeanLatencyMS, 266 mMeanAbsoluteDeviation, 267 mMeanConfidence, 268 mIsLowLatencyStream ? mYesString : mNoString, 269 mHas24BitHardwareSupport ? mYesString : mNoString, 270 mMeanTimestampLatencyMS); 271 } 272 273 return result; 274 } 275 276 // ReportLog Schema (per route) 277 private static final String KEY_ROUTEINDEX = "route_index"; 278 private static final String KEY_LATENCY = "latency"; 279 private static final String KEY_CONFIDENCE = "confidence"; 280 private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation"; 281 private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached"; 282 private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral"; 283 private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral"; 284 private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name"; 285 private static final String KEY_TIMESTAMP_LATENCY = "timestamp_latency"; 286 private static final String KEY_SAMPLE_RATE = "sample_rate"; 287 private static final String KEY_IS_LOW_LATENCY = "is_low_latency"; 288 private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT = 289 "has_24_bit_hardware_support"; 290 private static final String KEY_HARDWARE_FORMAT = "hardware_format"; 291 recordTestResults(CtsVerifierReportLog reportLog)292 void recordTestResults(CtsVerifierReportLog reportLog) { 293 reportLog.addValue( 294 KEY_ROUTEINDEX, 295 mRouteId, 296 ResultType.NEUTRAL, 297 ResultUnit.NONE); 298 299 reportLog.addValue( 300 KEY_LATENCY, 301 mMeanLatencyMS, 302 ResultType.LOWER_BETTER, 303 ResultUnit.MS); 304 305 reportLog.addValue( 306 KEY_CONFIDENCE, 307 mMeanConfidence, 308 ResultType.HIGHER_BETTER, 309 ResultUnit.NONE); 310 311 reportLog.addValue( 312 KEY_MEANABSDEVIATION, 313 mMeanAbsoluteDeviation, 314 ResultType.NEUTRAL, 315 ResultUnit.NONE); 316 317 reportLog.addValue( 318 KEY_TEST_PERIPHERAL_NAME, 319 mDeviceName, 320 ResultType.NEUTRAL, 321 ResultUnit.NONE); 322 323 reportLog.addValue( 324 KEY_TIMESTAMP_LATENCY, 325 mMeanTimestampLatencyMS, 326 ResultType.NEUTRAL, 327 ResultUnit.NONE); 328 329 reportLog.addValue( 330 KEY_SAMPLE_RATE, 331 mSampleRate, 332 ResultType.NEUTRAL, 333 ResultUnit.NONE); 334 335 reportLog.addValue( 336 KEY_IS_LOW_LATENCY, 337 mIsLowLatencyStream, 338 ResultType.NEUTRAL, 339 ResultUnit.NONE); 340 341 reportLog.addValue( 342 KEY_HAS_24_BIT_HARDWARE_SUPPORT, 343 mHas24BitHardwareSupport, 344 ResultType.NEUTRAL, 345 ResultUnit.NONE); 346 347 reportLog.addValue( 348 KEY_HARDWARE_FORMAT, 349 mHardwareFormat, 350 ResultType.NEUTRAL, 351 ResultUnit.NONE); 352 } 353 addToJson(JSONObject jsonObject)354 void addToJson(JSONObject jsonObject) { 355 try { 356 jsonObject.put( 357 KEY_ROUTEINDEX, 358 mRouteId); 359 360 jsonObject.put( 361 KEY_LATENCY, 362 mMeanLatencyMS); 363 364 jsonObject.put( 365 KEY_CONFIDENCE, 366 mMeanConfidence); 367 368 jsonObject.put( 369 KEY_MEANABSDEVIATION, 370 mMeanAbsoluteDeviation); 371 372 jsonObject.put( 373 KEY_TEST_PERIPHERAL_NAME, 374 mDeviceName); 375 376 jsonObject.put( 377 KEY_TIMESTAMP_LATENCY, 378 mMeanTimestampLatencyMS); 379 380 jsonObject.put( 381 KEY_SAMPLE_RATE, 382 mSampleRate); 383 384 jsonObject.put( 385 KEY_IS_LOW_LATENCY, 386 mIsLowLatencyStream); 387 388 jsonObject.put( 389 KEY_HAS_24_BIT_HARDWARE_SUPPORT, 390 mHas24BitHardwareSupport); 391 392 jsonObject.put( 393 KEY_HARDWARE_FORMAT, 394 mHardwareFormat); 395 } catch (JSONException e) { 396 Log.e(TAG, LOG_ERROR_STR, e); 397 } 398 } 399 } 400 401 @Override onCreate(Bundle savedInstanceState)402 protected void onCreate(Bundle savedInstanceState) { 403 setContentView(R.layout.audio_loopback_latency_activity); 404 405 super.onCreate(savedInstanceState); 406 407 mContext = this; 408 409 // MegaAudio Initialization 410 StreamBase.setup(this); 411 412 setPassFailButtonClickListeners(); 413 setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1); 414 415 mRequireReportLogToPass = true; 416 417 mClaimsOutput = AudioSystemFlags.claimsOutput(this); 418 mClaimsInput = AudioSystemFlags.claimsInput(this); 419 mClaimsProAudio = AudioSystemFlags.claimsProAudio(this); 420 mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this); 421 mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0; 422 mIsWatch = AudioSystemFlags.isWatch(this); 423 mIsTV = AudioSystemFlags.isTV(this); 424 mIsAutomobile = AudioSystemFlags.isAutomobile(this); 425 mIsHandheld = AudioSystemFlags.isHandheld(this); 426 427 mUSBAudioSupport = AudioDeviceUtils.supportsUsbAudio(this); 428 mAnalogJackSupport = AudioDeviceUtils.supportsAnalogHeadset(this); 429 430 // Setup test specifications 431 double mustLatency; 432 433 // Speaker/Mic Path 434 mTestSpecs[TESTROUTE_DEVICE] = 435 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT); 436 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; // Always 437 438 // Analog Jack Path 439 mTestSpecs[TESTROUTE_ANALOG_JACK] = 440 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED); 441 442 // USB Path 443 mTestSpecs[TESTROUTE_USB] = 444 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED); 445 446 // Setup UI 447 mYesString = getString(R.string.audio_general_yes); 448 mNoString = getString(R.string.audio_general_no); 449 mPassString = getString(R.string.audio_general_teststatus_pass); 450 mFailString = getString(R.string.audio_general_teststatus_fail); 451 mNotTestedString = getString(R.string.audio_general_not_tested); 452 mRequiredString = getString(R.string.audio_general_required); 453 mNoHardwareString = getString(R.string.audio_general_nohardware); 454 mUnknownHardwareString = getString(R.string.audio_general_unknownhardware); 455 456 // Pro Audio 457 ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText( 458 (mClaimsProAudio ? mYesString : mNoString)); 459 460 // Low Latency 461 ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText( 462 (mClaimsLowLatency ? mYesString : mNoString)); 463 464 // Media Performance Class 465 ((TextView) findViewById(R.id.audio_loopback_mpc)).setText( 466 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS) 467 : mNoString)); 468 469 // MMAP 470 ((TextView) findViewById(R.id.audio_loopback_mmap)).setText( 471 (mSupportsMMAP ? mYesString : mNoString)); 472 ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText( 473 (mSupportsMMAPExclusive ? mYesString : mNoString)); 474 475 // Device Type 476 ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText( 477 (mIsWatch ? mYesString : mNoString)); 478 ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText( 479 (mIsTV ? mYesString : mNoString)); 480 ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText( 481 (mIsAutomobile ? mYesString : mNoString)); 482 ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText( 483 (mIsHandheld ? mYesString : mNoString)); 484 485 // Individual Test Results 486 mRouteStatus[TESTROUTE_DEVICE] = 487 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info); 488 mRouteStatus[TESTROUTE_ANALOG_JACK] = 489 (TextView) findViewById(R.id.audio_loopback_headsetpath_info); 490 mRouteStatus[TESTROUTE_USB] = 491 (TextView) findViewById(R.id.audio_loopback_usbpath_info); 492 493 mStartButtons[TESTROUTE_DEVICE] = 494 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn); 495 mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener); 496 497 mStartButtons[TESTROUTE_ANALOG_JACK] = 498 (Button) findViewById(R.id.audio_loopback_headsetpath_btn); 499 mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener); 500 501 mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn); 502 mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener); 503 504 mCalibrateAudioButton = findViewById(R.id.audio_loopback_calibrate_button); 505 mCalibrateAudioButton.setOnClickListener(mBtnClickListener); 506 507 mAudioDevicesButton = findViewById(R.id.audio_loopback_devsupport_button); 508 mAudioDevicesButton.setOnClickListener(mBtnClickListener); 509 510 mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions); 511 512 mAudioManager = getSystemService(AudioManager.class); 513 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 514 515 connectLoopbackUI(); 516 517 if (mustRunTest()) { 518 getPassButton().setEnabled(false); 519 enableStartButtons(true); 520 } else { 521 getPassButton().setEnabled(isReportLogOkToPass()); 522 enableStartButtons(false); 523 } 524 525 mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler()); 526 527 showRouteStatus(); 528 showTestInstructions(); 529 handleTestCompletion(false); 530 531 DisplayUtils.setKeepScreenOn(this, true); 532 } 533 534 // 535 // UI State 536 // showRouteStatus()537 private void showRouteStatus() { 538 // mRouteStatus[TESTROUTE_DEVICE]; 539 // Nothing to do for this route. 540 541 // mRouteStatus[TESTROUTE_ANALOG_JACK]; 542 switch (mAnalogJackSupport) { 543 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 544 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 545 getString(R.string.audio_loopback_noanalog)); 546 break; 547 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 548 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 549 getString(R.string.audio_loopback_headsetpath_instructions)); 550 break; 551 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 552 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 553 getString(R.string.audio_loopback_unknownanalog)); 554 break; 555 } 556 557 // mRouteStatus[TESTROUTE_USB]; 558 switch (mUSBAudioSupport) { 559 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 560 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_nousb)); 561 break; 562 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 563 mRouteStatus[TESTROUTE_USB].setText( 564 getString(R.string.audio_loopback_usbpath_instructions)); 565 break; 566 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 567 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_unknownusb)); 568 break; 569 } 570 } 571 showTestInstructions()572 private void showTestInstructions() { 573 if (mustRunTest()) { 574 mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths)); 575 } else { 576 mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required)); 577 } 578 } 579 enableStartButtons(boolean enable)580 private void enableStartButtons(boolean enable) { 581 if (enable) { 582 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 583 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected); 584 } 585 } else { 586 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 587 mStartButtons[routeId].setEnabled(false); 588 } 589 } 590 mCalibrateAudioButton.setEnabled(enable); 591 mAudioDevicesButton.setEnabled(enable); 592 } 593 connectLoopbackUI()594 private void connectLoopbackUI() { 595 mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text); 596 mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar); 597 showWait(false); 598 } 599 600 // 601 // Peripheral Connection Logic 602 // clearDeviceIds()603 void clearDeviceIds() { 604 for (TestSpec testSpec : mTestSpecs) { 605 testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE; 606 } 607 } 608 clearDeviceConnected()609 void clearDeviceConnected() { 610 for (TestSpec testSpec : mTestSpecs) { 611 testSpec.mRouteConnected = false; 612 } 613 } 614 scanPeripheralList(AudioDeviceInfo[] devices)615 void scanPeripheralList(AudioDeviceInfo[] devices) { 616 clearDeviceIds(); 617 clearDeviceConnected(); 618 619 mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 620 mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 621 for (AudioDeviceInfo devInfo : devices) { 622 switch (devInfo.getType()) { 623 // TESTROUTE_DEVICE (i.e. Speaker & Mic) 624 // This needs to be handled differently. The other devices can be assumed 625 // to contain both input & output devices in the same type. 626 // For built-in we need to see both TYPES to be sure to have both input & output. 627 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: 628 mSpeakerDeviceId = devInfo.getId(); 629 break; 630 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 631 mMicDeviceId = devInfo.getId(); 632 break; 633 634 // TESTROUTE_ANALOG_JACK 635 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 636 case AudioDeviceInfo.TYPE_AUX_LINE: 637 if (devInfo.isSink()) { 638 mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId(); 639 } else if (devInfo.isSource()) { 640 mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId(); 641 } 642 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true; 643 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true; 644 mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName = 645 devInfo.getProductName().toString(); 646 break; 647 648 // TESTROUTE_USB 649 case AudioDeviceInfo.TYPE_USB_DEVICE: 650 case AudioDeviceInfo.TYPE_USB_HEADSET: 651 if (devInfo.isSink()) { 652 mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId(); 653 } else if (devInfo.isSource()) { 654 mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId(); 655 } 656 mTestSpecs[TESTROUTE_USB].mRouteAvailable = true; 657 mTestSpecs[TESTROUTE_USB].mRouteConnected = true; 658 mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString(); 659 break; 660 } 661 } 662 663 // do we have BOTH a Speaker and Mic? 664 if (hasInternalPath()) { 665 mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId; 666 mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId; 667 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; 668 mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true; 669 mTestSpecs[TESTROUTE_DEVICE].mDeviceName = 670 getString(R.string.audio_loopback_test_internal_devices); 671 } 672 673 enableStartButtons(mustRunTest()); 674 } 675 676 private class ConnectListener extends AudioDeviceCallback { ConnectListener()677 ConnectListener() {} 678 679 // 680 // AudioDevicesManager.OnDeviceConnectionListener 681 // 682 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)683 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 684 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 685 AudioDeviceUtils.validateUsbDevice(mContext); 686 } 687 688 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)689 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 690 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 691 } 692 } 693 694 // 695 // show active progress bar 696 // showWait(boolean show)697 protected void showWait(boolean show) { 698 mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 699 } 700 701 // 702 // Common logging 703 // 704 705 @Override getTestId()706 public String getTestId() { 707 return setTestNameSuffix(sCurrentDisplayMode, getClass().getName()); 708 } 709 710 @Override requiresReportLog()711 public boolean requiresReportLog() { 712 return true; 713 } 714 715 @Override getReportFileName()716 public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; } 717 718 @Override getReportSectionName()719 public final String getReportSectionName() { 720 return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity"); 721 } 722 723 // Global Test-Schema 724 private static final String KEY_IS_PRO_AUDIO = "is_pro_audio"; 725 private static final String KEY_TEST_MMAP = "supports_mmap"; 726 private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive"; 727 private static final String KEY_LEVEL = "level"; 728 729 // Contains the results for all routes 730 private static final String KEY_PATHS = "paths"; 731 recordGlobalResults(CtsVerifierReportLog reportLog)732 private void recordGlobalResults(CtsVerifierReportLog reportLog) { 733 // Leave this in to be sure not to break the ReportLog injestion 734 reportLog.addValue( 735 KEY_LEVEL, 736 -1, 737 ResultType.NEUTRAL, 738 ResultUnit.NONE); 739 740 reportLog.addValue( 741 KEY_IS_PRO_AUDIO, 742 mClaimsProAudio, 743 ResultType.NEUTRAL, 744 ResultUnit.NONE); 745 746 reportLog.addValue( 747 KEY_TEST_MMAP, 748 mSupportsMMAP, 749 ResultType.NEUTRAL, 750 ResultUnit.NONE); 751 752 reportLog.addValue( 753 KEY_TEST_MMAPEXCLUSIVE, 754 mSupportsMMAPExclusive, 755 ResultType.NEUTRAL, 756 ResultUnit.NONE); 757 758 reportLog.addValue( 759 Common.KEY_VERSION_CODE, 760 Common.VERSION_CODE, 761 ResultType.NEUTRAL, 762 ResultUnit.NONE); 763 } 764 recordAllRoutes(CtsVerifierReportLog reportLog)765 private void recordAllRoutes(CtsVerifierReportLog reportLog) { 766 JSONArray jsonArray = new JSONArray(); 767 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 768 if (mTestSpecs[route].isMeasurementValid()) { 769 JSONObject jsonObject = new JSONObject(); 770 mTestSpecs[route].addToJson(jsonObject); 771 jsonArray.put(jsonObject); 772 } 773 } 774 775 if (jsonArray.length() > 0) { 776 reportLog.addValues(KEY_PATHS, jsonArray); 777 } 778 } 779 780 @Override recordTestResults()781 public void recordTestResults() { 782 // Look for a valid route with the minimum latency. 783 int bestRoute = -1; 784 double minLatency = Double.MAX_VALUE; 785 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 786 if (mTestSpecs[route].isMeasurementValid()) { 787 if (mTestSpecs[route].mMeanLatencyMS < minLatency) { 788 bestRoute = route; 789 minLatency = mTestSpecs[route].mMeanLatencyMS; 790 } 791 } 792 } 793 794 if (bestRoute >= 0) { 795 CtsVerifierReportLog reportLog = getReportLog(); 796 recordGlobalResults(reportLog); 797 mTestSpecs[bestRoute].recordTestResults(reportLog); 798 recordAllRoutes(reportLog); 799 reportLog.submit(); 800 } 801 } 802 startAudioTest(Handler messageHandler, int testRouteId)803 private void startAudioTest(Handler messageHandler, int testRouteId) { 804 enableStartButtons(false); 805 mRouteStatus[testRouteId].setText(getString(R.string.audio_loopback_running)); 806 807 mTestRoute = testRouteId; 808 809 mTestSpecs[mTestRoute].startTest(); 810 811 getPassButton().setEnabled(false); 812 813 mTestPhase = 0; 814 815 // Set mmap enabled as mmap supported to get best latency 816 Globals.setMMapEnabled(Globals.isMMapSupported()); 817 818 mNativeAnalyzerThread = new NativeAnalyzerThread(this); 819 if (mNativeAnalyzerThread != null) { 820 mNativeAnalyzerThread.setMessageHandler(messageHandler); 821 // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 822 mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION); 823 startTestPhase(); 824 } else { 825 Log.e(TAG, "Couldn't allocate native analyzer thread"); 826 mTestStatusText.setText(getString(R.string.audio_loopback_failure)); 827 } 828 } 829 startTestPhase()830 private void startTestPhase() { 831 if (mNativeAnalyzerThread != null) { 832 if (LOG) { 833 Log.d(TAG, "mTestRoute: " + mTestRoute 834 + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId 835 + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId); 836 } 837 mNativeAnalyzerThread.startTest( 838 mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId); 839 840 // what is this for? 841 try { 842 Thread.sleep(200); 843 } catch (InterruptedException e) { 844 e.printStackTrace(); 845 } 846 } 847 } 848 handleTestPhaseCompletion()849 private void handleTestPhaseCompletion() { 850 if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) { 851 double latency = mNativeAnalyzerThread.getLatencyMillis(); 852 double confidence = mNativeAnalyzerThread.getConfidence(); 853 double timestampLatency = mNativeAnalyzerThread.getTimestampLatencyMillis(); 854 TestSpec testSpec = mTestSpecs[mTestRoute]; 855 testSpec.recordPhase(mTestPhase, latency, confidence, timestampLatency); 856 857 String result = String.format( 858 "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n" 859 + "TimestampLatency: %.2f\n", 860 mTestPhase, latency, confidence, timestampLatency); 861 862 mTestStatusText.setText(result); 863 try { 864 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 865 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500); 866 } catch (InterruptedException e) { 867 e.printStackTrace(); 868 } 869 870 871 mTestPhase++; 872 if (mTestPhase >= NUM_TEST_PHASES) { 873 handleTestCompletion(true); 874 } else { 875 startTestPhase(); 876 } 877 } 878 } 879 generateStatusString( LoopbackLatencyRequirements requirements, boolean showResult)880 private String generateStatusString( 881 LoopbackLatencyRequirements requirements, boolean showResult) { 882 883 if (!isReportLogOkToPass()) { 884 return getString(R.string.audio_general_reportlogtest); 885 } 886 887 if (!mustRunTest()) { 888 return getString(R.string.audio_loopback_test_non_handheld); 889 } 890 891 boolean pass = calcPass(requirements); 892 StringBuilder sb = new StringBuilder(); 893 sb.append(requirements.getResultsString()); 894 if (showResult) { 895 sb.append("\n" + (pass ? mPassString : mFailString)); 896 } 897 return sb.toString(); 898 } 899 mustRunTest()900 private boolean mustRunTest() { 901 return mIsHandheld && hasInternalPath(); 902 } 903 hasInternalPath()904 boolean hasInternalPath() { 905 return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN 906 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN; 907 } 908 calcPass(LoopbackLatencyRequirements requirements)909 private boolean calcPass(LoopbackLatencyRequirements requirements) { 910 if (!isReportLogOkToPass()) { 911 // Can't pass if we can't write the ReportLog 912 return false; 913 } 914 if (!mustRunTest()) { 915 // just grant a pass on non-handheld devices 916 return true; 917 } 918 919 boolean pass = true; 920 921 // Check to see if the tests supported by the hardware have run 922 // Analog Headset 923 if (mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES 924 && !mTestSpecs[TESTROUTE_ANALOG_JACK].mTestRun) { 925 pass = false; 926 } 927 928 if (mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES 929 && !mTestSpecs[TESTROUTE_USB].mTestRun) { 930 pass = false; 931 } 932 933 // Check if the test values have passed 934 // Even if the test is already a fail, this will generate the results string 935 pass &= requirements.evaluate(mClaimsProAudio, 936 Build.VERSION.MEDIA_PERFORMANCE_CLASS, 937 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 938 ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0, 939 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 940 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS : 0.0, 941 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 942 ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0, 943 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(), 944 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport(), 945 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 946 ? mTestSpecs[TESTROUTE_DEVICE].mMeanTimestampLatencyMS : 0.0, 947 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 948 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanTimestampLatencyMS : 0.0, 949 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 950 ? mTestSpecs[TESTROUTE_USB].mMeanTimestampLatencyMS : 0.0); 951 952 return pass; 953 } 954 handleTestCompletion(boolean showResult)955 private void handleTestCompletion(boolean showResult) { 956 TestSpec testSpec = mTestSpecs[mTestRoute]; 957 testSpec.handleTestCompletion(); 958 959 // Make sure the test thread is finished. It should already be done. 960 if (mNativeAnalyzerThread != null) { 961 try { 962 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 963 } catch (InterruptedException e) { 964 e.printStackTrace(); 965 } 966 } 967 968 mRouteStatus[mTestRoute].setText(testSpec.getResultString()); 969 970 LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements(); 971 boolean pass = calcPass(requirements); 972 973 getPassButton().setEnabled(pass); 974 975 mTestStatusText.setText(generateStatusString(requirements, showResult)); 976 977 showWait(false); 978 enableStartButtons(mustRunTest()); 979 } 980 981 /** 982 * handler for messages from audio thread 983 */ 984 private Handler mMessageHandler = new Handler() { 985 public void handleMessage(Message msg) { 986 super.handleMessage(msg); 987 switch(msg.what) { 988 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED: 989 Log.v(TAG,"got message native rec started!!"); 990 showWait(true); 991 mTestStatusText.setText(String.format(Locale.getDefault(), 992 "[phase: %d] - Test Running...", (mTestPhase + 1))); 993 break; 994 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR: 995 Log.v(TAG,"got message native rec can't start!!"); 996 mTestStatusText.setText("Test Error opening streams."); 997 handleTestCompletion(true); 998 break; 999 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR: 1000 Log.v(TAG,"got message native rec can't start!!"); 1001 mTestStatusText.setText("Test Error while recording."); 1002 handleTestCompletion(true); 1003 break; 1004 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS: 1005 mTestStatusText.setText("Test FAILED due to errors."); 1006 handleTestCompletion(true); 1007 break; 1008 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING: 1009 mTestStatusText.setText(String.format(Locale.getDefault(), 1010 "[phase: %d] - Analyzing ...", mTestPhase + 1)); 1011 break; 1012 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE: 1013 handleTestPhaseCompletion(); 1014 break; 1015 default: 1016 break; 1017 } 1018 } 1019 }; 1020 1021 private class OnBtnClickListener implements OnClickListener { 1022 @Override onClick(View v)1023 public void onClick(View v) { 1024 int id = v.getId(); 1025 if (id == R.id.audio_loopback_speakermicpath_btn) { 1026 startAudioTest(mMessageHandler, TESTROUTE_DEVICE); 1027 } else if (id == R.id.audio_loopback_headsetpath_btn) { 1028 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK); 1029 } else if (id == R.id.audio_loopback_usbpath_btn) { 1030 startAudioTest(mMessageHandler, TESTROUTE_USB); 1031 } else if (id == R.id.audio_loopback_calibrate_button) { 1032 (new AudioLoopbackCalibrationDialog(mContext)).show(); 1033 } else if (id == R.id.audio_loopback_devsupport_button) { 1034 (new AudioDevicesDialog(mContext)).show(); 1035 } 1036 } 1037 } 1038 1039 class LoopbackLatencyRequirements { 1040 public static final int MPC_NONE = 0; 1041 public static final int MPC_R = Build.VERSION_CODES.R; 1042 public static final int MPC_S = Build.VERSION_CODES.S; 1043 public static final int MPC_T = Build.VERSION_CODES.TIRAMISU; 1044 1045 String mResultsString = new String(); 1046 getResultsString()1047 String getResultsString() { 1048 return mResultsString; 1049 } 1050 1051 static final int RESULTCODE_NONE = 0; 1052 static final int RESULTCODE_PASS = 1; 1053 static final int RESULTCODE_FAIL_NOINTERNAL = 2; 1054 static final int RESULTCODE_FAIL_BASIC = 3; 1055 static final int RESULTCODE_FAIL_MPC = 4; 1056 static final int RESULTCODE_FAIL_PROONEPATH = 5; 1057 static final int RESULTCODE_FAIL_PROLIMITS_ANALOG = 6; 1058 static final int RESULTCODE_FAIL_PROLIMITS_USB = 7; 1059 static final int RESULTCODE_FAIL_24BIT = 8; 1060 static final int RESULTCODE_FAIL_PRO_NOWIRED = 9; 1061 static final int RESULTCODE_WARNING_TIMESTAMP = 10; 1062 int mResultCode = RESULTCODE_NONE; 1063 checkLatency(double measured, double limit)1064 private boolean checkLatency(double measured, double limit) { 1065 return measured == LATENCY_NOT_MEASURED || measured <= limit; 1066 } 1067 checkTimestampLatencyAccuracy(double measuredLatency, double timestampLatency)1068 private boolean checkTimestampLatencyAccuracy(double measuredLatency, 1069 double timestampLatency) { 1070 return (timestampLatency < 0.0) || (measuredLatency == LATENCY_NOT_MEASURED) 1071 || (Math.abs(measuredLatency - timestampLatency) <= TIMESTAMP_ACCURACY_MS); 1072 } 1073 setResultCode(boolean pass, int code)1074 private void setResultCode(boolean pass, int code) { 1075 // only set the first non-"none" result code 1076 if (!pass && mResultCode == RESULTCODE_NONE) { 1077 mResultCode = code; 1078 } 1079 } 1080 getResultCodeText()1081 public String getResultCodeText() { 1082 StringBuilder sb = new StringBuilder(); 1083 Locale locale = Locale.getDefault(); 1084 1085 switch (mResultCode) { 1086 case RESULTCODE_NONE: 1087 return getString(R.string.audio_loopback_resultcode_none); 1088 case RESULTCODE_PASS: 1089 return getString(R.string.audio_loopback_resultcode_pass); 1090 case RESULTCODE_FAIL_NOINTERNAL: 1091 return getString(R.string.audio_loopback_resultcode_nointernal); 1092 case RESULTCODE_FAIL_BASIC: 1093 sb.append(getString(R.string.audio_loopback_resultcode_failbasic)); 1094 sb.append(String.format(locale, " [%.2fms] ", LATENCY_BASIC)); 1095 sb.append(getString(R.string.audio_loopback_notmet)); 1096 return sb.toString(); 1097 case RESULTCODE_FAIL_MPC: 1098 sb.append(getString(R.string.audio_loopback_resultcode_failmpc)); 1099 sb.append(String.format(locale, " [%.2fms] ", LATENCY_MPC_AT_LEAST_ONE)); 1100 sb.append(getString(R.string.audio_loopback_notmet)); 1101 return sb.toString(); 1102 case RESULTCODE_FAIL_PROONEPATH: 1103 sb.append(getString(R.string.audio_loopback_resultcode_failpro)); 1104 sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_AT_LEAST_ONE)); 1105 sb.append(getString(R.string.audio_loopback_notmet)); 1106 return sb.toString(); 1107 case RESULTCODE_FAIL_PROLIMITS_ANALOG: 1108 sb.append(getString(R.string.audio_loopback_resultcode_failproanalog)); 1109 sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_ANALOG)); 1110 sb.append(getString(R.string.audio_loopback_notmet)); 1111 return sb.toString(); 1112 case RESULTCODE_FAIL_PROLIMITS_USB: 1113 sb.append(getString(R.string.audio_loopback_resultcode_failprousb)); 1114 sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_USB)); 1115 sb.append(getString(R.string.audio_loopback_notmet)); 1116 return sb.toString(); 1117 case RESULTCODE_FAIL_24BIT: 1118 return getString(R.string.audio_loopback_resultcode_fail24bit); 1119 case RESULTCODE_FAIL_PRO_NOWIRED: 1120 return getString(R.string.audio_loopback_resultcode_failproaudiowired); 1121 case RESULTCODE_WARNING_TIMESTAMP: 1122 return getString(R.string.audio_loopback_resultcode_warningtimestamp); 1123 default: 1124 // this should never happen 1125 return getString(R.string.audio_loopback_resultcode_invalid); 1126 } 1127 } 1128 evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport, double deviceTimestampLatency, double analogTimestampLatency, double usbTimestampLatency)1129 public boolean evaluate(boolean proAudio, 1130 int mediaPerformanceClass, 1131 double deviceLatency, 1132 double analogLatency, 1133 double usbLatency, 1134 boolean analog24BitHardwareSupport, 1135 boolean usb24BitHardwareSupport, 1136 double deviceTimestampLatency, 1137 double analogTimestampLatency, 1138 double usbTimestampLatency) { 1139 1140 if (LOG) { 1141 Log.d(TAG, "evaluate()"); 1142 } 1143 mResultCode = RESULTCODE_NONE; 1144 1145 // Required to test the Mic/Speaker path 1146 boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED; 1147 if (LOG) { 1148 Log.d(TAG, " internalPathRun:" + internalPathRun); 1149 } 1150 setResultCode(internalPathRun, RESULTCODE_FAIL_NOINTERNAL); 1151 1152 // All devices must be under the basic limit. 1153 boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC) 1154 && checkLatency(analogLatency, LATENCY_BASIC) 1155 && checkLatency(usbLatency, LATENCY_BASIC); 1156 if (LOG) { 1157 Log.d(TAG, " basicPass:" + basicPass); 1158 } 1159 setResultCode(basicPass, RESULTCODE_FAIL_BASIC); 1160 1161 // For Media Performance Class T the RT latency must be <= 80 msec on one path. 1162 boolean mpcAtLeastOnePass; 1163 if (mClaimsMediaPerformance) { 1164 mpcAtLeastOnePass = 1165 (mediaPerformanceClass < MPC_T) 1166 || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE) 1167 || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE) 1168 || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE); 1169 } else { 1170 mpcAtLeastOnePass = true; 1171 } 1172 if (LOG) { 1173 Log.d(TAG, " mpcAtLeastOnePass:" + mpcAtLeastOnePass); 1174 } 1175 setResultCode(mpcAtLeastOnePass, RESULTCODE_FAIL_MPC); 1176 1177 // For ProAudio, the RT latency must be <= 25 msec on one path. 1178 boolean proAudioAtLeastOnePass = !proAudio 1179 || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 1180 || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 1181 || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE); 1182 if (LOG) { 1183 Log.d(TAG, " proAudioAtLeastOnePass:" + proAudioAtLeastOnePass); 1184 } 1185 setResultCode(proAudioAtLeastOnePass, RESULTCODE_FAIL_PROONEPATH); 1186 1187 // For ProAudio, analog and USB have specific limits 1188 boolean proAudioLimitsPass = !proAudio; 1189 if (proAudio) { 1190 if (analogLatency > 0.0) { 1191 proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG; 1192 setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_ANALOG); 1193 } else if (usbLatency > 0.0) { 1194 // USB audio must be supported if 3.5mm jack not supported 1195 proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB; 1196 setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_USB); 1197 } else { 1198 setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PRO_NOWIRED); 1199 } 1200 } 1201 1202 // For Media Performance Class T, usb and analog should support >=24 bit audio. 1203 boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T) 1204 || (analog24BitHardwareSupport && usb24BitHardwareSupport); 1205 if (LOG) { 1206 Log.d(TAG, " has24BitHardwareSupportPass:" + has24BitHardwareSupportPass); 1207 } 1208 setResultCode(has24BitHardwareSupportPass, RESULTCODE_FAIL_24BIT); 1209 1210 // Timestamp latencies must be accurate enough. 1211 boolean timestampPass = 1212 checkTimestampLatencyAccuracy(deviceLatency, deviceTimestampLatency) 1213 && checkTimestampLatencyAccuracy(analogLatency, analogTimestampLatency) 1214 && checkTimestampLatencyAccuracy(usbLatency, usbTimestampLatency); 1215 if (LOG) { 1216 Log.d(TAG, " timestampPass:" + timestampPass); 1217 } 1218 setResultCode(timestampPass, RESULTCODE_WARNING_TIMESTAMP); 1219 1220 boolean pass = 1221 internalPathRun 1222 && basicPass 1223 && mpcAtLeastOnePass 1224 && proAudioAtLeastOnePass 1225 && proAudioLimitsPass 1226 && has24BitHardwareSupportPass; 1227 if (LOG) { 1228 Log.d(TAG, " pass:" + pass); 1229 } 1230 1231 // Build the results explanation 1232 StringBuilder sb = new StringBuilder(); 1233 if (proAudio) { 1234 sb.append("[Pro Audio]"); 1235 } else if (mediaPerformanceClass != MPC_NONE) { 1236 sb.append("[MPC " + mediaPerformanceClass + "]"); 1237 } else { 1238 sb.append("[Basic Audio]"); 1239 } 1240 sb.append(" "); 1241 1242 Locale locale = Locale.getDefault(); 1243 sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED 1244 ? String.format(locale, "%.2fms ", deviceLatency) 1245 : (mNotTestedString + " - " + mRequiredString))); 1246 1247 // Headset 1248 sb.append("\nHeadset: "); 1249 if (analogLatency != LATENCY_NOT_MEASURED) { 1250 // we have a legit measurement 1251 sb.append(String.format(locale, "%.2fms ", analogLatency)); 1252 } else { 1253 // Not measured 1254 switch (mAnalogJackSupport) { 1255 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 1256 sb.append(mRequiredString); 1257 break; 1258 1259 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 1260 sb.append(mNoHardwareString); 1261 break; 1262 1263 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 1264 default: 1265 sb.append(mUnknownHardwareString); 1266 break; 1267 } 1268 } 1269 1270 // USB 1271 sb.append("\nUSB: "); 1272 if (usbLatency != LATENCY_NOT_MEASURED) { 1273 sb.append(String.format(locale, "%.2fms ", usbLatency)); 1274 } else { 1275 // Not measured 1276 switch (mUSBAudioSupport) { 1277 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 1278 sb.append(mRequiredString); 1279 break; 1280 1281 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 1282 sb.append(mNoHardwareString); 1283 break; 1284 1285 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 1286 default: 1287 sb.append(mUnknownHardwareString); 1288 break; 1289 } 1290 } 1291 1292 mResultsString = sb.toString(); 1293 if (mResultCode > RESULTCODE_PASS) { 1294 mResultsString = mResultsString + "\n" + getResultCodeText(); 1295 } 1296 1297 return pass; 1298 } 1299 } 1300 } 1301