--- /dev/null
+/*
+ * 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;
+}
+