forked from sox_ng/sox_ng
396 lines
14 KiB
C
396 lines
14 KiB
C
/* libSoX effect: Voice Activity Detector (c) 2009 robs@users.sourceforge.net
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "sox_i.h"
|
|
|
|
typedef struct {
|
|
double * dftBuf, * noiseSpectrum, * spectrum, * measures, meanMeas;
|
|
} vad_channel_t;
|
|
|
|
typedef struct { /* Configuration parameters: */
|
|
double bootTime, noiseTcUp, noiseTcDown, noiseReductionAmount;
|
|
double measureFreq, measureDuration, measureTc, preTriggerTime;
|
|
double hpFilterFreq, lpFilterFreq, hpLifterFreq, lpLifterFreq;
|
|
double triggerTc, triggerLevel, searchTime, gapTime;
|
|
/* Working variables: */
|
|
sox_sample_t * samples;
|
|
unsigned dftLen_ws, samplesLen_ns, samplesIndex_ns, flushedLen_ns, gapLen;
|
|
unsigned measurePeriod_ns, measuresLen, measuresIndex;
|
|
unsigned measureTimer_ns, measureLen_ws, measureLen_ns;
|
|
unsigned spectrumStart, spectrumEnd, cepstrumStart, cepstrumEnd; /* bins */
|
|
int bootCountMax, bootCount;
|
|
double noiseTcUpMult, noiseTcDownMult;
|
|
double measureTcMult, triggerMeasTcMult;
|
|
double * spectrumWindow, * cepstrumWindow;
|
|
vad_channel_t * channels;
|
|
} priv_t;
|
|
|
|
#define GETOPT_FREQ(optstate, c, name, min) \
|
|
case c: p->name = lsx_parse_frequency(optstate.arg, &parseIndex); \
|
|
if (p->name < min || *parseIndex) { \
|
|
lsx_fail("cannot parse frequency `%s'", optstate.arg); \
|
|
return SOX_EOF; \
|
|
} \
|
|
break;
|
|
|
|
static int create_vad(sox_effect_t * effp, int argc, char * * argv)
|
|
{
|
|
priv_t * p = (priv_t *)effp->priv;
|
|
#define opt_str "+b:N:n:r:f:m:M:h:l:H:L:T:t:s:g:p:"
|
|
int c;
|
|
lsx_getopt_t optstate;
|
|
lsx_getopt_init(argc, argv, opt_str, NULL, lsx_getopt_flag_none, 1, &optstate);
|
|
|
|
p->bootTime = .35;
|
|
p->noiseTcUp = .1;
|
|
p->noiseTcDown = .01;
|
|
p->noiseReductionAmount = 1.35;
|
|
|
|
p->measureFreq = 20;
|
|
p->measureDuration = 2 / p->measureFreq; /* 50% overlap */
|
|
p->measureTc = .4;
|
|
|
|
p->hpFilterFreq = 50;
|
|
p->lpFilterFreq = 6000;
|
|
p->hpLifterFreq = 150;
|
|
p->lpLifterFreq = 2000;
|
|
|
|
p->triggerTc = .25;
|
|
p->triggerLevel = 7;
|
|
|
|
p->searchTime = 1;
|
|
p->gapTime = .25;
|
|
|
|
while ((c = lsx_getopt(&optstate)) != -1) switch (c) {
|
|
char * parseIndex;
|
|
GETOPT_NUMERIC(optstate, 'b', bootTime , .1 , 10)
|
|
GETOPT_NUMERIC(optstate, 'N', noiseTcUp , .1 , 10)
|
|
GETOPT_NUMERIC(optstate, 'n', noiseTcDown ,.001 , .1)
|
|
GETOPT_NUMERIC(optstate, 'r', noiseReductionAmount,0 , 2)
|
|
GETOPT_NUMERIC(optstate, 'f', measureFreq , 5 , 50)
|
|
GETOPT_NUMERIC(optstate, 'm', measureDuration, .01 , 1)
|
|
GETOPT_NUMERIC(optstate, 'M', measureTc , .1 , 1)
|
|
GETOPT_FREQ( optstate, 'h', hpFilterFreq , 10)
|
|
GETOPT_FREQ( optstate, 'l', lpFilterFreq , 1000)
|
|
GETOPT_FREQ( optstate, 'H', hpLifterFreq , 10)
|
|
GETOPT_FREQ( optstate, 'L', lpLifterFreq , 1000)
|
|
GETOPT_NUMERIC(optstate, 'T', triggerTc , .01 , 1)
|
|
GETOPT_NUMERIC(optstate, 't', triggerLevel , 0 , 20)
|
|
GETOPT_NUMERIC(optstate, 's', searchTime , .1 , 4)
|
|
GETOPT_NUMERIC(optstate, 'g', gapTime , .1 , 1)
|
|
GETOPT_NUMERIC(optstate, 'p', preTriggerTime, 0 , 4)
|
|
default: lsx_fail("invalid option `-%c'", optstate.opt); return lsx_usage(effp);
|
|
}
|
|
return optstate.ind !=argc? lsx_usage(effp) : SOX_SUCCESS;
|
|
}
|
|
|
|
static char *
|
|
get_vad(sox_effect_t *effp, char *name)
|
|
{
|
|
priv_t *p = (priv_t *)effp->priv;
|
|
char *s = NULL;
|
|
|
|
if (!strcmp(name, "trigger_level")) {
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", p->triggerLevel);
|
|
}
|
|
if (!strcmp(name, "trigger_time")) {
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", p->triggerTc);
|
|
}
|
|
if (!strcmp(name, "gap")) {
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", p->gapTime);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static char *
|
|
set_vad(sox_effect_t *effp, char *name, char *value)
|
|
{
|
|
priv_t *p = (priv_t *)effp->priv;
|
|
char *s = NULL;
|
|
char *endptr = value;
|
|
double v = lsx_strtod(value, &endptr);
|
|
|
|
if (endptr == value || *endptr != '\0') return NULL;
|
|
|
|
if (!strcmp(name, "trigger_level")) {
|
|
if (v < 0) v = 0;
|
|
if (v > 20) v = 20;
|
|
p->triggerLevel = v;
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", v);
|
|
}
|
|
if (!strcmp(name, "trigger_time")) {
|
|
if (v < 0.01) v = 0.01;
|
|
if (v > 1.0) v = 1.0;
|
|
p->triggerTc = v;
|
|
p->triggerMeasTcMult = exp(-1 / (p->triggerTc * p->measureFreq));
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", v);
|
|
}
|
|
if (!strcmp(name, "gap")) {
|
|
if (v < 0.1) v = 0.1;
|
|
if (v > 1.0) v = 1.0;
|
|
p->gapTime = v;
|
|
p->gapLen = p->gapTime * p->measureFreq + .5;
|
|
s = lsx_malloc(16);
|
|
sprintf(s, "%g", v);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static int start_vad(sox_effect_t * effp)
|
|
{
|
|
priv_t * p = (priv_t *)effp->priv;
|
|
unsigned i, fixedPreTriggerLen_ns, searchPreTriggerLen_ns;
|
|
|
|
fixedPreTriggerLen_ns = p->preTriggerTime * effp->in_signal.rate + .5;
|
|
fixedPreTriggerLen_ns *= effp->in_signal.channels;
|
|
|
|
p->measureLen_ws = effp->in_signal.rate * p->measureDuration + .5;
|
|
p->measureLen_ns = p->measureLen_ws * effp->in_signal.channels;
|
|
for (p->dftLen_ws = 16; p->dftLen_ws < p->measureLen_ws; p->dftLen_ws <<= 1);
|
|
lsx_debug("dftLen_ws=%u measureLen_ws=%u", p->dftLen_ws, p->measureLen_ws);
|
|
|
|
p->measurePeriod_ns = effp->in_signal.rate / p->measureFreq + .5;
|
|
p->measurePeriod_ns *= effp->in_signal.channels;
|
|
p->measuresLen = ceil(p->searchTime * p->measureFreq);
|
|
searchPreTriggerLen_ns = p->measuresLen * p->measurePeriod_ns;
|
|
p->gapLen = p->gapTime * p->measureFreq + .5;
|
|
|
|
p->samplesLen_ns =
|
|
fixedPreTriggerLen_ns + searchPreTriggerLen_ns + p->measureLen_ns;
|
|
lsx_vcalloc(p->samples, p->samplesLen_ns);
|
|
|
|
lsx_vcalloc(p->channels, effp->in_signal.channels);
|
|
for (i = 0; i < effp->in_signal.channels; ++i) {
|
|
vad_channel_t * c = &p->channels[i];
|
|
lsx_vcalloc(c->dftBuf, p->dftLen_ws);
|
|
lsx_vcalloc(c->spectrum, p->dftLen_ws);
|
|
lsx_vcalloc(c->noiseSpectrum, p->dftLen_ws);
|
|
lsx_vcalloc(c->measures, p->measuresLen);
|
|
}
|
|
|
|
lsx_vcalloc(p->spectrumWindow, p->measureLen_ws);
|
|
for (i = 0; i < p->measureLen_ws; ++i)
|
|
p->spectrumWindow[i] = -2./ SOX_SAMPLE_MIN / sqrt((double)p->measureLen_ws);
|
|
lsx_apply_hann(p->spectrumWindow, (int)p->measureLen_ws);
|
|
|
|
p->spectrumStart = p->hpFilterFreq / effp->in_signal.rate * p->dftLen_ws + .5;
|
|
p->spectrumStart = max(p->spectrumStart, 1);
|
|
p->spectrumEnd = p->lpFilterFreq / effp->in_signal.rate * p->dftLen_ws + .5;
|
|
p->spectrumEnd = min(p->spectrumEnd, p->dftLen_ws / 2);
|
|
|
|
lsx_vcalloc(p->cepstrumWindow, p->spectrumEnd - p->spectrumStart);
|
|
for (i = 0; i < p->spectrumEnd - p->spectrumStart; ++i)
|
|
p->cepstrumWindow[i] = 2 / sqrt((double)p->spectrumEnd - p->spectrumStart);
|
|
lsx_apply_hann(p->cepstrumWindow,(int)(p->spectrumEnd - p->spectrumStart));
|
|
|
|
p->cepstrumStart = ceil(effp->in_signal.rate * .5 / p->lpLifterFreq);
|
|
p->cepstrumEnd = floor(effp->in_signal.rate * .5 / p->hpLifterFreq);
|
|
p->cepstrumEnd = min(p->cepstrumEnd, p->dftLen_ws / 4);
|
|
if (p->cepstrumEnd <= p->cepstrumStart)
|
|
return SOX_EOF;
|
|
|
|
p->noiseTcUpMult = exp(-1 / (p->noiseTcUp * p->measureFreq));
|
|
p->noiseTcDownMult = exp(-1 / (p->noiseTcDown * p->measureFreq));
|
|
p->measureTcMult = exp(-1 / (p->measureTc * p->measureFreq));
|
|
p->triggerMeasTcMult = exp(-1 / (p->triggerTc * p->measureFreq));
|
|
|
|
p->bootCountMax = p->bootTime * p->measureFreq - .5;
|
|
p->measureTimer_ns = p->measureLen_ns;
|
|
p->bootCount = p->measuresIndex = p->flushedLen_ns = p->samplesIndex_ns = 0;
|
|
|
|
effp->out_signal.length = SOX_UNKNOWN_LEN; /* depends on input data */
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
static int flowFlush(sox_effect_t * effp, sox_sample_t const * ibuf,
|
|
sox_sample_t * obuf, size_t * ilen, size_t * olen)
|
|
{
|
|
priv_t * p = (priv_t *)effp->priv;
|
|
size_t odone = min(p->samplesLen_ns - p->flushedLen_ns, *olen);
|
|
size_t odone1 = min(odone, p->samplesLen_ns - p->samplesIndex_ns);
|
|
|
|
memcpy(obuf, p->samples + p->samplesIndex_ns, odone1 * sizeof(*obuf));
|
|
if ((p->samplesIndex_ns += odone1) == p->samplesLen_ns) {
|
|
memcpy(obuf + odone1, p->samples, (odone - odone1) * sizeof(*obuf));
|
|
p->samplesIndex_ns = odone - odone1;
|
|
}
|
|
if ((p->flushedLen_ns += odone) == p->samplesLen_ns) {
|
|
size_t olen1 = *olen - odone;
|
|
(effp->handler.flow = lsx_flow_copy)(effp, ibuf, obuf +odone, ilen, &olen1);
|
|
odone += olen1;
|
|
}
|
|
else *ilen = 0;
|
|
*olen = odone;
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
static double measure(
|
|
priv_t * p, vad_channel_t * c, size_t index_ns, unsigned step_ns, int bootCount)
|
|
{
|
|
double mult, result = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < p->measureLen_ws; ++i, index_ns = (index_ns + step_ns) % p->samplesLen_ns)
|
|
c->dftBuf[i] = p->samples[index_ns] * p->spectrumWindow[i];
|
|
memset(c->dftBuf + i, 0, (p->dftLen_ws - i) * sizeof(*c->dftBuf));
|
|
lsx_safe_rdft((int)p->dftLen_ws, 1, c->dftBuf);
|
|
|
|
memset(c->dftBuf, 0, p->spectrumStart * sizeof(*c->dftBuf));
|
|
for (i = p->spectrumStart; i < p->spectrumEnd; ++i) {
|
|
double d = sqrt(sqr(c->dftBuf[2 * i]) + sqr(c->dftBuf[2 * i + 1]));
|
|
mult = bootCount >= 0? bootCount / (1. + bootCount) : p->measureTcMult;
|
|
c->spectrum[i] = c->spectrum[i] * mult + d * (1 - mult);
|
|
d = sqr(c->spectrum[i]);
|
|
mult = bootCount >= 0? 0 :
|
|
d > c->noiseSpectrum[i]? p->noiseTcUpMult : p->noiseTcDownMult;
|
|
c->noiseSpectrum[i] = c->noiseSpectrum[i] * mult + d * (1 - mult);
|
|
d = sqrt(max(0, d - p->noiseReductionAmount * c->noiseSpectrum[i]));
|
|
c->dftBuf[i] = d * p->cepstrumWindow[i - p->spectrumStart];
|
|
}
|
|
memset(c->dftBuf + i, 0, ((p->dftLen_ws >> 1) - i) * sizeof(*c->dftBuf));
|
|
lsx_safe_rdft((int)p->dftLen_ws >> 1, 1, c->dftBuf);
|
|
|
|
for (i = p->cepstrumStart; i < p->cepstrumEnd; ++i)
|
|
result += sqr(c->dftBuf[2 * i]) + sqr(c->dftBuf[2 * i + 1]);
|
|
result = log(result / (p->cepstrumEnd - p->cepstrumStart));
|
|
return max(0, 21 + result);
|
|
}
|
|
|
|
static int flow_vad(sox_effect_t * effp, sox_sample_t const * ibuf,
|
|
sox_sample_t * obuf, size_t * ilen, size_t * olen)
|
|
{
|
|
priv_t * p = (priv_t *)effp->priv;
|
|
sox_bool hasTriggered = sox_false;
|
|
size_t i, idone = 0, numMeasuresToFlush = 0;
|
|
|
|
while (idone < *ilen && !hasTriggered) {
|
|
p->measureTimer_ns -= effp->in_signal.channels;
|
|
for (i = 0; i < effp->in_signal.channels; ++i, ++idone) {
|
|
vad_channel_t * c = &p->channels[i];
|
|
p->samples[p->samplesIndex_ns++] = *ibuf++;
|
|
if (!p->measureTimer_ns) {
|
|
size_t x = (p->samplesIndex_ns + p->samplesLen_ns - p->measureLen_ns) % p->samplesLen_ns;
|
|
double meas = measure(p, c, x, effp->in_signal.channels, p->bootCount);
|
|
c->measures[p->measuresIndex] = meas;
|
|
c->meanMeas = c->meanMeas * p->triggerMeasTcMult +
|
|
meas *(1 - p->triggerMeasTcMult);
|
|
|
|
hasTriggered |= c->meanMeas >= p->triggerLevel;
|
|
if (hasTriggered >= p->triggerLevel) {
|
|
unsigned n = p->measuresLen, k = p->measuresIndex;
|
|
unsigned j, jTrigger = n, jZero = n;
|
|
for (j = 0; j < n; ++j, k = (k + n - 1) % n)
|
|
if (c->measures[k] >= p->triggerLevel && j <= jTrigger + p->gapLen)
|
|
jZero = jTrigger = j;
|
|
else if (!c->measures[k] && jTrigger >= jZero)
|
|
jZero = j;
|
|
j = min(j, jZero);
|
|
numMeasuresToFlush = range_limit(j, numMeasuresToFlush, n);
|
|
}
|
|
lsx_debug_more("%12g %12g %u",
|
|
meas, c->meanMeas, (unsigned)numMeasuresToFlush);
|
|
}
|
|
}
|
|
if (p->samplesIndex_ns == p->samplesLen_ns)
|
|
p->samplesIndex_ns = 0;
|
|
if (!p->measureTimer_ns) {
|
|
p->measureTimer_ns = p->measurePeriod_ns;
|
|
++p->measuresIndex;
|
|
p->measuresIndex %= p->measuresLen;
|
|
if (p->bootCount >= 0)
|
|
p->bootCount = p->bootCount == p->bootCountMax? -1 : p->bootCount + 1;
|
|
}
|
|
}
|
|
if (hasTriggered) {
|
|
size_t ilen1 = *ilen - idone;
|
|
p->flushedLen_ns = (p->measuresLen - numMeasuresToFlush) * p->measurePeriod_ns;
|
|
p->samplesIndex_ns = (p->samplesIndex_ns + p->flushedLen_ns) % p->samplesLen_ns;
|
|
(effp->handler.flow = flowFlush)(effp, ibuf, obuf, &ilen1, olen);
|
|
idone += ilen1;
|
|
}
|
|
else *olen = 0;
|
|
*ilen = idone;
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
static int drain_vad(sox_effect_t * effp, sox_sample_t * obuf, size_t * olen)
|
|
{
|
|
size_t ilen = 0;
|
|
return effp->handler.flow(effp, NULL, obuf, &ilen, olen);
|
|
}
|
|
|
|
static int stop_vad(sox_effect_t * effp)
|
|
{
|
|
priv_t * p = (priv_t *)effp->priv;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < effp->in_signal.channels; ++i) {
|
|
vad_channel_t * c = &p->channels[i];
|
|
free(c->measures);
|
|
free(c->noiseSpectrum);
|
|
free(c->spectrum);
|
|
free(c->dftBuf);
|
|
}
|
|
free(p->channels);
|
|
free(p->cepstrumWindow);
|
|
free(p->spectrumWindow);
|
|
free(p->samples);
|
|
return SOX_SUCCESS;
|
|
}
|
|
|
|
sox_effect_handler_t const * lsx_vad_effect_fn(void)
|
|
{
|
|
static char const usage[] = "[options]";
|
|
|
|
static char const * const extra_usage[] = {
|
|
"FLAG RANGE DEFAULT DESCRIPTION",
|
|
"-t 0.1-20 7 Trigger level",
|
|
"-T 0.01-1 0.25 Trigger time constant",
|
|
"-s 0.1-4 1 Search time",
|
|
"-g 0.1-1 0.25 Allowed gap",
|
|
"-p 0-4 0 Pre-trigger time",
|
|
"Advanced options:",
|
|
"-b 0.1-10 0.35 Noise estimate boot time",
|
|
"-N 0.1-10 0.1 Noise estimate time constant up",
|
|
"-n 0.001-0.1 0.01 Noise estimate time constant down",
|
|
"-r 0-2 1.35 Noise reduction amount",
|
|
"-f 5-50 20 Measurement frequency",
|
|
"-m 0.01-1 0.1 Measurement duration",
|
|
"-M 0.1-1 0.4 Measurement time constant",
|
|
"-h 10- 50 High-pass filter frequency",
|
|
"-l 1000- 6000 Low-pass filter frequency",
|
|
"-H 10- 150 High-pass lifter frequency",
|
|
"-L 1000- 2000 Low-pass lifter frequency",
|
|
"Keymaps: vad.(trigger_level|trigger_time|gap)",
|
|
NULL
|
|
};
|
|
|
|
static sox_effect_handler_t handler = {
|
|
"vad", usage, SOX_EFF_MCHAN | SOX_EFF_LENGTH | SOX_EFF_MODIFY,
|
|
create_vad, start_vad, flow_vad, drain_vad, stop_vad, NULL,
|
|
sizeof(priv_t), extra_usage, get_vad, set_vad,
|
|
};
|
|
|
|
return &handler;
|
|
}
|