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.content.Context; 20 import android.media.AudioDeviceInfo; 21 import android.media.AudioFormat; 22 import android.media.AudioManager; 23 import android.media.AudioRecord; 24 import android.media.AudioTrack; 25 import android.media.MediaRecorder; 26 import android.os.Build; 27 import android.util.Log; 28 import android.os.Handler; 29 import android.os.Message; 30 31 /** 32 * A thread/audio track based audio synth. 33 */ 34 35 public class LoopbackAudioThread extends Thread { 36 private static final String TAG = "LoopbackAudioThread"; 37 38 private static final int THREAD_SLEEP_DURATION_MS = 1; 39 40 // for latency test 41 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED = 991; 42 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR = 992; 43 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE = 993; 44 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP = 994; 45 46 // for buffer test 47 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED = 996; 48 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR = 997; 49 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE = 998; 50 static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP = 999; 51 52 public boolean mIsRunning = false; 53 public AudioTrack mAudioTrack; 54 public int mSessionId; 55 private Thread mRecorderThread; 56 private RecorderRunnable mRecorderRunnable; 57 58 private final int mSamplingRate; 59 private final int mChannelIndex; 60 private final int mChannelConfigIn = AudioFormat.CHANNEL_IN_MONO; 61 private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; 62 private int mMinPlayerBufferSizeInBytes = 0; 63 private int mMinRecorderBuffSizeInBytes = 0; 64 private int mMinPlayerBufferSizeSamples = 0; 65 private final int mMicSource; 66 private final int mChannelConfigOut = AudioFormat.CHANNEL_OUT_MONO; 67 private boolean mIsPlaying = false; 68 private boolean mIsRequestStop = false; 69 private Handler mMessageHandler; 70 // This is the pipe that connects the player and the recorder in latency test. 71 private PipeShort mLatencyTestPipe = new PipeShort(Constant.MAX_SHORTS); 72 73 // for buffer test 74 private BufferPeriod mRecorderBufferPeriod; // used to collect recorder's buffer period 75 private BufferPeriod mPlayerBufferPeriod; // used to collect player's buffer period 76 private int mTestType; // latency test or buffer test 77 private int mBufferTestDurationInSeconds; // Duration of actual buffer test 78 private Context mContext; 79 private int mBufferTestWavePlotDurationInSeconds; 80 private final CaptureHolder mCaptureHolder; 81 private boolean mIsAdjustingSoundLevel = true; // only used in buffer test 82 computeDefaultSettings()83 public static TestSettings computeDefaultSettings() { 84 int samplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); 85 int minPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(samplingRate, 86 AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); 87 int minRecorderBufferSizeInBytes = AudioRecord.getMinBufferSize(samplingRate, 88 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); 89 return new TestSettings(samplingRate, minPlayerBufferSizeInBytes, 90 minRecorderBufferSizeInBytes); 91 } 92 LoopbackAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes, int micSource, BufferPeriod recorderBufferPeriod, BufferPeriod playerBufferPeriod, int testType, int bufferTestDurationInSeconds, int bufferTestWavePlotDurationInSeconds, Context context, int channelIndex, CaptureHolder captureHolder)93 public LoopbackAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes, 94 int micSource, BufferPeriod recorderBufferPeriod, 95 BufferPeriod playerBufferPeriod, int testType, 96 int bufferTestDurationInSeconds, 97 int bufferTestWavePlotDurationInSeconds, Context context, 98 int channelIndex, CaptureHolder captureHolder) { 99 mSamplingRate = samplingRate; 100 mMinPlayerBufferSizeInBytes = playerBufferInBytes; 101 mMinRecorderBuffSizeInBytes = recorderBufferInBytes; 102 mMicSource = micSource; 103 mRecorderBufferPeriod = recorderBufferPeriod; 104 mPlayerBufferPeriod = playerBufferPeriod; 105 mTestType = testType; 106 mBufferTestDurationInSeconds = bufferTestDurationInSeconds; 107 mBufferTestWavePlotDurationInSeconds = bufferTestWavePlotDurationInSeconds; 108 mContext = context; 109 mChannelIndex = channelIndex; 110 mCaptureHolder = captureHolder; 111 112 setName("Loopback_LoopbackAudio"); 113 } 114 115 run()116 public void run() { 117 setPriority(Thread.MAX_PRIORITY); 118 119 if (mMinPlayerBufferSizeInBytes <= 0) { 120 mMinPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(mSamplingRate, 121 mChannelConfigOut, mAudioFormat); 122 123 log("Player: computed min buff size = " + mMinPlayerBufferSizeInBytes + " bytes"); 124 } else { 125 log("Player: using min buff size = " + mMinPlayerBufferSizeInBytes + " bytes"); 126 } 127 128 mMinPlayerBufferSizeSamples = mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME; 129 short[] audioShortArrayOut = new short[mMinPlayerBufferSizeSamples]; 130 131 // we may want to adjust this to different multiplication of mMinPlayerBufferSizeSamples 132 int audioTrackWriteDataSize = mMinPlayerBufferSizeSamples; 133 134 // used for buffer test only 135 final double frequency1 = Constant.PRIME_FREQUENCY_1; 136 final double frequency2 = Constant.PRIME_FREQUENCY_2; // not actually used 137 short[] bufferTestTone = new short[audioTrackWriteDataSize]; // used by AudioTrack.write() 138 ToneGeneration toneGeneration = new SineWaveTone(mSamplingRate, frequency1); 139 140 mRecorderRunnable = new RecorderRunnable(mLatencyTestPipe, mSamplingRate, mChannelConfigIn, 141 mAudioFormat, mMinRecorderBuffSizeInBytes, MediaRecorder.AudioSource.MIC, this, 142 mRecorderBufferPeriod, mTestType, frequency1, frequency2, 143 mBufferTestWavePlotDurationInSeconds, mContext, mChannelIndex, mCaptureHolder); 144 mRecorderRunnable.setBufferTestDurationInSeconds(mBufferTestDurationInSeconds); 145 mRecorderThread = new Thread(mRecorderRunnable); 146 mRecorderThread.setName("Loopback_RecorderRunnable"); 147 148 // both player and recorder run at max priority 149 mRecorderThread.setPriority(Thread.MAX_PRIORITY); 150 mRecorderThread.start(); 151 152 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 153 mAudioTrack = new AudioTrack.Builder() 154 .setAudioFormat((mChannelIndex < 0 ? 155 new AudioFormat.Builder().setChannelMask(AudioFormat.CHANNEL_OUT_MONO) : 156 new AudioFormat.Builder().setChannelIndexMask(1 << mChannelIndex)) 157 .setSampleRate(mSamplingRate) 158 .setEncoding(mAudioFormat) 159 .build()) 160 .setBufferSizeInBytes(mMinPlayerBufferSizeInBytes) 161 .setTransferMode(AudioTrack.MODE_STREAM) 162 .build(); 163 } else { 164 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 165 mSamplingRate, 166 mChannelConfigOut, 167 mAudioFormat, 168 mMinPlayerBufferSizeInBytes, 169 AudioTrack.MODE_STREAM /* FIXME runtime test for API level 9, 170 mSessionId */); 171 } 172 173 if (mRecorderRunnable != null && mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) { 174 mIsPlaying = false; 175 mIsRunning = true; 176 177 while (mIsRunning && mRecorderThread.isAlive()) { 178 if (mIsPlaying) { 179 switch (mTestType) { 180 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 181 // read from the pipe and plays it out 182 int samplesAvailable = mLatencyTestPipe.availableToRead(); 183 if (samplesAvailable > 0) { 184 int samplesOfInterest = Math.min(samplesAvailable, 185 mMinPlayerBufferSizeSamples); 186 187 int samplesRead = mLatencyTestPipe.read(audioShortArrayOut, 0, 188 samplesOfInterest); 189 mAudioTrack.write(audioShortArrayOut, 0, samplesRead); 190 mPlayerBufferPeriod.collectBufferPeriod(); 191 } 192 break; 193 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 194 // don't collect buffer period when we are still adjusting the sound level 195 if (mIsAdjustingSoundLevel) { 196 toneGeneration.generateTone(bufferTestTone, bufferTestTone.length); 197 mAudioTrack.write(bufferTestTone, 0, audioTrackWriteDataSize); 198 } else { 199 mPlayerBufferPeriod.collectBufferPeriod(); 200 toneGeneration.generateTone(bufferTestTone, bufferTestTone.length); 201 mAudioTrack.write(bufferTestTone, 0, audioTrackWriteDataSize); 202 } 203 break; 204 } 205 } else { 206 // wait for a bit to allow AudioTrack to start playing 207 if (mIsRunning) { 208 try { 209 sleep(THREAD_SLEEP_DURATION_MS); 210 } catch (InterruptedException e) { 211 e.printStackTrace(); 212 } 213 } 214 } 215 } 216 endTest(); 217 218 } else { 219 log("Loopback Audio Thread couldn't run!"); 220 mAudioTrack.release(); 221 mAudioTrack = null; 222 if (mMessageHandler != null) { 223 Message msg = Message.obtain(); 224 switch (mTestType) { 225 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 226 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR; 227 break; 228 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 229 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR; 230 break; 231 } 232 233 mMessageHandler.sendMessage(msg); 234 } 235 236 } 237 } 238 239 setMessageHandler(Handler messageHandler)240 public void setMessageHandler(Handler messageHandler) { 241 mMessageHandler = messageHandler; 242 } 243 244 setIsAdjustingSoundLevel(boolean isAdjustingSoundLevel)245 public void setIsAdjustingSoundLevel(boolean isAdjustingSoundLevel) { 246 mIsAdjustingSoundLevel = isAdjustingSoundLevel; 247 } 248 249 runTest()250 public void runTest() { 251 if (mIsRunning) { 252 // start test 253 if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 254 log("...run test, but still playing..."); 255 endTest(); 256 } else { 257 // start playing 258 mIsPlaying = true; 259 mAudioTrack.play(); 260 boolean status = mRecorderRunnable.startRecording(); 261 262 log("Started capture test"); 263 if (mMessageHandler != null) { 264 Message msg = Message.obtain(); 265 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED; 266 if (!status) { 267 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR; 268 } 269 270 mMessageHandler.sendMessage(msg); 271 } 272 } 273 } 274 } 275 276 runBufferTest()277 public void runBufferTest() { 278 if (mIsRunning) { 279 // start test 280 if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 281 log("...run test, but still playing..."); 282 endTest(); 283 } else { 284 // start playing 285 mIsPlaying = true; 286 mAudioTrack.play(); 287 boolean status = mRecorderRunnable.startBufferRecording(); 288 log(" Started capture test"); 289 if (mMessageHandler != null) { 290 Message msg = Message.obtain(); 291 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED; 292 293 if (!status) { 294 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR; 295 } 296 297 mMessageHandler.sendMessage(msg); 298 } 299 } 300 } 301 } 302 303 304 /** Clean some things up before sending out a message to LoopbackActivity. */ endTest()305 public void endTest() { 306 switch (mTestType) { 307 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 308 log("--Ending latency test--"); 309 break; 310 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 311 log("--Ending buffer test--"); 312 break; 313 } 314 315 mIsPlaying = false; 316 mAudioTrack.pause(); 317 mLatencyTestPipe.flush(); 318 mAudioTrack.flush(); 319 320 if (mMessageHandler != null) { 321 Message msg = Message.obtain(); 322 if (mIsRequestStop) { 323 switch (mTestType) { 324 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 325 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP; 326 break; 327 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 328 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP; 329 break; 330 } 331 } else { 332 switch (mTestType) { 333 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 334 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE; 335 break; 336 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 337 msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE; 338 break; 339 } 340 } 341 342 mMessageHandler.sendMessage(msg); 343 } 344 } 345 346 347 /** 348 * This is called only when the user requests to stop the test through 349 * pressing a button in the LoopbackActivity. 350 */ requestStopTest()351 public void requestStopTest() throws InterruptedException { 352 mIsRequestStop = true; 353 mRecorderRunnable.requestStop(); 354 } 355 356 357 /** Release mAudioTrack and mRecorderThread. */ finish()358 public void finish() throws InterruptedException { 359 mIsRunning = false; 360 361 final AudioTrack at = mAudioTrack; 362 if (at != null) { 363 at.release(); 364 mAudioTrack = null; 365 } 366 367 Thread zeThread = mRecorderThread; 368 mRecorderThread = null; 369 if (zeThread != null) { 370 zeThread.interrupt(); 371 zeThread.join(Constant.JOIN_WAIT_TIME_MS); 372 } 373 } 374 375 log(String msg)376 private static void log(String msg) { 377 Log.v(TAG, msg); 378 } 379 380 getWaveData()381 public double[] getWaveData() { 382 return mRecorderRunnable.getWaveData(); 383 } 384 385 getAllGlitches()386 public int[] getAllGlitches() { 387 return mRecorderRunnable.getAllGlitches(); 388 } 389 390 getGlitchingIntervalTooLong()391 public boolean getGlitchingIntervalTooLong() { 392 return mRecorderRunnable.getGlitchingIntervalTooLong(); 393 } 394 395 getFFTSamplingSize()396 public int getFFTSamplingSize() { 397 return mRecorderRunnable.getFFTSamplingSize(); 398 } 399 400 getFFTOverlapSamples()401 public int getFFTOverlapSamples() { 402 return mRecorderRunnable.getFFTOverlapSamples(); 403 } 404 405 getDurationInSeconds()406 int getDurationInSeconds() { 407 return mBufferTestDurationInSeconds; 408 } 409 410 } 411