• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.media.AudioFormat;
20 import android.media.AudioRecord;
21 import android.media.MediaRecorder;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.SystemClock;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.widget.Button;
30 import android.widget.ProgressBar;
31 import android.widget.TextView;
32 
33 import com.android.compatibility.common.util.ResultType;
34 import com.android.compatibility.common.util.ResultUnit;
35 import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
36 import com.android.cts.verifier.CtsVerifierReportLog;
37 import com.android.cts.verifier.R;
38 import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
39 import com.android.cts.verifier.audio.wavelib.DspBufferDouble;
40 import com.android.cts.verifier.audio.wavelib.DspBufferMath;
41 import com.android.cts.verifier.audio.wavelib.DspFftServer;
42 import com.android.cts.verifier.audio.wavelib.DspWindow;
43 import com.android.cts.verifier.audio.wavelib.PipeShort;
44 import com.android.cts.verifier.audio.wavelib.VectorAverage;
45 
46 /**
47  * Tests Audio Device roundtrip latency by using a loopback plug.
48  */
49 public class AudioFrequencyLineActivity extends AudioFrequencyActivity implements Runnable,
50     AudioRecord.OnRecordPositionUpdateListener {
51     private static final String TAG = "AudioFrequencyLineActivity";
52 
53     static final int TEST_STARTED = 900;
54     static final int TEST_ENDED = 901;
55     static final int TEST_MESSAGE = 902;
56     static final double MIN_ENERGY_BAND_1 = -20.0;
57     static final double MIN_FRACTION_POINTS_IN_BAND = 0.3;
58 
59     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
60 
61     Button mWiredPortYes;
62     Button mWiredPortNo;
63 
64     Button mLoopbackPlugReady;
65     Button mTestButton;
66     TextView mResultText;
67     ProgressBar mProgressBar;
68     //recording
69     private boolean mIsRecording = false;
70     private final Object mRecordingLock = new Object();
71     private AudioRecord mRecorder;
72     private int mMinRecordBufferSizeInSamples = 0;
73     private short[] mAudioShortArray;
74     private short[] mAudioShortArray2;
75 
76     private final int mBlockSizeSamples = 1024;
77     private final int mSamplingRate = 48000;
78     private final int mSelectedRecordSource = MediaRecorder.AudioSource.UNPROCESSED;
79     private final int mChannelConfig = AudioFormat.CHANNEL_IN_MONO;
80     private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
81     private volatile Thread mRecordThread;
82     private boolean mRecordThreadShutdown = false;
83 
84     PipeShort mPipe = new PipeShort(65536);
85     SoundPlayerObject mSPlayer;
86 
87     private DspBufferComplex mC;
88     private DspBufferDouble mData;
89 
90     private DspWindow mWindow;
91     private DspFftServer mFftServer;
92     private VectorAverage mFreqAverageMain = new VectorAverage();
93 
94     private VectorAverage mFreqAverage0 = new VectorAverage();
95     private VectorAverage mFreqAverage1 = new VectorAverage();
96 
97     private int mCurrentTest = -1;
98     int mBands = 4;
99     AudioBandSpecs[] bandSpecsArray = new AudioBandSpecs[mBands];
100 
101     private class OnBtnClickListener implements OnClickListener {
102         @Override
onClick(View v)103         public void onClick(View v) {
104             int id = v.getId();
105             if (id == R.id.audio_frequency_line_plug_ready_btn) {
106                 Log.i(TAG, "audio loopback plug ready");
107                 //enable all the other views.
108                 enableLayout(R.id.audio_frequency_line_layout, true);
109                 setMaxLevel();
110                 testMaxLevel();
111             } else if (id == R.id.audio_frequency_line_test_btn) {
112                 Log.i(TAG, "audio loopback test");
113                 startAudioTest();
114             } else if (id == R.id.audio_wired_yes) {
115                 Log.i(TAG, "User confirms wired Port existence");
116                 mLoopbackPlugReady.setEnabled(true);
117                 recordHeasetPortFound(true);
118                 mWiredPortYes.setEnabled(false);
119                 mWiredPortNo.setEnabled(false);
120             } else if (id == R.id.audio_wired_no) {
121                 Log.i(TAG, "User denies wired Port existence");
122                 recordHeasetPortFound(false);
123                 getPassButton().setEnabled(true);
124                 mWiredPortYes.setEnabled(false);
125                 mWiredPortNo.setEnabled(false);
126             }
127         }
128     }
129 
130     @Override
onCreate(Bundle savedInstanceState)131     protected void onCreate(Bundle savedInstanceState) {
132         super.onCreate(savedInstanceState);
133         setContentView(R.layout.audio_frequency_line_activity);
134 
135         mWiredPortYes = (Button)findViewById(R.id.audio_wired_yes);
136         mWiredPortYes.setOnClickListener(mBtnClickListener);
137         mWiredPortNo = (Button)findViewById(R.id.audio_wired_no);
138         mWiredPortNo.setOnClickListener(mBtnClickListener);
139 
140         mLoopbackPlugReady = (Button)findViewById(R.id.audio_frequency_line_plug_ready_btn);
141         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
142         mLoopbackPlugReady.setEnabled(false);
143         mTestButton = (Button)findViewById(R.id.audio_frequency_line_test_btn);
144         mTestButton.setOnClickListener(mBtnClickListener);
145         mResultText = (TextView)findViewById(R.id.audio_frequency_line_results_text);
146         mProgressBar = (ProgressBar)findViewById(R.id.audio_frequency_line_progress_bar);
147         showWait(false);
148         enableLayout(R.id.audio_frequency_line_layout, false);         //disabled all content
149 
150         mSPlayer = new SoundPlayerObject();
151         mSPlayer.setSoundWithResId(mContext, R.raw.stereo_mono_white_noise_48);
152         mSPlayer.setBalance(0.5f);
153 
154         //Init FFT stuff
155         mAudioShortArray2 = new short[mBlockSizeSamples*2];
156         mData = new DspBufferDouble(mBlockSizeSamples);
157         mC = new DspBufferComplex(mBlockSizeSamples);
158         mFftServer = new DspFftServer(mBlockSizeSamples);
159 
160         int overlap = mBlockSizeSamples / 2;
161 
162         mWindow = new DspWindow(DspWindow.WINDOW_HANNING, mBlockSizeSamples, overlap);
163 
164         setPassFailButtonClickListeners();
165         getPassButton().setEnabled(false);
166         setInfoResources(R.string.audio_frequency_line_test,
167                 R.string.audio_frequency_line_info, -1);
168 
169         //Init bands
170         bandSpecsArray[0] = new AudioBandSpecs(
171                 50, 500,        /* frequency start,stop */
172                 4.0, -50,     /* start top,bottom value */
173                 4.0, -4.0       /* stop top,bottom value */);
174 
175         bandSpecsArray[1] = new AudioBandSpecs(
176                 500,4000,       /* frequency start,stop */
177                 4.0, -4.0,      /* start top,bottom value */
178                 4.0, -4.0        /* stop top,bottom value */);
179 
180         bandSpecsArray[2] = new AudioBandSpecs(
181                 4000, 12000,    /* frequency start,stop */
182                 4.0, -4.0,      /* start top,bottom value */
183                 5.0, -5.0       /* stop top,bottom value */);
184 
185         bandSpecsArray[3] = new AudioBandSpecs(
186                 12000, 20000,   /* frequency start,stop */
187                 5.0, -5.0,      /* start top,bottom value */
188                 5.0, -30.0      /* stop top,bottom value */);
189     }
190 
191     /**
192      * show active progress bar
193      */
showWait(boolean show)194     private void showWait(boolean show) {
195         if (show) {
196             mProgressBar.setVisibility(View.VISIBLE);
197         } else {
198             mProgressBar.setVisibility(View.INVISIBLE);
199         }
200     }
201 
202     /**
203      *  Start the loopback audio test
204      */
startAudioTest()205     private void startAudioTest() {
206         if (mTestThread != null && !mTestThread.isAlive()) {
207             mTestThread = null; //kill it.
208         }
209 
210         if (mTestThread == null) {
211             Log.v(TAG,"Executing test Thread");
212             mTestThread = new Thread(mPlayRunnable);
213             getPassButton().setEnabled(false);
214             if (!mSPlayer.isAlive())
215                 mSPlayer.start();
216             mTestThread.start();
217         } else {
218             Log.v(TAG,"test Thread already running.");
219         }
220     }
221 
222     Thread mTestThread;
223     Runnable mPlayRunnable = new Runnable() {
224         public void run() {
225             Message msg = Message.obtain();
226             msg.what = TEST_STARTED;
227             mMessageHandler.sendMessage(msg);
228 
229             sendMessage("Testing Left Capture");
230             mCurrentTest = 0;
231             mFreqAverage0.reset();
232             mSPlayer.setBalance(0.0f);
233             play();
234 
235             sendMessage("Testing Right Capture");
236             mCurrentTest = 1;
237             mFreqAverage1.reset();
238             mSPlayer.setBalance(1.0f);
239             play();
240 
241             mCurrentTest = -1;
242             sendMessage("Testing Completed");
243 
244             Message msg2 = Message.obtain();
245             msg2.what = TEST_ENDED;
246             mMessageHandler.sendMessage(msg2);
247         }
248 
249         private void play() {
250             startRecording();
251             mSPlayer.play(true);
252 
253             try {
254                 Thread.sleep(2000);
255             } catch (InterruptedException e) {
256                 e.printStackTrace();
257             }
258 
259             mSPlayer.play(false);
260             stopRecording();
261         }
262 
263         private void sendMessage(String str) {
264             Message msg = Message.obtain();
265             msg.what = TEST_MESSAGE;
266             msg.obj = str;
267             mMessageHandler.sendMessage(msg);
268         }
269     };
270 
271     private Handler mMessageHandler = new Handler() {
272         public void handleMessage(Message msg) {
273             super.handleMessage(msg);
274             switch (msg.what) {
275             case TEST_STARTED:
276                 showWait(true);
277                 getPassButton().setEnabled(false);
278                 break;
279             case TEST_ENDED:
280                 showWait(false);
281                 computeResults();
282                 break;
283             case TEST_MESSAGE:
284                 String str = (String)msg.obj;
285                 if (str != null) {
286                     mResultText.setText(str);
287                 }
288                 break;
289             default:
290                 Log.e(TAG, String.format("Unknown message: %d", msg.what));
291             }
292         }
293     };
294 
295     private class Results {
296         private String mLabel;
297         public double[] mValuesLog;
298         int[] mPointsPerBand = new int[mBands];
299         double[] mAverageEnergyPerBand = new double[mBands];
300         int[] mInBoundPointsPerBand = new int[mBands];
Results(String label)301         public Results(String label) {
302             mLabel = label;
303         }
304 
305         //append results
toString()306         public String toString() {
307             StringBuilder sb = new StringBuilder();
308             sb.append(String.format("Channel %s\n", mLabel));
309             sb.append("Level in Band 1 : " + (testLevel() ? "OK" :"Not Optimal") +"\n");
310             for (int b = 0; b < mBands; b++) {
311                 double percent = 0;
312                 if (mPointsPerBand[b] > 0) {
313                     percent = 100.0 * (double)mInBoundPointsPerBand[b] / mPointsPerBand[b];
314                 }
315                 sb.append(String.format(
316                         " Band %d: Av. Level: %.1f dB InBand: %d/%d (%.1f%%) %s\n",
317                         b, mAverageEnergyPerBand[b],
318                         mInBoundPointsPerBand[b],
319                         mPointsPerBand[b],
320                         percent,
321                         (testInBand(b) ? "OK" : "Not Optimal")));
322             }
323             return sb.toString();
324         }
325 
testLevel()326         public boolean testLevel() {
327             if (mAverageEnergyPerBand[1] >= MIN_ENERGY_BAND_1) {
328                 return true;
329             }
330             return false;
331         }
332 
testInBand(int b)333         public boolean testInBand(int b) {
334             if (b >= 0 && b < mBands && mPointsPerBand[b] > 0) {
335                 if ((double)mInBoundPointsPerBand[b] / mPointsPerBand[b] >
336                 MIN_FRACTION_POINTS_IN_BAND)
337                     return true;
338             }
339             return false;
340         }
341 
testAll()342         public boolean testAll() {
343             if (!testLevel()) {
344                 return false;
345             }
346             for (int b = 0; b < mBands; b++) {
347                 if (!testInBand(b)) {
348                     return false;
349                 }
350             }
351             return true;
352         }
353     }
354 
355     /**
356      * compute test results
357      */
computeResults()358     private void computeResults() {
359         Results resultsLeft = new Results("Left");
360         computeResultsForVector(mFreqAverage0, resultsLeft);
361         Results resultsRight = new Results("Right");
362         computeResultsForVector(mFreqAverage1, resultsRight);
363         if (resultsLeft.testAll() && resultsRight.testAll()) {
364             String strSuccess = getResources().getString(R.string.audio_general_test_passed);
365             appendResultsToScreen(strSuccess);
366         } else {
367             String strFailed = getResources().getString(R.string.audio_general_test_failed);
368             appendResultsToScreen(strFailed + "\n");
369             String strWarning = getResources().getString(R.string.audio_general_deficiency_found);
370             appendResultsToScreen(strWarning);
371         }
372         getPassButton().setEnabled(true); //Everybody passes! (for now...)
373     }
374 
computeResultsForVector(VectorAverage freqAverage,Results results)375     private void computeResultsForVector(VectorAverage freqAverage,Results results) {
376 
377         int points = freqAverage.getSize();
378         if (points > 0) {
379             //compute vector in db
380             double[] values = new double[points];
381             freqAverage.getData(values, false);
382             results.mValuesLog = new double[points];
383             for (int i = 0; i < points; i++) {
384                 results.mValuesLog[i] = 20 * Math.log10(values[i]);
385             }
386 
387             int currentBand = 0;
388             for (int i = 0; i < points; i++) {
389                 double freq = (double)mSamplingRate * i / (double)mBlockSizeSamples;
390                 if (freq > bandSpecsArray[currentBand].mFreqStop) {
391                     currentBand++;
392                     if (currentBand >= mBands)
393                         break;
394                 }
395 
396                 if (freq >= bandSpecsArray[currentBand].mFreqStart) {
397                     results.mAverageEnergyPerBand[currentBand] += results.mValuesLog[i];
398                     results.mPointsPerBand[currentBand]++;
399                 }
400             }
401 
402             for (int b = 0; b < mBands; b++) {
403                 if (results.mPointsPerBand[b] > 0) {
404                     results.mAverageEnergyPerBand[b] =
405                             results.mAverageEnergyPerBand[b] / results.mPointsPerBand[b];
406                 }
407             }
408 
409             //set offset relative to band 1 level
410             for (int b = 0; b < mBands; b++) {
411                 bandSpecsArray[b].setOffset(results.mAverageEnergyPerBand[1]);
412             }
413 
414             //test points in band.
415             currentBand = 0;
416             for (int i = 0; i < points; i++) {
417                 double freq = (double)mSamplingRate * i / (double)mBlockSizeSamples;
418                 if (freq >  bandSpecsArray[currentBand].mFreqStop) {
419                     currentBand++;
420                     if (currentBand >= mBands)
421                         break;
422                 }
423 
424                 if (freq >= bandSpecsArray[currentBand].mFreqStart) {
425                     double value = results.mValuesLog[i];
426                     if (bandSpecsArray[currentBand].isInBounds(freq, value)) {
427                         results.mInBoundPointsPerBand[currentBand]++;
428                     }
429                 }
430             }
431 
432             appendResultsToScreen(results.toString());
433 
434             //store results
435             storeTestResults(results);
436         } else {
437             appendResultsToScreen("Failed testing channel " + results.mLabel);
438         }
439     }
440 
441     //append results
appendResultsToScreen(String str)442     private void appendResultsToScreen(String str) {
443         String currentText = mResultText.getText().toString();
444         mResultText.setText(currentText + "\n" + str);
445     }
446 
447     /**
448      * Store test results in log
449      */
storeTestResults(Results results)450     private void storeTestResults(Results results) {
451         String channelLabel = "channel_" + results.mLabel;
452 
453         CtsVerifierReportLog reportLog = getReportLog();
454         for (int b = 0; b < mBands; b++) {
455             String bandLabel = String.format(channelLabel + "_%d", b);
456             reportLog.addValue(
457                     bandLabel + "_Level",
458                     results.mAverageEnergyPerBand[b],
459                     ResultType.HIGHER_BETTER,
460                     ResultUnit.NONE);
461 
462             reportLog.addValue(
463                     bandLabel + "_pointsinbound",
464                     results.mInBoundPointsPerBand[b],
465                     ResultType.HIGHER_BETTER,
466                     ResultUnit.COUNT);
467 
468             reportLog.addValue(
469                     bandLabel + "_pointstotal",
470                     results.mPointsPerBand[b],
471                     ResultType.NEUTRAL,
472                     ResultUnit.COUNT);
473         }
474 
475         reportLog.addValues(channelLabel + "_magnitudeSpectrumLog",
476                 results.mValuesLog,
477                 ResultType.NEUTRAL,
478                 ResultUnit.NONE);
479 
480         Log.v(TAG, "Results Recorded");
481     }
482 
483     @Override // PassFailButtons
recordTestResults()484     public void recordTestResults() {
485         getReportLog().submit();
486     }
487 
recordHeasetPortFound(boolean found)488     private void recordHeasetPortFound(boolean found) {
489         getReportLog().addValue(
490                 "User Reported Headset Port",
491                 found ? 1.0 : 0,
492                 ResultType.NEUTRAL,
493                 ResultUnit.NONE);
494     }
495 
startRecording()496     private void startRecording() {
497         synchronized (mRecordingLock) {
498             mIsRecording = true;
499         }
500 
501         boolean successful = initRecord();
502         if (successful) {
503             startRecordingForReal();
504         } else {
505             Log.v(TAG, "Recorder initialization error.");
506             synchronized (mRecordingLock) {
507                 mIsRecording = false;
508             }
509         }
510     }
511 
startRecordingForReal()512     private void startRecordingForReal() {
513         // start streaming
514         if (mRecordThread == null) {
515             mRecordThread = new Thread(AudioFrequencyLineActivity.this);
516             mRecordThread.setName("FrequencyAnalyzerThread");
517             mRecordThreadShutdown = false;
518         }
519         if (!mRecordThread.isAlive()) {
520             mRecordThread.start();
521         }
522 
523         mPipe.flush();
524 
525         long startTime = SystemClock.uptimeMillis();
526         mRecorder.startRecording();
527         if (mRecorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
528             stopRecording();
529             return;
530         }
531         Log.v(TAG, "Start time: " + (long) (SystemClock.uptimeMillis() - startTime) + " ms");
532     }
533 
stopRecording()534     private void stopRecording() {
535         synchronized (mRecordingLock) {
536             stopRecordingForReal();
537             mIsRecording = false;
538         }
539     }
540 
stopRecordingForReal()541     private void stopRecordingForReal() {
542 
543         // stop streaming
544         Thread zeThread = mRecordThread;
545         mRecordThread = null;
546         mRecordThreadShutdown = true;
547         if (zeThread != null) {
548             zeThread.interrupt();
549             try {
550                 zeThread.join();
551             } catch(InterruptedException e) {
552                 Log.v(TAG,"Error shutting down recording thread " + e);
553                 //we don't really care about this error, just logging it.
554             }
555         }
556          // release recording resources
557         if (mRecorder != null) {
558             mRecorder.stop();
559             mRecorder.release();
560             mRecorder = null;
561         }
562     }
563 
initRecord()564     private boolean initRecord() {
565         int minRecordBuffSizeInBytes = AudioRecord.getMinBufferSize(mSamplingRate,
566                 mChannelConfig, mAudioFormat);
567         Log.v(TAG,"FrequencyAnalyzer: min buff size = " + minRecordBuffSizeInBytes + " bytes");
568         if (minRecordBuffSizeInBytes <= 0) {
569             return false;
570         }
571 
572         mMinRecordBufferSizeInSamples = minRecordBuffSizeInBytes / 2;
573         // allocate the byte array to read the audio data
574 
575         mAudioShortArray = new short[mMinRecordBufferSizeInSamples];
576 
577         Log.v(TAG, "Initiating record:");
578         Log.v(TAG, "      using source " + mSelectedRecordSource);
579         Log.v(TAG, "      at " + mSamplingRate + "Hz");
580 
581         try {
582             mRecorder = new AudioRecord(mSelectedRecordSource, mSamplingRate,
583                     mChannelConfig, mAudioFormat, 2 * minRecordBuffSizeInBytes);
584         } catch (IllegalArgumentException e) {
585             Log.v(TAG, "Error: " + e.toString());
586             return false;
587         }
588         if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
589             mRecorder.release();
590             mRecorder = null;
591             Log.v(TAG, "Error: mRecorder not initialized");
592             return false;
593         }
594         mRecorder.setRecordPositionUpdateListener(this);
595         mRecorder.setPositionNotificationPeriod(mBlockSizeSamples / 2);
596         return true;
597     }
598 
599     // ---------------------------------------------------------
600     // Implementation of AudioRecord.OnPeriodicNotificationListener
601     // --------------------
onPeriodicNotification(AudioRecord recorder)602     public void onPeriodicNotification(AudioRecord recorder) {
603         int samplesAvailable = mPipe.availableToRead();
604         int samplesNeeded = mBlockSizeSamples;
605         if (samplesAvailable >= samplesNeeded) {
606             mPipe.read(mAudioShortArray2, 0, samplesNeeded);
607 
608             //compute stuff.
609             double maxval = Math.pow(2, 15);
610             int clipcount = 0;
611             double cliplevel = (maxval-10) / maxval;
612             double sum = 0;
613             double maxabs = 0;
614             int i;
615             int index = 0;
616 
617             for (i = 0; i < samplesNeeded; i++) {
618                 double value = mAudioShortArray2[i] / maxval;
619                 double valueabs = Math.abs(value);
620 
621                 if (valueabs > maxabs) {
622                     maxabs = valueabs;
623                 }
624 
625                 if (valueabs > cliplevel) {
626                     clipcount++;
627                 }
628 
629                 sum += value * value;
630                 //fft stuff
631                 if (index < mBlockSizeSamples) {
632                     mData.mData[index] = value;
633                 }
634                 index++;
635             }
636 
637             //for the current frame, compute FFT and send to the viewer.
638 
639             //apply window and pack as complex for now.
640             DspBufferMath.mult(mData, mData, mWindow.mBuffer);
641             DspBufferMath.set(mC, mData);
642             mFftServer.fft(mC, 1);
643 
644             double[] halfMagnitude = new double[mBlockSizeSamples / 2];
645             for (i = 0; i < mBlockSizeSamples / 2; i++) {
646                 halfMagnitude[i] = Math.sqrt(mC.mReal[i] * mC.mReal[i] + mC.mImag[i] * mC.mImag[i]);
647             }
648 
649             mFreqAverageMain.setData(halfMagnitude, false); //average all of them!
650 
651             switch(mCurrentTest) {
652                 case 0:
653                     mFreqAverage0.setData(halfMagnitude, false);
654                     break;
655                 case 1:
656                     mFreqAverage1.setData(halfMagnitude, false);
657                     break;
658             }
659         }
660     }
661 
onMarkerReached(AudioRecord track)662     public void onMarkerReached(AudioRecord track) {
663     }
664 
665     // ---------------------------------------------------------
666     // Implementation of Runnable for the audio recording + playback
667     // --------------------
run()668     public void run() {
669         int nSamplesRead = 0;
670 
671         Thread thisThread = Thread.currentThread();
672         while (mRecordThread == thisThread && !mRecordThreadShutdown) {
673             // read from native recorder
674             nSamplesRead = mRecorder.read(mAudioShortArray, 0, mMinRecordBufferSizeInSamples);
675             if (nSamplesRead > 0) {
676                 mPipe.write(mAudioShortArray, 0, nSamplesRead);
677             }
678         }
679     }
680 }
681