summaryrefslogtreecommitdiff
path: root/LoopbackApp/app
diff options
context:
space:
mode:
authorRicardo Garcia <rago@google.com>2015-05-11 11:21:11 -0700
committerRicardo Garcia <rago@google.com>2015-05-11 11:21:11 -0700
commite6be421448e90e322b87dfbdba636b2862a135db (patch)
tree43943f232d5fe7351b44fe86c02cab99204d2afa /LoopbackApp/app
parent02695430e4ef81065004c7a5a24282157dc13252 (diff)
downloaddrrickorang-e6be421448e90e322b87dfbdba636b2862a135db.tar.gz
Added simple latency estimation via graphic correlation
Simple algorithm to estimate and display latency in ms. Some cosmetic changes, applying the holo light schema, and added current level display on screen.
Diffstat (limited to 'LoopbackApp/app')
-rw-r--r--LoopbackApp/app/app.iml8
-rw-r--r--LoopbackApp/app/src/main/AndroidManifest.xml9
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java142
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java47
-rw-r--r--LoopbackApp/app/src/main/res/layout/main_activity.xml19
-rw-r--r--LoopbackApp/app/src/main/res/layout/settings_activity.xml2
6 files changed, 199 insertions, 28 deletions
diff --git a/LoopbackApp/app/app.iml b/LoopbackApp/app/app.iml
index 5590b6b..76a0905 100644
--- a/LoopbackApp/app/app.iml
+++ b/LoopbackApp/app/app.iml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LoopbackApp" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
+<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LoopbackApp" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
@@ -12,8 +12,9 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
- <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
+ <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
+ <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
@@ -86,5 +87,4 @@
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
-</module>
-
+</module> \ No newline at end of file
diff --git a/LoopbackApp/app/src/main/AndroidManifest.xml b/LoopbackApp/app/src/main/AndroidManifest.xml
index 7f77efe..0b14eff 100644
--- a/LoopbackApp/app/src/main/AndroidManifest.xml
+++ b/LoopbackApp/app/src/main/AndroidManifest.xml
@@ -23,10 +23,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.drrickorang.loopback"
android:versionCode="4"
- android:versionName="0.3">
+ android:versionName="2.0">
<uses-sdk
- android:minSdkVersion="8"
+ android:minSdkVersion="11"
android:targetSdkVersion="21"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
@@ -35,10 +35,12 @@
<application
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
- android:name="LoopbackApplication">
+ 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">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -50,6 +52,7 @@
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|keyboardHidden|screenLayout">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
new file mode 100644
index 0000000..7105fc2
--- /dev/null
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
@@ -0,0 +1,142 @@
+package org.drrickorang.loopback;
+
+import android.util.Log;
+
+/**
+ * Created by rago on 5/8/15.
+ */
+public class Correlation {
+
+ private int mBlockSize = 4096;
+ private int mSamplingRate = 44100;
+ private double [] mDataDownsampled = new double [mBlockSize];
+ private double [] mDataAutocorrelated = new double[mBlockSize];
+
+ public double mEstimatedLatencySamples = 0;
+ public double mEstimatedLatencyMs = 0;
+
+
+
+ public void init(int blockSize, int samplingRate) {
+ mBlockSize = blockSize;
+ mSamplingRate = samplingRate;
+ }
+
+ public boolean computeCorrelation(double [] data, int samplingRate) {
+ boolean status = false;
+ log("Started Auto Correlation for data with " + data.length + " points");
+ mSamplingRate = samplingRate;
+
+ //downsample by N -> 1024 points or 2048
+ // in full wave, find the first burst, center around that.
+ // find the second and third and get those.
+ // compute the fft method for autocorrelation, and find exact numbers. (zero pad?)
+ // display.
+
+ downsampleData(data, mDataDownsampled);
+
+ //correlation vector
+ autocorrelation(mDataDownsampled, mDataAutocorrelated);
+
+ //find max
+
+ int N = data.length; //all samples available
+ double groupSize = (double) N / mBlockSize; //samples per downsample point.
+
+ double maxValue = 0;
+ int maxIndex = -1;
+
+ double minLatencyMs = 8;
+ int minIndex = (int)(0.5 + minLatencyMs * mSamplingRate / (groupSize*1000));
+
+ for(int i=minIndex; i<mDataAutocorrelated.length; i++) {
+ if(mDataAutocorrelated[i] > maxValue) {
+ maxValue = mDataAutocorrelated[i];
+ maxIndex = i;
+ }
+ }
+
+ log(String.format(" Maxvalue %f, max Index : %d/%d (%d) minIndex=%d",maxValue, maxIndex, mDataAutocorrelated.length, data.length, minIndex));
+
+
+
+ mEstimatedLatencySamples = maxIndex*groupSize;
+
+ mEstimatedLatencyMs = mEstimatedLatencySamples *1000/mSamplingRate;
+
+ log(String.format(" latencySamples: %.2f %.2f ms", mEstimatedLatencySamples, mEstimatedLatencyMs));
+
+
+
+
+
+ //estimate crude
+
+ status = true;
+
+ return status;
+ }
+
+ private boolean downsampleData(double [] data, double [] dataDownsampled) {
+
+ boolean status = false;
+ // mDataDownsampled = new double[mBlockSize];
+ for (int i=0; i<mBlockSize; i++) {
+ dataDownsampled[i] = 0;
+ }
+
+ int N = data.length; //all samples available
+ double groupSize = (double) N / mBlockSize;
+
+ int currentIndex = 0;
+ double nextGroup = groupSize;
+ for (int i = 0; i<N && currentIndex<mBlockSize; i++) {
+
+ if(i> nextGroup) { //advanced to next group.
+ currentIndex++;
+ nextGroup += groupSize;
+ }
+
+ if (currentIndex>=mBlockSize) {
+ break;
+ }
+ dataDownsampled[currentIndex] += Math.abs(data[i]);
+ }
+
+ status = true;
+
+ return status;
+ }
+
+ private boolean autocorrelation(double [] data, double [] dataOut) {
+ boolean status = false;
+
+ double sumsquared = 0;
+ int N = data.length;
+ for(int i=0; i<N; i++) {
+ double value = data[i];
+ sumsquared += value*value;
+ }
+
+ //dataOut = new double[N];
+
+ if(sumsquared>0) {
+ //correlate (not circular correlation)
+ for (int i = 0; i < N; i++) {
+ dataOut[i] = 0;
+ for (int j = 0; j < N - i; j++) {
+
+ dataOut[i] += data[j] * data[i + j];
+ }
+ dataOut[i] = dataOut[i] / sumsquared;
+ }
+ status = true;
+ }
+
+ return status;
+ }
+
+ private static void log(String msg) {
+ Log.v("Recorder", 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 698db9d..87dfd21 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
@@ -72,7 +72,10 @@ public class LoopbackActivity extends Activity {
SeekBar mBarMasterLevel;
TextView mTextInfo;
+ TextView mTextViewCurrentLevel;
+ TextView mTextViewEstimatedLatency;
private double [] mWaveData;
+ private Correlation mCorrelation = new Correlation();
int mSamplingRate;
Toast toast;
@@ -96,6 +99,7 @@ public class LoopbackActivity extends Activity {
if(audioThread != null) {
mWaveData = audioThread.getWaveData();
mSamplingRate = audioThread.mSamplingRate;
+ mCorrelation.computeCorrelation(mWaveData,mSamplingRate);
log("got message java rec complete!!");
refreshPlots();
refreshState();
@@ -118,6 +122,7 @@ public class LoopbackActivity extends Activity {
if(nativeAudioThread != null) {
mWaveData = nativeAudioThread.getWaveData();
mSamplingRate = nativeAudioThread.mSamplingRate;
+ mCorrelation.computeCorrelation(mWaveData, mSamplingRate);
log("got message native rec complete!!");
refreshPlots();
refreshState();
@@ -166,16 +171,20 @@ public class LoopbackActivity extends Activity {
}
@Override
- public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_MUSIC,
progress, 0);
refreshState();
- log("Changed stream volume to: "+progress);
+ log("Changed stream volume to: " + progress);
}
});
mWavePlotView = (WavePlotView) findViewById(R.id.viewWavePlot);
+
+ mTextViewCurrentLevel = (TextView) findViewById(R.id.textViewCurrentLevel);
+
+ mTextViewEstimatedLatency = (TextView) findViewById(R.id.textViewEstimatedLatency);
refreshState();
}
@@ -316,15 +325,10 @@ public class LoopbackActivity extends Activity {
public void onButtonSave(View view) {
//create filename with date
- String date = (String) DateFormat.format("MMddkkmm", System.currentTimeMillis());
+ String date = (String) DateFormat.format("MMddkkmmss", System.currentTimeMillis());
String micSource = getApp().getMicSourceString( getApp().getMicSource());
String fileName = micSource+"_"+date;
- //MIC
- //VERSION?
- //hardware?
-
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -332,7 +336,7 @@ public class LoopbackActivity extends Activity {
intent2.addCategory(Intent.CATEGORY_OPENABLE);
intent2.setType("image/png");
- intent2.putExtra(Intent.EXTRA_TITLE,fileName+".png"); //suggested filename
+ intent2.putExtra(Intent.EXTRA_TITLE, fileName + ".png"); //suggested filename
startActivityForResult(intent2, SAVE_TO_PNG_REQUEST);
// browser.
@@ -340,17 +344,11 @@ public class LoopbackActivity extends Activity {
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("audio/wav");
- intent.putExtra(Intent.EXTRA_TITLE,fileName+".wav"); //suggested filename
+ intent.putExtra(Intent.EXTRA_TITLE, fileName + ".wav"); //suggested filename
startActivityForResult(intent, SAVE_TO_WAVE_REQUEST);
-
-
- }
- else {
-
+ } else {
showToast("Saving Wave to: "+fileName+".wav");
-// Toast.makeText(getApplicationContext(), "Saving Wave to: "+fileName,
-// Toast.LENGTH_SHORT).show();
//save to a given uri... local file?
Uri uri = Uri.parse("file://mnt/sdcard/"+fileName+".wav");
@@ -364,7 +362,7 @@ public class LoopbackActivity extends Activity {
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
- log("ActivityResult request: "+requestCode + " result:" + resultCode);
+ log("ActivityResult request: " + requestCode + " result:" + resultCode);
if (requestCode == SAVE_TO_WAVE_REQUEST && resultCode == Activity.RESULT_OK) {
log("got SAVE TO WAV intent back!");
Uri uri = null;
@@ -458,7 +456,10 @@ public class LoopbackActivity extends Activity {
int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
mBarMasterLevel.setProgress(currentVolume);
- log("refreshState 2");
+ mTextViewCurrentLevel.setText(String.format("Level: %d/%d",currentVolume,
+ mBarMasterLevel.getMax()));
+
+ log("refreshState 2b");
//get info
int samplingRate = getApp().getSamplingRate();
@@ -487,10 +488,16 @@ public class LoopbackActivity extends Activity {
}
String info = getApp().getSystemInfo();
- s.append(" "+info);
+ s.append(" " + info);
mTextInfo.setText(s.toString());
+ if(mCorrelation.mEstimatedLatencyMs>0.0001) {
+ mTextViewEstimatedLatency.setText(String.format("Latency: %.2f ms", mCorrelation.mEstimatedLatencyMs));
+ } else {
+ mTextViewEstimatedLatency.setText(String.format("Latency: ----"));
+ }
+
}
private static void log(String msg) {
diff --git a/LoopbackApp/app/src/main/res/layout/main_activity.xml b/LoopbackApp/app/src/main/res/layout/main_activity.xml
index 16e058c..70a7093 100644
--- a/LoopbackApp/app/src/main/res/layout/main_activity.xml
+++ b/LoopbackApp/app/src/main/res/layout/main_activity.xml
@@ -111,6 +111,25 @@
</LinearLayout>
<LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:text="Current Level"
+ android:id="@+id/textViewCurrentLevel"/>
+
+ <TextView
+ android:layout_width="180dp"
+ android:layout_height="wrap_content"
+ android:text="latency"
+ android:id="@+id/textViewEstimatedLatency"
+ android:textStyle="bold"/>
+ </LinearLayout>
+
+ <LinearLayout
android:layout_marginTop="0mm"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
diff --git a/LoopbackApp/app/src/main/res/layout/settings_activity.xml b/LoopbackApp/app/src/main/res/layout/settings_activity.xml
index b0e5a89..8aa61ca 100644
--- a/LoopbackApp/app/src/main/res/layout/settings_activity.xml
+++ b/LoopbackApp/app/src/main/res/layout/settings_activity.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:background="#000000">
+ android:background="#FFFFFF">
<ScrollView
android:layout_width="fill_parent"