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.util.Log; 20 21 import java.util.Arrays; 22 23 24 /** 25 * This thread is responsible for detecting glitches in the samples. 26 */ 27 28 public class GlitchDetectionThread extends Thread { 29 private static final String TAG = "GlitchDetectionThread"; 30 // the acceptable difference between the expected center of mass and what we actually get 31 private static final double mAcceptablePercentDifference = 0.02; // change this if necessary 32 33 // Measured in FFT samples 34 private static final int GLITCH_CONCENTRATION_WINDOW_SIZE = 1500; // approx 30 seconds at 48kHz 35 private static final int COOLDOWN_WINDOW = 4500; // approx 90 seconds at 48kHz 36 37 private boolean mIsRunning; // condition must be true for the thread to run 38 private short mShortBuffer[]; // keep the data read from Pipe 39 private int mShortBufferIndex = 0; 40 private Pipe mPipe; 41 private static int mThreadSleepDurationMs; 42 43 private double mDoubleBuffer[]; // keep the data used for FFT calculation 44 private boolean mIsFirstFFT = true; // whether or not it's the first FFT calculation 45 46 private WaveDataRingBuffer mWaveDataRing; // Record last n seconds of wave data 47 48 private final double mFrequency1; 49 private final double mFrequency2; //currently not used 50 private final int mSamplingRate; 51 private final int mFFTSamplingSize; // amount of samples used to perform a FFT 52 private final int mFFTOverlapSamples; // amount of overlapped samples used between two FFTs 53 private final int mNewSamplesPerFFT; // amount of new samples (not from last FFT) in a FFT 54 private double mCenterOfMass; // expected center of mass of samples 55 56 private final int[] mGlitches; // for every value = n, n is nth FFT where a glitch is found 57 private int mGlitchesIndex; 58 private int mFFTCount; // store the current number of FFT performed 59 private FFT mFFT; 60 private boolean mGlitchingIntervalTooLong = false; // true if mGlitches is full 61 62 // Pre-Allocated buffers for glitch detection process 63 private final double[] mFFTResult; 64 private final double[] mCurrentSamples; 65 private final double[] mImagArray; 66 67 // Used for captured SysTrace dumps 68 private CaptureHolder mCaptureHolder; 69 private int mLastGlitchCaptureAttempt = 0; 70 GlitchDetectionThread(double frequency1, double frequency2, int samplingRate, int FFTSamplingSize, int FFTOverlapSamples, int bufferTestDurationInSeconds, int bufferTestWavePlotDurationInSeconds, Pipe pipe, CaptureHolder captureHolder)71 GlitchDetectionThread(double frequency1, double frequency2, int samplingRate, 72 int FFTSamplingSize, int FFTOverlapSamples, int bufferTestDurationInSeconds, 73 int bufferTestWavePlotDurationInSeconds, Pipe pipe, CaptureHolder captureHolder) { 74 mPipe = pipe; 75 mFrequency1 = frequency1; 76 mFrequency2 = frequency2; 77 mFFTSamplingSize = FFTSamplingSize; 78 mFFTOverlapSamples = FFTOverlapSamples; 79 mNewSamplesPerFFT = mFFTSamplingSize - mFFTOverlapSamples; 80 mSamplingRate = samplingRate; 81 mIsRunning = true; 82 83 mShortBuffer = new short[mFFTSamplingSize]; 84 mDoubleBuffer = new double[mFFTSamplingSize]; 85 mWaveDataRing = new WaveDataRingBuffer(mSamplingRate * bufferTestWavePlotDurationInSeconds); 86 87 final int acceptableGlitchingIntervalsPerSecond = 10; 88 mGlitches = new int[bufferTestDurationInSeconds * acceptableGlitchingIntervalsPerSecond]; 89 mGlitchesIndex = 0; 90 mFFTCount = 0; 91 92 mFFTResult = new double[mFFTSamplingSize/2]; 93 mCurrentSamples = new double[mFFTSamplingSize]; 94 mImagArray = new double[mFFTSamplingSize]; 95 96 mFFT = new FFT(mFFTSamplingSize); 97 computeExpectedCenterOfMass(); 98 99 setName("Loopback_GlitchDetection"); 100 101 mCaptureHolder = captureHolder; 102 mCaptureHolder.setWaveDataBuffer(mWaveDataRing); 103 104 mThreadSleepDurationMs = FFTOverlapSamples * Constant.MILLIS_PER_SECOND / mSamplingRate; 105 if (mThreadSleepDurationMs < 1) { 106 mThreadSleepDurationMs = 1; // sleeps at least 1ms 107 } 108 } 109 110 run()111 public void run() { 112 while (mIsRunning) { 113 int requiredRead; 114 int actualRead; 115 116 requiredRead = mFFTSamplingSize - mShortBufferIndex; 117 actualRead = mPipe.read(mShortBuffer, mShortBufferIndex, requiredRead); 118 119 if (actualRead > 0) { 120 mShortBufferIndex += actualRead; 121 } 122 123 if (actualRead == Pipe.OVERRUN) { 124 log("There's an overrun"); 125 } 126 127 // Once we have enough data, we can do a FFT on it. Note that between two FFTs, part of 128 // the samples (of size mFFTOverlapSamples) are used in both FFTs . 129 if (mShortBufferIndex == mFFTSamplingSize) { 130 bufferShortToDouble(mShortBuffer, mDoubleBuffer); 131 132 // copy data in mDoubleBuffer to mWaveData 133 if (mIsFirstFFT) { 134 // if it's the first FFT, copy the whole "mNativeBuffer" to mWaveData 135 mWaveDataRing.writeWaveData(mDoubleBuffer, 0, mFFTSamplingSize); 136 mIsFirstFFT = false; 137 } else { 138 mWaveDataRing.writeWaveData(mDoubleBuffer, mFFTOverlapSamples, 139 mNewSamplesPerFFT); 140 } 141 142 detectGlitches(); 143 // move new samples to the beginning of the array as they will be reused in next fft 144 System.arraycopy(mShortBuffer, mNewSamplesPerFFT, mShortBuffer, 145 0, mFFTOverlapSamples); 146 mShortBufferIndex = mFFTOverlapSamples; 147 } else { 148 try { 149 sleep(mThreadSleepDurationMs); 150 } catch (InterruptedException e) { 151 e.printStackTrace(); 152 } 153 } 154 } 155 156 } 157 158 159 /** convert samples in shortBuffer to double, then copy into doubleBuffer. */ 160 // TODO move to audio_utils bufferShortToDouble(short[] shortBuffer, double[] doubleBuffer)161 private void bufferShortToDouble(short[] shortBuffer, double[] doubleBuffer) { 162 double temp; 163 for (int i = 0; i < shortBuffer.length; i++) { 164 temp = (double) shortBuffer[i]; 165 temp *= (1.0 / Short.MAX_VALUE); 166 doubleBuffer[i] = temp; 167 } 168 } 169 170 171 /** Should be called by other thread to stop this thread */ requestStop()172 public void requestStop() { 173 mIsRunning = false; 174 interrupt(); 175 } 176 177 178 /** 179 * Use the data in mDoubleBuffer to do glitch detection since we know what 180 * data we are expecting. 181 */ detectGlitches()182 private void detectGlitches() { 183 double centerOfMass; 184 185 // retrieve a copy of recorded wave data for manipulating and analyzing 186 System.arraycopy(mDoubleBuffer, 0, mCurrentSamples, 0, mDoubleBuffer.length); 187 188 Utilities.hanningWindow(mCurrentSamples); 189 190 double width = (double) mSamplingRate / mCurrentSamples.length; 191 computeFFT(mCurrentSamples, mFFTResult); // gives an array of sampleSize / 2 192 final double threshold = 0.1; 193 194 // for all elements in the FFT result that are smaller than threshold, 195 // eliminate them as they are probably noise 196 for (int j = 0; j < mFFTResult.length; j++) { 197 if (mFFTResult[j] < threshold) { 198 mFFTResult[j] = 0; 199 } 200 } 201 202 // calculate the center of mass of sample's FFT 203 centerOfMass = computeCenterOfMass(mFFTResult, width); 204 double difference = (Math.abs(centerOfMass - mCenterOfMass) / mCenterOfMass); 205 if (mGlitchesIndex >= mGlitches.length) { 206 // we just want to show this log once and set the flag once. 207 if (!mGlitchingIntervalTooLong) { 208 log("Not enough room to store glitches!"); 209 mGlitchingIntervalTooLong = true; 210 } 211 } else { 212 // centerOfMass == -1 if the wave we get is silence. 213 if (difference > mAcceptablePercentDifference || centerOfMass == -1) { 214 // Glitch Detected 215 mGlitches[mGlitchesIndex] = mFFTCount; 216 mGlitchesIndex++; 217 if (mCaptureHolder.isCapturing()) { 218 checkGlitchConcentration(); 219 } 220 } 221 } 222 mFFTCount++; 223 } 224 checkGlitchConcentration()225 private void checkGlitchConcentration() { 226 227 final int recordedGlitch = mGlitches[mGlitchesIndex-1]; 228 if (recordedGlitch - mLastGlitchCaptureAttempt <= COOLDOWN_WINDOW) { 229 return; 230 } 231 232 final int windowBegin = recordedGlitch - GLITCH_CONCENTRATION_WINDOW_SIZE; 233 234 int numGlitches = 0; 235 for (int index = mGlitchesIndex-1; index >= 0 && mGlitches[index] >= windowBegin; --index) { 236 ++numGlitches; 237 } 238 239 int captureResponse = mCaptureHolder.captureState(numGlitches); 240 if (captureResponse != CaptureHolder.NEW_CAPTURE_IS_LEAST_INTERESTING) { 241 mLastGlitchCaptureAttempt = recordedGlitch; 242 } 243 244 } 245 246 /** Compute the center of mass of fftResults. Width is the width of each beam. */ computeCenterOfMass(double[] fftResult, double width)247 private double computeCenterOfMass(double[] fftResult, double width) { 248 int length = fftResult.length; 249 double weightedSum = 0; 250 double totalWeight = 0; 251 for (int i = 0; i < length; i++) { 252 weightedSum += fftResult[i] * i; 253 totalWeight += fftResult[i]; 254 } 255 256 // this may happen since we are eliminating the noises. So if the wave we got is silence, 257 // totalWeight might == 0. 258 if (totalWeight == 0) { 259 return -1; 260 } 261 262 return (weightedSum * width) / totalWeight; 263 } 264 265 266 /** Compute FFT of a set of data "samples". */ computeFFT(double[] src, double[] dst)267 private void computeFFT(double[] src, double[] dst) { 268 Arrays.fill(mImagArray, 0); 269 mFFT.fft(src, mImagArray, 1); // here src array and imagArray get set 270 271 272 for (int i = 0; i < (src.length / 2); i++) { 273 dst[i] = Math.sqrt(src[i] * src[i] + mImagArray[i] * mImagArray[i]); 274 } 275 276 } 277 278 279 /** Compute the center of mass if the samples have no glitches. */ computeExpectedCenterOfMass()280 private void computeExpectedCenterOfMass() { 281 SineWaveTone sineWaveTone = new SineWaveTone(mSamplingRate, mFrequency1); 282 double[] sineWave = new double[mFFTSamplingSize]; 283 double centerOfMass; 284 double[] sineFFTResult = new double[mFFTSamplingSize/2]; 285 286 sineWaveTone.generateTone(sineWave, mFFTSamplingSize); 287 Utilities.hanningWindow(sineWave); 288 double width = (double) mSamplingRate / sineWave.length; 289 290 computeFFT(sineWave, sineFFTResult); // gives an array of sample sizes / 2 291 centerOfMass = computeCenterOfMass(sineFFTResult, width); // return center of mass 292 mCenterOfMass = centerOfMass; 293 log("the expected center of mass:" + Double.toString(mCenterOfMass)); 294 } 295 296 getWaveData()297 public double[] getWaveData() { 298 return mWaveDataRing.getWaveRecord(); 299 } 300 301 getGlitchingIntervalTooLong()302 public boolean getGlitchingIntervalTooLong() { 303 return mGlitchingIntervalTooLong; 304 } 305 306 getGlitches()307 public int[] getGlitches() { 308 //return a copy of recorded glitches in an array sized to hold only recorded glitches 309 int[] output = new int[mGlitchesIndex]; 310 System.arraycopy(mGlitches, 0, output, 0, mGlitchesIndex); 311 return output; 312 } 313 314 log(String msg)315 private static void log(String msg) { 316 Log.v(TAG, msg); 317 } 318 319 } 320