Home | History | Annotate | Download | only in audiolib
      1 /*
      2  * Copyright (C) 2017 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.audiolib;
     18 
     19 import android.media.AudioDeviceInfo;
     20 import android.media.AudioFormat;
     21 import android.media.AudioRecord;
     22 
     23 import android.util.Log;
     24 
     25 import java.lang.Math;
     26 
     27 /**
     28  * Records audio data to a stream.
     29  */
     30 public class StreamRecorder {
     31     @SuppressWarnings("unused")
     32     private static final String TAG = "StreamRecorder";
     33 
     34     // Sample Buffer
     35     private float[] mBurstBuffer;
     36     private int mNumBurstFrames;
     37     private int mNumChannels;
     38 
     39     // Recording attributes
     40     private int mSampleRate;
     41 
     42     // Recording state
     43     Thread mRecorderThread = null;
     44     private AudioRecord mAudioRecord = null;
     45     private boolean mRecording = false;
     46 
     47     private StreamRecorderListener mListener = null;
     48 
     49     private AudioDeviceInfo mRoutingDevice = null;
     50 
     51     public StreamRecorder() {}
     52 
     53     public int getNumBurstFrames() { return mNumBurstFrames; }
     54     public int getSampleRate() { return mSampleRate; }
     55 
     56     /*
     57      * State
     58      */
     59     public static int calcNumBufferBytes(int numChannels, int sampleRate, int encoding) {
     60         // NOTE: Special handling of 4-channels. There is currently no AudioFormat positional
     61         // constant for 4-channels of input, so in this case, calculate for 2 and double it.
     62         int numBytes = 0;
     63         if (numChannels == 4) {
     64             numBytes = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO,
     65                     encoding);
     66             numBytes *= 2;
     67         } else {
     68             numBytes = AudioRecord.getMinBufferSize(sampleRate,
     69                     AudioUtils.countToInPositionMask(numChannels), encoding);
     70         }
     71 
     72         return numBytes;
     73     }
     74 
     75     public static int calcNumBufferFrames(int numChannels, int sampleRate, int encoding) {
     76         return calcNumBufferBytes(numChannels, sampleRate, encoding) /
     77                 AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
     78     }
     79 
     80     public boolean isInitialized() {
     81         return mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED;
     82     }
     83 
     84     public boolean isRecording() { return mRecording; }
     85 
     86     public void setRouting(AudioDeviceInfo routingDevice) {
     87         Log.i(TAG, "setRouting(" + (routingDevice != null ? routingDevice.getId() : -1) + ")");
     88         mRoutingDevice = routingDevice;
     89         if (mAudioRecord != null) {
     90             mAudioRecord.setPreferredDevice(mRoutingDevice);
     91         }
     92     }
     93 
     94     /*
     95      * Accessors
     96      */
     97     public float[] getBurstBuffer() { return mBurstBuffer; }
     98 
     99     public int getNumChannels() { return mNumChannels; }
    100 
    101     /*
    102      * Events
    103      */
    104     public void setListener(StreamRecorderListener listener) {
    105         mListener = listener;
    106     }
    107 
    108     private void waitForRecorderThreadToExit() {
    109         try {
    110             if (mRecorderThread != null) {
    111                 mRecorderThread.join();
    112                 mRecorderThread = null;
    113             }
    114         } catch (InterruptedException e) {
    115             e.printStackTrace();
    116         }
    117     }
    118 
    119     private boolean open_internal(int numChans, int sampleRate) {
    120         mNumChannels = numChans;
    121         mSampleRate = sampleRate;
    122 
    123         int chanPosMask = AudioUtils.countToInPositionMask(numChans);
    124         int bufferSizeInBytes = 2048;   // Some, non-critical value
    125 
    126         try {
    127             mAudioRecord = new AudioRecord.Builder()
    128                     .setAudioFormat(new AudioFormat.Builder()
    129                             .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
    130                             .setSampleRate(mSampleRate)
    131                             .setChannelMask(chanPosMask)
    132                             .build())
    133                     .setBufferSizeInBytes(bufferSizeInBytes)
    134                     .build();
    135 
    136             return true;
    137         } catch (UnsupportedOperationException ex) {
    138             Log.i(TAG, "Couldn't open AudioRecord: " + ex);
    139             mAudioRecord = null;
    140             return false;
    141         }
    142     }
    143 
    144     public boolean open(int numChans, int sampleRate, int numBurstFrames) {
    145         boolean sucess = open_internal(numChans, sampleRate);
    146         if (sucess) {
    147             mNumBurstFrames = numBurstFrames;
    148             mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
    149             // put some non-zero data in the burst buffer.
    150             // this is to verify that the record is putting SOMETHING into each channel.
    151             for(int index = 0; index < mBurstBuffer.length; index++) {
    152                 mBurstBuffer[index] = (float)(Math.random() * 2.0) - 1.0f;
    153             }
    154         }
    155 
    156         return sucess;
    157     }
    158 
    159     public void close() {
    160         stop();
    161 
    162         waitForRecorderThreadToExit();
    163 
    164         mAudioRecord.release();
    165         mAudioRecord = null;
    166     }
    167 
    168     public boolean start() {
    169         mAudioRecord.setPreferredDevice(mRoutingDevice);
    170 
    171         if (mListener != null) {
    172             mListener.sendEmptyMessage(StreamRecorderListener.MSG_START);
    173         }
    174 
    175         try {
    176             mAudioRecord.startRecording();
    177         } catch (IllegalStateException ex) {
    178             Log.i("", "ex: " + ex);
    179         }
    180         mRecording = true;
    181 
    182         waitForRecorderThreadToExit(); // just to be sure.
    183 
    184         mRecorderThread = new Thread(new StreamRecorderRunnable(), "StreamRecorder Thread");
    185         mRecorderThread.start();
    186 
    187         return true;
    188     }
    189 
    190     public void stop() {
    191         if (mRecording) {
    192             mRecording = false;
    193         }
    194     }
    195 
    196     /*
    197      * StreamRecorderRunnable
    198      */
    199     private class StreamRecorderRunnable implements Runnable {
    200         @Override
    201         public void run() {
    202             final int numBurstSamples = mNumBurstFrames * mNumChannels;
    203             while (mRecording) {
    204                 int numReadSamples = mAudioRecord.read(
    205                         mBurstBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
    206 
    207                 if (numReadSamples < 0) {
    208                     // error
    209                     Log.i(TAG, "AudioRecord write error: " + numReadSamples);
    210                     stop();
    211                 } else if (numReadSamples < numBurstSamples) {
    212                     // got less than requested?
    213                     Log.i(TAG, "AudioRecord Underflow: " + numReadSamples +
    214                             " vs. " + numBurstSamples);
    215                     stop();
    216                 }
    217 
    218                 if (mListener != null && numReadSamples == numBurstSamples) {
    219                     mListener.sendEmptyMessage(StreamRecorderListener.MSG_BUFFER_FILL);
    220                 }
    221             }
    222 
    223             if (mListener != null) {
    224                 // TODO: on error or underrun we may be send bogus data.
    225                 mListener.sendEmptyMessage(StreamRecorderListener.MSG_STOP);
    226             }
    227             mAudioRecord.stop();
    228         }
    229     }
    230 }
    231