Home | History | Annotate | Download | only in loopback
      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 org.drrickorang.loopback;
     18 
     19 import android.os.Bundle;
     20 import android.os.Parcel;
     21 import android.os.Parcelable;
     22 import android.util.Log;
     23 
     24 import java.util.Arrays;
     25 
     26 
     27 /**
     28  * This class records the buffer period of the audio player or recorder when in Java mode.
     29  * Currently the accuracy is in 1ms.
     30  */
     31 
     32 //TODO for native mode, should use a scale more accurate than the current 1ms
     33 public class BufferPeriod implements Parcelable {
     34     private static final String TAG = "BufferPeriod";
     35 
     36     private long mStartTimeNs = 0;  // first time collectBufferPeriod() is called
     37     private long mPreviousTimeNs = 0;
     38     private long mCurrentTimeNs = 0;
     39 
     40     private int       mMeasurements = 0;
     41     private long      mVar;     // variance in nanoseconds^2
     42     private long      mSDM = 0; // sum of squares of deviations from the expected mean
     43     private int       mMaxBufferPeriod = 0;
     44 
     45     private int       mCount = 0;
     46     private final int range = 1002; // store counts for 0ms to 1000ms, and for > 1000ms
     47     private int       mExpectedBufferPeriod = 0;
     48 
     49     private int[] mBufferPeriod = new int[range];
     50     private BufferCallbackTimes mCallbackTimes;
     51     private CaptureHolder mCaptureHolder;
     52 
     53     public BufferPeriod() {
     54         // Default constructor for when no data will be restored
     55     }
     56 
     57     /**
     58      * For player, this function is called before every AudioTrack.write().
     59      * For recorder, this function is called after every AudioRecord.read() with read > 0.
     60      */
     61     public void collectBufferPeriod() {
     62         mCurrentTimeNs = System.nanoTime();
     63         mCount++;
     64 
     65         // if mPreviousTimeNs = 0, it's the first time this function is called
     66         if (mPreviousTimeNs == 0) {
     67             mStartTimeNs = mCurrentTimeNs;
     68         }
     69 
     70         if (mPreviousTimeNs != 0 && mCount > Constant.BUFFER_PERIOD_DISCARD) {
     71             mMeasurements++;
     72 
     73             long diffInNano = mCurrentTimeNs - mPreviousTimeNs;
     74             // diffInMilli is rounded up
     75             int diffInMilli = (int) ((diffInNano + Constant.NANOS_PER_MILLI - 1) /
     76                                       Constant.NANOS_PER_MILLI);
     77 
     78             long timeStampInNano = mCurrentTimeNs - mStartTimeNs;
     79             int timeStampInMilli = (int) ((timeStampInNano + Constant.NANOS_PER_MILLI - 1) /
     80                                            Constant.NANOS_PER_MILLI);
     81 
     82             if (diffInMilli > mMaxBufferPeriod) {
     83                 mMaxBufferPeriod = diffInMilli;
     84             }
     85 
     86             // from 0 ms to 1000 ms, plus a sum of all occurrences > 1000ms
     87             if (diffInMilli >= (range - 1)) {
     88                 mBufferPeriod[range - 1]++;
     89             } else if (diffInMilli >= 0) {
     90                 mBufferPeriod[diffInMilli]++;
     91             } else { // for diffInMilli < 0
     92                 log("Having negative BufferPeriod.");
     93             }
     94 
     95             long delta = diffInNano - (long) mExpectedBufferPeriod * Constant.NANOS_PER_MILLI;
     96             mSDM += delta * delta;
     97             if (mCount > 1) {
     98                 mVar = mSDM / mMeasurements;
     99             }
    100 
    101             mCallbackTimes.recordCallbackTime(timeStampInMilli, (short) diffInMilli);
    102 
    103             // If diagnosing specific Java thread callback behavior set a conditional here and use
    104             // mCaptureHolder.captureState(rank); to capture systraces and bugreport and/or wav file
    105         }
    106 
    107         mPreviousTimeNs = mCurrentTimeNs;
    108     }
    109 
    110 
    111     /** Reset all variables, called if wants to start a new buffer period's record. */
    112     public void resetRecord() {
    113         mPreviousTimeNs = 0;
    114         mCurrentTimeNs = 0;
    115         Arrays.fill(mBufferPeriod, 0);
    116         mMaxBufferPeriod = 0;
    117         mMeasurements = 0;
    118         mExpectedBufferPeriod = 0;
    119         mCount = 0;
    120         mCallbackTimes = null;
    121     }
    122 
    123     public void prepareMemberObjects(int maxRecords, int expectedBufferPeriod,
    124                                      CaptureHolder captureHolder){
    125         mCallbackTimes = new BufferCallbackTimes(maxRecords, expectedBufferPeriod);
    126         mCaptureHolder = captureHolder;
    127         mExpectedBufferPeriod = expectedBufferPeriod;
    128     }
    129 
    130     public int[] getBufferPeriodArray() {
    131         return mBufferPeriod;
    132     }
    133 
    134     public double getStdDevBufferPeriod() {
    135         return Math.sqrt(mVar) / (double) Constant.NANOS_PER_MILLI;
    136     }
    137 
    138     public int getMaxBufferPeriod() {
    139         return mMaxBufferPeriod;
    140     }
    141 
    142     public BufferCallbackTimes getCallbackTimes(){
    143         return mCallbackTimes;
    144     }
    145 
    146     @Override
    147     public int describeContents() {
    148         return 0;
    149     }
    150 
    151     // Only save values which represent the results. Any ongoing timing would not give useful
    152     // results after a save/restore.
    153     @Override
    154     public void writeToParcel(Parcel dest, int flags) {
    155         Bundle out = new Bundle();
    156         out.putInt("mMaxBufferPeriod", mMaxBufferPeriod);
    157         out.putIntArray("mBufferPeriod", mBufferPeriod);
    158         out.putInt("mExpectedBufferPeriod", mExpectedBufferPeriod);
    159         out.putParcelable("mCallbackTimes", mCallbackTimes);
    160         dest.writeBundle(out);
    161     }
    162 
    163     private BufferPeriod(Parcel source) {
    164         Bundle in = source.readBundle(getClass().getClassLoader());
    165         mMaxBufferPeriod = in.getInt("mMaxBufferPeriod");
    166         mBufferPeriod = in.getIntArray("mBufferPeriod");
    167         mExpectedBufferPeriod = in.getInt("mExpectedBufferPeriod");
    168         mCallbackTimes = in.getParcelable("mCallbackTimes");
    169     }
    170 
    171     public static final Parcelable.Creator<BufferPeriod> CREATOR
    172              = new Parcelable.Creator<BufferPeriod>() {
    173          public BufferPeriod createFromParcel(Parcel in) {
    174              return new BufferPeriod(in);
    175          }
    176 
    177          public BufferPeriod[] newArray(int size) {
    178              return new BufferPeriod[size];
    179          }
    180      };
    181 
    182     private static void log(String msg) {
    183         Log.v(TAG, msg);
    184     }
    185 
    186 }
    187