blob: d83b05644fb9bf34bd5ffd93f40c1df32f89d8fb [file] [log] [blame]
Keun young Park80176b72011-12-20 16:31:19 -08001/*
2 * Copyright (C) 2011 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/* This test accepts a collection of N speech waveforms collected as
18 part of N recognition attempts. The waveforms are ordered by
19 increasing presentation level. The test determines the extent to
20 which the peak amplitudes in the waveforms track the change in
21 presentation level. Failure to track the presentation level within
22 some reasonable margin is an indication of clipping or of automatic
23 gain control in the signal path.
24
25 RMS of each level is used as a parameter for deciding lienairy.
26 For each level, RMS is calculated, and a line fitting into RMS vs level
27 will be calculated. Then for each level, residual error of measurement
28 vs line fitting will be calculated, and the residual error is normalized
29 with each measurement. The test failes if residual error is bigger than
30 2dB.
31
32 This test will be robust to background noise as long as it is persistent.
33 But background noise which appears shortly with enough strength can
34 mess up the result.
35*/
36
37#include <stdlib.h>
38#include <stdio.h>
39#include <math.h>
40#include <cutils/log.h>
41#include "LinearityTest.h"
42
43#define LOG_TAG "LinearityTestRms"
44
45// vectorDot, vectorNorm, and solveLeastSquares
46// copied from frameworks/base/libs/ui/Input.cpp
47
48static inline float vectorDot(const float* a, const float* b, uint32_t m) {
49 float r = 0;
50 while (m--) {
51 r += *(a++) * *(b++);
52 }
53 return r;
54}
55
56static inline float vectorNorm(const float* a, uint32_t m) {
57 float r = 0;
58 while (m--) {
59 float t = *(a++);
60 r += t * t;
61 }
62 return sqrtf(r);
63}
64
65/**
66 * Solves a linear least squares problem to obtain a N degree polynomial that fits
67 * the specified input data as nearly as possible.
68 *
69 * Returns true if a solution is found, false otherwise.
70 *
71 * The input consists of two vectors of data points X and Y with indices 0..m-1.
72 * The output is a vector B with indices 0..n-1 that describes a polynomial
73 * that fits the data, such the sum of abs(Y[i] - (B[0] + B[1] X[i] + B[2] X[i]^2 ... B[n] X[i]^n))
74 * for all i between 0 and m-1 is minimized.
75 *
76 * That is to say, the function that generated the input data can be approximated
77 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
78 *
79 * The coefficient of determination (R^2) is also returned to describe the goodness
80 * of fit of the model for the given data. It is a value between 0 and 1, where 1
81 * indicates perfect correspondence.
82 *
83 * This function first expands the X vector to a m by n matrix A such that
84 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n.
85 *
86 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
87 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
88 * part is all zeroes), we can simplify the decomposition into an m by n matrix
89 * Q1 and a n by n matrix R1 such that A = Q1 R1.
90 *
91 * Finally we solve the system of linear equations given by R1 B = (Qtranspose Y)
92 * to find B.
93 *
94 * For efficiency, we lay out A and Q column-wise in memory because we frequently
95 * operate on the column vectors. Conversely, we lay out R row-wise.
96 *
97 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
98 * http://en.wikipedia.org/wiki/Gram-Schmidt
99 */
100static bool solveLeastSquares(const float* x, const float* y, uint32_t m, uint32_t n,
101 float* outB, float* outDet) {
102#if DEBUG_LEAST_SQUARES
103 LOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s", int(m), int(n),
104 vectorToString(x, m).string(), vectorToString(y, m).string());
105#endif
106
107 // Expand the X vector to a matrix A.
108 float a[n][m]; // column-major order
109 for (uint32_t h = 0; h < m; h++) {
110 a[0][h] = 1;
111 for (uint32_t i = 1; i < n; i++) {
112 a[i][h] = a[i - 1][h] * x[h];
113 }
114 }
115#if DEBUG_LEAST_SQUARES
116 LOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string());
117#endif
118
119 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
120 float q[n][m]; // orthonormal basis, column-major order
121 float r[n][n]; // upper triangular matrix, row-major order
122 for (uint32_t j = 0; j < n; j++) {
123 for (uint32_t h = 0; h < m; h++) {
124 q[j][h] = a[j][h];
125 }
126 for (uint32_t i = 0; i < j; i++) {
127 float dot = vectorDot(&q[j][0], &q[i][0], m);
128 for (uint32_t h = 0; h < m; h++) {
129 q[j][h] -= dot * q[i][h];
130 }
131 }
132
133 float norm = vectorNorm(&q[j][0], m);
134 if (norm < 0.000001f) {
135 // vectors are linearly dependent or zero so no solution
136#if DEBUG_LEAST_SQUARES
137 LOGD(" - no solution, norm=%f", norm);
138#endif
139 return false;
140 }
141
142 float invNorm = 1.0f / norm;
143 for (uint32_t h = 0; h < m; h++) {
144 q[j][h] *= invNorm;
145 }
146 for (uint32_t i = 0; i < n; i++) {
147 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
148 }
149 }
150#if DEBUG_LEAST_SQUARES
151 LOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string());
152 LOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string());
153
154 // calculate QR, if we factored A correctly then QR should equal A
155 float qr[n][m];
156 for (uint32_t h = 0; h < m; h++) {
157 for (uint32_t i = 0; i < n; i++) {
158 qr[i][h] = 0;
159 for (uint32_t j = 0; j < n; j++) {
160 qr[i][h] += q[j][h] * r[j][i];
161 }
162 }
163 }
164 LOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string());
165#endif
166
167 // Solve R B = Qt Y to find B. This is easy because R is upper triangular.
168 // We just work from bottom-right to top-left calculating B's coefficients.
169 for (uint32_t i = n; i-- != 0; ) {
170 outB[i] = vectorDot(&q[i][0], y, m);
171 for (uint32_t j = n - 1; j > i; j--) {
172 outB[i] -= r[i][j] * outB[j];
173 }
174 outB[i] /= r[i][i];
175 }
176#if DEBUG_LEAST_SQUARES
177 LOGD(" - b=%s", vectorToString(outB, n).string());
178#endif
179
180 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
181 // SSerr is the residual sum of squares (squared variance of the error),
182 // and SStot is the total sum of squares (squared variance of the data).
183 float ymean = 0;
184 for (uint32_t h = 0; h < m; h++) {
185 ymean += y[h];
186 }
187 ymean /= m;
188
189 float sserr = 0;
190 float sstot = 0;
191 for (uint32_t h = 0; h < m; h++) {
192 float err = y[h] - outB[0];
193 float term = 1;
194 for (uint32_t i = 1; i < n; i++) {
195 term *= x[h];
196 err -= term * outB[i];
197 }
198 sserr += err * err;
199 float var = y[h] - ymean;
200 sstot += var * var;
201 }
202 *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
203#if DEBUG_LEAST_SQUARES
204 LOGD(" - sserr=%f", sserr);
205 LOGD(" - sstot=%f", sstot);
206 LOGD(" - det=%f", *outDet);
207#endif
208 return true;
209}
210
211/* calculate RMS of given sample with numSamples of length */
212float calcRms(short* pcm, int numSamples)
213{
214 float energy = 0.0f;
215 for(int i = 0; i < numSamples; i++) {
216 float sample = (float)pcm[i];
217 energy += (sample * sample);
218 }
219 return sqrtf(energy);
220}
221
222/* There are numSignals int16 signals in pcms. sampleCounts is an
223 integer array of length numSignals containing their respective
224 lengths in samples. They are all sampled at sampleRate. The pcms
225 are ordered by increasing stimulus level. The level steps between
226 successive stimuli were of size dbStepSize dB.
227 The maximum deviation in linearity found
228 (in dB) is returned in maxDeviation. The function returns 1 if
229 the measurements could be made, or a negative number that
230 indicates the error, as defined in LinearityTest.h */
231int linearityTestRms(short** pcms, int* sampleCounts, int numSignals,
232 float sampleRate, float dbStepSize,
233 float* maxDeviation) {
234 if (!(pcms && sampleCounts)) {
235 return ERROR_INPUT_SIGNAL_MISSING;
236 }
237 if (numSignals < 2) {
238 return ERROR_INPUT_SIGNAL_NUMBERS;
239 }
240 if (sampleRate <= 4000.0) {
241 return ERROR_SAMPLE_RATE_TOO_LOW;
242 }
243 if (dbStepSize <= 0.0) {
244 return ERROR_NEGATIVE_STEP_SIZE;
245 }
246
247 float* levels = new float[numSignals];
248 levels[0] = 1.0f;
249 float stepInMag = powf(10.0f, dbStepSize/20.0f);
250 for(int i = 1; i < numSignals; i++) {
251 levels[i] = levels[i - 1] * stepInMag;
252 }
253
254 float* rmsValues = new float[numSignals];
255 for (int i = 0; i < numSignals; i++) {
256 rmsValues[i] = calcRms(pcms[i], sampleCounts[i]);
257 }
258 const int NO_COEFFS = 2; // only line fitting
259 float coeffs[NO_COEFFS];
260 float outDet;
261 if(!solveLeastSquares(levels, rmsValues, numSignals, NO_COEFFS,
262 coeffs, &outDet)) {
Steve Block11cd6062012-01-04 20:04:35 +0000263 ALOGI(" solveLeastSquares fails with det %f", outDet);
Keun young Park80176b72011-12-20 16:31:19 -0800264 return ERROR_LINEAR_FITTING;
265 }
Steve Block11cd6062012-01-04 20:04:35 +0000266 ALOGI(" coeffs offset %f linear %f", coeffs[0], coeffs[1]);
Keun young Park80176b72011-12-20 16:31:19 -0800267 float maxDev = 0.0f;
268 for(int i = 0; i < numSignals; i++) {
269 float residue = coeffs[0] + coeffs[1] * levels[i] - rmsValues[i];
270 // to make devInDb positive, add measured value itself
271 // then normalize
272 float devInDb = 20.0f * log10f((fabs(residue) + rmsValues[i])
273 / rmsValues[i]);
Steve Block11cd6062012-01-04 20:04:35 +0000274 ALOGI(" %d-th residue %f dB", i, devInDb);
Keun young Park80176b72011-12-20 16:31:19 -0800275 if (devInDb > maxDev) {
276 maxDev = devInDb;
277 }
278 }
279 *maxDeviation = maxDev;
280
281 delete[] levels;
282 delete[] rmsValues;
283
284 return 1;
285}