summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Kasten <gkasten@google.com>2016-06-02 08:09:36 -0700
committerGlenn Kasten <gkasten@google.com>2016-06-02 08:10:15 -0700
commitc58bf2f1d8871c89e1d32b237193997cd55ec7d1 (patch)
tree2b76ec84d2fb91011bf4a07ac91112ccb404336a
parent89b30344fe05be0580628a9e722567edaf27a956 (diff)
downloaddrrickorang-c58bf2f1d8871c89e1d32b237193997cd55ec7d1.tar.gz
Version 14
Snap to commit 27ed08872221cd6291777cab07bcc275e2c88106
-rw-r--r--LoopbackApp/app/src/main/AndroidManifest.xml7
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferCallbackTimes.java42
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java43
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java47
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java233
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java1
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/WavePlotView.java242
-rw-r--r--LoopbackApp/app/src/main/res/values/strings.xml2
8 files changed, 384 insertions, 233 deletions
diff --git a/LoopbackApp/app/src/main/AndroidManifest.xml b/LoopbackApp/app/src/main/AndroidManifest.xml
index 1608a12..fbe8ba6 100644
--- a/LoopbackApp/app/src/main/AndroidManifest.xml
+++ b/LoopbackApp/app/src/main/AndroidManifest.xml
@@ -23,14 +23,15 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.drrickorang.loopback"
- android:versionCode="12"
- android:versionName="0.9.7">
+ android:versionCode="14"
+ android:versionName="0.9.71">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.STORAGE" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
+ <uses-permission android:name="android.permission.VIBRATE" />
<application
android:label="@string/app_name"
@@ -38,7 +39,6 @@
android:name="LoopbackApplication">
<activity
android:name="org.drrickorang.loopback.LoopbackActivity"
- android:screenOrientation="sensorPortrait"
android:theme="@android:style/Theme.Holo.Light"
android:configChanges="orientation|keyboardHidden|screenLayout"
android:launchMode="singleTop">
@@ -53,7 +53,6 @@
<activity
android:name="org.drrickorang.loopback.SettingsActivity"
android:parentActivityName="org.drrickorang.loopback.LoopbackActivity"
- android:screenOrientation="sensorPortrait"
android:theme="@android:style/Theme.Holo.Light"
android:configChanges="orientation|screenLayout"
android:windowSoftInputMode="stateAlwaysHidden"
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferCallbackTimes.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferCallbackTimes.java
index 325459e..28e8c76 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferCallbackTimes.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferCallbackTimes.java
@@ -16,13 +16,17 @@
package org.drrickorang.loopback;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
import java.util.Iterator;
/**
* Maintains and returns pairs of callback timestamps (in milliseconds since beginning of test) and
* lengths (milliseconds between a callback and the previous callback).
*/
-public class BufferCallbackTimes implements Iterable<BufferCallbackTimes.BufferCallback> {
+public class BufferCallbackTimes implements Iterable<BufferCallbackTimes.BufferCallback>, Parcelable {
private final int[] mTimeStamps;
private final short[] mCallbackDurations;
private final short mExpectedBufferPeriod;
@@ -116,6 +120,42 @@ public class BufferCallbackTimes implements Iterable<BufferCallbackTimes.BufferC
};
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Bundle out = new Bundle();
+ out.putIntArray("mTimeStamps", mTimeStamps);
+ out.putShortArray("mCallbackDurations", mCallbackDurations);
+ out.putShort("mExpectedBufferPeriod", mExpectedBufferPeriod);
+ out.putBoolean("mExceededCapacity", mExceededCapacity);
+ out.putInt("mIndex", mIndex);
+ dest.writeBundle(out);
+ }
+
+ private BufferCallbackTimes(Parcel source) {
+ Bundle in = source.readBundle(getClass().getClassLoader());
+ mTimeStamps = in.getIntArray("mTimeStamps");
+ mCallbackDurations = in.getShortArray("mCallbackDurations");
+ mExpectedBufferPeriod = in.getShort("mExpectedBufferPeriod");
+ mExceededCapacity = in.getBoolean("mExceededCapacity");
+ mIndex = in.getInt("mIndex");
+ }
+
+ public static final Parcelable.Creator<BufferCallbackTimes> CREATOR
+ = new Parcelable.Creator<BufferCallbackTimes>() {
+ public BufferCallbackTimes createFromParcel(Parcel in) {
+ return new BufferCallbackTimes(in);
+ }
+
+ public BufferCallbackTimes[] newArray(int size) {
+ return new BufferCallbackTimes[size];
+ }
+ };
+
/** Wrapper for iteration over timestamp and length pairs */
public class BufferCallback {
public final int timeStamp;
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 7716d9f..b4cd87e 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/BufferPeriod.java
@@ -18,6 +18,9 @@ package org.drrickorang.loopback;
import java.util.Arrays;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
@@ -27,7 +30,7 @@ import android.util.Log;
*/
//TODO for native mode, should use a scale more accurate than the current 1ms
-public class BufferPeriod {
+public class BufferPeriod implements Parcelable {
private static final String TAG = "BufferPeriod";
private long mStartTimeNs = 0; // first time collectBufferPeriod() is called
@@ -42,6 +45,10 @@ public class BufferPeriod {
private BufferCallbackTimes mCallbackTimes;
private CaptureHolder mCaptureHolder;
+ public BufferPeriod() {
+ // Default constructor for when no data will be restored
+ }
+
/**
* For player, this function is called before every AudioTrack.write().
* For recorder, this function is called after every AudioRecord.read() with read > 0.
@@ -117,6 +124,40 @@ public class BufferPeriod {
return mCallbackTimes;
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ // Only save values which represent the results. Any ongoing timing would not give useful
+ // results after a save/restore.
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Bundle out = new Bundle();
+ out.putInt("mMaxBufferPeriod", mMaxBufferPeriod);
+ out.putIntArray("mBufferPeriod", mBufferPeriod);
+ out.putParcelable("mCallbackTimes", mCallbackTimes);
+ dest.writeBundle(out);
+ }
+
+ private BufferPeriod(Parcel source) {
+ Bundle in = source.readBundle(getClass().getClassLoader());
+ mMaxBufferPeriod = in.getInt("mMaxBufferPeriod");
+ mBufferPeriod = in.getIntArray("mBufferPeriod");
+ mCallbackTimes = in.getParcelable("mCallbackTimes");
+ }
+
+ public static final Parcelable.Creator<BufferPeriod> CREATOR
+ = new Parcelable.Creator<BufferPeriod>() {
+ public BufferPeriod createFromParcel(Parcel in) {
+ return new BufferPeriod(in);
+ }
+
+ public BufferPeriod[] newArray(int size) {
+ return new BufferPeriod[size];
+ }
+ };
+
private static void log(String msg) {
Log.v(TAG, msg);
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
index 1094fb0..d12cd7b 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
@@ -16,6 +16,9 @@
package org.drrickorang.loopback;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
@@ -23,7 +26,7 @@ import android.util.Log;
* This class is used to automatically estimate latency and its confidence.
*/
-public class Correlation {
+public class Correlation implements Parcelable {
private static final String TAG = "Correlation";
private int mBlockSize = 4096;
@@ -39,6 +42,9 @@ public class Correlation {
private boolean mDataIsValid = false; // Used to mark computed latency information is available
+ public Correlation() {
+ // Default constructor for when no data will be restored
+ }
public void init(int blockSize, int samplingRate) {
mBlockSize = blockSize;
@@ -179,6 +185,45 @@ public class Correlation {
return status;
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ // Store the results before this object is destroyed
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean("mDataIsValid", mDataIsValid);
+ if(mDataIsValid) {
+ bundle.putDouble("mEstimatedLatencySamples", mEstimatedLatencySamples);
+ bundle.putDouble("mEstimatedLatencyMs", mEstimatedLatencyMs);
+ bundle.putDouble("mEstimatedLatencyConfidence", mEstimatedLatencyConfidence);
+ }
+ dest.writeBundle(bundle);
+ }
+
+ // Restore the results which were previously calculated
+ private Correlation(Parcel in) {
+ Bundle bundle = in.readBundle(getClass().getClassLoader());
+ mDataIsValid = bundle.getBoolean("mDataIsValid");
+ if(mDataIsValid) {
+ mEstimatedLatencySamples = bundle.getDouble("mEstimatedLatencySamples");
+ mEstimatedLatencyMs = bundle.getDouble("mEstimatedLatencyMs");
+ mEstimatedLatencyConfidence = bundle.getDouble("mEstimatedLatencyConfidence");
+ }
+ }
+
+ public static final Parcelable.Creator<Correlation> CREATOR
+ = new Parcelable.Creator<Correlation>() {
+ public Correlation createFromParcel(Parcel in) {
+ return new Correlation(in);
+ }
+
+ public Correlation[] newArray(int size) {
+ return new Correlation[size];
+ }
+ };
private static void log(String msg) {
Log.v(TAG, msg);
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 c0f4bcb..3ae11d0 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
@@ -150,8 +150,10 @@ public class LoopbackActivity extends Activity
// Note: these values should only be assigned in restartAudioSystem()
private int mAudioThreadType = Constant.UNKNOWN;
+ private int mMicSource;
private int mSamplingRate;
private int mChannelIndex;
+ private int mSoundLevel;
private int mPlayerBufferSizeInBytes;
private int mRecorderBufferSizeInBytes;
@@ -162,6 +164,8 @@ public class LoopbackActivity extends Activity
private int mFFTOverlapSamples;
private long mBufferTestStartTime;
private int mBufferTestElapsedSeconds;
+ private int mBufferTestDurationInSeconds;
+ private int mBufferTestWavePlotDurationInSeconds;
// threads that load CPUs
private LoadThread[] mLoadThreads;
@@ -459,7 +463,7 @@ public class LoopbackActivity extends Activity
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_MUSIC,
progress, 0);
- refreshState();
+ refreshSoundLevelBar();
log("Changed stream volume to: " + progress);
}
});
@@ -469,12 +473,15 @@ public class LoopbackActivity extends Activity
mTextViewCurrentLevel.setTextSize(15);
mTextViewResultSummary = (TextView) findViewById(R.id.resultSummary);
- refreshState();
+ refreshSoundLevelBar();
+
+ if(savedInstanceState != null) {
+ restoreInstanceState(savedInstanceState);
+ }
applyIntent(getIntent());
}
-
@Override
protected void onStart() {
super.onStart();
@@ -501,7 +508,6 @@ public class LoopbackActivity extends Activity
}
}
-
@Override
public void onNewIntent(Intent intent) {
log("On New Intent called!");
@@ -700,11 +706,6 @@ public class LoopbackActivity extends Activity
case R.id.action_settings:
if (!isBusy()) {
- // Hide test result controls as results will be null on returning from settings
- findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
- findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
- findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE);
-
// Launch settings activity
Intent mySettingsIntent = new Intent(this, SettingsActivity.class);
startActivityForResult(mySettingsIntent, SETTINGS_ACTIVITY_REQUEST);
@@ -747,9 +748,11 @@ public class LoopbackActivity extends Activity
mRecorderBufferSizeInBytes = getApp().getRecorderBufferSizeInBytes();
mTestStartTimeString = (String) DateFormat.format("MMddkkmmss",
System.currentTimeMillis());
- int micSource = getApp().getMicSource();
- int bufferTestDurationInSeconds = getApp().getBufferTestDuration();
- int bufferTestWavePlotDurationInSeconds = getApp().getBufferTestWavePlotDuration();
+ mMicSource = getApp().getMicSource();
+ AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mBufferTestDurationInSeconds = getApp().getBufferTestDuration();
+ mBufferTestWavePlotDurationInSeconds = getApp().getBufferTestWavePlotDuration();
CaptureHolder captureHolder = new CaptureHolder(getApp().getNumStateCaptures(),
getFileNamePrefix(), getApp().isCaptureWavSnippetsEnabled(),
@@ -762,38 +765,38 @@ public class LoopbackActivity extends Activity
int micSourceMapped;
switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
- micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_JAVA, micSource);
+ micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_JAVA, mMicSource);
int expectedRecorderBufferPeriod = Math.round(
(float) (mRecorderBufferSizeInBytes * Constant.MILLIS_PER_SECOND)
/ (Constant.BYTES_PER_FRAME * mSamplingRate));
mRecorderBufferPeriod.prepareMemberObjects(
- Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * bufferTestDurationInSeconds,
+ Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds,
expectedRecorderBufferPeriod, captureHolder);
int expectedPlayerBufferPeriod = Math.round(
(float) (mPlayerBufferSizeInBytes * Constant.MILLIS_PER_SECOND)
/ (Constant.BYTES_PER_FRAME * mSamplingRate));
mPlayerBufferPeriod.prepareMemberObjects(
- Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * bufferTestDurationInSeconds,
+ Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds,
expectedPlayerBufferPeriod, captureHolder);
mAudioThread = new LoopbackAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
mRecorderBufferSizeInBytes, micSourceMapped, mRecorderBufferPeriod,
- mPlayerBufferPeriod, mTestType, bufferTestDurationInSeconds,
- bufferTestWavePlotDurationInSeconds, getApplicationContext(),
+ mPlayerBufferPeriod, mTestType, mBufferTestDurationInSeconds,
+ mBufferTestWavePlotDurationInSeconds, getApplicationContext(),
mChannelIndex, captureHolder);
mAudioThread.setMessageHandler(mMessageHandler);
mAudioThread.mSessionId = sessionId;
mAudioThread.start();
break;
case Constant.AUDIO_THREAD_TYPE_NATIVE:
- micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE, micSource);
+ micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE, mMicSource);
// Note: mRecorderBufferSizeInBytes will not actually be used, since recorder buffer
// size = player buffer size in native mode
mNativeAudioThread = new NativeAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
mRecorderBufferSizeInBytes, micSourceMapped, mTestType,
- bufferTestDurationInSeconds, bufferTestWavePlotDurationInSeconds,
+ mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds,
captureHolder);
mNativeAudioThread.setMessageHandler(mMessageHandler);
mNativeAudioThread.mSessionId = sessionId;
@@ -803,7 +806,6 @@ public class LoopbackActivity extends Activity
startLoadThreads();
- mWavePlotView.setSamplingRate(mSamplingRate);
refreshState();
}
@@ -893,7 +895,7 @@ public class LoopbackActivity extends Activity
/** Start the latency test. */
- public void onButtonLatencyTest(View view) throws InterruptedException{
+ public void onButtonLatencyTest(View view) throws InterruptedException {
if (isBusy()) {
stopTests();
return;
@@ -1265,11 +1267,6 @@ public class LoopbackActivity extends Activity
case SETTINGS_ACTIVITY_REQUEST:
log("return from new settings!");
- // here we wipe out all previous results, in order to avoid the condition where
- // previous results does not match the new settings
- resetResults();
- refreshState();
- refreshPlots();
break;
}
}
@@ -1285,6 +1282,9 @@ public class LoopbackActivity extends Activity
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
mBarMasterLevel.setProgress(currentVolume);
+
+ mTextViewCurrentLevel.setText(String.format("Current Sound Level: %d/%d", currentVolume,
+ mBarMasterLevel.getMax()));
}
@@ -1490,78 +1490,72 @@ public class LoopbackActivity extends Activity
/** Redraw the plot according to mWaveData */
void refreshPlots() {
- mWavePlotView.setData(mWaveData);
+ mWavePlotView.setData(mWaveData, mSamplingRate);
mWavePlotView.redraw();
}
-
/** Refresh the text on the main activity that shows the app states and audio settings. */
void refreshState() {
log("refreshState!");
-
- //get current audio level
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
- mBarMasterLevel.setProgress(currentVolume);
-
- mTextViewCurrentLevel.setText(String.format("Sound Level: %d/%d", currentVolume,
- mBarMasterLevel.getMax()));
-
- log("refreshState 2b");
+ refreshSoundLevelBar();
// get info
- int samplingRate = getApp().getSamplingRate();
- int channelIndex = getApp().getChannelIndex();
- int playerBuffer = getApp().getPlayerBufferSizeInBytes() / Constant.BYTES_PER_FRAME;
- int recorderBuffer = getApp().getRecorderBufferSizeInBytes() / Constant.BYTES_PER_FRAME;
+ int playerFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME;
+ int recorderFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME;
StringBuilder s = new StringBuilder(200);
- s.append("SR: " + samplingRate + " Hz");
- s.append(" ChannelIndex: " + channelIndex);
- int audioThreadType = getApp().getAudioThreadType();
- switch (audioThreadType) {
+
+ s.append("Settings from most recent run (at ");
+ s.append(mTestStartTimeString);
+ s.append("):\n");
+
+ s.append("SR: " + mSamplingRate + " Hz");
+ s.append(" ChannelIndex: " + mChannelIndex);
+ switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
- s.append(" Play Frames: " + playerBuffer);
- s.append(" Record Frames: " + recorderBuffer);
+ s.append(" Play Frames: " + playerFrames);
+ s.append(" Record Frames: " + recorderFrames);
s.append(" Audio: JAVA");
break;
case Constant.AUDIO_THREAD_TYPE_NATIVE:
- s.append(" Frames: " + playerBuffer);
+ s.append(" Frames: " + playerFrames);
s.append(" Audio: NATIVE");
break;
}
// mic source
- int micSource = getApp().getMicSource();
- String micSourceName = getApp().getMicSourceString(micSource);
+ String micSourceName = getApp().getMicSourceString(mMicSource);
if (micSourceName != null) {
s.append(String.format(" Mic: %s", micSourceName));
}
+ // sound level at start of test
+ s.append(String.format(" Sound Level: %d/%d", mSoundLevel,
+ mBarMasterLevel.getMax()));
+
String info = getApp().getSystemInfo();
s.append(" " + info);
- // show buffer test duration
- int bufferTestDuration = getApp().getBufferTestDuration();
- s.append("\nBuffer Test Duration: " + bufferTestDuration + "s");
-
- // show buffer test wave plot duration
- int bufferTestWavePlotDuration = getApp().getBufferTestWavePlotDuration();
- s.append(" Buffer Test Wave Plot Duration: last " + bufferTestWavePlotDuration + "s");
-
// Show short summary of results, round trip latency or number of glitches
- mTextInfo.setText(s.toString());
if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY &&
mCorrelation.isValid()) {
- mTextViewResultSummary.setText(String.format("Latency: %s Confidence: %.2f",
- String.format("%.2f ms", mCorrelation.mEstimatedLatencyMs),
- mCorrelation.mEstimatedLatencyConfidence));
+ 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
+ s.append("\nBuffer Test Duration: " + mBufferTestDurationInSeconds + " s");
+
+ // show buffer test wave plot duration
+ s.append(" Buffer Test Wave Plot Duration: last " +
+ mBufferTestWavePlotDurationInSeconds + " s");
+
mTextViewResultSummary.setText(getResources().getString(R.string.numGlitches) + " " +
estimateNumberOfGlitches(mGlitchesData));
} else {
mTextViewResultSummary.setText("");
}
+
+ mTextInfo.setText(s.toString());
}
@@ -1782,22 +1776,21 @@ public class LoopbackActivity extends Activity
}
- private StringBuilder getReport(){
+ private StringBuilder getReport() {
String endline = "\n";
final int stringLength = 300;
StringBuilder sb = new StringBuilder(stringLength);
sb.append("DateTime = " + mTestStartTimeString + endline);
- sb.append(INTENT_SAMPLING_FREQUENCY + " = " + getApp().getSamplingRate() + endline);
- sb.append(INTENT_RECORDER_BUFFER + " = " + getApp().getRecorderBufferSizeInBytes() /
+ sb.append(INTENT_SAMPLING_FREQUENCY + " = " + mSamplingRate + endline);
+ sb.append(INTENT_CHANNEL_INDEX + " = " + mChannelIndex + endline);
+ sb.append(INTENT_RECORDER_BUFFER + " = " + mRecorderBufferSizeInBytes /
Constant.BYTES_PER_FRAME + endline);
- sb.append(INTENT_PLAYER_BUFFER + " = "
- + getApp().getPlayerBufferSizeInBytes() / Constant.BYTES_PER_FRAME + endline);
- sb.append(INTENT_AUDIO_THREAD + " = " + getApp().getAudioThreadType() + endline);
- int micSource = getApp().getMicSource();
-
+ sb.append(INTENT_PLAYER_BUFFER + " = " + mPlayerBufferSizeInBytes /
+ Constant.BYTES_PER_FRAME + endline);
+ sb.append(INTENT_AUDIO_THREAD + " = " + mAudioThreadType + endline);
String audioType = "unknown";
- switch (getApp().getAudioThreadType()) {
+ switch (mAudioThreadType) {
case Constant.AUDIO_THREAD_TYPE_JAVA:
audioType = "JAVA";
break;
@@ -1807,13 +1800,10 @@ public class LoopbackActivity extends Activity
}
sb.append(INTENT_AUDIO_THREAD + "_String = " + audioType + endline);
- sb.append(INTENT_MIC_SOURCE + " = " + micSource + endline);
- sb.append(INTENT_MIC_SOURCE + "_String = " + getApp().getMicSourceString(micSource)
+ sb.append(INTENT_MIC_SOURCE + " = " + mMicSource + endline);
+ sb.append(INTENT_MIC_SOURCE + "_String = " + getApp().getMicSourceString(mMicSource)
+ endline);
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-
- int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
- sb.append(INTENT_AUDIO_LEVEL + " = " + currentVolume + endline);
+ sb.append(INTENT_AUDIO_LEVEL + " = " + mSoundLevel + endline);
switch (mTestType) {
case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
@@ -1828,7 +1818,7 @@ public class LoopbackActivity extends Activity
mCorrelation.mEstimatedLatencyConfidence) + endline);
break;
case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
- sb.append("Buffer Test Duration (s) = " + mBufferTestElapsedSeconds + endline);
+ sb.append("Buffer Test Duration (s) = " + mBufferTestDurationInSeconds + endline);
// report recorder results
int[] recorderBufferData = null;
@@ -2114,4 +2104,89 @@ public class LoopbackActivity extends Activity
SaveFilesWithDialog();
}
}
+
+ private void restoreInstanceState(Bundle in) {
+ mWaveData = in.getDoubleArray("mWaveData");
+
+ mTestType = in.getInt("mTestType");
+ mMicSource = in.getInt("mMicSource");
+ mAudioThreadType = in.getInt("mAudioThreadType");
+ mSamplingRate = in.getInt("mSamplingRate");
+ mChannelIndex = in.getInt("mChannelIndex");
+ mSoundLevel = in.getInt("mSoundLevel");
+ mPlayerBufferSizeInBytes = in.getInt("mPlayerBufferSizeInBytes");
+ mRecorderBufferSizeInBytes = in.getInt("mRecorderBufferSizeInBytes");
+
+ mTestStartTimeString = in.getString("mTestStartTimeString");
+
+ mGlitchesData = in.getIntArray("mGlitchesData");
+ if(mGlitchesData != null) {
+ mGlitchingIntervalTooLong = in.getBoolean("mGlitchingIntervalTooLong");
+ mFFTSamplingSize = in.getInt("mFFTSamplingSize");
+ mFFTOverlapSamples = in.getInt("mFFTOverlapSamples");
+ mBufferTestStartTime = in.getLong("mBufferTestStartTime");
+ mBufferTestElapsedSeconds = in.getInt("mBufferTestElapsedSeconds");
+ mBufferTestDurationInSeconds = in.getInt("mBufferTestDurationInSeconds");
+ mBufferTestWavePlotDurationInSeconds =
+ in.getInt("mBufferTestWavePlotDurationInSeconds");
+
+ findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE);
+ }
+
+ if(mWaveData != null) {
+ mCorrelation = in.getParcelable("mCorrelation");
+ mPlayerBufferPeriod = in.getParcelable("mPlayerBufferPeriod");
+ mRecorderBufferPeriod = in.getParcelable("mRecorderBufferPeriod");
+ mPlayerCallbackTimes = in.getParcelable("mPlayerCallbackTimes");
+ mRecorderCallbackTimes = in.getParcelable("mRecorderCallbackTimes");
+
+ mNativePlayerBufferPeriodArray = in.getIntArray("mNativePlayerBufferPeriodArray");
+ mNativePlayerMaxBufferPeriod = in.getInt("mNativePlayerMaxBufferPeriod");
+ mNativeRecorderBufferPeriodArray = in.getIntArray("mNativeRecorderBufferPeriodArray");
+ mNativeRecorderMaxBufferPeriod = in.getInt("mNativeRecorderMaxBufferPeriod");
+
+ mWavePlotView.setData(mWaveData, mSamplingRate);
+ refreshState();
+ findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
+ findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle out) {
+ super.onSaveInstanceState(out);
+ // TODO: keep larger pieces of data in a fragment to speed up response to rotation
+ out.putDoubleArray("mWaveData", mWaveData);
+
+ out.putInt("mTestType", mTestType);
+ out.putInt("mMicSource", mMicSource);
+ out.putInt("mAudioThreadType", mAudioThreadType);
+ out.putInt("mSamplingRate", mSamplingRate);
+ out.putInt("mChannelIndex", mChannelIndex);
+ out.putInt("mSoundLevel", mSoundLevel);
+ out.putInt("mPlayerBufferSizeInBytes", mPlayerBufferSizeInBytes);
+ out.putInt("mRecorderBufferSizeInBytes", mRecorderBufferSizeInBytes);
+ out.putString("mTestStartTimeString", mTestStartTimeString);
+
+ out.putParcelable("mCorrelation", mCorrelation);
+ out.putParcelable("mPlayerBufferPeriod", mPlayerBufferPeriod);
+ out.putParcelable("mRecorderBufferPeriod", mRecorderBufferPeriod);
+ out.putParcelable("mPlayerCallbackTimes", mPlayerCallbackTimes);
+ out.putParcelable("mRecorderCallbackTimes", mRecorderCallbackTimes);
+
+ out.putIntArray("mNativePlayerBufferPeriodArray", mNativePlayerBufferPeriodArray);
+ out.putInt("mNativePlayerMaxBufferPeriod", mNativePlayerMaxBufferPeriod);
+ out.putIntArray("mNativeRecorderBufferPeriodArray", mNativeRecorderBufferPeriodArray);
+ out.putInt("mNativeRecorderMaxBufferPeriod", mNativeRecorderMaxBufferPeriod);
+
+ // buffer test values
+ out.putIntArray("mGlitchesData", mGlitchesData);
+ out.putBoolean("mGlitchingIntervalTooLong", mGlitchingIntervalTooLong);
+ out.putInt("mFFTSamplingSize", mFFTSamplingSize);
+ out.putInt("mFFTOverlapSamples", mFFTOverlapSamples);
+ out.putLong("mBufferTestStartTime", mBufferTestStartTime);
+ out.putInt("mBufferTestElapsedSeconds", mBufferTestElapsedSeconds);
+ out.putInt("mBufferTestDurationInSeconds", mBufferTestDurationInSeconds);
+ out.putInt("mBufferTestWavePlotDurationInSeconds", mBufferTestWavePlotDurationInSeconds);
+ }
}
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 566dd18..8b5a2d7 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
@@ -299,7 +299,6 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
case R.id.spinnerChannelIndex:
int channelIndex = mSpinnerChannelIndex.getSelectedItemPosition() - 1;
getApp().setChannelIndex(channelIndex);
- getApp().computeDefaults();
setSettingsHaveChanged();
log("channelIndex:" + channelIndex);
refresh();
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/WavePlotView.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/WavePlotView.java
index eaf068a..71b31c5 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/WavePlotView.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/WavePlotView.java
@@ -23,12 +23,15 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
+import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.widget.Scroller;
/**
@@ -54,9 +57,11 @@ public class WavePlotView extends View {
private GestureDetector mDetector;
private ScaleGestureDetector mSGDetector;
private MyScaleGestureListener mSGDListener;
+ private Scroller mScroller;
private int mWidth;
private int mHeight;
+ private boolean mHasDimensions;
private Paint mMyPaint;
private Paint mPaintZoomBox;
@@ -66,12 +71,23 @@ public class WavePlotView extends View {
private Paint mPaintGrid;
private Paint mPaintGridText;
+ // Default values used when we don't have a valid waveform to display.
+ // This saves us having to add multiple special cases to handle null waveforms.
+ private int mDefaultSampleRate = 48000; // chosen because it is common in real world devices
+ private double[] mDefaultDataVector = new double[mDefaultSampleRate]; // 1 second of fake audio
+
public WavePlotView(Context context, AttributeSet attrs) {
super(context, attrs);
mSGDListener = new MyScaleGestureListener();
mDetector = new GestureDetector(context, new MyGestureListener());
mSGDetector = new ScaleGestureDetector(context, mSGDListener);
+ mScroller = new Scroller(context, new LinearInterpolator(), true);
initPaints();
+
+ // Initialize the value array to 1s silence
+ mSamplingRate = mDefaultSampleRate;
+ mBigDataArray = new double[mSamplingRate];
+ Arrays.fill(mDefaultDataVector, 0);
}
@@ -123,13 +139,6 @@ public class WavePlotView extends View {
mPaintGridText.setTextSize(textSize);
}
-
- /** Must call this function to set mSamplingRate before plotting the wave. */
- public void setSamplingRate(int samplingRate) {
- mSamplingRate = samplingRate;
- }
-
-
public double getZoom() {
return mZoomFactorX;
}
@@ -226,7 +235,9 @@ public class WavePlotView extends View {
mWidth = w;
mHeight = h;
log("New w: " + mWidth + " h: " + mHeight);
+ mHasDimensions = true;
initView();
+ refreshView();
}
@@ -236,12 +247,8 @@ public class WavePlotView extends View {
mInsetSize = mWidth / 5;
mValuesArray = new double[mArraySize];
mValuesArray2 = new double[mArraySize];
- int i;
-
- for (i = 0; i < mArraySize; i++) {
- mValuesArray[i] = 0;
- mValuesArray2[i] = 0;
- }
+ Arrays.fill(mValuesArray, 0);
+ Arrays.fill(mValuesArray2, 0);
//inset
mInsetArray = new double[mInsetSize];
@@ -254,7 +261,6 @@ public class WavePlotView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- boolean showZoomBox = mSGDListener.mIsScaling;
boolean showGrid = true;
boolean showInset = true;
@@ -338,33 +344,23 @@ public class WavePlotView extends View {
myPath.moveTo(0, h / 2); //start
if (mBigDataArray != null) {
- for (i = 0; i < mArraySize; i++) {
- double value = mValuesArray[i];
- double valueScaled = (valueMax - value) / valueRange;
- float newX = i * deltaX;
- float newY = (float) (valueScaled * h);
- myPath.lineTo(newX, newY);
- }
-
- //bottom
- for (i = mArraySize - 1; i >= 0; i--) {
- double value = mValuesArray2[i];
- double valueScaled = (valueMax - value) / valueRange;
- float newX = i * deltaX;
- float newY = (float) (valueScaled * h);
- myPath.lineTo(newX, newY);
+ if (getZoom() >= 2) {
+ for (i = 0; i < mArraySize; ++i) {
+ float top = (float) ((valueMax - mValuesArray[i]) / valueRange) * h;
+ float bottom = (float) ((valueMax - mValuesArray2[i]) / valueRange) * h + 1;
+ float left = i * deltaX;
+ canvas.drawRect(left, top, left + deltaX, bottom, mMyPaint);
+ }
+ } else {
+ for (i = 0; i < (mArraySize - 1); ++i) {
+ float first = (float) ((valueMax - mValuesArray[i]) / valueRange) * h;
+ float second = (float) ((valueMax - mValuesArray[i + 1]) / valueRange) * h;
+ float left = i * deltaX;
+ canvas.drawLine(left, first, left + deltaX, second, mMyPaint);
+ }
}
- //close
- myPath.close();
- canvas.drawPath(myPath, mMyPaint);
- if (showZoomBox) {
- float x1 = (float) mSGDListener.mX1;
- float x2 = (float) mSGDListener.mX2;
- canvas.drawRect(x1, 0, x2, h, mPaintZoomBox);
- }
-
if (showInset) {
float iW = (float) (w * 0.2);
float iH = (float) (h * 0.2);
@@ -376,31 +372,14 @@ public class WavePlotView extends View {
//paintInset
float iDeltaX = (float) iW / mInsetSize;
- //top
- Path iPath = new Path();
- iPath.moveTo(iX, iY + (iH / 2)); //start
-
- for (i = 0; i < mInsetSize; i++) {
- double value = mInsetArray[i];
- double valueScaled = (valueMax - value) / valueRange;
- float newX = iX + (i * iDeltaX);
- float newY = iY + (float) (valueScaled * iH);
- iPath.lineTo(newX, newY);
- }
-
- //bottom
- for (i = mInsetSize - 1; i >= 0; i--) {
- double value = mInsetArray2[i];
- double valueScaled = (valueMax - value) / valueRange;
- float newX = iX + i * iDeltaX;
- float newY = iY + (float) (valueScaled * iH);
- iPath.lineTo(newX, newY);
+ for (i = 0; i < mInsetSize; ++i) {
+ float top = iY + (float) ((valueMax - mInsetArray[i]) / valueRange) * iH;
+ float bottom = iY +
+ (float) ((valueMax - mInsetArray2[i]) / valueRange) * iH + 1;
+ float left = iX + i * iDeltaX;
+ canvas.drawRect(left, top, left + deltaX, bottom, mPaintInset);
}
- //close
- iPath.close();
- canvas.drawPath(iPath, mPaintInset);
-
if (mBigDataArray != null) {
//paint current region of zoom
int offsetSamples = getOffset();
@@ -416,6 +395,10 @@ public class WavePlotView extends View {
}
}
}
+ if (mScroller.computeScrollOffset()) {
+ setOffset(mScroller.getCurrX(), false);
+ refreshGraph();
+ }
}
@@ -424,6 +407,13 @@ public class WavePlotView extends View {
Arrays.fill(mValuesArray2, 0);
}
+ void refreshView() {
+ double maxZoom = getMaxZoomOut();
+ setZoom(maxZoom);
+ setOffset(0, false);
+ computeInset();
+ refreshGraph();
+ }
void computeInset() {
if (mBigDataArray != null) {
@@ -529,21 +519,22 @@ public class WavePlotView extends View {
}
- void setData(double [] dataVector) {
- mBigDataArray = dataVector;
- double maxZoom = getMaxZoomOut();
- setZoom(maxZoom);
- setOffset(0, false);
- computeInset();
- refreshGraph();
- }
+ void setData(double[] dataVector, int sampleRate) {
+ if (sampleRate < 1)
+ throw new IllegalArgumentException("sampleRate must be a positive integer");
+
+ mSamplingRate = sampleRate;
+ mBigDataArray = (dataVector != null ? dataVector : mDefaultDataVector);
+ if (mHasDimensions) { // only refresh the view if it has been initialized already
+ refreshView();
+ }
+ }
void redraw() {
invalidate();
}
-
@Override
public boolean onTouchEvent(MotionEvent event) {
mDetector.onTouchEvent(event);
@@ -552,14 +543,17 @@ public class WavePlotView extends View {
return true;
}
-
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "MyGestureListener";
-
+ private boolean mInDrag = false;
@Override
public boolean onDown(MotionEvent event) {
Log.d(DEBUG_TAG, "onDown: " + event.toString() + " " + TAG);
+ if(!mScroller.isFinished()) {
+ mScroller.forceFinished(true);
+ refreshGraph();
+ }
return true;
}
@@ -569,110 +563,68 @@ public class WavePlotView extends View {
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: VelocityX: " + velocityX + " velocityY: " + velocityY);
- //velocityX positive left to right
- // negative: right to left
- //double offset = getZoom()
-
- double samplesPerWindow = mArraySize * getZoom();
- int maxPixelsPerWindow = 8000;
- double offsetFactor = -(double) (velocityX / maxPixelsPerWindow);
- double offset = (samplesPerWindow * offsetFactor / 3.0);
- Log.d(DEBUG_TAG, " VELOCITY: " + velocityX + " samples/window = " + samplesPerWindow +
- " offsetFactor = " + offsetFactor + " offset: " + offset);
-
- setOffset((int) offset, true);
+ mScroller.fling(mCurrentOffset, 0,
+ (int) (-velocityX * getZoom()),
+ 0, 0, mBigDataArray.length, 0, 0);
refreshGraph();
return true;
}
@Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ setOffset((int) (distanceX * getZoom()), true);
+ refreshGraph();
+ return super.onScroll(e1, e2, distanceX, distanceY);
+ }
+
+ @Override
public boolean onDoubleTap(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
- setZoom(100000);
- setOffset(0, false);
+ int tappedSample = (int) (event.getX() * getZoom());
+ setZoom(getZoom() / 2);
+ setOffset(tappedSample / 2, true);
+
refreshGraph();
return true;
}
- }
+ @Override
+ public void onLongPress(MotionEvent e) {
+ Vibrator vibe = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
+ if (vibe.hasVibrator()) {
+ vibe.vibrate(20);
+ }
+ setZoom(getMaxZoomOut());
+ setOffset(0, false);
+ refreshGraph();
+ }
+ }
private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private static final String DEBUG_TAG = "MyScaleGestureListener";
- public boolean mIsScaling = false;
- public double mX1 = 0;
- public double mX2 = 0;
+ int focusSample = 0;
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
- mIsScaling = true;
+ focusSample = (int) (detector.getFocusX() * getZoom()) + mCurrentOffset;
return super.onScaleBegin(detector);
}
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mIsScaling = false;
- //now zoom
- {
- int w = getWidth();
- //int h = getHeight();
-
- //double currentSpan = detector.getCurrentSpan();
- double currentSpanX = detector.getCurrentSpanX();
- //double currentSpanY = detector.getCurrentSpanY();
- double focusX = detector.getFocusX();
- //double focusY = detector.getFocusY();
- //double scaleFactor = detector.getScaleFactor();
-
- //estimated X1, X2
- double x1 = focusX - (currentSpanX / 2);
- double x2 = focusX + (currentSpanX / 2);
- //double x1clip = x1 < 0 ? 0 : (x1 > w ? w : x1);
- //double x2clip = x2 < 0 ? 0 : (x2 > w ? w : x2);
-
- //int originalOffset = getOffset();
- double windowSamplesOriginal = getWindowSamples(); //samples in current window
- double currentZoom = getZoom();
- double windowFactor = Math.abs(mX2 - mX1) / w;
-
- double newZoom = currentZoom * windowFactor;
- setZoom(newZoom);
- int newOffset = (int) (windowSamplesOriginal * mX1 / w);
- setOffset(newOffset, true); //relative
-
- }
- refreshGraph();
- }
-
-
@Override
public boolean onScale(ScaleGestureDetector detector) {
+ setZoom(getZoom() / detector.getScaleFactor());
- int w = getWidth();
- //int h = getHeight();
- //double currentSpan = detector.getCurrentSpan();
- double currentSpanX = detector.getCurrentSpanX();
- //double currentSpanY = detector.getCurrentSpanY();
- double focusX = detector.getFocusX();
- //double focusY = detector.getFocusY();
- //double scaleFactor = detector.getScaleFactor();
-
- //estimated X1, X2
- double x1 = focusX - (currentSpanX / 2);
- double x2 = focusX + (currentSpanX / 2);
- double x1clip = x1 < 0 ? 0 : (x1 > w ? w : x1);
- double x2clip = x2 < 0 ? 0 : (x2 > w ? w : x2);
- mX1 = x1clip;
- mX2 = x2clip;
+ int newFocusSample = (int) (detector.getFocusX() * getZoom()) + mCurrentOffset;
+ int sampleDelta = (int) (focusSample - newFocusSample);
+ setOffset(sampleDelta, true);
refreshGraph();
return true;
}
}
-
private static void log(String msg) {
Log.v(TAG, msg);
}
diff --git a/LoopbackApp/app/src/main/res/values/strings.xml b/LoopbackApp/app/src/main/res/values/strings.xml
index 49fe37d..c58469e 100644
--- a/LoopbackApp/app/src/main/res/values/strings.xml
+++ b/LoopbackApp/app/src/main/res/values/strings.xml
@@ -52,7 +52,7 @@
<item>UNPROCESSED (N or later)</item>
</string-array>
- <string name="labelInfo">Info...</string>
+ <string name="labelInfo">Test settings will appear here after the first test is run</string>
<string name="labelSettings">SETTINGS</string>
<string name="labelAbout">About</string>
<string name="labelSamplingRate">Sampling Rate</string>