blob: 2605f77c6f0e8590b491f3a34dfd1c11aee0da52 [file] [log] [blame]
Brian Muramatsuf8946202010-11-09 13:43:39 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17#include "GlitchTest.h"
18#include "Window.h"
19#include "Fft.h"
20
21#include <math.h>
22
23GlitchTest::GlitchTest(void) : mRe(0), mIm(0), mFt(0), mWind(0) {}
24
25void GlitchTest::init(float sampleRate, float stimFreq, float onsetThresh,
26 float dbSnrThresh) {
27 cleanup(); // in case Init gets called multiple times.
28 // Analysis parameters
29 float windowDur = 0.025; // Duration of the analysis window in seconds.
30 float frameInterval = 0.01; // Interval in seconds between frame onsets
31 float lowestFreq = 200.0; // Lowest frequency to include in
32 // background noise power integration.
33 mSampleRate = sampleRate;
34 mOnsetThresh = onsetThresh;
35 mDbSnrThresh = dbSnrThresh;
36 mFrameStep = (int)(0.5 + (mSampleRate * frameInterval));
37 mWindowSize = (int)(0.5 + (mSampleRate * windowDur));
38 mWind = new Window(mWindowSize);
39 mFt = new Fft;
40 mFt->fftInit(mFt->fftPow2FromWindowSize(mWindowSize));
41 mFftSize = mFt->fftGetSize();
42 mRe = new float[mFftSize];
43 mIm = new float[mFftSize];
44 float freqInterval = mSampleRate / mFftSize;
45 // We can exclude low frequencies from consideration, since the
46 // phone may have DC offset in the A/D, and there may be rumble in
47 // the testing room.
48 mLowestSpectrumBin = (int)(0.5 + (lowestFreq / freqInterval));
49 // These are the bin indices within which most of the energy due to
50 // the (windowed) tone should be found.
51 mLowToneBin = (int)(0.5 + (stimFreq / freqInterval)) - 2;
52 mHighToneBin = (int)(0.5 + (stimFreq / freqInterval)) + 2;
53 if (mLowestSpectrumBin >= mLowToneBin) {
54 mLowestSpectrumBin = mHighToneBin + 1;
55 }
56}
57
58int GlitchTest::checkToneSnr(short* pcm, int numSamples, float* duration,
59 int* numBadFrames) {
60 *numBadFrames = 0;
61 *duration = 0.0;
62 if (!(mRe && mIm)) {
63 return -1; // not initialized.
64 }
65 int n_frames = 1 + ((numSamples - mWindowSize) / mFrameStep);
66 if (n_frames < 4) { // pathologically short input signal
67 return -2;
68 }
69 *numBadFrames = 0;
70 int onset = -1;
71 int offset = -1;
72 for (int frame = 0; frame < n_frames; ++frame) {
73 int numSpectra = 0;
74 mWind->window(pcm + frame*mFrameStep, mRe, 0.0);
75 realMagSqSpectrum(mRe, mWindowSize, mRe, &numSpectra);
76 int maxLoc = 0;
77 float maxValue = 0.0;
78 findPeak(mRe, mLowestSpectrumBin, numSpectra, &maxLoc, &maxValue);
79 // possible states: (1) before tone onset; (2) within tone
80 // region; (3) after tone offset.
81 if ((onset < 0) && (offset < 0)) { // (1)
82 if ((maxLoc >= mLowToneBin) && (maxLoc <= mHighToneBin)
83 && (maxValue > mOnsetThresh)) {
84 onset = frame;
85 }
86 continue;
87 }
88 if ((onset >= 0) && (offset < 0)) { // (2)
89 if (frame > (onset + 2)) { // let the framer get completely
90 // into the tonal signal
91 double sumNoise = 1.0; // init. to small non-zero vals to
92 // avoid log or divz problems
93 double sumSignal = 1.0;
94 float snr = 0.0;
95 if (maxValue < mOnsetThresh) {
96 offset = frame;
97 *duration = mFrameStep * (offset - onset) / mSampleRate;
98 if (*numBadFrames >= 1) {
99 (*numBadFrames) -= 1; // account for expected glitch at
100 // signal offset
101 }
102 continue;
103 }
104 for (int i = mLowestSpectrumBin; i < mLowToneBin; ++i) {
105 sumNoise += mRe[i]; // note that mRe contains the magnitude
106 // squared spectrum.
107 }
108 for (int i = mLowToneBin; i <= mHighToneBin; ++i)
109 sumSignal += mRe[i];
110 for (int i = mHighToneBin + 1; i < numSpectra; ++i) {
111 sumNoise += mRe[i]; // Note: mRe has the mag squared spectrum.
112 }
113 snr = 10.0 * log10(sumSignal / sumNoise);
114 if (snr < mDbSnrThresh)
115 (*numBadFrames) += 1;
116 }
117 continue;
118 }
119 if ((onset >= 0) && (offset > 0)) { // (3)
120 if ((maxLoc >= mLowToneBin) && (maxLoc <= mHighToneBin) &&
121 (maxValue > mOnsetThresh)) { // tone should not pop up again!
122 (*numBadFrames) += 1;
123 }
124 continue;
125 }
126 }
127 if ((onset >= 0) && (offset > 0))
128 return 1; // Success.
129 if (onset < 0) {
130 return -3; // Signal onset not found.
131 }
132 return -4; // Signal offset not found.
133}
134
135void GlitchTest::cleanup(void) {
136 delete [] mRe;
137 delete [] mIm;
138 delete mFt;
139 delete mWind;
140 mRe = 0;
141 mIm = 0;
142 mWind = 0;
143 mFt = 0;
144}
145
146int GlitchTest::realMagSqSpectrum(float* data, int numInput,
147 float* output, int* numOutput) {
148 *numOutput = 0;
149 if ((numInput <= 0) || (numInput > mFftSize))
150 return 0;
151 int i = 0;
152 for (i = 0; i < numInput; ++i) {
153 mRe[i] = data[i];
154 mIm[i] = 0.0;
155 }
156 for ( ; i < mFftSize; ++i) {
157 mRe[i] = 0.0;
158 mIm[i] = 0.0;
159 }
160 mFt->fft(mRe, mIm);
161 *numOutput = 1 + (mFftSize / 2);
162 for (i = 0; i < *numOutput; ++i) {
163 output[i] = (mRe[i] * mRe[i]) + (mIm[i] * mIm[i]);
164 }
165 return 1;
166}
167
168void GlitchTest::findPeak(float* data, int startSearch, int endSearch,
169 int* maxLoc, float* maxValue) {
170 float amax = data[startSearch];
171 int loc = startSearch;
172 for (int i = startSearch + 1; i < endSearch; ++i) {
173 if (data[i] > amax) {
174 amax = data[i];
175 loc = i;
176 }
177 }
178 *maxLoc = loc;
179 *maxValue = 10.0 * log10(amax / mWindowSize);
180}
181