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