Home | History | Annotate | Download | only in audioquality
      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