alsabat: add noise detection

Alsabat reports error when noise above threshold be detected.
Use either of the options below to designate the threshold. (e.g.
if the ratio of noise to signal is 5%, the snr is about 26dB.)
    --snr-db <value in dB>
    --snr-pc <value in %>

The noise detection is performed in time domain. On each period
of the sine wave being analyzed, alsabat substracts a clean sine
wave from the source, calculates the RMS value of the residual,
and compares the result with the threshold. At last, alsabat
returns the number of periods with noise above threshold. 0 is
returned when the source is clean.

Signed-off-by: Lu, Han <han.lu@intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Lu, Han 2016-06-09 03:42:49 +08:00 committed by Takashi Iwai
parent e1b7a5f376
commit 0cfe406618
5 changed files with 262 additions and 2 deletions

View file

@ -83,6 +83,10 @@ feature_list_test () {
"local mode: analyze local file"
feature_test "--roundtriplatency" \
"round trip latency test"
feature_test "--snr-db 26" \
"noise detect threshold in SNR(dB)"
feature_test "--snr-pc 5" \
"noise detect threshold in noise percentage(%)"
print_result
}

View file

@ -138,6 +138,14 @@ Round trip latency test.
Audio latency is the time delay as an audio signal passes through a system.
There are many kinds of audio latency metrics. One useful metric is the
round trip latency, which is the sum of output latency and input latency.
.TP
\fI\-\-snr\-db=#\fP
Noise detection threshold in SNR (dB). 26dB indicates 5% noise in amplitude.
ALSABAT will return error if signal SNR is smaller than the threshold.
.TP
\fI\-\-snr\-pc=#\fP
Noise detection threshold in percentage of noise amplitude (%).
ALSABAT will return error if the noise amplitude is larger than the threshold.
.SH EXAMPLES

View file

