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 static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode; 20 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix; 21 22 import android.media.AudioFormat; 23 import android.media.AudioManager; 24 import android.media.AudioRecord; 25 import android.media.MediaRecorder; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.os.SystemClock; 30 import android.util.Log; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.widget.Button; 34 import android.widget.ProgressBar; 35 import android.widget.TextView; 36 37 import com.android.compatibility.common.util.CddTest; 38 import com.android.compatibility.common.util.ResultType; 39 import com.android.compatibility.common.util.ResultUnit; 40 import com.android.cts.verifier.CtsVerifierReportLog; 41 import com.android.cts.verifier.R; 42 import com.android.cts.verifier.audio.wavelib.DspBufferComplex; 43 import com.android.cts.verifier.audio.wavelib.DspBufferDouble; 44 import com.android.cts.verifier.audio.wavelib.DspBufferMath; 45 import com.android.cts.verifier.audio.wavelib.DspFftServer; 46 import com.android.cts.verifier.audio.wavelib.DspWindow; 47 import com.android.cts.verifier.audio.wavelib.PipeShort; 48 import com.android.cts.verifier.audio.wavelib.VectorAverage; 49 50 /** 51 * Tests Audio built in Microphone response for Unprocessed audio source feature. 52 */ 53 @CddTest(requirement = "5.11/C-1-1,C-1-2,C-1-3,C-1-4,C-1-5") 54 public class AudioFrequencyUnprocessedActivity extends AudioFrequencyActivity implements Runnable, 55 AudioRecord.OnRecordPositionUpdateListener { 56 private static final String TAG = "AudioFrequencyUnprocessedActivity"; 57 58 private static final int TEST_STARTED = 900; 59 private static final int TEST_MESSAGE = 903; 60 private static final int TEST_ENDED = 904; 61 private static final int TEST_ENDED_ERROR = 905; 62 private static final double MIN_FRACTION_POINTS_IN_BAND = 0.5; 63 64 private static final double TONE_RMS_EXPECTED = -36.0; 65 private static final double TONE_RMS_MAX_ERROR = 3.0; 66 67 private static final double MAX_VAL = Math.pow(2, 15); 68 private static final double CLIP_LEVEL = (MAX_VAL-10) / MAX_VAL; 69 70 private static final int SOURCE_TONE = 0; 71 private static final int SOURCE_NOISE = 1; 72 73 private static final int TEST_NONE = -1; 74 private static final int TEST_TONE = 0; 75 private static final int TEST_NOISE = 1; 76 private static final int TEST_USB_BACKGROUND = 2; 77 private static final int TEST_USB_NOISE = 3; 78 private static final int TEST_COUNT = 4; 79 private int mCurrentTest = TEST_NONE; 80 private boolean mTestsDone[] = new boolean[TEST_COUNT]; 81 82 private static final int TEST_DURATION_DEFAULT = 2000; 83 private static final int TEST_DURATION_TONE = TEST_DURATION_DEFAULT; 84 private static final int TEST_DURATION_NOISE = TEST_DURATION_DEFAULT; 85 private static final int TEST_DURATION_USB_BACKGROUND = TEST_DURATION_DEFAULT; 86 private static final int TEST_DURATION_USB_NOISE = TEST_DURATION_DEFAULT; 87 88 final OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 89 90 Button mButtonTestTone; 91 ProgressBar mProgressTone; 92 TextView mResultTestTone; 93 Button mButtonPlayTone; 94 95 Button mButtonTestNoise; 96 ProgressBar mProgressNoise; 97 TextView mResultTestNoise; 98 Button mButtonPlayNoise; 99 100 Button mButtonTestUsbBackground; 101 ProgressBar mProgressUsbBackground; 102 TextView mResultTestUsbBackground; 103 104 Button mButtonTestUsbNoise; 105 ProgressBar mProgressUsbNoise; 106 TextView mResultTestUsbNoise; 107 Button mButtonPlayUsbNoise; 108 109 TextView mGlobalResultText; 110 TextView mTextViewUnprocessedStatus; 111 112 private boolean mIsRecording = false; 113 private final Object mRecordingLock = new Object(); 114 private AudioRecord mRecorder; 115 private int mMinRecordBufferSizeInSamples = 0; 116 private short[] mAudioShortArray; 117 private short[] mAudioShortArray2; 118 119 private final int mBlockSizeSamples = 4096; 120 private final int mSamplingRate = 48000; 121 private final int mSelectedRecordSource = MediaRecorder.AudioSource.UNPROCESSED; 122 private final int mChannelConfig = AudioFormat.CHANNEL_IN_MONO; 123 private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; 124 private Thread mRecordThread; 125 126 PipeShort mPipe = new PipeShort(65536); 127 128 SoundPlayerObject mSPlayer; 129 130 private boolean mSupportsUnprocessed = false; 131 132 private DspBufferComplex mC; 133 private DspBufferDouble mData; 134 135 private DspWindow mWindow; 136 private DspFftServer mFftServer; 137 private VectorAverage mFreqAverageTone = new VectorAverage(); 138 private VectorAverage mFreqAverageNoise = new VectorAverage(); 139 private VectorAverage mFreqAverageUsbBackground = new VectorAverage(); 140 private VectorAverage mFreqAverageUsbNoise = new VectorAverage(); 141 142 //RMS for tone: 143 private double mRMS; 144 private double mRMSMax; 145 146 private double mRMSTone; 147 private double mRMSMaxTone; 148 149 int mBands = 3; 150 int mBandsTone = 3; 151 int mBandsBack = 3; 152 AudioBandSpecs[] mBandSpecsMic = new AudioBandSpecs[mBands]; 153 AudioBandSpecs[] mBandSpecsTone = new AudioBandSpecs[mBandsTone]; 154 AudioBandSpecs[] mBandSpecsBack = new AudioBandSpecs[mBandsBack]; 155 private Results mResultsMic; 156 private Results mResultsTone; 157 private Results mResultsBack; 158 159 @Override onCreate(Bundle savedInstanceState)160 protected void onCreate(Bundle savedInstanceState) { 161 super.onCreate(savedInstanceState); 162 setContentView(R.layout.audio_frequency_unprocessed_activity); 163 mTextViewUnprocessedStatus = (TextView) findViewById( 164 R.id.audio_frequency_unprocessed_defined); 165 //unprocessed test 166 mSupportsUnprocessed = supportsUnprocessed(); 167 if (mSupportsUnprocessed) { 168 mTextViewUnprocessedStatus.setText( 169 getResources().getText(R.string.audio_frequency_unprocessed_defined)); 170 } else { 171 mTextViewUnprocessedStatus.setText( 172 getResources().getText(R.string.audio_frequency_unprocessed_not_defined)); 173 } 174 175 mSPlayer = new SoundPlayerObject(); 176 playerSetSource(SOURCE_TONE); 177 178 // Test tone 179 mButtonTestTone = (Button) findViewById(R.id.unprocessed_test_tone_btn); 180 mButtonTestTone.setOnClickListener(mBtnClickListener); 181 mProgressTone = (ProgressBar) findViewById(R.id.unprocessed_test_tone_progress_bar); 182 mResultTestTone = (TextView) findViewById(R.id.unprocessed_test_tone_result); 183 mButtonPlayTone = (Button) findViewById(R.id.unprocessed_play_tone_btn); 184 mButtonPlayTone.setOnClickListener(mBtnClickListener); 185 showWait(mProgressTone, false); 186 187 //Test Noise 188 mButtonTestNoise = (Button) findViewById(R.id.unprocessed_test_noise_btn); 189 mButtonTestNoise.setOnClickListener(mBtnClickListener); 190 mProgressNoise = (ProgressBar) findViewById(R.id.unprocessed_test_noise_progress_bar); 191 mResultTestNoise = (TextView) findViewById(R.id.unprocessed_test_noise_result); 192 mButtonPlayNoise = (Button) findViewById(R.id.unprocessed_play_noise_btn); 193 mButtonPlayNoise.setOnClickListener(mBtnClickListener); 194 showWait(mProgressNoise, false); 195 196 //USB Background 197 mButtonTestUsbBackground = (Button) findViewById(R.id.unprocessed_test_usb_background_btn); 198 mButtonTestUsbBackground.setOnClickListener(mBtnClickListener); 199 mProgressUsbBackground = (ProgressBar) 200 findViewById(R.id.unprocessed_test_usb_background_progress_bar); 201 mResultTestUsbBackground = (TextView) 202 findViewById(R.id.unprocessed_test_usb_background_result); 203 showWait(mProgressUsbBackground, false); 204 205 mButtonTestUsbNoise = (Button) findViewById(R.id.unprocessed_test_usb_noise_btn); 206 mButtonTestUsbNoise.setOnClickListener(mBtnClickListener); 207 mProgressUsbNoise = (ProgressBar)findViewById(R.id.unprocessed_test_usb_noise_progress_bar); 208 mResultTestUsbNoise = (TextView) findViewById(R.id.unprocessed_test_usb_noise_result); 209 mButtonPlayUsbNoise = (Button) findViewById(R.id.unprocessed_play_usb_noise_btn); 210 mButtonPlayUsbNoise.setOnClickListener(mBtnClickListener); 211 showWait(mProgressUsbNoise, false); 212 213 setButtonPlayStatus(-1); 214 mGlobalResultText = (TextView) findViewById(R.id.unprocessed_test_global_result); 215 216 //Init FFT stuff 217 mAudioShortArray2 = new short[mBlockSizeSamples*2]; 218 mData = new DspBufferDouble(mBlockSizeSamples); 219 mC = new DspBufferComplex(mBlockSizeSamples); 220 mFftServer = new DspFftServer(mBlockSizeSamples); 221 222 int overlap = mBlockSizeSamples / 2; 223 224 mWindow = new DspWindow(DspWindow.WINDOW_HANNING, mBlockSizeSamples, overlap); 225 226 setPassFailButtonClickListeners(); 227 getPassButton().setEnabled(false); 228 setInfoResources(R.string.audio_frequency_unprocessed_test, 229 R.string.audio_frequency_unprocessed_info, -1); 230 231 //Init bands for Mic test 232 mBandSpecsMic[0] = new AudioBandSpecs( 233 30, 100, /* frequency start,stop */ 234 20.0, -20.0, /* start top,bottom value */ 235 20.0, -20.0 /* stop top,bottom value */); 236 237 mBandSpecsMic[1] = new AudioBandSpecs( 238 100, 7000, /* frequency start,stop */ 239 10.0, -10.0, /* start top,bottom value */ 240 10.0, -10.0 /* stop top,bottom value */); 241 242 mBandSpecsMic[2] = new AudioBandSpecs( 243 7000, 20000, /* frequency start,stop */ 244 30.0, -30.0, /* start top,bottom value */ 245 30.0, -30.0 /* stop top,bottom value */); 246 247 //Init bands for Tone test 248 mBandSpecsTone[0] = new AudioBandSpecs( 249 30, 900, /* frequency start,stop */ 250 -10.0, -100.0, /* start top,bottom value */ 251 -10.0, -100.0 /* stop top,bottom value */); 252 253 mBandSpecsTone[1] = new AudioBandSpecs( 254 900, 1100, /* frequency start,stop */ 255 10.0, -50.0, /* start top,bottom value */ 256 10.0, -10.0 /* stop top,bottom value */); 257 258 mBandSpecsTone[2] = new AudioBandSpecs( 259 1100, 20000, /* frequency start,stop */ 260 -30.0, -120.0, /* start top,bottom value */ 261 -30.0, -120.0 /* stop top,bottom value */); 262 263 //Init bands for Background test 264 mBandSpecsBack[0] = new AudioBandSpecs( 265 30, 100, /* frequency start,stop */ 266 10.0, -120.0, /* start top,bottom value */ 267 -10.0, -120.0 /* stop top,bottom value */); 268 269 mBandSpecsBack[1] = new AudioBandSpecs( 270 100, 7000, /* frequency start,stop */ 271 -10.0, -120.0, /* start top,bottom value */ 272 -50.0, -120.0 /* stop top,bottom value */); 273 274 mBandSpecsBack[2] = new AudioBandSpecs( 275 7000, 20000, /* frequency start,stop */ 276 -50.0, -120.0, /* start top,bottom value */ 277 -50.0, -120.0 /* stop top,bottom value */); 278 279 mSupportsUnprocessed = supportsUnprocessed(); 280 Log.v(TAG, "Supports unprocessed: " + mSupportsUnprocessed); 281 282 mResultsMic = new Results("mic_response", mBands); 283 mResultsTone = new Results("tone_response", mBandsTone); 284 mResultsBack = new Results("background_response", mBandsBack); 285 286 connectRefMicUI(); 287 } 288 289 // 290 // Overrides 291 // enableTestUI(boolean enable)292 void enableTestUI(boolean enable) { 293 mButtonTestTone.setEnabled(enable); 294 mButtonPlayTone.setEnabled(enable); 295 296 mButtonTestNoise.setEnabled(enable); 297 mButtonPlayNoise.setEnabled(enable); 298 299 mButtonTestUsbBackground.setEnabled(enable); 300 301 mButtonTestUsbNoise.setEnabled(enable); 302 mButtonPlayUsbNoise.setEnabled(enable); 303 } 304 playerToggleButton(int buttonId, int sourceId)305 private void playerToggleButton(int buttonId, int sourceId) { 306 if (playerIsPlaying()) { 307 playerStopAll(); 308 } else { 309 playerSetSource(sourceId); 310 playerTransport(true); 311 setButtonPlayStatus(buttonId); 312 } 313 } 314 315 private class OnBtnClickListener implements OnClickListener { 316 @Override onClick(View v)317 public void onClick(View v) { 318 int id = v.getId(); 319 if (id == R.id.unprocessed_test_tone_btn) { 320 startTest(TEST_TONE); 321 } else if (id == R.id.unprocessed_play_tone_btn) { 322 playerToggleButton(id, SOURCE_TONE); 323 } else if (id == R.id.unprocessed_test_noise_btn) { 324 startTest(TEST_NOISE); 325 } else if (id == R.id.unprocessed_play_noise_btn) { 326 playerToggleButton(id, SOURCE_NOISE); 327 } else if (id == R.id.unprocessed_test_usb_background_btn) { 328 startTest(TEST_USB_BACKGROUND); 329 } else if (id == R.id.unprocessed_test_usb_noise_btn) { 330 startTest(TEST_USB_NOISE); 331 } else if (id == R.id.unprocessed_play_usb_noise_btn) { 332 playerToggleButton(id, SOURCE_NOISE); 333 } 334 } 335 } 336 setButtonPlayStatus(int playResId)337 private void setButtonPlayStatus(int playResId) { 338 String play = getResources().getText(R.string.unprocessed_play).toString(); 339 String stop = getResources().getText(R.string.unprocessed_stop).toString(); 340 341 mButtonPlayTone.setText(playResId == R.id.unprocessed_play_tone_btn ? stop : play); 342 mButtonPlayNoise.setText(playResId == R.id.unprocessed_play_noise_btn ? stop : play); 343 mButtonPlayUsbNoise.setText(playResId == 344 R.id.unprocessed_play_usb_noise_btn ? stop : play); 345 } 346 playerSetSource(int sourceIndex)347 private void playerSetSource(int sourceIndex) { 348 switch (sourceIndex) { 349 case SOURCE_TONE: 350 mSPlayer.setSoundWithResId(mContext, R.raw.onekhztone); 351 break; 352 default: 353 case SOURCE_NOISE: 354 mSPlayer.setSoundWithResId(mContext, 355 R.raw.stereo_mono_white_noise_48); 356 break; 357 } 358 } 359 playerTransport(boolean play)360 private void playerTransport(boolean play) { 361 if (!mSPlayer.isAlive()) { 362 mSPlayer.start(); 363 } 364 mSPlayer.play(play); 365 } 366 playerIsPlaying()367 private boolean playerIsPlaying() { 368 return mSPlayer.isPlaying(); 369 } 370 playerStopAll()371 private void playerStopAll() { 372 if (mSPlayer.isAlive() && mSPlayer.isPlaying()) { 373 mSPlayer.play(false); 374 setButtonPlayStatus(-1); 375 } 376 } 377 showWait(ProgressBar pb, boolean show)378 private void showWait(ProgressBar pb, boolean show) { 379 if (show) { 380 pb.setVisibility(View.VISIBLE); 381 } else { 382 pb.setVisibility(View.INVISIBLE); 383 } 384 } 385 getTestString(int testId)386 private String getTestString(int testId) { 387 String name = "undefined"; 388 switch(testId) { 389 case TEST_TONE: 390 name = "BuiltIn_tone"; 391 break; 392 case TEST_NOISE: 393 name = "BuiltIn_noise"; 394 break; 395 case TEST_USB_BACKGROUND: 396 name = "USB_background"; 397 break; 398 case TEST_USB_NOISE: 399 name = "USB_noise"; 400 break; 401 } 402 return name; 403 } 404 showWait(int testId, boolean show)405 private void showWait(int testId, boolean show) { 406 switch(testId) { 407 case TEST_TONE: 408 showWait(mProgressTone, show); 409 break; 410 case TEST_NOISE: 411 showWait(mProgressNoise, show); 412 break; 413 case TEST_USB_BACKGROUND: 414 showWait(mProgressUsbBackground, show); 415 break; 416 case TEST_USB_NOISE: 417 showWait(mProgressUsbNoise, show); 418 break; 419 } 420 } 421 showMessage(int testId, String msg)422 private void showMessage(int testId, String msg) { 423 if (msg != null && msg.length() > 0) { 424 switch(testId) { 425 case TEST_TONE: 426 mResultTestTone.setText(msg); 427 break; 428 case TEST_NOISE: 429 mResultTestNoise.setText(msg); 430 break; 431 case TEST_USB_BACKGROUND: 432 mResultTestUsbBackground.setText(msg); 433 break; 434 case TEST_USB_NOISE: 435 mResultTestUsbNoise.setText(msg); 436 break; 437 } 438 } 439 } 440 supportsUnprocessed()441 private boolean supportsUnprocessed() { 442 boolean unprocessedSupport = false; 443 String unprocessedSupportString = mAudioManager.getProperty( 444 AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED); 445 Log.v(TAG,"unprocessed support: " + unprocessedSupportString); 446 if (unprocessedSupportString == null || 447 unprocessedSupportString.equalsIgnoreCase(getResources().getString( 448 R.string.audio_general_default_false_string))) { 449 unprocessedSupport = false; 450 } else { 451 unprocessedSupport = true; 452 } 453 return unprocessedSupport; 454 } 455 computeAllResults()456 private void computeAllResults() { 457 StringBuilder sb = new StringBuilder(); 458 459 boolean allDone = true; 460 461 for (int i=0; i<TEST_COUNT; i++) { 462 allDone = allDone & mTestsDone[i]; 463 sb.append(String.format("%s : %s\n", getTestString(i), 464 mTestsDone[i] ? "DONE" :" NOT DONE")); 465 } 466 467 if (allDone) { 468 sb.append(computeResults()); 469 } else { 470 sb.append("Please execute all tests for results\n"); 471 } 472 mGlobalResultText.setText(sb.toString()); 473 } 474 processSpectrum(Results results, AudioBandSpecs[] bandsSpecs, int anchorBand)475 private void processSpectrum(Results results, AudioBandSpecs[] bandsSpecs, int anchorBand) { 476 int points = results.mValuesLog.length; 477 int bandCount = bandsSpecs.length; 478 int currentBand = 0; 479 for (int i = 0; i < points; i++) { 480 double freq = (double)mSamplingRate * i / (double)mBlockSizeSamples; 481 if (freq > bandsSpecs[currentBand].mFreqStop) { 482 currentBand++; 483 if (currentBand >= bandCount) 484 break; 485 } 486 487 if (freq >= bandsSpecs[currentBand].mFreqStart) { 488 results.mAverageEnergyPerBand[currentBand] += results.mValuesLog[i]; 489 results.mPointsPerBand[currentBand]++; 490 } 491 } 492 493 for (int b = 0; b < bandCount; b++) { 494 if (results.mPointsPerBand[b] > 0) { 495 results.mAverageEnergyPerBand[b] = 496 results.mAverageEnergyPerBand[b] / results.mPointsPerBand[b]; 497 } 498 } 499 500 //set offset relative to band anchor band level 501 for (int b = 0; b < bandCount; b++) { 502 if (anchorBand > -1 && anchorBand < bandCount) { 503 bandsSpecs[b].setOffset(results.mAverageEnergyPerBand[anchorBand]); 504 } else { 505 bandsSpecs[b].setOffset(0); 506 } 507 } 508 509 //test points in band. 510 currentBand = 0; 511 for (int i = 0; i < points; i++) { 512 double freq = (double) mSamplingRate * i / (double) mBlockSizeSamples; 513 if (freq > bandsSpecs[currentBand].mFreqStop) { 514 currentBand++; 515 if (currentBand >= bandCount) 516 break; 517 } 518 519 if (freq >= bandsSpecs[currentBand].mFreqStart) { 520 double value = results.mValuesLog[i]; 521 if (bandsSpecs[currentBand].isInBounds(freq, value)) { 522 results.mInBoundPointsPerBand[currentBand]++; 523 } 524 } 525 } 526 } 527 computeResults()528 private String computeResults() { 529 StringBuilder sb = new StringBuilder(); 530 531 int points = mFreqAverageNoise.getSize(); 532 //mFreqAverageNoise size is determined by the latest data writen to it. 533 //Currently, this data is always mBlockSizeSamples/2. 534 if (points < 1) { 535 return "error: not enough points"; 536 } 537 538 double[] tone = new double[points]; 539 double[] noise = new double[points]; 540 double[] reference = new double[points]; 541 double[] background = new double[points]; 542 543 mFreqAverageTone.getData(tone, false); 544 mFreqAverageNoise.getData(noise, false); 545 mFreqAverageUsbNoise.getData(reference, false); 546 mFreqAverageUsbBackground.getData(background, false); 547 548 //Convert to dB 549 double[] toneDb = new double[points]; 550 double[] noiseDb = new double[points]; 551 double[] referenceDb = new double[points]; 552 double[] backgroundDb = new double[points]; 553 554 double[] compensatedNoiseDb = new double[points]; 555 556 for (int i = 0; i < points; i++) { 557 toneDb[i] = 20 * Math.log10(tone[i]); 558 noiseDb[i] = 20 * Math.log10(noise[i]); 559 referenceDb[i] = 20 * Math.log10(reference[i]); 560 backgroundDb[i] = 20 * Math.log10(background[i]); 561 562 compensatedNoiseDb[i] = noiseDb[i] - referenceDb[i]; 563 } 564 565 mResultsMic.reset(); 566 mResultsTone.reset(); 567 mResultsBack.reset(); 568 569 mResultsMic.mValuesLog = compensatedNoiseDb; 570 mResultsTone.mValuesLog = toneDb; 571 mResultsBack.mValuesLog = backgroundDb; 572 573 processSpectrum(mResultsMic, mBandSpecsMic, 1); 574 processSpectrum(mResultsTone, mBandSpecsTone, 1); 575 processSpectrum(mResultsBack, mBandSpecsBack, -1); //no reference for offset 576 577 //Tone test 578 boolean toneTestSuccess = true; 579 { 580 //rms level should be -36 dbfs +/- 3 db? 581 double rmsMaxDb = 20 * Math.log10(mRMSMaxTone); 582 sb.append(String.format("RMS level of tone: %.2f dBFS\n", rmsMaxDb)); 583 sb.append(String.format("Target RMS level: %.2f dBFS +/- %.2f dB\n", 584 TONE_RMS_EXPECTED, 585 TONE_RMS_MAX_ERROR)); 586 if (Math.abs(rmsMaxDb - TONE_RMS_EXPECTED) > TONE_RMS_MAX_ERROR) { 587 toneTestSuccess = false; 588 sb.append("RMS level test FAILED\n"); 589 } else { 590 sb.append(" RMS level test SUCCESSFUL\n"); 591 } 592 //check the spectrum is really a tone around 1 khz 593 } 594 595 sb.append("\n"); 596 sb.append(mResultsTone.toString()); 597 if (mResultsTone.testAll()) { 598 sb.append(" 1 Khz Tone Frequency Response Test SUCCESSFUL\n"); 599 } else { 600 sb.append(" 1 Khz Tone Frequency Response Test FAILED\n"); 601 } 602 sb.append("\n"); 603 604 sb.append("\n"); 605 sb.append(mResultsBack.toString()); 606 if (mResultsBack.testAll()) { 607 sb.append(" Background environment Test SUCCESSFUL\n"); 608 } else { 609 sb.append(" Background environment Test FAILED\n"); 610 } 611 612 sb.append("\n"); 613 sb.append(mResultsMic.toString()); 614 if (mResultsMic.testAll()) { 615 sb.append(" Frequency Response Test SUCCESSFUL\n"); 616 } else { 617 sb.append(" Frequency Response Test FAILED\n"); 618 } 619 sb.append("\n"); 620 621 storeTestResults(mResultsTone); 622 storeTestResults(mResultsMic); 623 624 boolean allTestsPassed = false; 625 if (mResultsMic.testAll() && mResultsTone.testAll() && toneTestSuccess && 626 mResultsBack.testAll()) { 627 allTestsPassed = true; 628 String strSuccess = getResources().getString(R.string.audio_general_test_passed); 629 sb.append(strSuccess); 630 } else { 631 String strFailed = getResources().getString(R.string.audio_general_test_failed); 632 sb.append(strFailed); 633 } 634 635 sb.append("\n"); 636 if (mSupportsUnprocessed) { //test is mandatory 637 sb.append(getResources().getText( 638 R.string.audio_frequency_unprocessed_defined).toString()); 639 if (allTestsPassed) { 640 getPassButton().setEnabled(true); 641 } else { 642 getPassButton().setEnabled(false); 643 } 644 } else { 645 //test optional 646 sb.append(getResources().getText( 647 R.string.audio_frequency_unprocessed_not_defined).toString()); 648 getPassButton().setEnabled(true); 649 } 650 return sb.toString(); 651 } 652 653 Thread mTestThread; startTest(int testId)654 private void startTest(int testId) { 655 if (mTestThread != null && !mTestThread.isAlive()) { 656 mTestThread = null; //kill it. 657 } 658 659 if (mTestThread == null) { 660 mRMS = 0; 661 mRMSMax = 0; 662 Log.v(TAG,"Executing test Thread"); 663 switch(testId) { 664 case TEST_TONE: 665 mTestThread = new Thread(new TestRunnable(TEST_TONE) { 666 public void run() { 667 super.run(); 668 if (!mUsbMicConnected) { 669 sendMessage(mTestId, TEST_MESSAGE, 670 "Testing Built in Microphone: Tone"); 671 mRMSTone = 0; 672 mRMSMaxTone = 0; 673 mFreqAverageTone.reset(); 674 mFreqAverageTone.setCaptureType(VectorAverage.CAPTURE_TYPE_MAX); 675 record(TEST_DURATION_TONE); 676 sendMessage(mTestId, TEST_ENDED, "Testing Completed"); 677 mTestsDone[mTestId] = true; 678 } else { 679 sendMessage(mTestId, TEST_ENDED_ERROR, 680 "Please Unplug USB Microphone"); 681 mTestsDone[mTestId] = false; 682 } 683 } 684 }); 685 break; 686 case TEST_NOISE: 687 mTestThread = new Thread(new TestRunnable(TEST_NOISE) { 688 public void run() { 689 super.run(); 690 if (!mUsbMicConnected) { 691 sendMessage(mTestId, TEST_MESSAGE, 692 "Testing Built in Microphone: Noise"); 693 mFreqAverageNoise.reset(); 694 mFreqAverageNoise.setCaptureType(VectorAverage.CAPTURE_TYPE_MAX); 695 record(TEST_DURATION_NOISE); 696 sendMessage(mTestId, TEST_ENDED, "Testing Completed"); 697 mTestsDone[mTestId] = true; 698 } else { 699 sendMessage(mTestId, TEST_ENDED_ERROR, 700 "Please Unplug USB Microphone"); 701 mTestsDone[mTestId] = false; 702 } 703 } 704 }); 705 break; 706 case TEST_USB_BACKGROUND: 707 playerStopAll(); 708 mTestThread = new Thread(new TestRunnable(TEST_USB_BACKGROUND) { 709 public void run() { 710 super.run(); 711 if (mUsbMicConnected) { 712 sendMessage(mTestId, TEST_MESSAGE, 713 "Testing USB Microphone: background"); 714 mFreqAverageUsbBackground.reset(); 715 mFreqAverageUsbBackground.setCaptureType( 716 VectorAverage.CAPTURE_TYPE_AVERAGE); 717 record(TEST_DURATION_USB_BACKGROUND); 718 sendMessage(mTestId, TEST_ENDED, "Testing Completed"); 719 mTestsDone[mTestId] = true; 720 } else { 721 sendMessage(mTestId, TEST_ENDED_ERROR, 722 "USB Microphone not detected."); 723 mTestsDone[mTestId] = false; 724 } 725 } 726 }); 727 break; 728 case TEST_USB_NOISE: 729 mTestThread = new Thread(new TestRunnable(TEST_USB_NOISE) { 730 public void run() { 731 super.run(); 732 if (mUsbMicConnected) { 733 sendMessage(mTestId, TEST_MESSAGE, "Testing USB Microphone: Noise"); 734 mFreqAverageUsbNoise.reset(); 735 mFreqAverageUsbNoise.setCaptureType(VectorAverage.CAPTURE_TYPE_MAX); 736 record(TEST_DURATION_USB_NOISE); 737 sendMessage(mTestId, TEST_ENDED, "Testing Completed"); 738 mTestsDone[mTestId] = true; 739 } else { 740 sendMessage(mTestId, TEST_ENDED_ERROR, 741 "USB Microphone not detected."); 742 mTestsDone[mTestId] = false; 743 } 744 } 745 }); 746 break; 747 } 748 mTestThread.start(); 749 } else { 750 Log.v(TAG,"test Thread already running."); 751 } 752 } 753 public class TestRunnable implements Runnable { 754 public int mTestId; 755 public boolean mUsbMicConnected; TestRunnable(int testId)756 TestRunnable(int testId) { 757 Log.v(TAG,"New TestRunnable"); 758 mTestId = testId; 759 } run()760 public void run() { 761 mCurrentTest = mTestId; 762 sendMessage(mTestId, TEST_STARTED,""); 763 mUsbMicConnected = 764 UsbMicrophoneTester.getIsMicrophoneConnected(mContext); 765 }; record(int durationMs)766 public void record(int durationMs) { 767 startRecording(); 768 try { 769 Thread.sleep(durationMs); 770 } catch (InterruptedException e) { 771 e.printStackTrace(); 772 //restore interrupted status 773 Thread.currentThread().interrupt(); 774 } 775 stopRecording(); 776 } sendMessage(int testId, int msgType, String str)777 public void sendMessage(int testId, int msgType, String str) { 778 Message msg = Message.obtain(); 779 msg.what = msgType; 780 msg.obj = str; 781 msg.arg1 = testId; 782 mMessageHandler.sendMessage(msg); 783 } 784 } 785 786 private Handler mMessageHandler = new Handler() { 787 public void handleMessage(Message msg) { 788 super.handleMessage(msg); 789 int testId = msg.arg1; //testId 790 String str = (String) msg.obj; 791 switch (msg.what) { 792 case TEST_STARTED: 793 showWait(testId, true); 794 // getPassButton().setEnabled(false); 795 break; 796 case TEST_MESSAGE: 797 showMessage(testId, str); 798 break; 799 case TEST_ENDED: 800 showWait(testId, false); 801 playerStopAll(); 802 showMessage(testId, str); 803 appendResultsToScreen(testId, "test finished"); 804 computeAllResults(); 805 break; 806 case TEST_ENDED_ERROR: 807 showWait(testId, false); 808 playerStopAll(); 809 showMessage(testId, str); 810 computeAllResults(); 811 default: 812 Log.e(TAG, String.format("Unknown message: %d", msg.what)); 813 } 814 } 815 }; 816 817 private class Results { 818 private int mBandCount; 819 private String mLabel; 820 public double[] mValuesLog; 821 int[] mPointsPerBand; // = new int[mBands]; 822 double[] mAverageEnergyPerBand;// = new double[mBands]; 823 int[] mInBoundPointsPerBand;// = new int[mBands]; Results(String label, int bandCount)824 public Results(String label, int bandCount) { 825 mLabel = label; 826 mBandCount = bandCount; 827 mPointsPerBand = new int[mBandCount]; 828 mAverageEnergyPerBand = new double[mBandCount]; 829 mInBoundPointsPerBand = new int[mBandCount]; 830 } reset()831 public void reset() { 832 for (int i = 0; i < mBandCount; i++) { 833 mPointsPerBand[i] = 0; 834 mAverageEnergyPerBand[i] = 0; 835 mInBoundPointsPerBand[i] = 0; 836 } 837 } 838 839 //append results toString()840 public String toString() { 841 StringBuilder sb = new StringBuilder(); 842 sb.append(String.format("Channel %s\n", mLabel)); 843 for (int b = 0; b < mBandCount; b++) { 844 double percent = 0; 845 if (mPointsPerBand[b] > 0) { 846 percent = 100.0 * (double) mInBoundPointsPerBand[b] / mPointsPerBand[b]; 847 } 848 sb.append(String.format( 849 " Band %d: Av. Level: %.1f dB InBand: %d/%d (%.1f%%) %s\n", 850 b, mAverageEnergyPerBand[b], 851 mInBoundPointsPerBand[b], 852 mPointsPerBand[b], 853 percent, 854 (testInBand(b) ? "OK" : "Not Optimal"))); 855 } 856 return sb.toString(); 857 } 858 testInBand(int b)859 public boolean testInBand(int b) { 860 if (b >= 0 && b < mBandCount && mPointsPerBand[b] > 0) { 861 if ((double) mInBoundPointsPerBand[b] / mPointsPerBand[b] > 862 MIN_FRACTION_POINTS_IN_BAND) { 863 return true; 864 } 865 } 866 return false; 867 } 868 testAll()869 public boolean testAll() { 870 for (int b = 0; b < mBandCount; b++) { 871 if (!testInBand(b)) { 872 return false; 873 } 874 } 875 return true; 876 } 877 } 878 879 880 // /** 881 // * compute test results 882 // */ 883 // private void computeTestResults(int testId) { 884 // String testName = getTestString(testId); 885 // appendResultsToScreen(testId, "test finished"); 886 // } 887 888 //append results appendResultsToScreen(String str, TextView text)889 private void appendResultsToScreen(String str, TextView text) { 890 String currentText = text.getText().toString(); 891 text.setText(currentText + "\n" + str); 892 } 893 appendResultsToScreen(int testId, String str)894 private void appendResultsToScreen(int testId, String str) { 895 switch(testId) { 896 case TEST_TONE: 897 appendResultsToScreen(str, mResultTestTone); 898 showToneRMS(); 899 break; 900 case TEST_NOISE: 901 appendResultsToScreen(str, mResultTestNoise); 902 break; 903 case TEST_USB_BACKGROUND: 904 appendResultsToScreen(str, mResultTestUsbBackground); 905 break; 906 case TEST_USB_NOISE: 907 appendResultsToScreen(str, mResultTestUsbNoise); 908 break; 909 } 910 } 911 912 /** 913 * Store test results in log 914 */ 915 private static final String SECTION_AUDIOFREQUENCYUNPROCESSED = 916 "audio_frequency_unprocessed"; 917 @Override getReportSectionName()918 public final String getReportSectionName() { 919 return setTestNameSuffix(sCurrentDisplayMode, SECTION_AUDIOFREQUENCYUNPROCESSED); 920 } 921 storeTestResults(Results results)922 private void storeTestResults(Results results) { 923 String channelLabel = "channel_" + results.mLabel; 924 925 CtsVerifierReportLog reportLog = getReportLog(); 926 for (int b = 0; b < mBands; b++) { 927 String bandLabel = String.format(channelLabel + "_%d", b); 928 reportLog.addValue( 929 bandLabel + "_Level", 930 results.mAverageEnergyPerBand[b], 931 ResultType.HIGHER_BETTER, 932 ResultUnit.NONE); 933 934 reportLog.addValue( 935 bandLabel + "_pointsinbound", 936 results.mInBoundPointsPerBand[b], 937 ResultType.HIGHER_BETTER, 938 ResultUnit.COUNT); 939 940 reportLog.addValue( 941 bandLabel + "_pointstotal", 942 results.mPointsPerBand[b], 943 ResultType.NEUTRAL, 944 ResultUnit.COUNT); 945 } 946 947 reportLog.addValues(channelLabel + "_magnitudeSpectrumLog", 948 results.mValuesLog, 949 ResultType.NEUTRAL, 950 ResultUnit.NONE); 951 952 Log.v(TAG, "Results Stored"); 953 } 954 955 @Override // PassFailButtons recordTestResults()956 public void recordTestResults() { 957 getReportLog().submit(); 958 } 959 recordHeasetPortFound(boolean found)960 private void recordHeasetPortFound(boolean found) { 961 getReportLog().addValue( 962 "User Reported Headset Port", 963 found ? 1.0 : 0, 964 ResultType.NEUTRAL, 965 ResultUnit.NONE); 966 } 967 startRecording()968 private void startRecording() { 969 synchronized (mRecordingLock) { 970 mIsRecording = true; 971 } 972 973 boolean successful = initRecord(); 974 if (successful) { 975 startRecordingForReal(); 976 } else { 977 Log.v(TAG, "Recorder initialization error."); 978 synchronized (mRecordingLock) { 979 mIsRecording = false; 980 } 981 } 982 } 983 startRecordingForReal()984 private void startRecordingForReal() { 985 // start streaming 986 if (mRecordThread == null) { 987 mRecordThread = new Thread(AudioFrequencyUnprocessedActivity.this); 988 mRecordThread.setName("FrequencyAnalyzerThread"); 989 } 990 if (!mRecordThread.isAlive()) { 991 mRecordThread.start(); 992 } 993 994 mPipe.flush(); 995 996 long startTime = SystemClock.uptimeMillis(); 997 mRecorder.startRecording(); 998 if (mRecorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { 999 stopRecording(); 1000 return; 1001 } 1002 Log.v(TAG, "Start time: " + (long) (SystemClock.uptimeMillis() - startTime) + " ms"); 1003 } 1004 stopRecording()1005 private void stopRecording() { 1006 synchronized (mRecordingLock) { 1007 stopRecordingForReal(); 1008 mIsRecording = false; 1009 } 1010 } 1011 stopRecordingForReal()1012 private void stopRecordingForReal() { 1013 1014 // stop streaming 1015 Thread zeThread = mRecordThread; 1016 mRecordThread = null; 1017 if (zeThread != null) { 1018 zeThread.interrupt(); 1019 try { 1020 zeThread.join(); 1021 } catch(InterruptedException e) { 1022 //restore interrupted status of recording thread 1023 zeThread.interrupt(); 1024 } 1025 } 1026 // release recording resources 1027 if (mRecorder != null) { 1028 mRecorder.stop(); 1029 mRecorder.release(); 1030 mRecorder = null; 1031 } 1032 } 1033 initRecord()1034 private boolean initRecord() { 1035 int minRecordBuffSizeInBytes = AudioRecord.getMinBufferSize(mSamplingRate, 1036 mChannelConfig, mAudioFormat); 1037 Log.v(TAG,"FrequencyAnalyzer: min buff size = " + minRecordBuffSizeInBytes + " bytes"); 1038 if (minRecordBuffSizeInBytes <= 0) { 1039 return false; 1040 } 1041 1042 mMinRecordBufferSizeInSamples = minRecordBuffSizeInBytes / 2; 1043 // allocate the byte array to read the audio data 1044 1045 mAudioShortArray = new short[mMinRecordBufferSizeInSamples]; 1046 1047 Log.v(TAG, "Initiating record:"); 1048 Log.v(TAG, " using source " + mSelectedRecordSource); 1049 Log.v(TAG, " at " + mSamplingRate + "Hz"); 1050 1051 try { 1052 mRecorder = new AudioRecord(mSelectedRecordSource, mSamplingRate, 1053 mChannelConfig, mAudioFormat, 2 * minRecordBuffSizeInBytes); 1054 } catch (IllegalArgumentException e) { 1055 return false; 1056 } 1057 if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { 1058 mRecorder.release(); 1059 mRecorder = null; 1060 return false; 1061 } 1062 mRecorder.setRecordPositionUpdateListener(this); 1063 mRecorder.setPositionNotificationPeriod(mBlockSizeSamples / 2); 1064 return true; 1065 } showToneRMS()1066 private void showToneRMS() { 1067 String str = String.format("RMS: %.3f dBFS. Max RMS: %.3f dBFS", 1068 20 * Math.log10(mRMSTone), 1069 20 * Math.log10(mRMSMaxTone)); 1070 showMessage(TEST_TONE, str); 1071 } 1072 1073 // --------------------------------------------------------- 1074 // Implementation of AudioRecord.OnPeriodicNotificationListener 1075 // -------------------- onPeriodicNotification(AudioRecord recorder)1076 public void onPeriodicNotification(AudioRecord recorder) { 1077 int samplesAvailable = mPipe.availableToRead(); 1078 int samplesNeeded = mBlockSizeSamples; 1079 if (samplesAvailable >= samplesNeeded) { 1080 mPipe.read(mAudioShortArray2, 0, samplesNeeded); 1081 1082 //compute stuff. 1083 int clipcount = 0; 1084 // double sum = 0; 1085 double maxabs = 0; 1086 int i; 1087 double rmsTempSum = 0; 1088 1089 for (i = 0; i < samplesNeeded; i++) { 1090 double value = mAudioShortArray2[i] / MAX_VAL; 1091 double valueabs = Math.abs(value); 1092 1093 if (valueabs > maxabs) { 1094 maxabs = valueabs; 1095 } 1096 1097 if (valueabs > CLIP_LEVEL) { 1098 clipcount++; 1099 } 1100 1101 rmsTempSum += value * value; 1102 //fft stuff 1103 mData.mData[i] = value; 1104 } 1105 double rms = Math.sqrt(rmsTempSum / samplesNeeded); 1106 1107 double alpha = 0.9; 1108 double total_rms = rms * alpha + mRMS *(1-alpha); 1109 mRMS = total_rms; 1110 if (mRMS > mRMSMax) { 1111 mRMSMax = mRMS; 1112 } 1113 1114 //for the current frame, compute FFT and send to the viewer. 1115 1116 //apply window and pack as complex for now. 1117 DspBufferMath.mult(mData, mData, mWindow.mBuffer); 1118 DspBufferMath.set(mC, mData); 1119 mFftServer.fft(mC, 1); 1120 1121 double[] halfMagnitude = new double[mBlockSizeSamples / 2]; 1122 for (i = 0; i < mBlockSizeSamples / 2; i++) { 1123 halfMagnitude[i] = Math.sqrt(mC.mReal[i] * mC.mReal[i] + mC.mImag[i] * mC.mImag[i]); 1124 } 1125 1126 switch(mCurrentTest) { 1127 case TEST_TONE: { 1128 mFreqAverageTone.setData(halfMagnitude, false); 1129 //Update realtime info on screen 1130 mRMSTone = mRMS; 1131 mRMSMaxTone = mRMSMax; 1132 showToneRMS(); 1133 } 1134 break; 1135 case TEST_NOISE: 1136 mFreqAverageNoise.setData(halfMagnitude, false); 1137 break; 1138 case TEST_USB_BACKGROUND: 1139 mFreqAverageUsbBackground.setData(halfMagnitude, false); 1140 break; 1141 case TEST_USB_NOISE: 1142 mFreqAverageUsbNoise.setData(halfMagnitude, false); 1143 break; 1144 } 1145 } 1146 } 1147 onMarkerReached(AudioRecord track)1148 public void onMarkerReached(AudioRecord track) { 1149 } 1150 1151 // --------------------------------------------------------- 1152 // Implementation of Runnable for the audio recording + playback 1153 // -------------------- run()1154 public void run() { 1155 Thread thisThread = Thread.currentThread(); 1156 while (!thisThread.isInterrupted()) { 1157 // read from native recorder 1158 int nSamplesRead = mRecorder.read(mAudioShortArray, 0, mMinRecordBufferSizeInSamples); 1159 if (nSamplesRead > 0) { 1160 mPipe.write(mAudioShortArray, 0, nSamplesRead); 1161 } 1162 } 1163 } 1164 } 1165