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