Add Acoustic Echo Cancellation services 69/257069/14 accepted/tizen/unified/20210426.002351 submit/tizen/20210423.055047 submit/tizen/20210423.064540
authorJaechul Lee <jcsing.lee@samsung.com>
Fri, 12 Mar 2021 05:31:25 +0000 (14:31 +0900)
committerJaechul Lee <jcsing.lee@samsung.com>
Fri, 23 Apr 2021 02:59:41 +0000 (11:59 +0900)
Added AEC services

[Version] 0.13.7
[Issue Type] New feature

Change-Id: I4ac638a0d61dae635efecd5e198bf2d2fec28557
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
14 files changed:
Makefile.am
aec/Makefile.am [new file with mode: 0644]
aec/algo_speex.c [new file with mode: 0644]
aec/algo_speex.h [new file with mode: 0644]
aec/loopback.c [new file with mode: 0644]
aec/loopback.h [new file with mode: 0644]
aec/main.c [new file with mode: 0644]
aec/static_queue.c [new file with mode: 0644]
aec/static_queue.h [new file with mode: 0644]
configure.ac
packaging/audio-aec.conf [new file with mode: 0644]
packaging/audio-aec.service [new file with mode: 0644]
packaging/libmm-sound.spec
packaging/org.tizen.AudioAec.service [new file with mode: 0644]

index 1e21cee..88a8d74 100644 (file)
@@ -2,9 +2,12 @@ SUBDIRS = common \
                pkgconfig \
                . \
                testsuite
-
 SUBDIRS += focus_server
 
+if ENABLE_AEC
+SUBDIRS += aec
+endif
+
 if UNITTESTS_ENABLED
 SUBDIRS += unittest
 endif
