1 /* 2 * Copyright (C) 2014 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 org.drrickorang.loopback; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.DialogFragment; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.content.pm.PackageManager; 27 import android.database.Cursor; 28 import android.graphics.Bitmap; 29 import android.graphics.Canvas; 30 import android.media.AudioManager; 31 import android.net.Uri; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Message; 37 import android.os.ParcelFileDescriptor; 38 import android.provider.MediaStore; 39 import android.support.v4.app.ActivityCompat; 40 import android.support.v4.content.ContextCompat; 41 import android.text.format.DateFormat; 42 import android.util.Log; 43 import android.view.Gravity; 44 import android.view.Menu; 45 import android.view.MenuInflater; 46 import android.view.MenuItem; 47 import android.view.View; 48 import android.view.ViewGroup; 49 import android.widget.Button; 50 import android.widget.LinearLayout; 51 import android.widget.PopupWindow; 52 import android.widget.SeekBar; 53 import android.widget.TextView; 54 import android.widget.Toast; 55 56 import java.io.File; 57 import java.io.FileDescriptor; 58 import java.io.FileOutputStream; 59 import java.util.Arrays; 60 import java.util.Locale; 61 62 63 /** 64 * This is the main activity of the Loopback app. Two tests (latency test and buffer test) can be 65 * initiated here. Note: buffer test and glitch detection is the same test, it's just that this test 66 * has two parts of result. 67 */ 68 69 public class LoopbackActivity extends Activity 70 implements SaveFilesDialogFragment.NoticeDialogListener { 71 private static final String TAG = "LoopbackActivity"; 72 73 private static final int SAVE_TO_WAVE_REQUEST = 42; 74 private static final int SAVE_TO_PNG_REQUEST = 43; 75 private static final int SAVE_TO_TXT_REQUEST = 44; 76 private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST = 45; 77 private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST = 46; 78 private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST = 47; 79 private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST = 48; 80 private static final int SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 49; 81 private static final int SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 50; 82 private static final int SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST = 51; 83 private static final int SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST = 52; 84 85 private static final int SETTINGS_ACTIVITY_REQUEST = 54; 86 87 private static final int THREAD_SLEEP_DURATION_MS = 200; 88 private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY = 201; 89 private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER = 202; 90 private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS = 203; 91 private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT = 204; 92 private static final int LATENCY_TEST_STARTED = 300; 93 private static final int LATENCY_TEST_ENDED = 301; 94 private static final int BUFFER_TEST_STARTED = 302; 95 private static final int BUFFER_TEST_ENDED = 303; 96 private static final int CALIBRATION_STARTED = 304; 97 private static final int CALIBRATION_ENDED = 305; 98 99 // 0-100 controls compression rate, currently ignore because PNG format is being used 100 private static final int EXPORTED_IMAGE_QUALITY = 100; 101 102 private static final int HISTOGRAM_EXPORT_WIDTH = 2000; 103 private static final int HISTOGRAM_EXPORT_HEIGHT = 2000; 104 private static final int HEATMAP_DRAW_WIDTH = 2560; 105 private static final int HEATMAP_DRAW_HEIGHT = 1440; 106 private static final int HEATMAP_EXPORT_DIVISOR = 2; 107 108 LoopbackAudioThread mAudioThread = null; 109 NativeAudioThread mNativeAudioThread = null; 110 private Thread mCalibrationThread; 111 private WavePlotView mWavePlotView; 112 private String mTestStartTimeString = "IncorrectTime"; // The time the test begins 113 private static final String FILE_SAVE_PATH = "file://mnt/sdcard/"; 114 115 private SeekBar mBarMasterLevel; // drag the volume 116 private TextView mTextInfo; 117 private TextView mTextViewCurrentLevel; 118 private TextView mTextViewResultSummary; 119 120 private int mTestType; 121 private double [] mWaveData; // this is where we store the data for the wave plot 122 private Correlation mCorrelation = new Correlation(); 123 private BufferPeriod mRecorderBufferPeriod = new BufferPeriod(); 124 private BufferPeriod mPlayerBufferPeriod = new BufferPeriod(); 125 126 // for native buffer period 127 private int[] mNativeRecorderBufferPeriodArray; 128 private int mNativeRecorderMaxBufferPeriod; 129 private double mNativeRecorderStdDevBufferPeriod; 130 private int[] mNativePlayerBufferPeriodArray; 131 private int mNativePlayerMaxBufferPeriod; 132 private double mNativePlayerStdDevBufferPeriod; 133 private BufferCallbackTimes mRecorderCallbackTimes; 134 private BufferCallbackTimes mPlayerCallbackTimes; 135 136 private static final String INTENT_SAMPLING_FREQUENCY = "SF"; 137 private static final String INTENT_CHANNEL_INDEX = "CI"; 138 private static final String INTENT_FILENAME = "FileName"; 139 private static final String INTENT_RECORDER_BUFFER = "RecorderBuffer"; 140 private static final String INTENT_PLAYER_BUFFER = "PlayerBuffer"; 141 private static final String INTENT_AUDIO_THREAD = "AudioThread"; 142 private static final String INTENT_MIC_SOURCE = "MicSource"; 143 private static final String INTENT_PERFORMANCE_MODE = "PerformanceMode"; 144 private static final String INTENT_AUDIO_LEVEL = "AudioLevel"; 145 private static final String INTENT_IGNORE_FIRST_FRAMES = "IgnoreFirstFrames"; 146 private static final String INTENT_TEST_TYPE = "TestType"; 147 private static final String INTENT_BUFFER_TEST_DURATION = "BufferTestDuration"; 148 private static final String INTENT_NUMBER_LOAD_THREADS = "NumLoadThreads"; 149 private static final String INTENT_ENABLE_SYSTRACE = "CaptureSysTrace"; 150 private static final String INTENT_ENABLE_WAVCAPTURE = "CaptureWavs"; 151 private static final String INTENT_NUM_CAPTURES = "NumCaptures"; 152 private static final String INTENT_WAV_DURATION = "WavDuration"; 153 154 // for running the test using adb command 155 private boolean mIntentRunning = false; // if it is running triggered by intent with parameters 156 private String mIntentFileName; 157 158 // Note: these values should only be assigned in restartAudioSystem() 159 private int mAudioThreadType = Constant.UNKNOWN; 160 private int mMicSource; 161 private int mPerformanceMode; 162 private int mSamplingRate; 163 private int mChannelIndex; 164 private int mSoundLevel; 165 private int mPlayerBufferSizeInBytes; 166 private int mRecorderBufferSizeInBytes; 167 private int mIgnoreFirstFrames; // TODO: this only applies to native mode 168 private CaptureHolder mCaptureHolder; 169 170 // for buffer test 171 private int[] mGlitchesData; 172 private boolean mGlitchingIntervalTooLong; 173 private int mFFTSamplingSize; 174 private int mFFTOverlapSamples; 175 private long mBufferTestStartTime; 176 private int mBufferTestElapsedSeconds; 177 private int mBufferTestDurationInSeconds; 178 private int mBufferTestWavePlotDurationInSeconds; 179 180 // threads that load CPUs 181 private LoadThread[] mLoadThreads; 182 183 // for getting the Service 184 boolean mBound = false; 185 private AudioTestService mAudioTestService; 186 private final ServiceConnection mServiceConnection = new ServiceConnection() { 187 public void onServiceConnected(ComponentName className, IBinder service) { 188 mAudioTestService = ((AudioTestService.AudioTestBinder) service).getService(); 189 mBound = true; 190 } 191 192 public void onServiceDisconnected(ComponentName className) { 193 mAudioTestService = null; 194 mBound = false; 195 } 196 }; 197 198 private Handler mMessageHandler = new Handler() { 199 public void handleMessage(Message msg) { 200 super.handleMessage(msg); 201 switch (msg.what) { 202 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 203 log("got message java latency test started!!"); 204 showToast("Java Latency Test Started"); 205 resetResults(); 206 refreshState(); 207 refreshPlots(); 208 break; 209 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 210 log("got message java latency test rec can't start!!"); 211 showToast("Java Latency Test Recording Error. Please try again"); 212 refreshState(); 213 stopAudioTestThreads(); 214 mIntentRunning = false; 215 refreshSoundLevelBar(); 216 break; 217 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 218 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 219 if (mAudioThread != null) { 220 mWaveData = mAudioThread.getWaveData(); 221 mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes(); 222 mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes(); 223 mCorrelation.computeCorrelation(mWaveData, mSamplingRate); 224 log("got message java latency rec complete!!"); 225 refreshPlots(); 226 refreshState(); 227 228 switch (msg.what) { 229 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 230 showToast("Java Latency Test Stopped"); 231 break; 232 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 233 showToast("Java Latency Test Completed"); 234 break; 235 } 236 237 stopAudioTestThreads(); 238 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 239 saveAllTo(mIntentFileName); 240 } 241 mIntentRunning = false; 242 } 243 refreshSoundLevelBar(); 244 break; 245 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 246 log("got message java buffer test rec started!!"); 247 showToast("Java Buffer Test Started"); 248 resetResults(); 249 refreshState(); 250 refreshPlots(); 251 mBufferTestStartTime = System.currentTimeMillis(); 252 break; 253 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 254 log("got message java buffer test rec can't start!!"); 255 showToast("Java Buffer Test Recording Error. Please try again"); 256 refreshState(); 257 stopAudioTestThreads(); 258 mIntentRunning = false; 259 refreshSoundLevelBar(); 260 break; 261 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 262 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 263 if (mAudioThread != null) { 264 mWaveData = mAudioThread.getWaveData(); 265 mGlitchesData = mAudioThread.getAllGlitches(); 266 mGlitchingIntervalTooLong = mAudioThread.getGlitchingIntervalTooLong(); 267 mFFTSamplingSize = mAudioThread.getFFTSamplingSize(); 268 mFFTOverlapSamples = mAudioThread.getFFTOverlapSamples(); 269 mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes(); 270 mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes(); 271 refreshPlots(); // only plot that last few seconds 272 refreshState(); 273 //rounded up number of seconds elapsed 274 mBufferTestElapsedSeconds = 275 (int) ((System.currentTimeMillis() - mBufferTestStartTime + 276 Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND); 277 switch (msg.what) { 278 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 279 showToast("Java Buffer Test Stopped"); 280 break; 281 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 282 showToast("Java Buffer Test Completed"); 283 break; 284 } 285 if (getApp().isCaptureEnabled()) { 286 mCaptureHolder.stopLoopbackListenerScript(); 287 } 288 stopAudioTestThreads(); 289 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 290 saveAllTo(mIntentFileName); 291 } 292 mIntentRunning = false; 293 } 294 refreshSoundLevelBar(); 295 break; 296 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 297 log("got message native latency test rec started!!"); 298 showToast("Native Latency Test Started"); 299 resetResults(); 300 refreshState(); 301 refreshPlots(); 302 break; 303 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 304 log("got message native buffer test rec started!!"); 305 showToast("Native Buffer Test Started"); 306 resetResults(); 307 refreshState(); 308 refreshPlots(); 309 mBufferTestStartTime = System.currentTimeMillis(); 310 break; 311 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 312 log("got message native latency test rec can't start!!"); 313 showToast("Native Latency Test Recording Error. Please try again"); 314 refreshState(); 315 mIntentRunning = false; 316 refreshSoundLevelBar(); 317 break; 318 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 319 log("got message native buffer test rec can't start!!"); 320 showToast("Native Buffer Test Recording Error. Please try again"); 321 refreshState(); 322 mIntentRunning = false; 323 refreshSoundLevelBar(); 324 break; 325 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 326 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 327 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 328 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 329 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 330 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 331 if (mNativeAudioThread != null) { 332 mGlitchesData = mNativeAudioThread.getNativeAllGlitches(); 333 mGlitchingIntervalTooLong = mNativeAudioThread.getGlitchingIntervalTooLong(); 334 mFFTSamplingSize = mNativeAudioThread.getNativeFFTSamplingSize(); 335 mFFTOverlapSamples = mNativeAudioThread.getNativeFFTOverlapSamples(); 336 mWaveData = mNativeAudioThread.getWaveData(); 337 mNativeRecorderBufferPeriodArray = mNativeAudioThread.getRecorderBufferPeriod(); 338 mNativeRecorderMaxBufferPeriod = 339 mNativeAudioThread.getRecorderMaxBufferPeriod(); 340 mNativeRecorderStdDevBufferPeriod = 341 mNativeAudioThread.getRecorderStdDevBufferPeriod(); 342 mNativePlayerBufferPeriodArray = mNativeAudioThread.getPlayerBufferPeriod(); 343 mNativePlayerMaxBufferPeriod = mNativeAudioThread.getPlayerMaxBufferPeriod(); 344 mNativePlayerStdDevBufferPeriod = 345 mNativeAudioThread.getPlayerStdDevBufferPeriod(); 346 mRecorderCallbackTimes = mNativeAudioThread.getRecorderCallbackTimes(); 347 mPlayerCallbackTimes = mNativeAudioThread.getPlayerCallbackTimes(); 348 349 if (msg.what != NativeAudioThread. 350 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE) { 351 mCorrelation.computeCorrelation(mWaveData, mSamplingRate); 352 } 353 354 log("got message native buffer test rec complete!!"); 355 refreshPlots(); 356 refreshState(); 357 //rounded up number of seconds elapsed 358 mBufferTestElapsedSeconds = 359 (int) ((System.currentTimeMillis() - mBufferTestStartTime + 360 Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND); 361 switch (msg.what) { 362 case NativeAudioThread. 363 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 364 case NativeAudioThread. 365 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 366 showToast("Native Test Completed with Fatal Errors"); 367 break; 368 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 369 case NativeAudioThread. 370 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 371 showToast("Native Test Stopped"); 372 break; 373 default: 374 showToast("Native Test Completed"); 375 break; 376 } 377 378 379 stopAudioTestThreads(); 380 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 381 saveAllTo(mIntentFileName); 382 } 383 mIntentRunning = false; 384 385 if (getApp().isCaptureEnabled()) { 386 mCaptureHolder.stopLoopbackListenerScript(); 387 } 388 } // mNativeAudioThread != null 389 refreshSoundLevelBar(); 390 break; 391 default: 392 log("Got message:" + msg.what); 393 break; 394 } 395 396 // Control UI elements visibility specific to latency or buffer/glitch test 397 switch (msg.what) { 398 // Latency test started 399 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 400 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 401 setTransportButtonsState(LATENCY_TEST_STARTED); 402 break; 403 404 // Latency test ended 405 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 406 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 407 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 408 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 409 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 410 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 411 case NativeAudioThread. 412 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 413 setTransportButtonsState(LATENCY_TEST_ENDED); 414 break; 415 416 // Buffer test started 417 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 418 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 419 setTransportButtonsState(BUFFER_TEST_STARTED); 420 break; 421 422 // Buffer test ended 423 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 424 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 425 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 426 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 427 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 428 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 429 case NativeAudioThread. 430 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 431 setTransportButtonsState(BUFFER_TEST_ENDED); 432 break; 433 434 // Sound Calibration started 435 case CALIBRATION_STARTED: 436 setTransportButtonsState(CALIBRATION_STARTED); 437 break; 438 439 // Sound Calibration ended 440 case CALIBRATION_ENDED: 441 setTransportButtonsState(CALIBRATION_ENDED); 442 break; 443 } 444 } 445 }; 446 447 448 @Override 449 public void onCreate(Bundle savedInstanceState) { 450 super.onCreate(savedInstanceState); 451 452 // Set the layout for this activity. You can find it 453 View view = getLayoutInflater().inflate(R.layout.main_activity, null); 454 setContentView(view); 455 456 // TODO: Write script to file at more appropriate time, from settings activity or intent 457 // TODO: Respond to failure with more than just a toast 458 if (hasWriteFilePermission()){ 459 boolean successfulWrite = AtraceScriptsWriter.writeScriptsToFile(this); 460 if(!successfulWrite) { 461 showToast("Unable to write loopback_listener script to device"); 462 } 463 } else { 464 requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT); 465 } 466 467 468 mTextInfo = (TextView) findViewById(R.id.textInfo); 469 mBarMasterLevel = (SeekBar) findViewById(R.id.BarMasterLevel); 470 471 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 472 int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 473 mBarMasterLevel.setMax(maxVolume); 474 475 mBarMasterLevel.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 476 @Override 477 public void onStopTrackingTouch(SeekBar seekBar) { 478 } 479 480 @Override 481 public void onStartTrackingTouch(SeekBar seekBar) { 482 } 483 484 @Override 485 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 486 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 487 am.setStreamVolume(AudioManager.STREAM_MUSIC, 488 progress, 0); 489 refreshSoundLevelBar(); 490 log("Changed stream volume to: " + progress); 491 } 492 }); 493 mWavePlotView = (WavePlotView) findViewById(R.id.viewWavePlot); 494 495 mTextViewCurrentLevel = (TextView) findViewById(R.id.textViewCurrentLevel); 496 mTextViewCurrentLevel.setTextSize(15); 497 498 mTextViewResultSummary = (TextView) findViewById(R.id.resultSummary); 499 refreshSoundLevelBar(); 500 501 if(savedInstanceState != null) { 502 restoreInstanceState(savedInstanceState); 503 } 504 505 if (!hasRecordAudioPermission()) { 506 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 507 } 508 509 applyIntent(getIntent()); 510 } 511 512 @Override 513 protected void onStart() { 514 super.onStart(); 515 Intent audioTestIntent = new Intent(this, AudioTestService.class); 516 startService(audioTestIntent); 517 boolean bound = bindService(audioTestIntent, mServiceConnection, Context.BIND_AUTO_CREATE); 518 if (bound) { 519 log("Successfully bound to service!"); 520 } 521 else { 522 log("Failed to bind service!"); 523 } 524 } 525 526 527 @Override 528 protected void onStop() { 529 super.onStop(); 530 log("Activity on stop!"); 531 // Unbind from the service 532 if (mBound) { 533 unbindService(mServiceConnection); 534 mBound = false; 535 } 536 } 537 538 @Override 539 public void onNewIntent(Intent intent) { 540 log("On New Intent called!"); 541 applyIntent(intent); 542 } 543 544 545 /** 546 * This method will be called whenever the test starts running (either by operating on the 547 * device or by adb command). In the case where the test is started through adb command, 548 * adb parameters will be read into intermediate variables. 549 */ 550 private void applyIntent(Intent intent) { 551 Bundle b = intent.getExtras(); 552 if (b != null && !mIntentRunning) { 553 // adb shell am start -n org.drrickorang.loopback/.LoopbackActivity 554 // --ei SF 48000 --es FileName test1 --ei RecorderBuffer 512 --ei PlayerBuffer 512 555 // --ei AudioThread 1 --ei MicSource 3 --ei AudioLevel 12 556 // --ei TestType 223 --ei BufferTestDuration 60 --ei NumLoadThreads 4 557 // --ei CI -1 --ez CaptureSysTrace true --ez CaptureWavs false --ei NumCaptures 5 558 // --ei WavDuration 15 559 560 // Note: for native mode, player and recorder buffer sizes are the same, and can only be 561 // set through player buffer size 562 563 boolean hasRecordAudioPermission = hasRecordAudioPermission(); 564 boolean hasWriteFilePermission = hasWriteFilePermission(); 565 if (!hasRecordAudioPermission || !hasWriteFilePermission) { 566 if (!hasRecordAudioPermission) { 567 log("Missing Permission: RECORD_AUDIO"); 568 } 569 570 if (!hasWriteFilePermission) { 571 log("Missing Permission: WRITE_EXTERNAL_STORAGE"); 572 } 573 574 return; 575 } 576 577 if (b.containsKey(INTENT_BUFFER_TEST_DURATION)) { 578 getApp().setBufferTestDuration(b.getInt(INTENT_BUFFER_TEST_DURATION)); 579 mIntentRunning = true; 580 } 581 582 if (b.containsKey(INTENT_SAMPLING_FREQUENCY)) { 583 getApp().setSamplingRate(b.getInt(INTENT_SAMPLING_FREQUENCY)); 584 mIntentRunning = true; 585 } 586 587 if (b.containsKey(INTENT_CHANNEL_INDEX)) { 588 getApp().setChannelIndex(b.getInt(INTENT_CHANNEL_INDEX)); 589 mChannelIndex = b.getInt(INTENT_CHANNEL_INDEX); 590 mIntentRunning = true; 591 } 592 593 if (b.containsKey(INTENT_FILENAME)) { 594 mIntentFileName = b.getString(INTENT_FILENAME); 595 mIntentRunning = true; 596 } 597 598 if (b.containsKey(INTENT_RECORDER_BUFFER)) { 599 getApp().setRecorderBufferSizeInBytes( 600 b.getInt(INTENT_RECORDER_BUFFER) * Constant.BYTES_PER_FRAME); 601 mIntentRunning = true; 602 } 603 604 if (b.containsKey(INTENT_PLAYER_BUFFER)) { 605 getApp().setPlayerBufferSizeInBytes( 606 b.getInt(INTENT_PLAYER_BUFFER) * Constant.BYTES_PER_FRAME); 607 mIntentRunning = true; 608 } 609 610 if (b.containsKey(INTENT_AUDIO_THREAD)) { 611 getApp().setAudioThreadType(b.getInt(INTENT_AUDIO_THREAD)); 612 mIntentRunning = true; 613 } 614 615 if (b.containsKey(INTENT_MIC_SOURCE)) { 616 getApp().setMicSource(b.getInt(INTENT_MIC_SOURCE)); 617 mIntentRunning = true; 618 } 619 620 if (b.containsKey(INTENT_PERFORMANCE_MODE)) { 621 getApp().setPerformanceMode(b.getInt(INTENT_PERFORMANCE_MODE)); 622 mIntentRunning = true; 623 } 624 625 if (b.containsKey(INTENT_IGNORE_FIRST_FRAMES)) { 626 getApp().setIgnoreFirstFrames(b.getInt(INTENT_IGNORE_FIRST_FRAMES)); 627 mIntentRunning = true; 628 } 629 630 if (b.containsKey(INTENT_AUDIO_LEVEL)) { 631 int audioLevel = b.getInt(INTENT_AUDIO_LEVEL); 632 if (audioLevel >= 0) { 633 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 634 am.setStreamVolume(AudioManager.STREAM_MUSIC, audioLevel, 0); 635 getApp().setSoundLevelCalibrationEnabled(false); 636 } else { // AudioLevel of -1 means automatically calibrate 637 getApp().setSoundLevelCalibrationEnabled(true); 638 } 639 mIntentRunning = true; 640 } 641 642 if (b.containsKey(INTENT_NUMBER_LOAD_THREADS)) { 643 getApp().setNumberOfLoadThreads(b.getInt(INTENT_NUMBER_LOAD_THREADS)); 644 mIntentRunning = true; 645 } 646 647 if (b.containsKey(INTENT_ENABLE_SYSTRACE)) { 648 getApp().setCaptureSysTraceEnabled(b.getBoolean(INTENT_ENABLE_SYSTRACE)); 649 mIntentRunning = true; 650 } 651 652 if (b.containsKey(INTENT_ENABLE_WAVCAPTURE)) { 653 getApp().setCaptureWavsEnabled(b.getBoolean(INTENT_ENABLE_WAVCAPTURE)); 654 mIntentRunning = true; 655 } 656 657 if (b.containsKey(INTENT_NUM_CAPTURES)) { 658 getApp().setNumberOfCaptures(b.getInt(INTENT_NUM_CAPTURES)); 659 mIntentRunning = true; 660 } 661 662 if (b.containsKey(INTENT_WAV_DURATION)) { 663 getApp().setBufferTestWavePlotDuration(b.getInt(INTENT_WAV_DURATION)); 664 mIntentRunning = true; 665 } 666 667 if (mIntentRunning || b.containsKey(INTENT_TEST_TYPE)) { 668 // run tests with provided or default parameters 669 refreshState(); 670 671 // if no test is specified then Latency Test will be run 672 int testType = b.getInt(INTENT_TEST_TYPE, 673 Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY); 674 switch (testType) { 675 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 676 startBufferTest(); 677 break; 678 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_CALIBRATION: 679 doCalibration(); 680 break; 681 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 682 default: 683 startLatencyTest(); 684 break; 685 } 686 } 687 688 } else { 689 if (mIntentRunning && b != null) { 690 log("Test already in progress"); 691 showToast("Test already in progress"); 692 } 693 } 694 } 695 696 697 /** Stop all currently running threads that are related to audio test. */ 698 private void stopAudioTestThreads() { 699 log("stopping audio threads"); 700 if (mAudioThread != null) { 701 try { 702 mAudioThread.finish(); 703 mAudioThread.join(Constant.JOIN_WAIT_TIME_MS); 704 } catch (InterruptedException e) { 705 e.printStackTrace(); 706 } 707 mAudioThread = null; 708 } 709 710 if (mNativeAudioThread != null) { 711 try { 712 mNativeAudioThread.finish(); 713 mNativeAudioThread.join(Constant.JOIN_WAIT_TIME_MS); 714 } catch (InterruptedException e) { 715 e.printStackTrace(); 716 } 717 mNativeAudioThread = null; 718 } 719 720 stopLoadThreads(); 721 System.gc(); 722 } 723 724 725 public void onDestroy() { 726 stopAudioTestThreads(); 727 super.onDestroy(); 728 stopService(new Intent(this, AudioTestService.class)); 729 } 730 731 732 @Override 733 protected void onResume() { 734 super.onResume(); 735 log("on resume called"); 736 } 737 738 739 @Override 740 protected void onPause() { 741 super.onPause(); 742 } 743 744 @Override 745 public boolean onCreateOptionsMenu(Menu menu){ 746 MenuInflater inflater = getMenuInflater(); 747 inflater.inflate(R.menu.tool_bar_menu, menu); 748 return true; 749 } 750 751 @Override 752 public boolean onOptionsItemSelected(MenuItem item) { 753 // Respond to user selecting action bar buttons 754 switch (item.getItemId()) { 755 case R.id.action_help: 756 if (!isBusy()) { 757 // Launch about Activity 758 Intent aboutIntent = new Intent(this, AboutActivity.class); 759 startActivity(aboutIntent); 760 } else { 761 showToast("Test in progress... please wait"); 762 } 763 764 return true; 765 766 case R.id.action_settings: 767 if (!isBusy()) { 768 // Launch settings activity 769 Intent mySettingsIntent = new Intent(this, SettingsActivity.class); 770 startActivityForResult(mySettingsIntent, SETTINGS_ACTIVITY_REQUEST); 771 } else { 772 showToast("Test in progress... please wait"); 773 } 774 return true; 775 } 776 777 return super.onOptionsItemSelected(item); 778 } 779 780 781 /** Check if the app is busy (running test). */ 782 public boolean isBusy() { 783 boolean busy = false; 784 785 if (mAudioThread != null && mAudioThread.mIsRunning) { 786 busy = true; 787 } 788 789 if (mNativeAudioThread != null && mNativeAudioThread.mIsRunning) { 790 busy = true; 791 } 792 793 return busy; 794 } 795 796 797 /** Create a new audio thread according to the settings. */ 798 private void restartAudioSystem() { 799 log("restart audio system..."); 800 801 int sessionId = 0; /* FIXME runtime test for am.generateAudioSessionId() in API 21 */ 802 803 mAudioThreadType = getApp().getAudioThreadType(); 804 mSamplingRate = getApp().getSamplingRate(); 805 mChannelIndex = getApp().getChannelIndex(); 806 mPlayerBufferSizeInBytes = getApp().getPlayerBufferSizeInBytes(); 807 mRecorderBufferSizeInBytes = getApp().getRecorderBufferSizeInBytes(); 808 mTestStartTimeString = (String) DateFormat.format("MMddkkmmss", 809 System.currentTimeMillis()); 810 mMicSource = getApp().getMicSource(); 811 mPerformanceMode = getApp().getPerformanceMode(); 812 mIgnoreFirstFrames = getApp().getIgnoreFirstFrames(); 813 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 814 mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC); 815 mBufferTestDurationInSeconds = getApp().getBufferTestDuration(); 816 mBufferTestWavePlotDurationInSeconds = getApp().getBufferTestWavePlotDuration(); 817 818 mCaptureHolder = new CaptureHolder(getApp().getNumStateCaptures(), 819 getFileNamePrefix(), getApp().isCaptureWavSnippetsEnabled(), 820 getApp().isCaptureSysTraceEnabled(), getApp().isCaptureBugreportEnabled(), 821 this, mSamplingRate); 822 823 log(" current sampling rate: " + mSamplingRate); 824 stopAudioTestThreads(); 825 826 // select java or native audio thread 827 int micSourceMapped; 828 switch (mAudioThreadType) { 829 case Constant.AUDIO_THREAD_TYPE_JAVA: 830 micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_JAVA, mMicSource); 831 832 int expectedRecorderBufferPeriod = Math.round( 833 (float) (mRecorderBufferSizeInBytes * Constant.MILLIS_PER_SECOND) 834 / (Constant.BYTES_PER_FRAME * mSamplingRate)); 835 mRecorderBufferPeriod.prepareMemberObjects( 836 Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds, 837 expectedRecorderBufferPeriod, mCaptureHolder); 838 839 int expectedPlayerBufferPeriod = Math.round( 840 (float) (mPlayerBufferSizeInBytes * Constant.MILLIS_PER_SECOND) 841 / (Constant.BYTES_PER_FRAME * mSamplingRate)); 842 mPlayerBufferPeriod.prepareMemberObjects( 843 Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds, 844 expectedPlayerBufferPeriod, mCaptureHolder); 845 846 mAudioThread = new LoopbackAudioThread(mSamplingRate, mPlayerBufferSizeInBytes, 847 mRecorderBufferSizeInBytes, micSourceMapped, /* no performance mode */ mRecorderBufferPeriod, 848 mPlayerBufferPeriod, mTestType, mBufferTestDurationInSeconds, 849 mBufferTestWavePlotDurationInSeconds, getApplicationContext(), 850 mChannelIndex, mCaptureHolder); 851 mAudioThread.setMessageHandler(mMessageHandler); 852 mAudioThread.mSessionId = sessionId; 853 mAudioThread.start(); 854 break; 855 case Constant.AUDIO_THREAD_TYPE_NATIVE: 856 micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE, mMicSource); 857 int performanceModeMapped = getApp().mapPerformanceMode(mPerformanceMode); 858 // Note: mRecorderBufferSizeInBytes will not actually be used, since recorder buffer 859 // size = player buffer size in native mode 860 mNativeAudioThread = new NativeAudioThread(mSamplingRate, mPlayerBufferSizeInBytes, 861 mRecorderBufferSizeInBytes, micSourceMapped, performanceModeMapped, mTestType, 862 mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds, 863 mIgnoreFirstFrames, mCaptureHolder); 864 mNativeAudioThread.setMessageHandler(mMessageHandler); 865 mNativeAudioThread.mSessionId = sessionId; 866 mNativeAudioThread.start(); 867 break; 868 } 869 870 startLoadThreads(); 871 872 mMessageHandler.post(new Runnable() { 873 @Override 874 public void run() { 875 refreshState(); 876 } 877 }); 878 } 879 880 881 /** Start all LoadThread. */ 882 private void startLoadThreads() { 883 884 if (getApp().getNumberOfLoadThreads() > 0) { 885 886 mLoadThreads = new LoadThread[getApp().getNumberOfLoadThreads()]; 887 888 for (int i = 0; i < mLoadThreads.length; i++) { 889 mLoadThreads[i] = new LoadThread("Loopback_LoadThread_" + i); 890 mLoadThreads[i].start(); 891 } 892 } 893 } 894 895 896 /** Stop all LoadThread. */ 897 private void stopLoadThreads() { 898 log("stopping load threads"); 899 if (mLoadThreads != null) { 900 for (int i = 0; i < mLoadThreads.length; i++) { 901 if (mLoadThreads[i] != null) { 902 try { 903 mLoadThreads[i].requestStop(); 904 mLoadThreads[i].join(Constant.JOIN_WAIT_TIME_MS); 905 } catch (InterruptedException e) { 906 e.printStackTrace(); 907 } 908 mLoadThreads[i] = null; 909 } 910 } 911 } 912 } 913 914 915 private void resetBufferPeriodRecord(BufferPeriod recorderBufferPeriod, 916 BufferPeriod playerBufferPeriod) { 917 recorderBufferPeriod.resetRecord(); 918 playerBufferPeriod.resetRecord(); 919 } 920 921 922 private void setTransportButtonsState(int state){ 923 Button latencyStart = (Button) findViewById(R.id.buttonStartLatencyTest); 924 Button bufferStart = (Button) findViewById(R.id.buttonStartBufferTest); 925 Button calibrationStart = (Button) findViewById(R.id.buttonCalibrateSoundLevel); 926 927 switch (state) { 928 case LATENCY_TEST_STARTED: 929 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 930 mTextViewResultSummary.setText(""); 931 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 932 latencyStart.setCompoundDrawablesWithIntrinsicBounds( 933 R.drawable.ic_stop, 0, 0, 0); 934 bufferStart.setEnabled(false); 935 calibrationStart.setEnabled(false); 936 break; 937 938 case LATENCY_TEST_ENDED: 939 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 940 latencyStart.setCompoundDrawablesWithIntrinsicBounds( 941 R.drawable.ic_play_arrow, 0, 0, 0); 942 bufferStart.setEnabled(true); 943 calibrationStart.setEnabled(true); 944 break; 945 946 case BUFFER_TEST_STARTED: 947 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 948 mTextViewResultSummary.setText(""); 949 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 950 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 951 R.drawable.ic_stop, 0, 0, 0); 952 latencyStart.setEnabled(false); 953 calibrationStart.setEnabled(false); 954 break; 955 956 case BUFFER_TEST_ENDED: 957 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 958 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 959 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 960 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 961 R.drawable.ic_play_arrow, 0, 0, 0); 962 latencyStart.setEnabled(true); 963 calibrationStart.setEnabled(true); 964 break; 965 966 case CALIBRATION_STARTED: 967 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 968 findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE); 969 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 970 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 971 R.drawable.ic_stop, 0, 0, 0); 972 latencyStart.setEnabled(false); 973 bufferStart.setEnabled(false); 974 calibrationStart.setEnabled(false); 975 break; 976 977 case CALIBRATION_ENDED: 978 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 979 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 980 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 981 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 982 R.drawable.ic_play_arrow, 0, 0, 0); 983 latencyStart.setEnabled(true); 984 bufferStart.setEnabled(true); 985 calibrationStart.setEnabled(true); 986 break; 987 } 988 } 989 990 private void doCalibrationIfEnabled(final Runnable onComplete) { 991 if (getApp().isSoundLevelCalibrationEnabled()) { 992 doCalibration(onComplete); 993 } else { 994 if (onComplete != null) { 995 onComplete.run(); 996 } 997 } 998 } 999 1000 private void doCalibration() { 1001 doCalibration(null); 1002 } 1003 1004 private void doCalibration(final Runnable onComplete) { 1005 if (isBusy()) { 1006 showToast("Test in progress... please wait"); 1007 return; 1008 } 1009 1010 if (!hasRecordAudioPermission()) { 1011 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 1012 // Returning, otherwise we don't know if user accepted or rejected. 1013 return; 1014 } 1015 1016 showToast("Calibrating sound level..."); 1017 final SoundLevelCalibration calibration = 1018 new SoundLevelCalibration(getApp().getSamplingRate(), 1019 getApp().getPlayerBufferSizeInBytes(), 1020 getApp().getRecorderBufferSizeInBytes(), 1021 getApp().getMicSource(), getApp().getPerformanceMode(), this); 1022 1023 calibration.setChangeListener(new SoundLevelCalibration.SoundLevelChangeListener() { 1024 @Override 1025 void onChange(int newLevel) { 1026 refreshSoundLevelBar(); 1027 } 1028 }); 1029 1030 mCalibrationThread = new Thread(new Runnable() { 1031 @Override 1032 public void run() { 1033 calibration.calibrate(); 1034 showToast("Calibration complete"); 1035 if (onComplete != null) { 1036 onComplete.run(); 1037 } 1038 } 1039 }); 1040 1041 mCalibrationThread.start(); 1042 } 1043 1044 /** Start the latency test. */ 1045 public void onButtonLatencyTest(View view) throws InterruptedException { 1046 if (isBusy()) { 1047 stopTests(); 1048 return; 1049 } 1050 1051 // Ensure we have RECORD_AUDIO permissions 1052 // On Android M (API 23) we must request dangerous permissions each time we use them 1053 if (hasRecordAudioPermission()) { 1054 startLatencyTest(); 1055 } else { 1056 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 1057 } 1058 } 1059 1060 private void startLatencyTest() { 1061 if (isBusy()) { 1062 showToast("Test in progress... please wait"); 1063 return; 1064 } 1065 1066 doCalibrationIfEnabled(latencyTestRunnable); 1067 } 1068 1069 private Runnable latencyTestRunnable = new Runnable() { 1070 @Override 1071 public void run() { 1072 if (isBusy()) { 1073 showToast("Test in progress... please wait"); 1074 return; 1075 } 1076 1077 mBarMasterLevel.post(new Runnable() { 1078 @Override 1079 public void run() { 1080 mBarMasterLevel.setEnabled(false); 1081 } 1082 }); 1083 resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod); 1084 1085 mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY; 1086 restartAudioSystem(); 1087 try { 1088 Thread.sleep(THREAD_SLEEP_DURATION_MS); 1089 } catch (InterruptedException e) { 1090 e.printStackTrace(); 1091 } 1092 1093 switch (mAudioThreadType) { 1094 case Constant.AUDIO_THREAD_TYPE_JAVA: 1095 if (mAudioThread != null) { 1096 mAudioThread.runTest(); 1097 } 1098 break; 1099 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1100 if (mNativeAudioThread != null) { 1101 mNativeAudioThread.runTest(); 1102 } 1103 break; 1104 } 1105 } 1106 }; 1107 1108 1109 /** Start the Buffer (Glitch Detection) Test. */ 1110 public void onButtonBufferTest(View view) throws InterruptedException { 1111 if (isBusy()) { 1112 stopTests(); 1113 return; 1114 } 1115 1116 if (hasRecordAudioPermission()) { 1117 startBufferTest(); 1118 } else { 1119 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER); 1120 } 1121 } 1122 1123 1124 private void startBufferTest() { 1125 1126 if (!isBusy()) { 1127 mBarMasterLevel.setEnabled(false); 1128 resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod); 1129 mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD; 1130 restartAudioSystem(); // in this function a audio thread is created 1131 try { 1132 Thread.sleep(THREAD_SLEEP_DURATION_MS); 1133 } catch (InterruptedException e) { 1134 e.printStackTrace(); 1135 } 1136 1137 switch (mAudioThreadType) { 1138 case Constant.AUDIO_THREAD_TYPE_JAVA: 1139 if (mAudioThread != null) { 1140 mAudioThread.runBufferTest(); 1141 } 1142 break; 1143 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1144 if (mNativeAudioThread != null) { 1145 mNativeAudioThread.runBufferTest(); 1146 } 1147 break; 1148 } 1149 } else { 1150 int duration = 0; 1151 switch (mAudioThreadType) { 1152 case Constant.AUDIO_THREAD_TYPE_JAVA: 1153 duration = mAudioThread.getDurationInSeconds(); 1154 break; 1155 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1156 duration = mNativeAudioThread.getDurationInSeconds(); 1157 break; 1158 } 1159 showToast("Long-run Test in progress, in total should take " + 1160 Integer.toString(duration) + "s, please wait"); 1161 } 1162 } 1163 1164 1165 /** Stop the ongoing test. */ 1166 public void stopTests() throws InterruptedException { 1167 if (mAudioThread != null) { 1168 mAudioThread.requestStopTest(); 1169 } 1170 1171 if (mNativeAudioThread != null) { 1172 mNativeAudioThread.requestStopTest(); 1173 } 1174 } 1175 1176 public void onButtonCalibrateSoundLevel(final View view) { 1177 Message m = Message.obtain(); 1178 m.what = CALIBRATION_STARTED; 1179 mMessageHandler.sendMessage(m); 1180 Runnable onComplete = new Runnable() { 1181 @Override 1182 public void run() { 1183 Message m = Message.obtain(); 1184 m.what = CALIBRATION_ENDED; 1185 mMessageHandler.sendMessage(m); 1186 } 1187 }; 1188 doCalibration(onComplete); 1189 } 1190 1191 /*** 1192 * Show dialog to choose to save files with filename dialog or not 1193 */ 1194 public void onButtonSave(View view) { 1195 if (!isBusy()) { 1196 DialogFragment newFragment = new SaveFilesDialogFragment(); 1197 newFragment.show(getFragmentManager(), "saveFiles"); 1198 } else { 1199 showToast("Test in progress... please wait"); 1200 } 1201 } 1202 1203 /** 1204 * Save five files: one .png file for a screenshot on the main activity, one .wav file for 1205 * the plot displayed on the main activity, one .txt file for storing various test results, one 1206 * .txt file for storing recorder buffer period data, and one .txt file for storing player 1207 * buffer period data. 1208 */ 1209 private void SaveFilesWithDialog() { 1210 1211 String fileName = "loopback_" + mTestStartTimeString; 1212 1213 //Launch filename choosing activities if available, otherwise save without prompting 1214 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 1215 launchFileNameChoosingActivity("text/plain", fileName, ".txt", SAVE_TO_TXT_REQUEST); 1216 launchFileNameChoosingActivity("image/png", fileName, ".png", SAVE_TO_PNG_REQUEST); 1217 launchFileNameChoosingActivity("audio/wav", fileName, ".wav", SAVE_TO_WAVE_REQUEST); 1218 launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriod.txt", 1219 SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST); 1220 launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriodTimes.txt", 1221 SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST); 1222 launchFileNameChoosingActivity("image/png", fileName, "_recorderBufferPeriod.png", 1223 SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST); 1224 launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriod.txt", 1225 SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST); 1226 launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriodTimes.txt", 1227 SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST); 1228 launchFileNameChoosingActivity("image/png", fileName, "_playerBufferPeriod.png", 1229 SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST); 1230 1231 if (mGlitchesData != null) { 1232 launchFileNameChoosingActivity("text/plain", fileName, "_glitchMillis.txt", 1233 SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST); 1234 launchFileNameChoosingActivity("image/png", fileName, "_heatMap.png", 1235 SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST); 1236 } 1237 } else { 1238 saveAllTo(fileName); 1239 } 1240 } 1241 1242 /** 1243 * Launches an activity for choosing the filename of the file to be saved 1244 */ 1245 public void launchFileNameChoosingActivity(String type, String fileName, String suffix, 1246 int RequestCode) { 1247 Intent FilenameIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 1248 FilenameIntent.addCategory(Intent.CATEGORY_OPENABLE); 1249 FilenameIntent.setType(type); 1250 FilenameIntent.putExtra(Intent.EXTRA_TITLE, fileName + suffix); 1251 startActivityForResult(FilenameIntent, RequestCode); 1252 } 1253 1254 private String getFileNamePrefix(){ 1255 if (mIntentFileName != null && !mIntentFileName.isEmpty()) { 1256 return mIntentFileName; 1257 } else { 1258 return "loopback_" + mTestStartTimeString; 1259 } 1260 } 1261 1262 /** See the documentation on onButtonSave() */ 1263 public void saveAllTo(String fileName) { 1264 1265 if (!hasWriteFilePermission()) { 1266 requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS); 1267 return; 1268 } 1269 1270 showToast("Saving files to: " + fileName + ".(wav,png,txt)"); 1271 1272 //save to a given uri... local file? 1273 saveToWaveFile(Uri.parse(FILE_SAVE_PATH + fileName + ".wav")); 1274 1275 saveScreenShot(Uri.parse(FILE_SAVE_PATH + fileName + ".png")); 1276 1277 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + ".txt"), getReport().toString()); 1278 1279 int[] bufferPeriodArray = null; 1280 int maxBufferPeriod = Constant.UNKNOWN; 1281 switch (mAudioThreadType) { 1282 case Constant.AUDIO_THREAD_TYPE_JAVA: 1283 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1284 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1285 break; 1286 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1287 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1288 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1289 break; 1290 } 1291 saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.txt"), 1292 bufferPeriodArray, maxBufferPeriod); 1293 saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.png"), 1294 bufferPeriodArray, maxBufferPeriod); 1295 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriodTimes.txt"), 1296 mRecorderCallbackTimes.toString()); 1297 1298 bufferPeriodArray = null; 1299 maxBufferPeriod = Constant.UNKNOWN; 1300 switch (mAudioThreadType) { 1301 case Constant.AUDIO_THREAD_TYPE_JAVA: 1302 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1303 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1304 break; 1305 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1306 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1307 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1308 break; 1309 } 1310 saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.txt") 1311 , bufferPeriodArray, maxBufferPeriod); 1312 saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.png"), 1313 bufferPeriodArray, maxBufferPeriod); 1314 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriodTimes.txt"), 1315 mPlayerCallbackTimes.toString()); 1316 1317 if (mGlitchesData != null) { 1318 saveGlitchOccurrences(Uri.parse(FILE_SAVE_PATH + fileName + "_glitchMillis.txt"), 1319 mGlitchesData); 1320 saveHeatMap(Uri.parse(FILE_SAVE_PATH + fileName + "_heatMap.png"), 1321 mRecorderCallbackTimes, mPlayerCallbackTimes, 1322 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1323 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1324 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, fileName); 1325 } 1326 1327 } 1328 1329 1330 @Override 1331 public void onActivityResult(int requestCode, int resultCode, Intent resultData) { 1332 log("ActivityResult request: " + requestCode + " result:" + resultCode); 1333 1334 if (resultCode == Activity.RESULT_OK) { 1335 switch (requestCode) { 1336 case SAVE_TO_WAVE_REQUEST: 1337 log("got SAVE TO WAV intent back!"); 1338 if (resultData != null) { 1339 saveToWaveFile(resultData.getData()); 1340 } 1341 break; 1342 case SAVE_TO_PNG_REQUEST: 1343 log("got SAVE TO PNG intent back!"); 1344 if (resultData != null) { 1345 saveScreenShot(resultData.getData()); 1346 } 1347 break; 1348 case SAVE_TO_TXT_REQUEST: 1349 if (resultData != null) { 1350 saveTextToFile(resultData.getData(), getReport().toString()); 1351 } 1352 break; 1353 case SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST: 1354 if (resultData != null) { 1355 int[] bufferPeriodArray = null; 1356 int maxBufferPeriod = Constant.UNKNOWN; 1357 switch (mAudioThreadType) { 1358 case Constant.AUDIO_THREAD_TYPE_JAVA: 1359 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1360 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1361 break; 1362 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1363 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1364 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1365 break; 1366 } 1367 saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1368 } 1369 break; 1370 case SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST: 1371 if (resultData != null) { 1372 int[] bufferPeriodArray = null; 1373 int maxBufferPeriod = Constant.UNKNOWN; 1374 switch (mAudioThreadType) { 1375 case Constant.AUDIO_THREAD_TYPE_JAVA: 1376 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1377 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1378 break; 1379 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1380 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1381 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1382 break; 1383 } 1384 saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1385 } 1386 break; 1387 case SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST: 1388 if (resultData != null) { 1389 int[] bufferPeriodArray = null; 1390 int maxBufferPeriod = Constant.UNKNOWN; 1391 switch (mAudioThreadType) { 1392 case Constant.AUDIO_THREAD_TYPE_JAVA: 1393 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1394 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1395 break; 1396 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1397 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1398 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1399 break; 1400 } 1401 saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1402 } 1403 break; 1404 case SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST: 1405 if (resultData != null) { 1406 int[] bufferPeriodArray = null; 1407 int maxBufferPeriod = Constant.UNKNOWN; 1408 switch (mAudioThreadType) { 1409 case Constant.AUDIO_THREAD_TYPE_JAVA: 1410 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1411 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1412 break; 1413 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1414 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1415 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1416 break; 1417 } 1418 saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1419 } 1420 break; 1421 case SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST: 1422 if (resultData != null) { 1423 saveTextToFile(resultData.getData(), 1424 mPlayerCallbackTimes.toString()); 1425 } 1426 break; 1427 case SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST: 1428 if (resultData != null) { 1429 saveTextToFile(resultData.getData(), 1430 mRecorderCallbackTimes.toString()); 1431 } 1432 break; 1433 case SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST: 1434 if (resultData != null) { 1435 saveGlitchOccurrences(resultData.getData(), mGlitchesData); 1436 } 1437 break; 1438 case SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST: 1439 if (resultData != null && mGlitchesData != null && mRecorderCallbackTimes != null 1440 & mPlayerCallbackTimes != null){ 1441 saveHeatMap(resultData.getData(), mRecorderCallbackTimes, mPlayerCallbackTimes, 1442 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1443 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1444 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, 1445 resultData.getData().toString()); 1446 } 1447 case SETTINGS_ACTIVITY_REQUEST: 1448 log("return from new settings!"); 1449 1450 break; 1451 } 1452 } 1453 } 1454 1455 1456 /** 1457 * Refresh the sound level bar on the main activity to reflect the current sound level 1458 * of the system. 1459 */ 1460 private void refreshSoundLevelBar() { 1461 mBarMasterLevel.setEnabled(true); 1462 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 1463 int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); 1464 mBarMasterLevel.setProgress(currentVolume); 1465 1466 mTextViewCurrentLevel.setText(String.format("Current Sound Level: %d/%d", currentVolume, 1467 mBarMasterLevel.getMax())); 1468 } 1469 1470 1471 /** Reset all results gathered from previous round of test (if any). */ 1472 private void resetResults() { 1473 mCorrelation.invalidate(); 1474 mNativeRecorderBufferPeriodArray = null; 1475 mNativePlayerBufferPeriodArray = null; 1476 mPlayerCallbackTimes = null; 1477 mRecorderCallbackTimes = null; 1478 mGlitchesData = null; 1479 mWaveData = null; 1480 } 1481 1482 1483 /** Get the file path from uri. Doesn't work for all devices. */ 1484 private String getPath(Uri uri) { 1485 String[] projection = {MediaStore.Images.Media.DATA}; 1486 Cursor cursor1 = getContentResolver().query(uri, projection, null, null, null); 1487 if (cursor1 == null) { 1488 return uri.getPath(); 1489 } 1490 1491 int ColumnIndex = cursor1.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 1492 cursor1.moveToFirst(); 1493 String path = cursor1.getString(ColumnIndex); 1494 cursor1.close(); 1495 return path; 1496 } 1497 1498 1499 /** Zoom out the plot to its full size. */ 1500 public void onButtonZoomOutFull(View view) { 1501 double fullZoomOut = mWavePlotView.getMaxZoomOut(); 1502 mWavePlotView.setZoom(fullZoomOut); 1503 mWavePlotView.refreshGraph(); 1504 } 1505 1506 1507 /** Zoom out the plot. */ 1508 public void onButtonZoomOut(View view) { 1509 double zoom = mWavePlotView.getZoom(); 1510 zoom = 2.0 * zoom; 1511 mWavePlotView.setZoom(zoom); 1512 mWavePlotView.refreshGraph(); 1513 } 1514 1515 1516 /** Zoom in the plot. */ 1517 public void onButtonZoomIn(View view) { 1518 double zoom = mWavePlotView.getZoom(); 1519 zoom = zoom / 2.0; 1520 mWavePlotView.setZoom(zoom); 1521 mWavePlotView.refreshGraph(); 1522 } 1523 1524 1525 /** Go to RecorderBufferPeriodActivity */ 1526 public void onButtonRecorderBufferPeriod(View view) { 1527 if (!isBusy()) { 1528 Intent RecorderBufferPeriodIntent = new Intent(this, 1529 RecorderBufferPeriodActivity.class); 1530 int recorderBufferSizeInFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1531 log("recorderBufferSizeInFrames:" + recorderBufferSizeInFrames); 1532 1533 switch (mAudioThreadType) { 1534 case Constant.AUDIO_THREAD_TYPE_JAVA: 1535 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray", 1536 mRecorderBufferPeriod.getBufferPeriodArray()); 1537 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax", 1538 mRecorderBufferPeriod.getMaxBufferPeriod()); 1539 break; 1540 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1541 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray", 1542 mNativeRecorderBufferPeriodArray); 1543 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax", 1544 mNativeRecorderMaxBufferPeriod); 1545 break; 1546 } 1547 1548 RecorderBufferPeriodIntent.putExtra("recorderBufferSize", recorderBufferSizeInFrames); 1549 RecorderBufferPeriodIntent.putExtra("samplingRate", mSamplingRate); 1550 startActivity(RecorderBufferPeriodIntent); 1551 } else 1552 showToast("Test in progress... please wait"); 1553 } 1554 1555 1556 /** Go to PlayerBufferPeriodActivity */ 1557 public void onButtonPlayerBufferPeriod(View view) { 1558 if (!isBusy()) { 1559 Intent PlayerBufferPeriodIntent = new Intent(this, PlayerBufferPeriodActivity.class); 1560 int playerBufferSizeInFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1561 1562 switch (mAudioThreadType) { 1563 case Constant.AUDIO_THREAD_TYPE_JAVA: 1564 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray", 1565 mPlayerBufferPeriod.getBufferPeriodArray()); 1566 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax", 1567 mPlayerBufferPeriod.getMaxBufferPeriod()); 1568 break; 1569 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1570 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray", 1571 mNativePlayerBufferPeriodArray); 1572 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax", 1573 mNativePlayerMaxBufferPeriod); 1574 break; 1575 } 1576 1577 PlayerBufferPeriodIntent.putExtra("playerBufferSize", playerBufferSizeInFrames); 1578 PlayerBufferPeriodIntent.putExtra("samplingRate", mSamplingRate); 1579 startActivity(PlayerBufferPeriodIntent); 1580 } else 1581 showToast("Test in progress... please wait"); 1582 } 1583 1584 1585 /** Display pop up window of recorded glitches */ 1586 public void onButtonGlitches(View view) { 1587 if (!isBusy()) { 1588 if (mGlitchesData != null) { 1589 // Create a PopUpWindow with scrollable TextView 1590 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null); 1591 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1592 ViewGroup.LayoutParams.MATCH_PARENT, true); 1593 1594 // Generate report of glitch intervals and set pop up window text 1595 TextView GlitchText = 1596 (TextView) popUp.getContentView().findViewById(R.id.ReportInfo); 1597 GlitchText.setText(GlitchesStringBuilder.getGlitchString(mFFTSamplingSize, 1598 mFFTOverlapSamples, mGlitchesData, mSamplingRate, 1599 mGlitchingIntervalTooLong, estimateNumberOfGlitches(mGlitchesData))); 1600 1601 // display pop up window, dismissible with back button 1602 popUp.showAtLocation(findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1603 } else { 1604 showToast("Please run the buffer test to get data"); 1605 } 1606 1607 } else { 1608 showToast("Test in progress... please wait"); 1609 } 1610 } 1611 1612 /** Display pop up window of recorded metrics and system information */ 1613 public void onButtonReport(View view) { 1614 if (!isBusy()) { 1615 if ((mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD 1616 && mGlitchesData != null) 1617 || (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY 1618 && mCorrelation.isValid())) { 1619 // Create a PopUpWindow with scrollable TextView 1620 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null); 1621 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1622 ViewGroup.LayoutParams.MATCH_PARENT, true); 1623 1624 // Generate report of glitch intervals and set pop up window text 1625 TextView reportText = 1626 (TextView) popUp.getContentView().findViewById(R.id.ReportInfo); 1627 reportText.setText(getReport().toString()); 1628 1629 // display pop up window, dismissible with back button 1630 popUp.showAtLocation(findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1631 } else { 1632 showToast("Please run the tests to get data"); 1633 } 1634 1635 } else { 1636 showToast("Test in progress... please wait"); 1637 } 1638 } 1639 1640 /** Display pop up window of recorded metrics and system information */ 1641 public void onButtonHeatMap(View view) { 1642 if (!isBusy()) { 1643 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD 1644 && mGlitchesData != null && mRecorderCallbackTimes != null 1645 && mRecorderCallbackTimes != null) { 1646 1647 // Create a PopUpWindow with heatMap custom view 1648 View puLayout = this.getLayoutInflater().inflate(R.layout.heatmap_window, null); 1649 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1650 ViewGroup.LayoutParams.MATCH_PARENT, true); 1651 1652 ((LinearLayout) popUp.getContentView()).addView( 1653 new GlitchAndCallbackHeatMapView(this, mRecorderCallbackTimes, 1654 mPlayerCallbackTimes, 1655 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1656 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1657 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, 1658 getResources().getString(R.string.heatTitle))); 1659 1660 popUp.showAtLocation(findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1661 1662 } else { 1663 showToast("Please run the tests to get data"); 1664 } 1665 1666 } else { 1667 showToast("Test in progress... please wait"); 1668 } 1669 } 1670 1671 /** Redraw the plot according to mWaveData */ 1672 void refreshPlots() { 1673 mWavePlotView.setData(mWaveData, mSamplingRate); 1674 mWavePlotView.redraw(); 1675 } 1676 1677 /** Refresh the text on the main activity that shows the app states and audio settings. */ 1678 void refreshState() { 1679 log("refreshState!"); 1680 refreshSoundLevelBar(); 1681 1682 // get info 1683 int playerFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1684 int recorderFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1685 StringBuilder s = new StringBuilder(200); 1686 1687 s.append("Settings from most recent run (at "); 1688 s.append(mTestStartTimeString); 1689 s.append("):\n"); 1690 1691 s.append("SR: ").append(mSamplingRate).append(" Hz"); 1692 s.append(" ChannelIndex: ").append(mChannelIndex < 0 ? "MONO" : mChannelIndex); 1693 switch (mAudioThreadType) { 1694 case Constant.AUDIO_THREAD_TYPE_JAVA: 1695 s.append(" Play Frames: " ).append(playerFrames); 1696 s.append(" Record Frames: ").append(recorderFrames); 1697 s.append(" Audio: JAVA"); 1698 break; 1699 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1700 s.append(" Frames: ").append(playerFrames); 1701 s.append(" Audio: NATIVE"); 1702 break; 1703 } 1704 1705 // mic source 1706 String micSourceName = getApp().getMicSourceString(mMicSource); 1707 if (micSourceName != null) { 1708 s.append(" Mic: ").append(micSourceName); 1709 } 1710 1711 // performance mode 1712 String performanceModeName = getApp().getPerformanceModeString(mPerformanceMode); 1713 if (performanceModeName != null) { 1714 s.append(" Performance Mode: ").append(performanceModeName); 1715 } 1716 1717 // sound level at start of test 1718 s.append(" Sound Level: ").append(mSoundLevel).append("/").append(mBarMasterLevel.getMax()); 1719 1720 // load threads 1721 s.append(" Simulated Load Threads: ").append(getApp().getNumberOfLoadThreads()); 1722 1723 // Show short summary of results, round trip latency or number of glitches 1724 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) { 1725 if (mIgnoreFirstFrames > 0) { 1726 s.append(" First Frames Ignored: ").append(mIgnoreFirstFrames); 1727 } 1728 if (mCorrelation.isValid()) { 1729 mTextViewResultSummary.setText(String.format("Latency: %.2f ms Confidence: %.2f" + 1730 " Average = %.4f RMS = %.4f", 1731 mCorrelation.mEstimatedLatencyMs, mCorrelation.mEstimatedLatencyConfidence, 1732 mCorrelation.mAverage, mCorrelation.mRms)); 1733 } 1734 } else if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD && 1735 mGlitchesData != null) { 1736 // show buffer test duration 1737 s.append("\nBuffer Test Duration: ").append(mBufferTestDurationInSeconds).append(" s"); 1738 1739 // show buffer test wave plot duration 1740 s.append(" Buffer Test Wave Plot Duration: last "); 1741 s.append(mBufferTestWavePlotDurationInSeconds); 1742 s.append(" s"); 1743 1744 mTextViewResultSummary.setText(getResources().getString(R.string.numGlitches) + " " + 1745 estimateNumberOfGlitches(mGlitchesData)); 1746 } else { 1747 mTextViewResultSummary.setText(""); 1748 } 1749 1750 String info = getApp().getSystemInfo(); 1751 s.append(" ").append(info); 1752 1753 mTextInfo.setText(s.toString()); 1754 } 1755 1756 1757 private static void log(String msg) { 1758 Log.v(TAG, msg); 1759 } 1760 1761 1762 public void showToast(final String msg) { 1763 // Make sure UI manipulations are only done on the UI thread 1764 LoopbackActivity.this.runOnUiThread(new Runnable() { 1765 public void run() { 1766 Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG); 1767 toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 10, 10); 1768 toast.show(); 1769 } 1770 }); 1771 } 1772 1773 1774 /** Get the application that runs this activity. Wrapper for getApplication(). */ 1775 private LoopbackApplication getApp() { 1776 return (LoopbackApplication) this.getApplication(); 1777 } 1778 1779 1780 /** Save a .wav file of the wave plot on the main activity. */ 1781 void saveToWaveFile(Uri uri) { 1782 if (mWaveData != null && mWaveData.length > 0) { 1783 AudioFileOutput audioFileOutput = new AudioFileOutput(getApplicationContext(), uri, 1784 mSamplingRate); 1785 boolean status = audioFileOutput.writeData(mWaveData); 1786 if (status) { 1787 String wavFileAbsolutePath = getPath(uri); 1788 // for some devices getPath fails 1789 if (wavFileAbsolutePath != null) { 1790 File file = new File(wavFileAbsolutePath); 1791 wavFileAbsolutePath = file.getAbsolutePath(); 1792 } else { 1793 wavFileAbsolutePath = ""; 1794 } 1795 showToast("Finished exporting wave File " + wavFileAbsolutePath); 1796 } else { 1797 showToast("Something failed saving wave file"); 1798 } 1799 1800 } 1801 } 1802 1803 1804 /** Save a screenshot of the main activity. */ 1805 void saveScreenShot(Uri uri) { 1806 ParcelFileDescriptor parcelFileDescriptor = null; 1807 FileOutputStream outputStream; 1808 try { 1809 parcelFileDescriptor = getApplicationContext().getContentResolver(). 1810 openFileDescriptor(uri, "w"); 1811 1812 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 1813 outputStream = new FileOutputStream(fileDescriptor); 1814 1815 log("Done creating output stream"); 1816 1817 LinearLayout LL = (LinearLayout) findViewById(R.id.linearLayoutMain); 1818 1819 View v = LL.getRootView(); 1820 v.setDrawingCacheEnabled(true); 1821 Bitmap b = v.getDrawingCache(); 1822 1823 //save 1824 b.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 1825 parcelFileDescriptor.close(); 1826 v.setDrawingCacheEnabled(false); 1827 } catch (Exception e) { 1828 log("Failed to open png file " + e); 1829 } finally { 1830 try { 1831 if (parcelFileDescriptor != null) { 1832 parcelFileDescriptor.close(); 1833 } 1834 } catch (Exception e) { 1835 e.printStackTrace(); 1836 log("Error closing ParcelFile Descriptor"); 1837 } 1838 } 1839 } 1840 1841 private void saveHistogram(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) { 1842 // Create and histogram view bitmap 1843 HistogramView recordHisto = new HistogramView(this,null); 1844 recordHisto.setBufferPeriodArray(bufferPeriodArray); 1845 recordHisto.setMaxBufferPeriod(maxBufferPeriod); 1846 1847 // Draw histogram on bitmap canvas 1848 Bitmap histoBmp = Bitmap.createBitmap(HISTOGRAM_EXPORT_WIDTH, 1849 HISTOGRAM_EXPORT_HEIGHT, Bitmap.Config.ARGB_8888); // creates a MUTABLE bitmap 1850 recordHisto.fillCanvas(new Canvas(histoBmp), histoBmp.getWidth(), histoBmp.getHeight()); 1851 1852 saveImage(uri, histoBmp); 1853 } 1854 1855 private void saveHeatMap(Uri uri, BufferCallbackTimes recorderCallbackTimes, 1856 BufferCallbackTimes playerCallbackTimes, int[] glitchMilliseconds, 1857 boolean glitchesExceeded, int duration, String title) { 1858 Bitmap heatBmp = Bitmap.createBitmap(HEATMAP_DRAW_WIDTH, HEATMAP_DRAW_HEIGHT, 1859 Bitmap.Config.ARGB_8888); 1860 GlitchAndCallbackHeatMapView.fillCanvas(new Canvas(heatBmp), recorderCallbackTimes, 1861 playerCallbackTimes, glitchMilliseconds, glitchesExceeded, duration, 1862 title); 1863 saveImage(uri, Bitmap.createScaledBitmap(heatBmp, 1864 HEATMAP_DRAW_WIDTH / HEATMAP_EXPORT_DIVISOR, 1865 HEATMAP_DRAW_HEIGHT / HEATMAP_EXPORT_DIVISOR, false)); 1866 } 1867 1868 /** Save an image to file. */ 1869 private void saveImage(Uri uri, Bitmap bmp) { 1870 ParcelFileDescriptor parcelFileDescriptor = null; 1871 FileOutputStream outputStream; 1872 try { 1873 parcelFileDescriptor = getApplicationContext().getContentResolver(). 1874 openFileDescriptor(uri, "w"); 1875 1876 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 1877 outputStream = new FileOutputStream(fileDescriptor); 1878 1879 log("Done creating output stream"); 1880 1881 // Save compressed bitmap to file 1882 bmp.compress(Bitmap.CompressFormat.PNG, EXPORTED_IMAGE_QUALITY, outputStream); 1883 parcelFileDescriptor.close(); 1884 } catch (Exception e) { 1885 log("Failed to open png file " + e); 1886 } finally { 1887 try { 1888 if (parcelFileDescriptor != null) { 1889 parcelFileDescriptor.close(); 1890 } 1891 } catch (Exception e) { 1892 e.printStackTrace(); 1893 log("Error closing ParcelFile Descriptor"); 1894 } 1895 } 1896 } 1897 1898 1899 /** 1900 * Save a .txt file of the given buffer period's data. 1901 * First column is time, second column is count. 1902 */ 1903 void saveBufferPeriod(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) { 1904 ParcelFileDescriptor parcelFileDescriptor = null; 1905 FileOutputStream outputStream; 1906 if (bufferPeriodArray != null) { 1907 try { 1908 parcelFileDescriptor = getApplicationContext().getContentResolver(). 1909 openFileDescriptor(uri, "w"); 1910 1911 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 1912 outputStream = new FileOutputStream(fileDescriptor); 1913 log("Done creating output stream for saving buffer period"); 1914 1915 int usefulDataRange = Math.min(maxBufferPeriod + 1, bufferPeriodArray.length); 1916 int[] usefulBufferData = Arrays.copyOfRange(bufferPeriodArray, 0, usefulDataRange); 1917 1918 String endline = "\n"; 1919 String delimiter = ","; 1920 StringBuilder sb = new StringBuilder(); 1921 for (int i = 0; i < usefulBufferData.length; i++) { 1922 sb.append(i + delimiter + usefulBufferData[i] + endline); 1923 } 1924 1925 outputStream.write(sb.toString().getBytes()); 1926 1927 } catch (Exception e) { 1928 log("Failed to open text file " + e); 1929 } finally { 1930 try { 1931 if (parcelFileDescriptor != null) { 1932 parcelFileDescriptor.close(); 1933 } 1934 } catch (Exception e) { 1935 e.printStackTrace(); 1936 log("Error closing ParcelFile Descriptor"); 1937 } 1938 } 1939 } 1940 1941 } 1942 1943 /** Save a .txt file of various test results. */ 1944 void saveTextToFile(Uri uri, String outputText) { 1945 ParcelFileDescriptor parcelFileDescriptor = null; 1946 FileOutputStream outputStream; 1947 try { 1948 parcelFileDescriptor = getApplicationContext().getContentResolver(). 1949 openFileDescriptor(uri, "w"); 1950 1951 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 1952 outputStream = new FileOutputStream(fileDescriptor); 1953 log("Done creating output stream"); 1954 1955 outputStream.write(outputText.getBytes()); 1956 parcelFileDescriptor.close(); 1957 } catch (Exception e) { 1958 log("Failed to open text file " + e); 1959 } finally { 1960 try { 1961 if (parcelFileDescriptor != null) { 1962 parcelFileDescriptor.close(); 1963 } 1964 } catch (Exception e) { 1965 e.printStackTrace(); 1966 log("Error closing ParcelFile Descriptor"); 1967 } 1968 } 1969 } 1970 1971 private StringBuilder getReport() { 1972 String endline = "\n"; 1973 final int stringLength = 300; 1974 StringBuilder sb = new StringBuilder(stringLength); 1975 sb.append("DateTime = " + mTestStartTimeString + endline); 1976 sb.append(INTENT_SAMPLING_FREQUENCY + " = " + mSamplingRate + endline); 1977 sb.append(INTENT_CHANNEL_INDEX + " = " + mChannelIndex + endline); 1978 sb.append(INTENT_RECORDER_BUFFER + " = " + mRecorderBufferSizeInBytes / 1979 Constant.BYTES_PER_FRAME + endline); 1980 sb.append(INTENT_PLAYER_BUFFER + " = " + mPlayerBufferSizeInBytes / 1981 Constant.BYTES_PER_FRAME + endline); 1982 sb.append(INTENT_AUDIO_THREAD + " = " + mAudioThreadType + endline); 1983 1984 String audioType = "unknown"; 1985 switch (mAudioThreadType) { 1986 case Constant.AUDIO_THREAD_TYPE_JAVA: 1987 audioType = "JAVA"; 1988 break; 1989 case Constant.AUDIO_THREAD_TYPE_NATIVE: 1990 audioType = "NATIVE"; 1991 break; 1992 } 1993 sb.append(INTENT_AUDIO_THREAD + "_String = " + audioType + endline); 1994 1995 sb.append(INTENT_MIC_SOURCE + " = " + mMicSource + endline); 1996 sb.append(INTENT_MIC_SOURCE + "_String = " + getApp().getMicSourceString(mMicSource) 1997 + endline); 1998 sb.append(INTENT_AUDIO_LEVEL + " = " + mSoundLevel + endline); 1999 2000 switch (mTestType) { 2001 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 2002 sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames + endline); 2003 if (mCorrelation.isValid()) { 2004 sb.append(String.format("LatencyMs = %.2f", mCorrelation.mEstimatedLatencyMs) 2005 + endline); 2006 } else { 2007 sb.append(String.format("LatencyMs = unknown") + endline); 2008 } 2009 2010 sb.append(String.format("LatencyConfidence = %.2f", 2011 mCorrelation.mEstimatedLatencyConfidence) + endline); 2012 2013 sb.append(String.format("Average = %.4f", mCorrelation.mAverage) + endline); 2014 sb.append(String.format("RMS = %.4f", mCorrelation.mRms) + endline); 2015 break; 2016 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 2017 sb.append("Buffer Test Duration (s) = " + mBufferTestDurationInSeconds + endline); 2018 2019 // report recorder results 2020 int[] recorderBufferData = null; 2021 int recorderBufferDataMax = 0; 2022 double recorderBufferDataStdDev = 0.0; 2023 switch (mAudioThreadType) { 2024 case Constant.AUDIO_THREAD_TYPE_JAVA: 2025 recorderBufferData = mRecorderBufferPeriod.getBufferPeriodArray(); 2026 recorderBufferDataMax = mRecorderBufferPeriod.getMaxBufferPeriod(); 2027 recorderBufferDataStdDev = mRecorderBufferPeriod.getStdDevBufferPeriod(); 2028 break; 2029 case Constant.AUDIO_THREAD_TYPE_NATIVE: 2030 recorderBufferData = mNativeRecorderBufferPeriodArray; 2031 recorderBufferDataMax = mNativeRecorderMaxBufferPeriod; 2032 recorderBufferDataStdDev = mNativeRecorderStdDevBufferPeriod; 2033 break; 2034 } 2035 // report expected recorder buffer period 2036 if (recorderBufferData != null) { 2037 // this is the range of data that actually has values 2038 int usefulDataRange = Math.min(recorderBufferDataMax + 1, 2039 recorderBufferData.length); 2040 int[] usefulBufferData = Arrays.copyOfRange(recorderBufferData, 0, 2041 usefulDataRange); 2042 PerformanceMeasurement measurement = new PerformanceMeasurement( 2043 mRecorderCallbackTimes.getExpectedBufferPeriod(), usefulBufferData); 2044 float recorderPercentAtExpected = 2045 measurement.percentBufferPeriodsAtExpected(); 2046 double benchmark = measurement.computeWeightedBenchmark(); 2047 int outliers = measurement.countOutliers(); 2048 sb.append("Expected Recorder Buffer Period (ms) = " + 2049 mRecorderCallbackTimes.getExpectedBufferPeriod() + endline); 2050 sb.append("Recorder Buffer Periods At Expected = " + 2051 String.format("%.5f%%", recorderPercentAtExpected * 100) + endline); 2052 2053 sb.append("Recorder Buffer Period Std Dev = " 2054 + String.format(Locale.US, "%.5f ms", recorderBufferDataStdDev) 2055 + endline); 2056 2057 // output thousandths of a percent not at expected buffer period 2058 sb.append("kth% Late Recorder Buffer Callbacks = " 2059 + String.format("%.5f", (1 - recorderPercentAtExpected) * 100000) 2060 + endline); 2061 sb.append("Recorder Benchmark = " + benchmark + endline); 2062 sb.append("Recorder Number of Outliers = " + outliers + endline); 2063 } else { 2064 sb.append("Cannot Find Recorder Buffer Period Data!" + endline); 2065 } 2066 2067 // report player results 2068 int[] playerBufferData = null; 2069 int playerBufferDataMax = 0; 2070 double playerBufferDataStdDev = 0.0; 2071 switch (mAudioThreadType) { 2072 case Constant.AUDIO_THREAD_TYPE_JAVA: 2073 playerBufferData = mPlayerBufferPeriod.getBufferPeriodArray(); 2074 playerBufferDataMax = mPlayerBufferPeriod.getMaxBufferPeriod(); 2075 playerBufferDataStdDev = mPlayerBufferPeriod.getStdDevBufferPeriod(); 2076 break; 2077 case Constant.AUDIO_THREAD_TYPE_NATIVE: 2078 playerBufferData = mNativePlayerBufferPeriodArray; 2079 playerBufferDataMax = mNativePlayerMaxBufferPeriod; 2080 playerBufferDataStdDev = mNativePlayerStdDevBufferPeriod; 2081 break; 2082 } 2083 // report expected player buffer period 2084 sb.append("Expected Player Buffer Period (ms) = " + 2085 mPlayerCallbackTimes.getExpectedBufferPeriod() + endline); 2086 if (playerBufferData != null) { 2087 // this is the range of data that actually has values 2088 int usefulDataRange = Math.min(playerBufferDataMax + 1, 2089 playerBufferData.length); 2090 int[] usefulBufferData = Arrays.copyOfRange(playerBufferData, 0, 2091 usefulDataRange); 2092 PerformanceMeasurement measurement = new PerformanceMeasurement( 2093 mPlayerCallbackTimes.getExpectedBufferPeriod(), usefulBufferData); 2094 float playerPercentAtExpected = measurement.percentBufferPeriodsAtExpected(); 2095 double benchmark = measurement.computeWeightedBenchmark(); 2096 int outliers = measurement.countOutliers(); 2097 sb.append("Player Buffer Periods At Expected = " 2098 + String.format("%.5f%%", playerPercentAtExpected * 100) + endline); 2099 2100 sb.append("Player Buffer Period Std Dev = " 2101 + String.format(Locale.US, "%.5f ms", playerBufferDataStdDev) 2102 + endline); 2103 2104 // output thousandths of a percent not at expected buffer period 2105 sb.append("kth% Late Player Buffer Callbacks = " 2106 + String.format("%.5f", (1 - playerPercentAtExpected) * 100000) 2107 + endline); 2108 sb.append("Player Benchmark = " + benchmark + endline); 2109 sb.append("Player Number of Outliers = " + outliers + endline); 2110 2111 } else { 2112 sb.append("Cannot Find Player Buffer Period Data!" + endline); 2113 } 2114 // report glitches per hour 2115 int numberOfGlitches = estimateNumberOfGlitches(mGlitchesData); 2116 float testDurationInHours = mBufferTestElapsedSeconds 2117 / (float) Constant.SECONDS_PER_HOUR; 2118 2119 // Report Glitches Per Hour if sufficient data available, ie at least half an hour 2120 if (testDurationInHours >= .5) { 2121 int glitchesPerHour = (int) Math.ceil(numberOfGlitches/testDurationInHours); 2122 sb.append("Glitches Per Hour = " + glitchesPerHour + endline); 2123 } 2124 sb.append("Total Number of Glitches = " + numberOfGlitches + endline); 2125 2126 // report if the total glitching interval is too long 2127 sb.append("Total glitching interval too long = " + 2128 mGlitchingIntervalTooLong); 2129 2130 sb.append("\nLate Player Callbacks = "); 2131 sb.append(mPlayerCallbackTimes.getNumLateOrEarlyCallbacks()); 2132 sb.append("\nLate Player Callbacks Exceeded Capacity = "); 2133 sb.append(mPlayerCallbackTimes.isCapacityExceeded()); 2134 sb.append("\nLate Recorder Callbacks = "); 2135 sb.append(mRecorderCallbackTimes.getNumLateOrEarlyCallbacks()); 2136 sb.append("\nLate Recorder Callbacks Exceeded Capacity = "); 2137 sb.append(mRecorderCallbackTimes.isCapacityExceeded()); 2138 sb.append("\n"); 2139 } 2140 2141 2142 String info = getApp().getSystemInfo(); 2143 sb.append("SystemInfo = " + info + endline); 2144 2145 return sb; 2146 } 2147 2148 /** Save a .txt file of of glitch occurrences in ms from beginning of test. */ 2149 private void saveGlitchOccurrences(Uri uri, int[] glitchesData) { 2150 ParcelFileDescriptor parcelFileDescriptor = null; 2151 FileOutputStream outputStream; 2152 try { 2153 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2154 openFileDescriptor(uri, "w"); 2155 2156 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2157 outputStream = new FileOutputStream(fileDescriptor); 2158 2159 log("Done creating output stream"); 2160 2161 outputStream.write(GlitchesStringBuilder.getGlitchStringForFile(mFFTSamplingSize, 2162 mFFTOverlapSamples, glitchesData, mSamplingRate).getBytes()); 2163 } catch (Exception e) { 2164 log("Failed to open text file " + e); 2165 } finally { 2166 try { 2167 if (parcelFileDescriptor != null) { 2168 parcelFileDescriptor.close(); 2169 } 2170 } catch (Exception e) { 2171 e.printStackTrace(); 2172 log("Error closing ParcelFile Descriptor"); 2173 } 2174 } 2175 } 2176 2177 /** 2178 * Estimate the number of glitches. This version of estimation will count two consecutive 2179 * glitching intervals as one glitch. This is because two time intervals are partly overlapped. 2180 * Note: If the total glitching intervals exceed the length of glitchesData, this estimation 2181 * becomes incomplete. However, whether or not the total glitching interval is too long will 2182 * also be indicated, and in the case it's true, we know something went wrong. 2183 */ 2184 private static int estimateNumberOfGlitches(int[] glitchesData) { 2185 final int discard = 10; // don't count glitches occurring at the first few FFT interval 2186 boolean isPreviousGlitch = false; // is there a glitch in previous interval or not 2187 int previousFFTInterval = -1; 2188 int count = 0; 2189 // if there are three consecutive glitches, the first two will be counted as one, 2190 // the third will be counted as another one 2191 for (int i = 0; i < glitchesData.length; i++) { 2192 if (glitchesData[i] > discard) { 2193 if (glitchesData[i] == previousFFTInterval + 1 && isPreviousGlitch) { 2194 isPreviousGlitch = false; 2195 previousFFTInterval = glitchesData[i]; 2196 } else { 2197 isPreviousGlitch = true; 2198 previousFFTInterval = glitchesData[i]; 2199 count += 1; 2200 } 2201 } 2202 2203 } 2204 2205 return count; 2206 } 2207 2208 2209 /** 2210 * Estimate the number of glitches. This version of estimation will count the whole consecutive 2211 * intervals as one glitch. This version is not currently used. 2212 * Note: If the total glitching intervals exceed the length of glitchesData, this estimation 2213 * becomes incomplete. However, whether or not the total glitching interval is too long will 2214 * also be indicated, and in the case it's true, we know something went wrong. 2215 */ 2216 private static int estimateNumberOfGlitches2(int[] glitchesData) { 2217 final int discard = 10; // don't count glitches occurring at the first few FFT interval 2218 int previousFFTInterval = -1; 2219 int count = 0; 2220 for (int i = 0; i < glitchesData.length; i++) { 2221 if (glitchesData[i] > discard) { 2222 if (glitchesData[i] != previousFFTInterval + 1) { 2223 count += 1; 2224 } 2225 previousFFTInterval = glitchesData[i]; 2226 } 2227 } 2228 return count; 2229 } 2230 2231 /** 2232 * Check whether we have the RECORD_AUDIO permission 2233 * @return true if we do 2234 */ 2235 private boolean hasRecordAudioPermission(){ 2236 boolean hasPermission = (ContextCompat.checkSelfPermission(this, 2237 Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED); 2238 2239 log("Has RECORD_AUDIO permission? " + hasPermission); 2240 return hasPermission; 2241 } 2242 2243 /** 2244 * Requests the RECORD_AUDIO permission from the user 2245 */ 2246 private void requestRecordAudioPermission(int requestCode){ 2247 2248 String requiredPermission = Manifest.permission.RECORD_AUDIO; 2249 2250 // If the user previously denied this permission then show a message explaining why 2251 // this permission is needed 2252 if (ActivityCompat.shouldShowRequestPermissionRationale(this, 2253 requiredPermission)) { 2254 2255 showToast("This app needs to record audio through the microphone to test the device's "+ 2256 "performance"); 2257 } 2258 2259 // request the permission. 2260 ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode); 2261 } 2262 2263 @Override 2264 public void onRequestPermissionsResult(int requestCode, 2265 String permissions[], int[] grantResults) { 2266 2267 // Save all files or run requested test after being granted permissions 2268 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 2269 if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS ) { 2270 saveAllTo(getFileNamePrefix()); 2271 } else if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT ) { 2272 AtraceScriptsWriter.writeScriptsToFile(this); 2273 } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER) { 2274 startBufferTest(); 2275 } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY) { 2276 startLatencyTest(); 2277 } 2278 } 2279 } 2280 2281 /** 2282 * Check whether we have the WRITE_EXTERNAL_STORAGE permission 2283 * 2284 * @return true if we do 2285 */ 2286 private boolean hasWriteFilePermission() { 2287 boolean hasPermission = (ContextCompat.checkSelfPermission(this, 2288 Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); 2289 2290 log("Has WRITE_EXTERNAL_STORAGE? " + hasPermission); 2291 return hasPermission; 2292 } 2293 2294 /** 2295 * Requests the WRITE_EXTERNAL_STORAGE permission from the user 2296 */ 2297 private void requestWriteFilePermission(int requestCode) { 2298 2299 String requiredPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE; 2300 2301 // request the permission. 2302 ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode); 2303 } 2304 2305 /** 2306 * Receive results from save files DialogAlert and either save all files directly 2307 * or use filename dialog 2308 */ 2309 @Override 2310 public void onSaveDialogSelect(DialogFragment dialog, boolean saveWithoutDialog) { 2311 if (saveWithoutDialog) { 2312 saveAllTo("loopback_" + mTestStartTimeString); 2313 } else { 2314 SaveFilesWithDialog(); 2315 } 2316 } 2317 2318 private void restoreInstanceState(Bundle in) { 2319 mWaveData = in.getDoubleArray("mWaveData"); 2320 2321 mTestType = in.getInt("mTestType"); 2322 mMicSource = in.getInt("mMicSource"); 2323 mAudioThreadType = in.getInt("mAudioThreadType"); 2324 mSamplingRate = in.getInt("mSamplingRate"); 2325 mChannelIndex = in.getInt("mChannelIndex"); 2326 mSoundLevel = in.getInt("mSoundLevel"); 2327 mPlayerBufferSizeInBytes = in.getInt("mPlayerBufferSizeInBytes"); 2328 mRecorderBufferSizeInBytes = in.getInt("mRecorderBufferSizeInBytes"); 2329 2330 mTestStartTimeString = in.getString("mTestStartTimeString"); 2331 2332 mGlitchesData = in.getIntArray("mGlitchesData"); 2333 if(mGlitchesData != null) { 2334 mGlitchingIntervalTooLong = in.getBoolean("mGlitchingIntervalTooLong"); 2335 mFFTSamplingSize = in.getInt("mFFTSamplingSize"); 2336 mFFTOverlapSamples = in.getInt("mFFTOverlapSamples"); 2337 mBufferTestStartTime = in.getLong("mBufferTestStartTime"); 2338 mBufferTestElapsedSeconds = in.getInt("mBufferTestElapsedSeconds"); 2339 mBufferTestDurationInSeconds = in.getInt("mBufferTestDurationInSeconds"); 2340 mBufferTestWavePlotDurationInSeconds = 2341 in.getInt("mBufferTestWavePlotDurationInSeconds"); 2342 2343 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 2344 } 2345 2346 if(mWaveData != null) { 2347 mCorrelation = in.getParcelable("mCorrelation"); 2348 mPlayerBufferPeriod = in.getParcelable("mPlayerBufferPeriod"); 2349 mRecorderBufferPeriod = in.getParcelable("mRecorderBufferPeriod"); 2350 mPlayerCallbackTimes = in.getParcelable("mPlayerCallbackTimes"); 2351 mRecorderCallbackTimes = in.getParcelable("mRecorderCallbackTimes"); 2352 2353 mNativePlayerBufferPeriodArray = in.getIntArray("mNativePlayerBufferPeriodArray"); 2354 mNativePlayerMaxBufferPeriod = in.getInt("mNativePlayerMaxBufferPeriod"); 2355 mNativeRecorderBufferPeriodArray = in.getIntArray("mNativeRecorderBufferPeriodArray"); 2356 mNativeRecorderMaxBufferPeriod = in.getInt("mNativeRecorderMaxBufferPeriod"); 2357 2358 mWavePlotView.setData(mWaveData, mSamplingRate); 2359 refreshState(); 2360 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 2361 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 2362 } 2363 } 2364 2365 @Override 2366 protected void onSaveInstanceState(Bundle out) { 2367 super.onSaveInstanceState(out); 2368 // TODO: keep larger pieces of data in a fragment to speed up response to rotation 2369 out.putDoubleArray("mWaveData", mWaveData); 2370 2371 out.putInt("mTestType", mTestType); 2372 out.putInt("mMicSource", mMicSource); 2373 out.putInt("mAudioThreadType", mAudioThreadType); 2374 out.putInt("mSamplingRate", mSamplingRate); 2375 out.putInt("mChannelIndex", mChannelIndex); 2376 out.putInt("mSoundLevel", mSoundLevel); 2377 out.putInt("mPlayerBufferSizeInBytes", mPlayerBufferSizeInBytes); 2378 out.putInt("mRecorderBufferSizeInBytes", mRecorderBufferSizeInBytes); 2379 out.putString("mTestStartTimeString", mTestStartTimeString); 2380 2381 out.putParcelable("mCorrelation", mCorrelation); 2382 out.putParcelable("mPlayerBufferPeriod", mPlayerBufferPeriod); 2383 out.putParcelable("mRecorderBufferPeriod", mRecorderBufferPeriod); 2384 out.putParcelable("mPlayerCallbackTimes", mPlayerCallbackTimes); 2385 out.putParcelable("mRecorderCallbackTimes", mRecorderCallbackTimes); 2386 2387 out.putIntArray("mNativePlayerBufferPeriodArray", mNativePlayerBufferPeriodArray); 2388 out.putInt("mNativePlayerMaxBufferPeriod", mNativePlayerMaxBufferPeriod); 2389 out.putIntArray("mNativeRecorderBufferPeriodArray", mNativeRecorderBufferPeriodArray); 2390 out.putInt("mNativeRecorderMaxBufferPeriod", mNativeRecorderMaxBufferPeriod); 2391 2392 // buffer test values 2393 out.putIntArray("mGlitchesData", mGlitchesData); 2394 out.putBoolean("mGlitchingIntervalTooLong", mGlitchingIntervalTooLong); 2395 out.putInt("mFFTSamplingSize", mFFTSamplingSize); 2396 out.putInt("mFFTOverlapSamples", mFFTOverlapSamples); 2397 out.putLong("mBufferTestStartTime", mBufferTestStartTime); 2398 out.putInt("mBufferTestElapsedSeconds", mBufferTestElapsedSeconds); 2399 out.putInt("mBufferTestDurationInSeconds", mBufferTestDurationInSeconds); 2400 out.putInt("mBufferTestWavePlotDurationInSeconds", mBufferTestWavePlotDurationInSeconds); 2401 } 2402 } 2403