summaryrefslogtreecommitdiff
path: root/LoopbackApp/app/src/main/java
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 12:10:40 -0700
commit7e32cbe93ec148ce154ce24b6854d64a422a2660 (patch)
tree111996b5124cfbac2c9ee0a0c472cfc4a2f163be /LoopbackApp/app/src/main/java
parent92e3320b85d6771e9cb064e287bbcf71f24f7656 (diff)
downloaddrrickorang-7e32cbe93ec148ce154ce24b6854d64a422a2660.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/src/main/java')
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java130
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java47
2 files changed, 157 insertions, 20 deletions
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..aac3522
--- /dev/null
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Correlation.java
@@ -0,0 +1,130 @@
+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;
+
+ downsampleData(data, mDataDownsampled);
+
+ //correlation vector
+ autocorrelation(mDataDownsampled, mDataAutocorrelated);
+
+
+ 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; //min latency expected. This algorithm should be improved.
+ int minIndex = (int)(0.5 + minLatencyMs * mSamplingRate / (groupSize*1000));
+
+ //find max
+ 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));
+
+ 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) {