* Added the processor_holder structure for multi preprocessing.
* Added the processor_reference structure for receiving reference data.
* Added a NS method based on rnnoise.
* Added filesrc reference method.
* Replaced tizenaudio-echo-cancel with module-tizenaudio-preprocessor.
* Had dependencies on webrtc-audio-processing and rnnoise.
* Changed folder name echo-cancel to preprocessor.
* Disabled noise suppression functionality in method_webrtc.
* Removed method_adrian.
[Version] 15.0.41
[Issue Type] New feature
Change-Id: I22bfdffadabe4c9dbd08ce432c8d72a8f4d41dc4
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
module-tizenaudio-policy.la \
module-tizenaudio-discover.la \
module-tizenaudio-publish.la \
- module-tizenaudio-echo-cancel.la \
+ module-tizenaudio-preprocessor.la \
module-sound-player.la \
module-tone-player.la \
module-poweroff.la
src/tizenaudio-source2.h \
src/tizenaudio-util.c \
src/tizenaudio-util.h \
- src/echo-cancel/echo-cancel-def.h
+ src/preprocessor/preprocessor-def.h
libtizenaudio_util_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version
-libtizenaudio_util_la_LIBADD = $(AM_LIBADD) $(PA_LIBS) libhal-interface.la
+libtizenaudio_util_la_LIBADD = $(AM_LIBADD) $(PA_LIBS) libhal-interface.la libprocessor.la
libtizenaudio_util_la_CFLAGS = $(MODULE_CFLAGS)
module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c
module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source2
libprocessor_la_SOURCES = \
- src/echo-cancel/method_speex.c \
- src/echo-cancel/method_reference_copy.c \
- src/echo-cancel/method_adrian.c \
- src/echo-cancel/adrian-aec.c \
- src/echo-cancel/processor.c \
- src/echo-cancel/processor.h \
- src/echo-cancel/adrian-aec.h
+ src/preprocessor/method_speex.c \
+ src/preprocessor/method_rnnoise.c \
+ src/preprocessor/method_reference_copy.c \
+ src/preprocessor/method_webrtc.cpp \
+ src/preprocessor/reference_method_filesrc.c \
+ src/preprocessor/processor.c \
+ src/preprocessor/processor.h \
+ src/preprocessor/method_factory.c \
+ src/preprocessor/method_factory.h \
+ src/preprocessor/processor_reference.c \
+ src/preprocessor/processor_reference.h \
+ src/preprocessor/processor_holder.c \
+ src/preprocessor/processor_holder.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)
-
-if ENABLE_WEBRTC
-libprocessor_la_SOURCES += src/echo-cancel/method_webrtc.cpp
-libprocessor_la_LIBADD += $(WEBRTC_LIBS)
-libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -DSUPPORT_METHOD_WEBRTC -std=c++17
-endif
-
-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
-if ENABLE_WEBRTC
-module_tizenaudio_echo_cancel_la_CFLAGS += -DSUPPORT_METHOD_WEBRTC
-endif
+libprocessor_la_LIBADD = $(AM_LIBADD) $(LIBSPEEX_LIBS) $(RNNOISE_LIBS) $(WEBRTC_LIBS)
+libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS) $(RNNOISE_CFLAGS)
+libprocessor_la_CPPFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(WEBRTC_CFLAGS) -DSUPPORT_METHOD_WEBRTC -std=c++17
+
+module_tizenaudio_preprocessor_la_SOURCES = src/preprocessor/module-tizenaudio-preprocessor.c
+module_tizenaudio_preprocessor_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_preprocessor_la_LIBADD = $(MODULE_LIBADD) libprocessor.la
+module_tizenaudio_preprocessor_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_preprocessor
module_sound_player_la_SOURCES = src/module-sound-player.c
module_sound_player_la_LDFLAGS = $(MODULE_LDFLAGS)
AC_SUBST(SPEEX_CFLAGS)
AC_SUBST(SPEEX_LIBS)
+PKG_CHECK_MODULES(WEBRTC, webrtc-audio-processing)
+AC_SUBST(WEBRTC_CFLAGS)
+AC_SUBST(WEBRTC_LIBS)
+
+PKG_CHECK_MODULES(RNNOISE, rnnoise)
+AC_SUBST(RNNOISE_CFLAGS)
+AC_SUBST(RNNOISE_LIBS)
+
dnl use hal tc ------------------------------------------------------------
AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]),
[
AM_CONDITIONAL(ENABLE_ACM, test "x$ENABLE_ACM" = "xyes")
dnl end --------------------------------------------------------------------
-dnl use webrtc ----------------------------------------------------------------
-AC_ARG_ENABLE(webrtc, AC_HELP_STRING([--enable-webrtc], [using webrtc-audio-processing]),
-[
- case "${enableval}" in
- yes) ENABLE_WEBRTC=yes ;;
- no) ENABLE_WEBRTC=no ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-webrtc) ;;
- esac
- ],[USE_WEBRTC=no])
-
-if test "x$ENABLE_WEBRTC" = "xyes"; then
-PKG_CHECK_MODULES(WEBRTC, webrtc-audio-processing)
-AC_SUBST(WEBRTC_CFLAGS)
-AC_SUBST(WEBRTC_LIBS)
-fi
-
AM_CONDITIONAL(ENABLE_WEBRTC, test "x$ENABLE_WEBRTC" = "xyes")
dnl end --------------------------------------------------------------------
Name: pulseaudio-modules-tizen
Summary: Pulseaudio modules for Tizen
-Version: 15.0.40
+Version: 15.0.41
Release: 0
Group: Multimedia/Audio
License: LGPL-2.1+
BuildRequires: pkgconfig(dns_sd)
BuildRequires: pkgconfig(hal-api-audio)
BuildRequires: pkgconfig(speexdsp)
-%if "%{tizen_profile_name}" != "tv"
+BuildRequires: pkgconfig(rnnoise)
BuildRequires: pkgconfig(webrtc-audio-processing)
-%endif
BuildRequires: pulseaudio
BuildRequires: m4
Requires(post): /sbin/ldconfig
%reconfigure --prefix=%{_prefix} \
--disable-static \
--enable-acm \
-%if "%{tizen_profile_name}" != "tv"
- --enable-webrtc \
-%endif
%if "%{tizen_profile_name}" == "tv"
--enable-vconf-helper
%endif
%{_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/module-tizenaudio-preprocessor.so
%{_libdir}/pulse-%{module_ver}/modules/libprocessor.so
%{_libdir}/pulse-%{module_ver}/modules/libtizenaudio-util.so
%{_libdir}/pulse-%{module_ver}/modules/libhal-interface.so
+++ /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 method_adrian {
- int blocksize;
- AEC *aec;
-};
-
-void *adrian_create(size_t nframes, pa_sample_spec *ss) {
- struct method_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 method_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 method_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 method_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 <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(
- "method=<name of method using for echo cancellation. [method]=webrtc, adrian, speex, reference copy > ");
-
-#define DEFAULT_PROCESS_MSEC 10
-
-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;
-
- char *force_method;
-
- pa_thread *thread;
- pa_thread_mq thread_mq;
- pa_rtpoll *rtpoll;
- pa_echo_cancel *echo_cancel;
-
- /* use in thread */
- bool enable_in_thread;
- bool triggered;
-
- 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)
-
-static const char* const valid_modargs[] = {
- "method",
- NULL,
-};
-
-static int proplist_get_fragment_size_usec(pa_proplist *p, pa_sample_spec *sample_spec, pa_usec_t *usec) {
- const char *prop_fragsize;
- uint32_t fragsize;
-
- pa_assert(p);
- pa_assert(sample_spec);
- pa_assert(usec);
-
- if (!(prop_fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE)))
- return -1;
-
- if (pa_atou(prop_fragsize, &fragsize))
- return -1;
-
- *usec = pa_bytes_to_usec(fragsize, sample_spec);
-
- return 0;
-}
-
-static int proplist_get_method(pa_proplist *p, pa_processor_method_t *method) {
- const char *m;
-
- pa_assert(p);
- pa_assert(method);
-
- if (!(m = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_METHOD)))
- return -1;
-
- *method = pa_processor_get_method(m);
-
- return 0;
-}
-
-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 (o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)
- break;
- }
-
- if (!o)
- pa_log_error("Failed to find AEC source-output");
-
- return o ? o : NULL;
-}
-
-static pa_usec_t get_round_trip_latency(struct userdata *u) {
- pa_usec_t sink_latency = 0ULL;
- pa_usec_t source_latency = 0ULL;
-
- pa_assert(u);
- pa_assert(u->sink);
-
- pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_latency, 0, NULL);
- pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_latency, 0, NULL);
-
- pa_log_info("sink latency (%" PRIu64 "), source latency(%" PRIu64 ")", sink_latency, source_latency);
-
- return sink_latency + source_latency;
-}
-
-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 broadcast_echo_cancel_state(struct userdata *u, pa_source_output *o, bool enable) {
- void *v[2];
-
- pa_assert(u);
- pa_assert(u->source);
- pa_assert(u->sink);
-
- if (enable) {
- 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);
- }
-
- v[0] = (void *)enable;
- v[1] = o;
-
- pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->echo_cancel),
- PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, (void *)v, 0, NULL);
-
- 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);
-
- if (!enable) {
- send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL);
- send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL);
- }
-}
-
-static void set_echo_cancel_state(struct userdata *u, bool enable) {
- pa_source_output *o;
-
- pa_assert(u);
- pa_assert(u->source);
- pa_assert(u->sink);
-
- if (u->enable == enable)
- return;
-
- o = find_source_output_by_flags(u->source);
- if (!o)
- return;
-
- broadcast_echo_cancel_state(u, o, enable);
- u->enable = enable;
-
- pa_log_info("AEC state is changed. enable(%d)", u->enable);
-}
-
-static int update_state_by_sink(struct userdata *u, bool enable) {
- pa_assert(u);
-
- if (u->n_source_output == 0)
- return 0;
-
- set_echo_cancel_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_echo_cancel_state(u, enable);
- }
- } else {
- if (--u->n_source_output == 0)
- set_echo_cancel_state(u, enable);
- }
-
- return 0;
-}
-
-static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) {
- int ret = -1;
-
- pa_assert(o);
- pa_assert(chunk);
- pa_assert(ochunk);
-
- if ((ret = pa_processor_process(o->thread_info.processor, chunk, ochunk)) < 0)
- pa_log_error("Failed to process data");
-
- 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;
- pa_source_output *so = NULL;
-
- /* trigger resolves a race condition related to post_process between source and render thread */
- if (u->triggered) {
- if (code == PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO) {
- pa_usec_t latency;
-
- so = find_source_output_by_flags(u->source);
- if (!so) {
- u->triggered = false;
- return 0;
- }
-
- so->post_process = post_process;
-
- latency = get_round_trip_latency(u);
-
- if (pa_processor_setup_reference_memblockq_padding(so->thread_info.processor, latency) < 0)
- pa_log_warn("Failed to setup reference memblockq padding");
-
- u->triggered = false;
-
- pa_log_info("Triggered Echo-Cancellation. index(%d), latency(%" PRIu64 ") usec", so->index, latency);
- }
- }
-
- /* thread that pushes ref data should be called in render thread because of thread safe */
- switch (code) {
- case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA:
- if (u->enable_in_thread)
- pa_source_post(u->source, chunk);
-
- break;
- case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO:
- if (u->enable_in_thread) {
- pa_assert(u->source);
-
- so = find_source_output_by_flags(u->source);
- if (!so)
- break;
-
- if (pa_processor_push_reference(so->thread_info.processor, chunk) < 0)
- pa_log_error("Failed to push reference data");
- }
-
- break;
- case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE: {
- void **v = (void **)data;
- bool enable = (bool)v[0];
- so = (pa_source_output *)v[1];
-
- u->enable_in_thread = enable;
-
- if (enable) {
- u->triggered = true;
- } else {
- pa_processor_flush(so->thread_info.processor);
- so->post_process = NULL;
- }
-
- break;
- }
- default:
- break;
- }
-
- 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;
- pa_processor_method_t method;
- const char *m;
-
- pa_assert(c);
- pa_assert(u);
- pa_assert(data);
-
- if (!(m = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD)))
- return PA_HOOK_OK;
-
- method = pa_processor_get_method(m);
-
- /* TODO: source-output can be moved */
- data->flags |= PA_SOURCE_OUTPUT_DONT_MOVE;
- data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL;
-
- if (method == PA_PROCESSOR_REFERENCE_COPY)
- data->flags |= PA_SOURCE_OUTPUT_NO_REMAP;
-
- pa_log_info("echo-cancel source-output will be created. method(%d)", method);
-
- // TODO:add check limitation VARIOUS_RATE?
- return PA_HOOK_OK;
-}
-
-static pa_sink *find_reference_sink_by_proplist(pa_core *c, pa_proplist *p) {
- const char *ref_idx;
- int32_t idx;
- pa_sink *s;
-
- pa_assert(c);
- pa_assert(p);
-
- ref_idx = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK);
- if (!ref_idx)
- return NULL;
-
- if (pa_atoi(ref_idx, &idx) < 0)
- return NULL;
-
- s = pa_idxset_get_by_index(c->sinks, idx);
- if (!s)
- return NULL;
-
- pa_log_info("Found reference sink(%d, %s)", s->index, s->name);
-
- return s;
-}
-
-static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) {
- struct userdata *u = (struct userdata *)userdata;
- pa_processor_method_t method;
- pa_usec_t process_usec;
- pa_usec_t sink_process_usec;
- int r;
-
- pa_assert(c);
- pa_assert(o);
- pa_assert(u);
- pa_assert(o->source);
-
- if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL))
- return PA_HOOK_OK;
-
- if (u->n_source_output > 0) {
- pa_log_error("Not allow multi aec instance");
- goto fail;
- }
-
- if (u->force_method) {
- method = pa_processor_get_method(u->force_method);
- } else {
- if (proplist_get_method(o->proplist, &method) < 0) {
- pa_log_error("Failed to get method");
- goto fail;
- }
- }
-
- u->source = o->source;
- u->sink = find_reference_sink_by_proplist(c, o->proplist);
- if (!u->sink) {
- pa_log_error("Can't find reference sink for AEC");
- goto fail;
- }
-
- /* Get period size of sink and source */
- if (proplist_get_fragment_size_usec(u->source->proplist, &u->source->sample_spec, &process_usec) < 0) {
- pa_log_error("Failed to get fragment usec");
- goto fail;
- }
-
- if (proplist_get_fragment_size_usec(u->sink->proplist, &u->sink->sample_spec, &sink_process_usec) < 0) {
- pa_log_error("Failed to get fragment usec");
- goto fail;
- }
-
- if (sink_process_usec != process_usec)
- pa_log_info("period size of reference and source isn't same. memchunk will be copied");
-
- o->thread_info.processor = pa_processor_new(c, process_usec / PA_USEC_PER_MSEC,
- &o->sample_spec,
- &o->channel_map,
- &u->source->sample_spec,
- method);
- if (!o->thread_info.processor) {
- pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled");
- goto fail;
- }
-
- r = pa_processor_bind_reference(o->thread_info.processor,
- &u->sink->sample_spec,
- &u->sink->channel_map);
- if (r < 0) {
- pa_log_error("Failed to bind reference source");
- goto fail;
- }
-
- pa_log_info("echo-cancel source-output(%u) created. process_msec(%u), sink_process_msec(%u), method(%s)",
- o->index,
- (uint32_t)(process_usec / PA_USEC_PER_MSEC),
- (uint32_t)(sink_process_usec / PA_USEC_PER_MSEC),
- pa_processor_method_to_string(method));
-
- 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
- if (o->thread_info.processor)
- pa_processor_free(o->thread_info.processor);
-
- return PA_HOOK_OK;
-}
-
-/* Call from main thread */
-static pa_hook_result_t source_output_unlink_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 (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL))
- return PA_HOOK_OK;
-
- update_state_by_source(u, false);
-
- return PA_HOOK_OK;
-}
-
-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 (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL))
- return PA_HOOK_OK;
-
- pa_processor_free(o->thread_info.processor);
-
- u->source = NULL;
- u->sink = NULL;
-
- pa_log_info("echo-cancel source-output(%u) is unlinked", o->index);
-
- 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;
-
- pa_assert(m);
-
- if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
- pa_log_error("Failed to parse module arguments.");
- return -1;
- }
-
- m->userdata = u = pa_xnew0(struct userdata, 1);
- u->core = m->core;
- u->m = m;
- u->force_method = pa_xstrdup(pa_modargs_get_value(ma, "method", NULL));
-
- 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_slot =
- pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK],
- PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_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);
-
- /* source_output_new_cb must be called after new_cb callback in stream manager.
- * because stream-manager converts the device_id to the index of the sink */
- 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_source_output *o;
-
- 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);
-
- if ((o = find_source_output_by_flags(u->source)))
- pa_processor_free(o->thread_info.processor);
- }
-
- pa_asyncmsgq_unref(u->asyncmsgq_source);
- }
-
- if (u->rtpoll)
- pa_rtpoll_free(u->rtpoll);
-
- pa_thread_mq_done(&u->thread_mq);
-
- pa_xfree(u->force_method);
-
- 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 <pulse/timeval.h>
-#include <pulse/channelmap.h>
-#include <pulsecore/log.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/resampler.h>
-#include <pulsecore/core-util.h>
-
-#include "processor.h"
-
-//#define __DEBUG__
-#ifdef __DEBUG__
-#include <stdio.h>
-#include <sys/time.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#endif
-
-#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024)
-#define MEMBLOCKQ_PUSH_REFERENCE_BLOCK_ALIGN
-
-typedef struct pa_processor_method_interface pa_processor_method_interface;
-struct pa_processor {
- pa_core *core;
- pa_processor_method_interface *intf;
- pa_processor_method_t method;
- void *priv;
-
- size_t process_frames;
- pa_usec_t process_usec;
-
- /* process_bytes and reference_process_bytes must be same logically,
- * but they have a different scale */
- size_t process_bytes;
- size_t reference_process_bytes;
-
- pa_sample_spec *output_ss;
- pa_channel_map *output_chmap;
- pa_sample_spec *source_ss;
- pa_sample_spec reference_memblockq_ss;
-
- pa_resampler *resampler;
- pa_memblockq *reference_memblockq;
- pa_memblockq *output_memblockq;
-
-#ifdef __DEBUG__
- int fdrec, fdref, fdout;
- struct timeval before, after;
-#endif
-};
-
-struct pa_processor_method_interface {
- const char *name;
- 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);
- int32_t (*change_reference_spec)(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map);
-};
-
-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);
-
-#ifdef SUPPORT_METHOD_WEBRTC
-extern void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss);
-extern int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
-extern int32_t webrtc_audio_destroy(void *priv);
-#endif
-
-extern void *reference_copy_create(size_t nframes, pa_sample_spec *ss);
-extern int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
-extern int32_t reference_copy_destroy(void *priv);
-extern int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map);
-
-#ifdef __DEBUG__
-static void debug_open_file(pa_processor *processor);
-static void debug_timestamp_begin(pa_processor *processor);
-static void debug_timestamp_end(pa_processor *processor);
-static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out);
-static void debug_close_file(pa_processor *processor);
-#else
-#define debug_open_file(x)
-#define debug_timestamp_begin(x)
-#define debug_timestamp_end(x)
-#define debug_write_file(x, a, b, c)
-#define debug_close_file(x)
-#endif
-
-static struct pa_processor_method_interface method_table[PA_PROCESSOR_METHOD_MAX] = {
- {
- "speex",
- speex_create,
- speex_process,
- speex_destroy,
- NULL,
- },
- {
- "adrian",
- adrian_create,
- adrian_process,
- adrian_destroy,
- NULL,
- },
-#ifdef SUPPORT_METHOD_WEBRTC
- {
- "webrtc",
- webrtc_audio_create,
- webrtc_audio_process,
- webrtc_audio_destroy,
- NULL,
- },
-#endif
- {
- "reference_copy",
- reference_copy_create,
- reference_copy_process,
- reference_copy_destroy,
- reference_copy_change_reference_spec,
- },
-};
-
-static size_t pa_processor_usec_to_frame(pa_usec_t usec, pa_sample_spec *spec) {
- pa_assert(spec);
-
- return pa_usec_to_bytes(usec, spec) / pa_frame_size(spec);
-}
-
-pa_processor *pa_processor_new(pa_core *core,
- uint32_t process_msec,
- pa_sample_spec *output_ss,
- pa_channel_map *output_map,
- pa_sample_spec *source_ss,
- pa_processor_method_t method) {
- pa_processor *processor;
- pa_memchunk silence;
-
- pa_assert(core);
- pa_assert(output_ss);
- pa_assert(output_map);
- pa_assert(source_ss);
- pa_assert(method < PA_PROCESSOR_METHOD_MAX);
-
- processor = pa_xnew0(pa_processor, 1);
- processor->intf = &method_table[method];
- processor->core = core;
- processor->process_usec = process_msec * PA_USEC_PER_MSEC;
- processor->output_ss = output_ss;
- processor->output_chmap = output_map;
- processor->source_ss = source_ss;
- processor->method = method;
- processor->process_frames = pa_processor_usec_to_frame(processor->process_usec, processor->output_ss);
- processor->process_bytes = pa_usec_to_bytes(processor->process_usec, processor->output_ss);
-
- if (!(processor->priv = processor->intf->create(processor->process_frames, output_ss))) {
- pa_log_error("Failed to create processor. rate(%d), channels(%d).", output_ss->rate, output_ss->channels);
- pa_xfree(processor);
- return NULL;
- }
-
- pa_silence_memchunk_get(&core->silence_cache, core->mempool, &silence, output_ss, 0);
- processor->output_memblockq = pa_memblockq_new("source-output memblockq",
- 0,
- MEMBLOCKQ_MAXLENGTH,
- 0,
- processor->output_ss,
- 0,
- pa_usec_to_bytes(processor->process_usec, output_ss),
- 0,
- &silence);
- pa_memblock_unref(silence.memblock);
-
- pa_log_info("Created processor. memblockq rate(%d), channels(%d), process_msec(%u), "
- "process_bytes(%zu), method(%s), source rate(%d), channels(%d)",
- output_ss->rate,
- output_ss->channels,
- process_msec,
- pa_usec_to_bytes(processor->process_usec, output_ss),
- method_table[method].name,
- source_ss->rate,
- source_ss->channels);
-
- debug_open_file(processor);
-
- return processor;
-}
-
-int pa_processor_bind_reference(pa_processor *processor,
- pa_sample_spec *reference_ss,
- pa_channel_map *reference_chmap) {
-
- pa_sample_spec sample_spec;
- pa_channel_map channel_map;
- pa_memchunk silence;
-
- pa_assert(processor);
- pa_assert(processor->intf);
- pa_assert(processor->output_ss);
- pa_assert(processor->output_chmap);
- pa_assert(reference_ss);
- pa_assert(reference_chmap);
-
- sample_spec = *processor->output_ss;
- channel_map = *processor->output_chmap;
-
- /* select reference memblockq sample_spec and channelmap */
- if (processor->intf->change_reference_spec) {
- if (processor->intf->change_reference_spec(processor->priv, processor->source_ss, &sample_spec, &channel_map) < 0) {
- pa_log_error("Failed to get reference info");
- return -1;
- }
- }
-
- /* Create resampler */
- if (!pa_sample_spec_equal(reference_ss, &sample_spec)) {
- if (processor->resampler)
- pa_resampler_free(processor->resampler);
-
- processor->resampler = pa_resampler_new(processor->core->mempool,
- reference_ss, reference_chmap,
- &sample_spec, &channel_map,
- processor->core->lfe_crossover_freq,
- processor->core->resample_method, 0);
- if (!processor->resampler) {
- pa_log_error("Failed to allocate reference resampler");
- return -1;
- }
- }
-
- processor->reference_memblockq_ss = sample_spec;
- processor->reference_process_bytes = pa_usec_to_bytes(processor->process_usec, &processor->reference_memblockq_ss);
-
- /* Create memblockq */
- pa_silence_memchunk_get(&processor->core->silence_cache, processor->core->mempool, &silence, &sample_spec, 0);
-
- if (processor->reference_memblockq)
- pa_memblockq_free(processor->reference_memblockq);
-
- processor->reference_memblockq = pa_memblockq_new("reference memblockq",
- 0,
- MEMBLOCKQ_MAXLENGTH,
- 0,
- &processor->reference_memblockq_ss,
- 0,
- processor->reference_process_bytes,
- 0,
- &silence);
- pa_memblock_unref(silence.memblock);
-
- pa_log_debug("Created reference memblockq rate(%d), channels(%d), msec(%" PRId64 "), minreq bytes(%zu)",
- sample_spec.rate,
- sample_spec.channels,
- processor->process_usec / PA_USEC_PER_MSEC,
- processor->reference_process_bytes);
-
- return 0;
-}
-
-int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency) {
- pa_memchunk silence;
- int64_t write_index, read_index;
- size_t n, bytes;
-
- pa_assert(processor);
-
- bytes = pa_usec_to_bytes(latency, &processor->reference_memblockq_ss);
-
-#ifdef MEMBLOCKQ_PUSH_REFERENCE_BLOCK_ALIGN
- n = (bytes + (processor->reference_process_bytes - 1)) / processor->reference_process_bytes;
- pa_silence_memchunk_get(
- &processor->core->silence_cache,
- processor->core->mempool,
- &silence,
- &processor->reference_memblockq_ss,
- processor->reference_process_bytes);
-#else
- n = 1;
- pa_silence_memchunk_get(
- &processor->core->silence_cache,
- processor->core->mempool,
- &silence,
- &processor->reference_memblockq_ss,
- bytes);
-#endif
-
- write_index = pa_memblockq_get_write_index(processor->reference_memblockq);
- read_index = pa_memblockq_get_read_index(processor->reference_memblockq);
-
- for (; n > 0; n--)
- pa_memblockq_push(processor->reference_memblockq, &silence);
-
- pa_memblock_unref(silence.memblock);
-
- pa_log_info("push n(%u) silence blocks. latency(%" PRId64 "), ref_process_msec(%" PRId64 ") "
- "write_index(%" PRId64 "->%" PRId64 "), read_index(%" PRId64 "->%" PRId64 ")",
- pa_memblockq_get_nblocks(processor->reference_memblockq),
- latency,
- pa_bytes_to_usec(processor->reference_process_bytes,
- &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC,
- write_index, pa_memblockq_get_write_index(processor->reference_memblockq),
- read_index, pa_memblockq_get_read_index(processor->reference_memblockq));
-
- return 0;
-}
-
-int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchunk *ochunk) {
- int r = -1;
- int8_t *recording = NULL;
- int8_t *reference = NULL;
- int8_t *output = NULL;
- bool silence = false;
-
- pa_memchunk ichunk, rchunk;
-
- pa_assert(processor);
- pa_assert(processor->output_memblockq);
- pa_assert(processor->reference_memblockq);
- pa_assert(processor->intf);
- pa_assert(processor->process_bytes > 0ULL);
- pa_assert(processor->reference_process_bytes > 0ULL);
- pa_assert(chunk);
- pa_assert(ochunk);
-
- if ((r = pa_memblockq_push(processor->output_memblockq, chunk)) < 0) {
- pa_log_error("Failed to push chunk to reference memblockq");
- return r;
- }
-
- if ((r = pa_memblockq_peek_fixed_size(processor->reference_memblockq, processor->reference_process_bytes, &rchunk)) < 0) {
- pa_log_error("Failed to get memblock from reference memblockq");
- return r;
- }
- silence = pa_memblock_is_silence(rchunk.memblock);
-
- if ((r = pa_memblockq_peek_fixed_size(processor->output_memblockq, processor->process_bytes, &ichunk)) < 0) {
- pa_log_error("Failed to get memblock from output memblockq");
- return r;
- }
-
- ochunk->index = 0;
- ochunk->length = ichunk.length;
- ochunk->memblock = pa_memblock_new(processor->core->mempool, ochunk->length);
-
- recording = pa_memblock_acquire_chunk(&ichunk);
- reference = pa_memblock_acquire_chunk(&rchunk);
- output = pa_memblock_acquire_chunk(ochunk);
-
- debug_timestamp_begin(processor);
-
- r = processor->intf->process(processor->priv, recording, reference, output);
-
- debug_timestamp_end(processor);
- debug_write_file(processor, recording, reference, output);
-
- pa_memblock_release(ichunk.memblock);
- pa_memblock_release(rchunk.memblock);
- pa_memblock_release(ochunk->memblock);
-
- pa_memblock_unref(rchunk.memblock);
- pa_memblockq_drop(processor->reference_memblockq, rchunk.length);
-
- pa_memblock_unref(ichunk.memblock);
- pa_memblockq_drop(processor->output_memblockq, ichunk.length);
-
- pa_log_debug("Post-process. rec(%" PRIu64 "ms), ref(%" PRIu64 "ms) out(%" PRIu64 "ms), "
- "silence(%d), reference memblockq windex:rindex(%" PRId64 ":%" PRId64 ")",
- pa_bytes_to_usec(ichunk.length, processor->output_ss) / PA_USEC_PER_MSEC,
- pa_bytes_to_usec(rchunk.length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC,
- pa_bytes_to_usec(ochunk->length, processor->output_ss) / PA_USEC_PER_MSEC,
- silence,
- pa_memblockq_get_write_index(processor->reference_memblockq),
- pa_memblockq_get_read_index(processor->reference_memblockq));
-
- return r;
-}
-
-int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk) {
- pa_memchunk ochunk;
- int r;
-
- pa_assert(processor);
- pa_assert(chunk);
-
- if (processor->resampler) {
- pa_resampler_run(processor->resampler, chunk, &ochunk);
- chunk = &ochunk;
- }
-
- if ((r = pa_memblockq_push(processor->reference_memblockq, chunk)) < 0)
- pa_log_error("Failed to push chunk to reference memblockq");
-
- if (processor->resampler)
- pa_memblock_unref(chunk->memblock);
-
- pa_log_debug("Pushed reference data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")",
- chunk->length,
- pa_bytes_to_usec(chunk->length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC,
- pa_memblockq_get_nblocks(processor->reference_memblockq),
- pa_memblockq_get_write_index(processor->reference_memblockq),
- pa_memblockq_get_read_index(processor->reference_memblockq));
-
- return r;
-}
-
-void pa_processor_flush(pa_processor *processor) {
- pa_assert(processor);
-
- if (processor->reference_memblockq)
- pa_memblockq_flush_read(processor->reference_memblockq);
-
- if (processor->output_memblockq)
- pa_memblockq_flush_read(processor->output_memblockq);
-}
-
-int pa_processor_free(pa_processor *processor) {
- pa_assert(processor);
- pa_assert(processor->priv);
- pa_assert(processor->intf);
-
- if (processor->intf->destroy(processor->priv) < 0)
- pa_log_error("Failed to destroy processor");
-
- if (processor->resampler)
- pa_resampler_free(processor->resampler);
-
- if (processor->reference_memblockq)
- pa_memblockq_free(processor->reference_memblockq);
-
- if (processor->output_memblockq)
- pa_memblockq_free(processor->output_memblockq);
-
- debug_close_file(processor);
-
- pa_xfree(processor);
-
- return 0;
-}
-
-const char *pa_processor_method_to_string(pa_processor_method_t method) {
- if (method >= PA_PROCESSOR_METHOD_MAX)
- return NULL;
-
- return method_table[method].name;
-}
-
-pa_processor_method_t pa_processor_get_method(const char *request_method) {
- pa_processor_method_t method;
-
- if (pa_streq(request_method, "speex"))
- method = PA_PROCESSOR_SPEEX;
- else if (pa_streq(request_method, "adrian"))
- method = PA_PROCESSOR_ADRIAN;
-#ifdef SUPPORT_METHOD_WEBRTC
- else if (pa_streq(request_method, "webrtc"))
- method = PA_PROCESSOR_WEBRTC;
-#endif
- else if (pa_streq(request_method, "reference_copy"))
- method = PA_PROCESSOR_REFERENCE_COPY;
- else
-#ifdef SUPPORT_METHOD_WEBRTC
- method = PA_PROCESSOR_WEBRTC;
-#else
- method = PA_PROCESSOR_SPEEX;
-#endif
-
- pa_log_info("processing method is selected. method(%d), request_method(%s), method_to_string(%s)",
- method, request_method, pa_processor_method_to_string(method));
-
- return method;
-}
-
-#ifdef __DEBUG__
-static void debug_open_file(pa_processor *processor) {
- 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);
-}
-
-static void debug_timestamp_begin(pa_processor *processor) {
- gettimeofday(&processor->before, NULL);
-}
-
-static void debug_timestamp_end(pa_processor *processor) {
- gettimeofday(&processor->after, NULL);
-
- pa_log_debug("It takes time (%ld)ms.",
- 1000 * (processor->after.tv_sec - processor->before.tv_sec)
- + (processor->after.tv_usec - processor->before.tv_usec) / 1000);
-}
-
-static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) {
- if (rec && write(processor->fdrec, rec, processor->process_bytes) <= 0)
- pa_log_error("Failed to write recording buffer");
-
- if (ref && write(processor->fdref, ref, processor->reference_process_bytes) <= 0)
- pa_log_error("Failed to write reference buffer");
-
- if (out && write(processor->fdout, out, processor->process_bytes) <= 0)
- pa_log_error("Failed to write ref buffer");
-}
-
-static void debug_close_file(pa_processor *processor) {
- if (processor->fdrec) {
- close(processor->fdrec);
- processor->fdrec = -1;
- }
-
- if (processor->fdref) {
- close(processor->fdref);
- processor->fdref = -1;
- }
-
- if (processor->fdout) {
- close(processor->fdout);
- processor->fdout = -1;
- }
-}
-#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.
-***/
-
-#ifndef foopulseprocessorfoo
-#define foopulseprocessorfoo
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <pulsecore/core.h>
-#include <pulsecore/memblock.h>
-#include <pulse/channelmap.h>
-#include <pulse/sample.h>
-
-/* These are used as an index to choose a method in method_table. Keep this order */
-typedef enum {
- PA_PROCESSOR_SPEEX,
- PA_PROCESSOR_ADRIAN,
-#ifdef SUPPORT_METHOD_WEBRTC
- PA_PROCESSOR_WEBRTC,
-#endif
- PA_PROCESSOR_REFERENCE_COPY,
- PA_PROCESSOR_METHOD_MAX,
-} pa_processor_method_t;
-
-typedef struct pa_processor pa_processor;
-
-pa_processor *pa_processor_new(pa_core *core,
- uint32_t process_msec,
- pa_sample_spec *output_ss,
- pa_channel_map *output_map,
- pa_sample_spec *source_ss,
- pa_processor_method_t method);
-int pa_processor_bind_reference(pa_processor *processor,
- pa_sample_spec *reference_ss,
- pa_channel_map *reference_chmap);
-int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency);
-int pa_processor_process(pa_processor *processor, pa_memchunk *rec, pa_memchunk *out);
-int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk);
-void pa_processor_flush(pa_processor *processor);
-int pa_processor_free(pa_processor *processor);
-pa_processor_method_t pa_processor_get_method(const char *requset_method);
-const char *pa_processor_method_to_string(pa_processor_method_t method);
-
-#endif
-
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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 "processor.h"
+
+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);
+
+extern void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss);
+extern int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+extern int32_t webrtc_audio_destroy(void *priv);
+
+extern void *reference_copy_create(size_t nframes, pa_sample_spec *ss);
+extern int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+extern int32_t reference_copy_destroy(void *priv);
+
+extern void *rnnoise_ns_create(size_t nframes, pa_sample_spec *ss);
+extern int32_t rnnoise_ns_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out);
+extern int32_t rnnoise_ns_destroy(void *priv);
+
+extern void *processor_reference_filesrc_create(pa_sample_spec *ss);
+extern size_t processor_reference_filesec_read(void *priv, void *ref, size_t length);
+extern void processor_reference_filesrc_destroy(void *priv);
+
+pa_processor_method *pa_processor_method_create(pa_processor_method_t type) {
+ pa_processor_method *m = pa_xnew0(pa_processor_method, 1);
+
+ switch (type) {
+ case PROCESSOR_METHOD_SPEEX:
+ m->name = "speex";
+ m->create = speex_create;
+ m->process = speex_process;
+ m->destroy = speex_destroy;
+ break;
+ case PROCESSOR_METHOD_WEBRTC:
+ m->name = "webrtc";
+ m->create = webrtc_audio_create;
+ m->process = webrtc_audio_process;
+ m->destroy = webrtc_audio_destroy;
+ break;
+ case PROCESSOR_METHOD_REFERENCE_COPY:
+ m->name = "reference_copy";
+ m->create = reference_copy_create;
+ m->process = reference_copy_process;
+ m->destroy = reference_copy_destroy;
+ break;
+ case PROCESSOR_METHOD_RNNOISE:
+ m->name = "rnnoise";
+ m->create = rnnoise_ns_create;
+ m->process = rnnoise_ns_process;
+ m->destroy = rnnoise_ns_destroy;
+ break;
+ default:
+ pa_assert_not_reached();
+ break;
+ }
+
+ return m;
+}
+
+pa_processor_reference_method *pa_processor_reference_method_create(pa_processor_reference_method_t type) {
+ pa_processor_reference_method *m = pa_xnew0(pa_processor_reference_method, 1);
+
+ switch (type) {
+ case PROCESSOR_REFERENCE_METHOD_AUDIOSHARE:
+ /* TODO: not support yet */
+ break;
+ case PROCESSOR_REFERENCE_METHOD_FILESRC:
+ m->name = "filesrc";
+ m->create = processor_reference_filesrc_create;
+ m->read = processor_reference_filesec_read;
+ m->destroy = processor_reference_filesrc_destroy;
+ break;
+ default:
+ pa_log_error("type %d", type);
+ pa_assert_not_reached();
+ break;
+ }
+
+ return m;
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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.
+***/
+
+#ifndef foopulsemethod_factoryfoo
+#define foopulsemethod_factoryfoo
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+typedef enum {
+ PROCESSOR_METHOD_SPEEX,
+ PROCESSOR_METHOD_WEBRTC,
+ PROCESSOR_METHOD_REFERENCE_COPY,
+ PROCESSOR_METHOD_RNNOISE,
+ PROCESSOR_METHOD_MAX,
+} pa_processor_method_t;
+
+typedef enum {
+ PROCESSOR_REFERENCE_METHOD_NONE,
+ PROCESSOR_REFERENCE_METHOD_AUDIOSHARE,
+ PROCESSOR_REFERENCE_METHOD_FILESRC,
+ PROCESSOR_REFERENCE_METHOD_MAX,
+} pa_processor_reference_method_t;
+
+typedef struct pa_processor_method {
+ const char *name;
+ 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);
+} pa_processor_method;
+
+typedef struct pa_processor_reference_method {
+ const char *name;
+ void *(*create)(pa_sample_spec *ss);
+ size_t (*read)(void *priv, void *ref, size_t n);
+ void (*destroy)(void *priv);
+} pa_processor_reference_method;
+
+pa_processor_method *pa_processor_method_create(pa_processor_method_t type);
+pa_processor_reference_method *pa_processor_reference_method_create(pa_processor_reference_method_t type);
+
+#endif
+
pa_assert(out);
rec_bytes = pa_frame_size(&rc->ss);
- ref_bytes = rc->reference_channels * pa_sample_size(&rc->ss);
+ ref_bytes = pa_sample_size(&rc->ss); /* reference must be 1 channel */
actual_bytes = rec_bytes - ref_bytes;
total_bytes = rec_bytes * rc->nframes;
return 0;
}
-int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map) {
- struct reference_copy *rc = priv;
- int channels;
-
- pa_assert(rc);
- pa_assert(source_ss);
- pa_assert(sample_spec);
- pa_assert(map);
-
- channels = rc->ss.channels - source_ss->channels;
- if (channels <= 0) {
- pa_log_error("No empty channels. reference copy will be disabled");
- return -1;
- }
-
- /* TODO: temporary code for supporting mchstreamer. reference copy uses the last 2ch of its channels */
- if (rc->ss.channels == 16)
- channels = 2;
-
- *sample_spec = rc->ss;
- sample_spec->channels = rc->reference_channels = channels;
-
- pa_channel_map_init_auto(map, channels, PA_CHANNEL_MAP_AIFF);
-
- pa_log_info("reference will be copied to empty channels(%d). source-output ch(%d), source ch(%d)",
- channels, rc->ss.channels, source_ss->channels);
-
- return 0;
-}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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 <rnnoise.h>
+
+#include <assert.h>
+
+struct method_rnnoise {
+ DenoiseState *st;
+ pa_sample_spec ss;
+ size_t frames;
+ float *buffer;
+};
+
+void *rnnoise_ns_create(size_t nframes, pa_sample_spec *ss) {
+ struct method_rnnoise *rnnoise = NULL;
+
+ pa_assert(ss);
+
+ if (ss->channels >= 2 || ss->format != PA_SAMPLE_S16LE || ss->rate != 48000) {
+ pa_log_error("rnnoise limitation. not support rate(%d) ch(%d) format(%d)",
+ ss->rate, ss->channels, ss->format);
+ return NULL;
+ }
+
+ rnnoise = pa_xnew0(struct method_rnnoise, 1);
+ rnnoise->st = rnnoise_create(NULL);
+ rnnoise->ss = *ss;
+ rnnoise->frames = nframes;
+ rnnoise->buffer = pa_xnew(float, nframes * ss->channels);
+
+ pa_log_info("rnnoise initialized. frame(%zu), rate(%d) channels(%d)",
+ nframes, ss->rate, ss->channels);
+
+ return rnnoise;
+}
+
+int32_t rnnoise_ns_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) {
+ struct method_rnnoise *rnnoise = priv;
+ int16_t *ptr;
+ int i;
+
+ assert(rec);
+ assert(out);
+
+ ptr = (int16_t *)rec;
+ for (i=0; i<rnnoise->frames; i++) {
+ rnnoise->buffer[i] = 0;
+ rnnoise->buffer[i] = ptr[i];
+ }
+
+ rnnoise_process_frame(rnnoise->st, rnnoise->buffer, rnnoise->buffer);
+
+ ptr = (int16_t *)out;
+ for (i=0; i<rnnoise->frames; i++) {
+ ptr[i] = 0;
+ ptr[i] = rnnoise->buffer[i];
+ }
+
+ return 0;
+}
+
+int32_t rnnoise_ns_destroy(void *priv) {
+ struct method_rnnoise *rnnoise = priv;
+
+ pa_assert(rnnoise);
+
+ rnnoise_destroy(rnnoise->st);
+
+ pa_xfree(rnnoise->buffer);
+ pa_xfree(rnnoise);
+
+ return 0;
+}
}
webrtc->ap->echo_cancellation()->Enable(true);
- webrtc->ap->noise_suppression()->Enable(true);
- webrtc->ap->noise_suppression()->set_level(static_cast<NoiseSuppression::Level>(1));
webrtc->ap->gain_control()->set_mode(GainControl::kAdaptiveDigital);
//webrtc->ap->gain_control()->set_target_level_dbfs(30);
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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 "preprocessor-def.h"
+#include "processor.h"
+#include "processor_holder.h"
+
+PA_MODULE_AUTHOR("Tizen");
+PA_MODULE_DESCRIPTION("Tizen Audio Preprocessor");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+/*
+PA_MODULE_USAGE(
+ "use_system_reference=<name of method using for reference. [method]=audio-share, filesrc > ");
+ */
+
+#define DEFAULT_PROCESS_USEC 10000
+
+typedef struct preprocessor pa_preprocessor;
+struct userdata {
+ pa_core *core;
+ pa_module *m;
+
+ pa_hook_slot *source_output_fixate_slot;
+ pa_hook_slot *source_output_put_slot;
+ pa_hook_slot *source_output_unlink_post_slot;
+
+ pa_rtpoll *rtpoll;
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_asyncmsgq *asyncmsgq_sink;
+ pa_asyncmsgq *asyncmsgq_source;
+
+ pa_preprocessor *preprocessor;
+ bool enable_in_thread;
+ bool reset_lazy_reference;
+};
+
+struct preprocessor {
+ pa_msgobject parent;
+ struct userdata *u;
+};
+
+PA_DEFINE_PRIVATE_CLASS(pa_preprocessor, pa_msgobject);
+#define PA_PREPROCESSOR(o) (pa_preprocessor_cast(o))
+
+#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024)
+
+static const char* const valid_modargs[] = {
+ NULL,
+};
+
+static int proplist_get_fragment_size_usec(pa_proplist *p, pa_sample_spec *sample_spec, pa_usec_t *usec) {
+ const char *prop_fragsize;
+ uint32_t fragsize;
+
+ pa_assert(p);
+ pa_assert(sample_spec);
+ pa_assert(usec);
+
+ if (!(prop_fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE)))
+ return -1;
+
+ if (pa_atou(prop_fragsize, &fragsize))
+ return -1;
+
+ *usec = pa_bytes_to_usec(fragsize, sample_spec);
+
+ return 0;
+}
+
+static bool is_preprocessor_source_output(pa_proplist *p) {
+ pa_assert(p);
+
+ if (pa_proplist_gets(p, PA_PROP_MEDIA_PREPROCESSOR_METHOD))
+ return true;
+
+ return false;
+}
+
+static bool is_preprocessor_marked(pa_source_output_flags_t flags) {
+ if (flags & PA_SOURCE_OUTPUT_PREPROCESSOR)
+ return true;
+
+ return false;
+}
+
+static bool lookup_preprocessor_exist(pa_core *c) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_assert(c);
+
+ PA_IDXSET_FOREACH(o, c->source_outputs, idx) {
+ pa_source_output_assert_ref(o);
+
+ if (o->flags & PA_SOURCE_OUTPUT_PREPROCESSOR)
+ return true;
+ }
+
+ return false;
+}
+
+static pa_sink *convert_reference_str_to_sink(pa_core *c, const char *str) {
+ int32_t idx;
+ pa_sink *s;
+
+ pa_assert(c);
+ pa_assert(str);
+
+ if (pa_atoi(str, &idx) < 0)
+ return NULL;
+
+ s = pa_idxset_get_by_index(c->sinks, idx);
+ if (!s)
+ return NULL;
+
+ pa_log_info("Found reference sink(%d, %s)", s->index, s->name);
+
+ return s;
+}
+
+static int send_message_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsgq *q, pa_processor_holder *holder) {
+ struct arguments {
+ pa_msgobject *o;
+ pa_asyncmsgq *q;
+ pa_processor_holder *holder;
+ } args;
+
+ pa_asyncmsgq *asyncmsgq;
+ int code;
+
+ pa_assert(dst);
+ pa_assert(pa_sink_isinstance(dst));
+
+ args.o = src;
+ args.q = q;
+ args.holder = holder;
+
+ asyncmsgq = PA_SINK(dst)->asyncmsgq;
+ code = PA_SINK_MESSAGE_PREPROCESSOR_REBUILD_RTPOLL;
+
+ pa_asyncmsgq_send(asyncmsgq, dst, code, src ? (void *)&args : NULL, 0, NULL);
+
+ return 0;
+}
+
+static void connect_to_reference_sink(pa_processor_holder *holder, pa_msgobject *o, pa_asyncmsgq *q, bool enable) {
+ pa_sink *sink;
+ pa_processor_reference *reference;
+
+ pa_assert(holder);
+
+ reference = pa_processor_holder_get_connected_processor_reference(holder);
+ if (!reference)
+ return;
+
+ sink = pa_processor_reference_get_sink(reference);
+ if (!sink)
+ return;
+
+ if (enable)
+ send_message_rebuild_rtpoll(PA_MSGOBJECT(sink), o, q, holder);
+ else
+ send_message_rebuild_rtpoll(PA_MSGOBJECT(sink), NULL, NULL, NULL);
+
+ pa_log_info("connected to reference sink. enable(%d), sink(%s)", enable, sink->name);
+}
+
+static void terminate_preprocessor_holder(struct userdata *u) {
+ pa_processor_holder *holder;
+ pa_source_output *o;
+ pa_source *source;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ PA_IDXSET_FOREACH(o, u->core->source_outputs, idx) {
+ pa_source_output_assert_ref(o);
+
+ if (is_preprocessor_marked(o->flags)) {
+ holder = (pa_processor_holder *)o->thread_info.processor_holder;
+ connect_to_reference_sink(holder, NULL, NULL, false);
+
+ source = pa_processor_holder_get_current_source(holder);
+ pa_assert(source);
+ pa_asyncmsgq_send(source->asyncmsgq, PA_MSGOBJECT(source), PA_SOURCE_MESSAGE_PREPROCESSOR_TERMINATE, NULL, 0, NULL);
+
+ pa_processor_holder_free(holder);
+ o->thread_info.processor_holder = NULL;
+ o->preprocess = NULL;
+
+ o->flags &= ~PA_SOURCE_OUTPUT_PREPROCESSOR;
+ }
+ }
+}
+
+static pa_usec_t get_round_trip_latency(pa_source *source, pa_sink *sink) {
+ pa_usec_t sink_latency = 0ULL;
+ pa_usec_t source_latency = 0ULL;
+
+ pa_assert(sink);
+ pa_assert(source);
+
+ pa_asyncmsgq_send(sink->asyncmsgq, PA_MSGOBJECT(sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_latency, 0, NULL);
+ pa_asyncmsgq_send(source->asyncmsgq, PA_MSGOBJECT(source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_latency, 0, NULL);
+
+ pa_log_info("sink latency (%" PRIu64 "), source latency(%" PRIu64 ")", sink_latency, source_latency);
+
+ return sink_latency + source_latency;
+}
+
+static pa_processor_holder *build_processor_holder(pa_core *core, pa_source_output_new_data *data) {
+ const char *state = NULL;
+ const char *processors_list;
+ char *processor_str;
+
+ pa_processor_holder *holder;
+ pa_usec_t process_usec;
+
+ pa_assert(core);
+ pa_assert(data);
+
+ holder = pa_processor_holder_new(&data->sample_spec);
+ if (!holder) {
+ pa_log_error("Failed to allocate pa_processor_holder");
+ return NULL;
+ }
+
+ processors_list = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_PREPROCESSOR_METHOD);
+ pa_assert(processors_list);
+
+ if (proplist_get_fragment_size_usec(data->source->proplist, &data->source->sample_spec, &process_usec) < 0) {
+ pa_processor_holder_free(holder);
+ pa_log_error("Failed to get source fragment usec. use default process usec");
+ return NULL;
+ }
+
+ while ((processor_str = pa_split(processors_list, ",", &state))) {
+ pa_processor *processor;
+ pa_processor_method_t method = pa_processor_method_enum(processor_str);
+
+ processor = pa_processor_new(core, process_usec / PA_USEC_PER_MSEC,
+ &data->sample_spec, method);
+ if (!processor) {
+ pa_log_error("Failed to create pa_processor. preprocessor(aec) will be disabled");
+ continue;
+ }
+
+ /* reference */
+ if (pa_processor_method_need_reference_structure(method)) {
+ pa_sample_spec request_ss;
+ pa_processor_reference *reference;
+ pa_sink *sink = convert_reference_str_to_sink(core,
+ pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK));
+
+ pa_assert(sink);
+
+ request_ss = data->sample_spec;
+ if (method == PROCESSOR_METHOD_REFERENCE_COPY) {
+ request_ss.channels = 1;
+ data->flags |= PA_SOURCE_OUTPUT_NO_REMAP;
+ }
+
+ reference = pa_processor_reference_new_custom(core, sink, &sink->sample_spec,
+ &request_ss, process_usec,
+ PROCESSOR_REFERENCE_METHOD_NONE);
+ if (!reference) {
+ pa_processor_free(processor);
+ pa_log_error("Failed to create reference custom. processor_str(%s)", processor_str);
+ continue;
+ }
+
+ /* holder -> reference -> processor */
+ pa_processor_attach_reference(processor, reference);
+ if (pa_processor_holder_connect_reference(holder, reference) < 0) {
+ pa_processor_reference_free(reference);
+ pa_processor_free(processor);
+ pa_log_error("Failed to connect holder to reference");
+ continue;
+ }
+ }
+
+ pa_processor_holder_register_processor_sequencial(holder, processor);
+
+ pa_log_info("processor was created. processor(%s), process_msec(%lu)",
+ processor_str, process_usec / PA_USEC_PER_MSEC);
+
+ pa_xfree(processor_str);
+ }
+
+ return holder;
+}
+
+static void destroy_source_output_preprocessor(pa_source_output *o) {
+ pa_processor_holder *holder;
+
+ pa_assert(o);
+
+ holder = (pa_processor_holder *)o->thread_info.processor_holder;
+ pa_processor_holder_free(holder);
+
+ o->preprocess = NULL;
+ o->thread_info.processor_holder = NULL;
+}
+
+static int preprocess(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) {
+ pa_processor_holder *holder;
+
+ pa_assert(o);
+ pa_assert(chunk);
+ pa_assert(ochunk);
+ pa_assert(o->thread_info.processor_holder);
+
+ holder = (pa_processor_holder *)o->thread_info.processor_holder;
+
+ /* chunk must contain resampled sound pcm */
+ pa_processor_holder_push_data(holder, chunk);
+ if (pa_processor_holder_pump(holder) < 0) {
+ pa_log_warn("Failed to pump holder");
+ return -1;
+ }
+
+ /* TODO: need to check return value for pse */
+ pa_processor_holder_pull_data(holder, ochunk);
+
+ return 0;
+}
+
+/* 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_PREPROCESSOR(o)->u;
+
+ switch (code) {
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_ADD_OUTPUT: {
+ pa_source_output *output;
+ pa_processor_holder *holder;
+
+ output = PA_SOURCE_OUTPUT(data);
+ pa_assert(output);
+
+ holder = (pa_processor_holder *)output->thread_info.processor_holder;
+ pa_assert(holder);
+
+ pa_processor_holder_set_current_source(holder, output->source);
+ output->preprocess = preprocess;
+ u->enable_in_thread = true;
+ u->reset_lazy_reference = true;
+
+ pa_processor_holder_dump(holder);
+
+ pa_log_info("source-output with a preprocessor will be added");
+
+ break;
+ }
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_REMOVE_OUTPUT: {
+ pa_source_output *output;
+ pa_processor_holder *holder;
+
+ output = PA_SOURCE_OUTPUT(data);
+ pa_assert(output);
+
+ holder = (pa_processor_holder *)output->thread_info.processor_holder;
+ pa_assert(holder);
+
+ pa_processor_holder_set_current_source(holder, NULL);
+ output->preprocess = NULL;
+ u->enable_in_thread = false;
+
+ pa_processor_holder_dump(holder);
+
+ pa_log_info("source-output with a preprocessor will be removed");
+
+ break;
+ }
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_DATA:
+ if (!u->enable_in_thread)
+ break;
+
+ pa_source *source = PA_SOURCE(data);
+ pa_source_post(source, chunk);
+
+ break;
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_REFERENCE: {
+ pa_processor_holder *holder = (pa_processor_holder *)data;
+ pa_sink *sink;
+ pa_source *source;
+ pa_processor_reference *reference;
+
+ if (!u->enable_in_thread)
+ break;
+
+ reference = pa_processor_holder_get_connected_processor_reference(holder);
+ pa_assert(reference);
+
+ sink = pa_processor_reference_get_sink(reference);
+ source = pa_processor_holder_get_current_source(holder);
+ pa_assert(sink);
+ pa_assert(source);
+
+ /* first reference memchunk after connecting source-output */
+ if (u->reset_lazy_reference) {
+ pa_processor_reference_reset(reference);
+ pa_processor_reference_add_latency_padding(reference, get_round_trip_latency(source, sink));
+
+ u->reset_lazy_reference = false;
+ }
+
+ if (pa_processor_holder_push_reference_data(holder, chunk) < 0)
+ pa_log_error("Failed to push reference data");
+
+ break;
+ }
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_RESET_REFERENCE:
+ if (!u->enable_in_thread)
+ break;
+
+ if (!!data)
+ u->reset_lazy_reference = true;
+
+ break;
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_DESTROY: {
+ pa_source_output *output;
+
+ output = PA_SOURCE_OUTPUT(data);
+ pa_assert(output);
+
+ destroy_source_output_preprocessor(output);
+
+ u->enable_in_thread = false;
+
+ break;
+ }
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_TERMINATE:
+ terminate_preprocessor_holder(u);
+ u->enable_in_thread = false;
+ break;
+ default:
+ pa_assert(0);
+ break;
+ }
+
+ return 0;
+}
+
+static pa_hook_result_t source_output_fixate_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+ pa_processor_holder *holder;
+
+ pa_assert(c);
+ pa_assert(u);
+ pa_assert(data);
+
+ if (!is_preprocessor_source_output(data->proplist))
+ return PA_HOOK_OK;
+
+ if (lookup_preprocessor_exist(c)) {
+ pa_log_warn("Don't allow two instances");
+ return PA_HOOK_OK;
+ }
+
+ holder = build_processor_holder(c, data);
+ if (!holder) {
+ pa_log_error("Failed to build holder");
+ return PA_HOOK_OK;
+ }
+
+ data->processor_holder = (void *)holder;
+ if (!data->processor_holder) {
+ pa_log_error("Failed to create processor_holder");
+ goto fail;
+ }
+
+ data->flags |= PA_SOURCE_OUTPUT_PREPROCESSOR;
+
+ pa_processor_holder_set_private_data(holder, u->preprocessor, u->asyncmsgq_source);
+
+ return PA_HOOK_OK;
+
+fail:
+ data->flags &= ~ PA_SOURCE_OUTPUT_PREPROCESSOR;
+ pa_processor_holder_free(holder);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) {
+ struct userdata *u = (struct userdata *)userdata;
+ pa_processor_holder *holder;
+
+ pa_assert(c);
+ pa_assert(o);
+ pa_assert(u);
+
+ if (!is_preprocessor_marked(o->flags))
+ return PA_HOOK_OK;
+
+ holder = (pa_processor_holder *)o->thread_info.processor_holder;
+
+ pa_assert(holder);
+ pa_assert(pa_processor_holder_get_current_source(holder)); /* source must be set at ADD_SOURCE_OUTPUT */
+
+ connect_to_reference_sink(holder, PA_MSGOBJECT(u->preprocessor), u->asyncmsgq_sink, true);
+
+ return PA_HOOK_OK;
+}
+
+/* This function will be also called when source unlink */
+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_processor_holder *holder;
+
+ pa_assert(c);
+ pa_assert(o);
+ pa_assert(u);
+
+ if (!is_preprocessor_marked(o->flags))
+ return PA_HOOK_OK;
+
+ holder = (pa_processor_holder *)o->thread_info.processor_holder;
+
+ connect_to_reference_sink(holder, NULL, NULL, false);
+
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->preprocessor),
+ PA_SOURCE_MESSAGE_PREPROCESSOR_DESTROY, (void *)o, 0, NULL);
+
+ 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;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log_error("Failed to parse module arguments.");
+ return -1;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->m = m;
+
+ u->preprocessor = pa_msgobject_new(pa_preprocessor);
+ u->preprocessor->parent.process_msg = process_msg;
+ u->preprocessor->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-preprocessor", thread_func, u))) {
+ pa_log_error("Failed to create thread.");
+ goto fail;
+ }
+
+ 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);
+
+ /* source_output_fixate_cb must be called after new_cb callback in stream manager
+ * because stream-manager converts the device_id to the index of the sink */
+ u->source_output_fixate_slot =
+ pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE],
+ PA_HOOK_LATE, (pa_hook_cb_t) source_output_fixate_cb, u);
+
+ u->source_output_put_slot =
+ pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT],
+ PA_HOOK_LATE, (pa_hook_cb_t) source_output_put_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;
+
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->preprocessor),
+ PA_SOURCE_MESSAGE_PREPROCESSOR_TERMINATE, NULL, 0, NULL);
+
+ if (u->source_output_unlink_post_slot)
+ pa_hook_slot_free(u->source_output_unlink_post_slot);
+
+ if (u->source_output_fixate_slot)
+ pa_hook_slot_free(u->source_output_fixate_slot);
+
+ if (u->source_output_put_slot)
+ pa_hook_slot_free(u->source_output_put_slot);
+
+ if (u->asyncmsgq_sink)
+ pa_asyncmsgq_unref(u->asyncmsgq_sink);
+
+ if (u->asyncmsgq_source)
+ pa_asyncmsgq_unref(u->asyncmsgq_source);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ pa_xfree(u);
+}
+
USA.
***/
+#ifndef __PREPROCESSOR_DEF_H__
+#define __PREPROCESSOR_DEF_H__
+
#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,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_ADD_OUTPUT = PA_SOURCE_MESSAGE_MAX + 1, /* 21 */
+ PA_SOURCE_MESSAGE_PREPROCESSOR_REMOVE_OUTPUT,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_DATA,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_REFERENCE,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_RESET_REFERENCE,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_DESTROY,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_TERMINATE,
};
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,
+ PA_SINK_MESSAGE_PREPROCESSOR_REBUILD_RTPOLL = PA_SINK_MESSAGE_MAX,
};
+#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 <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/core-util.h>
+
+#include "processor.h"
+
+#ifdef __DEBUG__
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024)
+
+#ifdef __DEBUG__
+static void debug_open_file(pa_processor *processor);
+static void debug_timestamp_begin(pa_processor *processor);
+static void debug_timestamp_end(pa_processor *processor);
+static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out);
+static void debug_close_file(pa_processor *processor);
+#else
+#define debug_open_file(x)
+#define debug_timestamp_begin(x)
+#define debug_timestamp_end(x)
+#define debug_write_file(x, a, b, c)
+#define debug_close_file(x)
+#endif
+
+static size_t pa_processor_usec_to_frame(pa_usec_t usec, pa_sample_spec *spec) {
+ pa_assert(spec);
+
+ return pa_usec_to_bytes(usec, spec) / pa_frame_size(spec);
+}
+
+pa_processor *pa_processor_new(pa_core *core,
+ uint32_t process_msec,
+ pa_sample_spec *ss,
+ pa_processor_method_t method) {
+ pa_processor *processor;
+ pa_memchunk silence;
+
+ pa_assert(core);
+ pa_assert(ss);
+ pa_assert(method < PROCESSOR_METHOD_MAX);
+
+ processor = pa_xnew0(pa_processor, 1);
+ processor->core = core;
+ processor->interface = pa_processor_method_create(method);
+ processor->process_usec = process_msec * PA_USEC_PER_MSEC;
+ processor->ss = *ss;
+ processor->method = method;
+ processor->process_frames = pa_processor_usec_to_frame(processor->process_usec, &processor->ss);
+ processor->process_bytes = pa_usec_to_bytes(processor->process_usec, &processor->ss);
+
+ pa_assert(processor->interface->create);
+ pa_assert(processor->interface->process);
+ pa_assert(processor->interface->destroy);
+
+ if (!(processor->priv = processor->interface->create(processor->process_frames, &processor->ss))) {
+ pa_log_error("Failed to create processor. rate(%d), channels(%d).", processor->ss.rate, processor->ss.channels);
+ pa_xfree(processor);
+ return NULL;
+ }
+
+ pa_silence_memchunk_get(&core->silence_cache, core->mempool, &silence, &processor->ss, 0);
+ processor->result_memblockq = pa_memblockq_new("source-output memblockq",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &processor->ss,
+ 0,
+ pa_usec_to_bytes(processor->process_usec, &processor->ss),
+ 0,
+ &silence);
+ pa_memblock_unref(silence.memblock);
+
+ pa_log_info("Created processor. memblockq rate(%d), channels(%d), process_msec(%u), process_bytes(%zu), method(%d)",
+ processor->ss.rate,
+ processor->ss.channels,
+ process_msec,
+ pa_usec_to_bytes(processor->process_usec, &processor->ss),
+ method);
+
+ debug_open_file(processor);
+
+ return processor;
+}
+
+void pa_processor_free(pa_processor *processor) {
+
+ pa_assert(processor);
+ pa_assert(processor->priv);
+ pa_assert(processor->interface);
+
+ if (processor->interface->destroy(processor->priv) < 0)
+ pa_log_error("Failed to destroy processor");
+
+ if (processor->result_memblockq)
+ pa_memblockq_free(processor->result_memblockq);
+
+ if (processor->reference)
+ pa_processor_reference_free(processor->reference);
+
+ pa_xfree(processor->interface);
+
+ debug_close_file(processor);
+
+ pa_xfree(processor);
+}
+
+size_t pa_processor_get_process_bytes(pa_processor *p) {
+ pa_assert(p);
+
+ return p->process_bytes;
+}
+
+/* Do not touch chunk value */
+int pa_processor_process(pa_processor *processor, pa_memchunk *chunk) {
+ int r = -1;
+ int8_t *recording = NULL;
+ int8_t *reference = NULL;
+ int8_t *output = NULL;
+
+ pa_memchunk rchunk, ochunk;
+
+ pa_assert(processor);
+ pa_assert(processor->result_memblockq);
+ pa_assert(processor->interface);
+ pa_assert(processor->process_bytes > 0ULL);
+ pa_assert(chunk);
+
+ if (processor->reference) {
+ if (pa_processor_reference_pull(processor->reference, &rchunk) < 0)
+ pa_log_warn("Failed to get memblock from reference memblockq. It will be used silence chunk");
+
+ reference = pa_memblock_acquire_chunk(&rchunk);
+ }
+
+ ochunk.index = 0;
+ ochunk.length = chunk->length;
+ ochunk.memblock = pa_memblock_new(processor->core->mempool, ochunk.length);
+
+ recording = pa_memblock_acquire_chunk(chunk);
+ output = pa_memblock_acquire_chunk(&ochunk);
+
+ debug_timestamp_begin(processor);
+
+ r = processor->interface->process(processor->priv, recording, reference, output);
+ if (r < 0) {
+ pa_log_warn("Failed to process memchunk");
+ goto fail;
+ }
+
+ debug_timestamp_end(processor);
+ debug_write_file(processor, recording, reference, output);
+
+ pa_memblock_release(chunk->memblock);
+ pa_memblock_release(ochunk.memblock);
+
+ if (!processor->reference) {
+ pa_log_debug("Post-process(%s). rec(%" PRIu64 "ms), out(%" PRIu64 "ms)",
+ pa_processor_method_str(processor->method),
+ pa_bytes_to_usec(chunk->length, &processor->ss) / PA_USEC_PER_MSEC,
+ pa_bytes_to_usec(ochunk.length, &processor->ss) / PA_USEC_PER_MSEC);
+ } else {
+ char *reference_dump = pa_processor_reference_dump_index(processor->reference);
+
+ // need to add chunk->index
+ pa_log_debug("Post-process(%s). rec(%" PRIu64 "ms), ref(%" PRIu64 "ms), out(%" PRIu64 "ms), " \
+ "reference memblockq %s, silence %d",
+ pa_processor_method_str(processor->method),
+ pa_bytes_to_usec(chunk->length, &processor->ss) / PA_USEC_PER_MSEC,
+ pa_processor_reference_chunk_to_usec(processor->reference, &rchunk) / PA_USEC_PER_MSEC,
+ pa_bytes_to_usec(ochunk.length, &processor->ss) / PA_USEC_PER_MSEC,
+ reference_dump ? reference_dump : "None", pa_memblock_is_silence(rchunk.memblock));
+
+ pa_xfree(reference_dump);
+ }
+
+ if (processor->reference) {
+ pa_memblock_release(rchunk.memblock);
+ pa_processor_reference_drop(processor->reference, &rchunk);
+ }
+
+ /* Keep this memchunk for next processor */
+ if (pa_memblockq_push(processor->result_memblockq, &ochunk) < 0)
+ pa_log_error("Failed to push out chunk to result memblockq");
+
+ /* The memchunk in the result memblockq's refcount must be one */
+ pa_memblock_unref(ochunk.memblock);
+
+ return r;
+
+fail:
+ pa_memblock_release(chunk->memblock);
+ pa_memblock_release(ochunk.memblock);
+
+ if (processor->reference) {
+ pa_memblock_release(rchunk.memblock);
+ pa_processor_reference_drop(processor->reference, &rchunk);
+ }
+
+ pa_memblock_unref(ochunk.memblock);
+
+ return -1;
+}
+
+pa_memblockq *pa_processor_get_result_memblockq(pa_processor *processor) {
+ pa_assert(processor);
+
+ return processor->result_memblockq;
+}
+
+const char *pa_processor_method_str(pa_processor_method_t method) {
+ static const char *method_table[] = { "speex", "webrtc", "reference_copy", "rnnoise", "none-pse" };
+
+ if (method >= PROCESSOR_METHOD_MAX)
+ return NULL;
+
+ return method_table[method];
+}
+
+pa_processor_method_t pa_processor_method_enum(const char *method) {
+ pa_processor_method_t m;
+
+ if (pa_streq(method, "speex"))
+ m = PROCESSOR_METHOD_SPEEX;
+ else if (pa_streq(method, "webrtc"))
+ m = PROCESSOR_METHOD_WEBRTC;
+ else if (pa_streq(method, "reference_copy"))
+ m = PROCESSOR_METHOD_REFERENCE_COPY;
+ else if (pa_streq(method, "rnnoise"))
+ m = PROCESSOR_METHOD_RNNOISE;
+ else
+ pa_assert(0);
+
+ return m;
+}
+
+bool pa_processor_method_need_reference_structure(pa_processor_method_t m) {
+ if (m == PROCESSOR_METHOD_SPEEX)
+ return true;
+ else if (m == PROCESSOR_METHOD_WEBRTC)
+ return true;
+ else if (m == PROCESSOR_METHOD_REFERENCE_COPY)
+ return true;
+
+ return false;
+}
+
+void pa_processor_attach_reference(pa_processor *processor, pa_processor_reference *reference) {
+ pa_assert(processor);
+ pa_assert(reference);
+
+ processor->reference = reference;
+}
+
+pa_processor_reference *pa_processor_get_reference(pa_processor *processor) {
+ pa_assert(processor);
+
+ return processor->reference;
+}
+
+#ifdef __DEBUG__
+static void debug_open_file(pa_processor *processor) {
+ static int n = 1;
+ char rec[64], ref[64], out[64];
+ const char *method = pa_processor_method_str(processor->method);
+
+ snprintf(rec, sizeof(rec), "/tmp/processor-%s-rec-%d.raw", method, n);
+ snprintf(ref, sizeof(ref), "/tmp/processor-%s-ref-%d.raw", method, n);
+ snprintf(out, sizeof(out), "/tmp/processor-%s-out-%d.raw", method, 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);
+}
+
+static void debug_timestamp_begin(pa_processor *processor) {
+ gettimeofday(&processor->before, NULL);
+}
+
+static void debug_timestamp_end(pa_processor *processor) {
+ gettimeofday(&processor->after, NULL);
+
+ pa_log_debug("It takes time (%ld)ms.",
+ 1000 * (processor->after.tv_sec - processor->before.tv_sec)
+ + (processor->after.tv_usec - processor->before.tv_usec) / 1000);
+}
+
+static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) {
+ if (rec && write(processor->fdrec, rec, processor->process_bytes) <= 0)
+ pa_log_error("Failed to write recording buffer");
+
+ if (ref && write(processor->fdref, ref, pa_processor_reference_process_usec_to_bytes(processor->reference)) <= 0)
+ pa_log_error("Failed to write reference buffer");
+
+ if (out && write(processor->fdout, out, processor->process_bytes) <= 0)
+ pa_log_error("Failed to write ref buffer");
+}
+
+static void debug_close_file(pa_processor *processor) {
+ if (processor->fdrec) {
+ close(processor->fdrec);
+ processor->fdrec = -1;
+ }
+
+ if (processor->fdref) {
+ close(processor->fdref);
+ processor->fdref = -1;
+ }
+
+ if (processor->fdout) {
+ close(processor->fdout);
+ processor->fdout = -1;
+ }
+}
+#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.
+***/
+
+#ifndef foopulseprocessorfoo
+#define foopulseprocessorfoo
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core.h>
+#include <pulsecore/memblock.h>
+#include <pulse/channelmap.h>
+#include <pulse/sample.h>
+
+#include "processor_reference.h"
+#include "method_factory.h"
+
+typedef struct pa_processor_holder pa_processor_holder;
+typedef struct pa_processor {
+ pa_core *core;
+ pa_processor_holder *holder;
+
+ size_t process_frames;
+ size_t process_bytes;
+ pa_usec_t process_usec;
+ pa_sample_spec ss;
+ pa_memblockq *result_memblockq;
+
+ void *priv;
+ pa_processor_method *interface;
+ pa_processor_method_t method;
+
+ pa_processor_reference *reference;
+#ifdef __DEBUG__
+ int fdrec, fdref, fdout;
+ struct timeval before, after;
+#endif
+} pa_processor;
+
+pa_processor *pa_processor_new(pa_core *core,
+ uint32_t process_msec,
+ pa_sample_spec *ss,
+ pa_processor_method_t method);
+
+int pa_processor_process(pa_processor *processor, pa_memchunk *chunk);
+void pa_processor_free(pa_processor *p);
+void pa_processor_reset(pa_processor *p);
+pa_memblockq *pa_processor_get_result_memblockq(pa_processor *processor);
+size_t pa_processor_get_process_bytes(pa_processor *p);
+
+/* reference */
+void pa_processor_attach_reference(pa_processor *processor, pa_processor_reference *reference);
+pa_processor_reference *pa_processor_get_reference(pa_processor *processor);
+
+pa_processor_method_t pa_processor_method_enum(const char *method);
+const char *pa_processor_method_str(pa_processor_method_t method);
+bool pa_processor_method_need_reference_structure(pa_processor_method_t m);
+
+#endif
+
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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 <pulse/timeval.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/core-util.h>
+
+#include "processor_holder.h"
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
+pa_processor_holder *pa_processor_holder_new(pa_sample_spec *ss) {
+ pa_processor_holder *holder;
+
+ pa_assert(ss);
+
+ holder = pa_xnew0(pa_processor_holder, 1);
+ holder->processors = pa_idxset_new(NULL, NULL);
+
+ holder->input= pa_memblockq_new("holder input memblockq",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ ss,
+ 0,
+ 0,
+ 0,
+ NULL);
+
+ holder->output = pa_memblockq_new("holder output memblockq",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ ss,
+ 0,
+ 0,
+ 0,
+ NULL);
+
+ return holder;
+}
+
+void pa_processor_holder_register_processor_sequencial(pa_processor_holder *holder, pa_processor *processor) {
+ pa_assert(holder);
+ pa_assert(processor);
+
+ if (pa_idxset_put(holder->processors, processor, NULL) < 0)
+ pa_log_warn("processor was already registered");
+
+ processor->holder = holder;
+}
+
+int pa_processor_holder_connect_reference(pa_processor_holder *holder, pa_processor_reference *reference) {
+ pa_assert(holder);
+ pa_assert(reference);
+
+ if (holder->reference) {
+ pa_log_error("Don't allow multi referenced processor");
+ return -1;
+ }
+
+ holder->reference = reference;
+
+ return 0;
+}
+
+int pa_processor_holder_push_data(pa_processor_holder *holder, pa_memchunk *chunk) {
+ pa_assert(holder);
+ pa_assert(chunk);
+
+ return pa_memblockq_push(holder->input, chunk);
+}
+
+int pa_processor_holder_push_reference_data(pa_processor_holder *holder, pa_memchunk *chunk) {
+ pa_assert(holder);
+ pa_assert(chunk);
+ pa_assert(holder->reference);
+
+ return pa_processor_reference_push(holder->reference, chunk);
+}
+
+int pa_processor_holder_pull_data(pa_processor_holder *holder, pa_memchunk *chunk) {
+ int r;
+
+ pa_assert(holder);
+ pa_assert(chunk);
+
+ if ((r = pa_memblockq_peek(holder->output, chunk)) < 0)
+ pa_log_error("Failed to get memblock from output memblockq");
+
+ /* chunk ref count must be one after dropping */
+ pa_memblockq_drop(holder->output, chunk->length);
+
+ return r;
+}
+
+int pa_processor_holder_pump(pa_processor_holder *holder) {
+ pa_memblockq *pull_queue;
+ pa_memchunk chunk;
+ pa_processor *p;
+ int ret;
+ size_t size;
+ uint32_t idx;
+
+ pa_assert(holder);
+
+ pull_queue = holder->input;
+
+ PA_IDXSET_FOREACH(p, holder->processors, idx) {
+ if (pa_memblockq_peek(pull_queue, &chunk)) {
+ pa_log_info("Failed to peek data. processor(%s)", pa_processor_method_str(p->method));
+ return -1;
+ }
+
+ size = pa_processor_get_process_bytes(p);
+ if (chunk.length < size) {
+ pa_log_info("Not enough buffer. need to wait more buffer");
+ pa_memblock_unref(chunk.memblock);
+ return -1;
+ }
+
+ /* chunk will be copied */
+ /* TODO : ERROR BUFFERING */
+ if ((ret = pa_processor_process(p, &chunk))) {
+ if (ret == -2) {
+ pa_log_info("Tried to process but processor has a latency");
+ return 0;
+ } else {
+ pa_log_info("Failed to process");
+ return 0;
+ }
+ }
+
+ pa_memblock_unref(chunk.memblock);
+ pa_memblockq_drop(pull_queue, chunk.length);
+
+ pull_queue = pa_processor_get_result_memblockq(p);
+ }
+
+ /* in case of only pass though */
+ if (pull_queue == holder->input)
+ return -1;
+
+ pa_memblockq_peek(pull_queue, &chunk);
+ pa_memblockq_push(holder->output, &chunk);
+ pa_memblock_unref(chunk.memblock);
+ pa_memblockq_drop(pull_queue, chunk.length);
+
+ return 0;
+}
+
+static void processor_holder_destroy_processor(void *data) {
+ pa_processor *p = data;
+
+ pa_assert(p);
+ pa_processor_free(p);
+}
+
+void pa_processor_holder_free(pa_processor_holder *holder) {
+ pa_assert(holder);
+ pa_assert(holder->processors);
+
+ pa_idxset_remove_all(holder->processors, processor_holder_destroy_processor);
+
+ pa_memblockq_free(holder->output);
+ pa_memblockq_free(holder->input);
+
+ pa_xfree(holder);
+}
+
+pa_processor_reference *pa_processor_holder_get_connected_processor_reference(pa_processor_holder *holder) {
+ pa_assert(holder);
+
+ /* It could be NULL */
+ return holder->reference;
+}
+
+void pa_processor_holder_set_current_source(pa_processor_holder *holder, pa_source *source) {
+ pa_assert(holder);
+
+ holder->source = source;
+}
+
+pa_source *pa_processor_holder_get_current_source(pa_processor_holder *holder) {
+ pa_assert(holder);
+
+ return holder->source;
+}
+
+void pa_processor_holder_set_private_data(pa_processor_holder *holder, void *data1, void *data2) {
+ pa_assert(holder);
+
+ holder->data1 = data1;
+ holder->data2 = data2;
+}
+
+void pa_processor_holder_get_private_data(pa_processor_holder *holder, void **data1, void **data2) {
+ pa_assert(holder);
+
+ *data1 = holder->data1;
+ *data2 = holder->data2;
+}
+
+void pa_processor_holder_dump(pa_processor_holder *holder) {
+ pa_memblockq *pull_queue;
+ pa_processor *p;
+ uint32_t idx;
+
+ pa_assert(holder);
+
+ pull_queue = holder->input;
+
+ pa_log_info("processor holder input blocks(%d) index(%" PRId64 "->%" PRId64 ")",
+ pa_memblockq_get_nblocks(pull_queue),
+ pa_memblockq_get_write_index(pull_queue),
+ pa_memblockq_get_write_index(pull_queue));
+
+ PA_IDXSET_FOREACH(p, holder->processors, idx) {
+ pull_queue = pa_processor_get_result_memblockq(p);
+ pa_log_info("processor holder processor(%s) blocks(%d) index(%" PRId64 "->%" PRId64 "), reference(%d)",
+ pa_processor_method_str(p->method),
+ pa_memblockq_get_nblocks(pull_queue),
+ pa_memblockq_get_write_index(pull_queue),
+ pa_memblockq_get_write_index(pull_queue),
+ p->reference ? pa_processor_reference_get_nblocks(p->reference) : 0);
+ }
+
+ pa_log_info("processor holder output blocks(%d) index(%" PRId64 "->%" PRId64 ")",
+ pa_memblockq_get_nblocks(pull_queue),
+ pa_memblockq_get_write_index(pull_queue),
+ pa_memblockq_get_write_index(pull_queue));
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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.
+***/
+
+#ifndef __PROCESSOR_HOLDER_H__
+#define __PROCESSOR_HOLDER_H__
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+#include "processor.h"
+
+typedef struct pa_processor_holder {
+ size_t process_bytes;
+ pa_memblockq *input;
+ pa_memblockq *output;
+ pa_idxset *processors;
+ pa_processor_reference *reference; /* for exporting reference processor */
+ pa_source *source;
+ bool enable;
+ void *data1;
+ void *data2;
+} pa_processor_holder;
+
+pa_processor_holder *pa_processor_holder_new(pa_sample_spec *ss);
+void pa_processor_holder_register_processor_sequencial(pa_processor_holder *holder, pa_processor *processor);
+int pa_processor_holder_connect_reference(pa_processor_holder *holder, pa_processor_reference *reference);
+int pa_processor_holder_push_data(pa_processor_holder *holder, pa_memchunk *chunk);
+int pa_processor_holder_push_reference_data(pa_processor_holder *holder, pa_memchunk *chunk);
+int pa_processor_holder_pull_data(pa_processor_holder *holder, pa_memchunk *chunk);
+int pa_processor_holder_pump(pa_processor_holder *holder);
+void pa_processor_holder_free(pa_processor_holder *holder);
+void pa_processor_holder_set_current_source(pa_processor_holder *holder, pa_source *source);
+pa_source *pa_processor_holder_get_current_source(pa_processor_holder *holder);
+pa_processor_reference *pa_processor_holder_get_connected_processor_reference(pa_processor_holder *holder);
+void pa_processor_holder_set_private_data(pa_processor_holder *holder, void *data1, void *data2);
+void pa_processor_holder_get_private_data(pa_processor_holder *holder, void **data1, void **data2);
+void pa_processor_holder_dump(pa_processor_holder *holder);
+
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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/timeval.h>
+#include <pulsecore/core-util.h>
+
+#include "processor_reference.h"
+
+#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024)
+
+typedef struct pa_processor_reference {
+ pa_core *core;
+ pa_sink *sink;
+ pa_memblockq *memblockq;
+ pa_resampler *resampler;
+ pa_usec_t process_usec;
+ size_t process_bytes;
+ pa_sample_spec ss;
+
+ /* only for pull mode (audio-share) */
+ void *priv;
+ pa_processor_reference_method *interface;
+} pa_processor_reference;
+
+pa_processor_reference *pa_processor_reference_new_custom(pa_core *core,
+ pa_sink *sink,
+ pa_sample_spec *sink_ss,
+ pa_sample_spec *request_ss,
+ pa_usec_t process_usec,
+ pa_processor_reference_method_t method) {
+ pa_processor_reference *reference;
+
+ pa_assert(core);
+ pa_assert(sink_ss);
+ pa_assert(request_ss);
+
+ reference = pa_processor_reference_new(core, sink, sink_ss, request_ss, process_usec);
+ if (!reference) {
+ pa_log_error("Failed to create reference processor");
+ return NULL;
+ }
+
+
+ if (method > PROCESSOR_REFERENCE_METHOD_NONE && method < PROCESSOR_REFERENCE_METHOD_MAX) {
+ reference->interface = pa_processor_reference_method_create(method);
+ if (!reference->interface) {
+ pa_log_error("Failed to create reference interface");
+ return NULL;
+ }
+
+ pa_assert(reference->interface->create);
+ pa_assert(reference->interface->read);
+ pa_assert(reference->interface->destroy);
+
+ if (!(reference->priv = reference->interface->create(request_ss))) {
+ pa_processor_reference_free(reference);
+ pa_log_error("Failed to create custom reference");
+ return NULL;
+ }
+ }
+
+ return reference;
+}
+
+pa_processor_reference *pa_processor_reference_new(pa_core *core,
+ pa_sink *sink,
+ pa_sample_spec *sink_ss,
+ pa_sample_spec *request_ss,
+ pa_usec_t process_usec) {
+ pa_processor_reference *reference;
+ pa_memchunk silence;
+
+ pa_assert(core);
+ pa_assert(sink_ss);
+ pa_assert(request_ss);
+
+ reference = pa_xnew0(pa_processor_reference, 1);
+ reference->core = core;
+ reference->process_usec = process_usec;
+ reference->ss = *request_ss;
+ reference->process_bytes = pa_usec_to_bytes(reference->process_usec, &reference->ss);
+ reference->sink = sink;
+
+ pa_silence_memchunk_get(&core->silence_cache, core->mempool, &silence, request_ss, 0);
+ reference->memblockq = pa_memblockq_new("reference memblockq",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ request_ss,
+ 0,
+ 0,
+ 0,
+ &silence);
+ pa_memblock_unref(silence.memblock);
+
+ if (!pa_sample_spec_equal(sink_ss, request_ss)) {
+ reference->resampler = pa_resampler_new(core->mempool,
+ sink_ss, NULL,
+ request_ss, NULL,
+ core->lfe_crossover_freq,
+ core->resample_method, 0);
+ if (!reference->resampler) {
+ pa_log_error("Failed to create reference resampler");
+ goto fail;
+ }
+
+ pa_log_info("resampler was created for reference");
+ }
+
+ pa_log_info("Created reference memblockq rate(%d), channels(%d)", request_ss->rate, request_ss->channels);
+
+ return reference;
+
+fail:
+ if (reference->memblockq)
+ pa_memblockq_free(reference->memblockq);
+ if (reference->resampler)
+ pa_resampler_free(reference->resampler);
+
+ return NULL;
+}
+
+void pa_processor_reference_free(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ if (reference->resampler)
+ pa_resampler_free(reference->resampler);
+
+ if (reference->memblockq)
+ pa_memblockq_free(reference->memblockq);
+
+ if (reference->interface && reference->interface->destroy) {
+ reference->interface->destroy(reference->priv);
+ pa_xfree(reference->interface);
+ }
+
+ pa_xfree(reference);
+}
+
+void pa_processor_reference_reset(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ pa_memblockq_silence(reference->memblockq);
+ pa_memblockq_flush_write(reference->memblockq, false);
+ pa_resampler_reset(reference->resampler);
+}
+
+int pa_processor_reference_push(pa_processor_reference *reference, pa_memchunk *chunk) {
+ pa_memchunk ochunk;
+ int r;
+
+ pa_assert(reference);
+ pa_assert(chunk);
+ pa_assert(!reference->interface);
+
+ if (reference->resampler) {
+ pa_resampler_run(reference->resampler, chunk, &ochunk);
+ chunk = &ochunk;
+ }
+
+ if ((r = pa_memblockq_push(reference->memblockq, chunk)) < 0)
+ pa_log_error("Failed to push chunk to reference memblockq");
+
+ if (reference->resampler)
+ pa_memblock_unref(chunk->memblock);
+
+ pa_log_debug("Pushed reference data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")",
+ chunk->length,
+ reference->process_usec / PA_USEC_PER_MSEC,
+ pa_memblockq_get_nblocks(reference->memblockq),
+ pa_memblockq_get_write_index(reference->memblockq),
+ pa_memblockq_get_read_index(reference->memblockq));
+
+ return r;
+}
+
+int pa_processor_reference_pull(pa_processor_reference *reference, pa_memchunk *chunk) {
+ int r;
+ size_t length;
+
+ pa_assert(reference);
+ pa_assert(chunk);
+
+ length = reference->process_bytes;
+
+ if (reference->interface) {
+ pa_memchunk rchunk;
+ void *buffer;
+ size_t ret;
+
+ pa_assert(reference->interface->read);
+
+ rchunk.index = 0;
+ rchunk.length = length;
+ rchunk.memblock = pa_memblock_new(reference->core->mempool, rchunk.length);
+
+ buffer = pa_memblock_acquire_chunk(&rchunk);
+ if (reference->interface->read(reference->priv, buffer, length) < 0) {
+ pa_memblock_release(rchunk.memblock);
+ pa_log_error("Failed to read reference source. length(%zu), ret(%zu)", length, ret);
+ goto exit;
+ }
+
+ pa_memblock_release(rchunk.memblock);
+
+ pa_memblockq_push(reference->memblockq, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+ }
+
+exit:
+ if ((r = pa_memblockq_peek_fixed_size(reference->memblockq, length, chunk)) < 0) {
+ pa_log_error("Failed to get memblock from reference memblockq");
+ return r;
+ }
+
+ return r;
+}
+
+// TODO: It could be pushed memblock precisely.
+void pa_processor_reference_add_latency_padding(pa_processor_reference *reference, pa_usec_t latency) {
+ int64_t write_index, read_index;
+ pa_memchunk silence;
+ unsigned int n = 1; /* it must be added 1 block at least to prevent glitch sound */
+
+ pa_assert(reference);
+
+ if (latency > reference->process_usec)
+ n = (unsigned int)(latency / reference->process_usec);
+
+ pa_silence_memchunk_get(&reference->core->silence_cache,
+ reference->core->mempool, &silence,
+ &reference->ss,
+ pa_usec_to_bytes(reference->process_usec, &reference->ss));
+
+ write_index = pa_memblockq_get_write_index(reference->memblockq);
+ read_index = pa_memblockq_get_read_index(reference->memblockq);
+
+ while (n--)
+ pa_memblockq_push(reference->memblockq, &silence);
+
+ pa_memblock_unref(silence.memblock);
+
+ pa_log_info("push n(%u) silence blocks. latency(%" PRId64 "), ref_process_msec(%" PRId64 ") "
+ "write_index(%" PRId64 "->%" PRId64 "), read_index(%" PRId64 "->%" PRId64 ")",
+ pa_memblockq_get_nblocks(reference->memblockq),
+ latency,
+ pa_bytes_to_usec(silence.length, &reference->ss) / PA_USEC_PER_MSEC,
+ write_index, pa_memblockq_get_write_index(reference->memblockq),
+ read_index, pa_memblockq_get_read_index(reference->memblockq));
+}
+
+void pa_processor_reference_drop(pa_processor_reference *reference, pa_memchunk *chunk) {
+ pa_assert(reference);
+ pa_assert(chunk);
+
+ pa_memblock_unref(chunk->memblock);
+ pa_memblockq_drop(reference->memblockq, chunk->length);
+}
+
+unsigned pa_processor_reference_get_nblocks(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ return pa_memblockq_get_nblocks(reference->memblockq);
+}
+
+pa_usec_t pa_processor_reference_chunk_to_usec(pa_processor_reference *reference, pa_memchunk *chunk) {
+ pa_assert(reference);
+ pa_assert(chunk);
+
+ return pa_bytes_to_usec(chunk->length, &reference->ss);
+}
+
+size_t pa_processor_reference_process_usec_to_bytes(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ return pa_usec_to_bytes(reference->process_usec, &reference->ss);
+
+}
+
+char *pa_processor_reference_dump_index(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ return pa_sprintf_malloc("windex:rindex(%" PRId64 ":%" PRId64 ")",
+ pa_memblockq_get_write_index(reference->memblockq),
+ pa_memblockq_get_read_index(reference->memblockq));
+}
+
+pa_sink *pa_processor_reference_get_sink(pa_processor_reference *reference) {
+ pa_assert(reference);
+
+ return reference->sink;
+}
+
+pa_processor_reference_method_t pa_processor_reference_method_enum(const char *method) {
+ pa_processor_reference_method_t m;
+
+ pa_assert(method);
+
+ if (pa_streq(method, "audio-share"))
+ m = PROCESSOR_REFERENCE_METHOD_AUDIOSHARE;
+ else if (pa_streq(method, "filesrc"))
+ m = PROCESSOR_REFERENCE_METHOD_FILESRC;
+ else
+ pa_assert(0);
+
+ return m;
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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.
+***/
+
+#ifndef foopulseprocessorreferencefoo
+#define foopulseprocessorreferencefoo
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core.h>
+#include <pulsecore/memblock.h>
+#include <pulse/sample.h>
+
+#include "method_factory.h"
+
+typedef struct pa_processor_reference pa_processor_reference;
+
+pa_processor_reference *pa_processor_reference_new(pa_core *core,
+ pa_sink *sink,
+ pa_sample_spec *sink_ss,
+ pa_sample_spec *request_ss,
+ pa_usec_t process_usec);
+pa_processor_reference *pa_processor_reference_new_custom(pa_core *core,
+ pa_sink *sink,
+ pa_sample_spec *sink_ss,
+ pa_sample_spec *request_ss,
+ pa_usec_t process_usec,
+ pa_processor_reference_method_t method);
+void pa_processor_reference_free(pa_processor_reference *reference);
+void pa_processor_reference_reset(pa_processor_reference *reference);
+int pa_processor_reference_push(pa_processor_reference *reference, pa_memchunk *chunk);
+int pa_processor_reference_pull(pa_processor_reference *reference, pa_memchunk *chunk);
+void pa_processor_reference_add_latency_padding(pa_processor_reference *reference, pa_usec_t latency);
+void pa_processor_reference_drop(pa_processor_reference *reference, pa_memchunk *chunk);
+unsigned pa_processor_reference_get_nblocks(pa_processor_reference *reference);
+pa_usec_t pa_processor_reference_chunk_to_usec(pa_processor_reference *reference, pa_memchunk *chunk);
+size_t pa_processor_reference_process_usec_to_bytes(pa_processor_reference *reference);
+char *pa_processor_reference_dump_index(pa_processor_reference *reference);
+
+pa_sink *pa_processor_reference_get_sink(pa_processor_reference *reference);
+pa_processor_reference_method_t pa_processor_reference_method_enum(const char *method);
+
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2023 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 <stdio.h>
+#include <pulse/sample.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+
+void *processor_reference_filesrc_create(pa_sample_spec *ss) {
+ FILE *f;
+
+ f = fopen("/opt/usr/media/reference.raw", "r");
+ if (!f)
+ pa_log_error("Failed to open reference");
+
+ return (void *)f;
+}
+
+size_t processor_reference_filesec_read(void *priv, void *ref, size_t length) {
+ size_t n;
+ FILE *f = priv;
+
+ pa_assert(f);
+
+ n = fread(ref, sizeof(char), length, f);
+
+ if (n != length) {
+ pa_log_error("invalid read length");
+ return 0;
+ }
+
+ return n;
+}
+
+void processor_reference_filesrc_destroy(void *priv) {
+ FILE *f = priv;
+
+ pa_assert(f);
+ fclose(f);
+}
#include "hal-interface.h"
#include "tizenaudio-util.h"
-#include "echo-cancel/echo-cancel-def.h"
+#include "preprocessor/preprocessor-def.h"
+#include "preprocessor/processor_holder.h"
+#include "preprocessor/processor_reference.h"
#define DEFAULT_SINK_NAME "tizenaudio-sink2"
char* card;
char* device;
bool first;
- bool echo_on;
+ bool preprocess_state;
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;
+ pa_msgobject *preprocessor;
+ pa_asyncmsgq *preprocessor_asyncmsgq;
+ void *holder;
+ pa_rtpoll_item *preprocessor_poll_item;
};
static void userdata_free(struct userdata *u);
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: {
+ case PA_SINK_MESSAGE_PREPROCESSOR_REBUILD_RTPOLL: {
struct arguments {
pa_msgobject *o;
pa_asyncmsgq *q;
+ void *holder;
} *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);
+ u->preprocessor = args->o;
+ u->preprocessor_asyncmsgq = args->q;
+ u->holder = args->holder;
+ u->preprocessor_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q);
+ u->preprocess_state = true;
} else {
- pa_rtpoll_item_free(u->ec_poll_item);
- u->ec_poll_item = NULL;
- u->ec_object = NULL;
+ pa_rtpoll_item_free(u->preprocessor_poll_item);
+ u->preprocessor_poll_item = NULL;
+ u->preprocessor = NULL;
+ u->holder = NULL;
+ u->preprocess_state = false;
}
return 0;
}
+ case PA_SINK_MESSAGE_SET_STATE: {
+ struct state_data {
+ pa_sink_state_t state;
+ pa_suspend_cause_t suspend_cause;
+ } state;
+
+ pa_processor_reference *reference;
+ pa_sink *sink;
+
+ state = *((struct state_data *)data);
+
+ if (!u->holder)
+ break;
+
+ reference = pa_processor_holder_get_connected_processor_reference(u->holder);
+ if (!reference)
+ break;
+
+ sink = pa_processor_reference_get_sink(reference);
+ if (!sink)
+ break;
+
+ if (sink == u->sink && state.state == PA_SINK_RUNNING) {
+ u->preprocess_state = true;
+ pa_asyncmsgq_post(u->preprocessor_asyncmsgq, u->preprocessor,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_RESET_REFERENCE, (void *)true, 0, NULL, NULL);
+ } else {
+ u->preprocess_state = false;
+ }
+
+ break;
+ }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
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);
+ if (u->preprocess_state) {
+ pa_asyncmsgq_post(u->preprocessor_asyncmsgq, u->preprocessor,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_REFERENCE, u->holder, 0, &chunk, NULL);
}
pa_memblock_unref(chunk.memblock);
#include "hal-interface.h"
#include "tizenaudio-util.h"
-#include "echo-cancel/echo-cancel-def.h"
+#include "preprocessor/preprocessor-def.h"
+#include "preprocessor/processor_holder.h"
#define DEFAULT_SOURCE_NAME "tizenaudio-source2"
char* card;
char* device;
bool first;
- bool echo_on;
+ bool preprocess_state;
pa_rtpoll_item *rtpoll_item;
pa_usec_t latency_time;
pa_hal_interface *hal_interface;
- /* for echo-cancel */
- pa_msgobject *ec_object;
- pa_asyncmsgq *ec_asyncmsgq;
- pa_rtpoll_item *ec_poll_item;
+ /* for preprocessor */
+ pa_msgobject *preprocessor;
+ pa_asyncmsgq *preprocessor_asyncmsgq;
};
static void userdata_free(struct userdata *u);
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_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;
+ case PA_SOURCE_MESSAGE_ADD_OUTPUT: {
+ pa_source_output *output = PA_SOURCE_OUTPUT(data);
+
+ if (output->flags & PA_SOURCE_OUTPUT_PREPROCESSOR) {
+ pa_processor_holder *holder = (pa_processor_holder *)output->thread_info.processor_holder;
+
+ pa_assert(holder);
+
+ pa_processor_holder_get_private_data(holder, (void *)&u->preprocessor, (void *)&u->preprocessor_asyncmsgq);
+ pa_asyncmsgq_send(u->preprocessor_asyncmsgq, u->preprocessor, PA_SOURCE_MESSAGE_PREPROCESSOR_ADD_OUTPUT, data, offset, chunk);
+
+ u->preprocess_state = true;
}
+ break;
+ }
+ case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
+ pa_source_output *output = PA_SOURCE_OUTPUT(data);
- return 0;
+ if (output->flags & PA_SOURCE_OUTPUT_PREPROCESSOR) {
+ pa_processor_holder *holder = (pa_processor_holder *)output->thread_info.processor_holder;
+
+ pa_assert(holder);
+
+ pa_processor_holder_get_private_data(holder, (void *)&u->preprocessor, (void *)&u->preprocessor_asyncmsgq);
+ pa_asyncmsgq_send(u->preprocessor_asyncmsgq, u->preprocessor, PA_SOURCE_MESSAGE_PREPROCESSOR_REMOVE_OUTPUT, data, offset, chunk);
+
+ u->preprocess_state = false;
+ }
+ break;
}
+ case PA_SOURCE_MESSAGE_PREPROCESSOR_TERMINATE:
+ u->preprocess_state = false;
+ break;
+ default:
+ break;
}
return pa_source_process_msg(o, code, data, offset, chunk);
chunk.index = 0;
chunk.length = (size_t)frames_to_read * frame_size;
- if (u->echo_on) {
- pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object,
- PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL);
+ if (u->preprocess_state) {
+ pa_asyncmsgq_post(u->preprocessor_asyncmsgq, u->preprocessor,
+ PA_SOURCE_MESSAGE_PREPROCESSOR_PUSH_DATA, u->source, 0, &chunk, NULL);
} else {
pa_source_post(u->source, &chunk);
}