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 com.android.cts.verifier.audio; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.cts.verifier.audio.wavelib.*; 22 import com.android.compatibility.common.util.ReportLog; 23 import com.android.compatibility.common.util.ResultType; 24 import com.android.compatibility.common.util.ResultUnit; 25 import android.content.Context; 26 import android.content.BroadcastReceiver; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 30 import android.media.AudioDeviceCallback; 31 import android.media.AudioDeviceInfo; 32 import android.media.AudioFormat; 33 import android.media.AudioManager; 34 import android.media.AudioTrack; 35 import android.media.AudioRecord; 36 import android.media.MediaRecorder; 37 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.SystemClock; 42 43 import android.util.Log; 44 45 import android.view.View; 46 import android.view.View.OnClickListener; 47 48 import android.widget.Button; 49 import android.widget.TextView; 50 import android.widget.SeekBar; 51 import android.widget.LinearLayout; 52 import android.widget.ProgressBar; 53 54 /** 55 * Tests Audio built in Microphone response using external speakers and USB reference microphone. 56 */ 57 public class AudioFrequencyMicActivity extends PassFailButtons.Activity implements Runnable, 58 AudioRecord.OnRecordPositionUpdateListener { 59 private static final String TAG = "AudioFrequencyMicActivity"; 60 61 private static final int TEST_STARTED = 900; 62 private static final int TEST_ENDED = 901; 63 private static final int TEST_MESSAGE = 902; 64 private static final int TEST1_MESSAGE = 903; 65 private static final int TEST1_ENDED = 904; 66 private static final double MIN_ENERGY_BAND_1 = -50.0; //dB Full Scale 67 private static final double MAX_ENERGY_BAND_1_BASE = -60.0; //dB Full Scale 68 private static final double MIN_FRACTION_POINTS_IN_BAND = 0.3; 69 private static final double MAX_VAL = Math.pow(2, 15); 70 private static final double CLIP_LEVEL = (MAX_VAL-10) / MAX_VAL; 71 72 final OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 73 Context mContext; 74 75 Button mSpeakersReady; //user signal to have connected external speakers 76 Button mTest1Button; //execute test 1 77 Button mUsbMicReady; //user signal to have connected USB Microphone 78 Button mTest2Button; //user to start test 79 String mUsbDevicesInfo; //usb device info for report 80 LinearLayout mLayoutTest1; 81 LinearLayout mLayoutTest2a; 82 LinearLayout mLayoutTest2b; 83 84 TextView mSpeakerReadyText; 85 TextView mTest2Result; 86 TextView mUsbStatusText; 87 TextView mTest1Result; 88 ProgressBar mProgressBar; 89 90 private boolean mIsRecording = false; 91 private final Object mRecordingLock = new Object(); 92 private AudioRecord mRecorder; 93 private int mMinRecordBufferSizeInSamples = 0; 94 private short[] mAudioShortArray; 95 private short[] mAudioShortArray2; 96 97 private final int mBlockSizeSamples = 1024; 98 private final int mSamplingRate = 48000; 99 private final int mSelectedRecordSource = MediaRecorder.AudioSource.VOICE_RECOGNITION; 100 private final int mChannelConfig = AudioFormat.CHANNEL_IN_MONO; 101 private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; 102 private Thread mRecordThread; 103 104 PipeShort mPipe = new PipeShort(65536); 105 SoundPlayerObject mSPlayer; 106 107 private DspBufferComplex mC; 108 private DspBufferDouble mData; 109 110 private DspWindow mWindow; 111 private DspFftServer mFftServer; 112 private VectorAverage mFreqAverageMain = new VectorAverage(); 113 114 private VectorAverage mFreqAverageBase = new VectorAverage(); 115 private VectorAverage mFreqAverageBuiltIn = new VectorAverage(); 116 private VectorAverage mFreqAverageReference = new VectorAverage(); 117 118 private int mCurrentTest = -1; 119 int mBands = 4; 120 AudioBandSpecs[] bandSpecsArray = new AudioBandSpecs[mBands]; 121 AudioBandSpecs[] baseBandSpecsArray = new AudioBandSpecs[mBands]; 122 123 int mMaxLevel; 124 private class OnBtnClickListener implements OnClickListener { 125 @Override onClick(View v)126 public void onClick(View v) { 127 switch (v.getId()) { 128 case R.id.audio_frequency_mic_speakers_ready_btn: 129 testSpeakersReady(); 130 break; 131 case R.id.audio_frequency_mic_test1_btn: 132 startTest1(); 133 break; 134 case R.id.audio_frequency_mic_mic_ready_btn: 135 testUSB(); 136 break; 137 case R.id.audio_frequency_mic_test2_btn: 138 startTest2(); 139 break; 140 } 141 } 142 } 143 144 @Override onCreate(Bundle savedInstanceState)145 protected void onCreate(Bundle savedInstanceState) { 146 super.onCreate(savedInstanceState); 147 setContentView(R.layout.audio_frequency_mic_activity); 148 mContext = this; 149 mSpeakerReadyText = (TextView) findViewById(R.id.audio_frequency_mic_speakers_ready_status); 150 151 mSpeakersReady = (Button)findViewById(R.id.audio_frequency_mic_speakers_ready_btn); 152 mSpeakersReady.setOnClickListener(mBtnClickListener); 153 mTest1Button = (Button)findViewById(R.id.audio_frequency_mic_test1_btn); 154 mTest1Button.setOnClickListener(mBtnClickListener); 155 mTest1Result = (TextView)findViewById(R.id.audio_frequency_mic_results1_text); 156 mLayoutTest1 = (LinearLayout) findViewById(R.id.audio_frequency_mic_layout_test1); 157 mLayoutTest2a = (LinearLayout) findViewById(R.id.audio_frequency_mic_layout_test2a); 158 mLayoutTest2b = (LinearLayout) findViewById(R.id.audio_frequency_mic_layout_test2b); 159 mUsbMicReady = (Button)findViewById(R.id.audio_frequency_mic_mic_ready_btn); 160 mUsbMicReady.setOnClickListener(mBtnClickListener); 161 162 mUsbStatusText = (TextView)findViewById(R.id.audio_frequency_mic_usb_status); 163 mTest2Button = (Button)findViewById(R.id.audio_frequency_mic_test2_btn); 164 mTest2Button.setOnClickListener(mBtnClickListener); 165 mTest2Result = (TextView)findViewById(R.id.audio_frequency_mic_results_text); 166 mProgressBar = (ProgressBar)findViewById(R.id.audio_frequency_mic_progress_bar); 167 showWait(false); 168 enableLayout(mLayoutTest1, false); 169 enableLayout(mLayoutTest2a, false); 170 enableLayout(mLayoutTest2b, false); 171 172 mSPlayer = new SoundPlayerObject(); 173 mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.stereo_mono_white_noise_48); 174 mSPlayer.setBalance(0.5f); 175 176 //Init FFT stuff 177 mAudioShortArray2 = new short[mBlockSizeSamples*2]; 178 mData = new DspBufferDouble(mBlockSizeSamples); 179 mC = new DspBufferComplex(mBlockSizeSamples); 180 mFftServer = new DspFftServer(mBlockSizeSamples); 181 182 int overlap = mBlockSizeSamples / 2; 183 184 mWindow = new DspWindow(DspWindow.WINDOW_HANNING, mBlockSizeSamples, overlap); 185 186 setPassFailButtonClickListeners(); 187 getPassButton().setEnabled(false); 188 setInfoResources(R.string.audio_frequency_mic_test, 189 R.string.audio_frequency_mic_info, -1); 190 191 //Init bands for BuiltIn/Reference test 192 bandSpecsArray[0] = new AudioBandSpecs( 193 50, 500, /* frequency start,stop */ 194 -20.0, -50, /* start top,bottom value */ 195 4.0, -4.0 /* stop top,bottom value */); 196 197 bandSpecsArray[1] = new AudioBandSpecs( 198 500,4000, /* frequency start,stop */ 199 4.0, -4.0, /* start top,bottom value */ 200 4.0, -4.0 /* stop top,bottom value */); 201 202 bandSpecsArray[2] = new AudioBandSpecs( 203 4000, 12000, /* frequency start,stop */ 204 4.0, -4.0, /* start top,bottom value */ 205 5.0, -5.0 /* stop top,bottom value */); 206 207 bandSpecsArray[3] = new AudioBandSpecs( 208 12000, 20000, /* frequency start,stop */ 209 5.0, -5.0, /* start top,bottom value */ 210 5.0, -30.0 /* stop top,bottom value */); 211 212 //Init base bands for silence 213 baseBandSpecsArray[0] = new AudioBandSpecs( 214 50, 500, /* frequency start,stop */ 215 40.0, -50.0, /* start top,bottom value */ 216 5.0, -50.0 /* stop top,bottom value */); 217 218 baseBandSpecsArray[1] = new AudioBandSpecs( 219 500,4000, /* frequency start,stop */ 220 5.0, -50.0, /* start top,bottom value */ 221 5.0, -50.0 /* stop top,bottom value */); 222 223 baseBandSpecsArray[2] = new AudioBandSpecs( 224 4000, 12000, /* frequency start,stop */ 225 5.0, -50.0, /* start top,bottom value */ 226 5.0, -50.0 /* stop top,bottom value */); 227 228 baseBandSpecsArray[3] = new AudioBandSpecs( 229 12000, 20000, /* frequency start,stop */ 230 5.0, -50.0, /* start top,bottom value */ 231 5.0, -50.0 /* stop top,bottom value */); 232 233 } 234 235 /** 236 * enable test ui elements 237 */ enableLayout(LinearLayout layout, boolean enable)238 private void enableLayout(LinearLayout layout, boolean enable) { 239 for (int i = 0; i < layout.getChildCount(); i++) { 240 View view = layout.getChildAt(i); 241 view.setEnabled(enable); 242 } 243 } 244 245 /** 246 * show active progress bar 247 */ showWait(boolean show)248 private void showWait(boolean show) { 249 if (show) { 250 mProgressBar.setVisibility(View.VISIBLE); 251 } else { 252 mProgressBar.setVisibility(View.INVISIBLE); 253 } 254 } 255 setMaxLevel()256 private void setMaxLevel() { 257 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 258 mMaxLevel = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 259 am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(mMaxLevel), 0); 260 } 261 setMinLevel()262 private void setMinLevel() { 263 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 264 am.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); 265 } 266 267 /** 268 * Start the loopback audio test 269 */ startTest1()270 private void startTest1() { 271 if (mTestThread != null && !mTestThread.isAlive()) { 272 mTestThread = null; //kill it. 273 } 274 275 if (mTestThread == null) { 276 Log.v(TAG,"Executing test Thread"); 277 mTestThread = new Thread(mTest1Runnable); 278 //getPassButton().setEnabled(false); 279 if (!mSPlayer.isAlive()) 280 mSPlayer.start(); 281 mTestThread.start(); 282 } else { 283 Log.v(TAG,"test Thread already running."); 284 } 285 } 286 287 Thread mTestThread; 288 Runnable mTest1Runnable = new Runnable() { 289 public void run() { 290 Message msg = Message.obtain(); 291 msg.what = TEST_STARTED; 292 mMessageHandler.sendMessage(msg); 293 294 setMinLevel(); 295 sendMessage("Testing Background Environment"); 296 mCurrentTest = 0; 297 mSPlayer.setBalance(0.5f); 298 mFreqAverageBase.reset(); 299 play(); 300 301 setMaxLevel(); 302 sendMessage("Testing Built in Microphone"); 303 mCurrentTest = 1; 304 mFreqAverageBuiltIn.reset(); 305 mSPlayer.setBalance(0.5f); 306 play(); 307 308 mCurrentTest = -1; 309 sendMessage("Testing Completed"); 310 311 Message msg2 = Message.obtain(); 312 msg2.what = TEST1_ENDED; 313 mMessageHandler.sendMessage(msg2); 314 } 315 316 private void play() { 317 startRecording(); 318 mSPlayer.play(true); 319 320 try { 321 Thread.sleep(2000); 322 } catch (InterruptedException e) { 323 e.printStackTrace(); 324 //restore interrupted status 325 Thread.currentThread().interrupt(); 326 } 327 328 mSPlayer.play(false); 329 stopRecording(); 330 } 331 332 private void sendMessage(String str) { 333 Message msg = Message.obtain(); 334 msg.what = TEST1_MESSAGE; 335 msg.obj = str; 336 mMessageHandler.sendMessage(msg); 337 } 338 }; 339 340 /** 341 * Start the loopback audio test 342 */ startTest2()343 private void startTest2() { 344 if (mTestThread != null && !mTestThread.isAlive()) { 345 mTestThread = null; //kill it. 346 } 347 348 if (mTestThread == null) { 349 Log.v(TAG,"Executing test2 Thread"); 350 mTestThread = new Thread(mTest2Runnable); 351 //getPassButton().setEnabled(false); 352 if (!mSPlayer.isAlive()) 353 mSPlayer.start(); 354 mTestThread.start(); 355 } else { 356 Log.v(TAG,"test Thread already running."); 357 } 358 } 359 360 Runnable mTest2Runnable = new Runnable() { 361 public void run() { 362 Message msg = Message.obtain(); 363 msg.what = TEST_STARTED; 364 mMessageHandler.sendMessage(msg); 365 366 sendMessage("Testing Reference USB Microphone"); 367 mCurrentTest = 2; 368 mFreqAverageReference.reset(); 369 mSPlayer.setBalance(0.5f); 370 play(); 371 372 mCurrentTest = -1; 373 sendMessage("Testing Completed"); 374 375 Message msg2 = Message.obtain(); 376 msg2.what = TEST_ENDED; 377 mMessageHandler.sendMessage(msg2); 378 } 379 380 private void play() { 381 startRecording(); 382 mSPlayer.play(true); 383 384 try { 385 Thread.sleep(2000); 386 } catch (InterruptedException e) { 387 e.printStackTrace(); 388 //restore interrupted status 389 Thread.currentThread().interrupt(); 390 } 391 392 mSPlayer.play(false); 393 stopRecording(); 394 } 395 396 private void sendMessage(String str) { 397 Message msg = Message.obtain(); 398 msg.what = TEST_MESSAGE; 399 msg.obj = str; 400 mMessageHandler.sendMessage(msg); 401 } 402 }; 403 404 private Handler mMessageHandler = new Handler() { 405 public void handleMessage(Message msg) { 406 super.handleMessage(msg); 407 switch (msg.what) { 408 case TEST_STARTED: 409 showWait(true); 410 getPassButton().setEnabled(false); 411 break; 412 case TEST_ENDED: 413 showWait(false); 414 computeTest2Results(); 415 break; 416 case TEST1_MESSAGE: { 417 String str = (String)msg.obj; 418 if (str != null) { 419 mTest1Result.setText(str); 420 } 421 } 422 break; 423 case TEST1_ENDED: 424 showWait(false); 425 computeTest1Results(); 426 break; 427 case TEST_MESSAGE: { 428 String str = (String)msg.obj; 429 if (str != null) { 430 mTest2Result.setText(str); 431 } 432 } 433 break; 434 default: 435 Log.e(TAG, String.format("Unknown message: %d", msg.what)); 436 } 437 } 438 }; 439 440 private class Results { 441 private String mLabel; 442 public double[] mValuesLog; 443 int[] mPointsPerBand = new int[mBands]; 444 double[] mAverageEnergyPerBand = new double[mBands]; 445 int[] mInBoundPointsPerBand = new int[mBands]; 446 public boolean mIsBaseMeasurement = false; Results(String label)447 public Results(String label) { 448 mLabel = label; 449 } 450 451 //append results toString()452 public String toString() { 453 StringBuilder sb = new StringBuilder(); 454 sb.append(String.format("Channel %s\n", mLabel)); 455 sb.append("Level in Band 1 : " + (testLevel() ? "OK" :"FAILED") + 456 (mIsBaseMeasurement ? " (Base Meas.)" : "") + "\n"); 457 for (int b = 0; b < mBands; b++) { 458 double percent = 0; 459 if (mPointsPerBand[b] > 0) { 460 percent = 100.0 * (double) mInBoundPointsPerBand[b] / mPointsPerBand[b]; 461 } 462 sb.append(String.format( 463 " Band %d: Av. Level: %.1f dB InBand: %d/%d (%.1f%%) %s\n", 464 b, mAverageEnergyPerBand[b], 465 mInBoundPointsPerBand[b], 466 mPointsPerBand[b], 467 percent, 468 (testInBand(b) ? "OK" : "FAILED"))); 469 } 470 return sb.toString(); 471 } 472 testLevel()473 public boolean testLevel() { 474 if (mIsBaseMeasurement && mAverageEnergyPerBand[1] <= MAX_ENERGY_BAND_1_BASE) { 475 return true; 476 } else if (mAverageEnergyPerBand[1] >= MIN_ENERGY_BAND_1) { 477 return true; 478 } 479 return false; 480 } 481 testInBand(int b)482 public boolean testInBand(int b) { 483 if (b >= 0 && b < mBands && mPointsPerBand[b] > 0) { 484 if ((double) mInBoundPointsPerBand[b] / mPointsPerBand[b] > 485 MIN_FRACTION_POINTS_IN_BAND) { 486 return true; 487 } 488 } 489 return false; 490 } 491 testAll()492 public boolean testAll() { 493 if (!testLevel()) { 494 return false; 495 } 496 for (int b = 0; b < mBands; b++) { 497 if (!testInBand(b)) { 498 return false; 499 } 500 } 501 return true; 502 } 503 } 504 505 506 /** 507 * compute test1 results 508 */ computeTest1Results()509 private void computeTest1Results() { 510 511 Results resultsBase = new Results("Base"); 512 if (computeResultsForVector(mFreqAverageBase, resultsBase, true, baseBandSpecsArray)) { 513 appendResultsToScreen(resultsBase.toString(), mTest1Result); 514 recordTestResults(resultsBase); 515 } 516 517 Results resultsBuiltIn = new Results("BuiltIn"); 518 if (computeResultsForVector(mFreqAverageBuiltIn, resultsBuiltIn, false, bandSpecsArray)) { 519 appendResultsToScreen(resultsBuiltIn.toString(), mTest1Result); 520 recordTestResults(resultsBuiltIn); 521 } 522 523 //tell user to connect USB Microphone 524 appendResultsToScreen("\n\n" + 525 getResources().getText(R.string.audio_frequency_mic_connect_mic), mTest1Result); 526 enableLayout(mLayoutTest2a, true); 527 } 528 529 /** 530 * compute test results 531 */ computeTest2Results()532 private void computeTest2Results() { 533 Results resultsReference = new Results("Reference"); 534 if (computeResultsForVector(mFreqAverageReference, resultsReference, 535 false, bandSpecsArray)) { 536 appendResultsToScreen(resultsReference.toString(),mTest2Result); 537 recordTestResults(resultsReference); 538 getPassButton().setEnabled(true); 539 } 540 } 541 computeResultsForVector(VectorAverage freqAverage, Results results, boolean isBase, AudioBandSpecs[] bandSpecs)542 private boolean computeResultsForVector(VectorAverage freqAverage, Results results, 543 boolean isBase, AudioBandSpecs[] bandSpecs) { 544 545 results.mIsBaseMeasurement = isBase; 546 int points = freqAverage.getSize(); 547 if (points > 0) { 548 //compute vector in db 549 double[] values = new double[points]; 550 freqAverage.getData(values, false); 551 results.mValuesLog = new double[points]; 552 for (int i = 0; i < points; i++) { 553 results.mValuesLog[i] = 20 * Math.log10(values[i]); 554 } 555 556 int currentBand = 0; 557 for (int i = 0; i < points; i++) { 558 double freq = (double)mSamplingRate * i / (double)mBlockSizeSamples; 559 if (freq > bandSpecs[currentBand].mFreqStop) { 560 currentBand++; 561 if (currentBand >= mBands) 562 break; 563 } 564 565 if (freq >= bandSpecs[currentBand].mFreqStart) { 566 results.mAverageEnergyPerBand[currentBand] += results.mValuesLog[i]; 567 results.mPointsPerBand[currentBand]++; 568 } 569 } 570 571 for (int b = 0; b < mBands; b++) { 572 if (results.mPointsPerBand[b] > 0) { 573 results.mAverageEnergyPerBand[b] = 574 results.mAverageEnergyPerBand[b] / results.mPointsPerBand[b]; 575 } 576 } 577 578 //set offset relative to band 1 level 579 for (int b = 0; b < mBands; b++) { 580 bandSpecs[b].setOffset(results.mAverageEnergyPerBand[1]); 581 } 582 583 //test points in band. 584 currentBand = 0; 585 for (int i = 0; i < points; i++) { 586 double freq = (double)mSamplingRate * i / (double)mBlockSizeSamples; 587 if (freq > bandSpecs[currentBand].mFreqStop) { 588 currentBand++; 589 if (currentBand >= mBands) 590 break; 591 } 592 593 if (freq >= bandSpecs[currentBand].mFreqStart) { 594 double value = results.mValuesLog[i]; 595 if (bandSpecs[currentBand].isInBounds(freq, value)) { 596 results.mInBoundPointsPerBand[currentBand]++; 597 } 598 } 599 } 600 return true; 601 } else { 602 return false; 603 } 604 } 605 606 //append results appendResultsToScreen(String str, TextView text)607 private void appendResultsToScreen(String str, TextView text) { 608 String currentText = text.getText().toString(); 609 text.setText(currentText + "\n" + str); 610 } 611 612 /** 613 * Store test results in log 614 */ recordTestResults(Results results)615 private void recordTestResults(Results results) { 616 String channelLabel = "channel_" + results.mLabel; 617 618 for (int b = 0; b < mBands; b++) { 619 String bandLabel = String.format(channelLabel + "_%d", b); 620 getReportLog().addValue( 621 bandLabel + "_Level", 622 results.mAverageEnergyPerBand[b], 623 ResultType.HIGHER_BETTER, 624 ResultUnit.NONE); 625 626 getReportLog().addValue( 627 bandLabel + "_pointsinbound", 628 results.mInBoundPointsPerBand[b], 629 ResultType.HIGHER_BETTER, 630 ResultUnit.COUNT); 631 632 getReportLog().addValue( 633 bandLabel + "_pointstotal", 634 results.mPointsPerBand[b], 635 ResultType.NEUTRAL, 636 ResultUnit.COUNT); 637 } 638 639 getReportLog().addValues(channelLabel + "_magnitudeSpectrumLog", 640 results.mValuesLog, 641 ResultType.NEUTRAL, 642 ResultUnit.NONE); 643 644 Log.v(TAG, "Results Recorded"); 645 } 646 startRecording()647 private void startRecording() { 648 synchronized (mRecordingLock) { 649 mIsRecording = true; 650 } 651 652 boolean successful = initRecord(); 653 if (successful) { 654 startRecordingForReal(); 655 } else { 656 Log.v(TAG, "Recorder initialization error."); 657 synchronized (mRecordingLock) { 658 mIsRecording = false; 659 } 660 } 661 } 662 startRecordingForReal()663 private void startRecordingForReal() { 664 // start streaming 665 if (mRecordThread == null) { 666 mRecordThread = new Thread(AudioFrequencyMicActivity.this); 667 mRecordThread.setName("FrequencyAnalyzerThread"); 668 } 669 if (!mRecordThread.isAlive()) { 670 mRecordThread.start(); 671 } 672 673 mPipe.flush(); 674 675 long startTime = SystemClock.uptimeMillis(); 676 mRecorder.startRecording(); 677 if (mRecorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { 678 stopRecording(); 679 return; 680 } 681 Log.v(TAG, "Start time: " + (long) (SystemClock.uptimeMillis() - startTime) + " ms"); 682 } 683 stopRecording()684 private void stopRecording() { 685 synchronized (mRecordingLock) { 686 stopRecordingForReal(); 687 mIsRecording = false; 688 } 689 } 690 stopRecordingForReal()691 private void stopRecordingForReal() { 692 693 // stop streaming 694 Thread zeThread = mRecordThread; 695 mRecordThread = null; 696 if (zeThread != null) { 697 zeThread.interrupt(); 698 try { 699 zeThread.join(); 700 } catch(InterruptedException e) { 701 //restore interrupted status of recording thread 702 zeThread.interrupt(); 703 } 704 } 705 // release recording resources 706 if (mRecorder != null) { 707 mRecorder.stop(); 708 mRecorder.release(); 709 mRecorder = null; 710 } 711 } 712 initRecord()713 private boolean initRecord() { 714 int minRecordBuffSizeInBytes = AudioRecord.getMinBufferSize(mSamplingRate, 715 mChannelConfig, mAudioFormat); 716 Log.v(TAG,"FrequencyAnalyzer: min buff size = " + minRecordBuffSizeInBytes + " bytes"); 717 if (minRecordBuffSizeInBytes <= 0) { 718 return false; 719 } 720 721 mMinRecordBufferSizeInSamples = minRecordBuffSizeInBytes / 2; 722 // allocate the byte array to read the audio data 723 724 mAudioShortArray = new short[mMinRecordBufferSizeInSamples]; 725 726 Log.v(TAG, "Initiating record:"); 727 Log.v(TAG, " using source " + mSelectedRecordSource); 728 Log.v(TAG, " at " + mSamplingRate + "Hz"); 729 730 try { 731 mRecorder = new AudioRecord(mSelectedRecordSource, mSamplingRate, 732 mChannelConfig, mAudioFormat, 2 * minRecordBuffSizeInBytes); 733 } catch (IllegalArgumentException e) { 734 return false; 735 } 736 if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { 737 mRecorder.release(); 738 mRecorder = null; 739 return false; 740 } 741 mRecorder.setRecordPositionUpdateListener(this); 742 mRecorder.setPositionNotificationPeriod(mBlockSizeSamples / 2); 743 return true; 744 } 745 746 // --------------------------------------------------------- 747 // Implementation of AudioRecord.OnPeriodicNotificationListener 748 // -------------------- onPeriodicNotification(AudioRecord recorder)749 public void onPeriodicNotification(AudioRecord recorder) { 750 int samplesAvailable = mPipe.availableToRead(); 751 int samplesNeeded = mBlockSizeSamples; 752 if (samplesAvailable >= samplesNeeded) { 753 mPipe.read(mAudioShortArray2, 0, samplesNeeded); 754 755 //compute stuff. 756 int clipcount = 0; 757 double sum = 0; 758 double maxabs = 0; 759 int i; 760 761 for (i = 0; i < samplesNeeded; i++) { 762 double value = mAudioShortArray2[i] / MAX_VAL; 763 double valueabs = Math.abs(value); 764 765 if (valueabs > maxabs) { 766 maxabs = valueabs; 767 } 768 769 if (valueabs > CLIP_LEVEL) { 770 clipcount++; 771 } 772 773 sum += value * value; 774 //fft stuff 775 mData.mData[i] = value; 776 } 777 778 //for the current frame, compute FFT and send to the viewer. 779 780 //apply window and pack as complex for now. 781 DspBufferMath.mult(mData, mData, mWindow.mBuffer); 782 DspBufferMath.set(mC, mData); 783 mFftServer.fft(mC, 1); 784 785 double[] halfMagnitude = new double[mBlockSizeSamples / 2]; 786 for (i = 0; i < mBlockSizeSamples / 2; i++) { 787 halfMagnitude[i] = Math.sqrt(mC.mReal[i] * mC.mReal[i] + mC.mImag[i] * mC.mImag[i]); 788 } 789 790 mFreqAverageMain.setData(halfMagnitude, false); //average all of them! 791 792 switch(mCurrentTest) { 793 case 0: 794 mFreqAverageBase.setData(halfMagnitude, false); 795 break; 796 case 1: 797 mFreqAverageBuiltIn.setData(halfMagnitude, false); 798 break; 799 case 2: 800 mFreqAverageReference.setData(halfMagnitude, false); 801 break; 802 } 803 } 804 } 805 onMarkerReached(AudioRecord track)806 public void onMarkerReached(AudioRecord track) { 807 } 808 809 // --------------------------------------------------------- 810 // Implementation of Runnable for the audio recording + playback 811 // -------------------- run()812 public void run() { 813 Thread thisThread = Thread.currentThread(); 814 while (!thisThread.isInterrupted()) { 815 // read from native recorder 816 int nSamplesRead = mRecorder.read(mAudioShortArray, 0, mMinRecordBufferSizeInSamples); 817 if (nSamplesRead > 0) { 818 mPipe.write(mAudioShortArray, 0, nSamplesRead); 819 } 820 } 821 } 822 testSpeakersReady()823 private void testSpeakersReady() { 824 boolean isUsbConnected = 825 UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext()); 826 if (isUsbConnected) { 827 mSpeakerReadyText.setText(" USB device detected, please remove it"); 828 enableLayout(mLayoutTest1, false); 829 //fail 830 } else { 831 mSpeakerReadyText.setText(" No USB device detected. OK"); 832 enableLayout(mLayoutTest1, true); 833 } 834 } 835 testUSB()836 private void testUSB() { 837 boolean isConnected = UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext()); 838 mUsbDevicesInfo = UsbMicrophoneTester.getUSBDeviceListString(getApplicationContext()); 839 840 if (isConnected) { 841 mUsbStatusText.setText( 842 getResources().getText(R.string.audio_frequency_mic_mic_ready_text)); 843 enableLayout(mLayoutTest2b, true); 844 } else { 845 mUsbStatusText.setText( 846 getResources().getText(R.string.audio_frequency_mic_mic_not_ready_text)); 847 enableLayout(mLayoutTest2b, false); 848 } 849 } 850 851 } 852