/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#include <math.h>
/* Return the median of the n values in "values".
Uses a stupid bubble sort, but is only called once on small array. */
float getMedian(float* values, int n) {
if (n <= 0)
return 0.0;
if (n == 1)
return values[0];
if (n == 2)
return 0.5 * (values[0] + values[1]);
for (int i = 1; i < n; ++i)
for (int j = i; j < n; ++j) {
if (values[j] < values[i-1]) {
float tmp = values[i-1];
values[i-1] = values[j];
values[j] = tmp;
}
}
int ind = int(0.5 + (0.5 * n)) - 1;
return values[ind];
}
float computeAndRemoveMean(short* pcm, int numSamples) {
float sum = 0.0;
for (int i = 0; i < numSamples; ++i)
sum += pcm[i];
short mean;
if (sum >= 0.0)
mean = (short)(0.5 + (sum / numSamples));
else
mean = (short)((sum / numSamples) - 0.5);
for (int i = 0; i < numSamples; ++i)
pcm[i] -= mean;
return sum / numSamples;
}
void measureRms(short* pcm, int numSamples, float sampleRate, float onsetThresh,
float* rms, float* stdRms, float* mean, float* duration) {
*rms = 0.0;
*stdRms = 0.0;
*duration = 0.0;
float frameDur = 0.025; // Both the duration and interval of the
// analysis frames.
float calInterval = 0.250; // initial part of signal used to
// establish background level (seconds).
double sumFrameRms = 1.0;
float sumSampleSquares = 0.0;
double sumFrameSquares = 1.0; // init. to small number to avoid
// log and divz problems.
int frameSize = (int)(0.5 + (sampleRate * frameDur));
int numFrames = numSamples / frameSize;
int numCalFrames = int(0.5 + (calInterval / frameDur));
if (numCalFrames < 1)
numCalFrames = 1;
int frame = 0;
*mean = computeAndRemoveMean(pcm, numSamples);
if (onsetThresh < 0.0) { // Handle the case where we want to
// simply measure the RMS of the entire
// input sequence.
for (frame = 0; frame < numFrames; ++frame) {
short* p_data = pcm + (frame * frameSize);
int i;
for (i = 0, sumSampleSquares = 0.0; i < frameSize; ++i) {
float samp = p_data[i];
sumSampleSquares += samp * samp;
}
sumSampleSquares /= frameSize;
sumFrameSquares += sumSampleSquares;
double localRms = sqrt(sumSampleSquares);
sumFrameRms += localRms;
}
*rms = sumFrameRms / numFrames;
*stdRms = sqrt((sumFrameSquares / numFrames) - (*rms * *rms));
*duration = frameSize * numFrames / sampleRate;
return;
}
/* This handles the case where we look for a target signal against a
background, and expect the signal to start some time after the
beginning, and to finish some time before the end of the input
samples. */
if (numFrames < (3 * numCalFrames)) {
return;
}
float* calValues = new float[numCalFrames];
float calMedian = 0.0;
int onset = -1;
int offset = -1;
for (frame = 0; frame < numFrames; ++frame) {
short* p_data = pcm + (frame * frameSize);
int i;
for (i = 0, sumSampleSquares = 1.0; i < frameSize; ++i) {
float samp = p_data[i];
sumSampleSquares += samp * samp;
}
sumSampleSquares /= frameSize;
/* We handle three states: (1) before the onset of the signal; (2)
within the signal; (3) following the signal. The signal is
assumed to be at least onsetThresh dB above the background
noise, and that at least one frame of silence/background
precedes the onset of the signal. */
if (onset < 0) { // (1)
sumFrameSquares += sumSampleSquares;
if (frame < numCalFrames ) {
calValues[frame] = sumSampleSquares;
continue;
}
if (frame == numCalFrames) {
calMedian = getMedian(calValues, numCalFrames);
if (calMedian < 10.0)
calMedian = 10.0; // avoid divz, etc.
}
float ratio = 10.0 * log10(sumSampleSquares / calMedian);
if (ratio > onsetThresh) {
onset = frame;
sumFrameSquares = 1.0;
sumFrameRms = 1.0;
}
continue;
}
if ((onset > 0) && (offset < 0)) { // (2)
int sig_frame = frame - onset;
if (sig_frame < numCalFrames) {
calValues[sig_frame] = sumSampleSquares;
} else {
if (sig_frame == numCalFrames) {
calMedian = getMedian(calValues, numCalFrames);
if (calMedian < 10.0)
calMedian = 10.0; // avoid divz, etc.
}
float ratio = 10.0 * log10(sumSampleSquares / calMedian);
int denFrames = frame - onset - 1;
if (ratio < (-onsetThresh)) { // found signal end
*rms = sumFrameRms / denFrames;
*stdRms = sqrt((sumFrameSquares / denFrames) - (*rms * *rms));
*duration = frameSize * (frame - onset) / sampleRate;
offset = frame;
continue;
}
}
sumFrameSquares += sumSampleSquares;
double localRms = sqrt(sumSampleSquares);
sumFrameRms += localRms;
continue;
}
if (offset > 0) { // (3)
/* If we have found the real signal end, the level should stay
low till data end. If not, flag this anomaly by increasing the
reported duration. */
float localRms = 1.0 + sqrt(sumSampleSquares);
float localSnr = 20.0 * log10(*rms / localRms);
if (localSnr < onsetThresh)
*duration = frameSize * (frame - onset) / sampleRate;
continue;
}
}
delete [] calValues;
}