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. */ 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 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 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