diff options
author | Ricardo Garcia <rago@google.com> | 2015-05-11 11:21:11 -0700 |
---|---|---|
committer | Ricardo Garcia <rago@google.com> | 2015-05-11 12:10:40 -0700 |
commit | 7e32cbe93ec148ce154ce24b6854d64a422a2660 (patch) | |
tree | 111996b5124cfbac2c9ee0a0c472cfc4a2f163be /LoopbackApp/app/src/main/java | |
parent | 92e3320b85d6771e9cb064e287bbcf71f24f7656 (diff) | |
download | drrickorang-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.java | 130 | ||||
-rw-r--r-- | LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java | 47 |
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) { |