@ -226,6 +226,169 @@ out1:
return err;
}
static int calculate_noise_one_period(struct bat *bat,
struct noise_analyzer *na, float *src,
int length, int channel)
{
int i, shift = 0;
float tmp, rms, gain, residual;
float a = 0.0, b = 1.0;
/* step 1. phase compensation */
if (length < 2 * na->nsamples)
return -EINVAL;
/* search for the beginning of a sine period */
for (i = 0, tmp = 0.0, shift = -1; i < na->nsamples; i++) {
/* find i where src[i] >= 0 && src[i+1] < 0 */
if (src[i] < 0.0)
continue;
if (src[i + 1] < 0.0) {
tmp = src[i] - src[i + 1];
a = src[i] / tmp;
b = -src[i + 1] / tmp;
shift = i;
break;
}
}
/* didn't find the beginning of a sine period */
if (shift == -1)
return -EINVAL;
/* shift sine waveform to source[0] = 0.0 */
for (i = 0; i < na->nsamples; i++)
na->source[i] = a * src[i + shift + 1] + b * src[i + shift];
/* step 2. gain compensation */
/* calculate rms of signal amplitude */
for (i = 0, tmp = 0.0; i < na->nsamples; i++)
tmp += na->source[i] * na->source[i];
rms = sqrtf(tmp / na->nsamples);
gain = na->rms_tgt / rms;
for (i = 0; i < na->nsamples; i++)
na->source[i] *= gain;
/* step 3. calculate snr in dB */
for (i = 0, tmp = 0.0, residual = 0.0; i < na->nsamples; i++) {
tmp = fabsf(na->target[i] - na->source[i]);
residual += tmp * tmp;
}
tmp = na->rms_tgt / sqrtf(residual / na->nsamples);
na->snr_db = 20.0 * log10f(tmp);
return 0;
}
static int calculate_noise(struct bat *bat, float *src, int channel)
{
int err = 0;
struct noise_analyzer na;
float freq = bat->target_freq[channel];
float tmp, sum_snr_pc, avg_snr_pc, avg_snr_db;
int offset, i, cnt_noise, cnt_clean;
/* num of samples in each sine period */
int nsamples = (int) ceilf(bat->rate / freq);
/* each section has 2 sine periods, the first one for locating
* and the second one for noise calculating */
int nsamples_per_section = nsamples * 2;
/* all sine periods will be calculated except the first one */
int nsection = bat->frames / nsamples - 1;
fprintf(bat->log, _("samples per period: %d\n"), nsamples);
fprintf(bat->log, _("total sections to detect: %d\n"), nsection);
na.source = (float *)malloc(sizeof(float) * nsamples);
if (!na.source) {
err = -ENOMEM;
goto out1;
}
na.target = (float *)malloc(sizeof(float) * nsamples);
if (!na.target) {
err = -ENOMEM;
goto out2;
}
/* generate standard single-tone signal */
err = generate_sine_wave_raw_mono(bat, na.target, freq, nsamples);
if (err < 0)
goto out3;
na.nsamples = nsamples;
/* calculate rms of standard signal */
for (i = 0, tmp = 0.0; i < nsamples; i++)
tmp += na.target[i] * na.target[i];
na.rms_tgt = sqrtf(tmp / nsamples);
/* calculate average noise level */
sum_snr_pc = 0.0;
cnt_clean = cnt_noise = 0;
for (i = 0, offset = 0; i < nsection; i++) {
na.snr_db = SNR_DB_INVALID;
err = calculate_noise_one_period(bat, &na, src + offset,
nsamples_per_section, channel);
if (err < 0)
goto out3;
if (na.snr_db > bat->snr_thd_db) {
cnt_clean++;
sum_snr_pc += 100.0 / powf(10.0, na.snr_db / 20.0);
} else {
cnt_noise++;
}
offset += nsamples;
}
if (cnt_noise > 0) {
fprintf(bat->err, _("Noise detected at %d points.\n"),
cnt_noise);
err = -cnt_noise;
if (cnt_clean == 0)
goto out3;
} else {
fprintf(bat->log, _("No noise detected.\n"));
}
avg_snr_pc = sum_snr_pc / cnt_clean;
avg_snr_db = 20.0 * log10f(100.0 / avg_snr_pc);
fprintf(bat->log, _("Average SNR is %.2f dB (%.2f %%) at %d points.\n"),
avg_snr_db, avg_snr_pc, cnt_clean);
out3:
free(na.target);
out2:
free(na.source);
out1:
return err;
}
static int find_and_check_noise(struct bat *bat, void *buf, int channel)
{
int err = 0;
float *source;
source = (float *)malloc(sizeof(float) * bat->frames);
if (!source)
return -ENOMEM;
/* convert source PCM to floats */
bat->convert_sample_to_float(buf, source, bat->frames);
/* adjust waveform and calculate noise */
err = calculate_noise(bat, source, channel);
free(source);
return err;
}
/**
* Convert interleaved samples from channels in samples from a single channel
*/
@ -324,7 +487,21 @@ int analyze_capture(struct bat *bat)
a.buf = bat->buf +
c * bat->frames * bat->frame_size
/ bat->channels;
if (!bat->standalone) {
err = find_and_check_harmonics(bat, &a, c);
if (err != 0)
goto exit2;
}
if (snr_is_valid(bat->snr_thd_db)) {
fprintf(bat->log, _("\nChecking for SNR: "));
fprintf(bat->log, _("Threshold is %.2f dB (%.2f%%)\n"),
bat->snr_thd_db, 100.0
/ powf(10.0, bat->snr_thd_db / 20.0));
err = find_and_check_noise(bat, a.buf, c);
if (err != 0)
goto exit2;
}
}
exit2:

View file

