summaryrefslogtreecommitdiff
path: root/LoopbackApp/app/src/main/java/org/drrickorang
diff options
context:
space:
mode:
authorGlenn Kasten <gkasten@google.com>2016-08-04 15:18:50 -0700
committerGlenn Kasten <gkasten@google.com>2016-08-04 15:36:48 -0700
commit144dd00dcb045631df54ec7c755288a1dadeddc3 (patch)
treec0514070d3a01ca0f11e26d208f91250202b65c1 /LoopbackApp/app/src/main/java/org/drrickorang
parentc58bf2f1d8871c89e1d32b237193997cd55ec7d1 (diff)
downloaddrrickorang-144dd00dcb045631df54ec7c755288a1dadeddc3.tar.gz
Version 15
Snap to commit 1265b7e83b43ebc8227d9243591914ea0721cec4 Allow enabling systrace and bugreport captures separately in LoopbackApp. Capturing a bugreport takes a lot of time and CPU power. The CPU usage may cause another glitch, or an unrelated glitch may be missed because the app is waiting for a bugreport to complete. Capture systrace/bugreport on late callbacks in LoopbackApp. Remove obsolete method for systrace from native code in LoopbackApp. The code for this was unused. Add more atomic methods to audio_utils in LoopbackApp. Note that these methods are temporary until we can move to the standard atomic library for C++. Discard player callbacks if recorder is not yet running. Display channelIndex in LoopbackApp as MONO when appropriate. Iteratively calculate variance in LoopbackApp for Java. Iteratively calculate variance in LoopbackApp for native only. It is converted into standard deviation and reported by the app. Refactor statistics recording into a new function in LoopbackApp. This will make it easier to add new metrics and adjust the existing ones. Deduplicate code dealing with buffer period stats in LoopbackApp. Previously, the code for the recorder and player was copied and pasted with minimal changes. Fix build warning. Refactor LoopbackApp buffer stats into one struct. By unifying the player and recorder stats, copy-pasted code can be eliminated and adding new statistics is made much easier. Fix how late buffer callbacks are counted in LoopbackApp. The previous calculation id not take rounding into account which causes it to incorrectly label callbacks which occured only 1 ms late as over the threshold of 2 ms late. This results in the metric being oversensitive and flaky. Add setting to LoopbackApp to ignore beginning of recording. This is useful to work around hardware bugs that cause pops or other noise at the beginning of a recording.
Diffstat (limited to 'LoopbackApp/app/src/main/java/org/drrickorang')
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java29
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/CaptureHolder.java74
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java8
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/GlitchDetectionThread.java6
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java87
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java23
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java38
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/PerformanceMeasurement.java13
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java33
9 files changed, 226 insertions, 85 deletions
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java
index b4cd87e..97ab6ad 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java
@@ -16,13 +16,13 @@
package org.drrickorang.loopback;
-import java.util.Arrays;
-
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import java.util.Arrays;
+
/**
* This class records the buffer period of the audio player or recorder when in Java mode.
@@ -37,9 +37,14 @@ public class BufferPeriod implements Parcelable {
private long mPreviousTimeNs = 0;
private long mCurrentTimeNs = 0;
+ private int mMeasurements = 0;
+ private long mVar; // variance in nanoseconds^2
+ private long mSDM = 0; // sum of squares of deviations from the expected mean
private int mMaxBufferPeriod = 0;
+
private int mCount = 0;
private final int range = 1002; // store counts for 0ms to 1000ms, and for > 1000ms
+ private int mExpectedBufferPeriod = 0;
private int[] mBufferPeriod = new int[range];
private BufferCallbackTimes mCallbackTimes;
@@ -62,8 +67,9 @@ public class BufferPeriod implements Parcelable {
mStartTimeNs = mCurrentTimeNs;
}
- final int discard = 10; // discard the first few buffer period values
- if (mPreviousTimeNs != 0 && mCount > discard) {
+ if (mPreviousTimeNs != 0 && mCount > Constant.BUFFER_PERIOD_DISCARD) {
+ mMeasurements++;
+
long diffInNano = mCurrentTimeNs - mPreviousTimeNs;
// diffInMilli is rounded up
int diffInMilli = (int) ((diffInNano + Constant.NANOS_PER_MILLI - 1) /
@@ -86,6 +92,12 @@ public class BufferPeriod implements Parcelable {
log("Having negative BufferPeriod.");
}
+ long delta = diffInNano - (long) mExpectedBufferPeriod * Constant.NANOS_PER_MILLI;
+ mSDM += delta * delta;
+ if (mCount > 1) {
+ mVar = mSDM / mMeasurements;
+ }
+
mCallbackTimes.recordCallbackTime(timeStampInMilli, (short) diffInMilli);
// If diagnosing specific Java thread callback behavior set a conditional here and use
@@ -102,6 +114,8 @@ public class BufferPeriod implements Parcelable {
mCurrentTimeNs = 0;
Arrays.fill(mBufferPeriod, 0);
mMaxBufferPeriod = 0;
+ mMeasurements = 0;
+ mExpectedBufferPeriod = 0;
mCount = 0;
mCallbackTimes = null;
}
@@ -110,12 +124,17 @@ public class BufferPeriod implements Parcelable {
CaptureHolder captureHolder){
mCallbackTimes = new BufferCallbackTimes(maxRecords, expectedBufferPeriod);
mCaptureHolder = captureHolder;
+ mExpectedBufferPeriod = expectedBufferPeriod;
}
public int[] getBufferPeriodArray() {
return mBufferPeriod;
}
+ public double getStdDevBufferPeriod() {
+ return Math.sqrt(mVar) / (double) Constant.NANOS_PER_MILLI;
+ }
+
public int getMaxBufferPeriod() {
return mMaxBufferPeriod;
}
@@ -136,6 +155,7 @@ public class BufferPeriod implements Parcelable {
Bundle out = new Bundle();
out.putInt("mMaxBufferPeriod", mMaxBufferPeriod);
out.putIntArray("mBufferPeriod", mBufferPeriod);
+ out.putInt("mExpectedBufferPeriod", mExpectedBufferPeriod);
out.putParcelable("mCallbackTimes", mCallbackTimes);
dest.writeBundle(out);
}
@@ -144,6 +164,7 @@ public class BufferPeriod implements Parcelable {
Bundle in = source.readBundle(getClass().getClassLoader());
mMaxBufferPeriod = in.getInt("mMaxBufferPeriod");
mBufferPeriod = in.getIntArray("mBufferPeriod");
+ mExpectedBufferPeriod = in.getInt("mExpectedBufferPeriod");
mCallbackTimes = in.getParcelable("mCallbackTimes");
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/CaptureHolder.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/CaptureHolder.java
index fecacfe..774310b 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/CaptureHolder.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/CaptureHolder.java
@@ -24,6 +24,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
@@ -36,8 +37,11 @@ public class CaptureHolder {
public static final String STORAGE = "/sdcard/";
public static final String DIRECTORY = STORAGE + "Loopback";
private static final String SIGNAL_FILE = DIRECTORY + "/loopback_signal";
+ // These suffixes are used to tell the listener script what types of data to collect.
+ // They MUST match the definitions in the script file.
private static final String SYSTRACE_SUFFIX = ".trace";
private static final String BUGREPORT_SUFFIX = "_bugreport.txt.gz";
+
private static final String WAV_SUFFIX = ".wav";
private static final String TERMINATE_SIGNAL = "QUIT";
@@ -51,8 +55,9 @@ public class CaptureHolder {
private final long mStartTimeMS;
private final boolean mIsCapturingWavs;
private final boolean mIsCapturingSystraces;
+ private final boolean mIsCapturingBugreports;
private final int mCaptureCapacity;
- private Thread mCaptureThread;
+ private CaptureThread mCaptureThread;
private volatile CapturedState mCapturedStates[];
private WaveDataRingBuffer mWaveDataBuffer;
@@ -61,11 +66,13 @@ public class CaptureHolder {
private final int mSamplingRate;
public CaptureHolder(int captureCapacity, String fileNamePrefix, boolean captureWavs,
- boolean captureSystraces, Context context, int samplingRate) {
+ boolean captureSystraces, boolean captureBugreports, Context context,
+ int samplingRate) {
mCaptureCapacity = captureCapacity;
mFileNamePrefix = fileNamePrefix;
mIsCapturingWavs = captureWavs;
mIsCapturingSystraces = captureSystraces;
+ mIsCapturingBugreports = captureBugreports;
mStartTimeMS = System.currentTimeMillis();
mCapturedStates = new CapturedState[mCaptureCapacity];
mContext = context;
@@ -94,14 +101,15 @@ public class CaptureHolder {
*/
public synchronized int captureState(int rank) {
- if (!mIsCapturingWavs && !mIsCapturingSystraces) {
- Log.d(TAG, "captureState: Capturing wavs or systraces not enabled");
+ if (!isCapturing()) {
+ Log.d(TAG, "captureState: Capturing state not enabled");
return CAPTURING_DISABLED;
}
if (mCaptureThread != null && mCaptureThread.getState() != Thread.State.TERMINATED) {
// Capture already in progress
Log.d(TAG, "captureState: Capture thread already running");
+ mCaptureThread.updateRank(rank);
return CAPTURE_ALREADY_IN_PROGRESS;
}
@@ -113,8 +121,8 @@ public class CaptureHolder {
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeFromTestStartMS));
String timeString = String.format("%02dh%02dm%02ds", hours, minutes, seconds);
- String fullFileNamePrefix = STORAGE + mFileNamePrefix + '_' + timeString;
- CapturedState cs = new CapturedState(fullFileNamePrefix, timeFromTestStartMS, rank);
+ String fileNameBase = STORAGE + mFileNamePrefix + '_' + timeString;
+ CapturedState cs = new CapturedState(fileNameBase, timeFromTestStartMS, rank);
int indexOfLeastInteresting = getIndexOfLeastInterestingCapture(cs);
if (indexOfLeastInteresting == NEW_CAPTURE_IS_LEAST_INTERESTING) {
@@ -167,12 +175,8 @@ public class CaptureHolder {
return index;
}
- public boolean isCapturingWavs() {
- return mIsCapturingWavs;
- }
-
- public boolean isCapturingSysTraces() {
- return mIsCapturingSystraces;
+ public boolean isCapturing() {
+ return mIsCapturingWavs || mIsCapturingSystraces || mIsCapturingBugreports;
}
/**
@@ -180,19 +184,19 @@ public class CaptureHolder {
* for determining position in rolling queue
*/
private class CapturedState {
- public final String fileNamePrefix;
+ public final String fileNameBase;
public final long timeFromStartOfTestMS;
- public final int rank;
+ public int rank;
- public CapturedState(String fileNamePrefix, long timeFromStartOfTestMS, int rank) {
- this.fileNamePrefix = fileNamePrefix;
+ public CapturedState(String fileNameBase, long timeFromStartOfTestMS, int rank) {
+ this.fileNameBase = fileNameBase;
this.timeFromStartOfTestMS = timeFromStartOfTestMS;
this.rank = rank;
}
@Override
public String toString() {
- return "CapturedState { fileName:" + fileNamePrefix + ", Rank:" + rank + "}";
+ return "CapturedState { fileName:" + fileNameBase + ", Rank:" + rank + "}";
}
}
@@ -214,17 +218,22 @@ public class CaptureHolder {
@Override
public void run() {
- // Create two empty files and write that file name prefix to signal file, signalling
- // the listener script to write systrace and bugreport to those files
- if (mIsCapturingSystraces) {
+ // Write names of desired captures to signal file, signalling
+ // the listener script to write systrace and/or bugreport to those files
+ if (mIsCapturingSystraces || mIsCapturingBugreports) {
Log.d(TAG, "CaptureThread: signaling listener to write to:" +
- mNewCapturedState.fileNamePrefix);
+ mNewCapturedState.fileNameBase + "*");
try {
- new File(mNewCapturedState.fileNamePrefix + SYSTRACE_SUFFIX).createNewFile();
- new File(mNewCapturedState.fileNamePrefix + BUGREPORT_SUFFIX).createNewFile();
- OutputStream outputStream = new FileOutputStream(SIGNAL_FILE);
- outputStream.write(mNewCapturedState.fileNamePrefix.getBytes());
- outputStream.close();
+ PrintWriter writer = new PrintWriter(SIGNAL_FILE);
+ // mNewCapturedState.fileNameBase is the path and basename of the state files.
+ // Each suffix is used to tell the listener script to record that type of data.
+ if (mIsCapturingSystraces) {
+ writer.println(mNewCapturedState.fileNameBase + SYSTRACE_SUFFIX);
+ }
+ if (mIsCapturingBugreports) {
+ writer.println(mNewCapturedState.fileNameBase + BUGREPORT_SUFFIX);
+ }
+ writer.close();
} catch (IOException e) {
e.printStackTrace();
}
@@ -236,7 +245,7 @@ public class CaptureHolder {
WaveDataRingBuffer.ReadableWaveDeck deck = mWaveDataBuffer.getWaveDeck();
if (deck != null) {
AudioFileOutput audioFile = new AudioFileOutput(mContext,
- Uri.parse("file://mnt" + mNewCapturedState.fileNamePrefix
+ Uri.parse("file://mnt" + mNewCapturedState.fileNameBase
+ WAV_SUFFIX),
mSamplingRate);
boolean success = deck.writeToFile(audioFile);
@@ -246,11 +255,11 @@ public class CaptureHolder {
// Check for sys and bug finished
// loopback listener script signals completion by deleting signal file
- if (mIsCapturingSystraces) {
+ if (mIsCapturingSystraces || mIsCapturingBugreports) {
File signalFile = new File(SIGNAL_FILE);
while (signalFile.exists()) {
try {
- sleep(1);
+ sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -262,7 +271,7 @@ public class CaptureHolder {
if (mCapturedStates[mIndexToPlace] != null) {
Log.d(TAG, "Deleting capture: " + mCapturedStates[mIndexToPlace]);
for (String suffix : suffixes) {
- File oldFile = new File(mCapturedStates[mIndexToPlace].fileNamePrefix + suffix);
+ File oldFile = new File(mCapturedStates[mIndexToPlace].fileNameBase + suffix);
boolean deleted = oldFile.delete();
if (!deleted) {
Log.d(TAG, "Delete old capture: " + oldFile.toString() +
@@ -280,5 +289,10 @@ public class CaptureHolder {
Log.d(TAG, "Completed capture thread terminating");
}
+
+ // Sets the rank of the current capture to rank if it is greater than the current value
+ public synchronized void updateRank(int rank) {
+ mNewCapturedState.rank = Math.max(mNewCapturedState.rank, rank);
+ }
}
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
index d015352..988bc08 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
@@ -35,6 +35,7 @@ public class Constant {
public static final int BYTES_PER_SHORT = 2;
public static final int SHORTS_PER_INT = 2;
+ // FIXME Assumes 16-bit and mono, will not work for other bit depths or multi-channel.
public static final int BYTES_PER_FRAME = 2; // bytes per sample
// prime numbers that don't overlap with FFT frequencies
@@ -75,7 +76,14 @@ public class Constant {
public static final int MIN_NUM_CAPTURES = 1;
public static final int MAX_NUM_CAPTURES = 100;
public static final int DEFAULT_NUM_CAPTURES = 5;
+ public static final int MIN_IGNORE_FIRST_FRAMES = 0;
+ // impulse happens after 300 ms and shouldn't be ignored
+ public static final int MAX_IGNORE_FIRST_FRAMES = SAMPLING_RATE_MAX * 3 / 10;
+ public static final int DEFAULT_IGNORE_FIRST_FRAMES = 0;
+
// Controls size of pre allocated timestamp arrays
public static final int MAX_RECORDED_LATE_CALLBACKS_PER_SECOND = 2;
+ // Ignore first few buffer callback periods
+ public static final int BUFFER_PERIOD_DISCARD = 10;
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/GlitchDetectionThread.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/GlitchDetectionThread.java
index d88b065..e52c116 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/GlitchDetectionThread.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/GlitchDetectionThread.java
@@ -16,10 +16,10 @@
package org.drrickorang.loopback;
-import java.util.Arrays;
-
import android.util.Log;
+import java.util.Arrays;
+
/**
* This thread is responsible for detecting glitches in the samples.
@@ -213,7 +213,7 @@ public class GlitchDetectionThread extends Thread {
// Glitch Detected
mGlitches[mGlitchesIndex] = mFFTCount;
mGlitchesIndex++;
- if (mCaptureHolder.isCapturingSysTraces() || mCaptureHolder.isCapturingWavs()) {
+ if (mCaptureHolder.isCapturing()) {
checkGlitchConcentration();
}
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
index 3ae11d0..17b3bf8 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
@@ -16,11 +16,6 @@
package org.drrickorang.loopback;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.util.Arrays;
-
import android.Manifest;
import android.app.Activity;
import android.app.DialogFragment;
@@ -34,8 +29,8 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.media.AudioManager;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -55,8 +50,14 @@ import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.SeekBar;
-import android.widget.Toast;
import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Locale;
/**
@@ -121,10 +122,12 @@ public class LoopbackActivity extends Activity
private BufferPeriod mPlayerBufferPeriod = new BufferPeriod();
// for native buffer period
- private int[] mNativeRecorderBufferPeriodArray;
- private int mNativeRecorderMaxBufferPeriod;
- private int[] mNativePlayerBufferPeriodArray;
- private int mNativePlayerMaxBufferPeriod;
+ private int[] mNativeRecorderBufferPeriodArray;
+ private int mNativeRecorderMaxBufferPeriod;
+ private double mNativeRecorderStdDevBufferPeriod;
+ private int[] mNativePlayerBufferPeriodArray;
+ private int mNativePlayerMaxBufferPeriod;
+ private double mNativePlayerStdDevBufferPeriod;
private BufferCallbackTimes mRecorderCallbackTimes;
private BufferCallbackTimes mPlayerCallbackTimes;
@@ -136,6 +139,7 @@ public class LoopbackActivity extends Activity
private static final String INTENT_AUDIO_THREAD = "AudioThread";
private static final String INTENT_MIC_SOURCE = "MicSource";
private static final String INTENT_AUDIO_LEVEL = "AudioLevel";
+ private static final String INTENT_IGNORE_FIRST_FRAMES = "IgnoreFirstFrames";
private static final String INTENT_TEST_TYPE = "TestType";
private static final String INTENT_BUFFER_TEST_DURATION = "BufferTestDuration";
private static final String INTENT_NUMBER_LOAD_THREADS = "NumLoadThreads";
@@ -156,6 +160,7 @@ public class LoopbackActivity extends Activity
private int mSoundLevel;
private int mPlayerBufferSizeInBytes;
private int mRecorderBufferSizeInBytes;
+ private int mIgnoreFirstFrames; // TODO: this only applies to native mode
// for buffer test
private int[] mGlitchesData;
@@ -272,7 +277,7 @@ public class LoopbackActivity extends Activity
showToast("Java Buffer Test Completed");
break;
}
- if (getApp().isCaptureSysTraceEnabled()) {
+ if (getApp().isCaptureEnabled()) {
CaptureHolder.stopLoopbackListenerScript();
}
stopAudioTestThreads();
@@ -325,10 +330,14 @@ public class LoopbackActivity extends Activity
mFFTOverlapSamples = mNativeAudioThread.getNativeFFTOverlapSamples();
mWaveData = mNativeAudioThread.getWaveData();
mNativeRecorderBufferPeriodArray = mNativeAudioThread.getRecorderBufferPeriod();
- mNativeRecorderMaxBufferPeriod = mNativeAudioThread.
- getRecorderMaxBufferPeriod();
+ mNativeRecorderMaxBufferPeriod =
+ mNativeAudioThread.getRecorderMaxBufferPeriod();
+ mNativeRecorderStdDevBufferPeriod =
+ mNativeAudioThread.getRecorderStdDevBufferPeriod();
mNativePlayerBufferPeriodArray = mNativeAudioThread.getPlayerBufferPeriod();
mNativePlayerMaxBufferPeriod = mNativeAudioThread.getPlayerMaxBufferPeriod();
+ mNativePlayerStdDevBufferPeriod =
+ mNativeAudioThread.getPlayerStdDevBufferPeriod();
mRecorderCallbackTimes = mNativeAudioThread.getRecorderCallbackTimes();
mPlayerCallbackTimes = mNativeAudioThread.getPlayerCallbackTimes();
@@ -370,7 +379,7 @@ public class LoopbackActivity extends Activity
}
- if (getApp().isCaptureSysTraceEnabled()) {
+ if (getApp().isCaptureEnabled()) {
CaptureHolder.stopLoopbackListenerScript();
}
refreshSoundLevelBar();
@@ -577,6 +586,11 @@ public class LoopbackActivity extends Activity
mIntentRunning = true;
}
+ if (b.containsKey(INTENT_IGNORE_FIRST_FRAMES)) {
+ getApp().setIgnoreFirstFrames(b.getInt(INTENT_IGNORE_FIRST_FRAMES));
+ mIntentRunning = true;
+ }
+
if (b.containsKey(INTENT_AUDIO_LEVEL)) {
int audioLevel = b.getInt(INTENT_AUDIO_LEVEL);
if (audioLevel >= 0) {
@@ -749,6 +763,7 @@ public class LoopbackActivity extends Activity
mTestStartTimeString = (String) DateFormat.format("MMddkkmmss",
System.currentTimeMillis());
mMicSource = getApp().getMicSource();
+ mIgnoreFirstFrames = getApp().getIgnoreFirstFrames();
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
mBufferTestDurationInSeconds = getApp().getBufferTestDuration();
@@ -756,7 +771,8 @@ public class LoopbackActivity extends Activity
CaptureHolder captureHolder = new CaptureHolder(getApp().getNumStateCaptures(),
getFileNamePrefix(), getApp().isCaptureWavSnippetsEnabled(),
- getApp().isCaptureSysTraceEnabled(), this, mSamplingRate);
+ getApp().isCaptureSysTraceEnabled(), getApp().isCaptureBugreportEnabled(),
+ this, mSamplingRate);
log(" current sampling rate: " + mSamplingRate);
stopAudioTestThreads();
@@ -797,7 +813,7 @@ public class LoopbackActivity extends Activity
mNativeAudioThread = new NativeAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
mRecorderBufferSizeInBytes, micSourceMapped, mTestType,
mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds,
- captureHolder);
+ mIgnoreFirstFrames, captureHolder);
mNativeAudioThread.setMessageHandler(mMessageHandler);
mNativeAudioThread.mSessionId = sessionId;
mNativeAudioThread.start();
@@ -998,7 +1014,7 @@ public class LoopbackActivity extends Activity
/** Stop the ongoing test. */
- public void stopTests() throws InterruptedException{
+ public void stopTests() throws InterruptedException {
if (mAudioThread != null) {
mAudioThread.requestStopTest();
}
@@ -1509,7 +1525,7 @@ public class LoopbackActivity extends Activity
s.append("):\n");
s.append("SR: " + mSamplingRate + " Hz");
- s.append(" ChannelIndex: " + mChannelIndex);
+ s.append(" ChannelIndex: " + (mChannelIndex < 0 ? "MONO" : mChannelIndex));
switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
s.append(" Play Frames: " + playerFrames);
@@ -1532,14 +1548,15 @@ public class LoopbackActivity extends Activity
s.append(String.format(" Sound Level: %d/%d", mSoundLevel,
mBarMasterLevel.getMax()));
- String info = getApp().getSystemInfo();
- s.append(" " + info);
-
// Show short summary of results, round trip latency or number of glitches
- if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY &&
- mCorrelation.isValid()) {
- mTextViewResultSummary.setText(String.format("Latency: %.2f ms Confidence: %.2f",
- mCorrelation.mEstimatedLatencyMs, mCorrelation.mEstimatedLatencyConfidence));
+ if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) {
+ if (mIgnoreFirstFrames > 0) {
+ s.append(" First Frames Ignored: " + mIgnoreFirstFrames);
+ }
+ if (mCorrelation.isValid()) {
+ mTextViewResultSummary.setText(String.format("Latency: %.2f ms Confidence: %.2f",
+ mCorrelation.mEstimatedLatencyMs, mCorrelation.mEstimatedLatencyConfidence));
+ }
} else if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD &&
mGlitchesData != null) {
// show buffer test duration
@@ -1555,6 +1572,9 @@ public class LoopbackActivity extends Activity
mTextViewResultSummary.setText("");
}
+ String info = getApp().getSystemInfo();
+ s.append(" " + info);
+
mTextInfo.setText(s.toString());
}
@@ -1807,6 +1827,7 @@ public class LoopbackActivity extends Activity
switch (mTestType) {
case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
+ sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames);
if (mCorrelation.isValid()) {
sb.append(String.format("LatencyMs = %.2f", mCorrelation.mEstimatedLatencyMs)
+ endline);
@@ -1823,14 +1844,17 @@ public class LoopbackActivity extends Activity
// report recorder results
int[] recorderBufferData = null;
int recorderBufferDataMax = 0;
+ double recorderBufferDataStdDev = 0.0;
switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
recorderBufferData = mRecorderBufferPeriod.getBufferPeriodArray();
recorderBufferDataMax = mRecorderBufferPeriod.getMaxBufferPeriod();
+ recorderBufferDataStdDev = mRecorderBufferPeriod.getStdDevBufferPeriod();
break;
case Constant.AUDIO_THREAD_TYPE_NATIVE:
recorderBufferData = mNativeRecorderBufferPeriodArray;
recorderBufferDataMax = mNativeRecorderMaxBufferPeriod;
+ recorderBufferDataStdDev = mNativeRecorderStdDevBufferPeriod;
break;
}
// report expected recorder buffer period
@@ -1851,6 +1875,10 @@ public class LoopbackActivity extends Activity
sb.append("Recorder Buffer Periods At Expected = " +
String.format("%.5f%%", recorderPercentAtExpected * 100) + endline);
+ sb.append("Recorder Buffer Period Std Dev = "
+ + String.format(Locale.US, "%.5f ms", recorderBufferDataStdDev)
+ + endline);
+
// output thousandths of a percent not at expected buffer period
sb.append("kth% Late Recorder Buffer Callbacks = "
+ String.format("%.5f", (1 - recorderPercentAtExpected) * 100000)
@@ -1864,14 +1892,17 @@ public class LoopbackActivity extends Activity
// report player results
int[] playerBufferData = null;
int playerBufferDataMax = 0;
+ double playerBufferDataStdDev = 0.0;
switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
playerBufferData = mPlayerBufferPeriod.getBufferPeriodArray();
playerBufferDataMax = mPlayerBufferPeriod.getMaxBufferPeriod();
+ playerBufferDataStdDev = mPlayerBufferPeriod.getStdDevBufferPeriod();
break;
case Constant.AUDIO_THREAD_TYPE_NATIVE:
playerBufferData = mNativePlayerBufferPeriodArray;
playerBufferDataMax = mNativePlayerMaxBufferPeriod;
+ playerBufferDataStdDev = mNativePlayerStdDevBufferPeriod;
break;
}
// report expected player buffer period
@@ -1891,6 +1922,10 @@ public class LoopbackActivity extends Activity
sb.append("Player Buffer Periods At Expected = "
+ String.format("%.5f%%", playerPercentAtExpected * 100) + endline);
+ sb.append("Player Buffer Period Std Dev = "
+ + String.format(Locale.US, "%.5f ms", playerBufferDataStdDev)
+ + endline);
+
// output thousandths of a percent not at expected buffer period
sb.append("kth% Late Player Buffer Callbacks = "
+ String.format("%.5f", (1 - playerPercentAtExpected) * 100000)
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
index e779777..a0c4b61 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
@@ -44,10 +44,12 @@ public class LoopbackApplication extends Application {
private int mRecorderBuffSizeInBytes = 0; // for both native and java
private int mAudioThreadType = Constant.AUDIO_THREAD_TYPE_JAVA; //0:Java, 1:Native (JNI)
private int mMicSource = 3; //maps to MediaRecorder.AudioSource.VOICE_RECOGNITION;
+ private int mIgnoreFirstFrames = 0;
private int mBufferTestDurationInSeconds = 5;
private int mBufferTestWavePlotDurationInSeconds = 7;
private int mNumberOfLoadThreads = 4;
private boolean mCaptureSysTraceEnabled = false;
+ private boolean mCaptureBugreportEnabled = false;
private boolean mCaptureWavSnippetsEnabled = false;
private int mNumStateCaptures = Constant.DEFAULT_NUM_CAPTURES;
@@ -175,6 +177,13 @@ public class LoopbackApplication extends Application {
void setMicSource(int micSource) { mMicSource = micSource; }
+ int getIgnoreFirstFrames() {
+ return mIgnoreFirstFrames;
+ }
+
+ void setIgnoreFirstFrames(int ignoreFirstFrames) {
+ mIgnoreFirstFrames = ignoreFirstFrames;
+ }
int getPlayerBufferSizeInBytes() {
return mPlayerBufferSizeInBytes;
@@ -238,14 +247,26 @@ public class LoopbackApplication extends Application {
mCaptureSysTraceEnabled = enabled;
}
+ public void setCaptureBugreportEnabled(boolean enabled) {
+ mCaptureBugreportEnabled = enabled;
+ }
+
public void setCaptureWavsEnabled (boolean enabled){
mCaptureWavSnippetsEnabled = enabled;
}
- public boolean isCaptureSysTraceEnabled () {
+ public boolean isCaptureEnabled() {
+ return isCaptureSysTraceEnabled() || isCaptureBugreportEnabled();
+ }
+
+ public boolean isCaptureSysTraceEnabled() {
return mCaptureSysTraceEnabled;
}
+ public boolean isCaptureBugreportEnabled() {
+ return mCaptureBugreportEnabled;
+ }
+
public int getNumStateCaptures() {
return mNumStateCaptures;
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
index 48d9fdc..1bb2ae6 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
@@ -55,6 +55,7 @@ public class NativeAudioThread extends Thread {
private int mMinPlayerBufferSizeInBytes = 0;
private int mMinRecorderBuffSizeInBytes = 0; // currently not used
private int mMicSource;
+ private int mIgnoreFirstFrames;
private boolean mIsRequestStop = false;
private Handler mMessageHandler;
@@ -64,8 +65,10 @@ public class NativeAudioThread extends Thread {
// for buffer test
private int[] mRecorderBufferPeriod;
private int mRecorderMaxBufferPeriod;
+ private double mRecorderStdDevBufferPeriod;
private int[] mPlayerBufferPeriod;
private int mPlayerMaxBufferPeriod;
+ private double mPlayerStdDevBufferPeriod;
private BufferCallbackTimes mPlayerCallbackTimes;
private BufferCallbackTimes mRecorderCallbackTimes;
private int mBufferTestWavePlotDurationInSeconds;
@@ -84,7 +87,8 @@ public class NativeAudioThread extends Thread {
public NativeAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes,
int micSource, int testType, int bufferTestDurationInSeconds,
- int bufferTestWavePlotDurationInSeconds, CaptureHolder captureHolder) {
+ int bufferTestWavePlotDurationInSeconds, int ignoreFirstFrames,
+ CaptureHolder captureHolder) {
mSamplingRate = samplingRate;
mMinPlayerBufferSizeInBytes = playerBufferInBytes;
mMinRecorderBuffSizeInBytes = recorderBufferInBytes;
@@ -92,6 +96,7 @@ public class NativeAudioThread extends Thread {
mTestType = testType;
mBufferTestDurationInSeconds = bufferTestDurationInSeconds;
mBufferTestWavePlotDurationInSeconds = bufferTestWavePlotDurationInSeconds;
+ mIgnoreFirstFrames = ignoreFirstFrames;
mCaptureHolder = captureHolder;
setName("Loopback_NativeAudio");
}
@@ -113,18 +118,22 @@ public class NativeAudioThread extends Thread {
public native long slesInit(int samplingRate, int frameCount, int micSource,
int testType, double frequency1, ByteBuffer byteBuffer,
short[] sincTone, int maxRecordedLateCallbacks,
- CaptureHolder captureHolder);
+ int ignoreFirstFrames);
public native int slesProcessNext(long sles_data, double[] samples, long offset);
public native int slesDestroy(long sles_data);
// to get buffer period data
- public native int[] slesGetRecorderBufferPeriod(long sles_data);
- public native int slesGetRecorderMaxBufferPeriod(long sles_data);
- public native int[] slesGetPlayerBufferPeriod(long sles_data);
- public native int slesGetPlayerMaxBufferPeriod(long sles_data);
+ public native int[] slesGetRecorderBufferPeriod(long sles_data);
+ public native int slesGetRecorderMaxBufferPeriod(long sles_data);
+ public native double slesGetRecorderVarianceBufferPeriod(long sles_data);
+ public native int[] slesGetPlayerBufferPeriod(long sles_data);
+ public native int slesGetPlayerMaxBufferPeriod(long sles_data);
+ public native double slesGetPlayerVarianceBufferPeriod(long sles_data);
public native BufferCallbackTimes slesGetPlayerCallbackTimeStamps(long sles_data);
public native BufferCallbackTimes slesGetRecorderCallbackTimeStamps(long sles_data);
+ public native int slesGetCaptureRank(long sles_data);
+
public void run() {
setPriority(Thread.MAX_PRIORITY);
@@ -167,7 +176,7 @@ public class NativeAudioThread extends Thread {
mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME, mMicSource, mTestType,
mFrequency1, mPipeByteBuffer.getByteBuffer(), loopbackTone,
mBufferTestDurationInSeconds * Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND,
- mCaptureHolder);
+ mIgnoreFirstFrames);
log(String.format("sles_data = 0x%X", sles_data));
if (sles_data == 0) {
@@ -228,6 +237,11 @@ public class NativeAudioThread extends Thread {
if (mIsRequestStop) {
break;
} else {
+ int rank = slesGetCaptureRank(sles_data);
+ if (rank > 0) {
+ //log("Late callback detected");
+ mCaptureHolder.captureState(rank);
+ }
try {
final int setUpTime = 100;
sleep(setUpTime); //just to let it start properly
@@ -246,8 +260,10 @@ public class NativeAudioThread extends Thread {
// collect buffer period data
mRecorderBufferPeriod = slesGetRecorderBufferPeriod(sles_data);
mRecorderMaxBufferPeriod = slesGetRecorderMaxBufferPeriod(sles_data);
+ mRecorderStdDevBufferPeriod = Math.sqrt(slesGetRecorderVarianceBufferPeriod(sles_data));
mPlayerBufferPeriod = slesGetPlayerBufferPeriod(sles_data);
mPlayerMaxBufferPeriod = slesGetPlayerMaxBufferPeriod(sles_data);
+ mPlayerStdDevBufferPeriod = Math.sqrt(slesGetPlayerVarianceBufferPeriod(sles_data));
mPlayerCallbackTimes = slesGetPlayerCallbackTimeStamps(sles_data);
mRecorderCallbackTimes = slesGetRecorderCallbackTimeStamps(sles_data);
@@ -427,21 +443,25 @@ public class NativeAudioThread extends Thread {
return mRecorderBufferPeriod;
}
-
public int getRecorderMaxBufferPeriod() {
return mRecorderMaxBufferPeriod;
}
+ public double getRecorderStdDevBufferPeriod() {
+ return mRecorderStdDevBufferPeriod;
+ }
public int[] getPlayerBufferPeriod() {
return mPlayerBufferPeriod;
}
-
public int getPlayerMaxBufferPeriod() {
return mPlayerMaxBufferPeriod;
}
+ public double getPlayerStdDevBufferPeriod() {
+ return mPlayerStdDevBufferPeriod;
+ }
public int[] getNativeAllGlitches() {
return mAllGlitches;
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/PerformanceMeasurement.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/PerformanceMeasurement.java
index bd16707..35c5e18 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/PerformanceMeasurement.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/PerformanceMeasurement.java
@@ -107,17 +107,16 @@ public class PerformanceMeasurement {
/**
* Determine percent of Buffer Period Callbacks that occurred at the expected time
- * Note: due to current rounding in buffer sampling callbacks occurring at 1 ms after the
- * expected buffer period are also counted in the returned percentage
* Returns a value between 0 and 1
*/
public float percentBufferPeriodsAtExpected() {
int occurrenceNearExpectedBufferPeriod = 0;
- // indicate how many beams around mExpectedBufferPeriod do we want to add to the count
- int numberOfBeams = 2;
- int start = Math.max(0, mExpectedBufferPeriodMs - numberOfBeams);
- int end = Math.min(mBufferData.length, mExpectedBufferPeriodMs + numberOfBeams);
- for (int i = start; i < end; i++) {
+ // indicate how many buckets around mExpectedBufferPeriod do we want to add to the count
+ int acceptableOffset = 2;
+ int start = Math.max(0, mExpectedBufferPeriodMs - acceptableOffset);
+ int end = Math.min(mBufferData.length - 1, mExpectedBufferPeriodMs + acceptableOffset);
+ // include the next bucket too because the period is rounded up
+ for (int i = start; i <= end; i++) {
occurrenceNearExpectedBufferPeriod += mBufferData[i];
}
return ((float) occurrenceNearExpectedBufferPeriod) / mTotalOccurrence;
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
index 8b5a2d7..9224abf 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
@@ -23,12 +23,12 @@ import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.PopupWindow;
import android.widget.Spinner;
-import android.widget.ArrayAdapter;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.ToggleButton;
@@ -53,7 +53,9 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
private SettingsPicker mWavePlotDurationUI;
private SettingsPicker mLoadThreadUI;
private SettingsPicker mNumCapturesUI;
+ private SettingsPicker mIgnoreFirstFramesUI;
private ToggleButton mSystraceToggleButton;
+ private ToggleButton mBugreportToggleButton;
private ToggleButton mWavCaptureToggleButton;
ArrayAdapter<CharSequence> mAdapterSamplingRate;
@@ -220,10 +222,29 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
mWavCaptureToggleButton.setChecked(getApp().isCaptureWavSnippetsEnabled());
mWavCaptureToggleButton.setOnCheckedChangeListener(this);
+ mBugreportToggleButton = (ToggleButton) findViewById(R.id.BugreportEnabledToggle);
+ mBugreportToggleButton.setChecked(getApp().isCaptureBugreportEnabled());
+ mBugreportToggleButton.setOnCheckedChangeListener(this);
+
mSystraceToggleButton = (ToggleButton) findViewById(R.id.SystraceEnabledToggle);
mSystraceToggleButton.setChecked(getApp().isCaptureSysTraceEnabled());
mSystraceToggleButton.setOnCheckedChangeListener(this);
+ // Settings Picker for number of frames to ignore at the beginning
+ mIgnoreFirstFramesUI = (SettingsPicker) findViewById(R.id.ignoreFirstFramesSettingPicker);
+ mIgnoreFirstFramesUI.setMinMaxDefault(Constant.MIN_IGNORE_FIRST_FRAMES,
+ Constant.MAX_IGNORE_FIRST_FRAMES, getApp().getIgnoreFirstFrames());
+ mIgnoreFirstFramesUI.setTitle(getResources().getString(R.string.labelIgnoreFirstFrames,
+ Constant.MAX_IGNORE_FIRST_FRAMES));
+ mIgnoreFirstFramesUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() {
+ @Override
+ public void settingChanged(int frames) {
+ log("new number of first frames to ignore: " + frames);
+ getApp().setIgnoreFirstFrames(frames);
+ setSettingsHaveChanged();
+ }
+ });
+
refresh();
}
@@ -266,7 +287,7 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
mSpinnerChannelIndex.setEnabled(false);
}
- mNumCapturesUI.setEnabled(getApp().isCaptureSysTraceEnabled() ||
+ mNumCapturesUI.setEnabled(getApp().isCaptureEnabled() ||
getApp().isCaptureWavSnippetsEnabled());
String info = getApp().getSystemInfo();
@@ -319,8 +340,10 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
getApp().setCaptureWavsEnabled(isChecked);
} else if (buttonView.getId() == mSystraceToggleButton.getId()) {
getApp().setCaptureSysTraceEnabled(isChecked);
+ } else if (buttonView.getId() == mBugreportToggleButton.getId()) {
+ getApp().setCaptureBugreportEnabled(isChecked);
}
- mNumCapturesUI.setEnabled(getApp().isCaptureSysTraceEnabled() ||
+ mNumCapturesUI.setEnabled(getApp().isCaptureEnabled() ||
getApp().isCaptureWavSnippetsEnabled());
}