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