| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifier.audio; |
| |
| import android.util.Log; |
| |
| |
| 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 double mEstimatedLatencyConfidence = 0.0; |
| |
| private double mAmplitudeThreshold = 0.001; // 0.001 = -60 dB noise |
| |
| 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, mAmplitudeThreshold); |
| |
| //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)); |
| |
| double average = 0; |
| double rms = 0; |
| //find max |
| for (int i=minIndex; i<mDataAutocorrelated.length; i++) { |
| average += mDataAutocorrelated[i]; |
| rms += mDataAutocorrelated[i]*mDataAutocorrelated[i]; |
| if (mDataAutocorrelated[i] > maxValue) { |
| maxValue = mDataAutocorrelated[i]; |
| maxIndex = i; |
| } |
| } |
| |
| rms = Math.sqrt(rms/mDataAutocorrelated.length); |
| average = average/mDataAutocorrelated.length; |
| log(String.format(" Maxvalue %f, max Index : %d/%d (%d) minIndex=%d",maxValue, maxIndex, |
| mDataAutocorrelated.length, data.length, minIndex)); |
| |
| log(String.format(" average : %.3f rms: %.3f", average, rms)); |
| |
| mEstimatedLatencyConfidence = 0.0; |
| if (average>0) { |
| double factor = 3.0; |
| |
| double raw = (rms-average) /(factor*average); |
| log(String.format("Raw: %.3f",raw)); |
| mEstimatedLatencyConfidence = Math.max(Math.min(raw, 1.0),0.0); |
| } |
| |
| log(String.format(" ****Confidence: %.2f",mEstimatedLatencyConfidence)); |
| |
| 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, double threshold) { |
| |
| 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 ignored = 0; |
| |
| 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; |
| } |
| |
| double value = Math.abs(data[i]); |
| if (value >= threshold) { |
| dataDownsampled[currentIndex] += value; |
| } else { |
| ignored++; |
| } |
| } |
| |
| log(String.format(" Threshold: %.3f, ignored:%d/%d (%%.2f)", threshold, ignored, N, |
| (double) ignored/(double)N)); |
| |
| 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; |
| } |
| |
| if (sumsquared>0) { |
| 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); |
| } |
| } |