--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2021 Jaechul Lee <jcsing.lee@samsung.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/macro.h>
+
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/dbus-util.h>
+
+#include "aec-manager.h"
+
+#define AEC_MANAGER_OBJECT_PATH "/org/pulseaudio/AecManager"
+#define AEC_MANAGER_INTERFACE "org.pulseaudio.AecManager"
+
+#define AEC_MANAGER_METHOD_NAME_ON "On"
+#define AEC_MANAGER_METHOD_NAME_OFF "Off"
+#define AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS "GetSinkParam"
+#define AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS "GetSourceParam"
+
+#define AEC_UNIX_SOCKET_PATH "/tmp/.aec.socket"
+
+static struct aec_manager {
+ pa_dbus_connection *dbus_conn;
+ pa_source *source; /* builtin-mic */
+ pa_sink *sink; /* builtin-spk */
+} am;
+
+enum {
+ SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX,
+ SINK_MESSAGE_GET_AEC_PARAMS,
+};
+
+enum {
+ SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX,
+ SOURCE_MESSAGE_GET_AEC_PARAMS,
+};
+
+#define AEC_MANAGER_INTROSPECT_XML \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>" \
+ " <interface name=\"AEC_MANAGER_INTERFACE\">" \
+ " <method name=\"AEC_MANAGER_METHOD_NAME_ON\">" \
+ " </method>" \
+ " <method name=\"AEC_MANAGER_METHOD_NAME_OFF\">" \
+ " </method>" \
+ " <method name=\"AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS\">" \
+ " <arg name=\"rate\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"channels\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"format\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"frag_size\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"nfrags\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"card\" direction=\"out\" type=\"s\"/>" \
+ " <arg name=\"device\" direction=\"out\" type=\"s\"/>" \
+ " </method>" \
+ " <method name=\"AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS\">" \
+ " <arg name=\"rate\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"channels\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"format\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"frag_size\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"nfrags\" direction=\"out\" type=\"i\"/>" \
+ " <arg name=\"card\" direction=\"out\" type=\"s\"/>" \
+ " <arg name=\"device\" direction=\"out\" type=\"s\"/>" \
+ " </method>" \
+ " </interface>" \
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
+ " <method name=\"Introspect\">" \
+ " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
+ " </method>" \
+ " </interface>" \
+ "</node>"
+
+enum method_handler_index {
+ METHOD_HANDLER_ON,
+ METHOD_HANDLER_OFF,
+ METHOD_HANDLER_GET_SINK_PARAMS,
+ METHOD_HANDLER_GET_SOURCE_PARAMS,
+ METHOD_HANDLER_MAX
+};
+
+static void handle_method_on(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_method_off(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_method_get_sink_params(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_method_get_source_params(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_ON] = {
+ .method_name = AEC_MANAGER_METHOD_NAME_ON,
+ .receive_cb = handle_method_on },
+ [METHOD_HANDLER_OFF] = {
+ .method_name = AEC_MANAGER_METHOD_NAME_OFF,
+ .receive_cb = handle_method_off },
+ [METHOD_HANDLER_GET_SINK_PARAMS] = {
+ .method_name = AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS,
+ .receive_cb = handle_method_get_sink_params },
+ [METHOD_HANDLER_GET_SOURCE_PARAMS] = {
+ .method_name = AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS,
+ .receive_cb = handle_method_get_source_params },
+};
+
+static void send_get_param_reply(DBusConnection *conn, DBusMessage *msg, aec_params_t* param) {
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+
+ const char *ptr1 = param->card;
+ const char *ptr2 = param->device;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply) {
+ pa_log_error("Failed to alloc reply");
+ return;
+ }
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->rate);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->channels);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->format);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->frag_size);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->nfrags);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr1);
+ dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr2);
+ dbus_connection_send(conn, reply, NULL);
+
+ dbus_message_unref(reply);
+}
+
+static void handle_method_on(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_asyncmsgq_post(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink),
+ SINK_MESSAGE_SET_AEC_STATE, (void *)TRUE, 0, NULL, NULL);
+ pa_asyncmsgq_post(am.source->asyncmsgq, PA_MSGOBJECT(am.source),
+ SOURCE_MESSAGE_SET_AEC_STATE, (void *)TRUE, 0, NULL, NULL);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_method_off(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_asyncmsgq_post(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink),
+ SINK_MESSAGE_SET_AEC_STATE, (void *)FALSE, 0, NULL, NULL);
+ pa_asyncmsgq_post(am.source->asyncmsgq, PA_MSGOBJECT(am.source),
+ SOURCE_MESSAGE_SET_AEC_STATE, (void *)FALSE, 0, NULL, NULL);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_method_get_sink_params(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ aec_params_t param;
+
+ pa_asyncmsgq_send(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink),
+ SINK_MESSAGE_GET_AEC_PARAMS, ¶m, 0, NULL);
+
+ send_get_param_reply(conn, msg, ¶m);
+}
+
+static void handle_method_get_source_params(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ aec_params_t param;
+
+ pa_asyncmsgq_send(am.source->asyncmsgq, PA_MSGOBJECT(am.source),
+ SOURCE_MESSAGE_GET_AEC_PARAMS, ¶m, 0, NULL);
+
+ send_get_param_reply(conn, msg, ¶m);
+}
+
+static DBusHandlerResult method_handler_for_vt(DBusConnection *conn, DBusMessage *m, void *userdata) {
+ const char *path, *interface, *member;
+ DBusMessage *r = NULL;
+
+ pa_assert(conn);
+ pa_assert(m);
+
+ path = dbus_message_get_path(m);
+ interface = dbus_message_get_interface(m);
+ member = dbus_message_get_member(m);
+
+ pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+ if (!pa_safe_streq(path, AEC_MANAGER_OBJECT_PATH))
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+ r = dbus_message_new_method_return(m);
+ if (r) {
+ const char *xml = AEC_MANAGER_INTROSPECT_XML;
+ dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID);
+ dbus_connection_send((conn), r, NULL);
+ dbus_message_unref(r);
+ }
+ } else {
+ int i;
+ for (i = 0; i < METHOD_HANDLER_MAX; i++) {
+ if (dbus_message_is_method_call(m, AEC_MANAGER_INTERFACE,
+ method_handlers[i].method_name))
+ method_handlers[i].receive_cb(conn, m, NULL);
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+int aec_manager_init(pa_core *core, pa_source *source, pa_sink *sink) {
+ DBusError err;
+ pa_dbus_connection *conn = NULL;
+ static const DBusObjectPathVTable vtable = {
+ .message_function = method_handler_for_vt,
+ };
+
+ if (!source || !sink) {
+ pa_log_error("AEC init failed. source(%p)/sink(%p) is null.", source, sink);
+ return -1;
+ }
+
+ dbus_error_init(&err);
+ if (!(conn = pa_dbus_bus_get(core, DBUS_BUS_SYSTEM, &err)) || dbus_error_is_set(&err)) {
+ if (conn)
+ pa_dbus_connection_unref(conn);
+
+ dbus_error_free(&err);
+ pa_log_error("Unable to contact D-Bus system bus: %s: %s", err.name, err.message);
+ return -1;
+ }
+
+ am.dbus_conn = conn;
+ if (!dbus_connection_register_object_path(pa_dbus_connection_get(conn),
+ AEC_MANAGER_OBJECT_PATH, &vtable, NULL)) {
+ pa_dbus_connection_unref(conn);
+ pa_log_error("Failed to register object path");
+ return -1;
+ }
+
+ am.source = source;
+ am.sink = sink;
+
+ pa_log_info("AEC init success");
+
+ return 0;
+}
+
+void aec_manager_deinit(void) {
+ if (am.dbus_conn)
+ pa_dbus_connection_unref(am.dbus_conn);
+
+ pa_log_info("AEC deinit success");
+}
#include "hal-interface.h"
+#ifdef SUPPORT_AEC
+#include "aec-manager.h"
+#endif
+
PA_MODULE_AUTHOR("Tizen");
PA_MODULE_DESCRIPTION("Tizen Audio Sink");
PA_MODULE_VERSION(PACKAGE_VERSION);
#define DEVICE_NAME_MAX 30
+#ifdef SUPPORT_AEC
+enum {
+ PA_SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX,
+ PA_SINK_MESSAGE_GET_AEC_PARAMS,
+};
+#endif
+
struct userdata {
pa_core *core;
pa_module *module;
uint64_t write_count;
pa_hal_interface *hal_interface;
+
+#ifdef SUPPORT_AEC
+ bool aec_enable;
+ pa_sample_spec ss;
+#endif
};
static const char* const valid_modargs[] = {
u->rtpoll_item = NULL;
}
- pa_log_info("Device suspended...[%s,%s]", u->card, u->device);
+ pa_log_info("Device suspended...");
return 0;
}
int32_t ret;
size_t frame_size;
+ char *card = u->card;
+ char *device = u->device;
+
pa_assert(u);
pa_assert(!u->pcm_handle);
goto fail;
}
+#ifdef SUPPORT_AEC
+ if (u->aec_enable) {
+ card = "Loopback";
+ device = "0,0";
+ }
+#endif
+
ret = pa_hal_interface_pcm_open(u->hal_interface,
- u->card,
- u->device,
+ card,
+ device,
DIRECTION_OUT,
&sample_spec,
u->frag_size / frame_size,
u->write_count = 0;
u->first = true;
- pa_log_info("Resumed successfully...");
+ pa_log_info("Resumed successfully...device(%s:%s)", card, device);
return 0;
*((pa_usec_t*)data) = latency;
return 0;
}
+#ifdef SUPPORT_AEC
+ case PA_SINK_MESSAGE_SET_AEC_STATE: {
+ pa_sink *s = PA_SINK(o);
+ bool enable = (bool)data;
+
+ if (u->aec_enable == enable)
+ return 0;
+
+ pa_log_info("AEC enable(%d)", enable);
+
+ u->aec_enable = enable;
+ if (s->thread_info.state == PA_SINK_RUNNING) {
+ suspend(u);
+ unsuspend(u);
+ }
+
+ return 0;
+ }
+ case PA_SINK_MESSAGE_GET_AEC_PARAMS: {
+ aec_params_t *params = (aec_params_t *)data;
+ params->rate = u->ss.rate;
+ params->channels = u->ss.channels;
+ params->format = u->ss.format;
+ params->frag_size = u->frag_size;
+ params->nfrags = u->nfrags;
+ snprintf(params->card, DEVICE_NAME_MAX, "%s", u->card);
+ snprintf(params->device, DEVICE_NAME_MAX, "%s", u->device);
+ return 0;
+ }
+#endif
}
return pa_sink_process_msg(o, code, data, offset, chunk);
const char *modarg_device;
char card[DEVICE_NAME_MAX];
char device[DEVICE_NAME_MAX];
+ size_t frame_size, buffer_size, period_frames, buffer_frames;
pa_assert(m);
u->first = true;
u->hal_interface = pa_hal_interface_get(u->core);
u->rtpoll = pa_rtpoll_new();
+#ifdef SUPPORT_AEC
+ u->ss = ss;
+#endif
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) {
pa_proplist_sets(data.proplist, "tizen.device", u->device);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen");
+ frame_size = pa_frame_size(&ss);
+ buffer_size = u->frag_size * u->nfrags;
+ buffer_frames = buffer_size / frame_size;
+ period_frames = u->frag_size / frame_size;
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size);
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size);
+
if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log_error("Invalid properties.");
pa_sink_new_data_done(&data);
#include "hal-interface.h"
+#ifdef SUPPORT_AEC
+#include "aec-manager.h"
+#endif
+
PA_MODULE_AUTHOR("Tizen");
PA_MODULE_DESCRIPTION("Tizen Audio Source");
PA_MODULE_VERSION(PACKAGE_VERSION);
#define DEVICE_NAME_MAX 30
+#ifdef SUPPORT_AEC
+enum {
+ PA_SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX,
+ PA_SOURCE_MESSAGE_GET_AEC_PARAMS,
+};
+#endif
+
struct userdata {
pa_core *core;
pa_module *module;
uint64_t read_count;
pa_usec_t latency_time;
pa_hal_interface *hal_interface;
+
+#ifdef SUPPORT_AEC
+ bool aec_enable;
+ pa_sample_spec ss;
+#endif
};
static const char* const valid_modargs[] = {
u->rtpoll_item = NULL;
}
- pa_log_info("Device suspended...[%s,%s]", u->card, u->device);
+ pa_log_info("Device suspended...");
return 0;
}
int32_t ret;
size_t frame_size;
+ char *card = u->card;
+ char *device = u->device;
+
pa_assert(u);
pa_assert(!u->pcm_handle);
goto fail;
}
+#ifdef SUPPORT_AEC
+ if (u->aec_enable) {
+ card = "Loopback";
+ device = "1,1";
+ }
+#endif
+
ret = pa_hal_interface_pcm_open(u->hal_interface,
- u->card,
- u->device,
+ card,
+ device,
DIRECTION_IN,
&sample_spec,
u->frag_size / frame_size,
u->read_count = 0;
u->first = true;
- pa_log_info("Resumed successfully...");
+ pa_log_info("Resumed successfully...device(%s:%s)", card, device);
return 0;
*((pa_usec_t*)data) = u->timestamp > now ? 0ULL : now - u->timestamp;
return 0;
}
+#ifdef SUPPORT_AEC
+ case PA_SOURCE_MESSAGE_SET_AEC_STATE: {
+ pa_source *s = PA_SOURCE(o);
+ bool enable = (bool)data;
+
+ if (u->aec_enable == enable)
+ return 0;
+
+ pa_log_info("AEC enable(%d)", enable);
+
+ u->aec_enable = enable;
+ if (s->thread_info.state == PA_SOURCE_RUNNING) {
+ suspend(u);
+ unsuspend(u);
+ }
+
+ return 0;
+ }
+ case PA_SOURCE_MESSAGE_GET_AEC_PARAMS: {
+ aec_params_t *params = (aec_params_t *)data;
+ params->rate = u->ss.rate;
+ params->channels = u->ss.channels;
+ params->format = u->ss.format;
+ params->frag_size = u->frag_size;
+ params->nfrags = u->nfrags;
+ snprintf(params->card, DEVICE_NAME_MAX, "%s", u->card);
+ snprintf(params->device, DEVICE_NAME_MAX, "%s", u->device);
+ return 0;
+ }
+#endif
}
return pa_source_process_msg(o, code, data, offset, chunk);
const char *modarg_device;
char card[DEVICE_NAME_MAX];
char device[DEVICE_NAME_MAX];
+ size_t frame_size, buffer_size, period_frames, buffer_frames;
pa_assert(m);
u->first = true;
u->hal_interface = pa_hal_interface_get(u->core);
u->rtpoll = pa_rtpoll_new();
+#ifdef SUPPORT_AEC
+ u->ss = ss;
+#endif
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) {
pa_proplist_sets(data.proplist, "tizen.device", u->device);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen");
+ frame_size = pa_frame_size(&ss);
+ buffer_size = u->frag_size * u->nfrags;
+ buffer_frames = buffer_size / frame_size;
+ period_frames = u->frag_size / frame_size;
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size);
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size);
+
if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log_error("Invalid properties.");
pa_source_new_data_done(&data);