pulsemodlibexec_LTLIBRARIES = \
libhal-interface.la \
+ libprocessor.la \
libcommunicator.la \
module-tizenaudio-sink.la \
module-tizenaudio-source.la \
module-tizenaudio-policy.la \
module-tizenaudio-discover.la \
module-tizenaudio-publish.la \
+ module-tizenaudio-echo-cancel.la \
module-sound-player.la \
module-tone-player.la \
module-poweroff.la
if ENABLE_HALTC
-pulsemodlibexec_LTLIBRARIES += \
- module-tizenaudio-haltc.la
+pulsemodlibexec_LTLIBRARIES += module-tizenaudio-haltc.la
endif
if ENABLE_ACM
-pulsemodlibexec_LTLIBRARIES += \
- module-acm-sink.la
+pulsemodlibexec_LTLIBRARIES += module-acm-sink.la
endif
libhal_interface_la_SOURCES = \
module_tizenaudio_source_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la
module_tizenaudio_source_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source
-module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c
+module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c src/echo-cancel/echo-cancel-def.h
module_tizenaudio_sink2_la_LDFLAGS = $(MODULE_LDFLAGS)
module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la
module_tizenaudio_sink2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_sink2
-module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c
+module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c src/echo-cancel/echo-cancel-def.h
module_tizenaudio_source2_la_LDFLAGS = $(MODULE_LDFLAGS)
module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la
module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source2
+libprocessor_la_SOURCES = \
+ src/echo-cancel/algo_speex.c \
+ src/echo-cancel/algo_adrian.c \
+ src/echo-cancel/adrian-aec.c \
+ src/echo-cancel/processor.c \
+ src/echo-cancel/processor.h \
+ src/echo-cancel/adrian-aec.h
+libprocessor_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version
+libprocessor_la_LIBADD = $(AM_LIBADD) $(LIBSPEEX_LIBS)
+libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS)
+
+module_tizenaudio_echo_cancel_la_SOURCES = src/echo-cancel/module-tizenaudio-echo-cancel.c src/echo-cancel/echo-cancel-def.h
+module_tizenaudio_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_echo_cancel_la_LIBADD = $(MODULE_LIBADD) libprocessor.la
+module_tizenaudio_echo_cancel_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_echo_cancel
+
module_sound_player_la_SOURCES = src/module-sound-player.c
module_sound_player_la_LDFLAGS = $(MODULE_LDFLAGS)
module_sound_player_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
AC_SUBST(HALAPIAUDIO_CFLAGS)
AC_SUBST(HALAPIAUDIO_LIBS)
+PKG_CHECK_MODULES(SPEEX, speexdsp)
+AC_SUBST(SPEEX_CFLAGS)
+AC_SUBST(SPEEX_LIBS)
+
dnl use hal tc ------------------------------------------------------------
AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]),
[
Name: pulseaudio-modules-tizen
Summary: Pulseaudio modules for Tizen
-Version: 15.0.3
+Version: 15.0.4
Release: 0
Group: Multimedia/Audio
License: LGPL-2.1+
BuildRequires: pkgconfig(libsystemd)
BuildRequires: pkgconfig(dns_sd)
BuildRequires: pkgconfig(hal-api-audio)
+BuildRequires: pkgconfig(speexdsp)
BuildRequires: pulseaudio
BuildRequires: m4
Requires(post): /sbin/ldconfig
%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-source2.so
%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-discover.so
%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-publish.so
+%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-echo-cancel.so
+%{_libdir}/pulse-%{module_ver}/modules/libprocessor.so
%{_libdir}/pulse-%{module_ver}/modules/libhal-interface.so
%{_libdir}/pulse-%{module_ver}/modules/libcommunicator.so
%{_tmpfilesdir}/pulseaudio.conf
--- /dev/null
+/* aec.cpp
+ *
+ * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
+ * All Rights Reserved.
+ *
+ * Acoustic Echo Cancellation NLMS-pw algorithm
+ *
+ * Version 0.3 filter created with www.dsptutor.freeuk.com
+ * Version 0.3.1 Allow change of stability parameter delta
+ * Version 0.4 Leaky Normalized LMS - pre whitening algorithm
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <pulse/xmalloc.h>
+
+#include "adrian-aec.h"
+
+#ifndef DISABLE_ORC
+#include "adrian-aec-orc-gen.h"
+#endif
+
+#ifdef __SSE__
+#include <xmmintrin.h>
+#endif
+
+/* Double-Talk Detector
+ *
+ * in d: microphone sample (PCM as REALing point value)
+ * in x: loudspeaker sample (PCM as REALing point value)
+ * return: from 0 for doubletalk to 1.0 for single talk
+*/
+static float AEC_dtd(AEC *a, REAL d, REAL x);
+
+static void AEC_leaky(AEC *a);
+
+/* Normalized Least Mean Square Algorithm pre-whitening (NLMS-pw)
+ * The LMS algorithm was developed by Bernard Widrow
+ * book: Haykin, Adaptive Filter Theory, 4. edition, Prentice Hall, 2002
+ *
+ * in d: microphone sample (16bit PCM value)
+ * in x_: loudspeaker sample (16bit PCM value)
+ * in stepsize: NLMS adaptation variable
+ * return: echo cancelled microphone sample
+ */
+static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize);
+
+static void AEC_setambient(AEC *a, float Min_xf) {
+ a->dotp_xf_xf -= a->delta; // subtract old delta
+ a->delta = (NLMS_LEN-1) * Min_xf * Min_xf;
+ a->dotp_xf_xf += a->delta; // add new delta
+ }
+
+static REAL IIR1_highpass(IIR1 *i, REAL in) {
+ REAL out = i->a0 * in + i->a1 * i->in0 + i->b1 * i->out0;
+ i->in0 = in;
+ i->out0 = out;
+ return out;
+ }
+
+static IIR1* IIR1_init(REAL Fc) {
+ IIR1 *i = pa_xnew(IIR1, 1);
+ i->b1 = expf(-2.0f * M_PI * Fc);
+ i->a0 = (1.0f + i->b1) / 2.0f;
+ i->a1 = -(i->a0);
+ i->in0 = 0.0f;
+ i->out0 = 0.0f;
+ return i;
+}
+
+static REAL IIR_HP_highpass(IIR_HP *i, REAL in) {
+ const REAL a0 = 0.01f; /* controls Transfer Frequency */
+ /* Highpass = Signal - Lowpass. Lowpass = Exponential Smoothing */
+ i->x += a0 * (in - i->x);
+ return in - i->x;
+ }
+
+static IIR_HP* IIR_HP_init(void) {
+ IIR_HP *i = pa_xnew(IIR_HP, 1);
+ i->x = 0.0f;
+ return i;
+ }
+
+#if WIDEB==1
+/* 17 taps FIR Finite Impulse Response filter
+ * Coefficients calculated with
+ * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html
+ */
+class FIR_HP_300Hz {
+ REAL z[18];
+
+public:
+ FIR_HP_300Hz() {
+ memset(this, 0, sizeof(FIR_HP_300Hz));
+ }
+
+ REAL highpass(REAL in) {
+ const REAL a[18] = {
+ // Kaiser Window FIR Filter, Filter type: High pass
+ // Passband: 300.0 - 4000.0 Hz, Order: 16
+ // Transition band: 75.0 Hz, Stopband attenuation: 10.0 dB
+ -0.034870606, -0.039650206, -0.044063766, -0.04800318,
+ -0.051370874, -0.054082647, -0.056070227, -0.057283327,
+ 0.8214126, -0.057283327, -0.056070227, -0.054082647,
+ -0.051370874, -0.04800318, -0.044063766, -0.039650206,
+ -0.034870606, 0.0
+ };
+ memmove(z + 1, z, 17 * sizeof(REAL));
+ z[0] = in;
+ REAL sum0 = 0.0, sum1 = 0.0;
+ int j;
+
+ for (j = 0; j < 18; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * z[j];
+ sum1 += a[j + 1] * z[j + 1];
+ }
+ return sum0 + sum1;
+ }
+};
+
+#else
+
+/* 35 taps FIR Finite Impulse Response filter
+ * Passband 150Hz to 4kHz for 8kHz sample rate, 300Hz to 8kHz for 16kHz
+ * sample rate.
+ * Coefficients calculated with
+ * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html
+ */
+struct FIR_HP_300Hz {
+ REAL z[36];
+};
+
+static FIR_HP_300Hz* FIR_HP_300Hz_init(void) {
+ FIR_HP_300Hz *ret = pa_xnew(FIR_HP_300Hz, 1);
+ memset(ret, 0, sizeof(FIR_HP_300Hz));
+ return ret;
+ }
+
+static REAL FIR_HP_300Hz_highpass(FIR_HP_300Hz *f, REAL in) {
+ REAL sum0 = 0.0, sum1 = 0.0;
+ int j;
+ const REAL a[36] = {
+ // Kaiser Window FIR Filter, Filter type: High pass
+ // Passband: 150.0 - 4000.0 Hz, Order: 34
+ // Transition band: 34.0 Hz, Stopband attenuation: 10.0 dB
+ -0.016165324, -0.017454365, -0.01871232, -0.019931411,
+ -0.021104068, -0.022222936, -0.02328091, -0.024271343,
+ -0.025187887, -0.02602462, -0.026776174, -0.027437767,
+ -0.028004972, -0.028474221, -0.028842418, -0.029107114,
+ -0.02926664, 0.8524841, -0.02926664, -0.029107114,
+ -0.028842418, -0.028474221, -0.028004972, -0.027437767,
+ -0.026776174, -0.02602462, -0.025187887, -0.024271343,
+ -0.02328091, -0.022222936, -0.021104068, -0.019931411,
+ -0.01871232, -0.017454365, -0.016165324, 0.0
+ };
+ memmove(f->z + 1, f->z, 35 * sizeof(REAL));
+ f->z[0] = in;
+
+ for (j = 0; j < 36; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * f->z[j];
+ sum1 += a[j + 1] * f->z[j + 1];
+ }
+ return sum0 + sum1;
+ }
+#endif
+
+
+
+
+
+/* Vector Dot Product */
+static REAL dotp(REAL a[], REAL b[])
+{
+ REAL sum0 = 0.0f, sum1 = 0.0f;
+ int j;
+
+ for (j = 0; j < NLMS_LEN; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * b[j];
+ sum1 += a[j + 1] * b[j + 1];
+ }
+ return sum0 + sum1;
+}
+
+static REAL dotp_sse(REAL a[], REAL b[])
+{
+#ifdef __SSE__
+ /* This is taken from speex's inner product implementation */
+ int j;
+ REAL sum;
+ __m128 acc = _mm_setzero_ps();
+
+ for (j=0;j<NLMS_LEN;j+=8)
+ {
+ acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j), _mm_loadu_ps(b+j)));
+ acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j+4), _mm_loadu_ps(b+j+4)));
+ }
+ acc = _mm_add_ps(acc, _mm_movehl_ps(acc, acc));
+ acc = _mm_add_ss(acc, _mm_shuffle_ps(acc, acc, 0x55));
+ _mm_store_ss(&sum, acc);
+
+ return sum;
+#else
+ return dotp(a, b);
+#endif
+}
+
+
+AEC* AEC_init(int RATE, int have_vector)
+{
+ AEC *a = pa_xnew0(AEC, 1);
+ a->j = NLMS_EXT;
+ AEC_setambient(a, NoiseFloor);
+ a->dfast = a->dslow = M75dB_PCM;
+ a->xfast = a->xslow = M80dB_PCM;
+ a->gain = 1.0f;
+ a->Fx = IIR1_init(2000.0f/RATE);
+ a->Fe = IIR1_init(2000.0f/RATE);
+ a->cutoff = FIR_HP_300Hz_init();
+ a->acMic = IIR_HP_init();
+ a->acSpk = IIR_HP_init();
+
+ a->aes_y2 = M0dB;
+
+ a->fdwdisplay = -1;
+
+ if (have_vector) {
+ /* Get a 16-byte aligned location */
+ a->w = (REAL *) (((uintptr_t) a->w_arr) - (((uintptr_t) a->w_arr) % 16) + 16);
+ a->dotp = dotp_sse;
+ } else {
+ /* We don't care about alignment, just use the array as-is */
+ a->w = a->w_arr;
+ a->dotp = dotp;
+ }
+
+ return a;
+}
+
+void AEC_done(AEC *a) {
+ pa_assert(a);
+
+ pa_xfree(a->Fx);
+ pa_xfree(a->Fe);
+ pa_xfree(a->acMic);
+ pa_xfree(a->acSpk);
+ pa_xfree(a->cutoff);
+ pa_xfree(a);
+}
+
+// Adrian soft decision DTD
+// (Dual Average Near-End to Far-End signal Ratio DTD)
+// This algorithm uses exponential smoothing with different
+// ageing parameters to get fast and slow near-end and far-end
+// signal averages. The ratio of NFRs term
+// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize
+// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is
+// mapped to 1.0 with a limited linear function.
+static float AEC_dtd(AEC *a, REAL d, REAL x)
+{
+ float ratio, stepsize;
+
+ // fast near-end and far-end average
+ a->dfast += ALPHAFAST * (fabsf(d) - a->dfast);
+ a->xfast += ALPHAFAST * (fabsf(x) - a->xfast);
+
+ // slow near-end and far-end average
+ a->dslow += ALPHASLOW * (fabsf(d) - a->dslow);
+ a->xslow += ALPHASLOW * (fabsf(x) - a->xslow);
+
+ if (a->xfast < M70dB_PCM) {
+ return 0.0f; // no Spk signal
+ }
+
+ if (a->dfast < M70dB_PCM) {
+ return 0.0f; // no Mic signal
+ }
+
+ // ratio of NFRs
+ ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast);
+
+ // Linear interpolation with clamping at the limits
+ if (ratio < STEPX1)
+ stepsize = STEPY1;
+ else if (ratio > STEPX2)
+ stepsize = STEPY2;
+ else
+ stepsize = STEPY1 + (STEPY2 - STEPY1) * (ratio - STEPX1) / (STEPX2 - STEPX1);
+
+ return stepsize;
+}
+
+
+static void AEC_leaky(AEC *a)
+// The xfast signal is used to charge the hangover timer to Thold.
+// When hangover expires (no Spk signal for some time) the vector w
+// is erased. This is my implementation of Leaky NLMS.
+{
+ if (a->xfast >= M70dB_PCM) {
+ // vector w is valid for hangover Thold time
+ a->hangover = Thold;
+ } else {
+ if (a->hangover > 1) {
+ --(a->hangover);
+ } else if (1 == a->hangover) {
+ --(a->hangover);
+ // My Leaky NLMS is to erase vector w when hangover expires
+ memset(a->w_arr, 0, sizeof(a->w_arr));
+ }
+ }
+}
+
+
+#if 0
+void AEC::openwdisplay() {
+ // open TCP connection to program wdisplay.tcl
+ fdwdisplay = socket_async("127.0.0.1", 50999);
+};
+#endif
+
+
+static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize)
+{
+ REAL e;
+ REAL ef;
+ a->x[a->j] = x_;
+ a->xf[a->j] = IIR1_highpass(a->Fx, x_); // pre-whitening of x
+
+ // calculate error value
+ // (mic signal - estimated mic signal from spk signal)
+ e = d;
+ if (a->hangover > 0) {
+ e -= a->dotp(a->w, a->x + a->j);
+ }
+ ef = IIR1_highpass(a->Fe, e); // pre-whitening of e
+
+ // optimize: iterative dotp(xf, xf)
+ a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]);
+
+ if (stepsize > 0.0f) {
+ // calculate variable step size
+ REAL mikro_ef = stepsize * ef / a->dotp_xf_xf;
+
+#ifdef DISABLE_ORC
+ // update tap weights (filter learning)
+ int i;
+ for (i = 0; i < NLMS_LEN; i += 2) {
+ // optimize: partial loop unrolling
+ a->w[i] += mikro_ef * a->xf[i + a->j];
+ a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1];
+ }
+#else
+ update_tap_weights(a->w, &a->xf[a->j], mikro_ef, NLMS_LEN);
+#endif
+ }
+
+ if (--(a->j) < 0) {
+ // optimize: decrease number of memory copies
+ a->j = NLMS_EXT;
+ memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL));
+ memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL));
+ }
+
+ // Saturation
+ if (e > MAXPCM) {
+ return MAXPCM;
+ } else if (e < -MAXPCM) {
+ return -MAXPCM;
+ } else {
+ return e;
+ }
+}
+
+
+int AEC_doAEC(AEC *a, int d_, int x_)
+{
+ REAL d = (REAL) d_;
+ REAL x = (REAL) x_;
+
+ // Mic Highpass Filter - to remove DC
+ d = IIR_HP_highpass(a->acMic, d);
+
+ // Mic Highpass Filter - cut-off below 300Hz
+ d = FIR_HP_300Hz_highpass(a->cutoff, d);
+
+ // Amplify, for e.g. Soundcards with -6dB max. volume
+ d *= a->gain;
+
+ // Spk Highpass Filter - to remove DC
+ x = IIR_HP_highpass(a->acSpk, x);
+
+ // Double Talk Detector
+ a->stepsize = AEC_dtd(a, d, x);
+
+ // Leaky (ageing of vector w)
+ AEC_leaky(a);
+
+ // Acoustic Echo Cancellation
+ d = AEC_nlms_pw(a, d, x, a->stepsize);
+
+#if 0
+ if (fdwdisplay >= 0) {
+ if (++dumpcnt >= (WIDEB*RATE/10)) {
+ // wdisplay creates 10 dumps per seconds = large CPU load!
+ dumpcnt = 0;
+ write(fdwdisplay, ws, DUMP_LEN*sizeof(float));
+ // we don't check return value. This is not production quality!!!
+ memset(ws, 0, sizeof(ws));
+ } else {
+ int i;
+ for (i = 0; i < DUMP_LEN; i += 2) {
+ // optimize: partial loop unrolling
+ ws[i] += w[i];
+ ws[i + 1] += w[i + 1];
+ }
+ }
+ }
+#endif
+
+ return (int) d;
+}
--- /dev/null
+/* aec.h
+ *
+ * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
+ * All Rights Reserved.
+ * Author: Andre Adrian
+ *
+ * Acoustic Echo Cancellation Leaky NLMS-pw algorithm
+ *
+ * Version 0.3 filter created with www.dsptutor.freeuk.com
+ * Version 0.3.1 Allow change of stability parameter delta
+ * Version 0.4 Leaky Normalized LMS - pre whitening algorithm
+ */
+
+#ifndef _AEC_H /* include only once */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/gccmacro.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#ifdef __TIZEN__
+#define DISABLE_ORC
+#define _USE_MATH_DEFINES
+#include <math.h>
+#endif
+
+#define WIDEB 2
+
+// use double if your CPU does software-emulation of float
+#define REAL float
+
+/* dB Values */
+#define M0dB 1.0f
+#define M3dB 0.71f
+#define M6dB 0.50f
+#define M9dB 0.35f
+#define M12dB 0.25f
+#define M18dB 0.125f
+#define M24dB 0.063f
+
+/* dB values for 16bit PCM */
+/* MxdB_PCM = 32767 * 10 ^(x / 20) */
+#define M10dB_PCM 10362.0f
+#define M20dB_PCM 3277.0f
+#define M25dB_PCM 1843.0f
+#define M30dB_PCM 1026.0f
+#define M35dB_PCM 583.0f
+#define M40dB_PCM 328.0f
+#define M45dB_PCM 184.0f
+#define M50dB_PCM 104.0f
+#define M55dB_PCM 58.0f
+#define M60dB_PCM 33.0f
+#define M65dB_PCM 18.0f
+#define M70dB_PCM 10.0f
+#define M75dB_PCM 6.0f
+#define M80dB_PCM 3.0f
+#define M85dB_PCM 2.0f
+#define M90dB_PCM 1.0f
+
+#define MAXPCM 32767.0f
+
+/* Design constants (Change to fine tune the algorithms */
+
+/* The following values are for hardware AEC and studio quality
+ * microphone */
+
+/* NLMS filter length in taps (samples). A longer filter length gives
+ * better Echo Cancellation, but maybe slower convergence speed and
+ * needs more CPU power (Order of NLMS is linear) */
+#define NLMS_LEN (100*WIDEB*8)
+
+/* Vector w visualization length in taps (samples).
+ * Must match argv value for wdisplay.tcl */
+#define DUMP_LEN (40*WIDEB*8)
+
+/* minimum energy in xf. Range: M70dB_PCM to M50dB_PCM. Should be equal
+ * to microphone ambient Noise level */
+#define NoiseFloor M55dB_PCM
+
+/* Leaky hangover in taps.
+ */
+#define Thold (60 * WIDEB * 8)
+
+// Adrian soft decision DTD
+// left point. X is ratio, Y is stepsize
+#define STEPX1 1.0
+#define STEPY1 1.0
+// right point. STEPX2=2.0 is good double talk, 3.0 is good single talk.
+#define STEPX2 2.5
+#define STEPY2 0
+#define ALPHAFAST (1.0f / 100.0f)
+#define ALPHASLOW (1.0f / 20000.0f)
+
+
+
+/* Ageing multiplier for LMS memory vector w */
+#define Leaky 0.9999f
+
+/* Double Talk Detector Speaker/Microphone Threshold. Range <=1
+ * Large value (M0dB) is good for Single-Talk Echo cancellation,
+ * small value (M12dB) is good for Double-Talk AEC */
+#define GeigelThreshold M6dB
+
+/* for Non Linear Processor. Range >0 to 1. Large value (M0dB) is good
+ * for Double-Talk, small value (M12dB) is good for Single-Talk */
+#define NLPAttenuation M12dB
+
+/* Below this line there are no more design constants */
+
+typedef struct IIR_HP IIR_HP;
+
+/* Exponential Smoothing or IIR Infinite Impulse Response Filter */
+struct IIR_HP {
+ REAL x;
+};
+
+typedef struct FIR_HP_300Hz FIR_HP_300Hz;
+
+typedef struct IIR1 IIR1;
+
+/* Recursive single pole IIR Infinite Impulse response High-pass filter
+ *
+ * Reference: The Scientist and Engineer's Guide to Digital Processing
+ *
+ * output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1]
+ *
+ * X = exp(-2.0 * pi * Fc)
+ * A0 = (1 + X) / 2
+ * A1 = -(1 + X) / 2
+ * B1 = X
+ * Fc = cutoff freq / sample rate
+ */
+struct IIR1 {
+ REAL in0, out0;
+ REAL a0, a1, b1;
+};
+
+#if 0
+ IIR1() {
+ memset(this, 0, sizeof(IIR1));
+ }
+#endif
+
+
+#if 0
+/* Recursive two pole IIR Infinite Impulse Response filter
+ * Coefficients calculated with
+ * http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html
+ */
+class IIR2 {
+ REAL x[2], y[2];
+
+public:
+ IIR2() {
+ memset(this, 0, sizeof(IIR2));
+ }
+
+ REAL highpass(REAL in) {
+ // Butterworth IIR filter, Filter type: HP
+ // Passband: 2000 - 4000.0 Hz, Order: 2
+ const REAL a[] = { 0.29289323f, -0.58578646f, 0.29289323f };
+ const REAL b[] = { 1.3007072E-16f, 0.17157288f };
+ REAL out =
+ a[0] * in + a[1] * x[0] + a[2] * x[1] - b[0] * y[0] - b[1] * y[1];
+
+ x[1] = x[0];
+ x[0] = in;
+ y[1] = y[0];
+ y[0] = out;
+ return out;
+ }
+};
+#endif
+
+
+// Extension in taps to reduce mem copies
+#define NLMS_EXT (10*8)
+
+// block size in taps to optimize DTD calculation
+#define DTD_LEN 16
+
+typedef struct AEC AEC;
+
+struct AEC {
+ // Time domain Filters
+ IIR_HP *acMic, *acSpk; // DC-level remove Highpass)
+ FIR_HP_300Hz *cutoff; // 150Hz cut-off Highpass
+ REAL gain; // Mic signal amplify
+ IIR1 *Fx, *Fe; // pre-whitening Highpass for x, e
+
+ // Adrian soft decision DTD (Double Talk Detector)
+ REAL dfast, xfast;
+ REAL dslow, xslow;
+
+ // NLMS-pw
+ REAL x[NLMS_LEN + NLMS_EXT]; // tap delayed loudspeaker signal
+ REAL xf[NLMS_LEN + NLMS_EXT]; // pre-whitening tap delayed signal
+ REAL w_arr[NLMS_LEN + (16 / sizeof(REAL))]; // tap weights
+ REAL *w; // this will be a 16-byte aligned pointer into w_arr
+ int j; // optimize: less memory copies
+ double dotp_xf_xf; // double to avoid loss of precision
+ float delta; // noise floor to stabilize NLMS
+
+ // AES
+ float aes_y2; // not in use!
+
+ // w vector visualization
+ REAL ws[DUMP_LEN]; // tap weights sums
+ int fdwdisplay; // TCP file descriptor
+ int dumpcnt; // wdisplay output counter
+
+ // variables are public for visualization
+ int hangover;
+ float stepsize;
+
+ // vfuncs that are picked based on processor features available
+ REAL (*dotp) (REAL[], REAL[]);
+};
+
+AEC* AEC_init(int RATE, int have_vector);
+void AEC_done(AEC *a);
+
+/* Acoustic Echo Cancellation and Suppression of one sample
+ * in d: microphone signal with echo
+ * in x: loudspeaker signal
+ * return: echo cancelled microphone signal
+ */
+ int AEC_doAEC(AEC *a, int d_, int x_);
+
+PA_GCC_UNUSED static float AEC_getambient(AEC *a) {
+ return a->dfast;
+ }
+PA_GCC_UNUSED static void AEC_setgain(AEC *a, float gain_) {
+ a->gain = gain_;
+ }
+#if 0
+ void AEC_openwdisplay(AEC *a);
+#endif
+PA_GCC_UNUSED static void AEC_setaes(AEC *a, float aes_y2_) {
+ a->aes_y2 = aes_y2_;
+ }
+
+#define _AEC_H
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <pulse/xmalloc.h>
+#include <pulse/sample.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <assert.h>
+
+#include "adrian-aec.h"
+
+struct ec_adrian {
+ int blocksize;
+ AEC *aec;
+};
+
+void *adrian_create(size_t nframes, pa_sample_spec *ss) {
+ struct ec_adrian *adrian = NULL;
+
+ pa_assert(ss);
+
+ if (ss->channels > 2 || ss->format != PA_SAMPLE_S16LE) {
+ pa_log_error("Invalid channels(%d) or format(%d)", ss->channels, ss->format);
+ return NULL;
+ }
+
+ adrian = pa_xnew0(struct ec_adrian, 1);
+
+ if (!(adrian->aec = AEC_init(ss->rate, 0))) {
+ pa_log_error("Failed to init AEC");
+ goto fail;
+ }
+ adrian->blocksize = nframes * ss->channels * 2; /* format */
+
+ return adrian;
+
+fail:
+ pa_xfree(adrian);
+
+ return NULL;
+}
+
+int32_t adrian_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) {
+ struct ec_adrian *adrian = priv;
+ int i;
+
+ assert(rec);
+ assert(ref);
+ assert(out);
+
+ for (i=0; i<adrian->blocksize; i+=2) {
+ int r = *(int16_t *)(rec + i);
+ int p = *(int16_t *)(ref + i);
+ *(int16_t *)(out + i) = (int16_t) AEC_doAEC(adrian->aec, r, p);
+ }
+
+ return 0;
+}
+
+int32_t adrian_destroy(void *priv) {
+ struct ec_adrian *adrian = priv;
+
+ pa_assert(adrian);
+
+ AEC_done(adrian->aec);
+
+ pa_xfree(adrian);
+
+ return 0;
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <pulse/xmalloc.h>
+#include <pulse/sample.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include <speex/speex.h>
+#include <speex/speex_preprocess.h>
+#include <speex/speex_echo.h>
+
+#include <assert.h>
+
+struct algo_speex {
+ SpeexEchoState *echo_state;
+ SpeexPreprocessState *preprocess;
+};
+
+void *speex_create(size_t nframes, pa_sample_spec *ss) {
+ struct algo_speex *speex = NULL;
+ spx_int32_t value = 1;
+ int rate;
+
+ pa_assert(ss);
+
+ if (ss->channels > 2 || ss->format != PA_SAMPLE_S16LE) {
+ pa_log_error("Invalid channels(%d) or format(%d)", ss->channels, ss->format);
+ return NULL;
+ }
+
+ speex = pa_xnew0(struct algo_speex, 1);
+
+ /* TODO: need to check. weird behavior */
+ if (ss->channels == 2)
+ nframes *= 2;
+
+ speex->echo_state = speex_echo_state_init(nframes, nframes * 10);
+
+ if (!speex->echo_state) {
+ pa_log_error("_echo_state_init_mc failed");
+ goto fail;
+ }
+
+ rate = ss->rate;
+ if (!(speex->preprocess = speex_preprocess_state_init(nframes, rate))) {
+ pa_log_error("_preprocess_state_init failed");
+ goto fail;
+ }
+
+ if (speex_echo_ctl(speex->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate)) {
+ pa_log_error("_echo_ctl SET_SAMPLING_RATE failed");
+ goto fail;
+ }
+
+ if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_AGC, &value)) {
+ pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_AGC failed");
+ goto fail;
+ }
+
+ if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_DENOISE, &value)) {
+ pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_DENOISE failed");
+ goto fail;
+ }
+
+ if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_DEREVERB, &value)) {
+ pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_DEREVERB failed");
+ goto fail;
+ }
+
+ if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE, speex->echo_state)) {
+ pa_log_error("_echo_ctl SET_ECHO_STATE failed");
+ goto fail;
+ }
+
+ pa_log_info("speex echo-canceller initialized. frame(%zu), rate(%d) channels(%d)",
+ nframes, ss->rate, ss->channels);
+
+ return speex;
+
+fail:
+ pa_xfree(speex);
+
+ return NULL;
+}
+
+int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) {
+ struct algo_speex *speex = priv;
+
+ assert(rec);
+ assert(ref);
+ assert(out);
+
+ speex_echo_cancellation(speex->echo_state,
+ (const spx_int16_t *)rec,
+ (const spx_int16_t *)ref,
+ (spx_int16_t *)out);
+
+ speex_preprocess_run(speex->preprocess, (spx_int16_t *)out);
+
+ return 0;
+}
+
+int32_t speex_destroy(void *priv) {
+ struct algo_speex *speex = priv;
+
+ if (speex->echo_state)
+ speex_echo_state_destroy(speex->echo_state);
+ if (speex->preprocess)
+ speex_preprocess_state_destroy(speex->preprocess);
+
+ pa_xfree(speex);
+
+ return 0;
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+enum {
+ PA_ECHO_CANCEL_MESSAGE_PUSH_DATA,
+ PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO,
+ PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE,
+ PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK,
+};
+
+enum {
+ PA_SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX,
+ PA_SINK_MESSAGE_REBUILD_RTPOLL,
+ PA_SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX,
+ PA_SOURCE_MESSAGE_REBUILD_RTPOLL,
+};
+
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/poll.h>
+#include <pulsecore/namereg.h>
+#include <pulse/util.h>
+#include <pulse/timeval.h>
+
+#include "echo-cancel-def.h"
+#include "processor.h"
+
+PA_MODULE_AUTHOR("Tizen");
+PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+PA_MODULE_USAGE(
+ "blocksize=<the bytes of processing block on source domain> ");
+
+typedef struct echo_cancel pa_echo_cancel;
+struct userdata {
+ pa_core *core;
+ pa_module *m;
+ pa_sink *sink;
+ pa_source *source;
+
+ pa_hook_slot *sink_unlink_slot;
+ pa_hook_slot *source_unlink_slot;
+
+ pa_hook_slot *source_output_new_slot;
+ pa_hook_slot *source_output_put_slot;
+ pa_hook_slot *source_output_unlink_slot;
+ pa_hook_slot *source_output_unlink_post_slot;
+ pa_hook_slot *sink_state_changed_slot;
+
+ bool enable;
+ uint32_t n_source_output;
+ size_t blocksize;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+ pa_echo_cancel *echo_cancel;
+
+ /* use in thread */
+ pa_memblockq *delayq;
+ bool enable_in_thread;
+
+ pa_asyncmsgq *asyncmsgq_sink;
+ pa_asyncmsgq *asyncmsgq_source;
+};
+
+struct echo_cancel {
+ pa_msgobject parent;
+ struct userdata *u;
+};
+
+PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject);
+#define PA_ECHO_CANCEL(o) (pa_echo_cancel_cast(o))
+
+#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024)
+#define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL)
+#define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output)
+
+static const char* const valid_modargs[] = {
+ "blocksize",
+ NULL,
+};
+
+static int proplist_get_fragment_size(pa_proplist *p, size_t *size) {
+ const char *fragsize;
+ uint32_t blocksize;
+
+ if (!(fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE)))
+ return -1;
+
+ if (pa_atou(fragsize, &blocksize))
+ return -1;
+
+ *size = blocksize;
+
+ return 0;
+}
+
+static pa_processor_algo_t pa_processor_get_algo(pa_source_output *o) {
+ const char *algo = pa_proplist_gets(o->proplist, "echo");
+
+ if (!algo) {
+ pa_log_warn("Use default processor(speex)");
+ return PA_PROCESSOR_SPEEX;
+ }
+
+ if (pa_streq(algo, "adrian"))
+ return PA_PROCESSOR_ADRIAN;
+ else if (pa_streq(algo, "speex"))
+ return PA_PROCESSOR_SPEEX;
+ else {
+ pa_log_warn("invalid algo(%s), Use default processor(speex)", algo);
+ return PA_PROCESSOR_SPEEX;
+ }
+}
+
+static pa_source_output *find_source_output_by_flags(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_assert(s);
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+ pa_source_output_assert_ref(o);
+
+ if (CHECK_FLAGS_AEC(o->flags))
+ break;
+ }
+
+ return o ? o : NULL;
+}
+
+static void free_source_output_extra_resource(pa_source_output *o) {
+ pa_assert(o);
+
+ if (o->thread_info.processor) {
+ pa_processor_free(o->thread_info.processor);
+ o->thread_info.processor = NULL;
+ }
+
+ if (o->thread_info.resampler2) {
+ pa_resampler_free(o->thread_info.resampler2);
+ o->thread_info.resampler2 = NULL;
+ }
+
+ if (o->thread_info.echo) {
+ pa_memblockq_free(o->thread_info.echo);
+ o->thread_info.echo = NULL;
+ }
+}
+
+static void free_source_output_extra_resource_by_source(pa_source *s) {
+ pa_assert(s);
+
+ free_source_output_extra_resource(find_source_output_by_flags(s));
+}
+
+static pa_usec_t get_round_trip_latency(struct userdata *u) {
+ pa_usec_t sink_latency;
+ pa_usec_t source_latency;
+
+ pa_assert(u);
+ pa_assert(u->sink);
+
+ sink_latency = pa_sink_get_latency(u->sink);
+ source_latency = pa_source_get_latency(u->source);
+
+ pa_log_info("sink latency (%llu), source latency(%llu)", sink_latency, source_latency);
+
+ return sink_latency + source_latency;
+}
+
+static int setup_delayq_latency(struct userdata *u, pa_usec_t latency) {
+ int64_t write_index, read_index;
+ size_t bytes, blocksize, n;
+ pa_memchunk silence;
+
+ if (proplist_get_fragment_size(u->sink->proplist, &blocksize))
+ return -1;
+
+ if (u->delayq)
+ pa_memblockq_free(u->delayq);
+
+ u->delayq = pa_memblockq_new("echo reference delay",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &u->sink->sample_spec,
+ 0,
+ blocksize,
+ 0,
+ NULL);
+
+ bytes = pa_usec_to_bytes(latency, &u->sink->sample_spec);
+ n = (bytes + blocksize - 1) / blocksize;
+
+ pa_silence_memchunk_get(
+ &u->sink->core->silence_cache,
+ u->sink->core->mempool,
+ &silence,
+ &u->sink->sample_spec,
+ blocksize);
+
+ if (!silence.memblock)
+ return -1;
+
+ write_index = pa_memblockq_get_write_index(u->delayq);
+ read_index = pa_memblockq_get_read_index(u->delayq);
+
+ pa_memblockq_flush_write(u->delayq, true);
+ while (n-- > 0)
+ pa_memblockq_push(u->delayq, &silence);
+
+ pa_log_info("push n(%d) blocks. write_index(%llu->%llu), read_index(%llu->%llu)",
+ pa_memblockq_get_nblocks(u->delayq),
+ write_index, pa_memblockq_get_write_index(u->delayq),
+ read_index, pa_memblockq_get_read_index(u->delayq));
+
+ pa_memblock_unref(silence.memblock);
+
+ return 0;
+}
+
+static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsgq *q) {
+ struct arguments {
+ pa_msgobject *o;
+ pa_asyncmsgq *q;
+ } args;
+ pa_asyncmsgq *asyncmsgq;
+ int code;
+
+ pa_assert(dst);
+
+ args.o = src;
+ args.q = q;
+
+ if (pa_sink_isinstance(dst)) {
+ asyncmsgq = PA_SINK(dst)->asyncmsgq;
+ code = PA_SINK_MESSAGE_REBUILD_RTPOLL;
+ } else if (pa_source_isinstance(dst)) {
+ asyncmsgq = PA_SOURCE(dst)->asyncmsgq;
+ code = PA_SOURCE_MESSAGE_REBUILD_RTPOLL;
+ } else {
+ pa_assert_not_reached();
+ }
+
+ pa_asyncmsgq_send(asyncmsgq, dst, code, src ? (void *)&args : NULL, 0, NULL);
+
+ return 0;
+}
+
+/* Call from main thread */
+static void set_aec_state(struct userdata *u, bool enable) {
+ void *v[2];
+ pa_usec_t latency = 0ULL;
+
+ pa_assert(u);
+ pa_assert(u->source);
+ pa_assert(u->sink); /* not allow sink null */
+
+ pa_log_info("set_aec state %d -> %d", u->enable, enable);
+
+ if (u->enable == enable)
+ return;
+
+ latency = enable ? get_round_trip_latency(u) : 0ULL;
+
+ v[0] = (void *)enable;
+ v[1] = &latency;
+
+ /* There is a race condition between the source thread and the render thread.
+ * pa_source_post function can be overlapped at the same time */
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->echo_cancel),
+ PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, (void *)v, 0, NULL);
+
+ if (u->sink)
+ pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink),
+ PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL);
+
+ pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source),
+ PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL);
+
+ u->enable = enable;
+
+ pa_log_info("AEC state updated. enable(%d)", u->enable);
+}
+
+static int update_state_by_sink(struct userdata *u, bool enable) {
+ pa_assert(u);
+
+ if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) == 0)
+ return 0;
+
+ set_aec_state(u, enable);
+
+ return 0;
+}
+
+static int update_state_by_source(struct userdata *u, bool enable) {
+ pa_assert(u);
+
+ if (enable) {
+ if (u->n_source_output++ == 0) {
+ if (!u->sink || PA_SINK_IS_RUNNING(u->sink->state))
+ set_aec_state(u, enable);
+ }
+ } else {
+ if (--u->n_source_output == 0)
+ set_aec_state(u, enable);
+ }
+
+ return 0;
+}
+
+static int unlink_source_output_in_thread(pa_source_output *o) {
+ pa_assert(o);
+
+ free_source_output_extra_resource(o);
+
+ /* source-output should be remove by render thread to prevent race condition.
+ * 1. invoke REMOVE message
+ * 2. receive the message in tizenaudio-source
+ * 3. send the message to tizenaudio-echo-cancel
+ * 4. remove source-output in render thread
+ */
+ pa_source_process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+
+ return 0;
+}
+
+static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) {
+ pa_source_output *o = NULL;
+ pa_memchunk ochunk;
+ bool nf = false;
+ int r;
+
+ o = find_source_output_by_flags(s);
+ if (!o) {
+ pa_log_error("Can't find aec source-output");
+ return;
+ }
+
+ if (o->thread_info.resampler2) {
+ pa_resampler_run(o->thread_info.resampler2, chunk, &ochunk);
+ chunk = &ochunk;
+ nf = true;
+ }
+
+ r = pa_memblockq_push(o->thread_info.echo, chunk);
+ if (r != 0)
+ pa_log_error("Failed to push chunk to memblockq");
+
+ if (nf)
+ pa_memblock_unref(chunk->memblock);
+
+ pa_log_debug("Pushed echo data. index(%u) size(%llums), nblocks(%d) index(%lld:%lld)",
+ o->index,
+ pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC,
+ pa_memblockq_get_nblocks(o->thread_info.echo),
+ pa_memblockq_get_write_index(o->thread_info.echo),
+ pa_memblockq_get_read_index(o->thread_info.echo));
+}
+
+static void flush_echo_memblockq(pa_source *s) {
+ pa_source_output *o;
+
+ o = find_source_output_by_flags(s);
+ if (!o) {
+ pa_log_error("Can't find aec source-output");
+ return;
+ }
+
+ pa_memblockq_flush_write(o->thread_info.echo, true);
+}
+
+static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) {
+ int ret = -1;
+ int8_t *rec, *ref, *out;
+ size_t blocksize;
+
+ pa_memchunk tchunk;
+
+ if (!o->thread_info.processor) {
+ pa_log_error("Failed to get processor");
+ return ret;
+ }
+
+ /*
+ * pre-condition
+ * sink block >= source block >= blocksize * n
+ * if blocksize is not described blocksize, It should be same as source's fragment size
+ *
+ * chunk must be processed every data.
+ */
+
+ /* reference exist */
+ if (o->thread_info.echo) {
+ size_t block_bytes, length, n;
+
+ /* echo queue is not ready that means reference is not started */
+ if (pa_memblockq_is_empty(o->thread_info.echo))
+ return ret;
+
+ blocksize = pa_processor_get_blocksize(o->thread_info.processor);
+ block_bytes = blocksize * pa_frame_size(&o->sample_spec);
+
+ if (chunk->length % block_bytes) {
+ pa_log_warn("Skip to process aec. chunk size must be multiple of blocksize");
+ return -1;
+ }
+
+ n = chunk->length / block_bytes;
+ length = n * block_bytes;
+
+ if (!(ret = pa_memblockq_peek_fixed_size(o->thread_info.echo, length, &tchunk))) {
+ int i;
+
+ ochunk->index = 0;
+ ochunk->length = length;
+ ochunk->memblock = pa_memblock_new(o->core->mempool, length);
+
+ rec = pa_memblock_acquire(chunk->memblock);
+ ref = pa_memblock_acquire(tchunk.memblock);
+ out = pa_memblock_acquire(ochunk->memblock); /* TODO: buffer can be shared rec buffer */
+
+ for (i=0; i<n; i++)
+ pa_processor_process(o->thread_info.processor,
+ rec + (i * block_bytes),
+ ref + (i * block_bytes),
+ out + (i * block_bytes));
+
+ pa_memblock_release(chunk->memblock);
+ pa_memblock_release(tchunk.memblock);
+ pa_memblock_release(ochunk->memblock);
+
+ pa_log_debug("Post-process. i(%u), rec(%llums), ref(%llums) "
+ "block(%llums), process(%llums) * n(%d) "
+ "silence(%d), index(%lld:%lld)",
+ o->index,
+ pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC,
+ pa_bytes_to_usec(tchunk.length, &o->sample_spec) / PA_USEC_PER_MSEC,
+ pa_bytes_to_usec(length, &o->sample_spec) / PA_USEC_PER_MSEC,
+ pa_bytes_to_usec(block_bytes, &o->sample_spec) / PA_USEC_PER_MSEC,
+ n,
+ pa_memblock_is_silence(tchunk.memblock),
+ pa_memblockq_get_write_index(o->thread_info.echo),
+ pa_memblockq_get_read_index(o->thread_info.echo));
+
+ pa_memblock_unref(tchunk.memblock);
+ pa_memblockq_drop(o->thread_info.echo, tchunk.length);
+ }
+ } else {
+ /* no reference case like audio_share */
+ rec = pa_memblock_acquire(chunk->memblock);
+
+ ochunk->index = 0;
+ ochunk->length = chunk->length;
+ ochunk->memblock = pa_memblock_new(o->core->mempool, chunk->length);
+ out = pa_memblock_acquire(ochunk->memblock);
+
+ pa_processor_process(o->thread_info.processor, rec, NULL, out);
+
+ pa_memblock_release(chunk->memblock);
+ pa_memblock_release(ochunk->memblock);
+ }
+
+ return ret;
+}
+
+/* rendering thread is separated because ec/ns takes much time in I/O thread */
+static int process_msg(
+ pa_msgobject *o,
+ int code,
+ void *data,
+ int64_t offset,
+ pa_memchunk *chunk) {
+
+ struct userdata *u = PA_ECHO_CANCEL(o)->u;
+
+ /* thread that pushes ref data should be called in render thread because of thread safe */
+ switch (code) {
+ case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: {
+ /* a few pcm data will get lost. */
+ if (!u->enable_in_thread)
+ return 0;
+
+ pa_source_post(u->source, chunk);
+
+ return 0;
+ }
+ case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: {
+ pa_memchunk ochunk;
+
+ if (!u->enable_in_thread)
+ return 0;
+
+ pa_memblockq_push(u->delayq, chunk);
+
+ if (!pa_memblockq_peek(u->delayq, &ochunk)) {
+ pa_source_push_echo(u->source, &ochunk);
+
+ pa_memblock_unref(ochunk.memblock);
+ pa_memblockq_drop(u->delayq, ochunk.length);
+ }
+
+ return 0;
+ }
+ case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE : {
+ void **v = (void **)data;
+ pa_usec_t latency;
+
+ u->enable_in_thread = !!(int)v[0];
+ latency = *(pa_usec_t *)v[1];
+
+ if (u->enable_in_thread) {
+ if (setup_delayq_latency(u, latency)) {
+ pa_log_error("Failed to init delayq");
+ return 0;
+ }
+ } else {
+ if (u->delayq) {
+ pa_memblockq_free(u->delayq);
+ u->delayq = NULL;
+ }
+
+ flush_echo_memblockq(u->source);
+ }
+
+ pa_log_info("EC state change (%d)", u->enable_in_thread);
+
+ return 0;
+ }
+ case PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK:
+ unlink_source_output_in_thread((pa_source_output *)data);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+ const char *echo = pa_proplist_gets(data->proplist, "echo");
+
+ pa_assert(c);
+ pa_assert(data);
+ pa_assert(u);
+
+ if (!echo)
+ return PA_HOOK_OK;
+
+ if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) {
+ pa_log_error("Not allow multi aec instance");
+ return PA_HOOK_OK;
+ }
+
+ data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL;
+ data->flags |= PA_SOURCE_OUTPUT_DONT_MOVE;
+
+ // TODO:add check limitation VARIOUS_RATE?
+ return PA_HOOK_OK;
+}
+
+static int find_reference_sink(struct userdata *u, pa_source_output *o) {
+ const char *sink_name;
+
+ pa_assert(u);
+ pa_assert(o);
+
+ sink_name = pa_proplist_gets(o->proplist, "reference_sink");
+ if (!sink_name)
+ return -1;
+
+ u->sink = pa_namereg_get(u->core, sink_name, PA_NAMEREG_SINK);
+ if (!u->sink)
+ return -1;
+
+ pa_log_debug("Requested AEC source(%s), sink(%s)",
+ u->source->name, u->sink ? u->sink->name : "");
+
+ return 0;
+}
+
+static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) {
+ pa_usec_t sink_usec;
+ pa_usec_t source_usec;
+ pa_usec_t block_usec;
+ size_t blocksize;
+
+ if (proplist_get_fragment_size(source->proplist, &blocksize)) {
+ pa_log_debug("Failed to get blocksize from source");
+ return -1;
+ }
+
+ source_usec = pa_bytes_to_usec(blocksize, &source->sample_spec);
+ block_usec = u->blocksize ? pa_bytes_to_usec(u->blocksize, &source->sample_spec) : source_usec;
+
+ /*
+ * limitation
+ * sink block >= source block >= blocksize * n
+ */
+ if (source_usec < block_usec) {
+ pa_log_debug("Need to check period size. source >= block * n. "
+ "source(%llums), block_usec(%llums)",
+ source_usec / PA_USEC_PER_MSEC,
+ block_usec / PA_USEC_PER_MSEC);
+ return -1;
+ }
+
+ if (!u->sink)
+ return 0;
+
+ if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) {
+ pa_log_debug("Failed to get blocksize from sink");
+ return -1;
+ }
+
+ sink_usec = pa_bytes_to_usec(blocksize, &u->sink->sample_spec);
+
+ if (source_usec > sink_usec || sink_usec < block_usec) {
+ pa_log_debug("Need to check period size. sink >= source >= block * n. "
+ "source(%llums) sink(%llums) block_usec(%llums)",
+ source_usec / PA_USEC_PER_MSEC,
+ sink_usec / PA_USEC_PER_MSEC,
+ block_usec / PA_USEC_PER_MSEC);
+ return -1;
+ }
+
+ return 0;
+}
+
+static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+ size_t blocksize = u->blocksize;
+ pa_processor_algo_t backend;
+
+ pa_assert(c);
+ pa_assert(o);
+ pa_assert(u);
+ pa_assert(o->source);
+
+ if (!CHECK_FLAGS_AEC(o->flags))
+ return PA_HOOK_OK;
+
+ if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) {
+ pa_log_error("Not allow multi aec instance");
+ goto fail;
+ }
+
+ u->source = o->source;
+ if (find_reference_sink(u, o)) {
+ pa_log_error("Can't find reference sink for AEC");
+ goto fail;
+ }
+
+ if (check_latency_validation(u, u->sink, u->source)) {
+ pa_log_error("Failed to check latency validation");
+ goto fail;
+ }
+
+ /* Use the sources fragment size if blocksize is not specified */
+ if (!blocksize) {
+ if (proplist_get_fragment_size(u->source->proplist, &blocksize)) {
+ pa_log_error("Failed to get blocksize");
+ goto fail;
+ }
+ }
+
+ if (o->thread_info.resampler)
+ blocksize = pa_resampler_result(o->thread_info.resampler, blocksize);
+
+ backend = pa_processor_get_algo(o);
+ o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec),
+ &o->sample_spec,
+ backend, PA_PROCESSOR_FLAGS_ECHO_CANCEL);
+ if (!o->thread_info.processor) {
+ pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled");
+ goto fail;
+ }
+
+ if (u->sink) {
+ if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) {
+ pa_log_error("Failed to get blocksize");
+ goto fail;
+ }
+
+ if (!pa_sample_spec_equal(&u->sink->sample_spec, &o->sample_spec)) {
+ pa_resampler *resampler2;
+
+ resampler2 = pa_resampler_new(
+ c->mempool,
+ &u->sink->sample_spec, &u->sink->channel_map,
+ &o->sample_spec, &o->channel_map,
+ c->lfe_crossover_freq,
+ c->resample_method, 0);
+
+ if (!resampler2) {
+ pa_log_error("Failed to allocate resampler2 for echo-cancel");
+ goto fail;
+ }
+
+ o->thread_info.resampler2 = resampler2;
+ blocksize = pa_resampler_result(o->thread_info.resampler2, blocksize);
+
+ pa_log_info("Use resampler2. blocksize(%d) bytes", blocksize);
+ }
+
+ o->thread_info.echo = pa_memblockq_new("echo reference",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &o->sample_spec,
+ 0,
+ blocksize,
+ 0,
+ &o->source->silence);
+
+ if (!o->thread_info.echo) {
+ pa_log_error("Failed to alloc memblockq");
+ goto fail;
+ }
+ }
+
+ o->post_process = post_process;
+
+ /* connect to sink and source */
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source);
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink);
+
+ update_state_by_source(u, true);
+
+ return PA_HOOK_OK;
+
+fail:
+ o->flags &= ~PA_SOURCE_OUTPUT_ECHO_CANCEL; // TODO: need to consider DONT_MOVE define
+ free_source_output_extra_resource(o);
+
+ return PA_HOOK_OK;
+}
+
+/* Call from main thread */
+static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+
+ pa_assert(c);
+ pa_assert(o);
+ pa_assert(u);
+
+ if (!CHECK_FLAGS_AEC(o->flags))
+ return PA_HOOK_OK;
+
+ update_state_by_source(u, false);
+
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL);
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL);
+
+ u->source = NULL;
+ u->sink = NULL;
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+
+ pa_assert(c);
+ pa_assert(s);
+ pa_assert(u);
+
+ if (s != u->sink)
+ return PA_HOOK_OK;
+
+ if (s->state == PA_SINK_RUNNING)
+ update_state_by_sink(u, true);
+ else if (s->state == PA_SINK_SUSPENDED || s->state == PA_SINK_IDLE)
+ update_state_by_sink(u, false);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+
+ pa_assert(u);
+
+ if (!u->source || u->source != source)
+ return PA_HOOK_OK;
+
+ pa_log_warn("echo-cancel source is unlinked during processing.");
+
+ update_state_by_source(u, false);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_unlink_cb(pa_core *core, pa_sink *sink, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+
+ pa_assert(u);
+
+ if (!u->sink || u->sink != sink)
+ return PA_HOOK_OK;
+
+ pa_log_warn("echo-cancel sink is unlinked during processing.");
+
+ update_state_by_sink(u, false);
+
+ return PA_HOOK_OK;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_thread_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+
+ for (;;) {
+ int ret;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
+ goto fail;
+ }
+
+fail:
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core),
+ PA_CORE_MESSAGE_UNLOAD_MODULE, u->m, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module *m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ uint32_t blocksize = 0;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log_error("Failed to parse module arguments.");
+ return -1;
+ }
+ pa_modargs_get_value_u32(ma, "blocksize", &blocksize);
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->m = m;
+ u->blocksize = blocksize;
+
+ u->echo_cancel = pa_msgobject_new(pa_echo_cancel);
+ u->echo_cancel->parent.process_msg = process_msg;
+ u->echo_cancel->u = u;
+
+ u->rtpoll = pa_rtpoll_new();
+ u->asyncmsgq_source = pa_asyncmsgq_new(0);
+ u->asyncmsgq_sink = pa_asyncmsgq_new(0);
+
+ pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY, u->asyncmsgq_sink);
+ pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY, u->asyncmsgq_source);
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+
+ if (!(u->thread = pa_thread_new("tizenaudio-echo-cancel", thread_func, u))) {
+ pa_log_error("Failed to create thread.");
+ goto fail;
+ }
+
+ u->sink_unlink_slot =
+ pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_UNLINK],
+ PA_HOOK_NORMAL, (pa_hook_cb_t) sink_unlink_cb, u);
+ u->source_unlink_slot =
+ pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK],
+ PA_HOOK_NORMAL, (pa_hook_cb_t) source_unlink_cb, u);
+
+ u->source_output_put_slot =
+ pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT],
+ PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, u);
+
+ u->source_output_unlink_post_slot =
+ pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST],
+ PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u);
+
+ u->source_output_new_slot =
+ pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW],
+ PA_HOOK_LATE, (pa_hook_cb_t) source_output_new_cb, u);
+
+ u->sink_state_changed_slot =
+ pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED],
+ PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u);
+
+ /* TODO : need to check sink configuration change */
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->source_output_put_slot)
+ pa_hook_slot_free(u->source_output_put_slot);
+
+ if (u->source_output_unlink_slot)
+ pa_hook_slot_free(u->source_output_unlink_slot);
+
+ if (u->source_output_unlink_post_slot)
+ pa_hook_slot_free(u->source_output_unlink_post_slot);
+
+ if (u->source_output_new_slot)
+ pa_hook_slot_free(u->source_output_new_slot);
+
+ if (u->sink_state_changed_slot)
+ pa_hook_slot_free(u->sink_state_changed_slot);
+
+ if (u->asyncmsgq_sink) {
+ if (u->sink) {
+ pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink),
+ PA_SINK_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL);
+
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL);
+ }
+
+ pa_asyncmsgq_unref(u->asyncmsgq_sink);
+ }
+
+ if (u->asyncmsgq_source) {
+ if (u->source) {
+ pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source),
+ PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL);
+
+ send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL);
+ free_source_output_extra_resource_by_source(u->source);
+ }
+
+ pa_asyncmsgq_unref(u->asyncmsgq_source);
+ }
+
+ if (u->delayq)
+ pa_memblockq_free(u->delayq);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ pa_xfree(u);
+}
+
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "processor.h"
+
+#ifdef __DEBUG__
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+struct pa_processor_algo {
+ void *(*create)(size_t nframes, pa_sample_spec *ss);
+ int32_t (*process)(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+ int32_t (*destroy)(void *priv);
+};
+
+extern void *adrian_create(size_t nframes, pa_sample_spec *ss);
+extern int32_t adrian_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+extern int32_t adrian_destroy(void *priv);
+
+extern void *speex_create(size_t nframes, pa_sample_spec *ss);
+extern int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+extern int32_t speex_destroy(void *priv);
+
+pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags) {
+ pa_processor *processor = NULL;
+
+ pa_assert(ss);
+
+ if (ss->format != PA_SAMPLE_S16LE) {
+ pa_log_error("Not supported format(%d)", ss->format);
+ return NULL;
+ }
+
+ processor = pa_xnew0(pa_processor, 1);
+ processor->intf = pa_xnew0(pa_processor_algo, 1);
+ processor->nframes = nframes;
+ processor->framesize = pa_frame_size(ss);
+
+ switch (backend) {
+ case PA_PROCESSOR_SPEEX:
+ processor->intf->create = speex_create;
+ processor->intf->process = speex_process;
+ processor->intf->destroy = speex_destroy;
+ break;
+ case PA_PROCESSOR_ADRIAN:
+ processor->intf->create = adrian_create;
+ processor->intf->process = adrian_process;
+ processor->intf->destroy = adrian_destroy;
+ break;
+ default:
+ pa_log_error("Invalid backend(%d)", backend);
+ goto fail;
+ }
+
+ pa_log_info("Use backend(%d) nframes(%zu) framesize(%d)",
+ backend, processor->nframes, processor->framesize);
+
+ if (!(processor->priv = processor->intf->create(nframes, ss))) {
+ pa_log_error("Failed to create processor");
+ goto fail;
+ }
+
+#ifdef __DEBUG__
+ {
+ static int n = 1;
+ char rec[32], ref[32], out[32];
+
+ snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n);
+ snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n);
+ snprintf(out, sizeof(out), "/tmp/out-%d.raw", n);
+ n += 1;
+
+ unlink(rec);
+ unlink(ref);
+ unlink(out);
+
+ processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777);
+ processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777);
+ processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777);
+ }
+#endif
+
+ return processor;
+
+fail:
+ pa_xfree(processor->intf);
+ pa_xfree(processor);
+
+ return NULL;
+}
+
+int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) {
+ int ret = -1;
+
+ pa_assert(processor);
+ pa_assert(processor->intf);
+ pa_assert(rec);
+ pa_assert(out);
+
+#ifdef __DEBUG__
+ if (write(processor->fdrec, rec, processor->nframes * processor->framesize) <= 0)
+ pa_log_error("Failed to write rec buffer");
+
+ if (write(processor->fdref, ref, processor->nframes * processor->framesize) <= 0)
+ pa_log_error("Failed to write ref buffer");
+
+ gettimeofday(&processor->before, NULL);
+#endif
+
+ if (processor->intf->process)
+ ret = processor->intf->process(processor->priv, rec, ref, out);
+
+#ifdef __DEBUG__
+ if (write(processor->fdout, out, processor->nframes * processor->framesize) <= 0)
+ pa_log_error("Failed to write out buffer");
+
+ gettimeofday(&processor->after, NULL);
+
+ pa_log_debug("It takes time(%ld) bytes(%d)",
+ 1000 * (after.tv_sec-before.tv_sec) + (after.tv_usec-before.tv_usec) / 1000,
+ processor->buffer_size);
+#endif
+
+ return ret;
+}
+
+int pa_processor_free(pa_processor *processor) {
+ pa_assert(processor);
+ pa_assert(processor->intf);
+ pa_assert(processor->intf->destroy);
+
+#ifdef __DEBUG__
+ if (processor->fdrec)
+ close(processor->fdrec);
+ if (processor->fdref)
+ close(processor->fdref);
+ if (processor->fdout)
+ close(processor->fdout);
+#endif
+
+ if (processor->intf->destroy(processor->priv)) {
+ pa_log_error("Failed to destroy processor");
+ return -1;
+ }
+
+ pa_xfree(processor->intf);
+ pa_xfree(processor);
+
+ return 0;
+}
+
+size_t pa_processor_get_blocksize(pa_processor *processor) {
+ pa_assert(processor);
+
+ return processor->nframes;
+}
--- /dev/null
+#ifndef foopulseprocessorfoo
+#define foopulseprocessorfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/sample.h>
+
+//#define __DEBUG__
+
+typedef enum {
+ PA_PROCESSOR_FLAGS_ECHO_CANCEL,
+ PA_PROCESSOR_FLAGS_NOISE_SUPPRESSION,
+} pa_process_flags_t;
+
+typedef enum {
+ PA_PROCESSOR_SPEEX,
+ PA_PROCESSOR_ADRIAN,
+} pa_processor_algo_t;
+
+typedef struct pa_processor_algo pa_processor_algo;
+typedef struct pa_processor pa_processor;
+
+struct pa_processor {
+ pa_processor_algo *intf;
+ void *priv;
+ void *userdata;
+ size_t nframes;
+ size_t framesize;
+
+#ifdef __DEBUG__
+ int fdrec, fdref, fdout;
+ struct timeval before, after;
+#endif
+};
+
+pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags);
+int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out);
+int pa_processor_free(pa_processor *processor);
+size_t pa_processor_get_blocksize(pa_processor *processor);
+
+#endif
#include <pulsecore/poll.h>
#include "hal-interface.h"
+#include "echo-cancel/echo-cancel-def.h"
PA_MODULE_AUTHOR("Tizen");
PA_MODULE_DESCRIPTION("Tizen Audio Sink2");
#define DEFAULT_SINK_NAME "tizenaudio-sink2"
#define DEVICE_NAME_MAX 30
+#define DEFAULT_FRAGMENT_MSEC 20
+#define DEFAULT_FRAGMENTS 4
struct userdata {
pa_core *core;
char* card;
char* device;
bool first;
+ bool echo_on;
pa_rtpoll_item *rtpoll_item;
uint64_t write_count;
pa_hal_interface *hal_interface;
+
+ pa_msgobject *ec_object;
+ pa_asyncmsgq *ec_asyncmsgq;
+ pa_rtpoll_item *ec_poll_item;
};
static const char* const valid_modargs[] = {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
+ case PA_SINK_MESSAGE_SET_AEC_STATE: {
+ u->echo_on = !!data;
+ pa_log_info("EC state changed (%d)", u->echo_on);
+ return 0;
+ }
case PA_SINK_MESSAGE_GET_LATENCY: {
int64_t r = 0;
return 0;
}
+ case PA_SINK_MESSAGE_REBUILD_RTPOLL: {
+ struct arguments {
+ pa_msgobject *o;
+ pa_asyncmsgq *q;
+ } *args;
+
+ args = (struct arguments *)data;
+
+ if (args) {
+ u->ec_object = args->o;
+ u->ec_asyncmsgq = args->q;
+ u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q);
+ } else {
+ pa_rtpoll_item_free(u->ec_poll_item);
+ u->ec_poll_item = NULL;
+ u->ec_object = NULL;
+ }
+
+ return 0;
+ }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
pa_assert(u);
pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail);
- if (frames_to_write > avail) {
- pa_log_debug("not enough avail size. frames_to_write(%zu), avail(%d)", frames_to_write, avail);
+
+ if (frames_to_write > avail)
return 0;
- }
pa_sink_render_full(u->sink, u->frag_size, &chunk);
p = pa_memblock_acquire(chunk.memblock);
}
pa_memblock_release(chunk.memblock);
+
+ if (u->echo_on) {
+ pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object,
+ PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL);
+ }
+
pa_memblock_unref(chunk.memblock);
u->write_count += chunk.length;
pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents);
u->first = true;
revents = 0;
- } else {
- //pa_log_debug("Poll wakeup.", revents);
}
}
}
}
fail:
- /* If this was no regular exit from the loop we have to continue
- * processing messages until we received PA_MESSAGE_SHUTDOWN */
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
u->card = pa_xstrdup(card);
u->device = pa_xstrdup(device);
- u->frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
- u->nfrags = m->core->default_n_fragments;
+ u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss);
+ u->nfrags = DEFAULT_FRAGMENTS;
if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 ||
pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) {
pa_log_error("fragment_size or fragments are invalid.");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract");
pa_proplist_sets(data.proplist, "tizen.card", u->card);
pa_proplist_sets(data.proplist, "tizen.device", u->device);
+ pa_proplist_sets(data.proplist, "tizen.version", "2");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen");
frame_size = pa_frame_size(&ss);
pa_sink_set_max_request(u->sink, buffer_size);
pa_sink_set_max_rewind(u->sink, buffer_size);
- if (!(u->thread = pa_thread_new("tizenaudio-sink", thread_func, u))) {
+ if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) {
pa_log_error("Failed to create thread.");
goto fail;
}
#include <pulsecore/poll.h>
#include "hal-interface.h"
+#include "echo-cancel/echo-cancel-def.h"
PA_MODULE_AUTHOR("Tizen");
-PA_MODULE_DESCRIPTION("Tizen Audio Source");
+PA_MODULE_DESCRIPTION("Tizen Audio Source2");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(false);
PA_MODULE_USAGE(
"fragments=<number of fragments> "
"fragment_size=<fragment size> ");
-
#define DEFAULT_SOURCE_NAME "tizenaudio-source2"
#define DEVICE_NAME_MAX 30
+#define DEFAULT_FRAGMENT_MSEC 20
+#define DEFAULT_FRAGMENTS 4
struct userdata {
pa_core *core;
char* card;
char* device;
bool first;
+ bool echo_on;
pa_rtpoll_item *rtpoll_item;
uint64_t read_count;
pa_usec_t latency_time;
pa_hal_interface *hal_interface;
+
+ pa_msgobject *ec_object;
+ pa_asyncmsgq *ec_asyncmsgq;
+ pa_rtpoll_item *ec_poll_item;
};
static const char* const valid_modargs[] = {
struct userdata *u = PA_SOURCE(o)->userdata;
switch (code) {
+ case PA_SOURCE_MESSAGE_SET_AEC_STATE: {
+ u->echo_on = !!data;
+ pa_log_info("EC state changed (%d)", u->echo_on);
+ return 0;
+ }
case PA_SOURCE_MESSAGE_GET_LATENCY: {
uint64_t r = 0;
return 0;
}
+ case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
+ pa_source_output *o = (pa_source_output *)data;
+
+ if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL))
+ break;
+
+ if (u->ec_asyncmsgq) {
+ pa_asyncmsgq_send(u->ec_asyncmsgq, u->ec_object,
+ PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK, o, 0, NULL);
+ }
+
+ return 0;
+ }
+ case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: {
+ struct arguments {
+ pa_msgobject *o;
+ pa_asyncmsgq *q;
+ } *args;
+
+ args = (struct arguments *)data;
+
+ if (args) {
+ u->ec_object = args->o;
+ u->ec_asyncmsgq = args->q;
+ u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q);
+ } else {
+ pa_rtpoll_item_free(u->ec_poll_item);
+ u->ec_poll_item = NULL;
+ u->ec_object = NULL;
+ }
+
+ return 0;
+ }
}
return pa_source_process_msg(o, code, data, offset, chunk);
chunk.index = 0;
chunk.length = (size_t)frames_to_read * frame_size;
- pa_source_post(u->source, &chunk);
+
+ if (u->echo_on) {
+ pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object,
+ PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL);
+ } else {
+ pa_source_post(u->source, &chunk);
+ }
+
pa_memblock_unref(chunk.memblock);
u->read_count += chunk.length;
pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents);
u->first = true;
revents = 0;
- } else {
- //pa_log_debug("Poll wakeup.", revents);
}
}
}
}
fail:
- /* If this was no regular exit from the loop we have to continue
- * processing messages until we received PA_MESSAGE_SHUTDOWN */
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
u->card = pa_xstrdup(card);
u->device = pa_xstrdup(device);
- u->frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
- u->nfrags = m->core->default_n_fragments;
+ u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss);
+ u->nfrags = DEFAULT_FRAGMENTS;
if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 ||
pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) {
pa_log_error("fragment_size or fragments are invalid.");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract");
pa_proplist_sets(data.proplist, "tizen.card", u->card);
pa_proplist_sets(data.proplist, "tizen.device", u->device);
+ pa_proplist_sets(data.proplist, "tizen.version", "2");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen");
frame_size = pa_frame_size(&ss);
u->timestamp = 0ULL;
- if (!(u->thread = pa_thread_new("tizenaudio-source", thread_func, u))) {
+ if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) {
pa_log_error("Failed to create thread.");
goto fail;
}