1 /* 2 * Copyright 2019 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 android.content.Context; 20 import android.media.AudioManager; 21 import android.os.Bundle; 22 import android.support.annotation.Nullable; 23 24 import java.io.IOException; 25 import java.util.ArrayList; 26 27 public class BaseAutoGlitchActivity extends GlitchActivity { 28 29 private static final int SETUP_TIME_SECONDS = 4; // Time for the stream to settle. 30 protected static final int DEFAULT_DURATION_SECONDS = 8; // Run time for each test. 31 private static final int DEFAULT_GAP_MILLIS = 400; // Idle time between each test. 32 private static final String TEXT_SKIP = "SKIP"; 33 public static final String TEXT_PASS = "PASS"; 34 public static final String TEXT_FAIL = "FAIL !!!!"; 35 36 protected int mDurationSeconds = DEFAULT_DURATION_SECONDS; 37 protected int mGapMillis = DEFAULT_GAP_MILLIS; 38 private String mTestName = ""; 39 40 protected ArrayList<TestResult> mTestResults = new ArrayList<TestResult>(); 41 logDeviceInfo()42 void logDeviceInfo() { 43 log("\n############################"); 44 log("\nDevice Info:"); 45 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 46 log(AudioQueryTools.getAudioManagerReport(audioManager)); 47 log(AudioQueryTools.getAudioFeatureReport(getPackageManager())); 48 log(AudioQueryTools.getAudioPropertyReport()); 49 log("\n############################"); 50 } 51 setTestName(String name)52 void setTestName(String name) { 53 mTestName = name; 54 } 55 56 private static class TestDirection { 57 public final int channelUsed; 58 public final int channelCount; 59 public final int deviceId; 60 public final int mmapUsed; 61 public final int performanceMode; 62 public final int sharingMode; 63 TestDirection(StreamConfiguration configuration, int channelUsed)64 public TestDirection(StreamConfiguration configuration, int channelUsed) { 65 this.channelUsed = channelUsed; 66 channelCount = configuration.getChannelCount(); 67 deviceId = configuration.getDeviceId(); 68 mmapUsed = configuration.isMMap() ? 1 : 0; 69 performanceMode = configuration.getPerformanceMode(); 70 sharingMode = configuration.getSharingMode(); 71 } 72 countDifferences(TestDirection other)73 int countDifferences(TestDirection other) { 74 int count = 0; 75 count += (channelUsed != other.channelUsed) ? 1 : 0; 76 count += (channelCount != other.channelCount) ? 1 : 0; 77 count += (deviceId != other.deviceId) ? 1 : 0; 78 count += (mmapUsed != other.mmapUsed) ? 1 : 0; 79 count += (performanceMode != other.performanceMode) ? 1 : 0; 80 count += (sharingMode != other.sharingMode) ? 1 : 0; 81 return count; 82 } 83 comparePassedDirection(String prefix, TestDirection passed)84 public String comparePassedDirection(String prefix, TestDirection passed) { 85 StringBuffer text = new StringBuffer(); 86 text.append(TestDataPathsActivity.comparePassedField(prefix, this, passed, "channelUsed")); 87 text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "channelCount")); 88 text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "deviceId")); 89 text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "mmapUsed")); 90 text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "performanceMode")); 91 text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "sharingMode")); 92 return text.toString(); 93 } 94 @Override toString()95 public String toString() { 96 return "D=" + deviceId 97 + ", " + ((mmapUsed > 0) ? "MMAP" : "Lgcy") 98 + ", ch=" + channelText(channelUsed, channelCount) 99 + "," + StreamConfiguration.convertPerformanceModeToText(performanceMode) 100 + "," + StreamConfiguration.convertSharingModeToText(sharingMode); 101 } 102 } 103 104 protected static class TestResult { 105 final int testIndex; 106 final TestDirection input; 107 final TestDirection output; 108 public final int inputPreset; 109 public final int sampleRate; 110 final String testName; // name or purpose of test 111 112 int result = TEST_RESULT_SKIPPED; // TEST_RESULT_FAILED, etc 113 private String mComments = ""; // additional info, ideas for why it failed 114 TestResult(int testIndex, String testName, StreamConfiguration inputConfiguration, int inputChannel, StreamConfiguration outputConfiguration, int outputChannel)115 public TestResult(int testIndex, 116 String testName, 117 StreamConfiguration inputConfiguration, 118 int inputChannel, 119 StreamConfiguration outputConfiguration, 120 int outputChannel) { 121 this.testIndex = testIndex; 122 this.testName = testName; 123 input = new TestDirection(inputConfiguration, inputChannel); 124 output = new TestDirection(outputConfiguration, outputChannel); 125 sampleRate = outputConfiguration.getSampleRate(); 126 this.inputPreset = inputConfiguration.getInputPreset(); 127 } 128 countDifferences(TestResult other)129 int countDifferences(TestResult other) { 130 int count = 0; 131 count += input.countDifferences((other.input)); 132 count += output.countDifferences((other.output)); 133 count += (sampleRate != other.sampleRate) ? 1 : 0; 134 count += (inputPreset != other.inputPreset) ? 1 : 0; 135 return count; 136 } 137 failed()138 public boolean failed() { 139 return result == TEST_RESULT_FAILED; 140 } 141 passed()142 public boolean passed() { 143 return result == TEST_RESULT_PASSED; 144 } 145 comparePassed(TestResult passed)146 public String comparePassed(TestResult passed) { 147 StringBuffer text = new StringBuffer(); 148 text.append("Compare with passed test #" + passed.testIndex + "\n"); 149 text.append(input.comparePassedDirection("IN", passed.input)); 150 text.append(TestDataPathsActivity.comparePassedField("IN", this, passed, "inputPreset")); 151 text.append(output.comparePassedDirection("OUT", passed.output)); 152 text.append(TestDataPathsActivity.comparePassedField("I/O",this, passed, "sampleRate")); 153 154 return text.toString(); 155 } 156 157 @Override toString()158 public String toString() { 159 return "IN: " + input + ", ip=" + inputPreset + "\n" 160 + "OUT: " + output + ", sr=" + sampleRate 161 + mComments; 162 } 163 addComment(String comment)164 public void addComment(String comment) { 165 mComments += "\n"; 166 mComments += comment; 167 } 168 setResult(int result)169 public void setResult(int result) { 170 this.result = result; 171 } getResult(int result)172 public int getResult(int result) { 173 return result; 174 } 175 } 176 177 @Override onCreate(Bundle savedInstanceState)178 protected void onCreate(Bundle savedInstanceState) { 179 super.onCreate(savedInstanceState); 180 181 mAutomatedTestRunner = findViewById(R.id.auto_test_runner); 182 mAutomatedTestRunner.setActivity(this); 183 } 184 log(String text)185 protected void log(String text) { 186 mAutomatedTestRunner.log(text); 187 } 188 appendFailedSummary(String text)189 protected void appendFailedSummary(String text) { 190 mAutomatedTestRunner.appendFailedSummary(text); 191 } 192 appendSummary(String text)193 protected void appendSummary(String text) { 194 mAutomatedTestRunner.appendSummary(text); 195 } 196 197 @Override onStopTest()198 public void onStopTest() { 199 mAutomatedTestRunner.stopTest(); 200 } 201 channelText(int index, int count)202 static String channelText(int index, int count) { 203 return index + "/" + count; 204 } 205 getConfigText(StreamConfiguration config)206 protected String getConfigText(StreamConfiguration config) { 207 int channel = (config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) 208 ? getOutputChannel() : getInputChannel(); 209 return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "INP") 210 + (config.isMMap() ? "-M" : "-L") 211 + ", ID = " + String.format("%2d", config.getDeviceId()) 212 + ", SR = " + String.format("%5d", config.getSampleRate()) 213 + ", Perf = " + StreamConfiguration.convertPerformanceModeToText( 214 config.getPerformanceMode()) 215 + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()) 216 + ", ch = " + channelText(channel, config.getChannelCount()); 217 } 218 getStreamText(AudioStreamBase stream)219 protected String getStreamText(AudioStreamBase stream) { 220 return ("burst=" + stream.getFramesPerBurst() 221 + ", size=" + stream.getBufferSizeInFrames() 222 + ", cap=" + stream.getBufferCapacityInFrames() 223 ); 224 } 225 226 public final static int TEST_RESULT_FAILED = -2; 227 public final static int TEST_RESULT_WARNING = -1; 228 public final static int TEST_RESULT_SKIPPED = 0; 229 public final static int TEST_RESULT_PASSED = 1; 230 231 // Run one test based on the requested input/output configurations. 232 @Nullable testConfigurations()233 protected TestResult testConfigurations() throws InterruptedException { 234 int result = TEST_RESULT_SKIPPED; 235 mAutomatedTestRunner.incrementTestCount(); 236 if ((getSingleTestIndex() >= 0) && (mAutomatedTestRunner.getTestCount() != getSingleTestIndex())) { 237 return null; 238 } 239 240 log("========================== #" + mAutomatedTestRunner.getTestCount()); 241 242 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 243 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 244 245 StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; 246 StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration; 247 248 log("Requested:"); 249 log(" " + getConfigText(requestedInConfig)); 250 log(" " + getConfigText(requestedOutConfig)); 251 252 String reason = ""; 253 boolean openFailed = false; 254 try { 255 openAudio(); // this will fill in actualConfig 256 log("Actual:"); 257 // Set output size to a level that will avoid glitches. 258 AudioStreamBase outStream = mAudioOutTester.getCurrentAudioStream(); 259 int sizeFrames = outStream.getBufferCapacityInFrames() / 2; 260 sizeFrames = Math.max(sizeFrames, 2 * outStream.getFramesPerBurst()); 261 outStream.setBufferSizeInFrames(sizeFrames); 262 AudioStreamBase inStream = mAudioInputTester.getCurrentAudioStream(); 263 log(" " + getConfigText(actualInConfig)); 264 log(" " + getStreamText(inStream)); 265 log(" " + getConfigText(actualOutConfig)); 266 log(" " + getStreamText(outStream)); 267 } catch (Exception e) { 268 openFailed = true; 269 log(e.getMessage()); 270 reason = e.getMessage(); 271 } 272 273 TestResult testResult = new TestResult( 274 mAutomatedTestRunner.getTestCount(), 275 mTestName, 276 mAudioInputTester.actualConfiguration, 277 getInputChannel(), 278 mAudioOutTester.actualConfiguration, 279 getOutputChannel() 280 ); 281 282 // The test would only be worth running if we got the configuration we requested on input or output. 283 String skipReason = shouldTestBeSkipped(); 284 boolean skipped = skipReason.length() > 0; 285 boolean valid = !openFailed && !skipped; 286 boolean startFailed = false; 287 if (valid) { 288 try { 289 startAudioTest(); 290 } catch (IOException e) { 291 e.printStackTrace(); 292 valid = false; 293 startFailed = true; 294 log(e.getMessage()); 295 reason = e.getMessage(); 296 } 297 } 298 mAutomatedTestRunner.flushLog(); 299 300 if (valid) { 301 // Check for early return until we reach full duration. 302 long now = System.currentTimeMillis(); 303 long startedAt = now; 304 long endTime = System.currentTimeMillis() + (mDurationSeconds * 1000); 305 boolean finishedEarly = false; 306 while (now < endTime && !finishedEarly) { 307 Thread.sleep(100); // Let test run. 308 now = System.currentTimeMillis(); 309 finishedEarly = isFinishedEarly(); 310 if (finishedEarly) { 311 log("Finished early after " + (now - startedAt) + " msec."); 312 } 313 } 314 } 315 int inXRuns = 0; 316 int outXRuns = 0; 317 318 if (!openFailed) { 319 // get xRuns before closing the streams. 320 inXRuns = mAudioInputTester.getCurrentAudioStream().getXRunCount(); 321 outXRuns = mAudioOutTester.getCurrentAudioStream().getXRunCount(); 322 323 super.stopAudioTest(); 324 } 325 326 if (openFailed || startFailed) { 327 appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n"); 328 appendFailedSummary(getConfigText(requestedInConfig) + "\n"); 329 appendFailedSummary(getConfigText(requestedOutConfig) + "\n"); 330 appendFailedSummary(reason + "\n"); 331 mAutomatedTestRunner.incrementFailCount(); 332 } else if (skipped) { 333 log(TEXT_SKIP + " - " + skipReason); 334 } else { 335 log("Result:"); 336 reason += didTestFail(); 337 boolean passed = reason.length() == 0; 338 339 String resultText = getShortReport(); 340 resultText += ", xruns = " + inXRuns + "/" + outXRuns; 341 resultText += ", " + (passed ? TEXT_PASS : TEXT_FAIL); 342 resultText += reason; 343 log(" " + resultText); 344 if (!passed) { 345 appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n"); 346 appendFailedSummary(" " + getConfigText(actualInConfig) + "\n"); 347 appendFailedSummary(" " + getConfigText(actualOutConfig) + "\n"); 348 appendFailedSummary(" " + resultText + "\n"); 349 mAutomatedTestRunner.incrementFailCount(); 350 result = TEST_RESULT_FAILED; 351 } else { 352 mAutomatedTestRunner.incrementPassCount(); 353 result = TEST_RESULT_PASSED; 354 } 355 } 356 mAutomatedTestRunner.flushLog(); 357 358 // Give hardware time to settle between tests. 359 Thread.sleep(mGapMillis); 360 361 if (valid) { 362 testResult.setResult(result); 363 mTestResults.add(testResult); 364 } 365 366 return testResult; 367 } 368 isFinishedEarly()369 protected boolean isFinishedEarly() { 370 return false; 371 } 372 shouldTestBeSkipped()373 protected String shouldTestBeSkipped() { 374 String why = ""; 375 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 376 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 377 StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; 378 StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration; 379 // No point running the test if we don't get the sharing mode we requested. 380 if (actualInConfig.getSharingMode() != requestedInConfig.getSharingMode() 381 || actualOutConfig.getSharingMode() != requestedOutConfig.getSharingMode()) { 382 log("Did not get requested sharing mode."); 383 why += "share"; 384 } 385 // We don't skip based on performance mode because if you request LOW_LATENCY you might 386 // get a smaller burst than if you request NONE. 387 return why; 388 } 389 didTestFail()390 public String didTestFail() { 391 String why = ""; 392 if (getMaxSecondsWithNoGlitch() <= (mDurationSeconds - SETUP_TIME_SECONDS)) { 393 why += ", glitch"; 394 } 395 return why; 396 } 397 logAnalysis(String text)398 void logAnalysis(String text) { 399 appendFailedSummary(text + "\n"); 400 } 401 analyzeTestResults()402 protected void analyzeTestResults() { 403 logAnalysis("\n==== ANALYSIS ==========="); 404 logAnalysis("Compare failed configuration with closest one that passed."); 405 // Analyze each failed test. 406 for (TestResult testResult : mTestResults) { 407 if (testResult.failed()) { 408 logAnalysis("-------------------- #" + testResult.testIndex + " FAILED"); 409 String name = testResult.testName; 410 if (name.length() > 0) { 411 logAnalysis(name); 412 } 413 TestResult[] closest = findClosestPassingTestResults(testResult); 414 for (TestResult other : closest) { 415 logAnalysis(testResult.comparePassed(other)); 416 } 417 logAnalysis(testResult.toString()); 418 } 419 } 420 } 421 422 @Nullable findClosestPassingTestResults(TestResult testResult)423 private TestResult[] findClosestPassingTestResults(TestResult testResult) { 424 int minDifferences = Integer.MAX_VALUE; 425 for (TestResult other : mTestResults) { 426 if (other.passed()) { 427 int numDifferences = testResult.countDifferences(other); 428 if (numDifferences < minDifferences) { 429 minDifferences = numDifferences; 430 } 431 } 432 } 433 // Now find all the tests that are just as close as the closest. 434 ArrayList<TestResult> list = new ArrayList<TestResult>(); 435 for (TestResult other : mTestResults) { 436 if (other.passed()) { 437 int numDifferences = testResult.countDifferences(other); 438 if (numDifferences == minDifferences) { 439 list.add(other); 440 } 441 } 442 } 443 return list.toArray(new TestResult[0]); 444 } 445 446 } 447