1 /* 2 * Copyright 2020 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.mobileer.oboetester; 18 19 import static com.mobileer.oboetester.IntentBasedTestSupport.configureStreamsFromBundle; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.media.AudioDeviceInfo; 24 import android.media.AudioManager; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.support.annotation.NonNull; 29 import android.util.Log; 30 import android.widget.CheckBox; 31 32 import com.mobileer.audio_device.AudioDeviceInfoConverter; 33 34 import java.lang.reflect.Field; 35 36 /** 37 * Play a recognizable tone on each channel of each speaker device 38 * and listen for the result through a microphone. 39 * Also test each microphone channel and device. 40 * Try each InputPreset. 41 * 42 * The analysis is based on a cosine transform of a single 43 * frequency. The magnitude indicates the level. 44 * The variations in phase, "jitter" indicate how noisy the 45 * signal is or whether it is corrupted. A noisy room may have 46 * energy at the target frequency but the phase will be random. 47 * 48 * This test requires a quiet room but no other hardware. 49 */ 50 public class TestDataPathsActivity extends BaseAutoGlitchActivity { 51 52 public static final String KEY_USE_INPUT_PRESETS = "use_input_presets"; 53 public static final boolean VALUE_DEFAULT_USE_INPUT_PRESETS = true; 54 55 public static final String KEY_USE_INPUT_DEVICES = "use_input_devices"; 56 public static final boolean VALUE_DEFAULT_USE_INPUT_DEVICES = true; 57 58 public static final String KEY_USE_OUTPUT_DEVICES = "use_output_devices"; 59 public static final boolean VALUE_DEFAULT_USE_OUTPUT_DEVICES = true; 60 61 public static final String KEY_SINGLE_TEST_INDEX = "single_test_index"; 62 public static final int VALUE_DEFAULT_SINGLE_TEST_INDEX = -1; 63 64 public static final int DURATION_SECONDS = 3; 65 private final static double MIN_REQUIRED_MAGNITUDE = 0.001; 66 private final static double MAX_SINE_FREQUENCY = 1000.0; 67 private final static int TYPICAL_SAMPLE_RATE = 48000; 68 private final static double FRAMES_PER_CYCLE = TYPICAL_SAMPLE_RATE / MAX_SINE_FREQUENCY; 69 private final static double PHASE_PER_BIN = 2.0 * Math.PI / FRAMES_PER_CYCLE; 70 private final static double MAX_ALLOWED_JITTER = 2.0 * PHASE_PER_BIN; 71 private final static String MAGNITUDE_FORMAT = "%7.5f"; 72 73 final int TYPE_BUILTIN_SPEAKER_SAFE = 0x18; // API 30 74 75 private double mMagnitude; 76 private double mMaxMagnitude; 77 private int mPhaseCount; 78 private double mPhase; 79 private double mPhaseErrorSum; 80 private double mPhaseErrorCount; 81 82 AudioManager mAudioManager; 83 private CheckBox mCheckBoxInputPresets; 84 private CheckBox mCheckBoxInputDevices; 85 private CheckBox mCheckBoxOutputDevices; 86 87 private static final int[] INPUT_PRESETS = { 88 // VOICE_RECOGNITION gets tested in testInputs() 89 // StreamConfiguration.INPUT_PRESET_VOICE_RECOGNITION, 90 StreamConfiguration.INPUT_PRESET_GENERIC, 91 StreamConfiguration.INPUT_PRESET_CAMCORDER, 92 // TODO Resolve issue with echo cancellation killing the signal. 93 StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION, 94 StreamConfiguration.INPUT_PRESET_UNPROCESSED, 95 StreamConfiguration.INPUT_PRESET_VOICE_PERFORMANCE, 96 }; 97 98 @NonNull comparePassedField(String prefix, Object failed, Object passed, String name)99 public static String comparePassedField(String prefix, Object failed, Object passed, String name) { 100 try { 101 Field field = failed.getClass().getField(name); 102 int failedValue = field.getInt(failed); 103 int passedValue = field.getInt(passed); 104 return (failedValue == passedValue) ? "" 105 : (prefix + " " + name + ": passed = " + passedValue + ", failed = " + failedValue + "\n"); 106 } catch (NoSuchFieldException e) { 107 return "ERROR - no such field " + name; 108 } catch (IllegalAccessException e) { 109 return "ERROR - cannot access " + name; 110 } 111 } 112 calculatePhaseError(double p1, double p2)113 public static double calculatePhaseError(double p1, double p2) { 114 double diff = Math.abs(p1 - p2); 115 // Wrap around the circle. 116 while (diff > (2 * Math.PI)) { 117 diff -= (2 * Math.PI); 118 } 119 // A phase error close to 2*PI is actually a small phase error. 120 if (diff > Math.PI) { 121 diff = (2 * Math.PI) - diff; 122 } 123 return diff; 124 } 125 126 // Periodically query for magnitude and phase from the native detector. 127 protected class DataPathSniffer extends NativeSniffer { 128 DataPathSniffer(Activity activity)129 public DataPathSniffer(Activity activity) { 130 super(activity); 131 } 132 133 @Override startSniffer()134 public void startSniffer() { 135 mMagnitude = -1.0; 136 mMaxMagnitude = -1.0; 137 mPhaseCount = 0; 138 mPhase = 0.0; 139 mPhaseErrorSum = 0.0; 140 mPhaseErrorCount = 0; 141 super.startSniffer(); 142 } 143 144 @Override run()145 public void run() { 146 mMagnitude = getMagnitude(); 147 mMaxMagnitude = getMaxMagnitude(); 148 Log.d(TAG, String.format("magnitude = %7.4f, maxMagnitude = %7.4f", 149 mMagnitude, mMaxMagnitude)); 150 // Only look at the phase if we have a signal. 151 if (mMagnitude >= MIN_REQUIRED_MAGNITUDE) { 152 double phase = getPhase(); 153 // Wait for the analyzer to get a lock on the signal. 154 // Arbitrary number of phase measurements before we start measuring jitter. 155 final int kMinPhaseMeasurementsRequired = 4; 156 if (mPhaseCount >= kMinPhaseMeasurementsRequired) { 157 double phaseError = calculatePhaseError(phase, mPhase); 158 // low pass filter 159 mPhaseErrorSum += phaseError; 160 mPhaseErrorCount++; 161 Log.d(TAG, String.format("phase = %7.4f, diff = %7.4f, jitter = %7.4f", 162 phase, phaseError, getAveragePhaseError())); 163 } 164 mPhase = phase; 165 mPhaseCount++; 166 } 167 reschedule(); 168 } 169 getCurrentStatusReport()170 public String getCurrentStatusReport() { 171 StringBuffer message = new StringBuffer(); 172 message.append( 173 "magnitude = " + getMagnitudeText(mMagnitude) 174 + ", max = " + getMagnitudeText(mMaxMagnitude) 175 + "\nphase = " + getMagnitudeText(mPhase) 176 + ", jitter = " + getJitterText() 177 + ", #" + mPhaseCount 178 + "\n"); 179 return message.toString(); 180 } 181 182 @Override getShortReport()183 public String getShortReport() { 184 return "maxMag = " + getMagnitudeText(mMaxMagnitude) 185 + ", jitter = " + getJitterText(); 186 } 187 188 @Override updateStatusText()189 public void updateStatusText() { 190 mLastGlitchReport = getCurrentStatusReport(); 191 runOnUiThread(() -> { 192 setAnalyzerText(mLastGlitchReport); 193 }); 194 } 195 } 196 getJitterText()197 private String getJitterText() { 198 return isPhaseJitterValid() ? getMagnitudeText(getAveragePhaseError()) : "?"; 199 } 200 201 @Override createNativeSniffer()202 NativeSniffer createNativeSniffer() { 203 return new TestDataPathsActivity.DataPathSniffer(this); 204 } 205 getMagnitude()206 native double getMagnitude(); getMaxMagnitude()207 native double getMaxMagnitude(); getPhase()208 native double getPhase(); 209 210 @Override inflateActivity()211 protected void inflateActivity() { 212 setContentView(R.layout.activity_data_paths); 213 } 214 215 @Override onCreate(Bundle savedInstanceState)216 protected void onCreate(Bundle savedInstanceState) { 217 super.onCreate(savedInstanceState); 218 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 219 mCheckBoxInputPresets = (CheckBox)findViewById(R.id.checkbox_paths_input_presets); 220 mCheckBoxInputDevices = (CheckBox)findViewById(R.id.checkbox_paths_input_devices); 221 mCheckBoxOutputDevices = (CheckBox)findViewById(R.id.checkbox_paths_output_devices); 222 } 223 224 @Override getTestName()225 public String getTestName() { 226 return "DataPaths"; 227 } 228 229 @Override getActivityType()230 int getActivityType() { 231 return ACTIVITY_DATA_PATHS; 232 } 233 getMagnitudeText(double value)234 static String getMagnitudeText(double value) { 235 return String.format(MAGNITUDE_FORMAT, value); 236 } 237 getConfigText(StreamConfiguration config)238 protected String getConfigText(StreamConfiguration config) { 239 String text = super.getConfigText(config); 240 if (config.getDirection() == StreamConfiguration.DIRECTION_INPUT) { 241 text += ", inPre = " + StreamConfiguration.convertInputPresetToText(config.getInputPreset()); 242 } 243 return text; 244 } 245 246 @Override shouldTestBeSkipped()247 protected String shouldTestBeSkipped() { 248 String why = ""; 249 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 250 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 251 StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; 252 StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration; 253 // No point running the test if we don't get the data path we requested. 254 if (actualInConfig.isMMap() != requestedInConfig.isMMap()) { 255 log("Did not get requested MMap input stream"); 256 why += "mmap"; 257 } 258 if (actualOutConfig.isMMap() != requestedOutConfig.isMMap()) { 259 log("Did not get requested MMap output stream"); 260 why += "mmap"; 261 } 262 // Did we request a device and not get that device? 263 if (requestedInConfig.getDeviceId() != 0 264 && (requestedInConfig.getDeviceId() != actualInConfig.getDeviceId())) { 265 why += ", inDev(" + requestedInConfig.getDeviceId() 266 + "!=" + actualInConfig.getDeviceId() + ")"; 267 } 268 if (requestedOutConfig.getDeviceId() != 0 269 && (requestedOutConfig.getDeviceId() != actualOutConfig.getDeviceId())) { 270 why += ", outDev(" + requestedOutConfig.getDeviceId() 271 + "!=" + actualOutConfig.getDeviceId() + ")"; 272 } 273 if ((requestedInConfig.getInputPreset() != actualInConfig.getInputPreset())) { 274 why += ", inPre(" + requestedInConfig.getInputPreset() 275 + "!=" + actualInConfig.getInputPreset() + ")"; 276 } 277 return why; 278 } 279 280 @Override isFinishedEarly()281 protected boolean isFinishedEarly() { 282 return (mMaxMagnitude > MIN_REQUIRED_MAGNITUDE) 283 && (getAveragePhaseError() < MAX_ALLOWED_JITTER) 284 && isPhaseJitterValid(); 285 } 286 287 // @return reasons for failure of empty string 288 @Override didTestFail()289 public String didTestFail() { 290 String why = ""; 291 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 292 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 293 StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; 294 StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration; 295 if (mMaxMagnitude <= MIN_REQUIRED_MAGNITUDE) { 296 why += ", mag"; 297 } 298 if (!isPhaseJitterValid()) { 299 why += ", jitterUnknown"; 300 } else if (getAveragePhaseError() > MAX_ALLOWED_JITTER) { 301 why += ", jitterHigh"; 302 } 303 return why; 304 } 305 getAveragePhaseError()306 private double getAveragePhaseError() { 307 // If we have no measurements then return maximum possible phase jitter 308 // to avoid dividing by zero. 309 return (mPhaseErrorCount > 0) ? (mPhaseErrorSum / mPhaseErrorCount) : Math.PI; 310 } 311 isPhaseJitterValid()312 private boolean isPhaseJitterValid() { 313 // Arbitrary number of measurements to be considered valid. 314 final int kMinPhaseErrorCount = 5; 315 return mPhaseErrorCount >= kMinPhaseErrorCount; 316 } 317 getOneLineSummary()318 String getOneLineSummary() { 319 StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; 320 StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration; 321 return "#" + mAutomatedTestRunner.getTestCount() 322 + ", IN" + (actualInConfig.isMMap() ? "-M" : "-L") 323 + " D=" + actualInConfig.getDeviceId() 324 + ", ch=" + channelText(getInputChannel(), actualInConfig.getChannelCount()) 325 + ", OUT" + (actualOutConfig.isMMap() ? "-M" : "-L") 326 + " D=" + actualOutConfig.getDeviceId() 327 + ", ch=" + channelText(getOutputChannel(), actualOutConfig.getChannelCount()) 328 + ", mag = " + getMagnitudeText(mMaxMagnitude); 329 } 330 setupDeviceCombo(int numInputChannels, int inputChannel, int numOutputChannels, int outputChannel)331 void setupDeviceCombo(int numInputChannels, 332 int inputChannel, 333 int numOutputChannels, 334 int outputChannel) throws InterruptedException { 335 // Configure settings 336 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 337 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 338 339 requestedInConfig.reset(); 340 requestedOutConfig.reset(); 341 342 requestedInConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY); 343 requestedOutConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY); 344 345 requestedInConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED); 346 requestedOutConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED); 347 348 requestedInConfig.setChannelCount(numInputChannels); 349 requestedOutConfig.setChannelCount(numOutputChannels); 350 351 requestedInConfig.setMMap(false); 352 requestedOutConfig.setMMap(false); 353 354 setInputChannel(inputChannel); 355 setOutputChannel(outputChannel); 356 } 357 testConfigurationsAddMagJitter()358 private TestResult testConfigurationsAddMagJitter() throws InterruptedException { 359 TestResult testResult = testConfigurations(); 360 if (testResult != null) { 361 testResult.addComment("mag = " + TestDataPathsActivity.getMagnitudeText(mMagnitude) 362 + ", jitter = " + getJitterText()); 363 } 364 return testResult; 365 } 366 testPresetCombo(int inputPreset, int numInputChannels, int inputChannel, int numOutputChannels, int outputChannel, boolean mmapEnabled )367 void testPresetCombo(int inputPreset, 368 int numInputChannels, 369 int inputChannel, 370 int numOutputChannels, 371 int outputChannel, 372 boolean mmapEnabled 373 ) throws InterruptedException { 374 setupDeviceCombo(numInputChannels, inputChannel, numOutputChannels, outputChannel); 375 376 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 377 requestedInConfig.setInputPreset(inputPreset); 378 requestedInConfig.setMMap(mmapEnabled); 379 380 mMagnitude = -1.0; 381 TestResult testResult = testConfigurationsAddMagJitter(); 382 if (testResult != null) { 383 int result = testResult.result; 384 String summary = getOneLineSummary() 385 + ", inPre = " 386 + StreamConfiguration.convertInputPresetToText(inputPreset) 387 + "\n"; 388 appendSummary(summary); 389 if (result == TEST_RESULT_FAILED) { 390 if (getMagnitude() < 0.000001) { 391 testResult.addComment("The input is completely SILENT!"); 392 } else if (inputPreset == StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION) { 393 testResult.addComment("Maybe sine wave blocked by Echo Cancellation!"); 394 } 395 } 396 } 397 } 398 testPresetCombo(int inputPreset, int numInputChannels, int inputChannel, int numOutputChannels, int outputChannel )399 void testPresetCombo(int inputPreset, 400 int numInputChannels, 401 int inputChannel, 402 int numOutputChannels, 403 int outputChannel 404 ) throws InterruptedException { 405 if (NativeEngine.isMMapSupported()) { 406 testPresetCombo(inputPreset, numInputChannels, inputChannel, 407 numOutputChannels, outputChannel, true); 408 } 409 testPresetCombo(inputPreset, numInputChannels, inputChannel, 410 numOutputChannels, outputChannel, false); 411 } 412 testPresetCombo(int inputPreset)413 void testPresetCombo(int inputPreset) throws InterruptedException { 414 setTestName("Test InPreset = " + StreamConfiguration.convertInputPresetToText(inputPreset)); 415 testPresetCombo(inputPreset, 1, 0, 1, 0); 416 } 417 testInputPresets()418 private void testInputPresets() throws InterruptedException { 419 logBoth("\nTest InputPreset -------"); 420 421 for (int inputPreset : INPUT_PRESETS) { 422 testPresetCombo(inputPreset); 423 } 424 // TODO Resolve issue with echo cancellation killing the signal. 425 // testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION, 426 // 1, 0, 2, 0); 427 // testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION, 428 // 1, 0, 2, 1); 429 // testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION, 430 // 2, 0, 2, 0); 431 // testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION, 432 // 2, 0, 2, 1); 433 } 434 testInputDeviceCombo(int deviceId, int numInputChannels, int inputChannel, boolean mmapEnabled)435 void testInputDeviceCombo(int deviceId, 436 int numInputChannels, 437 int inputChannel, 438 boolean mmapEnabled) throws InterruptedException { 439 final int numOutputChannels = 2; 440 setupDeviceCombo(numInputChannels, inputChannel, numOutputChannels, 0); 441 442 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 443 requestedInConfig.setInputPreset(StreamConfiguration.INPUT_PRESET_VOICE_RECOGNITION); 444 requestedInConfig.setDeviceId(deviceId); 445 requestedInConfig.setMMap(mmapEnabled); 446 447 mMagnitude = -1.0; 448 TestResult testResult = testConfigurationsAddMagJitter(); 449 if (testResult != null) { 450 appendSummary(getOneLineSummary() + "\n"); 451 } 452 } 453 testInputDeviceCombo(int deviceId, int deviceType, int numInputChannels, int inputChannel)454 void testInputDeviceCombo(int deviceId, 455 int deviceType, 456 int numInputChannels, 457 int inputChannel) throws InterruptedException { 458 459 String typeString = AudioDeviceInfoConverter.typeToString(deviceType); 460 setTestName("Test InDev: #" + deviceId + " " + typeString 461 + "_" + inputChannel + "/" + numInputChannels); 462 if (NativeEngine.isMMapSupported()) { 463 testInputDeviceCombo(deviceId, numInputChannels, inputChannel, true); 464 } 465 testInputDeviceCombo(deviceId, numInputChannels, inputChannel, false); 466 } 467 testInputDevices()468 void testInputDevices() throws InterruptedException { 469 logBoth("\nTest Input Devices -------"); 470 471 AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); 472 int numTested = 0; 473 for (AudioDeviceInfo deviceInfo : devices) { 474 log("----\n" 475 + AudioDeviceInfoConverter.toString(deviceInfo) + "\n"); 476 if (!deviceInfo.isSource()) continue; // FIXME log as error?! 477 int deviceType = deviceInfo.getType(); 478 if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_MIC) { 479 int id = deviceInfo.getId(); 480 int[] channelCounts = deviceInfo.getChannelCounts(); 481 numTested++; 482 // Always test mono and stereo. 483 testInputDeviceCombo(id, deviceType, 1, 0); 484 testInputDeviceCombo(id, deviceType, 2, 0); 485 testInputDeviceCombo(id, deviceType, 2, 1); 486 if (channelCounts.length > 0) { 487 for (int numChannels : channelCounts) { 488 // Test higher channel counts. 489 if (numChannels > 2) { 490 log("numChannels = " + numChannels + "\n"); 491 for (int channel = 0; channel < numChannels; channel++) { 492 testInputDeviceCombo(id, deviceType, numChannels, channel); 493 } 494 } 495 } 496 } 497 } else { 498 log("Device skipped for type."); 499 } 500 } 501 502 if (numTested == 0) { 503 log("NO INPUT DEVICE FOUND!\n"); 504 } 505 } 506 testOutputDeviceCombo(int deviceId, int deviceType, int numOutputChannels, int outputChannel, boolean mmapEnabled)507 void testOutputDeviceCombo(int deviceId, 508 int deviceType, 509 int numOutputChannels, 510 int outputChannel, 511 boolean mmapEnabled) throws InterruptedException { 512 final int numInputChannels = 2; // TODO review, done because of mono problems on some devices 513 setupDeviceCombo(numInputChannels, 0, numOutputChannels, outputChannel); 514 515 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 516 requestedOutConfig.setDeviceId(deviceId); 517 requestedOutConfig.setMMap(mmapEnabled); 518 519 mMagnitude = -1.0; 520 TestResult testResult = testConfigurationsAddMagJitter(); 521 if (testResult != null) { 522 int result = testResult.result; 523 appendSummary(getOneLineSummary() + "\n"); 524 if (result == TEST_RESULT_FAILED) { 525 if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE 526 && numOutputChannels == 2 527 && outputChannel == 1) { 528 testResult.addComment("Maybe EARPIECE does not mix stereo to mono!"); 529 } 530 if (deviceType == TYPE_BUILTIN_SPEAKER_SAFE 531 && numOutputChannels == 2 532 && outputChannel == 0) { 533 testResult.addComment("Maybe SPEAKER_SAFE dropped channel zero!"); 534 } 535 } 536 } 537 } 538 testOutputDeviceCombo(int deviceId, int deviceType, int numOutputChannels, int outputChannel)539 void testOutputDeviceCombo(int deviceId, 540 int deviceType, 541 int numOutputChannels, 542 int outputChannel) throws InterruptedException { 543 String typeString = AudioDeviceInfoConverter.typeToString(deviceType); 544 setTestName("Test OutDev: #" + deviceId + " " + typeString 545 + "_" + outputChannel + "/" + numOutputChannels); 546 if (NativeEngine.isMMapSupported()) { 547 testOutputDeviceCombo(deviceId, deviceType, numOutputChannels, outputChannel, true); 548 } 549 testOutputDeviceCombo(deviceId, deviceType, numOutputChannels, outputChannel, false); 550 } 551 logBoth(String text)552 void logBoth(String text) { 553 log(text); 554 appendSummary(text + "\n"); 555 } 556 logFailed(String text)557 void logFailed(String text) { 558 log(text); 559 logAnalysis(text + "\n"); 560 } 561 testOutputDevices()562 void testOutputDevices() throws InterruptedException { 563 logBoth("\nTest Output Devices -------"); 564 565 AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 566 int numTested = 0; 567 for (AudioDeviceInfo deviceInfo : devices) { 568 log("----\n" 569 + AudioDeviceInfoConverter.toString(deviceInfo) + "\n"); 570 if (!deviceInfo.isSink()) continue; 571 int deviceType = deviceInfo.getType(); 572 if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER 573 || deviceType == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE 574 || deviceType == TYPE_BUILTIN_SPEAKER_SAFE) { 575 int id = deviceInfo.getId(); 576 int[] channelCounts = deviceInfo.getChannelCounts(); 577 numTested++; 578 // Always test mono and stereo. 579 testOutputDeviceCombo(id, deviceType, 1, 0); 580 testOutputDeviceCombo(id, deviceType, 2, 0); 581 testOutputDeviceCombo(id, deviceType, 2, 1); 582 if (channelCounts.length > 0) { 583 for (int numChannels : channelCounts) { 584 // Test higher channel counts. 585 if (numChannels > 2) { 586 log("numChannels = " + numChannels + "\n"); 587 for (int channel = 0; channel < numChannels; channel++) { 588 testOutputDeviceCombo(id, deviceType, numChannels, channel); 589 } 590 } 591 } 592 } 593 } else { 594 log("Device skipped for type."); 595 } 596 } 597 if (numTested == 0) { 598 log("NO OUTPUT DEVICE FOUND!\n"); 599 } 600 } 601 602 @Override runTest()603 public void runTest() { 604 try { 605 logDeviceInfo(); 606 log("min.required.magnitude = " + MIN_REQUIRED_MAGNITUDE); 607 log("max.allowed.jitter = " + MAX_ALLOWED_JITTER); 608 log("test.gap.msec = " + mGapMillis); 609 610 mTestResults.clear(); 611 mDurationSeconds = DURATION_SECONDS; 612 613 if (mCheckBoxInputPresets.isChecked()) { 614 runOnUiThread(() -> mCheckBoxInputPresets.setEnabled(false)); 615 testInputPresets(); 616 } 617 if (mCheckBoxInputDevices.isChecked()) { 618 runOnUiThread(() -> mCheckBoxInputDevices.setEnabled(false)); 619 testInputDevices(); 620 } 621 if (mCheckBoxOutputDevices.isChecked()) { 622 runOnUiThread(() -> mCheckBoxOutputDevices.setEnabled(false)); 623 testOutputDevices(); 624 } 625 626 analyzeTestResults(); 627 628 } catch (InterruptedException e) { 629 analyzeTestResults(); 630 } catch (Exception e) { 631 log(e.getMessage()); 632 showErrorToast(e.getMessage()); 633 } finally { 634 runOnUiThread(() -> { 635 mCheckBoxInputPresets.setEnabled(true); 636 mCheckBoxInputDevices.setEnabled(true); 637 mCheckBoxOutputDevices.setEnabled(true); 638 }); 639 } 640 } 641 642 @Override startTestUsingBundle()643 public void startTestUsingBundle() { 644 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 645 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 646 configureStreamsFromBundle(mBundleFromIntent, requestedInConfig, requestedOutConfig); 647 648 boolean shouldUseInputPresets = mBundleFromIntent.getBoolean(KEY_USE_INPUT_PRESETS, 649 VALUE_DEFAULT_USE_INPUT_PRESETS); 650 boolean shouldUseInputDevices = mBundleFromIntent.getBoolean(KEY_USE_INPUT_DEVICES, 651 VALUE_DEFAULT_USE_INPUT_DEVICES); 652 boolean shouldUseOutputDevices = mBundleFromIntent.getBoolean(KEY_USE_OUTPUT_DEVICES, 653 VALUE_DEFAULT_USE_OUTPUT_DEVICES); 654 int singleTestIndex = mBundleFromIntent.getInt(KEY_SINGLE_TEST_INDEX, 655 VALUE_DEFAULT_SINGLE_TEST_INDEX); 656 657 runOnUiThread(() -> { 658 mCheckBoxInputPresets.setChecked(shouldUseInputPresets); 659 mCheckBoxInputDevices.setChecked(shouldUseInputDevices); 660 mCheckBoxOutputDevices.setChecked(shouldUseOutputDevices); 661 mAutomatedTestRunner.setTestIndexText(singleTestIndex); 662 }); 663 664 mAutomatedTestRunner.startTest(); 665 } 666 } 667