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 #include <math.h>
18
19 /* Return the median of the n values in "values".
20 Uses a stupid bubble sort, but is only called once on small array. */
getMedian(float * values,int n)21 float getMedian(float* values, int n) {
22 if (n <= 0)
23 return 0.0;
24 if (n == 1)
25 return values[0];
26 if (n == 2)
27 return 0.5 * (values[0] + values[1]);
28 for (int i = 1; i < n; ++i)
29 for (int j = i; j < n; ++j) {
30 if (values[j] < values[i-1]) {
31 float tmp = values[i-1];
32 values[i-1] = values[j];
33 values[j] = tmp;
34 }
35 }
36 int ind = int(0.5 + (0.5 * n)) - 1;
37 return values[ind];
38 }
39
computeAndRemoveMean(short * pcm,int numSamples)40 float computeAndRemoveMean(short* pcm, int numSamples) {
41 float sum = 0.0;
42
43 for (int i = 0; i < numSamples; ++i)
44 sum += pcm[i];
45 short mean;
46 if (sum >= 0.0)
47 mean = (short)(0.5 + (sum / numSamples));
48 else
49 mean = (short)((sum / numSamples) - 0.5);
50 for (int i = 0; i < numSamples; ++i)
51 pcm[i] -= mean;
52 return sum / numSamples;
53 }
54
measureRms(short * pcm,int numSamples,float sampleRate,float onsetThresh,float * rms,float * stdRms,float * mean,float * duration)55 void measureRms(short* pcm, int numSamples, float sampleRate, float onsetThresh,
56 float* rms, float* stdRms, float* mean, float* duration) {
57 *rms = 0.0;
58 *stdRms = 0.0;
59 *duration = 0.0;
60 float frameDur = 0.025; // Both the duration and interval of the
61 // analysis frames.
62 float calInterval = 0.250; // initial part of signal used to
63 // establish background level (seconds).
64 double sumFrameRms = 1.0;
65 float sumSampleSquares = 0.0;
66 double sumFrameSquares = 1.0; // init. to small number to avoid
67 // log and divz problems.
68 int frameSize = (int)(0.5 + (sampleRate * frameDur));
69 int numFrames = numSamples / frameSize;
70 int numCalFrames = int(0.5 + (calInterval / frameDur));
71 if (numCalFrames < 1)
72 numCalFrames = 1;
73 int frame = 0;
74
75 *mean = computeAndRemoveMean(pcm, numSamples);
76
77 if (onsetThresh < 0.0) { // Handle the case where we want to
78 // simply measure the RMS of the entire
79 // input sequence.
80 for (frame = 0; frame < numFrames; ++frame) {
81 short* p_data = pcm + (frame * frameSize);
82 int i;
83 for (i = 0, sumSampleSquares = 0.0; i < frameSize; ++i) {
84 float samp = p_data[i];
85 sumSampleSquares += samp * samp;
86 }
87 sumSampleSquares /= frameSize;
88 sumFrameSquares += sumSampleSquares;
89 double localRms = sqrt(sumSampleSquares);
90 sumFrameRms += localRms;
91 }
92 *rms = sumFrameRms / numFrames;
93 *stdRms = sqrt((sumFrameSquares / numFrames) - (*rms * *rms));
94 *duration = frameSize * numFrames / sampleRate;
95 return;
96 }
97
98 /* This handles the case where we look for a target signal against a
99 background, and expect the signal to start some time after the
100 beginning, and to finish some time before the end of the input
101 samples. */
102 if (numFrames < (3 * numCalFrames)) {
103 return;
104 }
105 float* calValues = new float[numCalFrames];
106 float calMedian = 0.0;
107 int onset = -1;
108 int offset = -1;
109
110 for (frame = 0; frame < numFrames; ++frame) {
111 short* p_data = pcm + (frame * frameSize);
112 int i;
113 for (i = 0, sumSampleSquares = 1.0; i < frameSize; ++i) {
114 float samp = p_data[i];
115 sumSampleSquares += samp * samp;
116 }
117 sumSampleSquares /= frameSize;
118 /* We handle three states: (1) before the onset of the signal; (2)
119 within the signal; (3) following the signal. The signal is
120 assumed to be at least onsetThresh dB above the background
121 noise, and that at least one frame of silence/background
122 precedes the onset of the signal. */
123 if (onset < 0) { // (1)
124 sumFrameSquares += sumSampleSquares;
125 if (frame < numCalFrames ) {
126 calValues[frame] = sumSampleSquares;
127 continue;
128 }
129 if (frame == numCalFrames) {
130 calMedian = getMedian(calValues, numCalFrames);
131 if (calMedian < 10.0)
132 calMedian = 10.0; // avoid divz, etc.
133 }
134 float ratio = 10.0 * log10(sumSampleSquares / calMedian);
135 if (ratio > onsetThresh) {
136 onset = frame;
137 sumFrameSquares = 1.0;
138 sumFrameRms = 1.0;
139 }
140 continue;
141 }
142 if ((onset > 0) && (offset < 0)) { // (2)
143 int sig_frame = frame - onset;
144 if (sig_frame < numCalFrames) {
145 calValues[sig_frame] = sumSampleSquares;
146 } else {
147 if (sig_frame == numCalFrames) {
148 calMedian = getMedian(calValues, numCalFrames);
149 if (calMedian < 10.0)
150 calMedian = 10.0; // avoid divz, etc.
151 }
152 float ratio = 10.0 * log10(sumSampleSquares / calMedian);
153 int denFrames = frame - onset - 1;
154 if (ratio < (-onsetThresh)) { // found signal end
155 *rms = sumFrameRms / denFrames;
156 *stdRms = sqrt((sumFrameSquares / denFrames) - (*rms * *rms));
157 *duration = frameSize * (frame - onset) / sampleRate;
158 offset = frame;
159 continue;
160 }
161 }
162 sumFrameSquares += sumSampleSquares;
163 double localRms = sqrt(sumSampleSquares);
164 sumFrameRms += localRms;
165 continue;
166 }
167 if (offset > 0) { // (3)
168 /* If we have found the real signal end, the level should stay
169 low till data end. If not, flag this anomaly by increasing the
170 reported duration. */
171 float localRms = 1.0 + sqrt(sumSampleSquares);
172 float localSnr = 20.0 * log10(*rms / localRms);
173 if (localSnr < onsetThresh)
174 *duration = frameSize * (frame - onset) / sampleRate;
175 continue;
176 }
177 }
178 delete [] calValues;
179 }
180
181