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