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 com.android.cts.verifier.audio;
18 
19 import android.util.Log;
20 
21 
22 public class Correlation {
23 
24     private int mBlockSize = 4096;
25     private int mSamplingRate = 44100;
26     private double [] mDataDownsampled = new double [mBlockSize];
27     private double [] mDataAutocorrelated = new double[mBlockSize];
28 
29     public double mEstimatedLatencySamples = 0;
30     public double mEstimatedLatencyMs = 0;
31     public double mEstimatedLatencyConfidence = 0.0;
32 
33     private double mAmplitudeThreshold = 0.001;  // 0.001 = -60 dB noise
34 
init(int blockSize, int samplingRate)35     public void init(int blockSize, int samplingRate) {
36         mBlockSize = blockSize;
37         mSamplingRate = samplingRate;
38     }
39 
computeCorrelation(double [] data, int samplingRate)40     public boolean computeCorrelation(double [] data, int samplingRate) {
41         boolean status = false;
42         log("Started Auto Correlation for data with " + data.length + " points");
43         mSamplingRate = samplingRate;
44 
45         downsampleData(data, mDataDownsampled, mAmplitudeThreshold);
46 
47         //correlation vector
48         autocorrelation(mDataDownsampled, mDataAutocorrelated);
49 
50         int N = data.length; //all samples available
51         double groupSize =  (double) N / mBlockSize;  //samples per downsample point.
52 
53         double maxValue = 0;
54         int maxIndex = -1;
55 
56         double minLatencyMs = 8; //min latency expected. This algorithm should be improved.
57         int minIndex = (int)(0.5 + minLatencyMs * mSamplingRate / (groupSize*1000));
58 
59         double average = 0;
60         double rms = 0;
61         //find max
62         for (int i=minIndex; i<mDataAutocorrelated.length; i++) {
63             average += mDataAutocorrelated[i];
64             rms += mDataAutocorrelated[i]*mDataAutocorrelated[i];
65             if (mDataAutocorrelated[i] > maxValue) {
66                 maxValue = mDataAutocorrelated[i];
67                 maxIndex = i;
68             }
69         }
70 
71         rms = Math.sqrt(rms/mDataAutocorrelated.length);
72         average = average/mDataAutocorrelated.length;
73         log(String.format(" Maxvalue %f, max Index : %d/%d (%d)  minIndex=%d",maxValue, maxIndex,
74                 mDataAutocorrelated.length, data.length, minIndex));
75 
76         log(String.format("  average : %.3f  rms: %.3f", average, rms));
77 
78         mEstimatedLatencyConfidence = 0.0;
79         if (average>0) {
80             double factor = 3.0;
81 
82             double raw = (rms-average) /(factor*average);
83             log(String.format("Raw: %.3f",raw));
84             mEstimatedLatencyConfidence = Math.max(Math.min(raw, 1.0),0.0);
85         }
86 
87         log(String.format(" ****Confidence: %.2f",mEstimatedLatencyConfidence));
88 
89         mEstimatedLatencySamples = maxIndex*groupSize;
90 
91         mEstimatedLatencyMs = mEstimatedLatencySamples *1000/mSamplingRate;
92 
93         log(String.format(" latencySamples: %.2f  %.2f ms", mEstimatedLatencySamples,
94                 mEstimatedLatencyMs));
95 
96         status = true;
97         return status;
98     }
99 
downsampleData(double [] data, double [] dataDownsampled, double threshold)100     private boolean downsampleData(double [] data, double [] dataDownsampled, double threshold) {
101 
102         boolean status = false;
103         // mDataDownsampled = new double[mBlockSize];
104         for (int i=0; i<mBlockSize; i++) {
105             dataDownsampled[i] = 0;
106         }
107 
108         int N = data.length; //all samples available
109         double groupSize =  (double) N / mBlockSize;
110 
111         int ignored = 0;
112 
113         int currentIndex = 0;
114         double nextGroup = groupSize;
115         for (int i = 0; i<N && currentIndex<mBlockSize; i++) {
116 
117             if (i> nextGroup) { //advanced to next group.
118                 currentIndex++;
119                 nextGroup += groupSize;
120             }
121 
122             if (currentIndex>=mBlockSize) {
123                 break;
124             }
125 
126             double value =  Math.abs(data[i]);
127             if (value >= threshold) {
128                 dataDownsampled[currentIndex] += value;
129             } else {
130                 ignored++;
131             }
132         }
133 
134         log(String.format(" Threshold: %.3f, ignored:%d/%d (%%.2f)", threshold, ignored, N,
135                 (double) ignored/(double)N));
136 
137         status = true;
138         return status;
139     }
140 
autocorrelation(double [] data, double [] dataOut)141     private boolean autocorrelation(double [] data, double [] dataOut) {
142         boolean status = false;
143 
144         double sumsquared = 0;
145         int N = data.length;
146         for (int i=0; i<N; i++) {
147             double value = data[i];
148             sumsquared += value*value;
149         }
150 
151         if (sumsquared>0) {
152             for (int i = 0; i < N; i++) {
153                 dataOut[i] = 0;
154                 for (int j = 0; j < N - i; j++) {
155 
156                     dataOut[i] += data[j] * data[i + j];
157                 }
158                 dataOut[i] = dataOut[i] / sumsquared;
159             }
160             status = true;
161         }
162 
163         return status;
164     }
165 
log(String msg)166     private static void log(String msg) {
167         Log.v("Recorder", msg);
168     }
169 }
170