Home | History | Annotate | Download | only in loopback
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.drrickorang.loopback;
     18 
     19 import android.os.Bundle;
     20 import android.os.Parcel;
     21 import android.os.Parcelable;
     22 import android.util.Log;
     23 
     24 
     25 /**
     26  * This class is used to automatically estimate latency and its confidence.
     27  */
     28 
     29 public class Correlation implements Parcelable {
     30     private static final String TAG = "Correlation";
     31 
     32     private int       mBlockSize = Constant.DEFAULT_CORRELATION_BLOCK_SIZE;
     33     private int       mSamplingRate;
     34     private double [] mDataDownsampled;
     35     private double [] mDataAutocorrelated;
     36 
     37     public double mEstimatedLatencySamples = 0;
     38     public double mEstimatedLatencyMs = 0;
     39     public double mEstimatedLatencyConfidence = 0.0;
     40     public double mAverage = 0.0;
     41     public double mRms = 0.0;
     42 
     43     private double mAmplitudeThreshold = 0.001;  // 0.001 = -60 dB noise
     44 
     45     private boolean mDataIsValid = false; // Used to mark computed latency information is available
     46 
     47     public Correlation() {
     48         // Default constructor for when no data will be restored
     49 
     50     }
     51 
     52     public void init(int blockSize, int samplingRate) {
     53         setBlockSize(blockSize);
     54         mSamplingRate = samplingRate;
     55     }
     56 
     57     public void computeCorrelation(double [] data, int samplingRate) {
     58         log("Started Auto Correlation for data with " + data.length + " points");
     59         mSamplingRate = samplingRate;
     60         mDataDownsampled = new double [mBlockSize];
     61         mDataAutocorrelated = new double[mBlockSize];
     62         downsampleData(data, mDataDownsampled, mAmplitudeThreshold);
     63 
     64         //correlation vector
     65         autocorrelation(mDataDownsampled, mDataAutocorrelated);
     66 
     67 
     68         int N = data.length; //all samples available
     69         double groupSize =  (double) N / mBlockSize;  //samples per downsample point.
     70 
     71         double maxValue = 0;
     72         int maxIndex = -1;
     73 
     74         double minLatencyMs = 8; //min latency expected. This algorithm should be improved.
     75         int minIndex = (int) (0.5 + minLatencyMs * mSamplingRate / (groupSize * 1000));
     76 
     77         double average = 0;
     78         double rms = 0;
     79 
     80         //find max
     81         for (int i = minIndex; i < mDataAutocorrelated.length; i++) {
     82             average += mDataAutocorrelated[i];
     83             rms += mDataAutocorrelated[i] * mDataAutocorrelated[i];
     84            if (mDataAutocorrelated[i] > maxValue) {
     85                maxValue = mDataAutocorrelated[i];
     86                maxIndex = i;
     87            }
     88         }
     89 
     90         rms = Math.sqrt(rms / mDataAutocorrelated.length);
     91         average = average / mDataAutocorrelated.length;
     92         log(String.format(" Maxvalue %f, max Index : %d/%d (%d)  minIndex = %d", maxValue, maxIndex,
     93                           mDataAutocorrelated.length, data.length, minIndex));
     94         log(String.format("  average : %.3f  rms: %.3f", average, rms));
     95 
     96         mAverage = average;
     97         mRms = rms;
     98 
     99         mEstimatedLatencyConfidence = 0.0;
    100         if (average > 0) {
    101             double factor = 3.0;
    102 
    103             double raw = (rms - average) / (factor * average);
    104             log(String.format("Raw: %.3f", raw));
    105             mEstimatedLatencyConfidence = Math.max(Math.min(raw, 1.0), 0.0);
    106         }
    107         log(String.format(" ****Confidence: %.2f", mEstimatedLatencyConfidence));
    108 
    109         mEstimatedLatencySamples = maxIndex * groupSize;
    110         mEstimatedLatencyMs = mEstimatedLatencySamples * 1000 / mSamplingRate;
    111         log(String.format(" latencySamples: %.2f  %.2f ms", mEstimatedLatencySamples,
    112                           mEstimatedLatencyMs));
    113 
    114         mDataIsValid = mEstimatedLatencyMs > 0.0001;
    115     }
    116 
    117     // Called by LoopbackActivity before displaying latency test results
    118     public boolean isValid() {
    119         return mDataIsValid;
    120     }
    121 
    122     // Called at beginning of new test
    123     public void invalidate() {
    124         mDataIsValid = false;
    125     }
    126 
    127     public void setBlockSize(int blockSize) {
    128         mBlockSize = clamp(blockSize, Constant.CORRELATION_BLOCK_SIZE_MIN,
    129                 Constant.CORRELATION_BLOCK_SIZE_MAX);
    130     }
    131 
    132     private boolean downsampleData(double [] data, double [] dataDownsampled, double threshold) {
    133         log("Correlation block size used in down sample: " + mBlockSize);
    134 
    135         boolean status;
    136         for (int i = 0; i < mBlockSize; i++) {
    137             dataDownsampled[i] = 0;
    138         }
    139 
    140         int N = data.length; //all samples available
    141         double groupSize =  (double) N / mBlockSize;
    142 
    143         int ignored = 0;
    144 
    145         int currentIndex = 0;
    146         double nextGroup = groupSize;
    147         for (int i = 0; i < N && currentIndex < mBlockSize; i++) {
    148 
    149             if (i > nextGroup) { //advanced to next group.
    150                 currentIndex++;
    151                 nextGroup += groupSize;
    152             }
    153 
    154             if (currentIndex >= mBlockSize) {
    155                 break;
    156             }
    157 
    158             double value =  Math.abs(data[i]);
    159             if (value >= threshold) {
    160                 dataDownsampled[currentIndex] += value;
    161             } else {
    162                 ignored++;
    163             }
    164         }
    165 
    166         log(String.format(" Threshold: %.3f, ignored:%d/%d (%%.2f)",
    167                 threshold, ignored, N, (double) ignored/(double)N));
    168 
    169         status = true;
    170         return status;
    171     }
    172 
    173 
    174     private boolean autocorrelation(double [] data, double [] dataOut) {
    175         boolean status = false;
    176 
    177         double sumsquared = 0;
    178         int N = data.length;
    179         for (int i = 0; i < N; i++) {
    180             double value = data[i];
    181             sumsquared += value * value;
    182         }
    183 
    184         if (sumsquared > 0) {
    185             //correlate (not circular correlation)
    186             for (int i = 0; i < N; i++) {
    187                 dataOut[i] = 0;
    188                 for (int j = 0; j < N - i; j++) {
    189 
    190                     dataOut[i] += data[j] * data[i + j];
    191                 }
    192                 dataOut[i] = dataOut[i] / sumsquared;
    193             }
    194             status = true;
    195         }
    196 
    197         return status;
    198     }
    199 
    200     /**
    201      * Returns value if value is within inclusive bounds min through max
    202      * otherwise returns min or max according to if value is less than or greater than the range
    203      */
    204     // TODO move to audio_utils
    205     private int clamp(int value, int min, int max) {
    206 
    207         if (max < min) throw new UnsupportedOperationException("min must be <= max");
    208 
    209         if (value < min) return min;
    210         else if (value > max) return max;
    211         else return value;
    212     }
    213 
    214     @Override
    215     public int describeContents() {
    216         return 0;
    217     }
    218 
    219     // Store the results before this object is destroyed
    220     @Override
    221     public void writeToParcel(Parcel dest, int flags) {
    222         Bundle bundle = new Bundle();
    223         bundle.putBoolean("mDataIsValid", mDataIsValid);
    224         if (mDataIsValid) {
    225             bundle.putDouble("mEstimatedLatencySamples", mEstimatedLatencySamples);
    226             bundle.putDouble("mEstimatedLatencyMs", mEstimatedLatencyMs);
    227             bundle.putDouble("mEstimatedLatencyConfidence", mEstimatedLatencyConfidence);
    228             bundle.putDouble("mAverage", mAverage);
    229             bundle.putDouble("mRms", mRms);
    230         }
    231         dest.writeBundle(bundle);
    232     }
    233 
    234     // Restore the results which were previously calculated
    235     private Correlation(Parcel in) {
    236         Bundle bundle = in.readBundle(getClass().getClassLoader());
    237         mDataIsValid = bundle.getBoolean("mDataIsValid");
    238         if (mDataIsValid) {
    239             mEstimatedLatencySamples    = bundle.getDouble("mEstimatedLatencySamples");
    240             mEstimatedLatencyMs         = bundle.getDouble("mEstimatedLatencyMs");
    241             mEstimatedLatencyConfidence = bundle.getDouble("mEstimatedLatencyConfidence");
    242             mAverage                    = bundle.getDouble("mAverage");
    243             mRms                        = bundle.getDouble("mRms");
    244         }
    245     }
    246 
    247     public static final Parcelable.Creator<Correlation> CREATOR
    248             = new Parcelable.Creator<Correlation>() {
    249         public Correlation createFromParcel(Parcel in) {
    250             return new Correlation(in);
    251         }
    252 
    253         public Correlation[] newArray(int size) {
    254             return new Correlation[size];
    255         }
    256     };
    257 
    258     private static void log(String msg) {
    259         Log.v(TAG, msg);
    260     }
    261 
    262 }
    263