• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 // An amplitude-normalized spectrum comparison method.
18 
19 #include "Fft.h"
20 #include "Window.h"
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <math.h>
25 
26 /* Find the endpoints of the signal stored in data such that the rms of
27    the found segment exceeds signalOnRms.  data is of length n.  The
28    RMS calculations used to find the endpoints use a window of length
29    step and advance step samples.  The approximate, conservative
30    endpoint sample indices are returned in start and end. */
findEndpoints(short * data,int n,int step,float signalOnRms,int * start,int * end)31 static void findEndpoints(short* data, int n, int step, float signalOnRms,
32                           int* start, int* end) {
33     int size = step;
34     *start = *end = 0;
35     int last_frame = n - size;
36     for (int frame = 0; frame < last_frame; frame += step) {
37         double sum = 0.0;
38         for (int i=0; i < size; ++i) {
39             float val = data[i + frame];
40             sum += (val * val);
41         }
42         float rms = sqrt(sum / size);
43         if (! *start) {
44             if (rms >= signalOnRms) {
45                 *start = frame + size;
46             }
47             continue;
48         } else {
49             if (rms < signalOnRms) {
50                 *end = frame - size;
51                 // fprintf(stderr, "n:%d onset:%d offset:%d\n", n, *start, *end);
52                 return;
53             }
54         }
55     }
56     // Handle the case where the signal does not drop below threshold
57     // after onset.
58     if ((*start > 0) && (! *end)) {
59         *end = n - size - 1;
60     }
61 }
62 
63 // Sum the magnitude squared spectra.
accumulateMagnitude(float * im,float * re,int size,double * mag)64 static void accumulateMagnitude(float* im, float* re, int size, double* mag) {
65     for (int i = 0; i < size; ++i) {
66         mag[i] += ((re[i] * re[i]) + (im[i] * im[i]));
67     }
68 }
69 
70 /* Return a pointer to 1+(fftSize/2) spectrum magnitude values
71    averaged over all of the numSamples in pcm.  It is the
72    responsibility of the caller to free this magnitude array.  Return
73    NULL on failure.  Use 50% overlap on the spectra. An FFT of
74    fftSize points is used to compute the spectra, The overall signal
75    rms over all frequencies between lowestBin and highestBin is
76    returned as a scalar in rms. */
getAverageSpectrum(short * pcm,int numSamples,int fftSize,int lowestBin,int highestBin,float * rms)77 double* getAverageSpectrum(short* pcm, int numSamples, int fftSize,
78                            int lowestBin, int highestBin, float* rms) {
79     if (numSamples < fftSize) return NULL;
80     int numFrames = 1 + ((2 * (numSamples - fftSize)) / fftSize);
81     int numMag = 1 + (fftSize / 2);
82     float* re = new float[fftSize];
83     float* im = new float[fftSize];
84     double* mag = new double[numMag];
85     for (int i = 0; i < numMag; ++i) {
86         mag[i] = 0.0;
87     }
88     Window wind(fftSize);
89     Fft ft;
90     int pow2 = ft.fftPow2FromWindowSize(fftSize);
91     ft.fftInit(pow2);
92     int input_p = 0;
93     for (int i = 0; i < numFrames; ++i) {
94         wind.window(pcm + input_p, re, 0.0);
95         for (int j = 0; j < fftSize; ++j) {
96             im[j] = 0.0;
97         }
98         ft.fft(re,im);
99         accumulateMagnitude(im, re, numMag, mag);
100         input_p += fftSize / 2;
101     }
102     double averageEnergy = 0.0; // per frame average energy
103     for (int i = 0; i < numMag; ++i) {
104         double e = mag[i]/numFrames;
105         if ((i >= lowestBin) && (i <= highestBin))
106             averageEnergy += e;
107         mag[i] = sqrt(e);
108     }
109     *rms = sqrt(averageEnergy / (highestBin - lowestBin + 1));
110     delete [] re;
111     delete [] im;
112     return mag;
113 }
114 
115 /* Compare the average magnitude spectra of the signals in pcm and
116    refPcm, which are of length numSamples and numRefSamples,
117    respectively; both sampled at sample_rate.  The maximum deviation
118    between average spectra, expressed in dB, is returned in
119    maxDeviation, and the rms of all dB variations is returned in
120    rmsDeviation.  Note that a lower limit is set on the frequencies that
121    are compared so as to ignore irrelevant DC and rumble components.  If
122    the measurement fails for some reason, return 0; else return 1, for
123    success.  Causes for failure include the amplitude of one or both of
124    the signals being too low, or the duration of the signals being too
125    short.
126 
127    Note that the expected signal collection scenario is that the phone
128    would be stimulated with a broadband signal as in a recognition
129    attempt, so that there will be some "silence" regions at the start and
130    end of the pcm signals.  The preferred stimulus would be pink noise,
131    but any broadband signal should work. */
compareSpectra(short * pcm,int numSamples,short * refPcm,int numRefSamples,float sample_rate,float * maxDeviation,float * rmsDeviation)132 int compareSpectra(short* pcm, int numSamples, short* refPcm,
133                    int numRefSamples, float sample_rate,
134                    float* maxDeviation, float* rmsDeviation) {
135     int fftSize = 512;           // must be a power of 2
136     float lowestFreq = 100.0;    // don't count DC, room rumble, etc.
137     float highestFreq = 3500.0;  // ignore most effects of sloppy anti alias filters.
138     int lowestBin = int(0.5 + (lowestFreq * fftSize / sample_rate));
139     int highestBin = int(0.5 + (highestFreq * fftSize / sample_rate));
140     float signalOnRms = 1000.0; // endpointer RMS on/off threshold
141     int endpointStepSize = int(0.5 + (sample_rate * 0.02)); // 20ms setp
142     float rms1 = 0.0;
143     float rms2 = 0.0;
144     int onset = 0;
145     int offset = 0;
146     findEndpoints(refPcm, numRefSamples, endpointStepSize, signalOnRms,
147                    &onset, &offset);
148     double* spect1 = getAverageSpectrum(refPcm + onset, offset - onset,
149                                         fftSize, lowestBin, highestBin, &rms1);
150     findEndpoints(pcm, numSamples, endpointStepSize, signalOnRms,
151                   &onset, &offset);
152     double* spect2 = getAverageSpectrum(pcm + onset, offset - onset,
153                                         fftSize, lowestBin, highestBin, &rms2);
154     int magSize = 1 + (fftSize/2);
155     if ((rms1 <= 0.0) || (rms2 <= 0.0))
156         return 0; // failure because one or both signals are too short or
157                   // too low in amplitude.
158     float rmsNorm = rms2 / rms1; // compensate for overall gain differences
159     // fprintf(stderr, "Level normalization: %f dB\n", 20.0 * log10(rmsNorm));
160     *maxDeviation = 0.0;
161     float sumDevSquared = 0.0;
162     for (int i = lowestBin; i <= highestBin; ++i) {
163         double val = 1.0;
164         if ((spect1[i] > 0.0) && (spect2[i] > 0.0)) {
165             val = 20.0 * log10(rmsNorm * spect1[i] / spect2[i]);
166         }
167         sumDevSquared += val * val;
168         if (fabs(val) > fabs(*maxDeviation)) {
169             *maxDeviation = val;
170         }
171         // fprintf(stderr, "%d %f\n", i, val);
172     }
173     *rmsDeviation = sqrt(sumDevSquared / (highestBin - lowestBin + 1));
174     delete [] spect1;
175     delete [] spect2;
176     return 1; // success
177 }
178 
179 
180