diff options
-rw-r--r-- | LICENSE.WEBKIT | 27 | ||||
-rw-r--r-- | cras/src/Makefile.am | 42 | ||||
-rw-r--r-- | cras/src/dsp/biquad.c | 368 | ||||
-rw-r--r-- | cras/src/dsp/biquad.h | 57 | ||||
-rw-r--r-- | cras/src/dsp/dsp_util.c | 22 | ||||
-rw-r--r-- | cras/src/dsp/dsp_util.h | 22 | ||||
-rw-r--r-- | cras/src/dsp/eq.c | 152 | ||||
-rw-r--r-- | cras/src/dsp/eq.h | 67 | ||||
-rw-r--r-- | cras/src/dsp/tests/cmpraw.c | 54 | ||||
-rw-r--r-- | cras/src/dsp/tests/dsp_test_util.c | 37 | ||||
-rw-r--r-- | cras/src/dsp/tests/dsp_test_util.h | 27 | ||||
-rw-r--r-- | cras/src/dsp/tests/eq_test.c | 137 | ||||
-rw-r--r-- | cras/src/dsp/tests/plot_fftl.m | 40 | ||||
-rw-r--r-- | cras/src/dsp/tests/raw.c | 93 | ||||
-rw-r--r-- | cras/src/dsp/tests/raw.h | 46 | ||||
-rw-r--r-- | cras/src/server/cras_dsp.c | 2 | ||||
-rw-r--r-- | cras/src/server/cras_dsp_mod_builtin.c | 164 | ||||
-rw-r--r-- | cras/src/tests/dsp_core_unittest.cc | 98 | ||||
-rw-r--r-- | cras/src/tests/dsp_unittest.cc | 48 |
19 files changed, 1463 insertions, 40 deletions
diff --git a/LICENSE.WEBKIT b/LICENSE.WEBKIT new file mode 100644 index 00000000..2f69d9f0 --- /dev/null +++ b/LICENSE.WEBKIT @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/cras/src/Makefile.am b/cras/src/Makefile.am index 28db641c..d1918aaa 100644 --- a/cras/src/Makefile.am +++ b/cras/src/Makefile.am @@ -1,3 +1,7 @@ +# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + AUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} @@ -12,6 +16,9 @@ cras_SOURCES = \ common/cras_util.c \ common/dumper.c \ common/edid_utils.c \ + dsp/biquad.c \ + dsp/dsp_util.c \ + dsp/eq.c \ server/audio_thread.c \ server/config/cras_card_config.c \ server/config/cras_device_blacklist.c \ @@ -56,9 +63,10 @@ cras_SOURCES = \ server/softvol_curve.c cras_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \ - -I$(top_srcdir)/src/server -I$(top_srcdir)/src/server/config \ + -I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \ + -I$(top_srcdir)/src/server/config \ $(DBUS_CFLAGS) $(SBC_CFLAGS) -cras_LDADD = -lpthread -lasound -lrt -liniparser -ludev -ldl \ +cras_LDADD = -lpthread -lasound -lrt -liniparser -ludev -ldl -lm \ $(SBC_LIBS) \ $(DBUS_LIBS) @@ -108,7 +116,7 @@ libasound_module_pcm_cras_la_LIBADD = -lasound libcras.la libasound_module_ctl_cras_la_SOURCES = alsa_plugin/ctl_cras.c libasound_module_ctl_cras_la_LIBADD = -lasound libcras.la -check_PROGRAMS = \ +TESTS = \ a2dp_info_unittest \ a2dp_iodev_unittest \ alert_unittest \ @@ -136,18 +144,33 @@ check_PROGRAMS = \ alsa_ucm_unittest \ edid_utils_unittest \ expr_unittest \ + dsp_core_unittest \ dsp_ini_unittest \ dsp_pipeline_unittest \ dsp_unittest \ util_unittest -TESTS = $(check_PROGRAMS) +check_PROGRAMS = $(TESTS) cras_test_client_SOURCES = tests/cras_test_client.c cras_test_client_LDADD = libcras.la cras_test_client_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/libcras \ -I$(top_srcdir)/src/common +# dsp test programs (not run automatically) +check_PROGRAMS += \ + eq_test \ + cmpraw + +eq_test_SOURCES = dsp/biquad.c dsp/eq.c dsp/dsp_util.c dsp/tests/eq_test.c \ + dsp/tests/dsp_test_util.c dsp/tests/raw.c +eq_test_LDADD = -lrt -lm +eq_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp + +cmpraw_SOURCES = dsp/tests/cmpraw.c dsp/tests/raw.c +cmpraw_LDADD = -lm +cmpraw_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp + # unit tests alert_unittest_SOURCES = tests/alert_unittest.cc \ server/cras_alert.c @@ -242,6 +265,11 @@ device_blacklist_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \ -I$(top_srcdir)/src/server/config device_blacklist_unittest_LDADD = -lgtest -liniparser -lpthread +dsp_core_unittest_SOURCES = tests/dsp_core_unittest.cc dsp/eq.c \ + dsp/biquad.c dsp/dsp_util.c +dsp_core_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp +dsp_core_unittest_LDADD = -lgtest -lpthread + dsp_ini_unittest_SOURCES = tests/dsp_ini_unittest.cc \ server/cras_dsp_ini.c server/cras_expr.c common/dumper.c dsp_ini_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \ @@ -257,10 +285,10 @@ dsp_pipeline_unittest_LDADD = -lgtest -lrt -liniparser -lpthread dsp_unittest_SOURCES = tests/dsp_unittest.cc \ server/cras_dsp.c server/cras_dsp_ini.c server/cras_dsp_pipeline.c \ - server/cras_expr.c ../src/server/cras_dsp_mod_builtin.c \ - common/dumper.c + server/cras_expr.c common/dumper.c dsp/dsp_util.c \ + dsp/tests/dsp_test_util.c dsp_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \ - -I$(top_srcdir)/src/server + -I$(top_srcdir)/src/server -I$(top_srcdir)/src/dsp dsp_unittest_LDADD = -lgtest -lrt -liniparser -lpthread dumper_unittest_SOURCES = tests/dumper_unittest.cc common/dumper.c diff --git a/cras/src/dsp/biquad.c b/cras/src/dsp/biquad.c new file mode 100644 index 00000000..b28256d4 --- /dev/null +++ b/cras/src/dsp/biquad.c @@ -0,0 +1,368 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Copyright (C) 2010 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.WEBKIT file. + */ + +#include <math.h> +#include "biquad.h" + +#ifndef max +#define max(a, b) ({ __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif + +#ifndef min +#define min(a, b) ({ __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, + double a0, double a1, double a2) +{ + double a0_inv = 1 / a0; + bq->b0 = b0 * a0_inv; + bq->b1 = b1 * a0_inv; + bq->b2 = b2 * a0_inv; + bq->a1 = a1 * a0_inv; + bq->a2 = a2 * a0_inv; +} + +static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = max(0.0, min(cutoff, 1.0)); + + if (cutoff == 1) { + /* When cutoff is 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for lowpass filter */ + resonance = max(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * resonance); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta - gamma); + + double b0 = 2 * alpha; + double b1 = 2 * 2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, nothing gets through the filter, so set + * coefficients up correctly. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } +} + +static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = max(0.0, min(cutoff, 1.0)); + + if (cutoff == 1) { + /* The z-transform is 0. */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for highpass filter */ + resonance = max(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * resonance); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta + gamma); + + double b0 = 2 * alpha; + double b1 = 2 * -2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, we need to be careful because the above + * gives a quadratic divided by the same quadratic, with poles + * and zeros on the unit circle in the same place. When cutoff + * is zero, the z-transform is 1. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +static void biquad_bandpass(struct biquad *bq, double frequency, double Q) +{ + /* No negative frequencies allowed. */ + frequency = max(0.0, frequency); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency > 0 && frequency < 1) { + double w0 = M_PI * frequency; + if (Q > 0) { + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = alpha; + double b1 = 0; + double b2 = -alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 1, so set the filter that way. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } + } else { + /* When the cutoff is zero, the z-transform approaches 0, if Q + * > 0. When both Q and cutoff are zero, the z-transform is + * pretty much undefined. What should we do in this case? + * For now, just make the filter 0. When the cutoff is 1, the + * z-transform also approaches 0. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } +} + +static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is a constant gain. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + } else if (frequency > 0) { + double w0 = M_PI * frequency; + double S = 1; /* filter slope (1 is max value) */ + double alpha = 0.5 * sin(w0) * + sqrt((A + 1 / A) * (1 / S - 1) + 2); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one - a_minus_one * k + k2); + double b1 = 2 * A * (a_minus_one - a_plus_one * k); + double b2 = A * (a_plus_one - a_minus_one * k - k2); + double a0 = a_plus_one + a_minus_one * k + k2; + double a1 = -2 * (a_minus_one + a_plus_one * k); + double a2 = a_plus_one + a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When frequency is 0, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +static void biquad_highshelf(struct biquad *bq, double frequency, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } else if (frequency > 0) { + double w0 = M_PI * frequency; + double S = 1; /* filter slope (1 is max value) */ + double alpha = 0.5 * sin(w0) * + sqrt((A + 1 / A) * (1 / S - 1) + 2); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one + a_minus_one * k + k2); + double b1 = -2 * A * (a_minus_one + a_plus_one * k); + double b2 = A * (a_plus_one + a_minus_one * k - k2); + double a0 = a_plus_one - a_minus_one * k + k2; + double a1 = 2 * (a_minus_one - a_plus_one * k); + double a2 = a_plus_one - a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When frequency = 0, the filter is just a gain, A^2. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + } +} + +static void biquad_peaking(struct biquad *bq, double frequency, double Q, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + double A = pow(10.0, db_gain / 40); + + if (frequency > 0 && frequency < 1) { + if (Q > 0) { + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 + alpha * A; + double b1 = -2 * k; + double b2 = 1 - alpha * A; + double a0 = 1 + alpha / A; + double a1 = -2 * k; + double a2 = 1 - alpha / A; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is A^2, so set the filter that way. + */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + } + } else { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +static void biquad_notch(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency > 0 && frequency < 1) { + if (Q > 0) { + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1; + double b1 = -2 * k; + double b2 = 1; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 0, so set the filter that way. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } + } else { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +static void biquad_allpass(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency > 0 && frequency < 1) { + if (Q > 0) { + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 - alpha; + double b1 = -2 * k; + double b2 = 1 + alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); + } else { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is -1, so set the filter that way. + */ + set_coefficient(bq, -1, 0, 0, 1, 0, 0); + } + } else { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain) +{ + /* Default is an identity filter. Also clear history values. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + bq->x1 = 0; + bq->x2 = 0; + bq->y1 = 0; + bq->y2 = 0; + + switch (type) { + case BQ_LOWPASS: + biquad_lowpass(bq, freq, Q); + break; + case BQ_HIGHPASS: + biquad_highpass(bq, freq, Q); + break; + case BQ_BANDPASS: + biquad_bandpass(bq, freq, Q); + break; + case BQ_LOWSHELF: + biquad_lowshelf(bq, freq, gain); + break; + case BQ_HIGHSHELF: + biquad_highshelf(bq, freq, gain); + break; + case BQ_PEAKING: + biquad_peaking(bq, freq, Q, gain); + break; + case BQ_NOTCH: + biquad_notch(bq, freq, Q); + break; + case BQ_ALLPASS: + biquad_allpass(bq, freq, Q); + break; + case BQ_NONE: + break; + } +} diff --git a/cras/src/dsp/biquad.h b/cras/src/dsp/biquad.h new file mode 100644 index 00000000..c584aa96 --- /dev/null +++ b/cras/src/dsp/biquad.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef BIQUAD_H_ +#define BIQUAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) + * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs + * are stored in x1 and x2, and the previous two outputs are stored in y1 and + * y2. + * + * We use double during the coefficients calculation for better accurary, but + * float is used during the actual filtering for faster computation. + */ +struct biquad { + float b0, b1, b2; + float a1, a2; + float x1, x2; + float y1, y2; +}; + +/* The type of the biquad filters */ +enum biquad_type { + BQ_NONE, + BQ_LOWPASS, + BQ_HIGHPASS, + BQ_BANDPASS, + BQ_LOWSHELF, + BQ_HIGHSHELF, + BQ_PEAKING, + BQ_NOTCH, + BQ_ALLPASS +}; + +/* Initialize a biquad filter parameters from its type and parameters. + * Args: + * bq - The biquad filter we want to set. + * type - The type of the biquad filter. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + * Q - Quality factor. See Web Audio API for details. + * gain - The value is in dB. See Web Audio API for details. + */ +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BIQUAD_H_ */ diff --git a/cras/src/dsp/dsp_util.c b/cras/src/dsp/dsp_util.c new file mode 100644 index 00000000..7e129496 --- /dev/null +++ b/cras/src/dsp/dsp_util.c @@ -0,0 +1,22 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <fpu_control.h> +#include "dsp_util.h" + +void dsp_enable_flush_denormal_to_zero() +{ +#if defined(__i386__) || defined(__x86_64__) + unsigned int mxcsr; + mxcsr = __builtin_ia32_stmxcsr(); + __builtin_ia32_ldmxcsr(mxcsr | 0x8040); +#elif defined(__arm__) + int cw; + _FPU_GETCW(cw); + _FPU_SETCW(cw | (1 << 24)); +#else +#warning "Don't know how to disable denorms. Performace may suffer." +#endif +} diff --git a/cras/src/dsp/dsp_util.h b/cras/src/dsp/dsp_util.h new file mode 100644 index 00000000..21fdff3b --- /dev/null +++ b/cras/src/dsp/dsp_util.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef DSPUTIL_H_ +#define DSPUTIL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Disables denormal numbers in floating point calculation. Denormal numbers + * happens often in IIR filters, and it can be very slow. + */ +void dsp_enable_flush_denormal_to_zero(); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* DSPUTIL_H_ */ diff --git a/cras/src/dsp/eq.c b/cras/src/dsp/eq.c new file mode 100644 index 00000000..deec8718 --- /dev/null +++ b/cras/src/dsp/eq.c @@ -0,0 +1,152 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdlib.h> +#include "eq.h" + +struct eq { + int n; + struct biquad biquad[MAX_BIQUADS_PER_EQ]; +}; + +struct eq *eq_new() +{ + struct eq *eq = (struct eq *)calloc(1, sizeof(*eq)); + return eq; +} + +void eq_free(struct eq *eq) +{ + free(eq); +} + +int eq_append_biquad(struct eq *eq, enum biquad_type type, float freq, float Q, + float gain) +{ + if (eq->n >= MAX_BIQUADS_PER_EQ) + return -1; + biquad_set(&eq->biquad[eq->n++], type, freq, Q, gain); + return 0; +} + +int eq_append_biquad_direct(struct eq *eq, const struct biquad *biquad) +{ + if (eq->n >= MAX_BIQUADS_PER_EQ) + return -1; + eq->biquad[eq->n++] = *biquad; + return 0; +} + +/* This is the prototype of the processing loop. */ +void eq_process1(struct eq *eq, float *data, int count) +{ + int i, j; + for (i = 0; i < eq->n; i++) { + struct biquad *q = &eq->biquad[i]; + float x1 = q->x1; + float x2 = q->x2; + float y1 = q->y1; + float y2 = q->y2; + float b0 = q->b0; + float b1 = q->b1; + float b2 = q->b2; + float a1 = q->a1; + float a2 = q->a2; + for (j = 0; j < count; j++) { + float x = data[j]; + float y = b0*x + + (b1*x1 + b2*x2) + - (a1*y1 + a2*y2); + data[j] = y; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + q->x1 = x1; + q->x2 = x2; + q->y1 = y1; + q->y2 = y2; + } +} + +/* This is the actual processing loop used. It is the unrolled version of the + * above prototype. */ +void eq_process(struct eq *eq, float *data, int count) +{ + int i, j; + for (i = 0; i < eq->n; i += 2) { + if (i + 1 == eq->n) { + struct biquad *q = &eq->biquad[i]; + float x1 = q->x1; + float x2 = q->x2; + float y1 = q->y1; + float y2 = q->y2; + float b0 = q->b0; + float b1 = q->b1; + float b2 = q->b2; + float a1 = q->a1; + float a2 = q->a2; + for (j = 0; j < count; j++) { + float x = data[j]; + float y = b0*x + + (b1*x1 + b2*x2) + - (a1*y1 + a2*y2); + data[j] = y; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + q->x1 = x1; + q->x2 = x2; + q->y1 = y1; + q->y2 = y2; + } else { + struct biquad *q = &eq->biquad[i]; + struct biquad *r = &eq->biquad[i+1]; + float x1 = q->x1; + float x2 = q->x2; + float y1 = q->y1; + float y2 = q->y2; + float qb0 = q->b0; + float qb1 = q->b1; + float qb2 = q->b2; + float qa1 = q->a1; + float qa2 = q->a2; + + float z1 = r->y1; + float z2 = r->y2; + float rb0 = r->b0; + float rb1 = r->b1; + float rb2 = r->b2; + float ra1 = r->a1; + float ra2 = r->a2; + + for (j = 0; j < count; j++) { + float x = data[j]; + float y = qb0*x + + (qb1*x1 + qb2*x2) + - (qa1*y1 + qa2*y2); + float z = rb0*y + + (rb1*y1 + rb2*y2) + - (ra1*z1 + ra2*z2); + data[j] = z; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + z2 = z1; + z1 = z; + } + q->x1 = x1; + q->x2 = x2; + q->y1 = y1; + q->y2 = y2; + r->y1 = z1; + r->y2 = z2; + } + } +} diff --git a/cras/src/dsp/eq.h b/cras/src/dsp/eq.h new file mode 100644 index 00000000..c5836329 --- /dev/null +++ b/cras/src/dsp/eq.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef EQ_H_ +#define EQ_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* An EQ is a chain of biquad filters. See Web Audio API spec for details of the + * biquad filters and their parameters. */ + +#include "biquad.h" + +/* Maximum number of biquad filters an EQ can have */ +#define MAX_BIQUADS_PER_EQ 10 + +struct eq; + +/* Create an EQ. */ +struct eq *eq_new(); + +/* Free an EQ. */ +void eq_free(struct eq *eq); + +/* Append a biquad filter to an EQ. An EQ can have at most MAX_BIQUADS_PER_EQ + * biquad filters. + * Args: + * eq - The EQ we want to use. + * type - The type of the biquad filter we want to append. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + * Q, gain - The meaning depends on the type of the filter. See Web Audio + * API for details. + * Returns: + * 0 if success. -1 if the eq has no room for more biquads. + */ +int eq_append_biquad(struct eq *eq, enum biquad_type type, float freq, float Q, + float gain); + +/* Append a biquad filter to an EQ. An EQ can have at most MAX_BIQUADS_PER_EQ + * biquad filters. This is similar to eq_append_biquad(), but it specifies the + * biquad coefficients directly. + * Args: + * eq - The EQ we want to use. + * biquad - The parameters for the biquad filter. + * Returns: + * 0 if success. -1 if the eq has no room for more biquads. + */ +int eq_append_biquad_direct(struct eq *eq, const struct biquad *biquad); + +/* Process a buffer of audio data through the EQ. + * Args: + * eq - The EQ we want to use. + * data - The array of audio samples. + * count - The number of elements in the data array to process. + */ +void eq_process(struct eq *eq, float *data, int count); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* EQ_H_ */ diff --git a/cras/src/dsp/tests/cmpraw.c b/cras/src/dsp/tests/cmpraw.c new file mode 100644 index 00000000..3fed9440 --- /dev/null +++ b/cras/src/dsp/tests/cmpraw.c @@ -0,0 +1,54 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "raw.h" + +/* Compare the difference between two raw files */ + +static inline double max(double a, double b) +{ + return (a > b) ? a : b; +} + +int main(int argc, char **argv) +{ + size_t frame1, frame2; + float *data1, *data2; + size_t i, n; + double diff = 0; + double maxdiff = 0; + + if (argc != 3) { + fprintf(stderr, "usage: cmpraw 1.raw 2.raw\n"); + exit(1); + } + + data1 = read_raw(argv[1], &frame1); + data2 = read_raw(argv[2], &frame2); + + if (frame1 != frame2) { + fprintf(stderr, "mismatch size (%zu vs %zu)\n", frame1, frame2); + exit(1); + } + + n = frame1; + for (i = 0; i < n; i++) { + diff += fabs(data1[i] - data2[i]); + maxdiff = max(fabs(data1[i] - data2[i]), maxdiff); + } + printf("avg diff = %g, max diff = %g/65536.0\n", + diff / n / 65536.0, maxdiff); + + free(data1); + free(data2); + return 0; +} diff --git a/cras/src/dsp/tests/dsp_test_util.c b/cras/src/dsp/tests/dsp_test_util.c new file mode 100644 index 00000000..e52a481a --- /dev/null +++ b/cras/src/dsp/tests/dsp_test_util.c @@ -0,0 +1,37 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <fenv.h> +#include <float.h> +#include <stdio.h> +#include "dsp_test_util.h" + +int dsp_util_has_denormal() +{ + float x = 1; + while (x >= FLT_MIN) + x /= 2; + return x > 0; +} + +void dsp_util_clear_fp_exceptions() +{ + feclearexcept(FE_ALL_EXCEPT); +} + +void dsp_util_print_fp_exceptions() +{ + int excepts = fetestexcept(FE_ALL_EXCEPT); + printf("floating-point exceptions: "); + if (excepts & FE_DIVBYZERO) + printf("FE_DIVBYZERO "); + if (excepts & FE_INVALID) + printf("FE_INVALID "); + if (excepts & FE_OVERFLOW) + printf("FE_OVERFLOW "); + if (excepts & FE_UNDERFLOW) + printf("FE_UNDERFLOW "); + printf("\n"); +} diff --git a/cras/src/dsp/tests/dsp_test_util.h b/cras/src/dsp/tests/dsp_test_util.h new file mode 100644 index 00000000..eb97815c --- /dev/null +++ b/cras/src/dsp/tests/dsp_test_util.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef DSP_TEST_UTIL_H_ +#define DSP_TEST_UTIL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Tests if the system supports denormal numbers. Returns 1 if so, 0 + * otherwise.*/ +int dsp_util_has_denormal(); + +/* Clears floating point exceptions. For debugging only. */ +void dsp_util_clear_fp_exceptions(); + +/* Prints floating point exceptions to stdout. For debugging only. */ +void dsp_util_print_fp_exceptions(); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* DSP_TEST_UTIL_H_ */ diff --git a/cras/src/dsp/tests/eq_test.c b/cras/src/dsp/tests/eq_test.c new file mode 100644 index 00000000..d7991014 --- /dev/null +++ b/cras/src/dsp/tests/eq_test.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include "dsp_test_util.h" +#include "dsp_util.h" +#include "eq.h" +#include "raw.h" + +#ifndef min +#define min(a, b) ({ __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif + +static double tp_diff(struct timespec *tp2, struct timespec *tp1) +{ + return (tp2->tv_sec - tp1->tv_sec) + + (tp2->tv_nsec - tp1->tv_nsec) * 1e-9; +} + +/* Generates impulse response */ +static void test_ir() +{ + int N = 32768; + float *data; + struct eq *eq; + double NQ = 44100 / 2; /* nyquist frequency */ + struct timespec tp1, tp2; + int i; + FILE *ir; + + data = calloc(1, sizeof(float) * N); + data[0] = 1; + + eq = eq_new(); + eq_append_biquad(eq, BQ_PEAKING, 380/NQ, 3, -10); + eq_append_biquad(eq, BQ_PEAKING, 720/NQ, 3, -12); + eq_append_biquad(eq, BQ_PEAKING, 1705/NQ, 3, -8); + eq_append_biquad(eq, BQ_HIGHPASS, 218/NQ, 0.7, -10.2); + eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8); + eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 3, 2); + + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1); + eq_process(eq, data, N); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2); + printf("processing takes %g seconds\n", tp_diff(&tp2, &tp1)); + eq_free(eq); + + ir = fopen("ir.dat", "w"); + for (i = 0; i < N; i++) + fprintf(ir, "%g\n", data[i]); + fclose(ir); + free(data); +} + +/* Processes a buffer of data chunk by chunk using eq */ +static void process(struct eq *eq, float *data, int count) +{ + int start; + for (start = 0; start < count; start += 2048) + eq_process(eq, data + start, min(2048, count - start)); +} + +/* Runs the filters on an input file */ +static void test_file(const char *input_filename, const char *output_filename) +{ + size_t frames; + int i; + double NQ = 44100 / 2; /* nyquist frequency */ + struct timespec tp1, tp2; + struct eq *eq; + + float *data = read_raw(input_filename, &frames); + + /* Set some data to 0 to test for denormals. */ + for (i = frames / 10; i < frames; i++) + data[i] = 0.0; + + /* Left eq chain */ + eq = eq_new(); + eq_append_biquad(eq, BQ_PEAKING, 380/NQ, 3, -10); + eq_append_biquad(eq, BQ_PEAKING, 720/NQ, 3, -12); + eq_append_biquad(eq, BQ_PEAKING, 1705/NQ, 3, -8); + eq_append_biquad(eq, BQ_HIGHPASS, 218/NQ, 0.7, -10.2); + eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8); + eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 3, 2); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1); + process(eq, data, frames); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2); + printf("processing takes %g seconds for %zu samples\n", + tp_diff(&tp2, &tp1), frames); + eq_free(eq); + + /* Right eq chain */ + eq = eq_new(); + eq_append_biquad(eq, BQ_PEAKING, 450/NQ, 3, -12); + eq_append_biquad(eq, BQ_PEAKING, 721/NQ, 3, -12); + eq_append_biquad(eq, BQ_PEAKING, 1800/NQ, 8, -10.2); + eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8); + eq_append_biquad(eq, BQ_HIGHPASS, 250/NQ, 0.6578, 0); + eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 0, 2); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1); + process(eq, data + frames, frames); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2); + printf("processing takes %g seconds for %zu samples\n", + tp_diff(&tp2, &tp1), frames); + eq_free(eq); + + write_raw(output_filename, data, frames); + free(data); +} + +int main(int argc, char **argv) +{ + dsp_enable_flush_denormal_to_zero(); + if (dsp_util_has_denormal()) + printf("denormal still supported?\n"); + else + printf("denormal disabled\n"); + dsp_util_clear_fp_exceptions(); + + if (argc == 1) + test_ir(); + else if (argc == 3) + test_file(argv[1], argv[2]); + else + printf("Usage: eq_test [input.raw output.raw]\n"); + + dsp_util_print_fp_exceptions(); + return 0; +} diff --git a/cras/src/dsp/tests/plot_fftl.m b/cras/src/dsp/tests/plot_fftl.m new file mode 100644 index 00000000..bffc0b8e --- /dev/null +++ b/cras/src/dsp/tests/plot_fftl.m @@ -0,0 +1,40 @@ +% Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +% Use of this source code is governed by a BSD-style license that can be +% found in the LICENSE file. +% +% This is an octave script. +% It reads impulse response from "ir.dat" and plots frequency response. +% Both x-axis and y-axis is in log scale. +h=load("ir.dat"); +N=columns(h); +K=rows(h)/2; +NQ=44100/2; +% This tries to match the labels in the audio tuning UI. +xticks=[22050, 11025, 5513, 2756, 1378, 689, 345, 172, 86, 43, 21]; +xticklabels={"22050Hz", "11025Hz", "5513Hz", "2756Hz", "1378Hz", \ +"689Hz", "345Hz", "172Hz", "86Hz", "43Hz", "21Hz"}; +yticks=[18,12,6,0,-6,-12,-18,-24]; +yticklabels={"18dB","12dB","6dB","0dB","-6dB","-12dB","-18dB","-24dB"}; +xyrange=[21,22050,-24,18]; +xrange=[21,22050]; + +for i=1:N + figure(i); + title('fftl'); + fr = fft(h(:,i))(1:K); + subplot(2, 1, 1); + semilogx(NQ*(1:K)/K, 20*log10(abs(fr))); + xlabel('Frequency'), ylabel('Magnitude'), grid; + set (gca, "xtick", xticks); + set (gca, "xticklabel", xticklabels); + set (gca, "ytick", yticks); + set (gca, "yticklabel", yticklabels); + axis(xyrange); + subplot(2, 1, 2); + semilogx(NQ*(1:K)/K,180/pi*unwrap(angle(fr))); + xlabel('Frequency'), ylabel('Phase (degrees)'), grid; + set (gca, "xtick", xticks); + set (gca, "xticklabel", xticklabels); + axis(xrange); +end +pause diff --git a/cras/src/dsp/tests/raw.c b/cras/src/dsp/tests/raw.c new file mode 100644 index 00000000..65c1d92c --- /dev/null +++ b/cras/src/dsp/tests/raw.c @@ -0,0 +1,93 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +float *read_raw(const char *filename, size_t *frames) +{ + struct stat st; + int16_t *buf; + size_t f, n; + int fd; + float *data; + int i; + + if (stat(filename, &st) < 0) { + fprintf(stderr, "cannot stat file %s\n", filename); + return NULL; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "cannot open file %s\n", filename); + return NULL; + } + + f = st.st_size / 4; + n = f * 4; + buf = (int16_t *)malloc(n); + if (read(fd, buf, n) != n) { + fprintf(stderr, "short read %zu\n", n); + free(buf); + return NULL; + } + close(fd); + + /* deinterleave and convert to float */ + data = (float *)malloc(sizeof(float) * f * 2); + for (i = 0; i < f; i++) { + data[i] = buf[2*i] / 32768.0f; + data[i + f] = buf[2*i+1] / 32768.0f; + } + free(buf); + *frames = f; + return data; +} + +static int16_t f2s16(float f) +{ + int i; + f *= 32768; + i = (int)((f > 0) ? (f + 0.5f) : (f - 0.5f)); + if (i < -32768) + i = -32768; + else if (i > 32767) + i = 32767; + return (int16_t)i; +} + +int write_raw(const char *filename, float *input, size_t frames) +{ + int16_t *buf; + int rc = -1; + int n = frames * 4; + int i; + + buf = (int16_t *)malloc(n); + for (i = 0; i < frames; i++) { + buf[2*i] = f2s16(input[i]); + buf[2*i+1] = f2s16(input[i + frames]); + } + + int fd = open(filename, O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + fprintf(stderr, "cannot open file %s\n", filename); + goto quit; + } + if (write(fd, buf, n) != n) { + fprintf(stderr, "short write file %s\n", filename); + goto quit; + } + rc = 0; +quit: + close(fd); + free(buf); + return rc; +} diff --git a/cras/src/dsp/tests/raw.h b/cras/src/dsp/tests/raw.h new file mode 100644 index 00000000..a0a30ab3 --- /dev/null +++ b/cras/src/dsp/tests/raw.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef RAW_H_ +#define RAW_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +/* Reads a raw file to a float buffer. + * Args: + * filename - The name of the raw file. + * frames - Returns the number of frames read. + * Returns: + * The float buffer allocated by malloc(), or NULL if reading fails. The + * first half of the buffer contains left channel data, and the second half + * contains the right channel data. + * The raw file is assumed to have two channel 16 bit signed integer samples in + * native endian. The raw file can be created by: + * sox input.wav output.raw + * The raw file can be played by: + * play -r 44100 -s -b 16 -c 2 test.raw + */ +float *read_raw(const char *filename, size_t *frames); + +/* Writes a float buffer to a raw file. + * Args: + * filename - The name of the raw file. + * buf - The float buffer containing the samples. + * frames - The number of frames in the float buffer. + * Returns: + * 0 if success. -1 if writing fails. + * The format of the float buffer is the same as described in read_raw(). + */ +void write_raw(const char *filename, float *buf, size_t frames); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* RAW_H_ */ diff --git a/cras/src/server/cras_dsp.c b/cras/src/server/cras_dsp.c index c54bc8b4..5088e9d2 100644 --- a/cras/src/server/cras_dsp.c +++ b/cras/src/server/cras_dsp.c @@ -10,6 +10,7 @@ #include "cras_expr.h" #include "cras_dsp_ini.h" #include "cras_dsp_pipeline.h" +#include "dsp_util.h" #include "utlist.h" /* We have a dsp_context for each pipeline. The context records the @@ -277,6 +278,7 @@ static void *dsp_thread_function(void *arg) void cras_dsp_init(const char *filename) { + dsp_enable_flush_denormal_to_zero(); ini_filename = strdup(filename); syslog_dumper = syslog_dumper_create(LOG_ERR); pthread_create(&dsp_thread, NULL, dsp_thread_function, NULL); diff --git a/cras/src/server/cras_dsp_mod_builtin.c b/cras/src/server/cras_dsp_mod_builtin.c index 0736579c..1b3e6e25 100644 --- a/cras/src/server/cras_dsp_mod_builtin.c +++ b/cras/src/server/cras_dsp_mod_builtin.c @@ -4,38 +4,69 @@ */ #include <stdlib.h> - #include "cras_dsp_module.h" +#include "dsp_util.h" +#include "eq.h" - -static int instantiate(struct dsp_module *module, unsigned long sample_rate) +/* + * empty module functions (for source and sink) + */ +static int empty_instantiate(struct dsp_module *module, + unsigned long sample_rate) { return 0; } -static int instantiate_mix_stereo(struct dsp_module *module, +static void empty_connect_port(struct dsp_module *module, unsigned long port, + float *data_location) {} + +static void empty_run(struct dsp_module *module, unsigned long sample_count) {} + +static void empty_deinstantiate(struct dsp_module *module) {} + +static void empty_free_module(struct dsp_module *module) +{ + free(module); +} + +static int empty_get_properties(struct dsp_module *module) { return 0; } + +static void empty_dump(struct dsp_module *module, struct dumper *d) +{ + dumpf(d, "built-in module\n"); +} + +static void empty_init_module(struct dsp_module *module) +{ + module->instantiate = &empty_instantiate; + module->connect_port = &empty_connect_port; + module->run = &empty_run; + module->deinstantiate = &empty_deinstantiate; + module->free_module = &empty_free_module; + module->get_properties = &empty_get_properties; + module->dump = &empty_dump; +} + +/* + * mix_stereo module functions + */ +static int mix_stereo_instantiate(struct dsp_module *module, unsigned long sample_rate) { module->data = calloc(4, sizeof(float*)); - return 0; } -static void connect_port(struct dsp_module *module, unsigned long port, - float *data_location) {} - -static void connect_port_mix_stereo(struct dsp_module *module, - unsigned long port, float *data_location) +static void mix_stereo_connect_port(struct dsp_module *module, + unsigned long port, float *data_location) { float **ports; ports = (float **)module->data; ports[port] = data_location; } -static void run(struct dsp_module *module, unsigned long sample_count) {} - -static void run_mix_stereo(struct dsp_module *module, - unsigned long sample_count) +static void mix_stereo_run(struct dsp_module *module, + unsigned long sample_count) { size_t i; float tmp; @@ -48,24 +79,97 @@ static void run_mix_stereo(struct dsp_module *module, } } -static void deinstantiate(struct dsp_module *module) {} - -static void deinstantiate_mix_stereo(struct dsp_module *module) +static void mix_stereo_deinstantiate(struct dsp_module *module) { free(module->data); } -static int get_properties(struct dsp_module *module) { return 0; } -static void dump(struct dsp_module *module, struct dumper *d) +static void mix_stereo_init_module(struct dsp_module *module) { - dumpf(d, "built-in module\n"); + module->instantiate = &mix_stereo_instantiate; + module->connect_port = &mix_stereo_connect_port; + module->run = &mix_stereo_run; + module->deinstantiate = &mix_stereo_deinstantiate; + module->free_module = &empty_free_module; + module->get_properties = &empty_get_properties; + module->dump = &empty_dump; } -static void free_module(struct dsp_module *module) +/* + * eq module functions + */ +struct eq_data { + int sample_rate; + struct eq *eq; /* Initialized in the first call of eq_run() */ + + /* One port for input, one for output, and 4 parameters per eq */ + float *ports[2 + MAX_BIQUADS_PER_EQ * 4]; +}; + +static int eq_instantiate(struct dsp_module *module, unsigned long sample_rate) { - free(module); + struct eq_data *data; + + module->data = calloc(1, sizeof(struct eq_data)); + data = (struct eq_data *) module->data; + data->sample_rate = (int) sample_rate; + return 0; +} + +static void eq_connect_port(struct dsp_module *module, + unsigned long port, float *data_location) +{ + struct eq_data *data = (struct eq_data *) module->data; + data->ports[port] = data_location; } +static void eq_run(struct dsp_module *module, unsigned long sample_count) +{ + struct eq_data *data = (struct eq_data *) module->data; + if (!data->eq) { + float nyquist = data->sample_rate / 2; + int i; + + data->eq = eq_new(); + for (i = 2; i < 2 + MAX_BIQUADS_PER_EQ * 4; i += 4) { + if (!data->ports[i]) + break; + int type = (int) *data->ports[i]; + float freq = *data->ports[i+1]; + float Q = *data->ports[i+2]; + float gain = *data->ports[i+3]; + eq_append_biquad(data->eq, type, freq / nyquist, Q, + gain); + } + } + if (data->ports[0] != data->ports[1]) + memcpy(data->ports[1], data->ports[0], + sizeof(float) * sample_count); + eq_process(data->eq, data->ports[1], (int) sample_count); +} + +static void eq_deinstantiate(struct dsp_module *module) +{ + struct eq_data *data = (struct eq_data *) module->data; + if (data->eq) + eq_free(data->eq); + free(data); +} + +static void eq_init_module(struct dsp_module *module) +{ + module->instantiate = &eq_instantiate; + module->connect_port = &eq_connect_port; + module->run = &eq_run; + module->deinstantiate = &eq_deinstantiate; + module->free_module = &empty_free_module; + module->get_properties = &empty_get_properties; + module->dump = &empty_dump; +} + +/* + * builtin module dispatcher + */ struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin) { struct dsp_module *module; @@ -73,19 +177,13 @@ struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin) return NULL; module = calloc(1, sizeof(struct dsp_module)); - module->instantiate = &instantiate; - module->connect_port = &connect_port; - module->run = &run; - module->deinstantiate = &deinstantiate; - module->free_module = &free_module; - module->get_properties = &get_properties; - module->dump = &dump; if (strcmp(plugin->label, "mix_stereo") == 0) { - module->instantiate = &instantiate_mix_stereo; - module->run = &run_mix_stereo; - module->connect_port = &connect_port_mix_stereo; - module->deinstantiate = &deinstantiate_mix_stereo; + mix_stereo_init_module(module); + } else if (strcmp(plugin->label, "eq") == 0) { + eq_init_module(module); + } else { + empty_init_module(module); } return module; diff --git a/cras/src/tests/dsp_core_unittest.cc b/cras/src/tests/dsp_core_unittest.cc new file mode 100644 index 00000000..feac4246 --- /dev/null +++ b/cras/src/tests/dsp_core_unittest.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2013 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <gtest/gtest.h> +#include <math.h> +#include "dsp_util.h" +#include "eq.h" + +namespace { + +/* Adds amplitude * sin(pi*freq*i + offset) to the data array. */ +static void add_sine(float *data, size_t len, float freq, float offset, + float amplitude) +{ + for (size_t i = 0; i < len; i++) + data[i] += amplitude * sinf((float)M_PI*freq*i + offset); +} + +/* Calculates the magnitude at normalized frequency f. The output is + * the result of DFT, multiplied by 2/len. */ +static float magnitude_at(float *data, size_t len, float f) +{ + double re = 0, im = 0; + f *= (float)M_PI; + for (size_t i = 0; i < len; i++) { + re += data[i] * cos(i * f); + im += data[i] * sin(i * f); + } + return sqrt(re * re + im * im) * (2.0 / len); +} + +TEST(EqTest, All) { + struct eq *eq; + size_t len = 44100; + float NQ = len / 2; + float f_low = 10 / NQ; + float f_mid = 100 / NQ; + float f_high = 1000 / NQ; + float *data = (float *)malloc(sizeof(float) * len); + + dsp_enable_flush_denormal_to_zero(); + /* low pass */ + memset(data, 0, sizeof(float) * len); + add_sine(data, len, f_low, 0, 1); // 10Hz sine, magnitude = 1 + EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_low)); + add_sine(data, len, f_high, 0, 1); // 1000Hz sine, magnitude = 1 + EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_low)); + EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_high)); + + eq = eq_new(); + EXPECT_EQ(0, eq_append_biquad(eq, BQ_LOWPASS, f_mid, 0, 0)); + eq_process(eq, data, len); + EXPECT_NEAR(1, magnitude_at(data, len, f_low), 0.01); + EXPECT_NEAR(0, magnitude_at(data, len, f_high), 0.01); + eq_free(eq); + + /* high pass */ + memset(data, 0, sizeof(float) * len); + add_sine(data, len, f_low, 0, 1); + add_sine(data, len, f_high, 0, 1); + + eq = eq_new(); + EXPECT_EQ(0, eq_append_biquad(eq, BQ_HIGHPASS, f_mid, 0, 0)); + eq_process(eq, data, len); + EXPECT_NEAR(0, magnitude_at(data, len, f_low), 0.01); + EXPECT_NEAR(1, magnitude_at(data, len, f_high), 0.01); + eq_free(eq); + + /* peaking */ + memset(data, 0, sizeof(float) * len); + add_sine(data, len, f_low, 0, 1); + add_sine(data, len, f_high, 0, 1); + + eq = eq_new(); + EXPECT_EQ(0, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6)); // Q=5, 6dB gain + eq_process(eq, data, len); + EXPECT_NEAR(1, magnitude_at(data, len, f_low), 0.01); + EXPECT_NEAR(2, magnitude_at(data, len, f_high), 0.01); + eq_free(eq); + + free(data); + + /* Too many biquads */ + eq = eq_new(); + for (int i = 0; i < MAX_BIQUADS_PER_EQ; i++) { + EXPECT_EQ(0, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6)); + } + EXPECT_EQ(-1, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6)); + eq_free(eq); +} + +} // namespace + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/cras/src/tests/dsp_unittest.cc b/cras/src/tests/dsp_unittest.cc index 3abec8ff..32e0a1ed 100644 --- a/cras/src/tests/dsp_unittest.cc +++ b/cras/src/tests/dsp_unittest.cc @@ -5,6 +5,7 @@ #include <gtest/gtest.h> #include "cras_dsp.h" +#include "cras_dsp_module.h" #define FILENAME_TEMPLATE "DspTest.XXXXXX" @@ -104,8 +105,55 @@ TEST_F(DspTestSuite, Simple) { cras_dsp_stop(); } +static int empty_instantiate(struct dsp_module *module, + unsigned long sample_rate) +{ + return 0; +} + +static void empty_connect_port(struct dsp_module *module, unsigned long port, + float *data_location) {} + +static void empty_run(struct dsp_module *module, unsigned long sample_count) {} + +static void empty_deinstantiate(struct dsp_module *module) {} + +static void empty_free_module(struct dsp_module *module) +{ + free(module); +} + +static int empty_get_properties(struct dsp_module *module) { return 0; } + +static void empty_dump(struct dsp_module *module, struct dumper *d) +{ + dumpf(d, "built-in module\n"); +} + +static void empty_init_module(struct dsp_module *module) +{ + module->instantiate = &empty_instantiate; + module->connect_port = &empty_connect_port; + module->run = &empty_run; + module->deinstantiate = &empty_deinstantiate; + module->free_module = &empty_free_module; + module->get_properties = &empty_get_properties; + module->dump = &empty_dump; +} + } // namespace +extern "C" +{ +struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin) +{ + struct dsp_module *module; + module = (struct dsp_module *)calloc(1, sizeof(struct dsp_module)); + empty_init_module(module); + return module; +} +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); |