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