1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.drrickorang.loopback; 18 19 import java.nio.ByteBuffer; 20 import java.util.Arrays; 21 22 import android.content.Context; 23 import android.media.AudioManager; 24 import android.media.AudioTrack; 25 import android.os.Build; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.util.Log; 29 30 31 /** 32 * A thread/audio track based audio synth. 33 */ 34 35 public class NativeAudioThread extends Thread { 36 private static final String TAG = "NativeAudioThread"; 37 38 // for latency test 39 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED = 891; 40 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR = 892; 41 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE = 893; 42 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS = 894; 43 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP = 895; 44 45 // for buffer test 46 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED = 896; 47 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR = 897; 48 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE = 898; 49 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS = 899; 50 static final int LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP = 900; 51 52 public boolean mIsRunning = false; 53 public int mSessionId; 54 public double[] mSamples; // store samples that will be shown on WavePlotView 55 int mSamplesIndex; 56 57 private int mThreadType; 58 private int mTestType; 59 private int mSamplingRate; 60 private int mMinPlayerBufferSizeInBytes = 0; 61 private int mMinRecorderBuffSizeInBytes = 0; // currently not used 62 private int mMicSource; 63 private int mPerformanceMode = -1; 64 private int mIgnoreFirstFrames; 65 66 private boolean mIsRequestStop = false; 67 private Handler mMessageHandler; 68 private boolean isDestroying = false; 69 private boolean hasDestroyingErrors = false; 70 71 // for buffer test 72 private int[] mRecorderBufferPeriod; 73 private int mRecorderMaxBufferPeriod; 74 private double mRecorderStdDevBufferPeriod; 75 private int[] mPlayerBufferPeriod; 76 private int mPlayerMaxBufferPeriod; 77 private double mPlayerStdDevBufferPeriod; 78 private BufferCallbackTimes mPlayerCallbackTimes; 79 private BufferCallbackTimes mRecorderCallbackTimes; 80 private int mBufferTestWavePlotDurationInSeconds; 81 private double mFrequency1 = Constant.PRIME_FREQUENCY_1; 82 private double mFrequency2 = Constant.PRIME_FREQUENCY_2; // not actually used 83 private int mBufferTestDurationInSeconds; 84 private int mFFTSamplingSize; 85 private int mFFTOverlapSamples; 86 private int[] mAllGlitches; 87 private boolean mGlitchingIntervalTooLong; 88 private final CaptureHolder mCaptureHolder; 89 90 private PipeByteBuffer mPipeByteBuffer; 91 private GlitchDetectionThread mGlitchDetectionThread; 92 93 /** Check if it's safe to use getProperty(). */ isSafeToUseGetProperty()94 static boolean isSafeToUseGetProperty() { 95 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; 96 } 97 computeDefaultSettings(Context context, int threadType, int performanceMode)98 public static TestSettings computeDefaultSettings(Context context, 99 int threadType, int performanceMode) { 100 TestSettings nativeResult = nativeComputeDefaultSettings( 101 Constant.BYTES_PER_FRAME, threadType, performanceMode); 102 if (nativeResult != null) { 103 return nativeResult; 104 } 105 106 int samplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); 107 int minBufferSizeInFrames = 1024; 108 if (isSafeToUseGetProperty()) { 109 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 110 String value = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 111 minBufferSizeInFrames = Integer.parseInt(value); 112 } 113 int minBufferSizeInBytes = Constant.BYTES_PER_FRAME * minBufferSizeInFrames; 114 return new TestSettings(samplingRate, minBufferSizeInBytes, minBufferSizeInBytes); 115 } 116 NativeAudioThread(int threadType, int samplingRate, int playerBufferInBytes, int recorderBufferInBytes, int micSource, int performanceMode, int testType, int bufferTestDurationInSeconds, int bufferTestWavePlotDurationInSeconds, int ignoreFirstFrames, CaptureHolder captureHolder)117 public NativeAudioThread(int threadType, int samplingRate, int playerBufferInBytes, 118 int recorderBufferInBytes, int micSource, int performanceMode, 119 int testType, int bufferTestDurationInSeconds, 120 int bufferTestWavePlotDurationInSeconds, int ignoreFirstFrames, 121 CaptureHolder captureHolder) { 122 mThreadType = threadType; 123 mSamplingRate = samplingRate; 124 mMinPlayerBufferSizeInBytes = playerBufferInBytes; 125 mMinRecorderBuffSizeInBytes = recorderBufferInBytes; 126 mMicSource = micSource; 127 mPerformanceMode = performanceMode; 128 mTestType = testType; 129 mBufferTestDurationInSeconds = bufferTestDurationInSeconds; 130 mBufferTestWavePlotDurationInSeconds = bufferTestWavePlotDurationInSeconds; 131 mIgnoreFirstFrames = ignoreFirstFrames; 132 mCaptureHolder = captureHolder; 133 setName("Loopback_NativeAudio"); 134 } 135 NativeAudioThread(NativeAudioThread old)136 public NativeAudioThread(NativeAudioThread old) { 137 mThreadType = old.mThreadType; 138 mSamplingRate = old.mSamplingRate; 139 mMinPlayerBufferSizeInBytes = old.mMinPlayerBufferSizeInBytes; 140 mMinRecorderBuffSizeInBytes = old.mMinRecorderBuffSizeInBytes; 141 mMicSource = old.mMicSource; 142 mPerformanceMode = old.mPerformanceMode; 143 mTestType = old.mTestType; 144 mBufferTestDurationInSeconds = old.mBufferTestDurationInSeconds; 145 mBufferTestWavePlotDurationInSeconds = old.mBufferTestWavePlotDurationInSeconds; 146 mIgnoreFirstFrames = old.mIgnoreFirstFrames; 147 mCaptureHolder = old.mCaptureHolder; 148 setName("Loopback_NativeAudio"); 149 } 150 151 //JNI load 152 static { 153 try { 154 System.loadLibrary("loopback"); 155 } catch (UnsatisfiedLinkError e) { 156 log("Error loading loopback JNI library"); 157 e.printStackTrace(); 158 } 159 /* TODO: gracefully fail/notify if the library can't be loaded */ 160 } 161 162 163 //jni calls nativeComputeDefaultSettings( int bytesPerFrame, int threadType, int performanceMode)164 public static native TestSettings nativeComputeDefaultSettings( 165 int bytesPerFrame, int threadType, int performanceMode); nativeInit(int threadType, int samplingRate, int frameCount, int micSource, int performanceMode, int testType, double frequency1, ByteBuffer byteBuffer, short[] sincTone, int maxRecordedLateCallbacks, int ignoreFirstFrames)166 public native long nativeInit(int threadType, 167 int samplingRate, int frameCount, int micSource, 168 int performanceMode, 169 int testType, double frequency1, ByteBuffer byteBuffer, 170 short[] sincTone, int maxRecordedLateCallbacks, 171 int ignoreFirstFrames); nativeProcessNext(long nativeHandle, double[] samples, long offset)172 public native int nativeProcessNext(long nativeHandle, double[] samples, long offset); nativeDestroy(long nativeHandle)173 public native int nativeDestroy(long nativeHandle); 174 175 // to get buffer period data nativeGetRecorderBufferPeriod(long nativeHandle)176 public native int[] nativeGetRecorderBufferPeriod(long nativeHandle); nativeGetRecorderMaxBufferPeriod(long nativeHandle)177 public native int nativeGetRecorderMaxBufferPeriod(long nativeHandle); nativeGetRecorderVarianceBufferPeriod(long nativeHandle)178 public native double nativeGetRecorderVarianceBufferPeriod(long nativeHandle); nativeGetPlayerBufferPeriod(long nativeHandle)179 public native int[] nativeGetPlayerBufferPeriod(long nativeHandle); nativeGetPlayerMaxBufferPeriod(long nativeHandle)180 public native int nativeGetPlayerMaxBufferPeriod(long nativeHandle); nativeGetPlayerVarianceBufferPeriod(long nativeHandle)181 public native double nativeGetPlayerVarianceBufferPeriod(long nativeHandle); nativeGetPlayerCallbackTimeStamps(long nativeHandle)182 public native BufferCallbackTimes nativeGetPlayerCallbackTimeStamps(long nativeHandle); nativeGetRecorderCallbackTimeStamps(long nativeHandle)183 public native BufferCallbackTimes nativeGetRecorderCallbackTimeStamps(long nativeHandle); 184 nativeGetCaptureRank(long nativeHandle)185 public native int nativeGetCaptureRank(long nativeHandle); 186 187 run()188 public void run() { 189 setPriority(Thread.MAX_PRIORITY); 190 mIsRunning = true; 191 192 //erase output buffer 193 if (mSamples != null) 194 mSamples = null; 195 196 //start playing 197 log(" Started capture test"); 198 if (mMessageHandler != null) { 199 Message msg = Message.obtain(); 200 switch (mTestType) { 201 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 202 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED; 203 break; 204 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 205 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED; 206 break; 207 } 208 mMessageHandler.sendMessage(msg); 209 } 210 211 // generate windowed tone use for loopback test 212 short loopbackTone[] = new short[mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME]; 213 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) { 214 ToneGeneration sincToneGen = new RampedSineTone(mSamplingRate, 215 Constant.LOOPBACK_FREQUENCY); 216 int sincLength = Math.min(Constant.LOOPBACK_SAMPLE_FRAMES, loopbackTone.length); 217 sincToneGen.generateTone(loopbackTone, sincLength); 218 } 219 220 log(String.format("about to init, sampling rate: %d, buffer:%d", mSamplingRate, 221 mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME)); 222 223 // mPipeByteBuffer is only used in buffer test 224 mPipeByteBuffer = new PipeByteBuffer(Constant.MAX_SHORTS); 225 long startTimeMs = System.currentTimeMillis(); 226 long nativeHandle = nativeInit(mThreadType, mSamplingRate, 227 mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME, mMicSource, 228 mPerformanceMode, mTestType, 229 mFrequency1, mPipeByteBuffer.getByteBuffer(), loopbackTone, 230 mBufferTestDurationInSeconds * Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND, 231 mIgnoreFirstFrames); 232 log(String.format("nativeHandle = 0x%X", nativeHandle)); 233 234 if (nativeHandle == 0) { 235 //notify error!! 236 log(" ERROR at JNI initialization"); 237 if (mMessageHandler != null) { 238 Message msg = Message.obtain(); 239 switch (mTestType) { 240 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 241 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR; 242 break; 243 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 244 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR; 245 break; 246 } 247 mMessageHandler.sendMessage(msg); 248 } 249 } else { 250 // wait a little bit 251 try { 252 final int setUpTime = 10; 253 sleep(setUpTime); //just to let it start properly 254 } catch (InterruptedException e) { 255 e.printStackTrace(); 256 } 257 258 259 int totalSamplesRead = 0; 260 switch (mTestType) { 261 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 262 final int latencyTestDurationInSeconds = 2; 263 int nNewSize = (int) (1.1 * mSamplingRate * latencyTestDurationInSeconds); 264 mSamples = new double[nNewSize]; 265 mSamplesIndex = 0; //reset index 266 Arrays.fill(mSamples, 0); 267 268 //TODO use a ByteBuffer to retrieve recorded data instead 269 long offset = 0; 270 // retrieve native recorder's recorded data 271 for (int ii = 0; ii < latencyTestDurationInSeconds; ii++) { 272 log(String.format("block %d...", ii)); 273 int samplesRead = nativeProcessNext(nativeHandle, mSamples, offset); 274 totalSamplesRead += samplesRead; 275 offset += samplesRead; 276 log(" [" + ii + "] jni samples read:" + samplesRead + 277 " currentOffset:" + offset); 278 } 279 280 log(String.format(" samplesRead: %d, sampleOffset:%d", totalSamplesRead, offset)); 281 log("about to destroy..."); 282 break; 283 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 284 setUpGlitchDetectionThread(); 285 long testDurationMs = mBufferTestDurationInSeconds * Constant.MILLIS_PER_SECOND; 286 long elapsedTimeMs = System.currentTimeMillis() - startTimeMs; 287 while (elapsedTimeMs < testDurationMs) { 288 if (mIsRequestStop) { 289 break; 290 } else { 291 int rank = nativeGetCaptureRank(nativeHandle); 292 if (rank > 0) { 293 //log("Late callback detected"); 294 mCaptureHolder.captureState(rank); 295 } 296 try { 297 final int setUpTime = 100; 298 sleep(setUpTime); //just to let it start properly 299 } catch (InterruptedException e) { 300 e.printStackTrace(); 301 } 302 elapsedTimeMs = System.currentTimeMillis() - startTimeMs; 303 } 304 305 } 306 break; 307 308 309 } 310 311 // collect buffer period data 312 mRecorderBufferPeriod = nativeGetRecorderBufferPeriod(nativeHandle); 313 mRecorderMaxBufferPeriod = nativeGetRecorderMaxBufferPeriod(nativeHandle); 314 mRecorderStdDevBufferPeriod = Math.sqrt(nativeGetRecorderVarianceBufferPeriod( 315 nativeHandle)); 316 mPlayerBufferPeriod = nativeGetPlayerBufferPeriod(nativeHandle); 317 mPlayerMaxBufferPeriod = nativeGetPlayerMaxBufferPeriod(nativeHandle); 318 mPlayerStdDevBufferPeriod = Math.sqrt(nativeGetPlayerVarianceBufferPeriod( 319 nativeHandle)); 320 321 mPlayerCallbackTimes = nativeGetPlayerCallbackTimeStamps(nativeHandle); 322 mRecorderCallbackTimes = nativeGetRecorderCallbackTimeStamps(nativeHandle); 323 324 // get glitches data only for buffer test 325 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD) { 326 mAllGlitches = mGlitchDetectionThread.getGlitches(); 327 mSamples = mGlitchDetectionThread.getWaveData(); 328 mGlitchingIntervalTooLong = mGlitchDetectionThread.getGlitchingIntervalTooLong(); 329 endDetecting(); 330 } 331 332 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) { 333 mCaptureHolder.captureState(0); 334 } 335 336 runDestroy(nativeHandle); 337 338 final int maxTry = 20; 339 int tryCount = 0; 340 while (isDestroying) { 341 try { 342 sleep(40); 343 } catch (InterruptedException e) { 344 e.printStackTrace(); 345 } 346 347 tryCount++; 348 log("destroy try: " + tryCount); 349 350 if (tryCount >= maxTry) { 351 hasDestroyingErrors = true; 352 log("WARNING: waited for max time to properly destroy JNI."); 353 break; 354 } 355 } 356 log(String.format("after destroying. TotalSamplesRead = %d", totalSamplesRead)); 357 358 // for buffer test samples won't be read into here 359 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY 360 && totalSamplesRead == 0) { 361 //hasDestroyingErrors = true; 362 log("Warning: Latency test reads no sample from native recorder!"); 363 } 364 365 endTest(); 366 } 367 } 368 369 requestStopTest()370 public void requestStopTest() { 371 mIsRequestStop = true; 372 } 373 374 375 /** Set up parameters needed for GlitchDetectionThread, then create and run this thread. */ setUpGlitchDetectionThread()376 private void setUpGlitchDetectionThread() { 377 final int targetFFTMs = 20; // we want each FFT to cover 20ms of samples 378 mFFTSamplingSize = targetFFTMs * mSamplingRate / Constant.MILLIS_PER_SECOND; 379 // round to the nearest power of 2 380 mFFTSamplingSize = (int) Math.pow(2, Math.round(Math.log(mFFTSamplingSize) / Math.log(2))); 381 382 if (mFFTSamplingSize < 2) { 383 mFFTSamplingSize = 2; // mFFTSamplingSize should be at least 2 384 } 385 mFFTOverlapSamples = mFFTSamplingSize / 2; // mFFTOverlapSamples is half of mFFTSamplingSize 386 387 mGlitchDetectionThread = new GlitchDetectionThread(mFrequency1, mFrequency2, mSamplingRate, 388 mFFTSamplingSize, mFFTOverlapSamples, mBufferTestDurationInSeconds, 389 mBufferTestWavePlotDurationInSeconds, mPipeByteBuffer, mCaptureHolder); 390 mGlitchDetectionThread.start(); 391 } 392 393 endDetecting()394 public void endDetecting() { 395 mPipeByteBuffer.flush(); 396 mPipeByteBuffer = null; 397 mGlitchDetectionThread.requestStop(); 398 GlitchDetectionThread tempThread = mGlitchDetectionThread; 399 mGlitchDetectionThread = null; 400 try { 401 tempThread.join(Constant.JOIN_WAIT_TIME_MS); 402 } catch (InterruptedException e) { 403 e.printStackTrace(); 404 } 405 } 406 407 setMessageHandler(Handler messageHandler)408 public void setMessageHandler(Handler messageHandler) { 409 mMessageHandler = messageHandler; 410 } 411 412 runDestroy(final long localNativeHandle)413 private void runDestroy(final long localNativeHandle) { 414 isDestroying = true; 415 416 //start thread 417 Thread thread = new Thread(new Runnable() { 418 public void run() { 419 isDestroying = true; 420 log("**Start runnable destroy"); 421 422 int status = nativeDestroy(localNativeHandle); 423 log(String.format("**End runnable destroy native delete status: %d", status)); 424 isDestroying = false; 425 } 426 }); 427 428 thread.start(); 429 log("end of runDestroy()"); 430 } 431 432 433 /** not doing real work, just to keep consistency with LoopbackAudioThread. */ runTest()434 public void runTest() { 435 436 } 437 438 439 /** not doing real work, just to keep consistency with LoopbackAudioThread. */ runBufferTest()440 public void runBufferTest() { 441 442 } 443 444 endTest()445 public void endTest() { 446 log("--Ending capture test--"); 447 if (mMessageHandler != null) { 448 Message msg = Message.obtain(); 449 if (hasDestroyingErrors) { 450 switch (mTestType) { 451 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 452 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS; 453 break; 454 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 455 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS; 456 break; 457 } 458 } else if (mIsRequestStop) { 459 switch (mTestType) { 460 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 461 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP; 462 break; 463 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 464 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP; 465 break; 466 } 467 } else { 468 switch (mTestType) { 469 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 470 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE; 471 break; 472 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 473 msg.what = LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE; 474 break; 475 } 476 } 477 478 mMessageHandler.sendMessage(msg); 479 } 480 } 481 482 finish()483 public void finish() { 484 mIsRunning = false; 485 } 486 487 log(String msg)488 private static void log(String msg) { 489 Log.v(TAG, msg); 490 } 491 492 getWaveData()493 double[] getWaveData() { 494 return mSamples; 495 } 496 497 getRecorderBufferPeriod()498 public int[] getRecorderBufferPeriod() { 499 return mRecorderBufferPeriod; 500 } 501 getRecorderMaxBufferPeriod()502 public int getRecorderMaxBufferPeriod() { 503 return mRecorderMaxBufferPeriod; 504 } 505 getRecorderStdDevBufferPeriod()506 public double getRecorderStdDevBufferPeriod() { 507 return mRecorderStdDevBufferPeriod; 508 } 509 getPlayerBufferPeriod()510 public int[] getPlayerBufferPeriod() { 511 return mPlayerBufferPeriod; 512 } 513 getPlayerMaxBufferPeriod()514 public int getPlayerMaxBufferPeriod() { 515 return mPlayerMaxBufferPeriod; 516 } 517 getPlayerStdDevBufferPeriod()518 public double getPlayerStdDevBufferPeriod() { 519 return mPlayerStdDevBufferPeriod; 520 } 521 getNativeAllGlitches()522 public int[] getNativeAllGlitches() { 523 return mAllGlitches; 524 } 525 526 getGlitchingIntervalTooLong()527 public boolean getGlitchingIntervalTooLong() { 528 return mGlitchingIntervalTooLong; 529 } 530 531 getNativeFFTSamplingSize()532 public int getNativeFFTSamplingSize() { 533 return mFFTSamplingSize; 534 } 535 536 getNativeFFTOverlapSamples()537 public int getNativeFFTOverlapSamples() { 538 return mFFTOverlapSamples; 539 } 540 541 getDurationInSeconds()542 public int getDurationInSeconds() { 543 return mBufferTestDurationInSeconds; 544 } 545 getPlayerCallbackTimes()546 public BufferCallbackTimes getPlayerCallbackTimes() { 547 return mPlayerCallbackTimes; 548 } 549 getRecorderCallbackTimes()550 public BufferCallbackTimes getRecorderCallbackTimes() { 551 return mRecorderCallbackTimes; 552 } 553 } 554