Support stream-based echo-cancellation 98/272298/9 accepted/tizen/unified/20220325.133421 submit/tizen/20220324.031223
authorJaechul Lee <jcsing.lee@samsung.com>
Tue, 15 Mar 2022 02:17:22 +0000 (11:17 +0900)
committerJaechul Lee <jcsing.lee@samsung.com>
Tue, 22 Mar 2022 06:14:34 +0000 (15:14 +0900)
 * Added module-tizenaudio-echo-cancel (Supported adrian, speex)
 * Added processor module that processes resampled pcm data
 * Added a few messages in sink2/source2

[Version] 15.0.4
[Issue Type] New Feature

Change-Id: Ie6e8c3de0bf5fec20c3a087daf377fdcbd82303b
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
13 files changed:
Makefile.am
configure.ac
packaging/pulseaudio-modules-tizen.spec
src/echo-cancel/adrian-aec.c [new file with mode: 0644]
src/echo-cancel/adrian-aec.h [new file with mode: 0644]
src/echo-cancel/algo_adrian.c [new file with mode: 0644]
src/echo-cancel/algo_speex.c [new file with mode: 0644]
src/echo-cancel/echo-cancel-def.h [new file with mode: 0644]
src/echo-cancel/module-tizenaudio-echo-cancel.c [new file with mode: 0644]
src/echo-cancel/processor.c [new file with mode: 0644]
src/echo-cancel/processor.h [new file with mode: 0644]
src/module-tizenaudio-sink2.c
src/module-tizenaudio-source2.c

index 260a272..768114b 100644 (file)
@@ -36,6 +36,7 @@ MODULE_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS)
 
 pulsemodlibexec_LTLIBRARIES = \
                libhal-interface.la \
+               libprocessor.la \
                libcommunicator.la \
                module-tizenaudio-sink.la \
                module-tizenaudio-source.la \
@@ -44,16 +45,15 @@ pulsemodlibexec_LTLIBRARIES = \
                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 = \
@@ -78,16 +78,32 @@ module_tizenaudio_source_la_LDFLAGS = $(MODULE_LDFLAGS)
 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)
index acaaf3d..de19973 100644 (file)
@@ -369,6 +369,10 @@ PKG_CHECK_MODULES(HALAPIAUDIO, hal-api-audio)
 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]),
 [
index 64ffcb3..1a86b8f 100644 (file)
@@ -2,7 +2,7 @@
 
 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+
@@ -23,6 +23,7 @@ BuildRequires:    pkgconfig(pulsecore)
 BuildRequires:    pkgconfig(libsystemd)
 BuildRequires:    pkgconfig(dns_sd)
 BuildRequires:    pkgconfig(hal-api-audio)
+BuildRequires:    pkgconfig(speexdsp)
 BuildRequires:    pulseaudio
 BuildRequires:    m4
 Requires(post):   /sbin/ldconfig
@@ -88,6 +89,8 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf
 %{_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
diff --git a/src/echo-cancel/adrian-aec.c b/src/echo-cancel/adrian-aec.c
new file mode 100644 (file)
index 0000000..aaec55a
--- /dev/null
@@ -0,0 +1,433 @@
+/* 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;
+}
diff --git a/src/echo-cancel/adrian-aec.h b/src/echo-cancel/adrian-aec.h
new file mode 100644 (file)
index 0000000..1482923
--- /dev/null
@@ -0,0 +1,248 @@
+/* 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
diff --git a/src/echo-cancel/algo_adrian.c b/src/echo-cancel/algo_adrian.c
new file mode 100644 (file)
index 0000000..a5d2e72
--- /dev/null
@@ -0,0 +1,93 @@
+/***
+  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;
+}
diff --git a/src/echo-cancel/algo_speex.c b/src/echo-cancel/algo_speex.c
new file mode 100644 (file)
index 0000000..161812a
--- /dev/null
@@ -0,0 +1,138 @@
+/***
+  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;
+}
diff --git a/src/echo-cancel/echo-cancel-def.h b/src/echo-cancel/echo-cancel-def.h
new file mode 100644 (file)
index 0000000..4dbb240
--- /dev/null
@@ -0,0 +1,39 @@
+/***
+  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,
+};
+
diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c
new file mode 100644 (file)
index 0000000..33482eb
--- /dev/null
@@ -0,0 +1,985 @@
+/***
+  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);
+}
+
diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c
new file mode 100644 (file)
index 0000000..e941c0e
--- /dev/null
@@ -0,0 +1,186 @@
+/***
+  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;
+}
diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h
new file mode 100644 (file)
index 0000000..98482ab
--- /dev/null
@@ -0,0 +1,64 @@
+#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
index a5a5871..a81ac70 100644 (file)
@@ -46,6 +46,7 @@
 #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");
@@ -65,6 +66,8 @@ PA_MODULE_USAGE(
 #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;
@@ -83,11 +86,16 @@ struct userdata {
     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[] = {
@@ -254,6 +262,11 @@ static int sink_process_msg(
     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;
 
@@ -264,6 +277,26 @@ static int sink_process_msg(
 
             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);
@@ -319,10 +352,9 @@ static int process_render(struct userdata *u) {
     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);
@@ -333,6 +365,12 @@ static int process_render(struct userdata *u) {
     }
 
     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;
@@ -389,16 +427,12 @@ static void thread_func(void *userdata) {
                     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);
 
@@ -496,8 +530,8 @@ int pa__init(pa_module*m) {
     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.");
@@ -515,6 +549,7 @@ int pa__init(pa_module*m) {
     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);
@@ -560,7 +595,7 @@ int pa__init(pa_module*m) {
     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;
     }
index 3df3aef..0fc047c 100644 (file)
 #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(
@@ -62,10 +63,11 @@ 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;
@@ -85,12 +87,17 @@ struct userdata {
     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[] = {
@@ -257,6 +264,11 @@ static int source_process_msg(
     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;
 
@@ -267,6 +279,39 @@ static int source_process_msg(
 
             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);
@@ -301,7 +346,14 @@ static int process_render(struct userdata *u) {
 
     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;
@@ -355,16 +407,12 @@ static void thread_func(void *userdata) {
                     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);
 
@@ -461,8 +509,8 @@ int pa__init(pa_module*m) {
     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.");
@@ -480,6 +528,7 @@ int pa__init(pa_module*m) {
     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);
@@ -524,7 +573,7 @@ int pa__init(pa_module*m) {
 
     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;
     }