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