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.mediapc.cts.common.PerformanceClassEvaluator; 28 import android.mediapc.cts.common.Requirements; 29 import android.mediapc.cts.common.Requirements.RoundTripAudioLatencyRequirement; 30 import android.mediapc.cts.common.Requirements.TwentyFourBitAudioRequirement; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.os.SystemProperties; 36 import android.util.Log; 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 import android.webkit.WebView; 40 import android.widget.Button; 41 import android.widget.LinearLayout; 42 import android.widget.ProgressBar; 43 import android.widget.TextView; 44 45 import com.android.compatibility.common.util.CddTest; 46 import com.android.compatibility.common.util.ResultType; 47 import com.android.compatibility.common.util.ResultUnit; 48 import com.android.cts.verifier.CtsVerifierReportLog; 49 import com.android.cts.verifier.PassFailButtons; 50 import com.android.cts.verifier.R; 51 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils; 52 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils.UsbDeviceReport; 53 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags; 54 import com.android.cts.verifier.audio.audiolib.AudioUtils; 55 import com.android.cts.verifier.audio.audiolib.DisplayUtils; 56 import com.android.cts.verifier.audio.audiolib.StatUtils; 57 import com.android.cts.verifier.audio.audiolib.WavFileCapture; 58 import com.android.cts.verifier.libs.ui.HtmlFormatter; 59 import com.android.cts.verifier.libs.ui.TextFormatter; 60 61 import org.hyphonate.megaaudio.common.Globals; 62 import org.hyphonate.megaaudio.common.StreamBase; 63 import org.json.JSONArray; 64 import org.json.JSONException; 65 import org.json.JSONObject; 66 import org.junit.rules.TestName; 67 68 import java.io.File; 69 import java.util.Locale; 70 71 /** 72 * CtsVerifier Audio Loopback Latency Test 73 */ 74 @CddTest(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-2", "5.6/H-1-3"}) 75 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity { 76 private static final String TAG = "AudioLoopbackLatencyActivity"; 77 private static final boolean LOG = false; 78 79 // JNI load 80 static { 81 try { 82 System.loadLibrary("audioloopback_jni"); 83 } catch (UnsatisfiedLinkError e) { 84 Log.e(TAG, "Error loading Audio Loopback JNI library"); 85 Log.e(TAG, "e: " + e); 86 e.printStackTrace(); 87 } 88 89 /* TODO: gracefully fail/notify if the library can't be loaded */ 90 } 91 92 Context mContext; 93 protected AudioManager mAudioManager; 94 95 private ConnectListener mConnectListener; 96 97 // UI 98 TextView[] mRouteStatus = new TextView[NUM_TEST_ROUTES]; 99 100 TextView mTestStatusText; 101 ProgressBar mProgressBar; 102 int mMaxLevel; 103 104 TextView mTestInstructions; 105 106 protected AudioLoopbackUtilitiesHandler mUtiltitiesHandler; 107 108 private OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 109 private Button[] mStartButtons = new Button[NUM_TEST_ROUTES]; 110 111 private WebView mResultsWebView; 112 113 String mYesString; 114 String mNoString; 115 116 String mPassString; 117 String mFailString; 118 String mNotTestedString; 119 String mRequiredString; 120 String mNoHardwareString; 121 String mUnknownHardwareString; 122 123 // These flags determine the maximum allowed latency 124 private boolean mClaimsProAudio; 125 private boolean mClaimsLowLatency; 126 private boolean mClaimsMediaPerformance; 127 private boolean mClaimsOutput; 128 private boolean mClaimsInput; 129 130 // Useful info 131 int mFirstProductApiLevel = SystemProperties.getInt("ro.product.first_api_level", -1); 132 int mFirstBoardApiLevel = SystemProperties.getInt("ro.board.first_api_level", -1); 133 private boolean mSupportsMMAP = AudioUtils.isMMapSupported(); 134 private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported(); 135 private boolean mOverallPass = false; 136 137 private boolean mIsWatch; 138 private boolean mIsTV; 139 private boolean mIsAutomobile; 140 private boolean mIsHandheld; 141 private boolean mIsEmulator; 142 private int mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 143 private int mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 144 145 private int mUSBAudioSupport; 146 private int mAnalogJackSupport; 147 148 // Peripheral(s) 149 private static final int NUM_TEST_ROUTES = 3; 150 private static final int TESTROUTE_DEVICE = 0; // device speaker + mic 151 private static final int TESTROUTE_ANALOG_JACK = 1; 152 private static final int TESTROUTE_USB = 2; 153 private int mTestRoute = TESTROUTE_DEVICE; 154 155 // Loopback Logic 156 private NativeAnalyzerThread mNativeAnalyzerThread = null; 157 158 protected static final int NUM_TEST_PHASES = 5; 159 protected int mTestPhase = 0; 160 161 private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6; 162 private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6; 163 164 165 166 public static final double LATENCY_NOT_MEASURED = 0.0; 167 public static final double LATENCY_BASIC = 200.0; // Was 300 in CDD 14 for UDC 168 // Was 250 in CDD 15 for VIC 169 public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0; 170 public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0; 171 public static final double LATENCY_PRO_AUDIO_USB = 25.0; 172 public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0; 173 174 public static final double TIMESTAMP_ACCURACY_MS = 30.0; 175 176 // The audio stream callback threads should stop and close 177 // in less than a few hundred msec. This is a generous timeout value. 178 private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000; 179 180 private static final String LOG_ERROR_STR = "Could not log metric."; 181 182 private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES]; 183 private volatile UsbDeviceReport mUsbDeviceReport; 184 185 final TestName mTestName = new TestName(); 186 187 // WAV File Stuff 188 File mFilesDir; 189 WavFileCapture mWavFileCapture; 190 191 class TestSpec { 192 private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec"; 193 // impossibly low latencies (indicating something in the test went wrong). 194 protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0; 195 196 final int mRouteId; 197 198 // runtime assigned device ID 199 static final int DEVICEID_NONE = -1; 200 int mInputDeviceId; 201 int mOutputDeviceId; 202 203 String mDeviceName; 204 205 double[] mLatencyMS = new double[NUM_TEST_PHASES]; 206 double[] mConfidence = new double[NUM_TEST_PHASES]; 207 208 double[] mTimestampLatencyMS = new double[NUM_TEST_PHASES]; 209 210 double mMeanLatencyMS; 211 double mMeasuredLatencyMS; 212 double mLatencyOffsetMS; 213 double mMeanAbsoluteDeviation; 214 double mMeanConfidence; 215 double mRequiredConfidence; 216 double mMeanTimestampLatencyMS; 217 int mSampleRate; 218 boolean mHas24BitHardwareSupport; 219 int mHardwareFormat; 220 221 // Stream Attributes 222 int[] mBurstFrames = new int[NativeAnalyzerThread.NUM_STREAM_TYPES]; 223 int[] mCapacityFrames = new int[NativeAnalyzerThread.NUM_STREAM_TYPES]; 224 boolean[] mIsLowLatencyStream = new boolean[NativeAnalyzerThread.NUM_STREAM_TYPES]; 225 boolean[] mIsMMapStream = new boolean[NativeAnalyzerThread.NUM_STREAM_TYPES]; 226 227 boolean mRouteAvailable; // Have we seen this route/device at any time 228 boolean mRouteConnected; // is the route available NOW 229 boolean mTestRun; 230 231 String mWavCaptureFileName; 232 int mCaptureCode = mWavFileCapture.CAPTURE_NOTDONE; 233 TestSpec(int routeId, double requiredConfidence)234 TestSpec(int routeId, double requiredConfidence) { 235 mRouteId = routeId; 236 mRequiredConfidence = requiredConfidence; 237 238 mInputDeviceId = DEVICEID_NONE; 239 mOutputDeviceId = DEVICEID_NONE; 240 241 // Default to true if test not run. 242 mHas24BitHardwareSupport = true; 243 } 244 startTest()245 void startTest() { 246 mTestRun = true; 247 248 java.util.Arrays.fill(mLatencyMS, 0.0); 249 java.util.Arrays.fill(mConfidence, 0.0); 250 java.util.Arrays.fill(mTimestampLatencyMS, 0.0); 251 252 mTestStatusText.setVisibility(View.VISIBLE); 253 mProgressBar.setVisibility(View.VISIBLE); 254 255 // WAV Capture 256 mWavCaptureFileName = "AudioLoopbackTest_" + mRouteId + ".wav"; 257 mWavFileCapture.setCaptureFile(mFilesDir.getPath() + "/" + mWavCaptureFileName); 258 mWavFileCapture.setWavSpec(/*numChannels*/ 1, mSampleRate); 259 mWavFileCapture.startCapture(); 260 } 261 recordPhase(int phase, double latencyMS, double confidence, double timestampLatencyMS)262 void recordPhase(int phase, double latencyMS, double confidence, 263 double timestampLatencyMS) { 264 mLatencyMS[phase] = latencyMS; 265 mConfidence[phase] = confidence; 266 mTimestampLatencyMS[phase] = timestampLatencyMS; 267 } 268 handleTestCompletion()269 void handleTestCompletion() { 270 // Some USB devices have higher latency than the Google device. 271 // We subtract that extra latency so that we are just using the Android device latency. 272 mMeasuredLatencyMS = StatUtils.calculateMean(mLatencyMS); 273 mLatencyOffsetMS = 0.0; 274 if (mRouteId == TESTROUTE_USB) { 275 UsbDeviceReport report = mUsbDeviceReport; // fetch volatile object 276 if (report != null && report.isValid) { 277 mLatencyOffsetMS = report.latencyOffset; 278 } 279 } 280 mMeanLatencyMS = mMeasuredLatencyMS - mLatencyOffsetMS; 281 282 mMeanAbsoluteDeviation = 283 StatUtils.calculateMeanAbsoluteDeviation( 284 mMeanLatencyMS, mLatencyMS, mLatencyMS.length); 285 mMeanConfidence = StatUtils.calculateMean(mConfidence); 286 mMeanTimestampLatencyMS = StatUtils.calculateMean(mTimestampLatencyMS); 287 if (mNativeAnalyzerThread != null) { 288 // Get Stream Attributes 289 mSampleRate = mNativeAnalyzerThread.getSampleRate(); 290 mHas24BitHardwareSupport = mNativeAnalyzerThread.has24BitHardwareSupport(); 291 mHardwareFormat = mNativeAnalyzerThread.getHardwareFormat(); 292 293 // direction-dependent 294 int[] directions = new int[] {NativeAnalyzerThread.STREAM_INPUT, 295 NativeAnalyzerThread.STREAM_OUTPUT}; 296 for (int direction : directions) { 297 mIsLowLatencyStream[direction] = 298 mNativeAnalyzerThread.isLowLatencyStream(direction); 299 300 mBurstFrames[direction] = 301 mNativeAnalyzerThread.getBurstFrames(direction); 302 303 mCapacityFrames[direction] = 304 mNativeAnalyzerThread.getCapacityFrames(direction); 305 306 mIsMMapStream[direction] = 307 mNativeAnalyzerThread.isMMapStream(direction); 308 } 309 } 310 311 mTestStatusText.setVisibility(View.GONE); 312 mProgressBar.setVisibility(View.GONE); 313 314 mCaptureCode = mWavFileCapture.completeCapture(); 315 } 316 isMeasurementValid()317 boolean isMeasurementValid() { 318 return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence; 319 } 320 has24BitHardwareSupport()321 boolean has24BitHardwareSupport() { 322 return mHas24BitHardwareSupport; 323 } 324 getResultString()325 String getResultString() { 326 String result; 327 328 if (!mRouteAvailable) { 329 result = getString(R.string.audio_loopback_routenotavailable); 330 } else if (!mTestRun) { 331 result = getString(R.string.audio_loopback_testnotrun); 332 } else if (mMeanConfidence < mRequiredConfidence) { 333 result = String.format(getString(R.string.audio_loopback_insufficientconfidence), 334 mMeanConfidence, mRequiredConfidence); 335 } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) { 336 result = String.format(getString(R.string.audio_loopback_latencytoolow), 337 mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS); 338 } else { 339 // Print more info if we are correcting the latency measurement. 340 String adjustment = ""; 341 if (mLatencyOffsetMS > 0.0) { 342 adjustment = String.format(Locale.US, "Measured Latency: %.2f ms\n" 343 + "Latency Offset: %.2f ms\n", 344 mMeasuredLatencyMS, 345 mLatencyOffsetMS); 346 347 } 348 result = String.format(Locale.US, 349 "Test Finished\nMean Latency: %.2f ms\n" 350 + "%s" 351 + "Mean Absolute Deviation: %.2f\n" 352 + "Confidence: %.2f\n" 353 + "Low Latency Path: [out:%s, in:%s]\n" 354 + "24 Bit Hardware Support: %s\n" 355 + "Timestamp Latency:%.2f ms", 356 mMeanLatencyMS, 357 adjustment, 358 mMeanAbsoluteDeviation, 359 mMeanConfidence, 360 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT] 361 ? mYesString : mNoString, 362 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT] 363 ? mYesString : mNoString, 364 mHas24BitHardwareSupport ? mYesString : mNoString, 365 mMeanTimestampLatencyMS); 366 } 367 368 return result; 369 } 370 371 // ReportLog Schema (per route) 372 private static final String KEY_ROUTEINDEX = "route_index"; 373 private static final String KEY_LATENCY = "latency"; 374 private static final String KEY_CONFIDENCE = "confidence"; 375 private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation"; 376 private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached"; 377 private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral"; 378 private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral"; 379 private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name"; 380 private static final String KEY_TIMESTAMP_LATENCY = "timestamp_latency"; 381 private static final String KEY_SAMPLE_RATE = "sample_rate"; 382 private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT = 383 "has_24_bit_hardware_support"; 384 private static final String KEY_HARDWARE_FORMAT = "hardware_format"; 385 // key for output low-latency. Pre-existing key, don't change 386 private static final String KEY_OUTPUT_IS_LOW_LATENCY = "is_low_latency"; 387 private static final String KEY_INPUT_IS_LOW_LATENCY = "input_is_low_latency"; 388 private static final String KEY_OUTPUT_BURST_FRAMES = "output_burst_frames"; 389 private static final String KEY_INPUT_BURST_FRAMES = "input_burst_frames"; 390 private static final String KEY_OUTPUT_CAPACITY_FRAMES = "output_capacity_frames"; 391 private static final String KEY_INPUT_CAPACITY_FRAMES = "input_capacity_frames"; 392 private static final String KEY_OUTPUT_IS_MMAP_STREAM = "output_is_mmap_stream"; 393 private static final String KEY_INPUT_IS_MMAP_STREAM = "input_is_mmap_stream"; 394 recordTestResults(CtsVerifierReportLog reportLog)395 void recordTestResults(CtsVerifierReportLog reportLog) { 396 reportLog.addValue( 397 KEY_ROUTEINDEX, 398 mRouteId, 399 ResultType.NEUTRAL, 400 ResultUnit.NONE); 401 402 reportLog.addValue( 403 KEY_LATENCY, 404 mMeanLatencyMS, 405 ResultType.LOWER_BETTER, 406 ResultUnit.MS); 407 408 reportLog.addValue( 409 KEY_CONFIDENCE, 410 mMeanConfidence, 411 ResultType.HIGHER_BETTER, 412 ResultUnit.NONE); 413 414 reportLog.addValue( 415 KEY_MEANABSDEVIATION, 416 mMeanAbsoluteDeviation, 417 ResultType.NEUTRAL, 418 ResultUnit.NONE); 419 420 reportLog.addValue( 421 KEY_TEST_PERIPHERAL_NAME, 422 mDeviceName, 423 ResultType.NEUTRAL, 424 ResultUnit.NONE); 425 426 reportLog.addValue( 427 KEY_TIMESTAMP_LATENCY, 428 mMeanTimestampLatencyMS, 429 ResultType.NEUTRAL, 430 ResultUnit.NONE); 431 432 reportLog.addValue( 433 KEY_SAMPLE_RATE, 434 mSampleRate, 435 ResultType.NEUTRAL, 436 ResultUnit.NONE); 437 438 reportLog.addValue( 439 KEY_HAS_24_BIT_HARDWARE_SUPPORT, 440 mHas24BitHardwareSupport, 441 ResultType.NEUTRAL, 442 ResultUnit.NONE); 443 444 reportLog.addValue( 445 KEY_HARDWARE_FORMAT, 446 mHardwareFormat, 447 ResultType.NEUTRAL, 448 ResultUnit.NONE); 449 450 reportLog.addValue( 451 KEY_OUTPUT_IS_LOW_LATENCY, 452 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT], 453 ResultType.NEUTRAL, 454 ResultUnit.NONE); 455 456 reportLog.addValue( 457 KEY_INPUT_IS_LOW_LATENCY, 458 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT], 459 ResultType.NEUTRAL, 460 ResultUnit.NONE); 461 462 reportLog.addValue( 463 KEY_OUTPUT_BURST_FRAMES, 464 mBurstFrames[NativeAnalyzerThread.STREAM_OUTPUT], 465 ResultType.NEUTRAL, 466 ResultUnit.NONE); 467 468 reportLog.addValue( 469 KEY_INPUT_BURST_FRAMES, 470 mBurstFrames[NativeAnalyzerThread.STREAM_INPUT], 471 ResultType.NEUTRAL, 472 ResultUnit.NONE); 473 474 reportLog.addValue( 475 KEY_OUTPUT_CAPACITY_FRAMES, 476 mCapacityFrames[NativeAnalyzerThread.STREAM_OUTPUT], 477 ResultType.NEUTRAL, 478 ResultUnit.NONE); 479 480 reportLog.addValue( 481 KEY_INPUT_CAPACITY_FRAMES, 482 mCapacityFrames[NativeAnalyzerThread.STREAM_INPUT], 483 ResultType.NEUTRAL, 484 ResultUnit.NONE); 485 486 reportLog.addValue( 487 KEY_OUTPUT_IS_MMAP_STREAM, 488 mIsMMapStream[NativeAnalyzerThread.STREAM_OUTPUT], 489 ResultType.NEUTRAL, 490 ResultUnit.NONE); 491 492 reportLog.addValue( 493 KEY_INPUT_IS_MMAP_STREAM, 494 mIsMMapStream[NativeAnalyzerThread.STREAM_INPUT], 495 ResultType.NEUTRAL, 496 ResultUnit.NONE); 497 } 498 addToJson(JSONObject jsonObject)499 void addToJson(JSONObject jsonObject) { 500 try { 501 jsonObject.put( 502 KEY_ROUTEINDEX, 503 mRouteId); 504 505 jsonObject.put( 506 KEY_LATENCY, 507 mMeanLatencyMS); 508 509 jsonObject.put( 510 KEY_CONFIDENCE, 511 mMeanConfidence); 512 513 jsonObject.put( 514 KEY_MEANABSDEVIATION, 515 mMeanAbsoluteDeviation); 516 517 jsonObject.put( 518 KEY_TEST_PERIPHERAL_NAME, 519 mDeviceName); 520 521 jsonObject.put( 522 KEY_TIMESTAMP_LATENCY, 523 mMeanTimestampLatencyMS); 524 525 jsonObject.put( 526 KEY_SAMPLE_RATE, 527 mSampleRate); 528 529 jsonObject.put( 530 KEY_HAS_24_BIT_HARDWARE_SUPPORT, 531 mHas24BitHardwareSupport); 532 533 jsonObject.put( 534 KEY_HARDWARE_FORMAT, 535 mHardwareFormat); 536 537 jsonObject.put( 538 KEY_OUTPUT_IS_LOW_LATENCY, 539 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT]); 540 541 jsonObject.put( 542 KEY_INPUT_IS_LOW_LATENCY, 543 mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT]); 544 545 jsonObject.put( 546 KEY_OUTPUT_BURST_FRAMES, 547 mBurstFrames[NativeAnalyzerThread.STREAM_OUTPUT]); 548 549 jsonObject.put( 550 KEY_INPUT_BURST_FRAMES, 551 mBurstFrames[NativeAnalyzerThread.STREAM_INPUT]); 552 553 jsonObject.put( 554 KEY_OUTPUT_CAPACITY_FRAMES, 555 mCapacityFrames[NativeAnalyzerThread.STREAM_OUTPUT]); 556 557 jsonObject.put( 558 KEY_INPUT_CAPACITY_FRAMES, 559 mCapacityFrames[NativeAnalyzerThread.STREAM_INPUT]); 560 561 jsonObject.put( 562 KEY_OUTPUT_IS_MMAP_STREAM, 563 mIsMMapStream[NativeAnalyzerThread.STREAM_OUTPUT]); 564 565 jsonObject.put( 566 KEY_INPUT_IS_MMAP_STREAM, 567 mIsMMapStream[NativeAnalyzerThread.STREAM_INPUT]); 568 569 } catch (JSONException e) { 570 Log.e(TAG, LOG_ERROR_STR, e); 571 } 572 } 573 recordPerformanceClassTestResults()574 void recordPerformanceClassTestResults() { 575 PerformanceClassEvaluator pce = new PerformanceClassEvaluator(mTestName); 576 RoundTripAudioLatencyRequirement roundTripAudioLatencyRequirement = 577 Requirements.addR5_6__H_1_2().to(pce); 578 TwentyFourBitAudioRequirement twentyFourBitAudioRequirement = 579 Requirements.addR5_6__H_1_3().to(pce); 580 581 roundTripAudioLatencyRequirement.setRoundTripAudioLatencyMs(mMeanLatencyMS); 582 twentyFourBitAudioRequirement.setTwentyFourBitAudioSupported(mHas24BitHardwareSupport); 583 584 pce.submitAndVerify(); 585 } 586 } 587 588 @Override onCreate(Bundle savedInstanceState)589 protected void onCreate(Bundle savedInstanceState) { 590 setContentView(R.layout.audio_loopback_latency_activity); 591 592 super.onCreate(savedInstanceState); 593 594 mContext = this; 595 596 // MegaAudio Initialization 597 StreamBase.setup(this); 598 599 setPassFailButtonClickListeners(); 600 setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1); 601 602 mRequireReportLogToPass = true; 603 604 mClaimsOutput = AudioSystemFlags.claimsOutput(this); 605 mClaimsInput = AudioSystemFlags.claimsInput(this); 606 mClaimsProAudio = AudioSystemFlags.claimsProAudio(this); 607 mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this); 608 mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0; 609 mIsWatch = AudioSystemFlags.isWatch(this); 610 mIsTV = AudioSystemFlags.isTV(this); 611 mIsAutomobile = AudioSystemFlags.isAutomobile(this); 612 mIsHandheld = AudioSystemFlags.isHandheld(this); 613 mIsEmulator = Build.IS_EMULATOR; 614 615 mUSBAudioSupport = AudioDeviceUtils.supportsUsbAudio(this); 616 mAnalogJackSupport = AudioDeviceUtils.supportsAnalogHeadset(this); 617 618 // Setup test specifications 619 double mustLatency; 620 621 // Speaker/Mic Path 622 mTestSpecs[TESTROUTE_DEVICE] = 623 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT); 624 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; // Always 625 626 // Analog Jack Path 627 mTestSpecs[TESTROUTE_ANALOG_JACK] = 628 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED); 629 630 // USB Path 631 mTestSpecs[TESTROUTE_USB] = 632 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED); 633 634 // Setup UI 635 mYesString = getString(R.string.audio_general_yes); 636 mNoString = getString(R.string.audio_general_no); 637 mPassString = getString(R.string.audio_general_teststatus_pass); 638 mFailString = getString(R.string.audio_general_teststatus_fail); 639 mNotTestedString = getString(R.string.audio_general_not_tested); 640 mRequiredString = getString(R.string.audio_general_required); 641 mNoHardwareString = getString(R.string.audio_general_nohardware); 642 mUnknownHardwareString = getString(R.string.audio_general_unknownhardware); 643 644 // Utilities 645 mUtiltitiesHandler = new AudioLoopbackUtilitiesHandler(this); 646 647 // Pro Audio 648 ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText( 649 (mClaimsProAudio ? mYesString : mNoString)); 650 651 // Low Latency 652 ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText( 653 (mClaimsLowLatency ? mYesString : mNoString)); 654 655 // Media Performance Class 656 ((TextView) findViewById(R.id.audio_loopback_mpc)).setText( 657 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS) 658 : mNoString)); 659 660 // MMAP 661 ((TextView) findViewById(R.id.audio_loopback_mmap)).setText( 662 (mSupportsMMAP ? mYesString : mNoString)); 663 ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText( 664 (mSupportsMMAPExclusive ? mYesString : mNoString)); 665 666 // Device Type 667 ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText( 668 (mIsWatch ? mYesString : mNoString)); 669 ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText( 670 (mIsTV ? mYesString : mNoString)); 671 ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText( 672 (mIsAutomobile ? mYesString : mNoString)); 673 ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText( 674 (mIsHandheld ? mYesString : mNoString)); 675 ((TextView) findViewById(R.id.audio_loopback_emulator)).setText( 676 (mIsEmulator ? mYesString : mNoString)); 677 678 // Individual Test Results 679 mRouteStatus[TESTROUTE_DEVICE] = 680 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info); 681 mRouteStatus[TESTROUTE_ANALOG_JACK] = 682 (TextView) findViewById(R.id.audio_loopback_headsetpath_info); 683 mRouteStatus[TESTROUTE_USB] = 684 (TextView) findViewById(R.id.audio_loopback_usbpath_info); 685 686 mStartButtons[TESTROUTE_DEVICE] = 687 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn); 688 mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener); 689 690 mStartButtons[TESTROUTE_ANALOG_JACK] = 691 (Button) findViewById(R.id.audio_loopback_headsetpath_btn); 692 mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener); 693 694 mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn); 695 mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener); 696 697 mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions); 698 699 mAudioManager = getSystemService(AudioManager.class); 700 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 701 702 // Utilties Buttons 703 if (mIsWatch) { 704 ((LinearLayout) findViewById(R.id.audio_loopback_utilities_layout)) 705 .setOrientation(LinearLayout.VERTICAL); 706 } 707 708 mResultsWebView = findViewById(R.id.audio_loopback_results); 709 710 connectLoopbackUI(); 711 712 if (mustRunTest()) { 713 getPassButton().setEnabled(false); 714 } else { 715 getPassButton().setEnabled(isReportLogOkToPass()); 716 } 717 718 mConnectListener = new ConnectListener(); 719 720 // WAV Capture 721 String folderName = getDataDir().getAbsolutePath() + "/WavFiles"; 722 mFilesDir = new File(folderName); 723 if (!mFilesDir.exists()) { 724 mFilesDir.mkdir(); 725 } 726 727 mWavFileCapture = new WavFileCapture(); 728 729 showRouteStatus(); 730 showTestInstructions(); 731 handleTestCompletion(false); 732 733 DisplayUtils.setKeepScreenOn(this, true); 734 } 735 736 @Override onStart()737 public void onStart() { 738 super.onStart(); 739 mAudioManager.registerAudioDeviceCallback(mConnectListener, null); 740 } 741 742 @Override onStop()743 public void onStop() { 744 mAudioManager.unregisterAudioDeviceCallback(mConnectListener); 745 super.onStop(); 746 } 747 748 // 749 // UI State 750 // showRouteStatus()751 private void showRouteStatus() { 752 // mRouteStatus[TESTROUTE_DEVICE]; 753 // Nothing to do for this route. 754 755 // mRouteStatus[TESTROUTE_ANALOG_JACK]; 756 switch (mAnalogJackSupport) { 757 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 758 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 759 getString(R.string.audio_loopback_noanalog)); 760 break; 761 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 762 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 763 getString(R.string.audio_loopback_headsetpath_instructions)); 764 break; 765 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 766 mRouteStatus[TESTROUTE_ANALOG_JACK].setText( 767 getString(R.string.audio_loopback_unknownanalog)); 768 break; 769 } 770 771 // mRouteStatus[TESTROUTE_USB]; 772 switch (mUSBAudioSupport) { 773 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 774 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_nousb)); 775 break; 776 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 777 mRouteStatus[TESTROUTE_USB].setText( 778 getString(R.string.audio_loopback_usbpath_instructions)); 779 break; 780 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 781 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_unknownusb)); 782 break; 783 } 784 } 785 showTestInstructions()786 private void showTestInstructions() { 787 if (mustRunTest()) { 788 mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths)); 789 } else { 790 mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required)); 791 } 792 } 793 enableStartButtons(boolean enable)794 private void enableStartButtons(boolean enable) { 795 if (enable) { 796 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 797 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected); 798 } 799 } else { 800 for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) { 801 mStartButtons[routeId].setEnabled(false); 802 } 803 } 804 mUtiltitiesHandler.setEnabled(enable); 805 } 806 connectLoopbackUI()807 private void connectLoopbackUI() { 808 mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text); 809 mTestStatusText.setVisibility(View.GONE); 810 mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar); 811 mProgressBar.setVisibility(View.GONE); 812 showWait(false); 813 } 814 815 // 816 // Peripheral Connection Logic 817 // clearDeviceIds()818 void clearDeviceIds() { 819 for (TestSpec testSpec : mTestSpecs) { 820 testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE; 821 } 822 } 823 clearDeviceConnected()824 void clearDeviceConnected() { 825 for (TestSpec testSpec : mTestSpecs) { 826 testSpec.mRouteConnected = false; 827 } 828 } 829 scanPeripheralList(AudioDeviceInfo[] devices)830 void scanPeripheralList(AudioDeviceInfo[] devices) { 831 clearDeviceIds(); 832 clearDeviceConnected(); 833 834 mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 835 mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN; 836 for (AudioDeviceInfo devInfo : devices) { 837 switch (devInfo.getType()) { 838 // TESTROUTE_DEVICE (i.e. Speaker & Mic) 839 // This needs to be handled differently. The other devices can be assumed 840 // to contain both input & output devices in the same type. 841 // For built-in we need to see both TYPES to be sure to have both input & output. 842 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: 843 mSpeakerDeviceId = devInfo.getId(); 844 break; 845 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 846 mMicDeviceId = devInfo.getId(); 847 break; 848 849 // TESTROUTE_ANALOG_JACK 850 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 851 case AudioDeviceInfo.TYPE_AUX_LINE: 852 if (devInfo.isSink()) { 853 mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId(); 854 } else if (devInfo.isSource()) { 855 mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId(); 856 } 857 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true; 858 mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true; 859 mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName = 860 devInfo.getProductName().toString(); 861 break; 862 863 // TESTROUTE_USB 864 case AudioDeviceInfo.TYPE_USB_DEVICE: 865 case AudioDeviceInfo.TYPE_USB_HEADSET: 866 if (devInfo.isSink()) { 867 mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId(); 868 } else if (devInfo.isSource()) { 869 mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId(); 870 } 871 mTestSpecs[TESTROUTE_USB].mRouteAvailable = true; 872 mTestSpecs[TESTROUTE_USB].mRouteConnected = true; 873 mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString(); 874 break; 875 } 876 } 877 878 // do we have BOTH a Speaker and Mic? 879 if (hasInternalPath()) { 880 mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId; 881 mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId; 882 mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true; 883 mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true; 884 mTestSpecs[TESTROUTE_DEVICE].mDeviceName = 885 getString(R.string.audio_loopback_test_internal_devices); 886 } 887 888 enableStartButtons(mustRunTest()); 889 } 890 891 private class ConnectListener extends AudioDeviceCallback { ConnectListener()892 ConnectListener() {} 893 894 // 895 // AudioDevicesManager.OnDeviceConnectionListener 896 // 897 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)898 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 899 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 900 mUsbDeviceReport = AudioDeviceUtils.validateUsbDevice(mContext); 901 } 902 903 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)904 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 905 scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL)); 906 mUsbDeviceReport = null; 907 } 908 } 909 910 // 911 // show active progress bar 912 // showWait(boolean show)913 protected void showWait(boolean show) { 914 mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 915 } 916 917 // 918 // Common logging 919 // 920 921 @Override getTestId()922 public String getTestId() { 923 return setTestNameSuffix(sCurrentDisplayMode, getClass().getName()); 924 } 925 926 @Override requiresReportLog()927 public boolean requiresReportLog() { 928 return true; 929 } 930 931 @Override getReportFileName()932 public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; } 933 934 @Override getReportSectionName()935 public final String getReportSectionName() { 936 return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity"); 937 } 938 939 // Global Test-Schema 940 private static final String KEY_IS_PRO_AUDIO = "is_pro_audio"; 941 private static final String KEY_TEST_MMAP = "supports_mmap"; 942 private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive"; 943 private static final String KEY_LEVEL = "level"; 944 private static final String KEY_PRODUCT_FIRST_API_LEVEL = "product_first_api_level"; 945 private static final String KEY_BOARD_FIRST_API_LEVEL = "board_first_api_level"; 946 private static final String KEY_OVERALL_PASS = "overall_pass"; 947 948 // Contains the results for all routes 949 private static final String KEY_PATHS = "paths"; 950 recordGlobalResults(CtsVerifierReportLog reportLog)951 private void recordGlobalResults(CtsVerifierReportLog reportLog) { 952 // Leave this in to be sure not to break the ReportLog injestion 953 reportLog.addValue( 954 KEY_LEVEL, 955 -1, 956 ResultType.NEUTRAL, 957 ResultUnit.NONE); 958 959 reportLog.addValue( 960 KEY_IS_PRO_AUDIO, 961 mClaimsProAudio, 962 ResultType.NEUTRAL, 963 ResultUnit.NONE); 964 965 reportLog.addValue( 966 KEY_TEST_MMAP, 967 mSupportsMMAP, 968 ResultType.NEUTRAL, 969 ResultUnit.NONE); 970 971 reportLog.addValue( 972 KEY_TEST_MMAPEXCLUSIVE, 973 mSupportsMMAPExclusive, 974 ResultType.NEUTRAL, 975 ResultUnit.NONE); 976 977 reportLog.addValue( 978 Common.KEY_VERSION_CODE, 979 Common.VERSION_CODE, 980 ResultType.NEUTRAL, 981 ResultUnit.NONE); 982 983 reportLog.addValue( 984 KEY_PRODUCT_FIRST_API_LEVEL, 985 mFirstProductApiLevel, 986 ResultType.NEUTRAL, 987 ResultUnit.NONE); 988 989 reportLog.addValue( 990 KEY_BOARD_FIRST_API_LEVEL, 991 mFirstBoardApiLevel, 992 ResultType.NEUTRAL, 993 ResultUnit.NONE); 994 995 reportLog.addValue( 996 KEY_OVERALL_PASS, 997 mOverallPass, 998 ResultType.NEUTRAL, 999 ResultUnit.NONE); 1000 } 1001 recordAllRoutes(CtsVerifierReportLog reportLog)1002 private void recordAllRoutes(CtsVerifierReportLog reportLog) { 1003 JSONArray jsonArray = new JSONArray(); 1004 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 1005 if (mTestSpecs[route].isMeasurementValid()) { 1006 JSONObject jsonObject = new JSONObject(); 1007 mTestSpecs[route].addToJson(jsonObject); 1008 jsonArray.put(jsonObject); 1009 } 1010 } 1011 1012 if (jsonArray.length() > 0) { 1013 reportLog.addValues(KEY_PATHS, jsonArray); 1014 } 1015 } 1016 1017 @Override recordTestResults()1018 public void recordTestResults() { 1019 // Look for a valid route with the minimum latency. 1020 int bestRoute = -1; 1021 double minLatency = Double.MAX_VALUE; 1022 for (int route = 0; route < NUM_TEST_ROUTES; route++) { 1023 if (mTestSpecs[route].isMeasurementValid()) { 1024 if (mTestSpecs[route].mMeanLatencyMS < minLatency) { 1025 bestRoute = route; 1026 minLatency = mTestSpecs[route].mMeanLatencyMS; 1027 } 1028 } 1029 } 1030 1031 if (bestRoute >= 0) { 1032 CtsVerifierReportLog reportLog = getReportLog(); 1033 recordGlobalResults(reportLog); 1034 mTestSpecs[bestRoute].recordTestResults(reportLog); 1035 mTestSpecs[bestRoute].recordPerformanceClassTestResults(); 1036 recordAllRoutes(reportLog); 1037 reportLog.submit(); 1038 } 1039 } 1040 startAudioTest(Handler messageHandler, int testRouteId)1041 private void startAudioTest(Handler messageHandler, int testRouteId) { 1042 enableStartButtons(false); 1043 mRouteStatus[testRouteId].setText(getString(R.string.audio_loopback_running)); 1044 1045 mTestRoute = testRouteId; 1046 1047 mTestSpecs[mTestRoute].startTest(); 1048 1049 getPassButton().setEnabled(false); 1050 1051 mTestPhase = 0; 1052 1053 // Set mmap enabled as mmap supported to get best latency 1054 Globals.setMMapEnabled(Globals.isMMapSupported()); 1055 1056 mNativeAnalyzerThread = new NativeAnalyzerThread(this); 1057 if (mNativeAnalyzerThread != null) { 1058 mNativeAnalyzerThread.setMessageHandler(messageHandler); 1059 // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 1060 mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION); 1061 startTestPhase(); 1062 } else { 1063 Log.e(TAG, "Couldn't allocate native analyzer thread"); 1064 mTestStatusText.setText(getString(R.string.audio_loopback_failure)); 1065 } 1066 } 1067 startTestPhase()1068 private void startTestPhase() { 1069 if (mNativeAnalyzerThread != null) { 1070 if (LOG) { 1071 Log.d(TAG, "mTestRoute: " + mTestRoute 1072 + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId 1073 + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId); 1074 } 1075 mNativeAnalyzerThread.startTest( 1076 mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId); 1077 1078 // what is this for? 1079 try { 1080 Thread.sleep(200); 1081 } catch (InterruptedException e) { 1082 e.printStackTrace(); 1083 } 1084 } 1085 } 1086 handleTestPhaseCompletion()1087 private void handleTestPhaseCompletion() { 1088 if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) { 1089 double latency = mNativeAnalyzerThread.getLatencyMillis(); 1090 double confidence = mNativeAnalyzerThread.getConfidence(); 1091 double timestampLatency = mNativeAnalyzerThread.getTimestampLatencyMillis(); 1092 TestSpec testSpec = mTestSpecs[mTestRoute]; 1093 testSpec.recordPhase(mTestPhase, latency, confidence, timestampLatency); 1094 1095 String result = String.format(getString(R.string.audio_loopback_testphaseresult), 1096 mTestPhase, latency, confidence, timestampLatency); 1097 1098 mTestStatusText.setText(result); 1099 try { 1100 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 1101 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500); 1102 } catch (InterruptedException e) { 1103 e.printStackTrace(); 1104 } 1105 1106 1107 mTestPhase++; 1108 if (mTestPhase >= NUM_TEST_PHASES) { 1109 handleTestCompletion(true); 1110 } else { 1111 startTestPhase(); 1112 } 1113 } 1114 } 1115 mustRunTest()1116 private boolean mustRunTest() { 1117 // Special handling for emulators 1118 if (mIsEmulator) { 1119 return false; 1120 } 1121 1122 return mIsHandheld && hasInternalPath(); 1123 } 1124 hasInternalPath()1125 boolean hasInternalPath() { 1126 return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN 1127 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN; 1128 } 1129 calcPass(LoopbackLatencyRequirements requirements)1130 private boolean calcPass(LoopbackLatencyRequirements requirements) { 1131 if (!mustRunTest()) { 1132 // just grant a pass on non-handheld devices 1133 return true; 1134 } 1135 1136 boolean pass = true; 1137 1138 // Check to see if the tests supported by the hardware have run 1139 // Analog Headset 1140 if (mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES 1141 && !mTestSpecs[TESTROUTE_ANALOG_JACK].mTestRun) { 1142 pass = false; 1143 } 1144 1145 if (mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES 1146 && !mTestSpecs[TESTROUTE_USB].mTestRun) { 1147 pass = false; 1148 } 1149 1150 // Check if the test values have passed 1151 // Even if the test is already a fail, this will generate the results string 1152 pass &= requirements.evaluate(mClaimsProAudio, 1153 Build.VERSION.MEDIA_PERFORMANCE_CLASS, 1154 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 1155 ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0, 1156 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 1157 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS : 0.0, 1158 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 1159 ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0, 1160 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(), 1161 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport(), 1162 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid() 1163 ? mTestSpecs[TESTROUTE_DEVICE].mMeanTimestampLatencyMS : 0.0, 1164 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid() 1165 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanTimestampLatencyMS : 0.0, 1166 mTestSpecs[TESTROUTE_USB].isMeasurementValid() 1167 ? mTestSpecs[TESTROUTE_USB].mMeanTimestampLatencyMS : 0.0); 1168 1169 return pass; 1170 } 1171 handleTestCompletion(boolean showResult)1172 private void handleTestCompletion(boolean showResult) { 1173 TestSpec testSpec = mTestSpecs[mTestRoute]; 1174 testSpec.handleTestCompletion(); 1175 1176 // Make sure the test thread is finished. It should already be done. 1177 if (mNativeAnalyzerThread != null) { 1178 try { 1179 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC); 1180 } catch (InterruptedException e) { 1181 e.printStackTrace(); 1182 } 1183 } 1184 1185 mRouteStatus[mTestRoute].setText(testSpec.getResultString()); 1186 1187 LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements(); 1188 mOverallPass = calcPass(requirements); 1189 1190 getPassButton().setEnabled(mOverallPass); 1191 1192 showWait(false); 1193 enableStartButtons(true); 1194 } 1195 1196 /** 1197 * handler for messages from audio thread 1198 */ 1199 private Handler mMessageHandler = new Handler() { 1200 public void handleMessage(Message msg) { 1201 super.handleMessage(msg); 1202 Locale locale = Locale.getDefault(); 1203 1204 switch(msg.what) { 1205 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED: 1206 Log.v(TAG,"got message native rec started!!"); 1207 showWait(true); 1208 mTestStatusText.setText(String.format(locale, 1209 getString(R.string.audio_loopback_phaserunning), (mTestPhase + 1))); 1210 break; 1211 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR: 1212 Log.v(TAG,"got message native rec can't start!!"); 1213 mTestStatusText.setText(getString(R.string.audio_loopback_erroropeningstream)); 1214 handleTestCompletion(true); 1215 break; 1216 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR: 1217 Log.v(TAG,"got message native rec can't start!!"); 1218 mTestStatusText.setText(getString(R.string.audio_loopback_errorwhilerecording)); 1219 handleTestCompletion(true); 1220 break; 1221 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS: 1222 mTestStatusText.setText(getString(R.string.audio_loopback_failedduetoerrors)); 1223 handleTestCompletion(true); 1224 break; 1225 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING: 1226 mTestStatusText.setText(String.format(locale, 1227 getString(R.string.audio_loopback_phaseanalyzing), mTestPhase + 1)); 1228 break; 1229 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE: 1230 handleTestPhaseCompletion(); 1231 break; 1232 default: 1233 break; 1234 } 1235 } 1236 }; 1237 1238 private class OnBtnClickListener implements OnClickListener { 1239 @Override onClick(View v)1240 public void onClick(View v) { 1241 int id = v.getId(); 1242 if (id == R.id.audio_loopback_speakermicpath_btn) { 1243 startAudioTest(mMessageHandler, TESTROUTE_DEVICE); 1244 } else if (id == R.id.audio_loopback_headsetpath_btn) { 1245 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK); 1246 } else if (id == R.id.audio_loopback_usbpath_btn) { 1247 startAudioTest(mMessageHandler, TESTROUTE_USB); 1248 } 1249 } 1250 } 1251 1252 class LoopbackLatencyRequirements { 1253 public static final int MPC_NONE = 0; 1254 public static final int MPC_R = Build.VERSION_CODES.R; 1255 public static final int MPC_S = Build.VERSION_CODES.S; 1256 public static final int MPC_T = Build.VERSION_CODES.TIRAMISU; 1257 1258 String mResultsString = new String(); 1259 getResultsString()1260 String getResultsString() { 1261 return mResultsString; 1262 } 1263 checkLatency(double measured, double limit)1264 private boolean checkLatency(double measured, double limit) { 1265 return measured == LATENCY_NOT_MEASURED || measured <= limit; 1266 } 1267 checkTimestampLatencyAccuracy(double measuredLatency, double timestampLatency)1268 private boolean checkTimestampLatencyAccuracy(double measuredLatency, 1269 double timestampLatency) { 1270 return (timestampLatency < 0.0) || (measuredLatency == LATENCY_NOT_MEASURED) 1271 || (Math.abs(measuredLatency - timestampLatency) <= TIMESTAMP_ACCURACY_MS); 1272 } 1273 1274 /** 1275 * Calculate pass/not pass and generate on-screen messages. 1276 * @return true if the test is in a pass state. 1277 */ evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport, double deviceTimestampLatency, double analogTimestampLatency, double usbTimestampLatency)1278 public boolean evaluate(boolean proAudio, 1279 int mediaPerformanceClass, 1280 double deviceLatency, 1281 double analogLatency, 1282 double usbLatency, 1283 boolean analog24BitHardwareSupport, 1284 boolean usb24BitHardwareSupport, 1285 double deviceTimestampLatency, 1286 double analogTimestampLatency, 1287 double usbTimestampLatency) { 1288 1289 if (LOG) { 1290 Log.d(TAG, "evaluate()"); 1291 } 1292 1293 /* 1294 * Calculate PASS/FAIL 1295 */ 1296 1297 // Required to test the Mic/Speaker path 1298 boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED; 1299 if (LOG) { 1300 Log.d(TAG, " internalPathRun:" + internalPathRun); 1301 } 1302 1303 // *ALL* devices must be under the basic limit. 1304 boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC) 1305 && checkLatency(analogLatency, LATENCY_BASIC) 1306 && checkLatency(usbLatency, LATENCY_BASIC); 1307 if (LOG) { 1308 Log.d(TAG, " basicPass:" + basicPass); 1309 } 1310 1311 // For Media Performance Class T the RT latency must be <= 80 msec on one path. 1312 boolean mpcAtLeastOnePass; 1313 if (mClaimsMediaPerformance) { 1314 mpcAtLeastOnePass = 1315 (mediaPerformanceClass < MPC_T) 1316 || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE) 1317 || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE) 1318 || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE); 1319 } else { 1320 mpcAtLeastOnePass = true; 1321 } 1322 if (LOG) { 1323 Log.d(TAG, " mpcAtLeastOnePass:" + mpcAtLeastOnePass); 1324 } 1325 1326 // For ProAudio, the RT latency must be <= 25 msec on one path. 1327 boolean proAudioAtLeastOnePass = !proAudio 1328 || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 1329 || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 1330 || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE); 1331 if (LOG) { 1332 Log.d(TAG, " proAudioAtLeastOnePass:" + proAudioAtLeastOnePass); 1333 } 1334 1335 // For ProAudio, analog and USB have specific limits 1336 boolean proAudioLimitsPass = !proAudio; 1337 if (proAudio) { 1338 if (analogLatency > 0.0) { 1339 proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG; 1340 } else if (usbLatency > 0.0) { 1341 // USB audio must be supported if 3.5mm jack not supported 1342 proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB; 1343 } 1344 } 1345 1346 // For Media Performance Class T, usb and analog should support >=24 bit audio. 1347 boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T) 1348 || (analog24BitHardwareSupport && usb24BitHardwareSupport); 1349 if (LOG) { 1350 Log.d(TAG, " has24BitHardwareSupportPass:" + has24BitHardwareSupportPass); 1351 } 1352 1353 // Timestamp latencies must be accurate enough. 1354 boolean timestampPass = 1355 checkTimestampLatencyAccuracy(deviceLatency, deviceTimestampLatency) 1356 && checkTimestampLatencyAccuracy(analogLatency, analogTimestampLatency) 1357 && checkTimestampLatencyAccuracy(usbLatency, usbTimestampLatency); 1358 if (LOG) { 1359 Log.d(TAG, " timestampPass:" + timestampPass); 1360 } 1361 1362 boolean pass = 1363 internalPathRun 1364 && basicPass 1365 && mpcAtLeastOnePass 1366 && proAudioAtLeastOnePass 1367 && proAudioLimitsPass 1368 && has24BitHardwareSupportPass; 1369 if (LOG) { 1370 Log.d(TAG, " pass:" + pass); 1371 } 1372 1373 /* 1374 * Display Results 1375 */ 1376 buildResultsPanel(proAudio, mediaPerformanceClass); 1377 1378 return pass; 1379 } 1380 reportCapture(TextFormatter textFormatter, int pathId)1381 private void reportCapture(TextFormatter textFormatter, int pathId) { 1382 if (mTestSpecs[pathId].mCaptureCode == WavFileCapture.CAPTURE_SUCCESS) { 1383 textFormatter.appendBreak() 1384 .appendText(getString(R.string.audio_loopback_wavcapturefile) + " " 1385 + mTestSpecs[pathId].mWavCaptureFileName); 1386 } else { 1387 if (mTestSpecs[pathId].mWavCaptureFileName == null) { 1388 textFormatter.appendBreak() 1389 .appendText(getString(R.string.audio_loopback_nocapture)); 1390 } else { 1391 textFormatter.appendBreak() 1392 .openBold() 1393 .appendText(getString(R.string.audio_loopback_nocapture) 1394 + ": " + mTestSpecs[pathId].mWavCaptureFileName) 1395 .closeBold(); 1396 } 1397 } 1398 } 1399 buildResultsPanel(boolean proAudio, int mediaPerformanceClass)1400 private void buildResultsPanel(boolean proAudio, int mediaPerformanceClass) { 1401 // We will want to think about non-WebView devices 1402 TextFormatter textFormatter = new HtmlFormatter(); 1403 textFormatter.openDocument(); 1404 1405 Locale locale = Locale.getDefault(); 1406 1407 /* 1408 * ReportLog Warning 1409 */ 1410 if (!isReportLogOkToPass()) { 1411 textFormatter.openParagraph() 1412 .appendText(getString(R.string.ctsv_general_cantwritereportlog)) 1413 .closeParagraph(); 1414 } 1415 1416 /* 1417 * Audio Performance Level Criteria 1418 */ 1419 textFormatter.appendText(getString(R.string.ctsv_loopback_criteria)) 1420 .openBold(); 1421 if (proAudio) { 1422 textFormatter.appendText(getString(R.string.audio_general_proaudio)); 1423 } else if (mediaPerformanceClass != MPC_NONE) { 1424 textFormatter.appendText(getString(R.string.audio_general_mpc) + " " 1425 + mediaPerformanceClass); 1426 } else { 1427 textFormatter.appendText(getString(R.string.audio_general_basicaudio)); 1428 } 1429 textFormatter.closeBold() 1430 .appendBreak(); 1431 1432 // Basic Pass 1433 textFormatter.appendText(getString(R.string.ctsv_loopback_forbasiclatency)) 1434 .appendBreak(); 1435 textFormatter.appendText(getString(R.string.ctsv_loopback_atleastoneroute) 1436 + LATENCY_BASIC + getString(R.string.ctsv_general_mssuffix)) 1437 .appendBreak(); 1438 1439 // Media Performance Class 1440 if (mClaimsMediaPerformance) { 1441 textFormatter.appendText( 1442 getString(R.string.ctsv_loopback_formpclevel) + " " + mediaPerformanceClass) 1443 .appendBreak() 1444 .appendText(getString(R.string.ctsv_loopback_atleastoneroute) 1445 + LATENCY_MPC_AT_LEAST_ONE 1446 + getString(R.string.ctsv_general_mssuffix)) 1447 .appendBreak(); 1448 } 1449 1450 // Pro Audio 1451 if (proAudio) { 1452 textFormatter.appendText(getString(R.string.ctsv_loopback_forproaudio)) 1453 .appendBreak() 1454 .appendText(getString(R.string.ctsv_loopback_analogheadsetspec) 1455 + LATENCY_PRO_AUDIO_ANALOG 1456 + getString(R.string.ctsv_general_mssuffix)) 1457 .appendBreak() 1458 .appendText(getString(R.string.ctsv_loopback_usbspec) 1459 + LATENCY_PRO_AUDIO_USB 1460 + getString(R.string.ctsv_general_mssuffix)) 1461 .appendBreak(); 1462 } 1463 1464 textFormatter.appendBreak() 1465 .appendText(getString(R.string.audio_loopback_wavcapturefolder)) 1466 .appendBreak() 1467 .appendText(mFilesDir.getPath()) 1468 .appendBreak(); 1469 1470 /* 1471 * Speaker/Mic route 1472 */ 1473 double speakermicLatency = mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS; 1474 textFormatter.openParagraph() 1475 .openBold() 1476 .appendText(getString(R.string.audio_loopback_speakermic)) 1477 .closeBold() 1478 .appendBreak(); 1479 if (speakermicLatency == LATENCY_NOT_MEASURED) { 1480 textFormatter.openItalic() 1481 .appendText(getString(R.string.ctsv_loopback_testspeakermic)) 1482 .closeItalic(); 1483 } else { 1484 textFormatter.appendText(String.format(locale, "%.2f ms ", speakermicLatency)); 1485 if (speakermicLatency <= LATENCY_PRO_AUDIO_AT_LEAST_ONE) { 1486 textFormatter.appendText(" - " 1487 + getString(R.string.ctsv_loopback_meetsproaudio) 1488 + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_AT_LEAST_ONE)); 1489 } else if (mClaimsMediaPerformance 1490 && speakermicLatency <= LATENCY_MPC_AT_LEAST_ONE) { 1491 textFormatter.appendText(" - " 1492 + getString(R.string.ctsv_loopback_meetsmpclevel) + " " 1493 + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " " 1494 + getString(R.string.ctsv_general_specification) 1495 + String.format(locale, " (%.2f ms)", LATENCY_MPC_AT_LEAST_ONE)); 1496 } else if (speakermicLatency <= LATENCY_BASIC) { 1497 textFormatter.appendText(" - " 1498 + getString(R.string.ctsv_loopback_meetsbasicaudio) 1499 + String.format(locale, " (%.2f ms)", LATENCY_BASIC)); 1500 } else { 1501 textFormatter.appendText(getString(R.string.ctsv_general_failsuffix)); 1502 } 1503 } 1504 1505 // Capture Info 1506 reportCapture(textFormatter, TESTROUTE_DEVICE); 1507 textFormatter.closeParagraph(); 1508 1509 /* 1510 * Analog Headset Route 1511 */ 1512 double analogLatency = mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS; 1513 textFormatter.openParagraph() 1514 .openBold() 1515 .appendText(getString(R.string.audio_loopback_headset)) 1516 .closeBold() 1517 .appendBreak(); 1518 if (analogLatency != LATENCY_NOT_MEASURED) { 1519 // we have a legit measurement 1520 textFormatter.appendText(String.format(locale, "%.2f ms ", analogLatency)); 1521 if (analogLatency <= LATENCY_PRO_AUDIO_ANALOG) { 1522 textFormatter.appendText(" - " 1523 + getString(R.string.ctsv_loopback_meetsproaudio) 1524 + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_ANALOG)); 1525 } else if (mClaimsMediaPerformance && analogLatency <= LATENCY_MPC_AT_LEAST_ONE) { 1526 textFormatter.appendText(" - " 1527 + getString(R.string.ctsv_loopback_meetsmpclevel) + " " 1528 + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " " 1529 + getString(R.string.ctsv_general_specification) 1530 + String.format(locale, " (%.2f ms)", LATENCY_MPC_AT_LEAST_ONE)); 1531 } else if (analogLatency <= LATENCY_BASIC) { 1532 textFormatter.appendText(" - " 1533 + getString(R.string.ctsv_loopback_meetsbasicaudio) 1534 + String.format(locale, " (%.2f ms)", LATENCY_BASIC)); 1535 } else { 1536 textFormatter.appendText(getString(R.string.ctsv_general_failsuffix)); 1537 } 1538 } else { 1539 // Not measured 1540 textFormatter.openItalic(); 1541 switch (mAnalogJackSupport) { 1542 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 1543 textFormatter.appendText(getString(R.string.ctsv_loopback_testanalogjack)); 1544 break; 1545 1546 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 1547 textFormatter.appendText( 1548 getString(R.string.ctsv_loopback_noanalogjacksupport)); 1549 break; 1550 1551 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 1552 default: 1553 textFormatter.appendText( 1554 getString(R.string.ctsv_loopback_unknownanalogsupport)); 1555 break; 1556 } 1557 textFormatter.closeItalic(); 1558 } 1559 1560 // Capture Info 1561 reportCapture(textFormatter, TESTROUTE_ANALOG_JACK); 1562 textFormatter.closeParagraph(); 1563 1564 /* 1565 * USB Device 1566 */ 1567 double usbLatency = mTestSpecs[TESTROUTE_USB].mMeanLatencyMS; 1568 textFormatter.openParagraph() 1569 .openBold() 1570 .appendText(getString(R.string.audio_loopback_usb)) 1571 .closeBold() 1572 .appendBreak(); 1573 if (usbLatency != LATENCY_NOT_MEASURED) { 1574 textFormatter.appendText(String.format(locale, "%.2f ms ", usbLatency)); 1575 if (usbLatency <= LATENCY_PRO_AUDIO_USB) { 1576 textFormatter.appendText(" - " 1577 + getString(R.string.ctsv_loopback_meetsproaudio) 1578 + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_USB)); 1579 } else if (mClaimsMediaPerformance && usbLatency <= LATENCY_MPC_AT_LEAST_ONE) { 1580 textFormatter.appendText(" - " 1581 + getString(R.string.ctsv_loopback_meetsmpclevel) + " " 1582 + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " " 1583 + getString(R.string.ctsv_general_specification) 1584 + String.format(locale, " (%.2f ms", LATENCY_MPC_AT_LEAST_ONE)); 1585 } else if (usbLatency <= LATENCY_BASIC) { 1586 textFormatter.appendText(" - " 1587 + getString(R.string.ctsv_loopback_meetsbasicaudio) 1588 + String.format(locale, " (%.2f ms)", LATENCY_BASIC)); 1589 } else { 1590 textFormatter.appendText(getString(R.string.ctsv_general_failsuffix)); 1591 } 1592 } else { 1593 // Not measured 1594 textFormatter.openItalic(); 1595 switch (mUSBAudioSupport) { 1596 case AudioDeviceUtils.SUPPORTSDEVICE_YES: 1597 textFormatter.appendText(getString(R.string.ctsv_loopback_testusb)); 1598 break; 1599 1600 case AudioDeviceUtils.SUPPORTSDEVICE_NO: 1601 textFormatter.appendText(getString(R.string.ctsv_loopback_nousbsupport)); 1602 break; 1603 1604 case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED: 1605 default: 1606 textFormatter.appendText( 1607 getString(R.string.ctsv_loopback_unknownusbsupport)); 1608 break; 1609 } 1610 textFormatter.closeItalic(); 1611 } 1612 1613 // Capture Info 1614 reportCapture(textFormatter, TESTROUTE_USB); 1615 textFormatter.closeParagraph(); 1616 1617 textFormatter.openParagraph(); 1618 1619 boolean analogRequired = mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES; 1620 boolean usbRequired = mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES; 1621 1622 // First, do we have enough data to present a result. 1623 // Have all available paths been tested. 1624 boolean testSufficient = 1625 speakermicLatency != LATENCY_NOT_MEASURED 1626 && (analogLatency != LATENCY_NOT_MEASURED || !analogRequired) 1627 && (usbLatency != LATENCY_NOT_MEASURED || !usbRequired); 1628 1629 if (!testSufficient) { 1630 textFormatter.openItalic() 1631 .appendText(getString(R.string.ctsv_loopback_testallroutes)) 1632 .closeItalic(); 1633 } else { 1634 // All devices must be under the basic limit. 1635 boolean basicPass = checkLatency(speakermicLatency, LATENCY_BASIC) 1636 && checkLatency(analogLatency, LATENCY_BASIC) 1637 && checkLatency(usbLatency, LATENCY_BASIC); 1638 1639 textFormatter.appendText(getString(R.string.audio_loopback_basiclatency) + " ") 1640 .openBold() 1641 .appendText(getString(basicPass 1642 ? R.string.ctsv_general_pass : R.string.ctsv_general_fail)) 1643 .closeBold() 1644 .appendBreak(); 1645 1646 // MPC 1647 boolean mpcAtLeastOnePass = 1648 (mediaPerformanceClass < MPC_T) 1649 || checkLatency(speakermicLatency, LATENCY_MPC_AT_LEAST_ONE) 1650 || (analogRequired && checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)) 1651 || (usbRequired && checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE)); 1652 1653 textFormatter.appendText(getString(R.string.audio_loopback_mpclatency) + " " 1654 + mediaPerformanceClass + ": ") 1655 .openBold(); 1656 if (mClaimsMediaPerformance) { 1657 textFormatter.appendText(getString(mpcAtLeastOnePass 1658 ? R.string.ctsv_general_pass : R.string.ctsv_general_fail)); 1659 } else { 1660 textFormatter.appendText(getString(R.string.audio_loopback_nopmpcsupport)); 1661 } 1662 textFormatter.closeBold() 1663 .appendBreak(); 1664 1665 // Pro Audio 1666 // For ProAudio, the RT latency must be <= 25 msec on one path. 1667 boolean proAudioAtLeastOnePass = 1668 checkLatency(speakermicLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE) 1669 || (analogRequired 1670 && checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)) 1671 || (usbRequired 1672 && checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)); 1673 1674 textFormatter 1675 .appendText(getString(R.string.audio_loopback_proaudiolatency) + ": ") 1676 .openBold(); 1677 if (mClaimsProAudio) { 1678 textFormatter.appendText( 1679 getString( 1680 proAudioAtLeastOnePass 1681 ? R.string.ctsv_general_pass 1682 : R.string.ctsv_general_fail)); 1683 } else { 1684 textFormatter.appendText(getString(R.string.audio_loopback_noproaudiosupport)); 1685 } 1686 textFormatter.closeBold().appendBreak(); 1687 } 1688 1689 textFormatter.closeParagraph() 1690 .closeDocument(); 1691 1692 textFormatter.put(mResultsWebView); 1693 mResultsWebView.setVisibility(View.VISIBLE); 1694 } 1695 } 1696 } 1697