]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsabat: add noise detection
authorLu, Han <han.lu@intel.com>
Wed, 8 Jun 2016 19:42:49 +0000 (03:42 +0800)
committerTakashi Iwai <tiwai@suse.de>
Wed, 8 Jun 2016 13:17:14 +0000 (15:17 +0200)
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>
bat/alsabat-test.sh
bat/alsabat.1
bat/analyze.c
bat/bat.c
bat/common.h

index 24caa0bde2f348bdbbe0149ff72f1c62888534e8..aa0281b07d7bbb5c07032272eaaadb74d2e24337 100755 (executable)
@@ -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
 }
index 231b15451988eb6d9f01072c8687ca844415fbdd..d4a33860bb4a6a4e9f7509313f3498cbb0997e5b 100644 (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
 
index bdb1f83fafb7cbdbcf7fa63eb37d436068fa7a31..17fff50b9148f277c50e9ee8defed0c6b7e60899 100644 (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;
-               err = find_and_check_harmonics(bat, &a, c);
+               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:
index 4bbd81b13b33e6484921b153ca6b812b5c827fb7..72ab8b29d9ea9d2c0afd567441477a81ea4aabd1 100644 (file)
--- a/bat/bat.c
+++ b/bat/bat.c
@@ -24,6 +24,7 @@
 #include <math.h>
 #include <limits.h>
 #include <locale.h>
+#include <math.h>
 
 #include "aconfig.h"
 #include "gettext.h"
 #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"));
index e60bd9039dacd82a98f695c1909454e66471f92e..db49868a419a1335f2e754327083b5e667c92a14 100644 (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')
 #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 */