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