1f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu/*
2f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * Copyright (C) 2010 The Android Open Source Project
3f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu *
4f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * use this file except in compliance with the License. You may obtain a copy of
6f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * the License at
7f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu *
8f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * http://www.apache.org/licenses/LICENSE-2.0
9f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu *
10f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * Unless required by applicable law or agreed to in writing, software
11f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * License for the specific language governing permissions and limitations under
14f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu * the License.
15f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu */
16f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
17f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu#include <math.h>
18f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
19f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu/* Return the median of the n values in "values".
20f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu   Uses a stupid bubble sort, but is only called once on small array. */
21f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsufloat getMedian(float* values, int n) {
22f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (n <= 0)
23f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        return 0.0;
24f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (n == 1)
25f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        return values[0];
26f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (n == 2)
27f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        return 0.5 * (values[0] + values[1]);
28f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    for (int i = 1; i < n; ++i)
29f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        for (int j = i; j < n; ++j) {
30f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (values[j] < values[i-1]) {
31f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                float tmp = values[i-1];
32f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                values[i-1] = values[j];
33f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                values[j] = tmp;
34f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
35f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
36f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int ind = int(0.5 + (0.5 * n)) - 1;
37f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    return values[ind];
38f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu}
39f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
40f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsufloat computeAndRemoveMean(short* pcm, int numSamples) {
41f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float sum = 0.0;
42f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
43f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    for (int i = 0; i < numSamples; ++i)
44f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        sum += pcm[i];
45f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    short mean;
46f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (sum >= 0.0)
47f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        mean = (short)(0.5 + (sum / numSamples));
48f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    else
49f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        mean = (short)((sum / numSamples) - 0.5);
50f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    for (int i = 0; i < numSamples; ++i)
51f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        pcm[i] -= mean;
52f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    return sum / numSamples;
53f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu}
54f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
55f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsuvoid measureRms(short* pcm, int numSamples, float sampleRate, float onsetThresh,
56f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                float* rms, float* stdRms, float* mean, float* duration) {
57f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    *rms = 0.0;
58f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    *stdRms = 0.0;
59f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    *duration = 0.0;
60f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float frameDur = 0.025;    // Both the duration and interval of the
61f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                                // analysis frames.
62f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float calInterval = 0.250; // initial part of signal used to
63f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                                // establish background level (seconds).
64f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    double sumFrameRms = 1.0;
65f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float sumSampleSquares = 0.0;
66f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    double sumFrameSquares = 1.0; // init. to small number to avoid
67f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                                    // log and divz problems.
68f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int frameSize = (int)(0.5 + (sampleRate * frameDur));
69f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int numFrames = numSamples / frameSize;
70f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int numCalFrames = int(0.5 + (calInterval / frameDur));
71f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (numCalFrames < 1)
72f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        numCalFrames = 1;
73f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int frame = 0;
74f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
75f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    *mean = computeAndRemoveMean(pcm, numSamples);
76f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
77f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (onsetThresh < 0.0) { // Handle the case where we want to
78f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                              // simply measure the RMS of the entire
79f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                              // input sequence.
80f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        for (frame = 0; frame < numFrames; ++frame) {
81f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            short* p_data = pcm + (frame * frameSize);
82f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            int i;
83f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            for (i = 0, sumSampleSquares = 0.0; i < frameSize; ++i) {
84f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                float samp = p_data[i];
85f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                sumSampleSquares += samp * samp;
86f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
87f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumSampleSquares /= frameSize;
88f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumFrameSquares += sumSampleSquares;
89f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            double localRms = sqrt(sumSampleSquares);
90f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumFrameRms += localRms;
91f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
92f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        *rms = sumFrameRms / numFrames;
93f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        *stdRms = sqrt((sumFrameSquares / numFrames) - (*rms * *rms));
94f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        *duration = frameSize * numFrames / sampleRate;
95f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        return;
96f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    }
97f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
98f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    /* This handles the case where we look for a target signal against a
99f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu       background, and expect the signal to start some time after the
100f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu       beginning, and to finish some time before the end of the input
101f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu       samples. */
102f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    if (numFrames < (3 * numCalFrames)) {
103f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        return;
104f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    }
105f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float* calValues = new float[numCalFrames];
106f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    float calMedian = 0.0;
107f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int onset = -1;
108f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    int offset = -1;
109f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
110f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    for (frame = 0; frame < numFrames; ++frame) {
111f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        short* p_data = pcm + (frame * frameSize);
112f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        int i;
113f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        for (i = 0, sumSampleSquares = 1.0; i < frameSize; ++i) {
114f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            float samp = p_data[i];
115f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumSampleSquares += samp * samp;
116f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
117f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        sumSampleSquares /= frameSize;
118f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        /* We handle three states: (1) before the onset of the signal; (2)
119f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu           within the signal; (3) following the signal.  The signal is
120f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu           assumed to be at least onsetThresh dB above the background
121f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu           noise, and that at least one frame of silence/background
122f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu           precedes the onset of the signal. */
123f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        if (onset < 0) { // (1)
124f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumFrameSquares += sumSampleSquares;
125f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (frame < numCalFrames ) {
126f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                calValues[frame] = sumSampleSquares;
127f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                continue;
128f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
129f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (frame == numCalFrames) {
130f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                calMedian = getMedian(calValues, numCalFrames);
131f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                if (calMedian < 10.0)
132f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    calMedian = 10.0; // avoid divz, etc.
133f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
134f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            float ratio = 10.0 * log10(sumSampleSquares / calMedian);
135f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (ratio > onsetThresh) {
136f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                onset = frame;
137f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                sumFrameSquares = 1.0;
138f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                sumFrameRms = 1.0;
139f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
140f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            continue;
141f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
142f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        if ((onset > 0) && (offset < 0)) { // (2)
143f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            int sig_frame = frame - onset;
144f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (sig_frame < numCalFrames) {
145f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                calValues[sig_frame] = sumSampleSquares;
146f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            } else {
147f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                if (sig_frame == numCalFrames) {
148f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    calMedian = getMedian(calValues, numCalFrames);
149f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    if (calMedian < 10.0)
150f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                        calMedian = 10.0; // avoid divz, etc.
151f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                }
152f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                float ratio = 10.0 * log10(sumSampleSquares / calMedian);
153f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                int denFrames = frame - onset - 1;
154f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                if (ratio < (-onsetThresh)) { // found signal end
155f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    *rms = sumFrameRms / denFrames;
156f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    *stdRms = sqrt((sumFrameSquares / denFrames) - (*rms * *rms));
157f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    *duration = frameSize * (frame - onset) / sampleRate;
158f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    offset = frame;
159f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                    continue;
160f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                }
161f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            }
162f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumFrameSquares += sumSampleSquares;
163f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            double localRms = sqrt(sumSampleSquares);
164f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            sumFrameRms += localRms;
165f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            continue;
166f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
167f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        if (offset > 0) { // (3)
168f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            /* If we have found the real signal end, the level should stay
169f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu               low till data end.  If not, flag this anomaly by increasing the
170f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu               reported duration. */
171f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            float localRms = 1.0 + sqrt(sumSampleSquares);
172f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            float localSnr = 20.0 * log10(*rms / localRms);
173f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            if (localSnr < onsetThresh)
174f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu                *duration = frameSize * (frame - onset) / sampleRate;
175f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu            continue;
176f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu        }
177f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    }
178f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu    delete [] calValues;
179f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu}
180f894620337d47a1c8f04740e1a2fe55f24a503bdBrian Muramatsu
181