@ -24,6 +24,7 @@
#include <math.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include "aconfig.h"
#include "gettext.h"
@ -42,6 +43,38 @@
#endif
#include "latencytest.h"
/* get snr threshold in dB */
static void get_snr_thd_db(struct bat *bat, char *thd)
{
int err;
float thd_db;
char *ptrf;
thd_db = strtof(thd, &ptrf);
err = -errno;
if (!snr_is_valid(thd_db)) {
fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err);
exit(EXIT_FAILURE);
}
bat->snr_thd_db = thd_db;
}
/* get snr threshold in %, and convert to dB */
static void get_snr_thd_pc(struct bat *bat, char *thd)
{
int err;
float thd_pc;
char *ptrf;
thd_pc = strtof(thd, &ptrf);
err = -errno;
if (thd_pc <= 0.0 || thd_pc >= 100.0) {
fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err);
exit(EXIT_FAILURE);
}
bat->snr_thd_db = 20.0 * log10f(100.0 / thd_pc);
}
static int get_duration(struct bat *bat)
{
int err;
@ -301,6 +334,8 @@ _("Usage: alsabat [-options]...\n"
" --local internal loop, set to bypass pcm hardware devices\n"
" --standalone standalone mode, to bypass analysis\n"
" --roundtriplatency round trip latency mode\n"
" --snr-db=# noise detect threshold, in SNR(dB)\n"
" --snr-pc=# noise detect threshold, in noise percentage(%%)\n"
));
fprintf(bat->log, _("Recognized sample formats are: "));
fprintf(bat->log, _("U8 S16_LE S24_3LE S32_LE\n"));
@ -324,6 +359,7 @@ static void set_defaults(struct bat *bat)
bat->target_freq[0] = 997.0;
bat->target_freq[1] = 997.0;
bat->sigma_k = 3.0;
bat->snr_thd_db = SNR_DB_INVALID;
bat->playback.device = NULL;
bat->capture.device = NULL;
bat->buf = NULL;
@ -359,6 +395,8 @@ static void parse_arguments(struct bat *bat, int argc, char *argv[])
{"local", 0, 0, OPT_LOCAL},
{"standalone", 0, 0, OPT_STANDALONE},
{"roundtriplatency", 0, 0, OPT_ROUNDTRIPLATENCY},
{"snr-db", 1, 0, OPT_SNRTHD_DB},
{"snr-pc", 1, 0, OPT_SNRTHD_PC},
{0, 0, 0, 0}
};
@ -382,6 +420,11 @@ static void parse_arguments(struct bat *bat, int argc, char *argv[])
break;
case OPT_ROUNDTRIPLATENCY:
bat->roundtriplatency = true;
case OPT_SNRTHD_DB:
get_snr_thd_db(bat, optarg);
break;
case OPT_SNRTHD_PC:
get_snr_thd_pc(bat, optarg);
break;
case 'D':
if (bat->playback.device == NULL)
@ -670,7 +713,7 @@ int main(int argc, char *argv[])
analyze:
#ifdef HAVE_LIBFFTW3F
if (!bat.standalone)
if (!bat.standalone || snr_is_valid(bat.snr_thd_db))
err = analyze_capture(&bat);
#else
fprintf(bat.log, _("No libfftw3 library. Exit without analysis.\n"));

View file

@ -23,6 +23,8 @@
#define OPT_LOCAL (OPT_BASE + 4)
#define OPT_STANDALONE (OPT_BASE + 5)
#define OPT_ROUNDTRIPLATENCY (OPT_BASE + 6)
#define OPT_SNRTHD_DB (OPT_BASE + 7)
#define OPT_SNRTHD_PC (OPT_BASE + 8)
#define COMPOSE(a, b, c, d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
#define WAV_RIFF COMPOSE('R', 'I', 'F', 'F')
@ -80,6 +82,23 @@
#define SHIFT_MAX (sizeof(int) * 8 - 2)
#define SHIFT_MIN 8
/* Define SNR range in dB.
* if the noise is equal to signal, SNR = 0.0dB;
* if the noise is zero, SNR is limited by RIFF wav data width:
* 8 bit --> 20.0 * log10f (powf(2.0, 8.0)) = 48.16 dB
* 16 bit --> 20.0 * log10f (powf(2.0, 16.0)) = 96.33 dB
* 24 bit --> 20.0 * log10f (powf(2.0, 24.0)) = 144.49 dB
* 32 bit --> 20.0 * log10f (powf(2.0, 32.0)) = 192.66 dB
* so define the SNR range (0.0, 200.0) dB, value out of range is invalid. */
#define SNR_DB_INVALID -1.0
#define SNR_DB_MIN 0.0
#define SNR_DB_MAX 200.0
inline bool snr_is_valid(float db)
{
return (db > SNR_DB_MIN && db < SNR_DB_MAX);
}
struct wav_header {
unsigned int magic; /* 'RIFF' */
unsigned int length; /* file len */
@ -178,6 +197,14 @@ struct roundtrip_latency {
bool xrun_error;
};
struct noise_analyzer {
int nsamples; /* number of sample */
float *source; /* single-tone to be analyzed */
float *target; /* target single-tone as standard */
float rms_tgt; /* rms of target single-tone */
float snr_db; /* snr in dB */
};
struct bat {
unsigned int rate; /* sampling rate */
int channels; /* nb of channels */
@ -189,6 +216,7 @@ struct bat {
int period_size; /* period size in frames */
float sigma_k; /* threshold for peak detection */
float snr_thd_db; /* threshold for noise detection (dB) */
float target_freq[MAX_CHANNELS];
int sinus_duration; /* number of frames for playback */