summaryrefslogtreecommitdiff
path: root/LoopbackApp/app/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'LoopbackApp/app/src/main/java/org')
-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());
}