1 /*
2  * Copyright (C) 2014 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 org.drrickorang.loopback;
18 
19 import android.content.Context;
20 import android.media.AudioDeviceInfo;
21 import android.media.AudioFormat;
22 import android.media.AudioManager;
23 import android.media.AudioRecord;
24 import android.media.AudioTrack;
25 import android.media.MediaRecorder;
26 import android.os.Build;
27 import android.util.Log;
28 import android.os.Handler;
29 import android.os.Message;
30 
31 /**
32  * A thread/audio track based audio synth.
33  */
34 
35 public class LoopbackAudioThread extends Thread {
36     private static final String TAG = "LoopbackAudioThread";
37 
38     private static final int THREAD_SLEEP_DURATION_MS = 1;
39 
40     // for latency test
41     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED = 991;
42     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR = 992;
43     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE = 993;
44     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP = 994;
45 
46     // for buffer test
47     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED = 996;
48     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR = 997;
49     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE = 998;
50     static final int LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP = 999;
51 
52     public boolean           mIsRunning = false;
53     public AudioTrack        mAudioTrack;
54     public int               mSessionId;
55     private Thread           mRecorderThread;
56     private RecorderRunnable mRecorderRunnable;
57 
58     private final int mSamplingRate;
59     private final int mChannelIndex;
60     private final int mChannelConfigIn = AudioFormat.CHANNEL_IN_MONO;
61     private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
62     private int       mMinPlayerBufferSizeInBytes = 0;
63     private int       mMinRecorderBuffSizeInBytes = 0;
64     private int       mMinPlayerBufferSizeSamples = 0;
65     private final int mMicSource;
66     private final int mChannelConfigOut = AudioFormat.CHANNEL_OUT_MONO;
67     private boolean   mIsPlaying = false;
68     private boolean   mIsRequestStop = false;
69     private Handler   mMessageHandler;
70     // This is the pipe that connects the player and the recorder in latency test.
71     private PipeShort mLatencyTestPipe = new PipeShort(Constant.MAX_SHORTS);
72 
73     // for buffer test
74     private BufferPeriod   mRecorderBufferPeriod; // used to collect recorder's buffer period
75     private BufferPeriod   mPlayerBufferPeriod; // used to collect player's buffer period
76     private int            mTestType; // latency test or buffer test
77     private int            mBufferTestDurationInSeconds; // Duration of actual buffer test
78     private Context        mContext;
79     private int            mBufferTestWavePlotDurationInSeconds;
80     private final CaptureHolder mCaptureHolder;
81     private boolean        mIsAdjustingSoundLevel = true; // only used in buffer test
82 
computeDefaultSettings()83     public static TestSettings computeDefaultSettings() {
84         int samplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
85         int minPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(samplingRate,
86                 AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
87         int minRecorderBufferSizeInBytes = AudioRecord.getMinBufferSize(samplingRate,
88                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
89         return new TestSettings(samplingRate, minPlayerBufferSizeInBytes,
90                 minRecorderBufferSizeInBytes);
91     }
92 
LoopbackAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes, int micSource, BufferPeriod recorderBufferPeriod, BufferPeriod playerBufferPeriod, int testType, int bufferTestDurationInSeconds, int bufferTestWavePlotDurationInSeconds, Context context, int channelIndex, CaptureHolder captureHolder)93     public LoopbackAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes,
94                                int micSource, BufferPeriod recorderBufferPeriod,
95                                BufferPeriod playerBufferPeriod, int testType,
96                                int bufferTestDurationInSeconds,
97                                int bufferTestWavePlotDurationInSeconds, Context context,
98                                int channelIndex, CaptureHolder captureHolder) {
99         mSamplingRate = samplingRate;
100         mMinPlayerBufferSizeInBytes = playerBufferInBytes;
101         mMinRecorderBuffSizeInBytes = recorderBufferInBytes;
102         mMicSource = micSource;
103         mRecorderBufferPeriod = recorderBufferPeriod;
104         mPlayerBufferPeriod = playerBufferPeriod;
105         mTestType = testType;
106         mBufferTestDurationInSeconds = bufferTestDurationInSeconds;
107         mBufferTestWavePlotDurationInSeconds = bufferTestWavePlotDurationInSeconds;
108         mContext = context;
109         mChannelIndex = channelIndex;
110         mCaptureHolder = captureHolder;
111 
112         setName("Loopback_LoopbackAudio");
113     }
114 
115 
run()116     public void run() {
117         setPriority(Thread.MAX_PRIORITY);
118 
119         if (mMinPlayerBufferSizeInBytes <= 0) {
120             mMinPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(mSamplingRate,
121                                         mChannelConfigOut, mAudioFormat);
122 
123             log("Player: computed min buff size = " + mMinPlayerBufferSizeInBytes + " bytes");
124         } else {
125             log("Player: using min buff size = " + mMinPlayerBufferSizeInBytes + " bytes");
126         }
127 
128         mMinPlayerBufferSizeSamples = mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME;
129         short[] audioShortArrayOut = new short[mMinPlayerBufferSizeSamples];
130 
131         // we may want to adjust this to different multiplication of mMinPlayerBufferSizeSamples
132         int audioTrackWriteDataSize = mMinPlayerBufferSizeSamples;
133 
134         // used for buffer test only
135         final double frequency1 = Constant.PRIME_FREQUENCY_1;
136         final double frequency2 = Constant.PRIME_FREQUENCY_2; // not actually used
137         short[] bufferTestTone = new short[audioTrackWriteDataSize]; // used by AudioTrack.write()
138         ToneGeneration toneGeneration = new SineWaveTone(mSamplingRate, frequency1);
139 
140         mRecorderRunnable = new RecorderRunnable(mLatencyTestPipe, mSamplingRate, mChannelConfigIn,
141                 mAudioFormat, mMinRecorderBuffSizeInBytes, MediaRecorder.AudioSource.MIC, this,
142                 mRecorderBufferPeriod, mTestType, frequency1, frequency2,
143                 mBufferTestWavePlotDurationInSeconds, mContext, mChannelIndex, mCaptureHolder);
144         mRecorderRunnable.setBufferTestDurationInSeconds(mBufferTestDurationInSeconds);
145         mRecorderThread = new Thread(mRecorderRunnable);
146         mRecorderThread.setName("Loopback_RecorderRunnable");
147 
148         // both player and recorder run at max priority
149         mRecorderThread.setPriority(Thread.MAX_PRIORITY);
150         mRecorderThread.start();
151 
152         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
153             mAudioTrack = new AudioTrack.Builder()
154                     .setAudioFormat((mChannelIndex < 0 ?
155                             new AudioFormat.Builder().setChannelMask(AudioFormat.CHANNEL_OUT_MONO) :
156                             new AudioFormat.Builder().setChannelIndexMask(1 << mChannelIndex))
157                             .setSampleRate(mSamplingRate)
158                             .setEncoding(mAudioFormat)
159                             .build())
160                     .setBufferSizeInBytes(mMinPlayerBufferSizeInBytes)
161                     .setTransferMode(AudioTrack.MODE_STREAM)
162                     .build();
163         } else {
164             mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
165                     mSamplingRate,
166                     mChannelConfigOut,
167                     mAudioFormat,
168                     mMinPlayerBufferSizeInBytes,
169                     AudioTrack.MODE_STREAM /* FIXME runtime test for API level 9,
170                     mSessionId */);
171         }
172 
173         if (mRecorderRunnable != null && mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
174             mIsPlaying = false;
175             mIsRunning = true;
176 
177             while (mIsRunning && mRecorderThread.isAlive()) {
178                 if (mIsPlaying) {
179                     switch (mTestType) {
180                     case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
181                         // read from the pipe and plays it out
182                         int samplesAvailable = mLatencyTestPipe.availableToRead();
183                         if (samplesAvailable > 0) {
184                             int samplesOfInterest = Math.min(samplesAvailable,
185                                     mMinPlayerBufferSizeSamples);
186 
187                             int samplesRead = mLatencyTestPipe.read(audioShortArrayOut, 0,
188                                                                     samplesOfInterest);
189                             mAudioTrack.write(audioShortArrayOut, 0, samplesRead);
190                             mPlayerBufferPeriod.collectBufferPeriod();
191                         }
192                         break;
193                     case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
194                         // don't collect buffer period when we are still adjusting the sound level
195                         if (mIsAdjustingSoundLevel) {
196                             toneGeneration.generateTone(bufferTestTone, bufferTestTone.length);
197                             mAudioTrack.write(bufferTestTone, 0, audioTrackWriteDataSize);
198                         } else {
199                             mPlayerBufferPeriod.collectBufferPeriod();
200                             toneGeneration.generateTone(bufferTestTone, bufferTestTone.length);
201                             mAudioTrack.write(bufferTestTone, 0, audioTrackWriteDataSize);
202                         }
203                         break;
204                     }
205                 } else {
206                     // wait for a bit to allow AudioTrack to start playing
207                     if (mIsRunning) {
208                         try {
209                             sleep(THREAD_SLEEP_DURATION_MS);
210                         } catch (InterruptedException e) {
211                             e.printStackTrace();
212                         }
213                     }
214                 }
215             }
216             endTest();
217 
218         } else {
219             log("Loopback Audio Thread couldn't run!");
220             mAudioTrack.release();
221             mAudioTrack = null;
222             if (mMessageHandler != null) {
223                 Message msg = Message.obtain();
224                 switch (mTestType) {
225                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
226                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR;
227                     break;
228                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
229                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR;
230                     break;
231                 }
232 
233                 mMessageHandler.sendMessage(msg);
234             }
235 
236         }
237     }
238 
239 
setMessageHandler(Handler messageHandler)240     public void setMessageHandler(Handler messageHandler) {
241         mMessageHandler = messageHandler;
242     }
243 
244 
setIsAdjustingSoundLevel(boolean isAdjustingSoundLevel)245     public void setIsAdjustingSoundLevel(boolean isAdjustingSoundLevel) {
246         mIsAdjustingSoundLevel = isAdjustingSoundLevel;
247     }
248 
249 
runTest()250     public void runTest() {
251         if (mIsRunning) {
252             // start test
253             if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
254                 log("...run test, but still playing...");
255                 endTest();
256             } else {
257                 // start playing
258                 mIsPlaying = true;
259                 mAudioTrack.play();
260                 boolean status = mRecorderRunnable.startRecording();
261 
262                 log("Started capture test");
263                 if (mMessageHandler != null) {
264                     Message msg = Message.obtain();
265                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED;
266                     if (!status) {
267                         msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR;
268                     }
269 
270                     mMessageHandler.sendMessage(msg);
271                 }
272             }
273         }
274     }
275 
276 
runBufferTest()277     public void runBufferTest() {
278         if (mIsRunning) {
279             // start test
280             if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
281                 log("...run test, but still playing...");
282                 endTest();
283             } else {
284                 // start playing
285                 mIsPlaying = true;
286                 mAudioTrack.play();
287                 boolean status = mRecorderRunnable.startBufferRecording();
288                 log(" Started capture test");
289                 if (mMessageHandler != null) {
290                     Message msg = Message.obtain();
291                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED;
292 
293                     if (!status) {
294                         msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR;
295                     }
296 
297                     mMessageHandler.sendMessage(msg);
298                 }
299             }
300         }
301     }
302 
303 
304     /** Clean some things up before sending out a message to LoopbackActivity. */
endTest()305     public void endTest() {
306         switch (mTestType) {
307         case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
308             log("--Ending latency test--");
309             break;
310         case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
311             log("--Ending buffer test--");
312             break;
313         }
314 
315         mIsPlaying = false;
316         mAudioTrack.pause();
317         mLatencyTestPipe.flush();
318         mAudioTrack.flush();
319 
320         if (mMessageHandler != null) {
321             Message msg = Message.obtain();
322             if (mIsRequestStop) {
323                 switch (mTestType) {
324                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
325                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP;
326                     break;
327                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
328                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP;
329                     break;
330                 }
331             } else {
332                 switch (mTestType) {
333                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
334                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE;
335                     break;
336                 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
337                     msg.what = LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE;
338                     break;
339                 }
340             }
341 
342             mMessageHandler.sendMessage(msg);
343         }
344     }
345 
346 
347     /**
348      * This is called only when the user requests to stop the test through
349      * pressing a button in the LoopbackActivity.
350      */
requestStopTest()351     public void requestStopTest() throws InterruptedException {
352         mIsRequestStop = true;
353         mRecorderRunnable.requestStop();
354     }
355 
356 
357     /** Release mAudioTrack and mRecorderThread. */
finish()358     public void finish() throws InterruptedException {
359         mIsRunning = false;
360 
361         final AudioTrack at = mAudioTrack;
362         if (at != null) {
363             at.release();
364             mAudioTrack = null;
365         }
366 
367         Thread zeThread = mRecorderThread;
368         mRecorderThread = null;
369         if (zeThread != null) {
370             zeThread.interrupt();
371             zeThread.join(Constant.JOIN_WAIT_TIME_MS);
372         }
373     }
374 
375 
log(String msg)376     private static void log(String msg) {
377         Log.v(TAG, msg);
378     }
379 
380 
getWaveData()381     public double[] getWaveData() {
382         return mRecorderRunnable.getWaveData();
383     }
384 
385 
getAllGlitches()386     public int[] getAllGlitches() {
387         return mRecorderRunnable.getAllGlitches();
388     }
389 
390 
getGlitchingIntervalTooLong()391     public boolean getGlitchingIntervalTooLong() {
392         return mRecorderRunnable.getGlitchingIntervalTooLong();
393     }
394 
395 
getFFTSamplingSize()396     public int getFFTSamplingSize() {
397         return mRecorderRunnable.getFFTSamplingSize();
398     }
399 
400 
getFFTOverlapSamples()401     public int getFFTOverlapSamples() {
402         return mRecorderRunnable.getFFTOverlapSamples();
403     }
404 
405 
getDurationInSeconds()406     int getDurationInSeconds() {
407         return mBufferTestDurationInSeconds;
408     }
409 
410 }
411