diff options
Diffstat (limited to 'LoopbackApp/app/src/main/java/org')
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()); } |