pkgconfig \
. \
testsuite
-
SUBDIRS += focus_server
+if ENABLE_AEC
+SUBDIRS += aec
+endif
+
if UNITTESTS_ENABLED
SUBDIRS += unittest
endif
--- /dev/null
+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
+
--- /dev/null
+/*
+* 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;
+}
+
--- /dev/null
+/*
+* 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__
--- /dev/null
+/*
+* 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;
+}
+
--- /dev/null
+/*
+* 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__
--- /dev/null
+/*
+* 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);
+}
+
--- /dev/null
+/*
+* 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;
+}
+
--- /dev/null
+/*
+* 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__
+
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
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])
pkgconfig/mm-bootsound.pc
testsuite/Makefile
unittest/Makefile
+aec/Makefile
])
AC_OUTPUT
--- /dev/null
+<?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>
--- /dev/null
+[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
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
%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
--enable-prelink \
--enable-lwipc \
%endif
+%if "%{tizen_profile_name}" != "tv"
+ --enable-aec \
+%endif
%if 0%{?gtests:1}
--enable-unittests \
%endif
%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
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
%{_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
--- /dev/null
+[D-BUS Service]
+Name=org.tizen.AudioAec
+Exec=/bin/false
+SystemdService=audio-aec.service