diff --git a/aec/Makefile.am b/aec/Makefile.am
new file mode 100644 (file)
index 0000000..a0748a7
--- /dev/null
@@ -0,0 +1,10 @@
+bin_PROGRAMS = audio_aec
+
+audio_aec_SOURCES = loopback.c static_queue.c algo_speex.c main.c
+
+audio_aec_CFLAGS = -I$(srcdir) $(ALSA_CFLAGS) $(SPEEX_CFLAGS) $(DBUS_CFLAGS) $(DLOG_CFLAGS) $(GLIB2_CFLAGS) $(GIO_CFLAGS)
+audio_aec_CFLAGS += -fPIC -pie
+audio_aec_CFLAGS += -DLOG_TAG=\"AUDIO_AEC\"
+
+audio_aec_LDADD = $(ALSA_LIBS) $(SPEEX_LIBS) $(DBUS_LIBS) $(DLOG_LIBS) $(GLIB2_LIBS) $(GIO_LIBS) -lpthread
+
diff --git a/aec/algo_speex.c b/aec/algo_speex.c
new file mode 100644 (file)
index 0000000..60e762d
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <dlog.h>
+#include <speex/speex.h>
+#include <speex/speex_preprocess.h>
+#include <speex/speex_echo.h>
+#include "algo_speex.h"
+
+//#define __DEBUG__
+
+#ifdef __DEBUG__
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+static int fdrec, fdref, fdout;
+#endif
+
+static int speex_init(int nframes, int rate, int channels);
+static int speex_process(unsigned char *rec, unsigned char *ref, unsigned char *out);
+static int speex_deinit();
+
+static struct ec_speex {
+       SpeexEchoState *echo_state;
+       SpeexPreprocessState *preprocess;
+       int nframes;
+       int filter_frames;
+       int channels;
+       int rate;
+} sp;
+
+static ec_operation_t op = {
+       .init = speex_init,
+       .process = speex_process,
+       .deinit = speex_deinit,
+};
+
+ec_operation_t *get_speex_instance()
+{
+       return &op;
+}
+
+static int speex_init(int nframes, int rate, int channels)
+{
+       sp.nframes = nframes;
+       sp.filter_frames = nframes; /* TODO:It takes much time when 10 times of nframe */
+       sp.channels = channels;
+       sp.rate = rate;
+
+       LOGI("nframes(%d), filter_frames(%d) rate(%d), channels(%d)",
+                       sp.nframes, sp.filter_frames, sp.rate, sp.channels);
+
+       sp.echo_state = speex_echo_state_init_mc(sp.nframes, sp.filter_frames,
+                                                       sp.channels, sp.channels);
+       if (!sp.echo_state) {
+               LOGE("speex_echo_state_init_mc failed\n");
+               return -1;
+       }
+
+       sp.preprocess = speex_preprocess_state_init(sp.nframes, sp.rate);
+       if (!sp.preprocess) {
+               LOGE("speex_echo_state_init_mc failed\n");
+               return -1;
+       }
+
+       if (speex_echo_ctl(sp.echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &sp.rate)) {
+               LOGE("speex_echo_ctl SET_SAMPLING_RATE failed\n");
+               return -1;
+       }
+
+       if (speex_preprocess_ctl(sp.preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE, sp.echo_state)) {
+               LOGE("speex_echo_ctl SET_ECHO_STATE failed\n");
+               return -1;
+       }
+
+#ifdef __DEBUG__
+       unlink("/tmp/rec.raw");
+       unlink("/tmp/ref.raw");
+       unlink("/tmp/out.raw");
+
+       fdrec = open("/tmp/rec.raw", O_RDWR | O_CREAT | O_TRUNC, 777);
+       fdref = open("/tmp/ref.raw", O_RDWR | O_CREAT | O_TRUNC, 777);
+       fdout = open("/tmp/out.raw", O_RDWR | O_CREAT | O_TRUNC, 777);
+#endif
+
+       return 0;
+}
+
+static int speex_process(unsigned char *rec, unsigned char *ref, unsigned char *out)
+{
+#ifdef __DEBUG__
+       struct timeval before,after;
+       write(fdrec, rec, sp.nframes * 4);
+       write(fdref, ref, sp.nframes * 4);
+       gettimeofday(&before,NULL);
+#endif
+
+       speex_echo_cancellation(sp.echo_state,
+                               (const spx_int16_t *)rec,
+                               (const spx_int16_t *)ref,
+                               (spx_int16_t *)out);
+       speex_preprocess_run(sp.preprocess, (spx_int16_t *)out);
+
+       rec += sp.nframes * sp.channels;
+       ref += sp.nframes * sp.channels;
+       out += sp.nframes * sp.channels;
+
+       speex_echo_cancellation(sp.echo_state,
+                               (const spx_int16_t *)rec,
+                               (const spx_int16_t *)ref,
+                               (spx_int16_t *)out);
+       speex_preprocess_run(sp.preprocess, (spx_int16_t *)out);
+
+#ifdef __DEBUG__
+       gettimeofday(&after,NULL);
+       LOGE("It takes time(%ld)",
+                       1000*(after.tv_sec-before.tv_sec) + (after.tv_usec-before.tv_usec)/1000);
+       write(fdout, out, sp.nframes * 4);
+#endif
+
+       return 0;
+}
+
+static int speex_deinit()
+{
+       if (sp.echo_state)
+               speex_echo_state_destroy(sp.echo_state);
+       if (sp.preprocess)
+               speex_preprocess_state_destroy(sp.preprocess);
+
+#ifdef __DEBUG__
+       if (fdrec)
+               close(fdrec);
+       if (fdref)
+               close(fdref);
+       if (fdout)
+               close(fdout);
+#endif
+
+       return 0;
+}
+
diff --git a/aec/algo_speex.h b/aec/algo_speex.h
new file mode 100644 (file)
index 0000000..20c846f
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+#ifndef __ALGO_SPEEX__
+#define __ALGO_SPEEX__
+
+#include "loopback.h"
+
+ec_operation_t *get_speex_instance();
+
+#endif // __ALGO_SPEEX__
diff --git a/aec/loopback.c b/aec/loopback.c
new file mode 100644 (file)
index 0000000..56e87f7
--- /dev/null
@@ -0,0 +1,912 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <pthread.h>
+#include <alsa/asoundlib.h>
+#include <dlog.h>
+
+#include "loopback.h"
+#include "static_queue.h"
+
+#define FIXME_FORMAT_SIZE 2
+#define CTL_LOOPBACK_DEVICE "hw:Loopback"
+#define PCM_MASTER_ACTIVE "PCM Master Active"
+#define PCM_SLAVE_ACTIVE "PCM Slave Active"
+
+enum {
+       CAPTURE = 0,
+       PLAYBACK,
+       DEV_MAX
+};
+
+enum {
+       POLL_WAKEUP = 0,
+       POLL_CTRL,
+       POLL_CAPTURE,
+       POLL_PLAYBACK,
+       POLL_MAX
+};
+
+enum {
+       STATE_STOPPED,
+       STATE_RUNNING,
+};
+
+struct loopback_dev {
+       snd_pcm_t *handle;
+       char *name;
+       int mode;
+       int rate;
+       int channels;
+       int format;
+       unsigned int buffer_size;
+       unsigned int buffer_time;
+       unsigned int period_size;
+       unsigned int buffer_frame;
+       unsigned int period_time;
+};
+
+struct loopback {
+       char *name;
+       struct loopback_dev *dev[DEV_MAX];
+       struct pollfd pfds[POLL_MAX];
+       int poll_cnt;
+       int wakeup[2];
+       pthread_t t;
+       volatile int state;
+       volatile bool quit;
+
+       char *empty_buf;
+       int ec_frame;
+       int rate;
+       int channels;
+
+       /* loopback ctl */
+       snd_ctl_t *ctl;
+       int ctl_id;
+       int ctl_subid;
+       snd_ctl_elem_value_t *elm_active;
+
+       /* related to reference */
+       ec_operation_t *op;
+
+       sq *q;
+
+       /* reference queue */
+       loopback_t *master;
+       sq *refq; /* recording consumption */
+       sq *master_refq; /* playback feeding */
+
+       int logcnt;
+};
+
+static int __open_loopback_controls(loopback_t *loopback, const char *dev, const char *elm);
+static int __check_ctl_active_event(loopback_t *loopback, int *status);
+static int __handle_revents(loopback_t *loopback, int mode, unsigned short revents);
+static void *__loop_thread(void *userdata);
+
+static void  __close_dev(loopback_t *loopback);
+static int __open_dev(loopback_t *loopback, int index, struct pollfd *pfd);
+static int __xrun_recovery(loopback_dev_t *dev, int err);
+static int __print_buffer(loopback_t *loopback);
+
+loopback_dev_t *loopback_alloc_device(const char *name, int mode, int rate,
+                                       int channels, int format, int buffer_frame)
+{
+       loopback_dev_t *dev = (loopback_dev_t *)calloc(1, sizeof(struct loopback_dev));
+       if (!dev) {
+               LOGE("Failed to alloc memory");
+               return NULL;
+       }
+
+       dev->name = strdup(name);
+       dev->mode = mode;
+       dev->rate = rate;
+       dev->channels = channels;
+       dev->format = format;
+       dev->buffer_frame = buffer_frame;
+
+       return dev;
+}
+
+int loopback_free_device(loopback_dev_t *dev)
+{
+       if (!dev)
+               return -1;
+
+       if (dev->name) {
+               free(dev->name);
+               dev->name = NULL;
+       }
+
+       return 0;
+}
+
+static int __open_loopback_controls(loopback_t *loopback, const char *dev, const char *elm)
+{
+       int k, err;
+       char ctl_name[64];
+       const char *elm_name = elm;
+
+       loopback->ctl_id = dev[12] - '0';
+       loopback->ctl_subid = dev[14] - '0';
+
+       for (k = 0; *dev != ',' && *dev != '\0'; dev++)
+               ctl_name[k++] = *dev;
+       ctl_name[k] = '\0';
+
+       err = snd_ctl_open(&loopback->ctl, ctl_name, SND_CTL_NONBLOCK);
+       if (err < 0) {
+               LOGE("Failed to open capture control. name(%s)", ctl_name);
+               goto fail;
+       }
+
+       err = snd_ctl_subscribe_events(loopback->ctl, 1);
+       if (err < 0) {
+               LOGE("Failed to subscribe ctrl event");
+               goto fail;
+       }
+
+       if (snd_ctl_elem_value_malloc(&loopback->elm_active) < 0) {
+               LOGE("Failed to alloc elem");
+               goto fail;
+       }
+
+       snd_ctl_elem_value_set_interface(loopback->elm_active, SND_CTL_ELEM_IFACE_PCM);
+       snd_ctl_elem_value_set_device(loopback->elm_active, loopback->ctl_id);
+       snd_ctl_elem_value_set_subdevice(loopback->elm_active, loopback->ctl_subid);
+       snd_ctl_elem_value_set_name(loopback->elm_active, elm_name);
+
+       LOGD("loopback ctrl opened. name(%s) id(%d) subid(%d) elm_name(%s)",
+                       ctl_name, loopback->ctl_id, loopback->ctl_subid, elm_name);
+
+       return 0;
+
+fail:
+       if (loopback->ctl)
+               snd_ctl_close(loopback->ctl);
+       if (loopback->elm_active)
+               snd_ctl_elem_value_free(loopback->elm_active);
+
+       return -1;
+}
+
+loopback_t *loopback_create(const char *name,
+                               loopback_dev_t *capture,
+                               loopback_dev_t *playback,
+                               int process_frame, int nbuffer)
+{
+       const char *elm_name;
+       char *dev_loopback;
+       loopback_t *loopback;
+       int bytes, len;
+
+       if (capture->rate != playback->rate ||
+               capture->channels != playback->channels ||
+               capture->format != playback->format) {
+               /* TODO: support resample or remap */
+               LOGE("capture and playback info is not matched."
+                               "rate(%d:%d), channels(%d:%d), format(%d:%d)",
+                               capture->rate, playback->rate,
+                               capture->channels, playback->channels,
+                               capture->format, playback->format);
+               return NULL;
+       }
+
+       len = strlen(CTL_LOOPBACK_DEVICE);
+       if (!strncmp(capture->name, CTL_LOOPBACK_DEVICE, len))
+               dev_loopback = capture->name;
+       else if (!strncmp(playback->name, CTL_LOOPBACK_DEVICE, len))
+               dev_loopback = playback->name;
+       else {
+               LOGE("not support");
+               return NULL;
+       }
+
+       loopback = (loopback_t *)calloc(1, sizeof(struct loopback));
+       if (!loopback) {
+               LOGE("Failed to alloc memory");
+               return NULL;
+       }
+       loopback->name = name ? strdup(name) : strdup("noname");
+       loopback->dev[CAPTURE] = capture;
+       loopback->dev[PLAYBACK] = playback;
+       loopback->state = STATE_STOPPED;
+
+       /* TODO: Support resample */
+       loopback->channels = capture->channels;
+       loopback->rate = capture->rate;
+
+       elm_name = dev_loopback == capture->name ? PCM_SLAVE_ACTIVE : PCM_MASTER_ACTIVE;
+       if (__open_loopback_controls(loopback, dev_loopback, elm_name)) {
+               LOGE("Failed to open loopback control. name(%s)", elm_name);
+               goto exit;
+       }
+
+       loopback->ec_frame = process_frame;
+       bytes = loopback->ec_frame * capture->channels * FIXME_FORMAT_SIZE;
+
+       // TEMP: padding for speex
+       bytes += bytes >> 1;
+
+       loopback->q = create_static_queue(bytes, nbuffer);
+       if (!loopback->q) {
+               LOGE("Failed to create sq");
+               goto exit;
+       }
+       LOGI("created static-queue bytes(%d), n(%d)", bytes, nbuffer);
+
+       loopback->empty_buf = (char *)calloc(1, bytes);
+       if (!loopback->empty_buf) {
+               LOGE("Failed to alloc empty_buf\n");
+               goto exit;
+       }
+
+       if (pipe(loopback->wakeup)) {
+               LOGE("Failed to create pipe");
+               goto exit;
+       }
+
+       return loopback;
+
+exit:
+       if (loopback->ctl)
+               snd_ctl_close(loopback->ctl);
+       if (loopback->elm_active)
+               snd_ctl_elem_value_free(loopback->elm_active);
+       if (loopback->q)
+               destory_static_queue(loopback->q);
+       if (loopback->empty_buf)
+               free(loopback->empty_buf);
+       if (loopback)
+               free(loopback);
+
+       return NULL;
+}
+
+int loopback_start(loopback_t *loopback)
+{
+       if (!loopback->q) {
+               LOGE("loopback is not ready\n");
+               return -1;
+       }
+
+       loopback->pfds[POLL_WAKEUP].fd = loopback->wakeup[0];
+       loopback->pfds[POLL_WAKEUP].events = POLLIN;
+
+       /* poll fds */
+       if (snd_ctl_poll_descriptors(loopback->ctl, &(loopback->pfds[POLL_CTRL]), 1) <= 0) {
+               LOGE("cannot get ctrl descriptor\n");
+               return -1;
+       }
+
+       loopback->poll_cnt = POLL_CTRL + 1;
+
+       if (loopback->op) {
+               if (loopback->op->init(loopback->ec_frame, loopback->rate, loopback->channels)) {
+                       LOGE("ec init failed\n");
+                       return -1;
+               }
+       }
+
+       if (pthread_create(&loopback->t, NULL, __loop_thread, (void *)loopback) < 0) {
+               LOGE("failed to create thread\nn");
+               return -1;
+       }
+
+       return 0;
+}
+
+int loopback_stop(loopback_t *loopback)
+{
+       ssize_t n;
+       int status;
+       char dummy = 'c';
+
+       if (!loopback)
+               return -1;
+
+       loopback->quit = 1;
+       n = write(loopback->wakeup[1], &dummy, 1);
+       if (n <= 0) {
+               LOGE("Failed to wake up thread");
+               return -1;
+       }
+
+       if (pthread_join(loopback->t, (void **)&status)) {
+               LOGE("thread join failed\nn");
+               return -1;
+       }
+
+       loopback->quit = 0;
+
+       return 0;
+}
+
+int loopback_destroy(loopback_t *loopback)
+{
+       if (!loopback)
+               return -1;
+
+       if (loopback->state == STATE_RUNNING) {
+               LOGE("Failed to destroy loopback. state running");
+               return -1;
+       }
+
+       if (loopback->q)
+               destory_static_queue(loopback->q);
+
+       if (loopback->refq)
+               destory_static_queue(loopback->refq);
+
+       if (loopback->name) {
+               free(loopback->name);
+               loopback->name = NULL;
+       }
+
+       if (loopback->empty_buf) {
+               free(loopback->empty_buf);
+               loopback->empty_buf = NULL;
+       }
+
+       if (loopback->elm_active) {
+               snd_ctl_elem_value_free(loopback->elm_active);
+               loopback->elm_active = NULL;
+       }
+
+       if (loopback->ctl) {
+               snd_ctl_close(loopback->ctl);
+               loopback->ctl = NULL;
+       }
+
+       __close_dev(loopback);
+
+       free(loopback);
+
+       return 0;
+}
+
+int loopback_bind_reference(loopback_t *loopback, loopback_t *ref, ec_operation_t *op)
+{
+       if (loopback->state == STATE_RUNNING) {
+               LOGE("Failed to bind. state(%d)", loopback->state);
+               return -1;
+       }
+
+       if (loopback->refq) {
+               LOGE("Failed to bind. already bound.");
+               return -1;
+       }
+
+       loopback->op = op;
+       loopback->refq = create_static_queue(0, 0);
+
+       ref->master = loopback;
+       ref->master_refq = loopback->refq;
+
+       LOGI("bind loopback master(%s), refernece(%s)", loopback->name, ref->name);
+
+       return 0;
+}
+
+static int __handle_revents(struct loopback *loopback, int mode, unsigned short revents)
+{
+       int err;
+       struct loopback_dev *dev = loopback->dev[mode];
+       snd_pcm_sframes_t frames = loopback->ec_frame;
+       snd_pcm_sframes_t avail;
+
+       if (revents & POLLIN) {
+               unsigned char *recbuf;
+               sqbuffer *rec;
+
+               avail = snd_pcm_avail_update(dev->handle);
+               if (avail < 0) {
+                       LOGW("%s: Can't get avail: %s", dev->name, snd_strerror(avail));
+
+                       if (__xrun_recovery(dev, avail)) {
+                               LOGE("%s: AEC will be stopped.", dev->name);
+                               return 0;
+                       }
+
+               } else if (avail < frames) {
+                       LOGW("%s: not enough avail(%ld) < frames(%ld)\n", dev->name, avail, frames);
+                       return 0;
+               }
+
+               rec = sq_get_node_lock(loopback->q);
+               if (!rec) {
+                       LOGE("%s: Failed to get valid sq node. maybe overflow", dev->name);
+                       return 0;
+               }
+
+               recbuf = sq_get_buffer(rec);
+               if ((err = snd_pcm_readi(dev->handle, recbuf, frames)) < 0) {
+                       LOGE("%s: Failed to read: %s(%d)", dev->name, snd_strerror(err), err);
+
+                       sq_put_node_lock(rec);
+                       __xrun_recovery(dev, err);
+
+                       return 0;
+               }
+
+               sq_enqueue_node(loopback->q, rec);
+
+       } else if (revents & POLLOUT) {
+               unsigned char *playbuf;
+               sqbuffer *play;
+
+               avail = snd_pcm_avail_update(dev->handle);
+               if (avail < 0) {
+                       LOGW("%s: Can't get avail. Try to recover: %s", dev->name, snd_strerror(avail));
+
+                       if (__xrun_recovery(dev, avail))
+                               LOGE("%s: Failed to recover. AEC will be stopped.", dev->name);
+
+                       return 0;
+
+               } else if (avail < frames) {
+                       LOGW("%s: not enough avail(%ld) < frames(%ld)\n", dev->name, avail, frames);
+                       return 0;
+               }
+
+               play = sq_dequeue_node(loopback->q);
+               if (!play) {
+                       if ((err = snd_pcm_writei(dev->handle, loopback->empty_buf, frames)) < 0)
+                               LOGE("%s: Failed to write. frame(%ld): %s(%d)",
+                                       dev->name, frames, snd_strerror(err), err);
+                       return 0;
+               }
+
+               playbuf = sq_get_buffer(play);
+
+               /* only recording thread */
+               if (loopback->refq && !sq_is_empty_lock(loopback->refq)) {
+                       sqbuffer *ref = sq_dequeue_node_lock(loopback->refq);
+                       sqbuffer *out = sq_get_node_lock(loopback->q);
+                       unsigned char *refbuf = sq_get_buffer(ref);
+                       unsigned char *outbuf = sq_get_buffer(out);
+
+                       loopback->op->process(playbuf, refbuf, outbuf);
+
+                       sq_put_node_lock(play);
+                       sq_put_node_lock(ref);
+
+                       play = out;
+                       playbuf = outbuf;
+               }
+
+
+               if ((err = snd_pcm_writei(dev->handle, playbuf, frames)) < 0) {
+                       LOGE("%s: Failed to write: %s", dev->name, snd_strerror(err));
+                       if (__xrun_recovery(dev, err))
+                               sq_put_node_lock(play);
+                       return 0;
+               }
+
+               /* only reference thread */
+               if (loopback->master && loopback->master->state == STATE_RUNNING) {
+                       sq_enqueue_node_lock(loopback->master_refq, play);
+                       return 0;
+               }
+
+               sq_put_node_lock(play);
+       } else
+               LOGD("unknown event %x\n", revents);
+
+       return 0;
+}
+
+static int __check_ctl_active_event(loopback_t *loopback, int *status)
+{
+       int err;
+       unsigned int event_mask;
+       snd_ctl_event_t *ev;
+       snd_ctl_elem_id_t *id1, *id2;
+
+       snd_ctl_event_alloca(&ev);
+       snd_ctl_elem_id_alloca(&id1);
+       snd_ctl_elem_id_alloca(&id2);
+
+       while ((err = snd_ctl_read(loopback->ctl, ev)) != 0 && err != -EAGAIN);
+
+       snd_ctl_elem_value_get_id(loopback->elm_active, id1);
+       snd_ctl_event_elem_get_id(ev, id2);
+
+       event_mask = snd_ctl_event_elem_get_mask(ev);
+
+       if (event_mask == SND_CTL_EVENT_MASK_REMOVE || !(event_mask & SND_CTL_EVENT_MASK_VALUE))
+               return 0;
+
+       /*
+       LOGD("intf(%d:%d) id(%d:%d) subid(%d:%d) elem(%s:%s) index(%d:%d)\n",
+                       snd_ctl_elem_id_get_interface(id1), snd_ctl_elem_id_get_interface(id2),
+                       snd_ctl_elem_id_get_device(id1), snd_ctl_elem_id_get_device(id2),
+                       snd_ctl_elem_id_get_subdevice(id1), snd_ctl_elem_id_get_subdevice(id2),
+                       snd_ctl_elem_id_get_name(id1), snd_ctl_elem_id_get_name(id2),
+                       snd_ctl_elem_id_get_index(id1), snd_ctl_elem_id_get_index(id2));
+       */
+
+       if (snd_ctl_elem_id_get_interface(id1) != snd_ctl_elem_id_get_interface(id2))
+               return 0;
+
+       if (snd_ctl_elem_id_get_device(id1) != snd_ctl_elem_id_get_device(id2))
+               return 0;
+
+       if (snd_ctl_elem_id_get_subdevice(id1) != snd_ctl_elem_id_get_subdevice(id2))
+               return 0;
+
+       if (strcmp(snd_ctl_elem_id_get_name(id1), snd_ctl_elem_id_get_name(id2)) != 0)
+               return 0;
+
+       if (snd_ctl_elem_id_get_index(id1) != snd_ctl_elem_id_get_index(id2))
+               return 0;
+
+       err = snd_ctl_elem_read(loopback->ctl, loopback->elm_active);
+       if (err < 0) {
+               LOGE("Failed to read ctl elem\n");
+               return 0;
+       }
+
+       err = snd_ctl_elem_value_get_boolean(loopback->elm_active, 0);
+
+       *status = err;
+
+       LOGE("dev:subid:elm(%d:%d:%s) is changed to (%d)\n",
+                       snd_ctl_elem_id_get_device(id2), snd_ctl_elem_id_get_subdevice(id2),
+                       snd_ctl_elem_id_get_name(id2), *status);
+
+       return 1;
+}
+
+static void *__loop_thread(void *userdata)
+{
+       int err;
+       unsigned short revents;
+       struct loopback *loopback = (struct loopback *)userdata;
+       struct loopback_dev **dev = loopback->dev;
+
+       LOGI("start thread. name(%s), capture(%s) playback(%s), frame(%d), poll_cnt(%d)\n",
+               loopback->name, dev[CAPTURE]->name, dev[PLAYBACK]->name,
+               loopback->ec_frame, loopback->poll_cnt);
+
+       while (1) {
+               if (poll(loopback->pfds, loopback->poll_cnt, -1) < 0) {
+                       LOGE("poll err %s\n", strerror(errno));
+                       continue;
+               }
+
+               if (loopback->quit) {
+                       if (loopback->op)
+                               loopback->op->deinit();
+
+                       __close_dev(loopback);
+                       loopback->state = STATE_STOPPED;
+
+                       LOGE("thread(%s) exit\n", loopback->name);
+
+                       pthread_exit(0);
+               }
+
+               revents = 0;
+               if (!snd_ctl_poll_descriptors_revents(loopback->ctl, &loopback->pfds[POLL_CTRL], 1, &revents)) {
+                       int onoff;
+
+                       if (__check_ctl_active_event(loopback, &onoff)) {
+                               if (onoff) { /* Turn on */
+                                       if (__open_dev(loopback, PLAYBACK, loopback->pfds+POLL_PLAYBACK)) {
+                                               LOGE("failed to open playback dev\n");
+                                               pthread_exit(0);
+                                       }
+
+                                       if (__open_dev(loopback, CAPTURE, loopback->pfds+POLL_CAPTURE)) {
+                                               LOGE("failed to open capture dev\n");
+                                               pthread_exit(0);
+                                       }
+
+                                       if ((err = snd_pcm_start(dev[CAPTURE]->handle)) < 0)
+                                               LOGE("capture dev start failed. %s\n", snd_strerror(err));
+
+                                       loopback->state = STATE_RUNNING;
+
+                               } else { /* Turn off */
+                                       loopback->state = STATE_STOPPED;
+
+                                       __close_dev(loopback);
+
+                                       sq_flush(loopback->q);
+                                       sq_flush_lock(loopback->refq);
+
+                                       LOGI("sleep...\n");
+
+                                       continue;
+                               }
+                       }
+               }
+
+               if (loopback->state != STATE_RUNNING)
+                       continue;
+
+               revents = 0;
+               if (!snd_pcm_poll_descriptors_revents(dev[CAPTURE]->handle,
+                                                       &loopback->pfds[POLL_CAPTURE], 1, &revents)) {
+                       if (revents != 0)
+                               __handle_revents(loopback, CAPTURE, revents);
+               }
+               revents = 0;
+               if (!snd_pcm_poll_descriptors_revents(dev[PLAYBACK]->handle,
+                                                       &loopback->pfds[POLL_PLAYBACK], 1, &revents)) {
+                       if (revents != 0)
+                               __handle_revents(loopback, PLAYBACK, revents);
+               }
+
+               if (loopback->logcnt++ > 100) {
+                       loopback->logcnt = 0;
+                       __print_buffer(loopback);
+               }
+       }
+}
+
+static void  __close_dev(loopback_t *loopback)
+{
+       if (loopback->dev[PLAYBACK]->handle) {
+               snd_pcm_close(loopback->dev[PLAYBACK]->handle);
+               loopback->dev[PLAYBACK]->handle = NULL;
+       }
+       loopback->poll_cnt--;
+
+       if (loopback->dev[CAPTURE]->handle) {
+               snd_pcm_close(loopback->dev[CAPTURE]->handle);
+               loopback->dev[CAPTURE]->handle = NULL;
+       }
+       loopback->poll_cnt--;
+}
+
+static int __xrun_recovery(struct loopback_dev *dev, int err)
+{
+       snd_pcm_state_t state;
+
+       state = snd_pcm_state(dev->handle);
+
+       if (state != SND_PCM_STATE_XRUN) {
+               LOGW("Invalid state(%d)", state);
+               return -1;
+       }
+
+       LOGE("%s: Try to recover. state(%d)\n", dev->name, state);
+
+       if (err == -EPIPE) { /* underrun or overrun */
+               err = snd_pcm_prepare(dev->handle);
+               if (err < 0) {
+                       LOGE("XRUN: Failed to prepare pcm\n");
+                       return -1;
+               }
+       } else if (err == -ESTRPIPE) { /* suspended */
+               while ((err = snd_pcm_resume(dev->handle)) == -EAGAIN)
+                       usleep(50000);
+
+               if (err) {
+                       LOGE("XRUN: Failed to resume pcm\n");
+                       return -1;
+               }
+
+               err = snd_pcm_prepare(dev->handle);
+               if (err < 0) {
+                       LOGE("XRUN: Failed to prepare pcm\n");
+                       return -1;
+               }
+       } else {
+               LOGE("XRUN: Unknown error\n");
+               return -1;
+       }
+
+       // TODO: add device close open
+       LOGE("XRUN: recovered");
+
+       return 0;
+}
+
+static int __open_dev(loopback_t *loopback, int index, struct pollfd *pfd)
+{
+       int err;
+       int flags = SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_FORMAT;
+       unsigned int buffer_time;
+
+       const char *name = loopback->dev[index]->name;
+       int mode = loopback->dev[index]->mode;
+       unsigned int rate = loopback->dev[index]->rate;
+       unsigned int _rate = rate;
+       int channels = loopback->dev[index]->channels;
+       //int format = dev->format;
+       snd_pcm_uframes_t buffer_frame = loopback->dev[index]->buffer_frame;
+       struct loopback_dev *dev = loopback->dev[index];
+
+       snd_pcm_stream_t _mode = (mode == CAPTURE) ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
+       snd_pcm_sw_params_t *sw_params = NULL;
+       snd_pcm_hw_params_t *hw_params = NULL;
+       snd_pcm_uframes_t buffer_size, period_size;
+
+       if ((err = snd_pcm_open(&dev->handle, name, _mode, flags)) < 0) {
+               LOGE("%s: cannot open audio device (%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+               LOGE("%s: cannot allocate hardware parameter structure(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_any(dev->handle, hw_params)) < 0) {
+               LOGE("%s: cannot initialize hardware parameter structure(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_set_rate_resample(dev->handle, hw_params, 0)) < 0) {
+               LOGE("%s: cannot set sample SAMPLE_RATE(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_set_access(dev->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+               LOGE("%s: cannot set access type(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_set_format(dev->handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
+               LOGE("%s: cannot set sample format(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_set_channels(dev->handle, hw_params, channels)) < 0) {
+               unsigned int ch;
+
+               LOGE("%s: cannot set channel. try near. channels(%d). %s\n",
+                               name, channels, snd_strerror(err));
+
+               if ((err = snd_pcm_hw_params_set_channels_near(dev->handle, hw_params, &ch)) < 0) {
+                       LOGE("%s: cannot set channel. %s\n", name, snd_strerror(err));
+                       goto fail;
+               }
+               dev->channels = ch;
+       }
+
+       if ((err = snd_pcm_hw_params_set_rate_near(dev->handle, hw_params, &_rate, NULL)) < 0) {
+               LOGE("%s: cannot set sample SAMPLE_RATE(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       if (_rate != rate) {
+               LOGE("%s: rate doesn't matched rate(%d) _rate(%d)\n", name, rate, _rate);
+               return -1;
+       }
+
+       if ((err = snd_pcm_hw_params_set_buffer_size_near(dev->handle, hw_params, &buffer_frame)) < 0) {
+               LOGE("%s: snd_pcm_hw_params_set_buffer_size_near(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
+       snd_pcm_hw_params_get_buffer_time(hw_params, &buffer_time, NULL);
+
+       unsigned int period_time = buffer_time / 4;
+       if ((err = snd_pcm_hw_params_set_period_time_near(dev->handle, hw_params, &period_time, NULL))) {
+               LOGE("%s: hw set period time near %d)\n", name, period_time);
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, NULL))) {
+               LOGE("%s: hw set period time near %d)\n", name, period_time);
+               goto fail;
+       }
+
+       if ((err = snd_pcm_hw_params(dev->handle, hw_params)) < 0) {
+               LOGE("%s: cannot set parameters(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       snd_pcm_hw_params_free(hw_params);
+
+       /* sw params */
+       if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
+               LOGE("%s: cannot allocate software parameters structure(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       err = snd_pcm_sw_params_current(dev->handle, sw_params);
+       if (err < 0) {
+               LOGE("%s: cannot initialize software parameters structure(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       err = snd_pcm_sw_params_set_start_threshold(dev->handle, sw_params, period_size);
+       if (err < 0) {
+               LOGE("Unable to set start threshold mode for %s", snd_strerror(err));
+               goto fail;
+       }
+
+       if ((err = snd_pcm_sw_params_set_avail_min(dev->handle, sw_params, period_size)) < 0) {
+               LOGE("%s: cannot set minimum available count(%s)\n", name, snd_strerror(err));
+               goto fail;
+       }
+
+       err = snd_pcm_sw_params(dev->handle, sw_params);
+       if (err < 0) {
+               LOGE("Unable to set sw params for %s", snd_strerror(err));
+               goto fail;
+       }
+
+       snd_pcm_sw_params_free(sw_params);
+
+       dev->period_size = period_size;
+       dev->period_time = period_time;
+       dev->buffer_size = buffer_size;
+       dev->buffer_time = buffer_time;
+
+       if (pfd) {
+               if (snd_pcm_poll_descriptors(dev->handle, pfd, 1) <= 0) {
+                       LOGE("cannot get capture descriptor\n");
+                       goto fail;
+               }
+       }
+
+       loopback->poll_cnt++;
+
+       LOGI("%s dev(%s) opened successfully. rate(%d), ch(%d), request buffer_frame(%d)\n",
+                       mode ? "playback" : "capture", name, dev->rate, dev->channels, dev->buffer_frame);
+       LOGI("\tbuffer size : %ld frames, %d usec\n", buffer_size, buffer_time);
+       LOGI("\tperiod size : %ld frames, %d usec\n", period_size, period_time);
+       LOGI("\tthread_hold : %ld, avail_min %ld\n", period_size, period_size);
+       LOGI("\tloopback process frame %d", loopback->ec_frame);
+
+       return 0;
+
+fail:
+       if (dev->handle)
+               snd_pcm_close(dev->handle);
+       if (hw_params)
+               snd_pcm_hw_params_free(hw_params);
+       if (sw_params)
+               snd_pcm_sw_params_free(sw_params);
+
+       return -1;
+}
+
+static int __print_buffer(loopback_t *loopback)
+{
+       struct loopback_dev **dev = loopback->dev;
+       snd_pcm_sframes_t c_avail, c_delay;
+       snd_pcm_sframes_t p_avail, p_delay;
+       float capture;
+       float playback;
+
+       snd_pcm_avail_delay(dev[CAPTURE]->handle, &c_avail, &c_delay);
+       snd_pcm_avail_delay(dev[PLAYBACK]->handle, &p_avail, &p_delay);
+
+       capture = (float)c_avail / (float)(dev[CAPTURE]->buffer_size) * 100;
+       playback = (float)p_avail / (float)(dev[PLAYBACK]->buffer_size) * 100;
+
+       LOGD("name(%s) capture avail(%.2f%%), playback avail(%.2f%%) buffer delay(%d)",
+                       loopback->name, capture, playback, sq_get_work_node_count(loopback->q));
+
+       if (loopback->refq)
+               LOGE("refq delay(%d)", sq_get_work_node_count(loopback->refq));
+
+       return 0;
+}
+
diff --git a/aec/loopback.h b/aec/loopback.h
new file mode 100644 (file)
index 0000000..c656861
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+#ifndef __LOOPBACK__
+#define __LOOPBACK__
+
+typedef struct loopback loopback_t;
+typedef struct loopback_dev loopback_dev_t;
+
+typedef struct ec_operation {
+       int (*init)(int, int, int);
+       int (*process)(unsigned char *, unsigned char *, unsigned char *);
+       int (*deinit)();
+} ec_operation_t;
+
+enum {
+       CAPTURE_MODE = 0,
+       PLAYBACK_MODE,
+};
+
+loopback_dev_t *loopback_alloc_device(const char *name, int mode, int rate,
+                                       int channels, int format, int latency);
+int loopback_open_device(loopback_dev_t *dev);
+loopback_t *loopback_create(const char *name,
+                               loopback_dev_t *capture,
+                               loopback_dev_t *playback,
+                               int process_frames, int nbuffer);
+int loopback_free_device(loopback_dev_t *dev);
+int loopback_start(loopback_t *loopback);
+int loopback_stop(loopback_t *loopback);
+int loopback_destroy(loopback_t *loopback);
+
+int loopback_bind_reference(loopback_t *loopback, loopback_t *ref, ec_operation_t *op);
+
+int loopback_print_buffer(loopback_t *loopback);
+
+#endif // __LOOPBACK__
diff --git a/aec/main.c b/aec/main.c
new file mode 100644 (file)
index 0000000..a9b1510
--- /dev/null
@@ -0,0 +1,522 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <alsa/asoundlib.h>
+#include <dbus/dbus.h>
+#include <dlog.h>
+#include <unistd.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "loopback.h"
+#include "algo_speex.h"
+
+#define LOOPBACK_BUFFER_COUNT 40
+#define FIXME_DEFAULT_FORMAT_SIZE 2
+
+/* AEC daemon interfaceis */
+#define AEC_BUS_NAME                                           "org.tizen.AudioAec"
+#define AEC_OBJECT                                             "/org/tizen/AudioAec"
+#define AEC_INTERFACE                                          "org.tizen.AudioAec"
+
+/* pulseaudio aec-manager interfaces */
+#define PA_BUS_NAME                                            "org.pulseaudio.Server"
+#define PA_AEC_MANAGER_OBJECT_PATH                             "/org/pulseaudio/AecManager"
+#define PA_AEC_MANAGER_INTERFACE                               "org.pulseaudio.AecManager"
+#define PA_AEC_MANAGER_METHOD_NAME_ON                          "On"
+#define PA_AEC_MANAGER_METHOD_NAME_OFF                         "Off"
+#define PA_AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS             "GetSinkParam"
+#define PA_AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS           "GetSourceParam"
+
+#define SOUNDCARD_LOOPBACK             "Loopback"
+#define LOOPBACK_PLAYBACK_SOURCE       "hw:Loopback,1,0"
+#define LOOPBACK_CAPTURE_SINK          "hw:Loopback,0,1"
+
+#define DEVICE_NAME_MAX 64
+
+enum {
+       CAPTURE_DEVICE,
+       PLAYBACK_DEVICE,
+       DEVICE_MAX
+};
+
+enum {
+       THREAD_CAPTURE,
+       THREAD_PLAYBACK,
+       THREAD_MAX
+};
+
+static struct sigaction sigabt, sigsegv, sigterm;
+
+static DBusConnection *conn;
+static loopback_dev_t *reference[DEVICE_MAX];
+static loopback_dev_t *recording[DEVICE_MAX];
+static loopback_t *thread[THREAD_MAX];
+static int opt_ec;
+
+static struct aec_params {
+        int rate;
+       int channels;
+       int format;
+       int frag_size;
+       int nfrags;
+       char *card;
+       char *device;
+       char fullname[DEVICE_NAME_MAX];
+} aec_params[DEVICE_MAX];
+
+static int _get_devices_params(int dev, struct aec_params *params);
+static int __dbus_method_call(const char *method, GVariant *param, GVariant **reply);
+static void __register_signal_handler();
+
+loopback_t *__create_loopback_instance(const char *name, const char *c, const char *p,
+                                       struct aec_params *params, loopback_dev_t **dev)
+{
+       int i;
+       const char *devs[DEVICE_MAX] = { c, p };
+       loopback_dev_t *_dev[DEVICE_MAX] = { 0, 0 };
+       loopback_t *ret = NULL;
+
+       unsigned int buffer_frame, period_frame;
+       const int BUFFER_COUNT = 60; /* refq needs a lot of nodes than others */
+
+       if (!c || !p || !name) {
+               LOGE("Invalid args capture(%s), playback(%s), thread name(%s)",
+                               c != NULL ? c : "", p != NULL ? p : "",
+                               name != NULL ? name : "");
+               return NULL;
+       }
+
+       buffer_frame = params->nfrags * params->frag_size;
+       buffer_frame /= FIXME_DEFAULT_FORMAT_SIZE * params->channels;
+       period_frame = params->frag_size / FIXME_DEFAULT_FORMAT_SIZE / params->channels;
+
+       for (i = 0; i < DEVICE_MAX; i++) {
+               _dev[i] = loopback_alloc_device(devs[i], i, params->rate, params->channels,
+                                               FIXME_DEFAULT_FORMAT_SIZE, buffer_frame);
+               if (!_dev[i]) {
+                       LOGE("Failed to alloc device index(%d)", i);
+                       goto exit;
+               }
+       }
+
+       ret = loopback_create(name, _dev[CAPTURE_DEVICE], _dev[PLAYBACK_DEVICE],
+                               period_frame, BUFFER_COUNT);
+       if (!ret) {
+               LOGE("Failed to create loopback. capture(%s) playback(%s)", c, p);
+               goto exit;
+       }
+
+       dev[CAPTURE_DEVICE] = _dev[CAPTURE_DEVICE];
+       dev[PLAYBACK_DEVICE] = _dev[PLAYBACK_DEVICE];
+
+       return ret;
+
+exit:
+       if (_dev[CAPTURE_DEVICE])
+               loopback_free_device(_dev[CAPTURE_DEVICE]);
+
+       if (_dev[PLAYBACK_DEVICE])
+               loopback_free_device(_dev[PLAYBACK_DEVICE]);
+
+       return NULL;
+}
+
+static void __send_success_reply(DBusConnection *conn, DBusMessage *msg)
+{
+       DBusMessage *reply = NULL;
+
+       if (!(reply = dbus_message_new_method_return(msg))) {
+               LOGE("Failed to get reply.");
+               return;
+       }
+
+       dbus_connection_send(conn, reply, NULL);
+       dbus_message_unref(reply);
+}
+
+static void __send_error_reply(DBusConnection *conn, DBusMessage *msg,
+                               const char *name, const char *s)
+{
+       DBusMessage *reply = NULL;
+
+       if (!(reply = dbus_message_new_error(msg, name, s))) {
+               LOGE("Failed to get reply.");
+               return;
+       }
+
+       dbus_connection_send(conn, reply, NULL);
+       dbus_message_unref(reply);
+}
+
+static int __run_thread_workers(struct aec_params *params)
+{
+       int i;
+
+       thread[THREAD_PLAYBACK] = __create_loopback_instance("playback",
+                                       LOOPBACK_PLAYBACK_SOURCE,
+                                       params[PLAYBACK_DEVICE].fullname,
+                                       &aec_params[PLAYBACK_DEVICE], reference);
+       if (!thread[THREAD_PLAYBACK]) {
+               LOGE("Failed to create reference thread");
+               goto fail;
+       }
+
+       thread[THREAD_CAPTURE] = __create_loopback_instance("capture",
+                                       aec_params[CAPTURE_DEVICE].fullname,
+                                       LOOPBACK_CAPTURE_SINK,
+                                       &aec_params[CAPTURE_DEVICE], recording);
+       if (!thread[THREAD_CAPTURE]) {
+               LOGE("Failed to create recording thread");
+               goto fail;
+       }
+
+       // FIXME
+       if (opt_ec) {
+               if (loopback_bind_reference(thread[THREAD_CAPTURE],
+                                               thread[THREAD_PLAYBACK],
+                                               get_speex_instance())) {
+                       LOGE("Failed to bind reference");
+                       goto fail;
+               }
+       }
+
+       if (loopback_start(thread[THREAD_CAPTURE])) {
+               LOGE("Failed to start reference thread");
+               goto fail;
+       }
+
+       if (loopback_start(thread[THREAD_PLAYBACK])) {
+               LOGE("Failed to start recording thread");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       LOGE("Failed to Launch/Quit. Process will be terminated");
+
+       for (i = 0; i < THREAD_MAX; i++) {
+               loopback_stop(thread[i]);
+               loopback_destroy(thread[i]);
+       }
+       for (i = 0; i < DEVICE_MAX; i++) {
+               loopback_free_device(reference[i]);
+               loopback_free_device(recording[i]);
+       }
+
+       return -1;
+}
+
+static int __stop_thread_workers() {
+       int i;
+       int ret = 0;
+       loopback_dev_t **devs[THREAD_MAX] = { recording, reference };
+
+       for (i = 0; i < THREAD_MAX; i++) {
+               if (thread[i]) {
+                       ret = loopback_stop(thread[i]);
+                       ret |= loopback_destroy(thread[i]);
+                       thread[i] = NULL;
+               }
+               ret |= loopback_free_device(devs[i][CAPTURE_DEVICE]);
+               ret |= loopback_free_device(devs[i][PLAYBACK_DEVICE]);
+               recording[CAPTURE_DEVICE] = NULL;
+               recording[PLAYBACK_DEVICE] = NULL;
+
+               if (ret)
+                       LOGE("Failed to stop thread workers(%d)", i);
+       }
+
+       return 0;
+}
+
+static DBusHandlerResult __message_handler(DBusConnection *conn,
+                                               DBusMessage *msg, void *userdata)
+{
+       LOGD("type(%d) path(%s) member(%s) intf(%s), sig(%s)",
+               dbus_message_get_type(msg), dbus_message_get_path(msg),
+               dbus_message_get_member(msg), dbus_message_get_interface(msg),
+               dbus_message_get_signature(msg));
+
+       if (dbus_message_is_method_call(msg, AEC_INTERFACE, "Launch")) {
+               if (thread[THREAD_PLAYBACK] || thread[THREAD_CAPTURE]) {
+                       LOGW("Already working");
+                       goto exit;
+               }
+
+               if (__run_thread_workers(aec_params)) {
+                       __send_error_reply(conn, msg, DBUS_ERROR_FAILED, "Process will be terminated");
+                       dbus_connection_unref(conn);
+                       raise(SIGTERM);
+               }
+               __dbus_method_call(PA_AEC_MANAGER_METHOD_NAME_ON, NULL, NULL);
+
+               LOGI("Launched successfully");
+
+       } else if (dbus_message_is_method_call(msg, AEC_INTERFACE, "Quit")) {
+               if (!thread[THREAD_PLAYBACK] || !thread[THREAD_CAPTURE]) {
+                       LOGW("not working");
+                       goto exit;
+               }
+
+               if (__stop_thread_workers()) {
+                       __send_error_reply(conn, msg, DBUS_ERROR_FAILED, "Process will be terminated");
+                       dbus_connection_unref(conn);
+                       raise(SIGTERM);
+               }
+               __dbus_method_call(PA_AEC_MANAGER_METHOD_NAME_OFF, NULL, NULL);
+
+               LOGE("Quit successfully");
+       }
+
+exit:
+       __send_success_reply(conn, msg);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusConnection *register_service()
+{
+       int rv;
+       DBusError err;
+       static DBusObjectPathVTable vtable = { .message_function = __message_handler };
+
+       dbus_error_init(&err);
+
+       conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+       if (!conn) {
+               LOGE("Failed to get a private DBus connection");
+               goto exit;
+       }
+
+       rv = dbus_bus_request_name(conn, AEC_BUS_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+       if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+               LOGE("Failed to request name on bus err(%s)\n", err.message);
+               goto exit;
+       }
+
+       if (!dbus_connection_register_object_path(conn, AEC_OBJECT, &vtable, NULL)) {
+               LOGE("Failed to register object path(%s)", AEC_OBJECT);
+               goto exit;
+       }
+
+       return conn;
+
+exit:
+       dbus_error_free(&err);
+
+       return NULL;
+}
+
+void run_service(DBusConnection *conn)
+{
+       while (dbus_connection_read_write_dispatch(conn, -1));
+}
+
+int main(int argc, char *argv[])
+{
+       if (argc != 1 && argc != 2)
+               exit(EXIT_FAILURE);
+
+       if (snd_card_get_index(SOUNDCARD_LOOPBACK) < 0) {
+               LOGE("Failed to get Loopback card");
+               exit(EXIT_FAILURE);
+       }
+
+       __register_signal_handler();
+
+       conn = register_service();
+
+       if (getopt(argc, argv, "c") == 'c')
+               opt_ec = 1;
+
+       if (_get_devices_params(CAPTURE_DEVICE, &aec_params[CAPTURE_DEVICE])) {
+               LOGE("Failed to get source params");
+               exit(EXIT_FAILURE);
+       }
+
+       if (_get_devices_params(PLAYBACK_DEVICE, &aec_params[PLAYBACK_DEVICE])) {
+               LOGE("Failed to get sink params");
+               exit(EXIT_FAILURE);
+       }
+
+       run_service(conn);
+
+       dbus_connection_flush(conn);
+       dbus_connection_close(conn);
+
+       return 0;
+}
+
+static int _get_devices_params(int dev, struct aec_params *params)
+{
+       GVariant *reply = NULL;
+
+       int rate, channels, format, frag_size, nfrags;
+       char *card = NULL;
+       char    *device = NULL;
+       char *msg;
+
+       if (dev == PLAYBACK_DEVICE)
+               msg = PA_AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS;
+       else
+               msg = PA_AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS;
+
+       if (__dbus_method_call(msg, NULL, &reply)) {
+               LOGE("Failed to get device params");
+               return -1;
+       }
+
+       if (!reply) {
+               LOGE("Failed to start aec daemon");
+               return -1;
+       }
+
+       g_variant_get(reply, "(iiiii&s&s)", &rate, &channels, &format,
+                       &frag_size, &nfrags, &card, &device);
+       if (!card || !device) {
+               LOGE("Failed to get device and card names");
+               g_variant_unref(reply);
+               return -1;
+       }
+
+       LOGI("source param rate(%d), channels(%d), format(%d), "
+               "frag_size(%d), nfrag(%d), card(%s), device(%s)",
+               rate, channels, format, frag_size, nfrags, card, device);
+
+       params->rate = rate;
+       params->channels = channels;
+       params->format = format;
+       params->frag_size = frag_size;
+       params->nfrags = nfrags;
+       params->card = strdup(card);
+       params->device = strdup(device);
+       snprintf(params->fullname, DEVICE_NAME_MAX, "hw:%s,%s", params->card, params->device);
+
+       g_variant_unref(reply);
+
+       return 0;
+}
+
+static GDBusConnection *__get_dbus_connection(void)
+{
+       GDBusConnection *conn = NULL;
+       GError *err = NULL;
+
+       conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err);
+       if (!conn) {
+               LOGE("g_bus_get_sync() error (%s)", err->message);
+               g_error_free(err);
+       }
+
+       return conn;
+}
+
+static int __dbus_method_call(const char *method, GVariant *param, GVariant **reply)
+{
+       GDBusConnection *conn = NULL;
+       GVariant *_reply = NULL;
+       GError *err = NULL;
+
+       if (!method) {
+               LOGE("invalid parameter");
+               goto error;
+       }
+
+       if (!(conn = __get_dbus_connection()))
+               goto error;
+
+       _reply = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
+                       PA_AEC_MANAGER_OBJECT_PATH,
+                       PA_AEC_MANAGER_INTERFACE,
+                       method, param, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
+
+       g_object_unref(conn);
+
+       if (!_reply) {
+               LOGE("g_dbus_connection_call_sync() method(%s), err-msg(%s)", method, err->message);
+               g_error_free(err);
+               return -1;
+       }
+
+       if (reply)
+               *reply = _reply;
+       else
+               g_variant_unref(_reply);
+
+       return 0;
+
+error:
+       if (param)
+               g_variant_unref(param);
+
+       return -1;
+}
+
+static void _signal_handler(int sig)
+{
+       int i;
+
+       __dbus_method_call(PA_AEC_MANAGER_METHOD_NAME_OFF, NULL, NULL);
+
+       for (i = 0; i < 2; i++) {
+               loopback_stop(thread[i]);
+               loopback_destroy(thread[i]);
+               loopback_free_device(reference[i]);
+               loopback_free_device(recording[i]);
+       }
+
+       switch (sig) {
+       case SIGABRT:
+               sigaction(SIGABRT, &sigabt, NULL);
+               break;
+       case SIGSEGV:
+               sigaction(SIGSEGV, &sigsegv, NULL);
+               break;
+       case SIGTERM:
+               sigaction(SIGTERM, &sigterm, NULL);
+               break;
+       default:
+               break;
+       }
+
+       raise(sig);
+}
+
+static void __register_signal_handler()
+{
+       struct sigaction action;
+
+       action.sa_handler = _signal_handler;
+       action.sa_flags = 0;
+       sigemptyset(&action.sa_mask);
+
+       sigaction(SIGABRT, &action, &sigabt);
+       sigaction(SIGSEGV, &action, &sigsegv);
+       sigaction(SIGTERM, &action, &sigterm);
+}
+
diff --git a/aec/static_queue.c b/aec/static_queue.c
new file mode 100644 (file)
index 0000000..4dcd181
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <dlog.h>
+
+#include "static_queue.h"
+
+struct sqbuffer {
+       unsigned char *buffer;
+       struct static_queue *p;
+       struct sqbuffer *n;
+};
+
+struct static_queue {
+       struct sqbuffer *hold;
+       struct sqbuffer *idle;
+       struct sqbuffer *front, *rear;
+
+       int count;
+       int bytes;
+       int inqueue;
+
+       pthread_mutex_t lock;
+};
+
+sq *create_static_queue(int bytes, int count)
+{
+       struct static_queue *sq;
+       struct sqbuffer *sqb = NULL;
+       int i;
+
+       if (bytes != 0 && count == 0) {
+               LOGE("invalid count(%d)", count);
+               return NULL;
+       }
+
+       if (bytes == 0 && count != 0) {
+               LOGE("invalid bytes(%d)", bytes);
+               return NULL;
+       }
+
+       sq = (struct static_queue *)calloc(1, sizeof(struct static_queue));
+       if (!sq) {
+               LOGE("Failed to alloc sq");
+               return NULL;
+       }
+
+       sq->bytes = bytes;
+       sq->count = count;
+       pthread_mutex_init(&sq->lock, NULL);
+
+       /* for refq */
+       if (bytes == 0 && count == 0)
+               return sq;
+
+       sqb = (struct sqbuffer *)calloc(count, sizeof(struct sqbuffer));
+       if (!sqb) {
+               LOGE("Failed to alloc sqb %d", count);
+               goto fail;
+       }
+       sq->hold = sqb;
+
+       for (i = 0; i < count; i++) {
+               sqb[i].buffer = (unsigned char *)malloc(bytes);
+               if (!sqb[i].buffer) {
+                       LOGE("Failed to alloc sqb buffer");
+                       goto fail;
+               }
+               sqb[i].p = sq;
+               sqb[i].n = sq->idle;
+               sq->idle = sqb + i;
+       }
+
+       return sq;
+
+fail:
+       if (sqb) {
+               for (i = 0; i < count; i++) {
+                       if (sqb[i].buffer)
+                               free(sqb[i].buffer);
+               }
+               free(sqb);
+       }
+       if (sq)
+               free(sq);
+
+       return NULL;
+}
+
+void destory_static_queue(sq *q)
+{
+       int i;
+
+       if (!q) {
+               LOGE("invalid args");
+               return;
+       }
+
+       for (i = 0; i < q->count; i++) {
+               if (q->hold[i].buffer)
+                       free(q->hold[i].buffer);
+       }
+
+       pthread_mutex_destroy(&q->lock);
+
+       if (q->hold)
+               free(q->hold);
+       if (q)
+               free(q);
+}
+
+sqbuffer *sq_get_node(sq *q)
+{
+       sqbuffer *sqb;
+
+       if (!q) {
+               LOGE("invalid args");
+               return NULL;
+       }
+
+       if (!q->idle) {
+               LOGE("Failed to get available node");
+               return NULL;
+       }
+
+       sqb = q->idle;
+       q->idle = sqb->n;
+       sqb->n = NULL;
+
+       return sqb;
+}
+
+sqbuffer *sq_get_node_lock(sq *q)
+{
+       sqbuffer *ret;
+
+       if (!q)
+               return NULL;
+
+       pthread_mutex_lock(&q->lock);
+       ret = sq_get_node(q);
+       pthread_mutex_unlock(&q->lock);
+
+       return ret;
+}
+
+void sq_put_node(sqbuffer *b)
+{
+       sq *q;
+
+       if (!b) {
+               LOGE("invalid args");
+               return;
+       }
+
+       q = b->p;
+       b->n = q->idle;
+       q->idle = b;
+}
+
+void sq_put_node_lock(sqbuffer *b)
+{
+       sq *q;
+
+       if (!b) {
+               LOGE("invalid args");
+               return;
+       }
+
+       q = b->p;
+
+       pthread_mutex_lock(&q->lock);
+       sq_put_node(b);
+       pthread_mutex_unlock(&q->lock);
+}
+
+unsigned char *sq_get_buffer(sqbuffer *sqb)
+{
+       return sqb ? sqb->buffer : NULL;
+}
+
+int sq_enqueue_node(sq *q, sqbuffer *b)
+{
+       if (!q || !b) {
+               LOGE("invalid args");
+               return -1;
+       }
+
+       if (!q->front)
+               q->front = q->rear = b;
+       else
+               q->rear = q->rear->n = b;
+
+       q->inqueue++;
+
+       return q->inqueue;
+}
+
+int sq_enqueue_node_lock(sq *q, sqbuffer *b)
+{
+       int ret;
+
+       pthread_mutex_lock(&q->lock);
+       ret = sq_enqueue_node(q, b);
+       pthread_mutex_unlock(&q->lock);
+
+       return ret;
+}
+
+sqbuffer *sq_dequeue_node(sq *q)
+{
+       sqbuffer *sqb;
+
+       if (!q) {
+               LOGE("invalid args");
+               return NULL;
+       }
+
+       if (!q->front)
+               return NULL;
+
+       sqb = q->front;
+       q->front = sqb->n;
+       sqb->n = NULL;
+       q->inqueue--;
+
+       return sqb;
+}
+
+sqbuffer *sq_dequeue_node_lock(sq *q)
+{
+       sqbuffer *sqb;
+
+       pthread_mutex_lock(&q->lock);
+       sqb = sq_dequeue_node(q);
+       pthread_mutex_unlock(&q->lock);
+
+       return sqb;
+}
+
+void sq_flush(sq *q)
+{
+       sqbuffer *i;
+
+       if (!q)
+               return;
+
+       while ((i = sq_dequeue_node(q)) != NULL)
+               sq_put_node(i);
+}
+
+void sq_flush_lock(sq *q)
+{
+       sqbuffer *i;
+
+       if (!q)
+               return;
+
+       pthread_mutex_lock(&q->lock);
+
+       while ((i = sq_dequeue_node(q)) != NULL)
+               sq_put_node_lock(i);
+
+       pthread_mutex_unlock(&q->lock);
+}
+
+int sq_is_empty(sq *q)
+{
+       if (!q)
+               return -1;
+
+       return q->front ? 0 : 1;
+}
+
+int sq_is_empty_lock(sq *q)
+{
+       int ret;
+
+       if (!q)
+               return -1;
+
+       pthread_mutex_lock(&q->lock);
+       ret = sq_is_empty(q);
+       pthread_mutex_unlock(&q->lock);
+
+       return ret;
+}
+
+int sq_get_work_node_count(sq *q)
+{
+       if (!q)
+               return -1;
+
+       return q->inqueue;
+}
+
diff --git a/aec/static_queue.h b/aec/static_queue.h
new file mode 100644 (file)
index 0000000..f5ca8ff
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+#ifndef __STATIC_QUEUE__
+#define __STATIC_QUEUE__
+
+typedef struct sqbuffer sqbuffer;
+typedef struct static_queue sq;
+
+sq *create_static_queue(int bytes, int count);
+void destory_static_queue(sq *q);
+
+sqbuffer *sq_get_node(sq *q);
+void sq_put_node(sqbuffer *b);
+unsigned char *sq_get_buffer(sqbuffer *sqb);
+int sq_enqueue_node(sq *q, sqbuffer *b);
+sqbuffer *sq_dequeue_node(sq *q);
+void sq_flush(sq *q);
+
+/* lockable api */
+sqbuffer *sq_get_node_lock(sq *q);
+void sq_put_node_lock(sqbuffer *b);
+int sq_enqueue_node_lock(sq *q, sqbuffer *b);
+sqbuffer *sq_dequeue_node_lock(sq *q);
+void sq_flush_lock(sq *q);
+
+int sq_is_empty(sq *q);
+int sq_is_empty_lock(sq *q);
+int sq_get_work_node_count(sq *q);
+
+#endif // __STATIC_QUEUE__
+
index 123e3f8..a7c5fc5 100644 (file)
@@ -41,6 +41,22 @@ PKG_CHECK_MODULES(VCONF, vconf)
 AC_SUBST(VCONF_CFLAGS)
 AC_SUBST(VCONF_LIBS)
 
+PKG_CHECK_MODULES(ALSA, alsa)
+AC_SUBST(ALSA_CFLAGS)
+AC_SUBST(ALSA_LIBS)
+
+PKG_CHECK_MODULES(SPEEX, speexdsp)
+AC_SUBST(SPEEX_CFLAGS)
+AC_SUBST(SPEEX_LIBS)
+
+PKG_CHECK_MODULES(DBUS, dbus-1)
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+
+PKG_CHECK_MODULES(DLOG, dlog)
+AC_SUBST(DLOG_CFLAGS)
+AC_SUBST(DLOG_LIBS)
+
 AC_ARG_ENABLE(pulse, AC_HELP_STRING([--enable-pulse], [enable pulseaudio client]),
 [
  case "${enableval}" in
@@ -101,6 +117,15 @@ AS_IF([test "x$enable_unittests" = "xyes"], [
        AC_SUBST(GTESTS_LIBS)
 ])
 
+AC_ARG_ENABLE(aec, AC_HELP_STRING([--enable-aec], [using aec]),
+[
+ case "${enableval}" in
+        yes) ENABLE_AEC=yes ;;
+        no)  ENABLE_AEC=no ;;
+        *)   AC_MSG_ERROR(bad value ${enableval} for --enable-aec) ;;
+ esac
+ ],[USE_AEC=no])
+AM_CONDITIONAL(ENABLE_AEC, test "x$ENABLE_AEC" = "xyes")
 # Checks for header files.
 AC_HEADER_STDC
 AC_CHECK_HEADERS([fcntl.h memory.h stdlib.h string.h sys/time.h unistd.h errno.h sys/types.h sys/stat.h])
@@ -128,5 +153,6 @@ pkgconfig/mm-keysound.pc
 pkgconfig/mm-bootsound.pc
 testsuite/Makefile
 unittest/Makefile
+aec/Makefile
 ])
 AC_OUTPUT
diff --git a/packaging/audio-aec.conf b/packaging/audio-aec.conf
new file mode 100644 (file)
index 0000000..059cd6f
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+  <policy user="multimedia_fw">
+    <allow own="org.tizen.AudioAec"/>
+    <allow send_destination="org.tizen.AudioAec"/>
+  </policy>
+  <policy user="root">
+    <allow own="org.tizen.AudioAec"/>
+    <allow send_destination="org.tizen.AudioAec"/>
+  </policy>
+  <policy context="default">
+    <deny own="org.tizen.AudioAec"/>
+    <deny send_destination="org.tizen.AudioAec"/>
+    <allow send_destination="org.tizen.AudioAec" send_interface="org.tizen.AudioAec"/>
+  </policy>
+</busconfig>
diff --git a/packaging/audio-aec.service b/packaging/audio-aec.service
new file mode 100644 (file)
index 0000000..9a240cd
--- /dev/null
@@ -0,0 +1,14 @@
+[Unit]
+Description=Audio Acoustic Echo Cancellation
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/audio_aec -c
+Restart=no
+RestartSec=0
+MemoryLimit=500M
+User=multimedia_fw
+Group=multimedia_fw
+SmackProcessLabel=System
+SecureBits=keep-caps
+Capabilities=cap_fowner,cap_lease=i
index e213627..3b8e11c 100644 (file)
@@ -1,10 +1,13 @@
 Name:       libmm-sound
 Summary:    MMSound Package contains client lib and sound_server binary
-Version:    0.13.6
+Version:    0.13.7
 Release:    0
 Group:      System/Libraries
 License:    Apache-2.0
 Source0:    %{name}-%{version}.tar.gz
+Source1:    audio-aec.conf
+Source2:    audio-aec.service
+Source3:    org.tizen.AudioAec.service
 Source4:    focus-server.service
 Source5:    focus-server.path
 Source6:    focus-server.conf
@@ -26,6 +29,10 @@ BuildRequires: pkgconfig(lwipc)
 %if 0%{?gtests:1}
 BuildRequires:  pkgconfig(gmock)
 %endif
+BuildRequires: pkgconfig(alsa)
+BuildRequires: pkgconfig(speexdsp)
+BuildRequires: pkgconfig(dbus-1)
+BuildRequires:  pkgconfig(dlog)
 
 %description
 MMSound package contains focus-server and client interfaces connected to audio system
@@ -77,6 +84,9 @@ export LDFLAGS+=" -lgcov "
        --enable-prelink \
        --enable-lwipc \
 %endif
+%if "%{tizen_profile_name}" != "tv"
+       --enable-aec \
+%endif
 %if 0%{?gtests:1}
        --enable-unittests \
 %endif
@@ -91,6 +101,10 @@ cp %{SOURCE6} %{buildroot}/etc/dbus-1/system.d/focus-server.conf
 %if "%{tizen_profile_name}" == "tv"
 cp %{SOURCE8} %{SOURCE4}
 %endif
+%if "%{tizen_profile_name}" != "tv"
+cp %{SOURCE1} %{buildroot}/etc/dbus-1/system.d/audio-aec.conf
+%endif
+
 mkdir -p %{buildroot}/usr/share/dbus-1/system-services/
 
 %make_install
@@ -102,6 +116,13 @@ install -d %{buildroot}%{_unitdir}/paths.target.wants
 install -m0644 %{SOURCE4} %{buildroot}%{_unitdir}/
 install -m0644 %{SOURCE5} %{buildroot}%{_unitdir}/
 ln -sf ../focus-server.path %{buildroot}%{_unitdir}/paths.target.wants/focus-server.path
+
+%if "%{tizen_profile_name}" != "tv"
+install -m0644 %{SOURCE2} %{buildroot}%{_unitdir}/
+%endif
+mkdir -p %{buildroot}/usr/share/dbus-1/system-services/
+cp %{SOURCE3} %{buildroot}/usr/share/dbus-1/system-services/org.tizen.AudioAec.service
+
 %post
 /sbin/ldconfig
 
@@ -122,8 +143,17 @@ ln -sf ../focus-server.path %{buildroot}%{_unitdir}/paths.target.wants/focus-ser
 %{_unitdir}/paths.target.wants/focus-server.path
 %{_unitdir}/focus-server.service
 %{_unitdir}/focus-server.path
+%{_datadir}/dbus-1/system-services/org.tizen.AudioAec.service
+%if "%{tizen_profile_name}" != "tv"
+%{_bindir}/audio_aec
+%{_unitdir}/audio-aec.service
+%endif
+
 %license LICENSE.APLv2
 /etc/dbus-1/system.d/focus-server.conf
+%if "%{tizen_profile_name}" != "tv"
+/etc/dbus-1/system.d/audio-aec.conf
+%endif
 %if 0%{?gtests:1}
 %{_bindir}/gtest-libmm-sound
 %endif
diff --git a/packaging/org.tizen.AudioAec.service b/packaging/org.tizen.AudioAec.service
new file mode 100644 (file)
index 0000000..14a9189
--- /dev/null
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.tizen.AudioAec
+Exec=/bin/false
+SystemdService=audio-aec.service