From 290a87181918376a0a1130885010358ab9106668 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Tue, 22 Feb 2022 19:34:43 +0900 Subject: [PATCH 01/16] device-manager: Fix svace issues (DEREF_OF_NULL.RET.STAT) - Add null check for pulse_device_get_proplist() - Use foreach macro for dynarray on handle_internal_pulse_device() - Remove unnecessary braces - Fix some invalid indents - Revise some code format [Version] 15.0.1 [Issue Type] Svace Change-Id: I9ae16c35194f94633063b38b49ff133d9372538d --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 261 ++++++++++++-------------------- 2 files changed, 100 insertions(+), 163 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 2194382..f19401e 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.0 +Version: 15.0.1 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index 7cb46c9..510ef24 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -315,21 +315,19 @@ static void file_info_free_func(struct device_file_info *file_info) { } static dm_device_class_t device_string_get_class(const char *device_string) { - if (!device_string) { + if (!device_string) return DM_DEVICE_CLASS_NONE; - } - if (device_string == strstr(device_string, "alsa")) { + if (device_string == strstr(device_string, "alsa")) return DM_DEVICE_CLASS_ALSA; - } else if (device_string == strstr(device_string, "null")) { + else if (device_string == strstr(device_string, "null")) return DM_DEVICE_CLASS_NULL; - } else if (device_string == strstr(device_string, "tizen")) { + else if (device_string == strstr(device_string, "tizen")) return DM_DEVICE_CLASS_TIZEN; - } else if (device_string == strstr(device_string, "acm")) { + else if (device_string == strstr(device_string, "acm")) return DM_DEVICE_CLASS_ACM; - } else { - return DM_DEVICE_CLASS_NONE; - } + + return DM_DEVICE_CLASS_NONE; } /* device_string looks like "alsa:0,0" or "tizen:0,0" @@ -359,46 +357,37 @@ static int device_string_get_name(const char *device_string, char *name) { } static pa_proplist* pulse_device_get_proplist(pa_object *pdevice) { - if (pa_sink_isinstance(pdevice)) - return PA_SINK(pdevice)->proplist; - else - return PA_SOURCE(pdevice)->proplist; + if (!pdevice) + return NULL; + + return pa_sink_isinstance(pdevice) ? PA_SINK(pdevice)->proplist : + PA_SOURCE(pdevice)->proplist; } static bool pulse_device_is_alsa(pa_object *pdevice) { const char *api_name = NULL; - pa_proplist *prop; + pa_proplist *prop = pulse_device_get_proplist(pdevice); - if ((prop = pulse_device_get_proplist(pdevice)) == NULL) + if (!prop) return false; - if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) { - if (pa_safe_streq(api_name, DEVICE_API_ALSA)) { - return true; - } else { - return false; - } - } else { - return false; - } + if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) + return pa_safe_streq(api_name, DEVICE_API_ALSA); + + return false; } static bool pulse_device_is_bluez(pa_object *pdevice) { const char *api_name = NULL; - pa_proplist *prop; + pa_proplist *prop = pulse_device_get_proplist(pdevice); - if ((prop = pulse_device_get_proplist(pdevice)) == NULL) + if (!prop) return false; - if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) { - if (pa_safe_streq(api_name, DEVICE_API_BLUEZ)) { - return true; - } else { - return false; - } - } else { - return false; - } + if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) + return pa_safe_streq(api_name, DEVICE_API_BLUEZ); + + return false; } static bool pulse_device_is_acm(pa_object *pdevice) { @@ -445,37 +434,26 @@ static bool pulse_device_is_tizenaudio(pa_object *pdevice) { static bool pulse_device_is_usb(pa_object *pdevice) { const char *bus_name = NULL; - pa_proplist *prop; + pa_proplist *prop = pulse_device_get_proplist(pdevice); - if ((prop = pulse_device_get_proplist(pdevice)) == NULL) + if (!prop) return false; - if ((bus_name = pa_proplist_gets(prop, PA_PROP_DEVICE_BUS))) { - if (pa_safe_streq(bus_name, DEVICE_BUS_USB)) { - return true; - } else { - return false; - } - } else { - pa_log_debug("This device doesn't have property '%s'", PA_PROP_DEVICE_BUS); - return false; - } + if ((bus_name = pa_proplist_gets(prop, PA_PROP_DEVICE_BUS))) + return pa_safe_streq(bus_name, DEVICE_BUS_USB); + + pa_log_debug("This device doesn't have property '%s'", PA_PROP_DEVICE_BUS); + return false; } static bool pulse_device_is_null(pa_object *pdevice) { - pa_sink *sink; - pa_source *source; - if (!pdevice) return false; - if (pa_sink_isinstance(pdevice)) { - sink = PA_SINK(pdevice); - return pa_safe_streq(sink->module->name, "module-null-sink"); - } else { - source = PA_SOURCE(pdevice); - return pa_safe_streq(source->module->name, "module-null-source"); - } + if (pa_sink_isinstance(pdevice)) + return pa_safe_streq(PA_SINK(pdevice)->module->name, "module-null-sink"); + else + return pa_safe_streq(PA_SOURCE(pdevice)->module->name, "module-null-source"); } static bool pulse_device_is_rtsp(pa_object *pdevice) { @@ -505,33 +483,24 @@ static const char* pulse_device_get_device_string_removed_argument(pa_object *pd const char *params_p, *params; char *end_p = NULL; int len = 0, prev_len = 0; - pa_sink *sink; - pa_source *source; - if (pa_sink_isinstance(pdevice)) { - sink = PA_SINK(pdevice); - params = sink->module->argument; - } else { - source = PA_SOURCE(pdevice); - params = source->module->argument; - } + params = pa_sink_isinstance(pdevice) ? PA_SINK(pdevice)->module->argument : + PA_SOURCE(pdevice)->module->argument; params_p = params; - if (!params) { + if (!params) return NULL; - } - if (!(device_string_p = strstr(params, "device="))) { + + if (!(device_string_p = strstr(params, "device="))) return params; - } next_p = device_string_p; - while (!isblank(*next_p)) { + while (!isblank(*next_p)) next_p++; - } - while (isblank(*next_p)) { + + while (isblank(*next_p)) next_p++; - } pa_strlcpy(removed_param, next_p, DEVICE_PARAM_STRING_MAX); @@ -554,38 +523,33 @@ static dm_device_class_t pulse_device_get_class(pa_object *pdevice) { return DM_DEVICE_CLASS_NONE; } - if (pulse_device_is_null(pdevice)) { + if (pulse_device_is_null(pdevice)) return DM_DEVICE_CLASS_NULL; - } else if (pulse_device_is_alsa(pdevice)) { + else if (pulse_device_is_alsa(pdevice)) return DM_DEVICE_CLASS_ALSA; - } else if (pulse_device_is_tizenaudio(pdevice)) { + else if (pulse_device_is_tizenaudio(pdevice)) return DM_DEVICE_CLASS_TIZEN; - } else if (pulse_device_is_bluez(pdevice)) { + else if (pulse_device_is_bluez(pdevice)) return DM_DEVICE_CLASS_BT; - } else if (pulse_device_is_acm(pdevice)) { + else if (pulse_device_is_acm(pdevice)) return DM_DEVICE_CLASS_ACM; - } else { - return DM_DEVICE_CLASS_NONE; - } + + return DM_DEVICE_CLASS_NONE; } static dm_device_direction_t pulse_device_get_direction(pa_object *pdevice) { - if (pa_sink_isinstance(pdevice)) - return DM_DEVICE_DIRECTION_OUT; - else - return DM_DEVICE_DIRECTION_IN; + return pa_sink_isinstance(pdevice) ? DM_DEVICE_DIRECTION_OUT : DM_DEVICE_DIRECTION_IN; } static bool pulse_device_is_monitor(pa_object *pdevice) { const char *device_class = NULL; - pa_proplist *prop; + pa_proplist *prop = pulse_device_get_proplist(pdevice); - prop = pulse_device_get_proplist(pdevice); + if (!prop) + return false; - if ((device_class = pa_proplist_gets(prop, PA_PROP_DEVICE_CLASS))) { - if (pa_safe_streq(device_class, DEVICE_CLASS_MONITOR)) - return true; - } + if ((device_class = pa_proplist_gets(prop, PA_PROP_DEVICE_CLASS))) + return pa_safe_streq(device_class, DEVICE_CLASS_MONITOR); return false; } @@ -671,9 +635,8 @@ static struct device_file_info* _device_manager_get_file_info(pa_idxset *file_in pa_assert(file_infos); PA_IDXSET_FOREACH(file_info, file_infos, file_idx) { - if (pa_safe_streq(file_info->device_string, device_string)) { + if (pa_safe_streq(file_info->device_string, device_string)) return file_info; - } } return NULL; @@ -694,7 +657,7 @@ static struct device_status_info* _device_status_new(const char *type, static void _device_status_free(struct device_status_info *status_info) { if (!status_info) - return ; + return; pa_xfree(status_info->type); pa_xfree(status_info->name); @@ -773,7 +736,7 @@ void device_set_detected(pa_device_manager *manager, const char *type, pa_assert(type); if (!device_type_is_need_detect(type)) - return ; + return; pa_log_info("Set device detected, type(%s) system_id(%s) -> %s(%d)", type, pa_strempty(system_id), @@ -832,10 +795,10 @@ pa_tz_device* device_list_get_device_by_id(pa_device_manager *manager, uint32_t pa_assert(manager->device_list); PA_IDXSET_FOREACH(device, manager->device_list, idx) { - if (pa_tz_device_get_id(device) == id) { + if (pa_tz_device_get_id(device) == id) return device; - } } + return NULL; } @@ -923,18 +886,11 @@ finish: static bool pulse_device_params_is_equal(pa_object *pdevice, const char *params) { const char *removed_module_args; const char *module_args; - pa_sink *sink; - pa_source *source; pa_assert(pdevice); - if (pa_sink_isinstance(pdevice)) { - sink = PA_SINK(pdevice); - module_args = sink->module->argument; - } else { - source = PA_SOURCE(pdevice); - module_args = source->module->argument; - } + module_args = pa_sink_isinstance(pdevice) ? PA_SINK(pdevice)->module->argument : + PA_SOURCE(pdevice)->module->argument; if (!params && !module_args) return 0; @@ -942,6 +898,7 @@ static bool pulse_device_params_is_equal(pa_object *pdevice, const char *params) return -1; removed_module_args = pulse_device_get_device_string_removed_argument(pdevice); + return device_params_is_equal(params, removed_module_args); } @@ -1189,13 +1146,10 @@ static int pulse_device_get_product_id(pa_object *pdevice) { static void pulse_device_set_use_internal_codec(pa_object *pdevice, bool use_internal_codec) { pa_assert(pdevice); - if (pa_sink_isinstance(pdevice)) { - pa_sink *sink = PA_SINK(pdevice); - sink->use_internal_codec = use_internal_codec; - } else { - pa_source *source = PA_SOURCE(pdevice); - source->use_internal_codec = use_internal_codec; - } + if (pa_sink_isinstance(pdevice)) + PA_SINK(pdevice)->use_internal_codec = use_internal_codec; + else + PA_SOURCE(pdevice)->use_internal_codec = use_internal_codec; } /* Get system_id of physical device, it should be a unique id */ @@ -1203,6 +1157,7 @@ static const char* pulse_device_get_system_id(pa_object *pdevice) { pa_proplist *prop; prop = pulse_device_get_proplist(pdevice); + if (pulse_device_is_usb(pdevice)) return pa_proplist_gets(prop, "sysfs.path"); else if (pulse_device_is_bluez(pdevice)) @@ -1217,8 +1172,8 @@ static const char* pulse_device_get_system_id(pa_object *pdevice) { else if (pulse_device_is_btsco(pdevice)) return pa_proplist_gets(prop, PA_PROP_DEVICE_DESCRIPTION); #endif - else - return NULL; + + return NULL; } static pa_sink* _core_get_sink(pa_core *core, const char *device_string, const char *params) { @@ -1231,12 +1186,10 @@ static pa_sink* _core_get_sink(pa_core *core, const char *device_string, const c PA_IDXSET_FOREACH(sink, core->sinks, device_idx) { if (pulse_device_is_monitor(PA_OBJECT(sink))) continue; - if (pulse_device_same_device_string(PA_OBJECT(sink), device_string)) { - if (params == NULL) - return sink; - else if (pulse_device_params_is_equal(PA_OBJECT(sink), params)) + + if (pulse_device_same_device_string(PA_OBJECT(sink), device_string)) + if (params == NULL || pulse_device_params_is_equal(PA_OBJECT(sink), params)) return sink; - } } return NULL; @@ -1252,12 +1205,10 @@ static pa_source* _core_get_source(pa_core *core, const char *device_string, con PA_IDXSET_FOREACH(source, core->sources, device_idx) { if (pulse_device_is_monitor(PA_OBJECT(source))) continue; - if (pulse_device_same_device_string(PA_OBJECT(source), device_string)) { - if (params == NULL) - return source; - else if (pulse_device_params_is_equal(PA_OBJECT(source), params)) + + if (pulse_device_same_device_string(PA_OBJECT(source), device_string)) + if (params == NULL || pulse_device_params_is_equal(PA_OBJECT(source), params)) return source; - } } return NULL; @@ -1688,6 +1639,7 @@ static void handle_internal_pulse_device(pa_object *pdevice, bool is_loaded, pa_ struct composite_type *ctype; pa_dynarray *ctypes; dm_device_direction_t direction; + int idx; pa_assert(pdevice); pa_assert(dm); @@ -1698,38 +1650,27 @@ static void handle_internal_pulse_device(pa_object *pdevice, bool is_loaded, pa_ /* Get types which this pulse_device belongs to */ if ((ctypes = pulse_device_get_belongs_type(pdevice, dm)) == NULL) { pa_log_debug("Failed to get device type. Skip this"); - return ; + return; } - if (is_loaded) { - /* Put this pulse_device to already loaded devices */ - for (int i = 0; i < pa_dynarray_size(ctypes); i++) { - ctype = pa_dynarray_get(ctypes, i); - pa_log_info("Found belongs type %s.%s", ctype->type, ctype->role); - if ((device = device_list_get_device(dm, ctype->type, ctype->role, NULL))) { - pa_log_info("Add this pulse_device to device(%u)", pa_tz_device_get_id(device)); + /* Put/Remove this pulse_device to already loaded devices */ + PA_DYNARRAY_FOREACH(ctype, ctypes, idx) { + pa_log_info("Found belongs type %s.%s", ctype->type, ctype->role); + if ((device = device_list_get_device(dm, ctype->type, ctype->role, NULL))) { + pa_log_info("%s this pulse_device to device(%u)", is_loaded ? "Add" : "Remove", pa_tz_device_get_id(device)); + if (is_loaded) { if (direction == DM_DEVICE_DIRECTION_OUT) pa_tz_device_add_sink(device, ctype->role, PA_SINK(pdevice)); else pa_tz_device_add_source(device, ctype->role, PA_SOURCE(pdevice)); } else { - pa_log_info("No device for %s.%s", ctype->type, ctype->role); - } - } - } else { - /* Remove this pulse_device from already loaded devices */ - for (int i = 0; i < pa_dynarray_size(ctypes); i++) { - ctype = pa_dynarray_get(ctypes, i); - pa_log_info("Found belongs type %s.%s", ctype->type, ctype->role); - if ((device = device_list_get_device(dm, ctype->type, ctype->role, NULL))) { - pa_log_info("Remove this pulse_device from device(%u)", pa_tz_device_get_id(device)); if (direction == DM_DEVICE_DIRECTION_OUT) pa_tz_device_remove_sink(device, PA_SINK(pdevice)); else pa_tz_device_remove_source(device, PA_SOURCE(pdevice)); - } else { - pa_log_info("No device for %s.%s", ctype->type, ctype->role); } + } else { + pa_log_info("No device for %s.%s", ctype->type, ctype->role); } } @@ -1772,10 +1713,10 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, pa_dev handle_external_btsco_pulse_device(PA_OBJECT(sink), true, dm); return PA_HOOK_OK; #endif - } else { - pa_log_debug("Don't care this sink"); } + pa_log_debug("Don't care this sink"); + return PA_HOOK_OK; } @@ -1821,10 +1762,10 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, pa_ handle_external_btsco_pulse_device(PA_OBJECT(sink), false, dm); return PA_HOOK_OK; #endif - } else { - pa_log_debug("Don't care this sink"); } + pa_log_debug("Don't care this sink"); + return PA_HOOK_OK; } @@ -1859,10 +1800,10 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, pulse_device_set_use_internal_codec(PA_OBJECT(source), false); handle_external_btsco_pulse_device(PA_OBJECT(source), true, dm); #endif - } else { - pa_log_debug("Don't care this source"); } + pa_log_debug("Don't care this source"); + return PA_HOOK_OK; } @@ -1904,10 +1845,10 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc handle_external_btsco_pulse_device(PA_OBJECT(source), false, dm); return PA_HOOK_OK; #endif - } else { - pa_log_debug("Don't care this source"); } + pa_log_debug("Don't care this source"); + return PA_HOOK_OK; } @@ -1962,6 +1903,7 @@ static pa_hook_result_t sink_proplist_changed(pa_core *core, pa_sink *sink, pa_d pulse_device_set_use_internal_codec(PA_OBJECT(sink), false); handle_tunnel_pulse_device(PA_OBJECT(sink), pa_proplist_remote_is_allowed(sink->proplist), dm); } + return PA_HOOK_OK; } @@ -2245,11 +2187,9 @@ void handle_device_connected(pa_device_manager *dm, const char *type, const char pa_tz_device_new_data_done(&data); } else { pa_log_error("Invalid case : not static direction"); - return ; + return; } } - - return ; } static int load_builtin_devices(pa_device_manager *dm) { @@ -2621,10 +2561,10 @@ static void device_type_status_init(pa_device_manager *manager) { pa_log_warn("Unknown earjack status : %d", earjack_status); } else if (device_type_is_equal(type, DEVICE_TYPE_BT_SCO)) { - device_set_detected(manager, type, NULL, NULL, DEVICE_DISCONNECTED); + device_set_detected(manager, type, NULL, NULL, DEVICE_DISCONNECTED); } else if (device_type_is_equal(type, DEVICE_TYPE_HDMI)) { - device_set_detected(manager, type, NULL, NULL, DEVICE_DISCONNECTED); + device_set_detected(manager, type, NULL, NULL, DEVICE_DISCONNECTED); } else if (device_type_is_equal(type, DEVICE_TYPE_FORWARDING)) { int miracast_wfd_status = 0; @@ -2639,7 +2579,6 @@ static void device_type_status_init(pa_device_manager *manager) { device_set_detected(manager, type, NULL, NULL, DEVICE_CONNECTED); } } - return ; } static pa_sink* load_sink(pa_device_manager *dm, const char *type, const char *role) { @@ -2849,8 +2788,6 @@ void unload_acm_sink(pa_device_manager *dm) { } unload_sink(dm, DEVICE_TYPE_NETWORK, DEVICE_ROLE_ACM); - - return; } pa_idxset* pa_device_manager_get_device_list(pa_device_manager *dm) { -- 2.7.4 From d972a340200f90a0b9afffb50d10cf139ebd1640 Mon Sep 17 00:00:00 2001 From: Sangchul Lee Date: Wed, 23 Feb 2022 14:40:50 +0900 Subject: [PATCH 02/16] device-manager: Add null check in handle_device_connected() When the last parameter of handle_device_connected() is NULL, it gets the required structure again from the list referring to device-map.json. The result is checked now to avoid unwanted assertion by other functions. [Version] 15.0.2 [Issue Type] Improvement Change-Id: Id3c07d579b1d273386a6eab2e6618abf27baabf8 Signed-off-by: Sangchul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index f19401e..667909a 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.1 +Version: 15.0.2 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index 510ef24..51fdba9 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -2128,8 +2128,10 @@ void handle_device_connected(pa_device_manager *dm, const char *type, const char pa_log_info("Device connected, type(%s) name(%s) system_id(%s) detected_type(%d)", type, pa_strempty(name), pa_strempty(system_id), detected_type); - if (!type_info) - type_info = _device_manager_get_type_info(dm->type_infos, type, NULL); + if (!type_info && !(type_info = _device_manager_get_type_info(dm->type_infos, type, NULL))) { + pa_log_error("failed to _device_manager_get_type_info(), check device-map.json first"); + return; + } if (device_type_is_equal(type, DEVICE_TYPE_BT_SCO)) { pa_tz_device_new_data_init(&data, dm->device_list, dm->comm, dm->dbus_conn); -- 2.7.4 From 6353d6c26a851101dbe98234748fb6bcecd07535 Mon Sep 17 00:00:00 2001 From: jungsup lee Date: Mon, 21 Feb 2022 13:17:50 +0900 Subject: [PATCH 03/16] tizenaudio-sink: Reset pollfd's revents after use revents should be cleared after handling POLLERR. mainloop send a lot of async message to I/O thread that is stucking on writing pcm. After that, I/O thread couldn't call poll api because of handling asyncq message. [Version] 15.0.3 [Issue Type] Bug fix Change-Id: Ib806cdd77a86fae648b08a5b5e1edc1ed60fbec5 Signed-off-by: jungsup lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-tizenaudio-sink.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 667909a..64ffcb3 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.2 +Version: 15.0.3 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 9dc3226..6c56de9 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -150,6 +150,9 @@ static int build_pollfd(struct userdata *u) { u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); + + pa_log_info("get fd(%d) of PCM device successfully", fd); + if (ret < 0 || fd < 0) { pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); return -1; @@ -402,6 +405,7 @@ static int process_render(struct userdata *u, pa_usec_t now) { void *p; size_t frames_to_write, frame_size; uint32_t avail = 0; + pa_usec_t before_write, after_write; pa_assert(u); @@ -449,7 +453,12 @@ static int process_render(struct userdata *u, pa_usec_t now) { pa_sink_render_full(u->sink, frames_to_write * frame_size, &chunk); p = pa_memblock_acquire(chunk.memblock); + before_write = pa_rtclock_now(); pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write); + after_write = pa_rtclock_now(); + + if (after_write - before_write > 10 * PA_USEC_PER_MSEC) + pa_log_warn("write takes long time %" PRIu64 " : (%" PRIu64 " - %" PRIu64 ")", 10 * PA_USEC_PER_MSEC, after_write, before_write); pa_memblock_release(chunk.memblock); pa_memblock_unref(chunk.memblock); @@ -532,9 +541,12 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); revents = pollfd->revents; if (revents & ~POLLOUT) { - pa_log_debug("Poll error 0x%x occured, try recover.", revents); + pa_log_error("Poll error 0x%x(%d) occured, try recover.", revents, pollfd->fd); pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); u->first = true; + + /* Need to reset revents because pa_rtpoll_run just returns without calling poll */ + pollfd->revents = 0; revents = 0; } else { //pa_log_debug("Poll wakeup.", revents); -- 2.7.4 From 0833683089f3b2c2bc50bff37e477b2e41cd0705 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 15 Mar 2022 11:17:22 +0900 Subject: [PATCH 04/16] Support stream-based echo-cancellation * Added module-tizenaudio-echo-cancel (Supported adrian, speex) * Added processor module that processes resampled pcm data * Added a few messages in sink2/source2 [Version] 15.0.4 [Issue Type] New Feature Change-Id: Ie6e8c3de0bf5fec20c3a087daf377fdcbd82303b Signed-off-by: Jaechul Lee --- Makefile.am | 28 +- configure.ac | 4 + packaging/pulseaudio-modules-tizen.spec | 5 +- src/echo-cancel/adrian-aec.c | 433 +++++++++++ src/echo-cancel/adrian-aec.h | 248 ++++++ src/echo-cancel/algo_adrian.c | 93 +++ src/echo-cancel/algo_speex.c | 138 ++++ src/echo-cancel/echo-cancel-def.h | 39 + src/echo-cancel/module-tizenaudio-echo-cancel.c | 985 ++++++++++++++++++++++++ src/echo-cancel/processor.c | 186 +++++ src/echo-cancel/processor.h | 64 ++ src/module-tizenaudio-sink2.c | 55 +- src/module-tizenaudio-source2.c | 69 +- 13 files changed, 2320 insertions(+), 27 deletions(-) create mode 100644 src/echo-cancel/adrian-aec.c create mode 100644 src/echo-cancel/adrian-aec.h create mode 100644 src/echo-cancel/algo_adrian.c create mode 100644 src/echo-cancel/algo_speex.c create mode 100644 src/echo-cancel/echo-cancel-def.h create mode 100644 src/echo-cancel/module-tizenaudio-echo-cancel.c create mode 100644 src/echo-cancel/processor.c create mode 100644 src/echo-cancel/processor.h diff --git a/Makefile.am b/Makefile.am index 260a272..768114b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,7 @@ MODULE_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS) pulsemodlibexec_LTLIBRARIES = \ libhal-interface.la \ + libprocessor.la \ libcommunicator.la \ module-tizenaudio-sink.la \ module-tizenaudio-source.la \ @@ -44,16 +45,15 @@ pulsemodlibexec_LTLIBRARIES = \ module-tizenaudio-policy.la \ module-tizenaudio-discover.la \ module-tizenaudio-publish.la \ + module-tizenaudio-echo-cancel.la \ module-sound-player.la \ module-tone-player.la \ module-poweroff.la if ENABLE_HALTC -pulsemodlibexec_LTLIBRARIES += \ - module-tizenaudio-haltc.la +pulsemodlibexec_LTLIBRARIES += module-tizenaudio-haltc.la endif if ENABLE_ACM -pulsemodlibexec_LTLIBRARIES += \ - module-acm-sink.la +pulsemodlibexec_LTLIBRARIES += module-acm-sink.la endif libhal_interface_la_SOURCES = \ @@ -78,16 +78,32 @@ module_tizenaudio_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_source_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la module_tizenaudio_source_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source -module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c +module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c src/echo-cancel/echo-cancel-def.h module_tizenaudio_sink2_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la module_tizenaudio_sink2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_sink2 -module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c +module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c src/echo-cancel/echo-cancel-def.h module_tizenaudio_source2_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source2 +libprocessor_la_SOURCES = \ + src/echo-cancel/algo_speex.c \ + src/echo-cancel/algo_adrian.c \ + src/echo-cancel/adrian-aec.c \ + src/echo-cancel/processor.c \ + src/echo-cancel/processor.h \ + src/echo-cancel/adrian-aec.h +libprocessor_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version +libprocessor_la_LIBADD = $(AM_LIBADD) $(LIBSPEEX_LIBS) +libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS) + +module_tizenaudio_echo_cancel_la_SOURCES = src/echo-cancel/module-tizenaudio-echo-cancel.c src/echo-cancel/echo-cancel-def.h +module_tizenaudio_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS) +module_tizenaudio_echo_cancel_la_LIBADD = $(MODULE_LIBADD) libprocessor.la +module_tizenaudio_echo_cancel_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_echo_cancel + module_sound_player_la_SOURCES = src/module-sound-player.c module_sound_player_la_LDFLAGS = $(MODULE_LDFLAGS) module_sound_player_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) diff --git a/configure.ac b/configure.ac index acaaf3d..de19973 100644 --- a/configure.ac +++ b/configure.ac @@ -369,6 +369,10 @@ PKG_CHECK_MODULES(HALAPIAUDIO, hal-api-audio) AC_SUBST(HALAPIAUDIO_CFLAGS) AC_SUBST(HALAPIAUDIO_LIBS) +PKG_CHECK_MODULES(SPEEX, speexdsp) +AC_SUBST(SPEEX_CFLAGS) +AC_SUBST(SPEEX_LIBS) + dnl use hal tc ------------------------------------------------------------ AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]), [ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 64ffcb3..1a86b8f 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.3 +Version: 15.0.4 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -23,6 +23,7 @@ BuildRequires: pkgconfig(pulsecore) BuildRequires: pkgconfig(libsystemd) BuildRequires: pkgconfig(dns_sd) BuildRequires: pkgconfig(hal-api-audio) +BuildRequires: pkgconfig(speexdsp) BuildRequires: pulseaudio BuildRequires: m4 Requires(post): /sbin/ldconfig @@ -88,6 +89,8 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-source2.so %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-discover.so %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-publish.so +%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-echo-cancel.so +%{_libdir}/pulse-%{module_ver}/modules/libprocessor.so %{_libdir}/pulse-%{module_ver}/modules/libhal-interface.so %{_libdir}/pulse-%{module_ver}/modules/libcommunicator.so %{_tmpfilesdir}/pulseaudio.conf diff --git a/src/echo-cancel/adrian-aec.c b/src/echo-cancel/adrian-aec.c new file mode 100644 index 0000000..aaec55a --- /dev/null +++ b/src/echo-cancel/adrian-aec.c @@ -0,0 +1,433 @@ +/* aec.cpp + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * + * Acoustic Echo Cancellation NLMS-pw algorithm + * + * Version 0.3 filter created with www.dsptutor.freeuk.com + * Version 0.3.1 Allow change of stability parameter delta + * Version 0.4 Leaky Normalized LMS - pre whitening algorithm + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "adrian-aec.h" + +#ifndef DISABLE_ORC +#include "adrian-aec-orc-gen.h" +#endif + +#ifdef __SSE__ +#include +#endif + +/* Double-Talk Detector + * + * in d: microphone sample (PCM as REALing point value) + * in x: loudspeaker sample (PCM as REALing point value) + * return: from 0 for doubletalk to 1.0 for single talk +*/ +static float AEC_dtd(AEC *a, REAL d, REAL x); + +static void AEC_leaky(AEC *a); + +/* Normalized Least Mean Square Algorithm pre-whitening (NLMS-pw) + * The LMS algorithm was developed by Bernard Widrow + * book: Haykin, Adaptive Filter Theory, 4. edition, Prentice Hall, 2002 + * + * in d: microphone sample (16bit PCM value) + * in x_: loudspeaker sample (16bit PCM value) + * in stepsize: NLMS adaptation variable + * return: echo cancelled microphone sample + */ +static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize); + +static void AEC_setambient(AEC *a, float Min_xf) { + a->dotp_xf_xf -= a->delta; // subtract old delta + a->delta = (NLMS_LEN-1) * Min_xf * Min_xf; + a->dotp_xf_xf += a->delta; // add new delta + } + +static REAL IIR1_highpass(IIR1 *i, REAL in) { + REAL out = i->a0 * in + i->a1 * i->in0 + i->b1 * i->out0; + i->in0 = in; + i->out0 = out; + return out; + } + +static IIR1* IIR1_init(REAL Fc) { + IIR1 *i = pa_xnew(IIR1, 1); + i->b1 = expf(-2.0f * M_PI * Fc); + i->a0 = (1.0f + i->b1) / 2.0f; + i->a1 = -(i->a0); + i->in0 = 0.0f; + i->out0 = 0.0f; + return i; +} + +static REAL IIR_HP_highpass(IIR_HP *i, REAL in) { + const REAL a0 = 0.01f; /* controls Transfer Frequency */ + /* Highpass = Signal - Lowpass. Lowpass = Exponential Smoothing */ + i->x += a0 * (in - i->x); + return in - i->x; + } + +static IIR_HP* IIR_HP_init(void) { + IIR_HP *i = pa_xnew(IIR_HP, 1); + i->x = 0.0f; + return i; + } + +#if WIDEB==1 +/* 17 taps FIR Finite Impulse Response filter + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +class FIR_HP_300Hz { + REAL z[18]; + +public: + FIR_HP_300Hz() { + memset(this, 0, sizeof(FIR_HP_300Hz)); + } + + REAL highpass(REAL in) { + const REAL a[18] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 300.0 - 4000.0 Hz, Order: 16 + // Transition band: 75.0 Hz, Stopband attenuation: 10.0 dB + -0.034870606, -0.039650206, -0.044063766, -0.04800318, + -0.051370874, -0.054082647, -0.056070227, -0.057283327, + 0.8214126, -0.057283327, -0.056070227, -0.054082647, + -0.051370874, -0.04800318, -0.044063766, -0.039650206, + -0.034870606, 0.0 + }; + memmove(z + 1, z, 17 * sizeof(REAL)); + z[0] = in; + REAL sum0 = 0.0, sum1 = 0.0; + int j; + + for (j = 0; j < 18; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * z[j]; + sum1 += a[j + 1] * z[j + 1]; + } + return sum0 + sum1; + } +}; + +#else + +/* 35 taps FIR Finite Impulse Response filter + * Passband 150Hz to 4kHz for 8kHz sample rate, 300Hz to 8kHz for 16kHz + * sample rate. + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +struct FIR_HP_300Hz { + REAL z[36]; +}; + +static FIR_HP_300Hz* FIR_HP_300Hz_init(void) { + FIR_HP_300Hz *ret = pa_xnew(FIR_HP_300Hz, 1); + memset(ret, 0, sizeof(FIR_HP_300Hz)); + return ret; + } + +static REAL FIR_HP_300Hz_highpass(FIR_HP_300Hz *f, REAL in) { + REAL sum0 = 0.0, sum1 = 0.0; + int j; + const REAL a[36] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 150.0 - 4000.0 Hz, Order: 34 + // Transition band: 34.0 Hz, Stopband attenuation: 10.0 dB + -0.016165324, -0.017454365, -0.01871232, -0.019931411, + -0.021104068, -0.022222936, -0.02328091, -0.024271343, + -0.025187887, -0.02602462, -0.026776174, -0.027437767, + -0.028004972, -0.028474221, -0.028842418, -0.029107114, + -0.02926664, 0.8524841, -0.02926664, -0.029107114, + -0.028842418, -0.028474221, -0.028004972, -0.027437767, + -0.026776174, -0.02602462, -0.025187887, -0.024271343, + -0.02328091, -0.022222936, -0.021104068, -0.019931411, + -0.01871232, -0.017454365, -0.016165324, 0.0 + }; + memmove(f->z + 1, f->z, 35 * sizeof(REAL)); + f->z[0] = in; + + for (j = 0; j < 36; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * f->z[j]; + sum1 += a[j + 1] * f->z[j + 1]; + } + return sum0 + sum1; + } +#endif + + + + + +/* Vector Dot Product */ +static REAL dotp(REAL a[], REAL b[]) +{ + REAL sum0 = 0.0f, sum1 = 0.0f; + int j; + + for (j = 0; j < NLMS_LEN; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * b[j]; + sum1 += a[j + 1] * b[j + 1]; + } + return sum0 + sum1; +} + +static REAL dotp_sse(REAL a[], REAL b[]) +{ +#ifdef __SSE__ + /* This is taken from speex's inner product implementation */ + int j; + REAL sum; + __m128 acc = _mm_setzero_ps(); + + for (j=0;jj = NLMS_EXT; + AEC_setambient(a, NoiseFloor); + a->dfast = a->dslow = M75dB_PCM; + a->xfast = a->xslow = M80dB_PCM; + a->gain = 1.0f; + a->Fx = IIR1_init(2000.0f/RATE); + a->Fe = IIR1_init(2000.0f/RATE); + a->cutoff = FIR_HP_300Hz_init(); + a->acMic = IIR_HP_init(); + a->acSpk = IIR_HP_init(); + + a->aes_y2 = M0dB; + + a->fdwdisplay = -1; + + if (have_vector) { + /* Get a 16-byte aligned location */ + a->w = (REAL *) (((uintptr_t) a->w_arr) - (((uintptr_t) a->w_arr) % 16) + 16); + a->dotp = dotp_sse; + } else { + /* We don't care about alignment, just use the array as-is */ + a->w = a->w_arr; + a->dotp = dotp; + } + + return a; +} + +void AEC_done(AEC *a) { + pa_assert(a); + + pa_xfree(a->Fx); + pa_xfree(a->Fe); + pa_xfree(a->acMic); + pa_xfree(a->acSpk); + pa_xfree(a->cutoff); + pa_xfree(a); +} + +// Adrian soft decision DTD +// (Dual Average Near-End to Far-End signal Ratio DTD) +// This algorithm uses exponential smoothing with different +// ageing parameters to get fast and slow near-end and far-end +// signal averages. The ratio of NFRs term +// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize +// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is +// mapped to 1.0 with a limited linear function. +static float AEC_dtd(AEC *a, REAL d, REAL x) +{ + float ratio, stepsize; + + // fast near-end and far-end average + a->dfast += ALPHAFAST * (fabsf(d) - a->dfast); + a->xfast += ALPHAFAST * (fabsf(x) - a->xfast); + + // slow near-end and far-end average + a->dslow += ALPHASLOW * (fabsf(d) - a->dslow); + a->xslow += ALPHASLOW * (fabsf(x) - a->xslow); + + if (a->xfast < M70dB_PCM) { + return 0.0f; // no Spk signal + } + + if (a->dfast < M70dB_PCM) { + return 0.0f; // no Mic signal + } + + // ratio of NFRs + ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast); + + // Linear interpolation with clamping at the limits + if (ratio < STEPX1) + stepsize = STEPY1; + else if (ratio > STEPX2) + stepsize = STEPY2; + else + stepsize = STEPY1 + (STEPY2 - STEPY1) * (ratio - STEPX1) / (STEPX2 - STEPX1); + + return stepsize; +} + + +static void AEC_leaky(AEC *a) +// The xfast signal is used to charge the hangover timer to Thold. +// When hangover expires (no Spk signal for some time) the vector w +// is erased. This is my implementation of Leaky NLMS. +{ + if (a->xfast >= M70dB_PCM) { + // vector w is valid for hangover Thold time + a->hangover = Thold; + } else { + if (a->hangover > 1) { + --(a->hangover); + } else if (1 == a->hangover) { + --(a->hangover); + // My Leaky NLMS is to erase vector w when hangover expires + memset(a->w_arr, 0, sizeof(a->w_arr)); + } + } +} + + +#if 0 +void AEC::openwdisplay() { + // open TCP connection to program wdisplay.tcl + fdwdisplay = socket_async("127.0.0.1", 50999); +}; +#endif + + +static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize) +{ + REAL e; + REAL ef; + a->x[a->j] = x_; + a->xf[a->j] = IIR1_highpass(a->Fx, x_); // pre-whitening of x + + // calculate error value + // (mic signal - estimated mic signal from spk signal) + e = d; + if (a->hangover > 0) { + e -= a->dotp(a->w, a->x + a->j); + } + ef = IIR1_highpass(a->Fe, e); // pre-whitening of e + + // optimize: iterative dotp(xf, xf) + a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]); + + if (stepsize > 0.0f) { + // calculate variable step size + REAL mikro_ef = stepsize * ef / a->dotp_xf_xf; + +#ifdef DISABLE_ORC + // update tap weights (filter learning) + int i; + for (i = 0; i < NLMS_LEN; i += 2) { + // optimize: partial loop unrolling + a->w[i] += mikro_ef * a->xf[i + a->j]; + a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1]; + } +#else + update_tap_weights(a->w, &a->xf[a->j], mikro_ef, NLMS_LEN); +#endif + } + + if (--(a->j) < 0) { + // optimize: decrease number of memory copies + a->j = NLMS_EXT; + memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL)); + memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL)); + } + + // Saturation + if (e > MAXPCM) { + return MAXPCM; + } else if (e < -MAXPCM) { + return -MAXPCM; + } else { + return e; + } +} + + +int AEC_doAEC(AEC *a, int d_, int x_) +{ + REAL d = (REAL) d_; + REAL x = (REAL) x_; + + // Mic Highpass Filter - to remove DC + d = IIR_HP_highpass(a->acMic, d); + + // Mic Highpass Filter - cut-off below 300Hz + d = FIR_HP_300Hz_highpass(a->cutoff, d); + + // Amplify, for e.g. Soundcards with -6dB max. volume + d *= a->gain; + + // Spk Highpass Filter - to remove DC + x = IIR_HP_highpass(a->acSpk, x); + + // Double Talk Detector + a->stepsize = AEC_dtd(a, d, x); + + // Leaky (ageing of vector w) + AEC_leaky(a); + + // Acoustic Echo Cancellation + d = AEC_nlms_pw(a, d, x, a->stepsize); + +#if 0 + if (fdwdisplay >= 0) { + if (++dumpcnt >= (WIDEB*RATE/10)) { + // wdisplay creates 10 dumps per seconds = large CPU load! + dumpcnt = 0; + write(fdwdisplay, ws, DUMP_LEN*sizeof(float)); + // we don't check return value. This is not production quality!!! + memset(ws, 0, sizeof(ws)); + } else { + int i; + for (i = 0; i < DUMP_LEN; i += 2) { + // optimize: partial loop unrolling + ws[i] += w[i]; + ws[i + 1] += w[i + 1]; + } + } + } +#endif + + return (int) d; +} diff --git a/src/echo-cancel/adrian-aec.h b/src/echo-cancel/adrian-aec.h new file mode 100644 index 0000000..1482923 --- /dev/null +++ b/src/echo-cancel/adrian-aec.h @@ -0,0 +1,248 @@ +/* aec.h + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * Author: Andre Adrian + * + * Acoustic Echo Cancellation Leaky NLMS-pw algorithm + * + * Version 0.3 filter created with www.dsptutor.freeuk.com + * Version 0.3.1 Allow change of stability parameter delta + * Version 0.4 Leaky Normalized LMS - pre whitening algorithm + */ + +#ifndef _AEC_H /* include only once */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#ifdef __TIZEN__ +#define DISABLE_ORC +#define _USE_MATH_DEFINES +#include +#endif + +#define WIDEB 2 + +// use double if your CPU does software-emulation of float +#define REAL float + +/* dB Values */ +#define M0dB 1.0f +#define M3dB 0.71f +#define M6dB 0.50f +#define M9dB 0.35f +#define M12dB 0.25f +#define M18dB 0.125f +#define M24dB 0.063f + +/* dB values for 16bit PCM */ +/* MxdB_PCM = 32767 * 10 ^(x / 20) */ +#define M10dB_PCM 10362.0f +#define M20dB_PCM 3277.0f +#define M25dB_PCM 1843.0f +#define M30dB_PCM 1026.0f +#define M35dB_PCM 583.0f +#define M40dB_PCM 328.0f +#define M45dB_PCM 184.0f +#define M50dB_PCM 104.0f +#define M55dB_PCM 58.0f +#define M60dB_PCM 33.0f +#define M65dB_PCM 18.0f +#define M70dB_PCM 10.0f +#define M75dB_PCM 6.0f +#define M80dB_PCM 3.0f +#define M85dB_PCM 2.0f +#define M90dB_PCM 1.0f + +#define MAXPCM 32767.0f + +/* Design constants (Change to fine tune the algorithms */ + +/* The following values are for hardware AEC and studio quality + * microphone */ + +/* NLMS filter length in taps (samples). A longer filter length gives + * better Echo Cancellation, but maybe slower convergence speed and + * needs more CPU power (Order of NLMS is linear) */ +#define NLMS_LEN (100*WIDEB*8) + +/* Vector w visualization length in taps (samples). + * Must match argv value for wdisplay.tcl */ +#define DUMP_LEN (40*WIDEB*8) + +/* minimum energy in xf. Range: M70dB_PCM to M50dB_PCM. Should be equal + * to microphone ambient Noise level */ +#define NoiseFloor M55dB_PCM + +/* Leaky hangover in taps. + */ +#define Thold (60 * WIDEB * 8) + +// Adrian soft decision DTD +// left point. X is ratio, Y is stepsize +#define STEPX1 1.0 +#define STEPY1 1.0 +// right point. STEPX2=2.0 is good double talk, 3.0 is good single talk. +#define STEPX2 2.5 +#define STEPY2 0 +#define ALPHAFAST (1.0f / 100.0f) +#define ALPHASLOW (1.0f / 20000.0f) + + + +/* Ageing multiplier for LMS memory vector w */ +#define Leaky 0.9999f + +/* Double Talk Detector Speaker/Microphone Threshold. Range <=1 + * Large value (M0dB) is good for Single-Talk Echo cancellation, + * small value (M12dB) is good for Double-Talk AEC */ +#define GeigelThreshold M6dB + +/* for Non Linear Processor. Range >0 to 1. Large value (M0dB) is good + * for Double-Talk, small value (M12dB) is good for Single-Talk */ +#define NLPAttenuation M12dB + +/* Below this line there are no more design constants */ + +typedef struct IIR_HP IIR_HP; + +/* Exponential Smoothing or IIR Infinite Impulse Response Filter */ +struct IIR_HP { + REAL x; +}; + +typedef struct FIR_HP_300Hz FIR_HP_300Hz; + +typedef struct IIR1 IIR1; + +/* Recursive single pole IIR Infinite Impulse response High-pass filter + * + * Reference: The Scientist and Engineer's Guide to Digital Processing + * + * output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1] + * + * X = exp(-2.0 * pi * Fc) + * A0 = (1 + X) / 2 + * A1 = -(1 + X) / 2 + * B1 = X + * Fc = cutoff freq / sample rate + */ +struct IIR1 { + REAL in0, out0; + REAL a0, a1, b1; +}; + +#if 0 + IIR1() { + memset(this, 0, sizeof(IIR1)); + } +#endif + + +#if 0 +/* Recursive two pole IIR Infinite Impulse Response filter + * Coefficients calculated with + * http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html + */ +class IIR2 { + REAL x[2], y[2]; + +public: + IIR2() { + memset(this, 0, sizeof(IIR2)); + } + + REAL highpass(REAL in) { + // Butterworth IIR filter, Filter type: HP + // Passband: 2000 - 4000.0 Hz, Order: 2 + const REAL a[] = { 0.29289323f, -0.58578646f, 0.29289323f }; + const REAL b[] = { 1.3007072E-16f, 0.17157288f }; + REAL out = + a[0] * in + a[1] * x[0] + a[2] * x[1] - b[0] * y[0] - b[1] * y[1]; + + x[1] = x[0]; + x[0] = in; + y[1] = y[0]; + y[0] = out; + return out; + } +}; +#endif + + +// Extension in taps to reduce mem copies +#define NLMS_EXT (10*8) + +// block size in taps to optimize DTD calculation +#define DTD_LEN 16 + +typedef struct AEC AEC; + +struct AEC { + // Time domain Filters + IIR_HP *acMic, *acSpk; // DC-level remove Highpass) + FIR_HP_300Hz *cutoff; // 150Hz cut-off Highpass + REAL gain; // Mic signal amplify + IIR1 *Fx, *Fe; // pre-whitening Highpass for x, e + + // Adrian soft decision DTD (Double Talk Detector) + REAL dfast, xfast; + REAL dslow, xslow; + + // NLMS-pw + REAL x[NLMS_LEN + NLMS_EXT]; // tap delayed loudspeaker signal + REAL xf[NLMS_LEN + NLMS_EXT]; // pre-whitening tap delayed signal + REAL w_arr[NLMS_LEN + (16 / sizeof(REAL))]; // tap weights + REAL *w; // this will be a 16-byte aligned pointer into w_arr + int j; // optimize: less memory copies + double dotp_xf_xf; // double to avoid loss of precision + float delta; // noise floor to stabilize NLMS + + // AES + float aes_y2; // not in use! + + // w vector visualization + REAL ws[DUMP_LEN]; // tap weights sums + int fdwdisplay; // TCP file descriptor + int dumpcnt; // wdisplay output counter + + // variables are public for visualization + int hangover; + float stepsize; + + // vfuncs that are picked based on processor features available + REAL (*dotp) (REAL[], REAL[]); +}; + +AEC* AEC_init(int RATE, int have_vector); +void AEC_done(AEC *a); + +/* Acoustic Echo Cancellation and Suppression of one sample + * in d: microphone signal with echo + * in x: loudspeaker signal + * return: echo cancelled microphone signal + */ + int AEC_doAEC(AEC *a, int d_, int x_); + +PA_GCC_UNUSED static float AEC_getambient(AEC *a) { + return a->dfast; + } +PA_GCC_UNUSED static void AEC_setgain(AEC *a, float gain_) { + a->gain = gain_; + } +#if 0 + void AEC_openwdisplay(AEC *a); +#endif +PA_GCC_UNUSED static void AEC_setaes(AEC *a, float aes_y2_) { + a->aes_y2 = aes_y2_; + } + +#define _AEC_H +#endif diff --git a/src/echo-cancel/algo_adrian.c b/src/echo-cancel/algo_adrian.c new file mode 100644 index 0000000..a5d2e72 --- /dev/null +++ b/src/echo-cancel/algo_adrian.c @@ -0,0 +1,93 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include "adrian-aec.h" + +struct ec_adrian { + int blocksize; + AEC *aec; +}; + +void *adrian_create(size_t nframes, pa_sample_spec *ss) { + struct ec_adrian *adrian = NULL; + + pa_assert(ss); + + if (ss->channels > 2 || ss->format != PA_SAMPLE_S16LE) { + pa_log_error("Invalid channels(%d) or format(%d)", ss->channels, ss->format); + return NULL; + } + + adrian = pa_xnew0(struct ec_adrian, 1); + + if (!(adrian->aec = AEC_init(ss->rate, 0))) { + pa_log_error("Failed to init AEC"); + goto fail; + } + adrian->blocksize = nframes * ss->channels * 2; /* format */ + + return adrian; + +fail: + pa_xfree(adrian); + + return NULL; +} + +int32_t adrian_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) { + struct ec_adrian *adrian = priv; + int i; + + assert(rec); + assert(ref); + assert(out); + + for (i=0; iblocksize; i+=2) { + int r = *(int16_t *)(rec + i); + int p = *(int16_t *)(ref + i); + *(int16_t *)(out + i) = (int16_t) AEC_doAEC(adrian->aec, r, p); + } + + return 0; +} + +int32_t adrian_destroy(void *priv) { + struct ec_adrian *adrian = priv; + + pa_assert(adrian); + + AEC_done(adrian->aec); + + pa_xfree(adrian); + + return 0; +} diff --git a/src/echo-cancel/algo_speex.c b/src/echo-cancel/algo_speex.c new file mode 100644 index 0000000..161812a --- /dev/null +++ b/src/echo-cancel/algo_speex.c @@ -0,0 +1,138 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct algo_speex { + SpeexEchoState *echo_state; + SpeexPreprocessState *preprocess; +}; + +void *speex_create(size_t nframes, pa_sample_spec *ss) { + struct algo_speex *speex = NULL; + spx_int32_t value = 1; + int rate; + + pa_assert(ss); + + if (ss->channels > 2 || ss->format != PA_SAMPLE_S16LE) { + pa_log_error("Invalid channels(%d) or format(%d)", ss->channels, ss->format); + return NULL; + } + + speex = pa_xnew0(struct algo_speex, 1); + + /* TODO: need to check. weird behavior */ + if (ss->channels == 2) + nframes *= 2; + + speex->echo_state = speex_echo_state_init(nframes, nframes * 10); + + if (!speex->echo_state) { + pa_log_error("_echo_state_init_mc failed"); + goto fail; + } + + rate = ss->rate; + if (!(speex->preprocess = speex_preprocess_state_init(nframes, rate))) { + pa_log_error("_preprocess_state_init failed"); + goto fail; + } + + if (speex_echo_ctl(speex->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate)) { + pa_log_error("_echo_ctl SET_SAMPLING_RATE failed"); + goto fail; + } + + if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_AGC, &value)) { + pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_AGC failed"); + goto fail; + } + + if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_DENOISE, &value)) { + pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_DENOISE failed"); + goto fail; + } + + if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_DEREVERB, &value)) { + pa_log_error("_echo_ctl SPEEX_PREPROCESS_SET_DEREVERB failed"); + goto fail; + } + + if (speex_preprocess_ctl(speex->preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE, speex->echo_state)) { + pa_log_error("_echo_ctl SET_ECHO_STATE failed"); + goto fail; + } + + pa_log_info("speex echo-canceller initialized. frame(%zu), rate(%d) channels(%d)", + nframes, ss->rate, ss->channels); + + return speex; + +fail: + pa_xfree(speex); + + return NULL; +} + +int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) { + struct algo_speex *speex = priv; + + assert(rec); + assert(ref); + assert(out); + + speex_echo_cancellation(speex->echo_state, + (const spx_int16_t *)rec, + (const spx_int16_t *)ref, + (spx_int16_t *)out); + + speex_preprocess_run(speex->preprocess, (spx_int16_t *)out); + + return 0; +} + +int32_t speex_destroy(void *priv) { + struct algo_speex *speex = priv; + + if (speex->echo_state) + speex_echo_state_destroy(speex->echo_state); + if (speex->preprocess) + speex_preprocess_state_destroy(speex->preprocess); + + pa_xfree(speex); + + return 0; +} diff --git a/src/echo-cancel/echo-cancel-def.h b/src/echo-cancel/echo-cancel-def.h new file mode 100644 index 0000000..4dbb240 --- /dev/null +++ b/src/echo-cancel/echo-cancel-def.h @@ -0,0 +1,39 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +enum { + PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, + PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, + PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, + PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK, +}; + +enum { + PA_SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX, + PA_SINK_MESSAGE_REBUILD_RTPOLL, + PA_SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX, + PA_SOURCE_MESSAGE_REBUILD_RTPOLL, +}; + diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c new file mode 100644 index 0000000..33482eb --- /dev/null +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -0,0 +1,985 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "echo-cancel-def.h" +#include "processor.h" + +PA_MODULE_AUTHOR("Tizen"); +PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); +PA_MODULE_USAGE( + "blocksize= "); + +typedef struct echo_cancel pa_echo_cancel; +struct userdata { + pa_core *core; + pa_module *m; + pa_sink *sink; + pa_source *source; + + pa_hook_slot *sink_unlink_slot; + pa_hook_slot *source_unlink_slot; + + pa_hook_slot *source_output_new_slot; + pa_hook_slot *source_output_put_slot; + pa_hook_slot *source_output_unlink_slot; + pa_hook_slot *source_output_unlink_post_slot; + pa_hook_slot *sink_state_changed_slot; + + bool enable; + uint32_t n_source_output; + size_t blocksize; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_echo_cancel *echo_cancel; + + /* use in thread */ + pa_memblockq *delayq; + bool enable_in_thread; + + pa_asyncmsgq *asyncmsgq_sink; + pa_asyncmsgq *asyncmsgq_source; +}; + +struct echo_cancel { + pa_msgobject parent; + struct userdata *u; +}; + +PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject); +#define PA_ECHO_CANCEL(o) (pa_echo_cancel_cast(o)) + +#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) +#define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL) +#define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output) + +static const char* const valid_modargs[] = { + "blocksize", + NULL, +}; + +static int proplist_get_fragment_size(pa_proplist *p, size_t *size) { + const char *fragsize; + uint32_t blocksize; + + if (!(fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE))) + return -1; + + if (pa_atou(fragsize, &blocksize)) + return -1; + + *size = blocksize; + + return 0; +} + +static pa_processor_algo_t pa_processor_get_algo(pa_source_output *o) { + const char *algo = pa_proplist_gets(o->proplist, "echo"); + + if (!algo) { + pa_log_warn("Use default processor(speex)"); + return PA_PROCESSOR_SPEEX; + } + + if (pa_streq(algo, "adrian")) + return PA_PROCESSOR_ADRIAN; + else if (pa_streq(algo, "speex")) + return PA_PROCESSOR_SPEEX; + else { + pa_log_warn("invalid algo(%s), Use default processor(speex)", algo); + return PA_PROCESSOR_SPEEX; + } +} + +static pa_source_output *find_source_output_by_flags(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_assert(s); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + + if (CHECK_FLAGS_AEC(o->flags)) + break; + } + + return o ? o : NULL; +} + +static void free_source_output_extra_resource(pa_source_output *o) { + pa_assert(o); + + if (o->thread_info.processor) { + pa_processor_free(o->thread_info.processor); + o->thread_info.processor = NULL; + } + + if (o->thread_info.resampler2) { + pa_resampler_free(o->thread_info.resampler2); + o->thread_info.resampler2 = NULL; + } + + if (o->thread_info.echo) { + pa_memblockq_free(o->thread_info.echo); + o->thread_info.echo = NULL; + } +} + +static void free_source_output_extra_resource_by_source(pa_source *s) { + pa_assert(s); + + free_source_output_extra_resource(find_source_output_by_flags(s)); +} + +static pa_usec_t get_round_trip_latency(struct userdata *u) { + pa_usec_t sink_latency; + pa_usec_t source_latency; + + pa_assert(u); + pa_assert(u->sink); + + sink_latency = pa_sink_get_latency(u->sink); + source_latency = pa_source_get_latency(u->source); + + pa_log_info("sink latency (%llu), source latency(%llu)", sink_latency, source_latency); + + return sink_latency + source_latency; +} + +static int setup_delayq_latency(struct userdata *u, pa_usec_t latency) { + int64_t write_index, read_index; + size_t bytes, blocksize, n; + pa_memchunk silence; + + if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) + return -1; + + if (u->delayq) + pa_memblockq_free(u->delayq); + + u->delayq = pa_memblockq_new("echo reference delay", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &u->sink->sample_spec, + 0, + blocksize, + 0, + NULL); + + bytes = pa_usec_to_bytes(latency, &u->sink->sample_spec); + n = (bytes + blocksize - 1) / blocksize; + + pa_silence_memchunk_get( + &u->sink->core->silence_cache, + u->sink->core->mempool, + &silence, + &u->sink->sample_spec, + blocksize); + + if (!silence.memblock) + return -1; + + write_index = pa_memblockq_get_write_index(u->delayq); + read_index = pa_memblockq_get_read_index(u->delayq); + + pa_memblockq_flush_write(u->delayq, true); + while (n-- > 0) + pa_memblockq_push(u->delayq, &silence); + + pa_log_info("push n(%d) blocks. write_index(%llu->%llu), read_index(%llu->%llu)", + pa_memblockq_get_nblocks(u->delayq), + write_index, pa_memblockq_get_write_index(u->delayq), + read_index, pa_memblockq_get_read_index(u->delayq)); + + pa_memblock_unref(silence.memblock); + + return 0; +} + +static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsgq *q) { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } args; + pa_asyncmsgq *asyncmsgq; + int code; + + pa_assert(dst); + + args.o = src; + args.q = q; + + if (pa_sink_isinstance(dst)) { + asyncmsgq = PA_SINK(dst)->asyncmsgq; + code = PA_SINK_MESSAGE_REBUILD_RTPOLL; + } else if (pa_source_isinstance(dst)) { + asyncmsgq = PA_SOURCE(dst)->asyncmsgq; + code = PA_SOURCE_MESSAGE_REBUILD_RTPOLL; + } else { + pa_assert_not_reached(); + } + + pa_asyncmsgq_send(asyncmsgq, dst, code, src ? (void *)&args : NULL, 0, NULL); + + return 0; +} + +/* Call from main thread */ +static void set_aec_state(struct userdata *u, bool enable) { + void *v[2]; + pa_usec_t latency = 0ULL; + + pa_assert(u); + pa_assert(u->source); + pa_assert(u->sink); /* not allow sink null */ + + pa_log_info("set_aec state %d -> %d", u->enable, enable); + + if (u->enable == enable) + return; + + latency = enable ? get_round_trip_latency(u) : 0ULL; + + v[0] = (void *)enable; + v[1] = &latency; + + /* There is a race condition between the source thread and the render thread. + * pa_source_post function can be overlapped at the same time */ + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->echo_cancel), + PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, (void *)v, 0, NULL); + + if (u->sink) + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + + pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), + PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + + u->enable = enable; + + pa_log_info("AEC state updated. enable(%d)", u->enable); +} + +static int update_state_by_sink(struct userdata *u, bool enable) { + pa_assert(u); + + if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) == 0) + return 0; + + set_aec_state(u, enable); + + return 0; +} + +static int update_state_by_source(struct userdata *u, bool enable) { + pa_assert(u); + + if (enable) { + if (u->n_source_output++ == 0) { + if (!u->sink || PA_SINK_IS_RUNNING(u->sink->state)) + set_aec_state(u, enable); + } + } else { + if (--u->n_source_output == 0) + set_aec_state(u, enable); + } + + return 0; +} + +static int unlink_source_output_in_thread(pa_source_output *o) { + pa_assert(o); + + free_source_output_extra_resource(o); + + /* source-output should be remove by render thread to prevent race condition. + * 1. invoke REMOVE message + * 2. receive the message in tizenaudio-source + * 3. send the message to tizenaudio-echo-cancel + * 4. remove source-output in render thread + */ + pa_source_process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + + return 0; +} + +static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { + pa_source_output *o = NULL; + pa_memchunk ochunk; + bool nf = false; + int r; + + o = find_source_output_by_flags(s); + if (!o) { + pa_log_error("Can't find aec source-output"); + return; + } + + if (o->thread_info.resampler2) { + pa_resampler_run(o->thread_info.resampler2, chunk, &ochunk); + chunk = &ochunk; + nf = true; + } + + r = pa_memblockq_push(o->thread_info.echo, chunk); + if (r != 0) + pa_log_error("Failed to push chunk to memblockq"); + + if (nf) + pa_memblock_unref(chunk->memblock); + + pa_log_debug("Pushed echo data. index(%u) size(%llums), nblocks(%d) index(%lld:%lld)", + o->index, + pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, + pa_memblockq_get_nblocks(o->thread_info.echo), + pa_memblockq_get_write_index(o->thread_info.echo), + pa_memblockq_get_read_index(o->thread_info.echo)); +} + +static void flush_echo_memblockq(pa_source *s) { + pa_source_output *o; + + o = find_source_output_by_flags(s); + if (!o) { + pa_log_error("Can't find aec source-output"); + return; + } + + pa_memblockq_flush_write(o->thread_info.echo, true); +} + +static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) { + int ret = -1; + int8_t *rec, *ref, *out; + size_t blocksize; + + pa_memchunk tchunk; + + if (!o->thread_info.processor) { + pa_log_error("Failed to get processor"); + return ret; + } + + /* + * pre-condition + * sink block >= source block >= blocksize * n + * if blocksize is not described blocksize, It should be same as source's fragment size + * + * chunk must be processed every data. + */ + + /* reference exist */ + if (o->thread_info.echo) { + size_t block_bytes, length, n; + + /* echo queue is not ready that means reference is not started */ + if (pa_memblockq_is_empty(o->thread_info.echo)) + return ret; + + blocksize = pa_processor_get_blocksize(o->thread_info.processor); + block_bytes = blocksize * pa_frame_size(&o->sample_spec); + + if (chunk->length % block_bytes) { + pa_log_warn("Skip to process aec. chunk size must be multiple of blocksize"); + return -1; + } + + n = chunk->length / block_bytes; + length = n * block_bytes; + + if (!(ret = pa_memblockq_peek_fixed_size(o->thread_info.echo, length, &tchunk))) { + int i; + + ochunk->index = 0; + ochunk->length = length; + ochunk->memblock = pa_memblock_new(o->core->mempool, length); + + rec = pa_memblock_acquire(chunk->memblock); + ref = pa_memblock_acquire(tchunk.memblock); + out = pa_memblock_acquire(ochunk->memblock); /* TODO: buffer can be shared rec buffer */ + + for (i=0; ithread_info.processor, + rec + (i * block_bytes), + ref + (i * block_bytes), + out + (i * block_bytes)); + + pa_memblock_release(chunk->memblock); + pa_memblock_release(tchunk.memblock); + pa_memblock_release(ochunk->memblock); + + pa_log_debug("Post-process. i(%u), rec(%llums), ref(%llums) " + "block(%llums), process(%llums) * n(%d) " + "silence(%d), index(%lld:%lld)", + o->index, + pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(tchunk.length, &o->sample_spec) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(length, &o->sample_spec) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(block_bytes, &o->sample_spec) / PA_USEC_PER_MSEC, + n, + pa_memblock_is_silence(tchunk.memblock), + pa_memblockq_get_write_index(o->thread_info.echo), + pa_memblockq_get_read_index(o->thread_info.echo)); + + pa_memblock_unref(tchunk.memblock); + pa_memblockq_drop(o->thread_info.echo, tchunk.length); + } + } else { + /* no reference case like audio_share */ + rec = pa_memblock_acquire(chunk->memblock); + + ochunk->index = 0; + ochunk->length = chunk->length; + ochunk->memblock = pa_memblock_new(o->core->mempool, chunk->length); + out = pa_memblock_acquire(ochunk->memblock); + + pa_processor_process(o->thread_info.processor, rec, NULL, out); + + pa_memblock_release(chunk->memblock); + pa_memblock_release(ochunk->memblock); + } + + return ret; +} + +/* rendering thread is separated because ec/ns takes much time in I/O thread */ +static int process_msg( + pa_msgobject *o, + int code, + void *data, + int64_t offset, + pa_memchunk *chunk) { + + struct userdata *u = PA_ECHO_CANCEL(o)->u; + + /* thread that pushes ref data should be called in render thread because of thread safe */ + switch (code) { + case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: { + /* a few pcm data will get lost. */ + if (!u->enable_in_thread) + return 0; + + pa_source_post(u->source, chunk); + + return 0; + } + case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: { + pa_memchunk ochunk; + + if (!u->enable_in_thread) + return 0; + + pa_memblockq_push(u->delayq, chunk); + + if (!pa_memblockq_peek(u->delayq, &ochunk)) { + pa_source_push_echo(u->source, &ochunk); + + pa_memblock_unref(ochunk.memblock); + pa_memblockq_drop(u->delayq, ochunk.length); + } + + return 0; + } + case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE : { + void **v = (void **)data; + pa_usec_t latency; + + u->enable_in_thread = !!(int)v[0]; + latency = *(pa_usec_t *)v[1]; + + if (u->enable_in_thread) { + if (setup_delayq_latency(u, latency)) { + pa_log_error("Failed to init delayq"); + return 0; + } + } else { + if (u->delayq) { + pa_memblockq_free(u->delayq); + u->delayq = NULL; + } + + flush_echo_memblockq(u->source); + } + + pa_log_info("EC state change (%d)", u->enable_in_thread); + + return 0; + } + case PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK: + unlink_source_output_in_thread((pa_source_output *)data); + return 0; + default: + return 0; + } +} + +static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + const char *echo = pa_proplist_gets(data->proplist, "echo"); + + pa_assert(c); + pa_assert(data); + pa_assert(u); + + if (!echo) + return PA_HOOK_OK; + + if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { + pa_log_error("Not allow multi aec instance"); + return PA_HOOK_OK; + } + + data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL; + data->flags |= PA_SOURCE_OUTPUT_DONT_MOVE; + + // TODO:add check limitation VARIOUS_RATE? + return PA_HOOK_OK; +} + +static int find_reference_sink(struct userdata *u, pa_source_output *o) { + const char *sink_name; + + pa_assert(u); + pa_assert(o); + + sink_name = pa_proplist_gets(o->proplist, "reference_sink"); + if (!sink_name) + return -1; + + u->sink = pa_namereg_get(u->core, sink_name, PA_NAMEREG_SINK); + if (!u->sink) + return -1; + + pa_log_debug("Requested AEC source(%s), sink(%s)", + u->source->name, u->sink ? u->sink->name : ""); + + return 0; +} + +static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) { + pa_usec_t sink_usec; + pa_usec_t source_usec; + pa_usec_t block_usec; + size_t blocksize; + + if (proplist_get_fragment_size(source->proplist, &blocksize)) { + pa_log_debug("Failed to get blocksize from source"); + return -1; + } + + source_usec = pa_bytes_to_usec(blocksize, &source->sample_spec); + block_usec = u->blocksize ? pa_bytes_to_usec(u->blocksize, &source->sample_spec) : source_usec; + + /* + * limitation + * sink block >= source block >= blocksize * n + */ + if (source_usec < block_usec) { + pa_log_debug("Need to check period size. source >= block * n. " + "source(%llums), block_usec(%llums)", + source_usec / PA_USEC_PER_MSEC, + block_usec / PA_USEC_PER_MSEC); + return -1; + } + + if (!u->sink) + return 0; + + if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { + pa_log_debug("Failed to get blocksize from sink"); + return -1; + } + + sink_usec = pa_bytes_to_usec(blocksize, &u->sink->sample_spec); + + if (source_usec > sink_usec || sink_usec < block_usec) { + pa_log_debug("Need to check period size. sink >= source >= block * n. " + "source(%llums) sink(%llums) block_usec(%llums)", + source_usec / PA_USEC_PER_MSEC, + sink_usec / PA_USEC_PER_MSEC, + block_usec / PA_USEC_PER_MSEC); + return -1; + } + + return 0; +} + +static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + size_t blocksize = u->blocksize; + pa_processor_algo_t backend; + + pa_assert(c); + pa_assert(o); + pa_assert(u); + pa_assert(o->source); + + if (!CHECK_FLAGS_AEC(o->flags)) + return PA_HOOK_OK; + + if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { + pa_log_error("Not allow multi aec instance"); + goto fail; + } + + u->source = o->source; + if (find_reference_sink(u, o)) { + pa_log_error("Can't find reference sink for AEC"); + goto fail; + } + + if (check_latency_validation(u, u->sink, u->source)) { + pa_log_error("Failed to check latency validation"); + goto fail; + } + + /* Use the sources fragment size if blocksize is not specified */ + if (!blocksize) { + if (proplist_get_fragment_size(u->source->proplist, &blocksize)) { + pa_log_error("Failed to get blocksize"); + goto fail; + } + } + + if (o->thread_info.resampler) + blocksize = pa_resampler_result(o->thread_info.resampler, blocksize); + + backend = pa_processor_get_algo(o); + o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec), + &o->sample_spec, + backend, PA_PROCESSOR_FLAGS_ECHO_CANCEL); + if (!o->thread_info.processor) { + pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled"); + goto fail; + } + + if (u->sink) { + if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { + pa_log_error("Failed to get blocksize"); + goto fail; + } + + if (!pa_sample_spec_equal(&u->sink->sample_spec, &o->sample_spec)) { + pa_resampler *resampler2; + + resampler2 = pa_resampler_new( + c->mempool, + &u->sink->sample_spec, &u->sink->channel_map, + &o->sample_spec, &o->channel_map, + c->lfe_crossover_freq, + c->resample_method, 0); + + if (!resampler2) { + pa_log_error("Failed to allocate resampler2 for echo-cancel"); + goto fail; + } + + o->thread_info.resampler2 = resampler2; + blocksize = pa_resampler_result(o->thread_info.resampler2, blocksize); + + pa_log_info("Use resampler2. blocksize(%d) bytes", blocksize); + } + + o->thread_info.echo = pa_memblockq_new("echo reference", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &o->sample_spec, + 0, + blocksize, + 0, + &o->source->silence); + + if (!o->thread_info.echo) { + pa_log_error("Failed to alloc memblockq"); + goto fail; + } + } + + o->post_process = post_process; + + /* connect to sink and source */ + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink); + + update_state_by_source(u, true); + + return PA_HOOK_OK; + +fail: + o->flags &= ~PA_SOURCE_OUTPUT_ECHO_CANCEL; // TODO: need to consider DONT_MOVE define + free_source_output_extra_resource(o); + + return PA_HOOK_OK; +} + +/* Call from main thread */ +static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(c); + pa_assert(o); + pa_assert(u); + + if (!CHECK_FLAGS_AEC(o->flags)) + return PA_HOOK_OK; + + update_state_by_source(u, false); + + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + + u->source = NULL; + u->sink = NULL; + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(c); + pa_assert(s); + pa_assert(u); + + if (s != u->sink) + return PA_HOOK_OK; + + if (s->state == PA_SINK_RUNNING) + update_state_by_sink(u, true); + else if (s->state == PA_SINK_SUSPENDED || s->state == PA_SINK_IDLE) + update_state_by_sink(u, false); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(u); + + if (!u->source || u->source != source) + return PA_HOOK_OK; + + pa_log_warn("echo-cancel source is unlinked during processing."); + + update_state_by_source(u, false); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_cb(pa_core *core, pa_sink *sink, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(u); + + if (!u->sink || u->sink != sink) + return PA_HOOK_OK; + + pa_log_warn("echo-cancel sink is unlinked during processing."); + + update_state_by_sink(u, false); + + return PA_HOOK_OK; +} + +static void thread_func(void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_thread_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + for (;;) { + int ret; + + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + } + +fail: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), + PA_CORE_MESSAGE_UNLOAD_MODULE, u->m, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + + pa_thread_mq_done(&u->thread_mq); + + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u = NULL; + uint32_t blocksize = 0; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log_error("Failed to parse module arguments."); + return -1; + } + pa_modargs_get_value_u32(ma, "blocksize", &blocksize); + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->m = m; + u->blocksize = blocksize; + + u->echo_cancel = pa_msgobject_new(pa_echo_cancel); + u->echo_cancel->parent.process_msg = process_msg; + u->echo_cancel->u = u; + + u->rtpoll = pa_rtpoll_new(); + u->asyncmsgq_source = pa_asyncmsgq_new(0); + u->asyncmsgq_sink = pa_asyncmsgq_new(0); + + pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY, u->asyncmsgq_sink); + pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY, u->asyncmsgq_source); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (!(u->thread = pa_thread_new("tizenaudio-echo-cancel", thread_func, u))) { + pa_log_error("Failed to create thread."); + goto fail; + } + + u->sink_unlink_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_UNLINK], + PA_HOOK_NORMAL, (pa_hook_cb_t) sink_unlink_cb, u); + u->source_unlink_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], + PA_HOOK_NORMAL, (pa_hook_cb_t) source_unlink_cb, u); + + u->source_output_put_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], + PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, u); + + u->source_output_unlink_post_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], + PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u); + + u->source_output_new_slot = + pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], + PA_HOOK_LATE, (pa_hook_cb_t) source_output_new_cb, u); + + u->sink_state_changed_slot = + pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u); + + /* TODO : need to check sink configuration change */ + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->source_output_put_slot) + pa_hook_slot_free(u->source_output_put_slot); + + if (u->source_output_unlink_slot) + pa_hook_slot_free(u->source_output_unlink_slot); + + if (u->source_output_unlink_post_slot) + pa_hook_slot_free(u->source_output_unlink_post_slot); + + if (u->source_output_new_slot) + pa_hook_slot_free(u->source_output_new_slot); + + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->asyncmsgq_sink) { + if (u->sink) { + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL); + + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + } + + pa_asyncmsgq_unref(u->asyncmsgq_sink); + } + + if (u->asyncmsgq_source) { + if (u->source) { + pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), + PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL); + + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); + free_source_output_extra_resource_by_source(u->source); + } + + pa_asyncmsgq_unref(u->asyncmsgq_source); + } + + if (u->delayq) + pa_memblockq_free(u->delayq); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_thread_mq_done(&u->thread_mq); + + pa_xfree(u); +} + diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c new file mode 100644 index 0000000..e941c0e --- /dev/null +++ b/src/echo-cancel/processor.c @@ -0,0 +1,186 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include +#include +#include + +#include "processor.h" + +#ifdef __DEBUG__ +#include +#include +#include +#include +#include +#endif + +struct pa_processor_algo { + void *(*create)(size_t nframes, pa_sample_spec *ss); + int32_t (*process)(void *priv, int8_t *rec, int8_t *ref, int8_t *out); + int32_t (*destroy)(void *priv); +}; + +extern void *adrian_create(size_t nframes, pa_sample_spec *ss); +extern int32_t adrian_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +extern int32_t adrian_destroy(void *priv); + +extern void *speex_create(size_t nframes, pa_sample_spec *ss); +extern int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +extern int32_t speex_destroy(void *priv); + +pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags) { + pa_processor *processor = NULL; + + pa_assert(ss); + + if (ss->format != PA_SAMPLE_S16LE) { + pa_log_error("Not supported format(%d)", ss->format); + return NULL; + } + + processor = pa_xnew0(pa_processor, 1); + processor->intf = pa_xnew0(pa_processor_algo, 1); + processor->nframes = nframes; + processor->framesize = pa_frame_size(ss); + + switch (backend) { + case PA_PROCESSOR_SPEEX: + processor->intf->create = speex_create; + processor->intf->process = speex_process; + processor->intf->destroy = speex_destroy; + break; + case PA_PROCESSOR_ADRIAN: + processor->intf->create = adrian_create; + processor->intf->process = adrian_process; + processor->intf->destroy = adrian_destroy; + break; + default: + pa_log_error("Invalid backend(%d)", backend); + goto fail; + } + + pa_log_info("Use backend(%d) nframes(%zu) framesize(%d)", + backend, processor->nframes, processor->framesize); + + if (!(processor->priv = processor->intf->create(nframes, ss))) { + pa_log_error("Failed to create processor"); + goto fail; + } + +#ifdef __DEBUG__ + { + static int n = 1; + char rec[32], ref[32], out[32]; + + snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n); + snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n); + snprintf(out, sizeof(out), "/tmp/out-%d.raw", n); + n += 1; + + unlink(rec); + unlink(ref); + unlink(out); + + processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777); + } +#endif + + return processor; + +fail: + pa_xfree(processor->intf); + pa_xfree(processor); + + return NULL; +} + +int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) { + int ret = -1; + + pa_assert(processor); + pa_assert(processor->intf); + pa_assert(rec); + pa_assert(out); + +#ifdef __DEBUG__ + if (write(processor->fdrec, rec, processor->nframes * processor->framesize) <= 0) + pa_log_error("Failed to write rec buffer"); + + if (write(processor->fdref, ref, processor->nframes * processor->framesize) <= 0) + pa_log_error("Failed to write ref buffer"); + + gettimeofday(&processor->before, NULL); +#endif + + if (processor->intf->process) + ret = processor->intf->process(processor->priv, rec, ref, out); + +#ifdef __DEBUG__ + if (write(processor->fdout, out, processor->nframes * processor->framesize) <= 0) + pa_log_error("Failed to write out buffer"); + + gettimeofday(&processor->after, NULL); + + pa_log_debug("It takes time(%ld) bytes(%d)", + 1000 * (after.tv_sec-before.tv_sec) + (after.tv_usec-before.tv_usec) / 1000, + processor->buffer_size); +#endif + + return ret; +} + +int pa_processor_free(pa_processor *processor) { + pa_assert(processor); + pa_assert(processor->intf); + pa_assert(processor->intf->destroy); + +#ifdef __DEBUG__ + if (processor->fdrec) + close(processor->fdrec); + if (processor->fdref) + close(processor->fdref); + if (processor->fdout) + close(processor->fdout); +#endif + + if (processor->intf->destroy(processor->priv)) { + pa_log_error("Failed to destroy processor"); + return -1; + } + + pa_xfree(processor->intf); + pa_xfree(processor); + + return 0; +} + +size_t pa_processor_get_blocksize(pa_processor *processor) { + pa_assert(processor); + + return processor->nframes; +} diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h new file mode 100644 index 0000000..98482ab --- /dev/null +++ b/src/echo-cancel/processor.h @@ -0,0 +1,64 @@ +#ifndef foopulseprocessorfoo +#define foopulseprocessorfoo + +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include + +//#define __DEBUG__ + +typedef enum { + PA_PROCESSOR_FLAGS_ECHO_CANCEL, + PA_PROCESSOR_FLAGS_NOISE_SUPPRESSION, +} pa_process_flags_t; + +typedef enum { + PA_PROCESSOR_SPEEX, + PA_PROCESSOR_ADRIAN, +} pa_processor_algo_t; + +typedef struct pa_processor_algo pa_processor_algo; +typedef struct pa_processor pa_processor; + +struct pa_processor { + pa_processor_algo *intf; + void *priv; + void *userdata; + size_t nframes; + size_t framesize; + +#ifdef __DEBUG__ + int fdrec, fdref, fdout; + struct timeval before, after; +#endif +}; + +pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags); +int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out); +int pa_processor_free(pa_processor *processor); +size_t pa_processor_get_blocksize(pa_processor *processor); + +#endif diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index a5a5871..a81ac70 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -46,6 +46,7 @@ #include #include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Sink2"); @@ -65,6 +66,8 @@ PA_MODULE_USAGE( #define DEFAULT_SINK_NAME "tizenaudio-sink2" #define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 struct userdata { pa_core *core; @@ -83,11 +86,16 @@ struct userdata { char* card; char* device; bool first; + bool echo_on; pa_rtpoll_item *rtpoll_item; uint64_t write_count; pa_hal_interface *hal_interface; + + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; }; static const char* const valid_modargs[] = { @@ -254,6 +262,11 @@ static int sink_process_msg( struct userdata *u = PA_SINK(o)->userdata; switch (code) { + case PA_SINK_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } case PA_SINK_MESSAGE_GET_LATENCY: { int64_t r = 0; @@ -264,6 +277,26 @@ static int sink_process_msg( return 0; } + case PA_SINK_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -319,10 +352,9 @@ static int process_render(struct userdata *u) { pa_assert(u); pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); - if (frames_to_write > avail) { - pa_log_debug("not enough avail size. frames_to_write(%zu), avail(%d)", frames_to_write, avail); + + if (frames_to_write > avail) return 0; - } pa_sink_render_full(u->sink, u->frag_size, &chunk); p = pa_memblock_acquire(chunk.memblock); @@ -333,6 +365,12 @@ static int process_render(struct userdata *u) { } pa_memblock_release(chunk.memblock); + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL); + } + pa_memblock_unref(chunk.memblock); u->write_count += chunk.length; @@ -389,16 +427,12 @@ static void thread_func(void *userdata) { pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); u->first = true; revents = 0; - } else { - //pa_log_debug("Poll wakeup.", revents); } } } } fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); @@ -496,8 +530,8 @@ int pa__init(pa_module*m) { u->card = pa_xstrdup(card); u->device = pa_xstrdup(device); - u->frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); - u->nfrags = m->core->default_n_fragments; + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { pa_log_error("fragment_size or fragments are invalid."); @@ -515,6 +549,7 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); pa_proplist_sets(data.proplist, "tizen.card", u->card); pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); frame_size = pa_frame_size(&ss); @@ -560,7 +595,7 @@ int pa__init(pa_module*m) { pa_sink_set_max_request(u->sink, buffer_size); pa_sink_set_max_rewind(u->sink, buffer_size); - if (!(u->thread = pa_thread_new("tizenaudio-sink", thread_func, u))) { + if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) { pa_log_error("Failed to create thread."); goto fail; } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 3df3aef..0fc047c 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -46,9 +46,10 @@ #include #include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" PA_MODULE_AUTHOR("Tizen"); -PA_MODULE_DESCRIPTION("Tizen Audio Source"); +PA_MODULE_DESCRIPTION("Tizen Audio Source2"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); PA_MODULE_USAGE( @@ -62,10 +63,11 @@ PA_MODULE_USAGE( "fragments= " "fragment_size= "); - #define DEFAULT_SOURCE_NAME "tizenaudio-source2" #define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 struct userdata { pa_core *core; @@ -85,12 +87,17 @@ struct userdata { char* card; char* device; bool first; + bool echo_on; pa_rtpoll_item *rtpoll_item; uint64_t read_count; pa_usec_t latency_time; pa_hal_interface *hal_interface; + + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; }; static const char* const valid_modargs[] = { @@ -257,6 +264,11 @@ static int source_process_msg( struct userdata *u = PA_SOURCE(o)->userdata; switch (code) { + case PA_SOURCE_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } case PA_SOURCE_MESSAGE_GET_LATENCY: { uint64_t r = 0; @@ -267,6 +279,39 @@ static int source_process_msg( return 0; } + case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { + pa_source_output *o = (pa_source_output *)data; + + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) + break; + + if (u->ec_asyncmsgq) { + pa_asyncmsgq_send(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK, o, 0, NULL); + } + + return 0; + } + case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } } return pa_source_process_msg(o, code, data, offset, chunk); @@ -301,7 +346,14 @@ static int process_render(struct userdata *u) { chunk.index = 0; chunk.length = (size_t)frames_to_read * frame_size; - pa_source_post(u->source, &chunk); + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL); + } else { + pa_source_post(u->source, &chunk); + } + pa_memblock_unref(chunk.memblock); u->read_count += chunk.length; @@ -355,16 +407,12 @@ static void thread_func(void *userdata) { pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); u->first = true; revents = 0; - } else { - //pa_log_debug("Poll wakeup.", revents); } } } } fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); @@ -461,8 +509,8 @@ int pa__init(pa_module*m) { u->card = pa_xstrdup(card); u->device = pa_xstrdup(device); - u->frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); - u->nfrags = m->core->default_n_fragments; + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { pa_log_error("fragment_size or fragments are invalid."); @@ -480,6 +528,7 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); pa_proplist_sets(data.proplist, "tizen.card", u->card); pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); frame_size = pa_frame_size(&ss); @@ -524,7 +573,7 @@ int pa__init(pa_module*m) { u->timestamp = 0ULL; - if (!(u->thread = pa_thread_new("tizenaudio-source", thread_func, u))) { + if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) { pa_log_error("Failed to create thread."); goto fail; } -- 2.7.4 From c553d33485641eea10b33160be6ec7071492d3b2 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 14 Mar 2022 15:29:30 +0900 Subject: [PATCH 05/16] Support webrtc-audio-processsing echo-cancellation [Version] 15.0.5 [Issue Type] New Feature Change-Id: Ib3227c185779353747af7f930de13535988b467c Signed-off-by: Jaechul Lee --- Makefile.am | 6 + configure.ac | 19 ++ packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/algo_webrtc.cpp | 286 ++++++++++++++++++++++++ src/echo-cancel/module-tizenaudio-echo-cancel.c | 4 + src/echo-cancel/processor.c | 9 + src/echo-cancel/processor.h | 1 + 7 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 src/echo-cancel/algo_webrtc.cpp diff --git a/Makefile.am b/Makefile.am index 768114b..c41e653 100644 --- a/Makefile.am +++ b/Makefile.am @@ -99,6 +99,12 @@ libprocessor_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version libprocessor_la_LIBADD = $(AM_LIBADD) $(LIBSPEEX_LIBS) libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS) +if ENABLE_WEBRTC +libprocessor_la_SOURCES += src/echo-cancel/algo_webrtc.cpp +libprocessor_la_LIBADD += $(WEBRTC_LIBS) +libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -std=c++17 +endif + module_tizenaudio_echo_cancel_la_SOURCES = src/echo-cancel/module-tizenaudio-echo-cancel.c src/echo-cancel/echo-cancel-def.h module_tizenaudio_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_echo_cancel_la_LIBADD = $(MODULE_LIBADD) libprocessor.la diff --git a/configure.ac b/configure.ac index de19973..170b340 100644 --- a/configure.ac +++ b/configure.ac @@ -409,6 +409,25 @@ AC_ARG_ENABLE(aec, AC_HELP_STRING([--enable-aec], [using aec]), AM_CONDITIONAL(ENABLE_AEC, test "x$ENABLE_AEC" = "xyes") dnl end -------------------------------------------------------------------- +dnl use webrtc ---------------------------------------------------------------- +AC_ARG_ENABLE(webrtc, AC_HELP_STRING([--enable-webrtc], [using webrtc-audio-processing]), +[ + case "${enableval}" in + yes) ENABLE_WEBRTC=yes ;; + no) ENABLE_WEBRTC=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-webrtc) ;; + esac + ],[USE_WEBRTC=no]) + +if test "x$ENABLE_WEBRTC" = "xyes"; then +PKG_CHECK_MODULES(WEBRTC, webrtc-audio-processing) +AC_SUBST(WEBRTC_CFLAGS) +AC_SUBST(WEBRTC_LIBS) +fi + +AM_CONDITIONAL(ENABLE_WEBRTC, test "x$ENABLE_WEBRTC" = "xyes") +dnl end -------------------------------------------------------------------- + #### D-Bus support (optional) #### AC_ARG_ENABLE([dbus], diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 1a86b8f..11f3e2f 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.4 +Version: 15.0.5 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/algo_webrtc.cpp b/src/echo-cancel/algo_webrtc.cpp new file mode 100644 index 0000000..8253820 --- /dev/null +++ b/src/echo-cancel/algo_webrtc.cpp @@ -0,0 +1,286 @@ +/*** + This file is part of PulseAudio. + + Copyright 2021 Jaechul Lee + + 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 +#endif + +#include + +#include + +#define DEFAULT_PROCESS_SIZE_MS 10 + +using namespace webrtc; + +PA_C_DECL_BEGIN +#include +#include +#include +#include +#include +#include +#include +#include + +static void allocate_stream_buffer(struct algo_webrtc *webrtc, size_t nframes); +static void deallocate_stream_buffer(struct algo_webrtc *webrtc); +static void convert_s16_to_float(float *dst, int16_t *src, size_t n); +static void convert_float_to_s16(int16_t *dst, float *src, size_t n); + +void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss); +int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +int32_t webrtc_audio_destroy(void *priv); +PA_C_DECL_END + +struct algo_webrtc { + AudioProcessing *ap; + Config config; + StreamConfig *sconfig; + + float *rec_fbuf; + float *rec_dbuf[PA_CHANNELS_MAX]; + + float *ref_fbuf; + float *ref_dbuf[PA_CHANNELS_MAX]; + + float *out_fbuf; + float *out_dbuf[PA_CHANNELS_MAX]; + + pa_sample_spec ss; + size_t frames; + + /* Currently, webrtc uses fixed size(10ms) buffer */ + int loops; + size_t fixed_bytes; + size_t fixed_frames; +}; + +void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss) { + struct algo_webrtc *webrtc = NULL; + size_t fixed_bytes, request_bytes; + Config config; + + pa_assert(ss); + + if (ss->channels > 2 || ss->format != PA_SAMPLE_S16LE) { + pa_log_error("Invalid channels (%d) or format (%d)", ss->channels, ss->format); + return NULL; + } + + fixed_bytes = pa_usec_to_bytes(DEFAULT_PROCESS_SIZE_MS * PA_USEC_PER_MSEC, ss); + request_bytes = nframes * pa_frame_size(ss); + + if (fixed_bytes > request_bytes) { + pa_log_error("nframes should be bigger than %dms. nframes(%zu) request_bytes(%zu)", + DEFAULT_PROCESS_SIZE_MS, nframes, request_bytes); + return NULL; + } + + if (request_bytes % fixed_bytes) { + pa_log_error("request_bytes(%zu) should be multiples of fixed_bytes(%zu)", + nframes, request_bytes); + return NULL; + } + + webrtc = pa_xnew0(struct algo_webrtc, 1); + webrtc->ss = *ss; + webrtc->fixed_bytes = fixed_bytes; + webrtc->fixed_frames = fixed_bytes / pa_frame_size(ss); + webrtc->loops = request_bytes / fixed_bytes; + + config.Set(new ExperimentalNs(false)); + config.Set(new Intelligibility(false)); + config.Set(new ExperimentalAgc(false)); + + webrtc->ap = AudioProcessing::Create(config); + if (!webrtc->ap) { + pa_log_error("Failed to create audio processing"); + goto fail; + } + + webrtc->ap->echo_cancellation()->Enable(true); + webrtc->ap->noise_suppression()->Enable(true); + webrtc->ap->noise_suppression()->set_level(static_cast(1)); + + webrtc->ap->gain_control()->set_mode(GainControl::kAdaptiveDigital); + //webrtc->ap->gain_control()->set_target_level_dbfs(30); + //webrtc->ap->gain_control()->set_stream_analog_level(30); + webrtc->ap->echo_cancellation()->set_suppression_level(static_cast(1)); + + webrtc->sconfig = new StreamConfig(ss->rate, ss->channels, false); + if (!webrtc->sconfig) { + pa_log_error("Failed to create stream config"); + goto fail; + } + + webrtc->sconfig->set_sample_rate_hz(ss->rate); + webrtc->sconfig->set_num_channels(ss->channels); + + /* webrtc supports 10ms by default */ + webrtc->frames = webrtc->sconfig->num_frames(); + if (webrtc->frames != webrtc->fixed_frames) { + pa_log_error("Failed to set frames. frames(%zu), fixed_frames(%zu)", + webrtc->frames, webrtc->fixed_frames); + goto fail; + } + + allocate_stream_buffer(webrtc, webrtc->fixed_frames); + + pa_log_info("webrtc processes request block(%llumsec) block(%llumsec) n(%d)\n", + pa_bytes_to_usec(request_bytes, ss) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(fixed_bytes, ss) / PA_USEC_PER_MSEC, + webrtc->loops); + + return webrtc; + +fail: + if (webrtc->ap) + delete webrtc->ap; + if (webrtc->sconfig) + delete webrtc->sconfig; + + pa_xfree(webrtc); + + return NULL; +} + +int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) { + struct algo_webrtc *webrtc = (struct algo_webrtc *)priv; + pa_sample_spec ss; + size_t frames; + + pa_assert(webrtc); + pa_assert(rec); + pa_assert(ref); + pa_assert(out); + + ss.format = PA_SAMPLE_FLOAT32LE; + ss.rate = webrtc->ss.rate; + ss.channels = webrtc->ss.channels; + + frames = webrtc->fixed_frames; + + for (int i = 0; i < webrtc->loops; i++) { + int ret; + + convert_s16_to_float(webrtc->ref_fbuf, (int16_t *)ref, frames * ss.channels); + pa_deinterleave(webrtc->ref_fbuf, (void **)webrtc->ref_dbuf, ss.channels, + pa_sample_size(&ss), frames); + + /* reference */ + ret = webrtc->ap->ProcessReverseStream(webrtc->ref_dbuf, + *webrtc->sconfig, + *webrtc->sconfig, + webrtc->ref_dbuf); + if (ret != AudioProcessing::kNoError) { + pa_log_error("Failed to process reverse stream"); + return -1; + } + + webrtc->ap->set_stream_delay_ms(0); + + /* capture */ + convert_s16_to_float(webrtc->rec_fbuf, (int16_t *)rec, frames * ss.channels); + pa_deinterleave(webrtc->rec_fbuf, (void **)webrtc->rec_dbuf, ss.channels, + pa_sample_size(&ss), frames); + + ret = webrtc->ap->ProcessStream(webrtc->rec_dbuf, + *webrtc->sconfig, + *webrtc->sconfig, + webrtc->out_dbuf); + if (ret != AudioProcessing::kNoError) { + pa_log_error("Failed to process stream"); + return -1; + } + + pa_interleave((const void **)webrtc->out_dbuf, ss.channels, webrtc->out_fbuf, + pa_sample_size(&ss), frames); + convert_float_to_s16((int16_t *)out, webrtc->out_fbuf, frames * ss.channels); + + rec += webrtc->fixed_bytes; + ref += webrtc->fixed_bytes; + out += webrtc->fixed_bytes; + } + + return 0; +} + +int32_t webrtc_audio_destroy(void *priv) { + struct algo_webrtc *webrtc = (struct algo_webrtc *)priv; + + pa_assert(webrtc); + + delete webrtc->sconfig; + delete webrtc->ap; + + deallocate_stream_buffer(webrtc); + pa_xfree(webrtc); + + return 0; +} + +static void deallocate_stream_buffer(struct algo_webrtc *webrtc) { + pa_assert(webrtc); + + for (int i = 0; i < webrtc->ss.channels; i++) { + pa_xfree(webrtc->rec_dbuf[i]); + pa_xfree(webrtc->ref_dbuf[i]); + pa_xfree(webrtc->out_dbuf[i]); + } + + pa_xfree(webrtc->rec_fbuf); + pa_xfree(webrtc->ref_fbuf); + pa_xfree(webrtc->out_fbuf); +} + +static void allocate_stream_buffer(struct algo_webrtc *webrtc, size_t nframes) { + int channels; + + pa_assert(webrtc); + + channels = webrtc->ss.channels; + + webrtc->rec_fbuf = pa_xnew(float, nframes * channels); + webrtc->ref_fbuf = pa_xnew(float, nframes * channels); + webrtc->out_fbuf = pa_xnew(float, nframes * channels); + + for (int i = 0; i < channels; i++) { + webrtc->rec_dbuf[i] = pa_xnew(float, nframes); + webrtc->ref_dbuf[i] = pa_xnew(float, nframes); + webrtc->out_dbuf[i] = pa_xnew(float, nframes); + } +} + +static void convert_s16_to_float(float *dst, int16_t *src, size_t n) { + pa_assert(dst); + pa_assert(src); + + ((pa_convert_func_t)pa_get_convert_to_float32ne_function(PA_SAMPLE_S16LE))(n, src, dst); +} + +static void convert_float_to_s16(int16_t *dst, float *src, size_t n) { + pa_assert(dst); + pa_assert(src); + + ((pa_convert_func_t)pa_get_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE))(n, src, dst); +} + diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 33482eb..a80eb9f 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -125,6 +125,10 @@ static pa_processor_algo_t pa_processor_get_algo(pa_source_output *o) { return PA_PROCESSOR_ADRIAN; else if (pa_streq(algo, "speex")) return PA_PROCESSOR_SPEEX; + else if (pa_streq(algo, "webrtc")) + return PA_PROCESSOR_WEBRTC; + else if (pa_streq(algo, "auto")) + return PA_PROCESSOR_WEBRTC; else { pa_log_warn("invalid algo(%s), Use default processor(speex)", algo); return PA_PROCESSOR_SPEEX; diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index e941c0e..3681347 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -51,6 +51,10 @@ extern void *speex_create(size_t nframes, pa_sample_spec *ss); extern int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); extern int32_t speex_destroy(void *priv); +extern void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss); +extern int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +extern int32_t webrtc_audio_destroy(void *priv); + pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags) { pa_processor *processor = NULL; @@ -77,6 +81,11 @@ pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_ processor->intf->process = adrian_process; processor->intf->destroy = adrian_destroy; break; + case PA_PROCESSOR_WEBRTC: + processor->intf->create = webrtc_audio_create; + processor->intf->process = webrtc_audio_process; + processor->intf->destroy = webrtc_audio_destroy; + break; default: pa_log_error("Invalid backend(%d)", backend); goto fail; diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h index 98482ab..fa642ce 100644 --- a/src/echo-cancel/processor.h +++ b/src/echo-cancel/processor.h @@ -38,6 +38,7 @@ typedef enum { typedef enum { PA_PROCESSOR_SPEEX, PA_PROCESSOR_ADRIAN, + PA_PROCESSOR_WEBRTC, } pa_processor_algo_t; typedef struct pa_processor_algo pa_processor_algo; -- 2.7.4 From e6a83316b8fd44bebd82fd9da0c7164c80c0cda0 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 11:33:13 +0900 Subject: [PATCH 06/16] Fix coverity issues The issue of coverity(1286103) was fixed [Version] 15.0.6 [Issue Type] Coverity Change-Id: I3e4b5ca2e925642cc4574136b07a3c736b9a2e69 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 11f3e2f..7972173 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.5 +Version: 15.0.6 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index a80eb9f..51216b2 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -869,7 +869,11 @@ int pa__init(pa_module *m) { pa_log_error("Failed to parse module arguments."); return -1; } - pa_modargs_get_value_u32(ma, "blocksize", &blocksize); + + if (pa_modargs_get_value_u32(ma, "blocksize", &blocksize) < 0) { + pa_log_info("Failed to get blocksize"); + goto fail; + } m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; -- 2.7.4 From cfaa0eda8c44f26728045d9c685f762b38510868 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 24 Mar 2022 16:57:45 +0900 Subject: [PATCH 07/16] Add default_method to module parameters Use default method that is configured at init function if source-output's 'echo_cancel' property is set to 'default' [Version] 15.0.7 [Issue Type] Improvement Change-Id: I137282f954cd01b1eb5e08aedd3f2a5a075e74d6 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 61 +++++++++++++++---------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 7972173..a350f73 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.6 +Version: 15.0.7 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 51216b2..f6571d1 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -46,6 +46,7 @@ PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( + "method= " "blocksize= "); typedef struct echo_cancel pa_echo_cancel; @@ -67,6 +68,7 @@ struct userdata { bool enable; uint32_t n_source_output; size_t blocksize; + char *default_method; pa_thread *thread; pa_thread_mq thread_mq; @@ -92,8 +94,10 @@ PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject); #define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) #define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL) #define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output) +#define DEFAULT_AEC_METHOD "speex" static const char* const valid_modargs[] = { + "method", "blocksize", NULL, }; @@ -113,26 +117,28 @@ static int proplist_get_fragment_size(pa_proplist *p, size_t *size) { return 0; } -static pa_processor_algo_t pa_processor_get_algo(pa_source_output *o) { - const char *algo = pa_proplist_gets(o->proplist, "echo"); +static int pa_processor_get_method(pa_source_output *o, const char *default_method, pa_processor_algo_t *method) { + const char *selected, *requested; - if (!algo) { - pa_log_warn("Use default processor(speex)"); - return PA_PROCESSOR_SPEEX; - } + pa_assert(method); + pa_assert(default_method); - if (pa_streq(algo, "adrian")) - return PA_PROCESSOR_ADRIAN; - else if (pa_streq(algo, "speex")) - return PA_PROCESSOR_SPEEX; - else if (pa_streq(algo, "webrtc")) - return PA_PROCESSOR_WEBRTC; - else if (pa_streq(algo, "auto")) - return PA_PROCESSOR_WEBRTC; - else { - pa_log_warn("invalid algo(%s), Use default processor(speex)", algo); - return PA_PROCESSOR_SPEEX; - } + requested = pa_proplist_gets(o->proplist, "echo_cancel"); + if (!requested) + return -1; + + selected = pa_streq(requested, "default") ? default_method : requested; + + if (pa_streq(selected, "webrtc")) + *method = PA_PROCESSOR_WEBRTC; + else if (pa_streq(selected, "speex")) + *method = PA_PROCESSOR_SPEEX; + else if (pa_streq(selected, "adrian")) + *method = PA_PROCESSOR_ADRIAN; + else + *method = PA_PROCESSOR_SPEEX; + + return 0; } static pa_source_output *find_source_output_by_flags(pa_source *s) { @@ -561,13 +567,14 @@ static int process_msg( static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *echo = pa_proplist_gets(data->proplist, "echo"); + const char *echo_cancel; pa_assert(c); - pa_assert(data); pa_assert(u); + pa_assert(data); - if (!echo) + echo_cancel = pa_proplist_gets(data->proplist, "echo_cancel"); + if (!echo_cancel) return PA_HOOK_OK; if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { @@ -653,7 +660,7 @@ static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; size_t blocksize = u->blocksize; - pa_processor_algo_t backend; + pa_processor_algo_t method; pa_assert(c); pa_assert(o); @@ -690,10 +697,14 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo if (o->thread_info.resampler) blocksize = pa_resampler_result(o->thread_info.resampler, blocksize); - backend = pa_processor_get_algo(o); + if (pa_processor_get_method(o, u->default_method, &method)) { + pa_log_error("Can't find method"); + goto fail; + } + o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec), &o->sample_spec, - backend, PA_PROCESSOR_FLAGS_ECHO_CANCEL); + method, PA_PROCESSOR_FLAGS_ECHO_CANCEL); if (!o->thread_info.processor) { pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled"); goto fail; @@ -879,6 +890,7 @@ int pa__init(pa_module *m) { u->core = m->core; u->m = m; u->blocksize = blocksize; + u->default_method = pa_xstrdup(pa_modargs_get_value(ma, "method", DEFAULT_AEC_METHOD)); u->echo_cancel = pa_msgobject_new(pa_echo_cancel); u->echo_cancel->parent.process_msg = process_msg; @@ -988,6 +1000,7 @@ void pa__done(pa_module *m) { pa_thread_mq_done(&u->thread_mq); + pa_xfree(u->default_method); pa_xfree(u); } -- 2.7.4 From 9a4141161da544772bc31c1226f96ce55d026bae Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 14:00:35 +0900 Subject: [PATCH 08/16] tones: Fix coverity issues The issue of coverity(1286168) was fixed [Version] 15.0.8 [Issue Type] Coverity Change-Id: I5eaaa3e59050963ad207634bae5a68631b9b286e Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/tones.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index a350f73..28cd7d8 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.7 +Version: 15.0.8 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/tones.h b/src/tones.h index 9616d14..10dd13f 100644 --- a/src/tones.h +++ b/src/tones.h @@ -23,9 +23,9 @@ #define footoneplayerfoo #include -#define TONE_ELEMENTS_ROW 14 +#define TONE_ELEMENTS_ROW 14 #define TONE_ELEMENTS_COLUMN 6 -#define TONE_ELEMENTS TONE_ELEMENTS_ROW * TONE_ELEMENTS_COLUMN +#define TONE_ELEMENTS (TONE_ELEMENTS_ROW * TONE_ELEMENTS_COLUMN) /* need to improve: 18KiB approximately */ static const uint16_t tones[][TONE_ELEMENTS] = { -- 2.7.4 From a9c80a3b213030772331d5373c367e15d4d54e71 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 11:20:50 +0900 Subject: [PATCH 09/16] tizenaudio-echo-cancel: Find reference sink by proplist [Version] 15.0.9 [Issue Type] New Feature Change-Id: I28c7c2cb5a569f7f1159ee1a6aa2a4ede54ed0cc Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 43 ++++++++++++++----------- src/stream-manager-priv.h | 1 + src/stream-manager.c | 40 +++++++++++++++++++++++ 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 28cd7d8..38eda7d 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.8 +Version: 15.0.9 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index f6571d1..2f95314 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -123,7 +123,7 @@ static int pa_processor_get_method(pa_source_output *o, const char *default_meth pa_assert(method); pa_assert(default_method); - requested = pa_proplist_gets(o->proplist, "echo_cancel"); + requested = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); if (!requested) return -1; @@ -567,14 +567,14 @@ static int process_msg( static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *echo_cancel; + const char *method; pa_assert(c); pa_assert(u); pa_assert(data); - echo_cancel = pa_proplist_gets(data->proplist, "echo_cancel"); - if (!echo_cancel) + method = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); + if (!method) return PA_HOOK_OK; if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { @@ -589,24 +589,28 @@ static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_da return PA_HOOK_OK; } -static int find_reference_sink(struct userdata *u, pa_source_output *o) { - const char *sink_name; +static pa_sink *find_reference_sink_by_proplist(pa_core *c, pa_proplist *p) { + const char *ref_idx; + int32_t idx; + pa_sink *s; - pa_assert(u); - pa_assert(o); + pa_assert(c); + pa_assert(p); - sink_name = pa_proplist_gets(o->proplist, "reference_sink"); - if (!sink_name) - return -1; + ref_idx = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK); + if (!ref_idx) + return NULL; - u->sink = pa_namereg_get(u->core, sink_name, PA_NAMEREG_SINK); - if (!u->sink) - return -1; + if (pa_atoi(ref_idx, &idx) < 0) + return NULL; - pa_log_debug("Requested AEC source(%s), sink(%s)", - u->source->name, u->sink ? u->sink->name : ""); + s = pa_idxset_get_by_index(c->sinks, idx); + if (!s) + return NULL; - return 0; + pa_log_info("Found reference sink(%d, %s)", s->index, s->name); + + return s; } static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) { @@ -676,7 +680,8 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo } u->source = o->source; - if (find_reference_sink(u, o)) { + u->sink = find_reference_sink_by_proplist(c, o->proplist); + if (!u->sink) { pa_log_error("Can't find reference sink for AEC"); goto fail; } @@ -924,6 +929,8 @@ int pa__init(pa_module *m) { pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u); + /* source_output_new_cb must be called after new_cb callback in stream manager. + * because stream-manager converts the device_id to the index of the sink */ u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_LATE, (pa_hook_cb_t) source_output_new_cb, u); diff --git a/src/stream-manager-priv.h b/src/stream-manager-priv.h index e3d9298..b7c019f 100644 --- a/src/stream-manager-priv.h +++ b/src/stream-manager-priv.h @@ -76,6 +76,7 @@ typedef enum _process_command_type { PROCESS_COMMAND_REMOVE_STREAM, PROCESS_COMMAND_UPDATE_BUFFER_ATTR, PROCESS_COMMAND_APPLY_FILTER, + PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE, } process_command_type_t; typedef enum _notify_command_type { diff --git a/src/stream-manager.c b/src/stream-manager.c index e50e13b..36351ed 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -93,6 +93,7 @@ static const char* process_command_type_str[] = { [PROCESS_COMMAND_REMOVE_STREAM] = "REMOVE_STREAM", [PROCESS_COMMAND_UPDATE_BUFFER_ATTR] = "UPDATE_BUFFER_ATTR", [PROCESS_COMMAND_APPLY_FILTER] = "APPLY_FILTER", + [PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE] = "SET_AEC_REFERENCE", }; static const char* notify_command_type_str[] = { @@ -2487,6 +2488,44 @@ process_stream_result_t process_stream(pa_stream_manager *m, void *stream, strea } break; } + case PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE: { + int32_t id; + pa_tz_device *device; + const char *ref_device; + pa_proplist *p = ((pa_source_output_new_data*)stream)->proplist; + pa_sink *s; + + result = PROCESS_STREAM_RESULT_SKIP; + + ref_device = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_DEVICE); + if (!ref_device) + break; + + result = PROCESS_STREAM_RESULT_STOP; + + if (pa_atoi(ref_device, &id) < 0) { + pa_log_error("Can't convert ref_device(%s) to integer", ref_device); + break; + } + + device = pa_device_manager_get_device_by_id(m->dm, id); + if (!device) { + pa_log_error("Can't find device by id(%d)", id); + break; + } + + s = pa_tz_device_get_sink(device, "normal"); + if (!s) { + pa_log_error("Can't find sink by device"); + break; + } + + pa_proplist_setf(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK, "%d", s->index); + + result = PROCESS_STREAM_RESULT_OK; + + break; + } } finish: @@ -2733,6 +2772,7 @@ static pa_hook_result_t source_output_new_cb(pa_core *core, pa_source_output_new process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_BUFFER_ATTR, true); process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_VOLUME, true); process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED, true); + process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE, true); return PA_HOOK_OK; } -- 2.7.4 From edbaef30fbd6be4f8dcc1d67c937e138ac1da07b Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 29 Mar 2022 17:06:43 +0900 Subject: [PATCH 10/16] tizenaudio-echo-cancel: Support reference raw copy functionality processing echo-cancellation in module-tizenaudio-echo-cancel moved to processor modules for supporting reference copy. * created reference copy * removed delayq [Version] 15.0.10 [Issue Type] New Feature Change-Id: Id4529e7f6225b1c3f1d6e499bc915aaef1a940c5 Signed-off-by: Jaechul Lee --- Makefile.am | 3 +- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/algo_reference_copy.c | 106 ++++ src/echo-cancel/module-tizenaudio-echo-cancel.c | 623 +++++++----------------- src/echo-cancel/processor.c | 545 +++++++++++++++++---- src/echo-cancel/processor.h | 56 +-- src/module-tizenaudio-source2.c | 13 - 7 files changed, 765 insertions(+), 583 deletions(-) create mode 100644 src/echo-cancel/algo_reference_copy.c diff --git a/Makefile.am b/Makefile.am index c41e653..e61e057 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,6 +90,7 @@ module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_t libprocessor_la_SOURCES = \ src/echo-cancel/algo_speex.c \ + src/echo-cancel/algo_reference_copy.c \ src/echo-cancel/algo_adrian.c \ src/echo-cancel/adrian-aec.c \ src/echo-cancel/processor.c \ @@ -102,7 +103,7 @@ libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS) if ENABLE_WEBRTC libprocessor_la_SOURCES += src/echo-cancel/algo_webrtc.cpp libprocessor_la_LIBADD += $(WEBRTC_LIBS) -libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -std=c++17 +libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -DSUPPORT_METHOD_WEBRTC -std=c++17 endif module_tizenaudio_echo_cancel_la_SOURCES = src/echo-cancel/module-tizenaudio-echo-cancel.c src/echo-cancel/echo-cancel-def.h diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 38eda7d..2611033 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.9 +Version: 15.0.10 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/algo_reference_copy.c b/src/echo-cancel/algo_reference_copy.c new file mode 100644 index 0000000..2d9cf0b --- /dev/null +++ b/src/echo-cancel/algo_reference_copy.c @@ -0,0 +1,106 @@ +/*** + This file is part of PulseAudio. + + Copyright 2022 Jaechul Lee + + 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 +#endif + +#include +#include +#include +#include +#include +#include + +struct reference_copy { + size_t nframes; + pa_sample_spec ss; + int reference_channels; +}; + +void *reference_copy_create(size_t nframes, pa_sample_spec *ss) { + struct reference_copy *rc; + + pa_assert(ss); + + rc = pa_xnew0(struct reference_copy, 1); + rc->nframes = nframes; + rc->ss = *ss; + + return rc; +} + +int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) { + struct reference_copy *rc = priv; + size_t rec_bytes, ref_bytes, actual_bytes, total_bytes; + int8_t *dst = out; + + pa_assert(rc); + pa_assert(rec); + pa_assert(ref); + pa_assert(out); + + rec_bytes = pa_frame_size(&rc->ss); + ref_bytes = rc->reference_channels * pa_sample_size(&rc->ss); + actual_bytes = rec_bytes - ref_bytes; + total_bytes = rec_bytes * rc->nframes; + + while ((dst - out) < total_bytes) { + memcpy(dst, rec, actual_bytes); + memcpy(dst + actual_bytes, ref, ref_bytes); + dst += rec_bytes; + rec += rec_bytes; + ref += ref_bytes; + } + + return 0; +} + +int32_t reference_copy_destroy(void *priv) { + struct reference_copy *rc = priv; + + pa_assert(rc); + + pa_xfree(rc); + + return 0; +} + +int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map) { + struct reference_copy *rc = priv; + int channels; + + pa_assert(rc); + pa_assert(source_ss); + pa_assert(sample_spec); + pa_assert(map); + + channels = rc->ss.channels - source_ss->channels; + if (channels <= 0) + return -1; + + *sample_spec = rc->ss; + sample_spec->channels = rc->reference_channels = channels; + + pa_channel_map_init_auto(map, channels, PA_CHANNEL_MAP_AIFF); + + return 0; +} diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 2f95314..8aa456c 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -46,8 +46,9 @@ PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( - "method= " - "blocksize= "); + "method= "); + +#define DEFAULT_PROCESS_MSEC 10 typedef struct echo_cancel pa_echo_cancel; struct userdata { @@ -67,7 +68,7 @@ struct userdata { bool enable; uint32_t n_source_output; - size_t blocksize; + char *default_method; pa_thread *thread; @@ -76,8 +77,8 @@ struct userdata { pa_echo_cancel *echo_cancel; /* use in thread */ - pa_memblockq *delayq; bool enable_in_thread; + bool triggered; pa_asyncmsgq *asyncmsgq_sink; pa_asyncmsgq *asyncmsgq_source; @@ -92,51 +93,42 @@ PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject); #define PA_ECHO_CANCEL(o) (pa_echo_cancel_cast(o)) #define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) -#define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL) -#define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output) #define DEFAULT_AEC_METHOD "speex" static const char* const valid_modargs[] = { "method", - "blocksize", NULL, }; -static int proplist_get_fragment_size(pa_proplist *p, size_t *size) { - const char *fragsize; - uint32_t blocksize; +static int proplist_get_fragment_size_usec(pa_proplist *p, pa_sample_spec *sample_spec, pa_usec_t *usec) { + const char *prop_fragsize; + uint32_t fragsize; + + pa_assert(p); + pa_assert(sample_spec); + pa_assert(usec); - if (!(fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE))) + if (!(prop_fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE))) return -1; - if (pa_atou(fragsize, &blocksize)) + if (pa_atou(prop_fragsize, &fragsize)) return -1; - *size = blocksize; + *usec = pa_bytes_to_usec(fragsize, sample_spec); return 0; } -static int pa_processor_get_method(pa_source_output *o, const char *default_method, pa_processor_algo_t *method) { - const char *selected, *requested; +static int proplist_get_method(pa_proplist *p, const char *default_method, pa_processor_method_t *method) { + const char *m; + pa_assert(p); pa_assert(method); - pa_assert(default_method); - requested = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); - if (!requested) + if (!(m = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_METHOD))) return -1; - selected = pa_streq(requested, "default") ? default_method : requested; - - if (pa_streq(selected, "webrtc")) - *method = PA_PROCESSOR_WEBRTC; - else if (pa_streq(selected, "speex")) - *method = PA_PROCESSOR_SPEEX; - else if (pa_streq(selected, "adrian")) - *method = PA_PROCESSOR_ADRIAN; - else - *method = PA_PROCESSOR_SPEEX; + *method = pa_processor_get_method(m, default_method); return 0; } @@ -150,38 +142,13 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { pa_source_output_assert_ref(o); - if (CHECK_FLAGS_AEC(o->flags)) + if (o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL) break; } return o ? o : NULL; } -static void free_source_output_extra_resource(pa_source_output *o) { - pa_assert(o); - - if (o->thread_info.processor) { - pa_processor_free(o->thread_info.processor); - o->thread_info.processor = NULL; - } - - if (o->thread_info.resampler2) { - pa_resampler_free(o->thread_info.resampler2); - o->thread_info.resampler2 = NULL; - } - - if (o->thread_info.echo) { - pa_memblockq_free(o->thread_info.echo); - o->thread_info.echo = NULL; - } -} - -static void free_source_output_extra_resource_by_source(pa_source *s) { - pa_assert(s); - - free_source_output_extra_resource(find_source_output_by_flags(s)); -} - static pa_usec_t get_round_trip_latency(struct userdata *u) { pa_usec_t sink_latency; pa_usec_t source_latency; @@ -189,65 +156,14 @@ static pa_usec_t get_round_trip_latency(struct userdata *u) { pa_assert(u); pa_assert(u->sink); - sink_latency = pa_sink_get_latency(u->sink); - source_latency = pa_source_get_latency(u->source); + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_latency, 0, NULL); + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_latency, 0, NULL); - pa_log_info("sink latency (%llu), source latency(%llu)", sink_latency, source_latency); + pa_log_info("sink latency (%" PRIu64 "), source latency(%" PRIu64 ")", sink_latency, source_latency); return sink_latency + source_latency; } -static int setup_delayq_latency(struct userdata *u, pa_usec_t latency) { - int64_t write_index, read_index; - size_t bytes, blocksize, n; - pa_memchunk silence; - - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) - return -1; - - if (u->delayq) - pa_memblockq_free(u->delayq); - - u->delayq = pa_memblockq_new("echo reference delay", - 0, - MEMBLOCKQ_MAXLENGTH, - 0, - &u->sink->sample_spec, - 0, - blocksize, - 0, - NULL); - - bytes = pa_usec_to_bytes(latency, &u->sink->sample_spec); - n = (bytes + blocksize - 1) / blocksize; - - pa_silence_memchunk_get( - &u->sink->core->silence_cache, - u->sink->core->mempool, - &silence, - &u->sink->sample_spec, - blocksize); - - if (!silence.memblock) - return -1; - - write_index = pa_memblockq_get_write_index(u->delayq); - read_index = pa_memblockq_get_read_index(u->delayq); - - pa_memblockq_flush_write(u->delayq, true); - while (n-- > 0) - pa_memblockq_push(u->delayq, &silence); - - pa_log_info("push n(%d) blocks. write_index(%llu->%llu), read_index(%llu->%llu)", - pa_memblockq_get_nblocks(u->delayq), - write_index, pa_memblockq_get_write_index(u->delayq), - read_index, pa_memblockq_get_read_index(u->delayq)); - - pa_memblock_unref(silence.memblock); - - return 0; -} - static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsgq *q) { struct arguments { pa_msgobject *o; @@ -277,48 +193,65 @@ static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsg } /* Call from main thread */ -static void set_aec_state(struct userdata *u, bool enable) { +static void broadcast_echo_cancel_state(struct userdata *u, pa_source_output *o, bool enable) { void *v[2]; - pa_usec_t latency = 0ULL; pa_assert(u); pa_assert(u->source); - pa_assert(u->sink); /* not allow sink null */ - - pa_log_info("set_aec state %d -> %d", u->enable, enable); - - if (u->enable == enable) - return; + pa_assert(u->sink); - latency = enable ? get_round_trip_latency(u) : 0ULL; + if (enable) { + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink); + } v[0] = (void *)enable; - v[1] = &latency; + v[1] = o; - /* There is a race condition between the source thread and the render thread. - * pa_source_post function can be overlapped at the same time */ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->echo_cancel), PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, (void *)v, 0, NULL); - if (u->sink) - pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), - PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + if (!enable) { + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + } +} + +static void set_echo_cancel_state(struct userdata *u, bool enable) { + pa_source_output *o; + + pa_assert(u); + pa_assert(u->source); + pa_assert(u->sink); + + if (u->enable == enable) + return; + + o = find_source_output_by_flags(u->source); + if (!o) { + pa_log_error("Failed to find EC source-output"); + return; + } + + broadcast_echo_cancel_state(u, o, enable); u->enable = enable; - pa_log_info("AEC state updated. enable(%d)", u->enable); + pa_log_info("AEC state is changed. enable(%d)", u->enable); } static int update_state_by_sink(struct userdata *u, bool enable) { pa_assert(u); - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) == 0) + if (u->n_source_output == 0) return 0; - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); return 0; } @@ -329,37 +262,18 @@ static int update_state_by_source(struct userdata *u, bool enable) { if (enable) { if (u->n_source_output++ == 0) { if (!u->sink || PA_SINK_IS_RUNNING(u->sink->state)) - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); } } else { if (--u->n_source_output == 0) - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); } return 0; } -static int unlink_source_output_in_thread(pa_source_output *o) { - pa_assert(o); - - free_source_output_extra_resource(o); - - /* source-output should be remove by render thread to prevent race condition. - * 1. invoke REMOVE message - * 2. receive the message in tizenaudio-source - * 3. send the message to tizenaudio-echo-cancel - * 4. remove source-output in render thread - */ - pa_source_process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); - - return 0; -} - static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { pa_source_output *o = NULL; - pa_memchunk ochunk; - bool nf = false; - int r; o = find_source_output_by_flags(s); if (!o) { @@ -367,129 +281,19 @@ static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { return; } - if (o->thread_info.resampler2) { - pa_resampler_run(o->thread_info.resampler2, chunk, &ochunk); - chunk = &ochunk; - nf = true; - } - - r = pa_memblockq_push(o->thread_info.echo, chunk); - if (r != 0) - pa_log_error("Failed to push chunk to memblockq"); - - if (nf) - pa_memblock_unref(chunk->memblock); - - pa_log_debug("Pushed echo data. index(%u) size(%llums), nblocks(%d) index(%lld:%lld)", - o->index, - pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_memblockq_get_nblocks(o->thread_info.echo), - pa_memblockq_get_write_index(o->thread_info.echo), - pa_memblockq_get_read_index(o->thread_info.echo)); -} - -static void flush_echo_memblockq(pa_source *s) { - pa_source_output *o; - - o = find_source_output_by_flags(s); - if (!o) { - pa_log_error("Can't find aec source-output"); - return; - } - - pa_memblockq_flush_write(o->thread_info.echo, true); + if (pa_processor_push_data(o->thread_info.processor, chunk) < 0) + pa_log_error("Failed to push reference data"); } static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) { int ret = -1; - int8_t *rec, *ref, *out; - size_t blocksize; - - pa_memchunk tchunk; - - if (!o->thread_info.processor) { - pa_log_error("Failed to get processor"); - return ret; - } - - /* - * pre-condition - * sink block >= source block >= blocksize * n - * if blocksize is not described blocksize, It should be same as source's fragment size - * - * chunk must be processed every data. - */ - /* reference exist */ - if (o->thread_info.echo) { - size_t block_bytes, length, n; - - /* echo queue is not ready that means reference is not started */ - if (pa_memblockq_is_empty(o->thread_info.echo)) - return ret; - - blocksize = pa_processor_get_blocksize(o->thread_info.processor); - block_bytes = blocksize * pa_frame_size(&o->sample_spec); - - if (chunk->length % block_bytes) { - pa_log_warn("Skip to process aec. chunk size must be multiple of blocksize"); - return -1; - } - - n = chunk->length / block_bytes; - length = n * block_bytes; - - if (!(ret = pa_memblockq_peek_fixed_size(o->thread_info.echo, length, &tchunk))) { - int i; - - ochunk->index = 0; - ochunk->length = length; - ochunk->memblock = pa_memblock_new(o->core->mempool, length); - - rec = pa_memblock_acquire(chunk->memblock); - ref = pa_memblock_acquire(tchunk.memblock); - out = pa_memblock_acquire(ochunk->memblock); /* TODO: buffer can be shared rec buffer */ - - for (i=0; ithread_info.processor, - rec + (i * block_bytes), - ref + (i * block_bytes), - out + (i * block_bytes)); - - pa_memblock_release(chunk->memblock); - pa_memblock_release(tchunk.memblock); - pa_memblock_release(ochunk->memblock); - - pa_log_debug("Post-process. i(%u), rec(%llums), ref(%llums) " - "block(%llums), process(%llums) * n(%d) " - "silence(%d), index(%lld:%lld)", - o->index, - pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(tchunk.length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(block_bytes, &o->sample_spec) / PA_USEC_PER_MSEC, - n, - pa_memblock_is_silence(tchunk.memblock), - pa_memblockq_get_write_index(o->thread_info.echo), - pa_memblockq_get_read_index(o->thread_info.echo)); - - pa_memblock_unref(tchunk.memblock); - pa_memblockq_drop(o->thread_info.echo, tchunk.length); - } - } else { - /* no reference case like audio_share */ - rec = pa_memblock_acquire(chunk->memblock); - - ochunk->index = 0; - ochunk->length = chunk->length; - ochunk->memblock = pa_memblock_new(o->core->mempool, chunk->length); - out = pa_memblock_acquire(ochunk->memblock); - - pa_processor_process(o->thread_info.processor, rec, NULL, out); + pa_assert(o); + pa_assert(chunk); + pa_assert(ochunk); - pa_memblock_release(chunk->memblock); - pa_memblock_release(ochunk->memblock); - } + if ((ret = pa_processor_process(o->thread_info.processor, chunk, ochunk)) < 0) + pa_log_error("Failed to process data"); return ret; } @@ -504,86 +308,80 @@ static int process_msg( struct userdata *u = PA_ECHO_CANCEL(o)->u; - /* thread that pushes ref data should be called in render thread because of thread safe */ - switch (code) { - case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: { - /* a few pcm data will get lost. */ - if (!u->enable_in_thread) - return 0; + /* trigger resolves a race condition related to post_process between source and render thread */ + if (u->triggered) { + if (code == PA_ECHO_CANCEL_MESSAGE_PUSH_DATA || code == PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO) { + pa_source_output *o = NULL; + pa_usec_t latency; - pa_source_post(u->source, chunk); + o = find_source_output_by_flags(u->source); + o->post_process = post_process; - return 0; - } - case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: { - pa_memchunk ochunk; + latency = get_round_trip_latency(u); + + if (pa_processor_setup_reference_memblockq_padding(o->thread_info.processor, latency) < 0) + pa_log_warn("Failed to setup reference memblockq padding"); - if (!u->enable_in_thread) - return 0; + u->triggered = false; - pa_memblockq_push(u->delayq, chunk); + pa_log_info("Triggered Echo-Cancellation. index(%d), latency(%" PRIu64 ") usec", o->index, latency); + } + } - if (!pa_memblockq_peek(u->delayq, &ochunk)) { - pa_source_push_echo(u->source, &ochunk); + /* thread that pushes ref data should be called in render thread because of thread safe */ + switch (code) { + case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: + if (u->enable_in_thread) + pa_source_post(u->source, chunk); - pa_memblock_unref(ochunk.memblock); - pa_memblockq_drop(u->delayq, ochunk.length); - } + break; + case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: + if (u->enable_in_thread) + pa_source_push_echo(u->source, chunk); - return 0; - } - case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE : { + break; + case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE: { void **v = (void **)data; - pa_usec_t latency; + bool enable = (bool)v[0]; + pa_source_output *o = (pa_source_output *)v[1]; - u->enable_in_thread = !!(int)v[0]; - latency = *(pa_usec_t *)v[1]; + u->enable_in_thread = enable; - if (u->enable_in_thread) { - if (setup_delayq_latency(u, latency)) { - pa_log_error("Failed to init delayq"); - return 0; - } + if (enable) { + u->triggered = true; } else { - if (u->delayq) { - pa_memblockq_free(u->delayq); - u->delayq = NULL; - } - - flush_echo_memblockq(u->source); + pa_processor_flush(o->thread_info.processor); + o->post_process = NULL; } - pa_log_info("EC state change (%d)", u->enable_in_thread); - - return 0; + break; } - case PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK: - unlink_source_output_in_thread((pa_source_output *)data); - return 0; default: - return 0; + break; } + + return 0; } static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *method; + pa_processor_method_t method; pa_assert(c); pa_assert(u); pa_assert(data); - method = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); - if (!method) - return PA_HOOK_OK; - - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { - pa_log_error("Not allow multi aec instance"); + if (proplist_get_method(data->proplist, u->default_method, &method) < 0) return PA_HOOK_OK; - } - data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL; + /* TODO: source-output can be moved */ data->flags |= PA_SOURCE_OUTPUT_DONT_MOVE; + data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL; + + if (method == PA_PROCESSOR_REFERENCE_COPY) + data->flags |= PA_SOURCE_OUTPUT_NO_REMAP; + + pa_log_info("echo-cancel source-output will be created. method(%d)", method); // TODO:add check limitation VARIOUS_RATE? return PA_HOOK_OK; @@ -613,72 +411,31 @@ static pa_sink *find_reference_sink_by_proplist(pa_core *c, pa_proplist *p) { return s; } -static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) { - pa_usec_t sink_usec; - pa_usec_t source_usec; - pa_usec_t block_usec; - size_t blocksize; - - if (proplist_get_fragment_size(source->proplist, &blocksize)) { - pa_log_debug("Failed to get blocksize from source"); - return -1; - } - - source_usec = pa_bytes_to_usec(blocksize, &source->sample_spec); - block_usec = u->blocksize ? pa_bytes_to_usec(u->blocksize, &source->sample_spec) : source_usec; - - /* - * limitation - * sink block >= source block >= blocksize * n - */ - if (source_usec < block_usec) { - pa_log_debug("Need to check period size. source >= block * n. " - "source(%llums), block_usec(%llums)", - source_usec / PA_USEC_PER_MSEC, - block_usec / PA_USEC_PER_MSEC); - return -1; - } - - if (!u->sink) - return 0; - - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { - pa_log_debug("Failed to get blocksize from sink"); - return -1; - } - - sink_usec = pa_bytes_to_usec(blocksize, &u->sink->sample_spec); - - if (source_usec > sink_usec || sink_usec < block_usec) { - pa_log_debug("Need to check period size. sink >= source >= block * n. " - "source(%llums) sink(%llums) block_usec(%llums)", - source_usec / PA_USEC_PER_MSEC, - sink_usec / PA_USEC_PER_MSEC, - block_usec / PA_USEC_PER_MSEC); - return -1; - } - - return 0; -} - static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; - size_t blocksize = u->blocksize; - pa_processor_algo_t method; + pa_processor_method_t method; + pa_usec_t process_usec; + pa_usec_t sink_process_usec; + int r; pa_assert(c); pa_assert(o); pa_assert(u); pa_assert(o->source); - if (!CHECK_FLAGS_AEC(o->flags)) + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) return PA_HOOK_OK; - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { + if (u->n_source_output > 0) { pa_log_error("Not allow multi aec instance"); goto fail; } + if (proplist_get_method(o->proplist, u->default_method, &method) < 0) { + pa_log_error("Failed to get method"); + goto fail; + } + u->source = o->source; u->sink = find_reference_sink_by_proplist(c, o->proplist); if (!u->sink) { @@ -686,83 +443,46 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo goto fail; } - if (check_latency_validation(u, u->sink, u->source)) { - pa_log_error("Failed to check latency validation"); + /* Get period size of sink and source */ + if (proplist_get_fragment_size_usec(u->source->proplist, &u->source->sample_spec, &process_usec) < 0) { + pa_log_error("Failed to get fragment usec"); goto fail; } - /* Use the sources fragment size if blocksize is not specified */ - if (!blocksize) { - if (proplist_get_fragment_size(u->source->proplist, &blocksize)) { - pa_log_error("Failed to get blocksize"); - goto fail; - } + if (proplist_get_fragment_size_usec(u->sink->proplist, &u->sink->sample_spec, &sink_process_usec) < 0) { + pa_log_error("Failed to get fragment usec"); + goto fail; } - if (o->thread_info.resampler) - blocksize = pa_resampler_result(o->thread_info.resampler, blocksize); - - if (pa_processor_get_method(o, u->default_method, &method)) { - pa_log_error("Can't find method"); + if (sink_process_usec < process_usec) { + pa_log_error("sink process usec should be bigger than source"); goto fail; } - o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec), + o->thread_info.processor = pa_processor_new(c, process_usec / PA_USEC_PER_MSEC, &o->sample_spec, - method, PA_PROCESSOR_FLAGS_ECHO_CANCEL); + &o->channel_map, + &u->source->sample_spec, + method); if (!o->thread_info.processor) { pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled"); goto fail; } - if (u->sink) { - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { - pa_log_error("Failed to get blocksize"); - goto fail; - } - - if (!pa_sample_spec_equal(&u->sink->sample_spec, &o->sample_spec)) { - pa_resampler *resampler2; - - resampler2 = pa_resampler_new( - c->mempool, - &u->sink->sample_spec, &u->sink->channel_map, - &o->sample_spec, &o->channel_map, - c->lfe_crossover_freq, - c->resample_method, 0); - - if (!resampler2) { - pa_log_error("Failed to allocate resampler2 for echo-cancel"); - goto fail; - } - - o->thread_info.resampler2 = resampler2; - blocksize = pa_resampler_result(o->thread_info.resampler2, blocksize); - - pa_log_info("Use resampler2. blocksize(%d) bytes", blocksize); - } - - o->thread_info.echo = pa_memblockq_new("echo reference", - 0, - MEMBLOCKQ_MAXLENGTH, - 0, - &o->sample_spec, - 0, - blocksize, - 0, - &o->source->silence); - - if (!o->thread_info.echo) { - pa_log_error("Failed to alloc memblockq"); - goto fail; - } + r = pa_processor_bind_reference(o->thread_info.processor, + sink_process_usec / PA_USEC_PER_MSEC, + &u->sink->sample_spec, + &u->sink->channel_map); + if (r < 0) { + pa_log_error("Failed to bind reference source"); + goto fail; } - o->post_process = post_process; - - /* connect to sink and source */ - send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source); - send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink); + pa_log_info("echo-cancel source-output(%u) created. process_msec(%u), sink_process_msec(%u), method(%s)", + o->index, + (uint32_t)(process_usec / PA_USEC_PER_MSEC), + (uint32_t)(sink_process_usec / PA_USEC_PER_MSEC), + pa_processor_method_to_string(method)); update_state_by_source(u, true); @@ -770,30 +490,45 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo fail: o->flags &= ~PA_SOURCE_OUTPUT_ECHO_CANCEL; // TODO: need to consider DONT_MOVE define - free_source_output_extra_resource(o); + if (o->thread_info.processor) + pa_processor_free(o->thread_info.processor); return PA_HOOK_OK; } /* Call from main thread */ -static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) { +static pa_hook_result_t source_output_unlink_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; pa_assert(c); pa_assert(o); pa_assert(u); - if (!CHECK_FLAGS_AEC(o->flags)) + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) return PA_HOOK_OK; update_state_by_source(u, false); - send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); - send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(c); + pa_assert(o); + pa_assert(u); + + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) + return PA_HOOK_OK; + + pa_processor_free(o->thread_info.processor); u->source = NULL; u->sink = NULL; + pa_log_info("echo-cancel source-output(%u) is unlinked", o->index); + return PA_HOOK_OK; } @@ -877,7 +612,6 @@ fail: int pa__init(pa_module *m) { pa_modargs *ma = NULL; struct userdata *u = NULL; - uint32_t blocksize = 0; pa_assert(m); @@ -886,15 +620,9 @@ int pa__init(pa_module *m) { return -1; } - if (pa_modargs_get_value_u32(ma, "blocksize", &blocksize) < 0) { - pa_log_info("Failed to get blocksize"); - goto fail; - } - m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->m = m; - u->blocksize = blocksize; u->default_method = pa_xstrdup(pa_modargs_get_value(ma, "method", DEFAULT_AEC_METHOD)); u->echo_cancel = pa_msgobject_new(pa_echo_cancel); @@ -925,6 +653,10 @@ int pa__init(pa_module *m) { pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, u); + u->source_output_unlink_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], + PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_cb, u); + u->source_output_unlink_post_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u); @@ -989,19 +721,20 @@ void pa__done(pa_module *m) { if (u->asyncmsgq_source) { if (u->source) { + pa_source_output *o; + pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL); send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); - free_source_output_extra_resource_by_source(u->source); + + if ((o = find_source_output_by_flags(u->source))) + pa_processor_free(o->thread_info.processor); } pa_asyncmsgq_unref(u->asyncmsgq_source); } - if (u->delayq) - pa_memblockq_free(u->delayq); - if (u->rtpoll) pa_rtpoll_free(u->rtpoll); diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index 3681347..a97b99c 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -24,11 +24,16 @@ #endif #include +#include +#include #include #include +#include +#include #include "processor.h" +//#define __DEBUG__ #ifdef __DEBUG__ #include #include @@ -37,10 +42,42 @@ #include #endif -struct pa_processor_algo { +#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) + +typedef struct pa_processor_method_interface pa_processor_method_interface; +struct pa_processor { + pa_core *core; + pa_processor_method_interface *intf; + pa_processor_method_t method; + void *priv; + + size_t process_frames; + pa_usec_t process_usec; + pa_usec_t reference_process_usec; + size_t process_bytes; + size_t reference_process_bytes; + + pa_sample_spec *output_ss; + pa_channel_map *output_chmap; + pa_sample_spec *source_ss; + pa_sample_spec reference_memblockq_ss; + + pa_resampler *resampler; + pa_memblockq *reference_memblockq; + pa_memblockq *output_memblockq; + +#ifdef __DEBUG__ + int fdrec, fdref, fdout; + struct timeval before, after; +#endif +}; + +struct pa_processor_method_interface { + const char *name; void *(*create)(size_t nframes, pa_sample_spec *ss); int32_t (*process)(void *priv, int8_t *rec, int8_t *ref, int8_t *out); int32_t (*destroy)(void *priv); + int32_t (*change_reference_spec)(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map); }; extern void *adrian_create(size_t nframes, pa_sample_spec *ss); @@ -51,145 +88,463 @@ extern void *speex_create(size_t nframes, pa_sample_spec *ss); extern int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); extern int32_t speex_destroy(void *priv); +#ifdef SUPPORT_METHOD_WEBRTC extern void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss); extern int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); extern int32_t webrtc_audio_destroy(void *priv); +#endif -pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags) { - pa_processor *processor = NULL; +extern void *reference_copy_create(size_t nframes, pa_sample_spec *ss); +extern int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +extern int32_t reference_copy_destroy(void *priv); +extern int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map); - pa_assert(ss); +#ifdef __DEBUG__ +static void debug_open_file(pa_processor *processor); +static void debug_timestamp_begin(pa_processor *processor); +static void debug_timestamp_end(pa_processor *processor); +static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out); +static void debug_close_file(pa_processor *processor); +#else +#define debug_open_file(x) +#define debug_timestamp_begin(x) +#define debug_timestamp_end(x) +#define debug_write_file(x, a, b, c) +#define debug_close_file(x) +#endif - if (ss->format != PA_SAMPLE_S16LE) { - pa_log_error("Not supported format(%d)", ss->format); +static struct pa_processor_method_interface method_table[PA_PROCESSOR_METHOD_MAX] = { + { + "speex", + speex_create, + speex_process, + speex_destroy, + NULL, + }, + { + "adrian", + adrian_create, + adrian_process, + adrian_destroy, + NULL, + }, +#ifdef SUPPORT_METHOD_WEBRTC + { + "webrtc", + webrtc_audio_create, + webrtc_audio_process, + webrtc_audio_destroy, + NULL, + }, +#endif + { + "reference_copy", + reference_copy_create, + reference_copy_process, + reference_copy_destroy, + reference_copy_change_reference_spec, + }, +}; + +static size_t pa_processor_usec_to_frame(pa_usec_t usec, pa_sample_spec *spec) { + pa_assert(spec); + + return pa_usec_to_bytes(usec, spec) / pa_frame_size(spec); +} + +pa_processor *pa_processor_new(pa_core *core, + uint32_t process_msec, + pa_sample_spec *output_ss, + pa_channel_map *output_map, + pa_sample_spec *source_ss, + pa_processor_method_t method) { + pa_processor *processor; + pa_memchunk silence; + + pa_assert(core); + pa_assert(output_ss); + pa_assert(output_map); + pa_assert(source_ss); + pa_assert(method < PA_PROCESSOR_METHOD_MAX); + + processor = pa_xnew0(pa_processor, 1); + processor->intf = &method_table[method]; + processor->core = core; + processor->process_usec = process_msec * PA_USEC_PER_MSEC; + processor->output_ss = output_ss; + processor->output_chmap = output_map; + processor->source_ss = source_ss; + processor->method = method; + processor->process_frames = pa_processor_usec_to_frame(processor->process_usec, processor->output_ss); + processor->process_bytes = pa_usec_to_bytes(processor->process_usec, processor->output_ss); + + if (!(processor->priv = processor->intf->create(processor->process_frames, output_ss))) { + pa_log_error("Failed to create processor. rate(%d), channels(%d).", output_ss->rate, output_ss->channels); + pa_xfree(processor); return NULL; } - processor = pa_xnew0(pa_processor, 1); - processor->intf = pa_xnew0(pa_processor_algo, 1); - processor->nframes = nframes; - processor->framesize = pa_frame_size(ss); - - switch (backend) { - case PA_PROCESSOR_SPEEX: - processor->intf->create = speex_create; - processor->intf->process = speex_process; - processor->intf->destroy = speex_destroy; - break; - case PA_PROCESSOR_ADRIAN: - processor->intf->create = adrian_create; - processor->intf->process = adrian_process; - processor->intf->destroy = adrian_destroy; - break; - case PA_PROCESSOR_WEBRTC: - processor->intf->create = webrtc_audio_create; - processor->intf->process = webrtc_audio_process; - processor->intf->destroy = webrtc_audio_destroy; - break; - default: - pa_log_error("Invalid backend(%d)", backend); - goto fail; + pa_silence_memchunk_get(&core->silence_cache, core->mempool, &silence, output_ss, 0); + processor->output_memblockq = pa_memblockq_new("source-output memblockq", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + processor->output_ss, + 0, + pa_usec_to_bytes(processor->process_usec, output_ss), + 0, + &silence); + pa_memblock_unref(silence.memblock); + + pa_log_info("Created processor. memblockq rate(%d), channels(%d), process_msec(%u), " + "process_bytes(%zu), method(%s), source rate(%d), channels(%d)", + output_ss->rate, + output_ss->channels, + process_msec, + pa_usec_to_bytes(processor->process_usec, output_ss), + method_table[method].name, + source_ss->rate, + source_ss->channels); + + debug_open_file(processor); + + return processor; +} + +int pa_processor_bind_reference(pa_processor *processor, + uint32_t process_msec, + pa_sample_spec *reference_ss, + pa_channel_map *reference_chmap) { + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_memchunk silence; + + pa_assert(processor); + pa_assert(processor->intf); + pa_assert(processor->output_ss); + pa_assert(processor->output_chmap); + pa_assert(reference_ss); + pa_assert(reference_chmap); + + processor->reference_process_usec = process_msec * PA_USEC_PER_MSEC; + if (processor->reference_process_usec < processor->process_usec) { + pa_log_error("Failed to bind reference. ref_usec(%" PRId64 "), process_usec(%" PRId64 ")", + processor->reference_process_usec, processor->process_usec); + return -1; } - pa_log_info("Use backend(%d) nframes(%zu) framesize(%d)", - backend, processor->nframes, processor->framesize); + sample_spec = *processor->output_ss; + channel_map = *processor->output_chmap; - if (!(processor->priv = processor->intf->create(nframes, ss))) { - pa_log_error("Failed to create processor"); - goto fail; + /* select reference memblockq sample_spec and channelmap */ + if (processor->intf->change_reference_spec) { + if (processor->intf->change_reference_spec(processor->priv, processor->source_ss, &sample_spec, &channel_map) < 0) { + pa_log_error("Failed to get reference info"); + return -1; + } } -#ifdef __DEBUG__ - { - static int n = 1; - char rec[32], ref[32], out[32]; + /* Create resampler */ + if (!pa_sample_spec_equal(reference_ss, &sample_spec)) { + if (processor->resampler) + pa_resampler_free(processor->resampler); + + processor->resampler = pa_resampler_new(processor->core->mempool, + reference_ss, reference_chmap, + &sample_spec, &channel_map, + processor->core->lfe_crossover_freq, + processor->core->resample_method, 0); + if (!processor->resampler) { + pa_log_error("Failed to allocate reference resampler"); + return -1; + } + } - snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n); - snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n); - snprintf(out, sizeof(out), "/tmp/out-%d.raw", n); - n += 1; + processor->reference_memblockq_ss = sample_spec; + processor->reference_process_bytes = pa_usec_to_bytes(processor->reference_process_usec, &processor->reference_memblockq_ss); - unlink(rec); - unlink(ref); - unlink(out); + /* Create memblockq */ + pa_silence_memchunk_get(&processor->core->silence_cache, processor->core->mempool, &silence, &sample_spec, 0); - processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777); - processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777); - processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777); - } -#endif + if (processor->reference_memblockq) + pa_memblockq_free(processor->reference_memblockq); - return processor; + processor->reference_memblockq = pa_memblockq_new("reference memblockq", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &processor->reference_memblockq_ss, + 0, + processor->reference_process_bytes, + 0, + &silence); + pa_memblock_unref(silence.memblock); -fail: - pa_xfree(processor->intf); - pa_xfree(processor); + pa_log_debug("Created reference memblockq rate(%d), channels(%d), msec(%u), bytes(%zu)", + sample_spec.rate, + sample_spec.channels, + process_msec, + processor->reference_process_bytes); - return NULL; + return 0; } -int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) { - int ret = -1; +int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency) { + pa_memchunk silence; + int64_t write_index, read_index; + size_t n, bytes; pa_assert(processor); + + bytes = pa_usec_to_bytes(latency, &processor->reference_memblockq_ss); + n = (bytes + (processor->reference_process_bytes - 1)) / processor->reference_process_bytes; + + pa_silence_memchunk_get( + &processor->core->silence_cache, + processor->core->mempool, + &silence, + &processor->reference_memblockq_ss, + processor->reference_process_bytes); + + write_index = pa_memblockq_get_write_index(processor->reference_memblockq); + read_index = pa_memblockq_get_read_index(processor->reference_memblockq); + + for (; n > 0; n--) + pa_memblockq_push(processor->reference_memblockq, &silence); + + pa_memblock_unref(silence.memblock); + + pa_log_info("push n(%u) silence blocks. ref_process_bytes(%zu) " + "write_index(%" PRId64 "->%" PRId64 "), read_index(%" PRId64 "->%" PRId64 ")", + pa_memblockq_get_nblocks(processor->reference_memblockq), + processor->reference_process_bytes, + write_index, pa_memblockq_get_write_index(processor->reference_memblockq), + read_index, pa_memblockq_get_read_index(processor->reference_memblockq)); + + return 0; +} + +// TODO naming +int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchunk *ochunk) { + int r = -1; + int8_t *recording = NULL; + int8_t *reference = NULL; + int8_t *output = NULL; + bool silence = false; + + pa_memchunk ichunk, rchunk; + + pa_assert(processor); + pa_assert(processor->output_memblockq); + pa_assert(processor->reference_memblockq); pa_assert(processor->intf); - pa_assert(rec); - pa_assert(out); + pa_assert(processor->process_bytes > 0ULL); + pa_assert(processor->reference_process_bytes > 0ULL); + pa_assert(chunk); + pa_assert(ochunk); + + if ((r = pa_memblockq_push(processor->output_memblockq, chunk)) < 0) { + pa_log_error("Failed to push chunk to echo memblockq"); + return r; + } -#ifdef __DEBUG__ - if (write(processor->fdrec, rec, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write rec buffer"); + if ((r = pa_memblockq_peek_fixed_size(processor->reference_memblockq, processor->reference_process_bytes, &rchunk)) < 0) { + pa_log_error("Failed to get memblock from reference memblockq"); + return r; + } + silence = pa_memblock_is_silence(rchunk.memblock); - if (write(processor->fdref, ref, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write ref buffer"); + if ((r = pa_memblockq_peek_fixed_size(processor->output_memblockq, processor->process_bytes, &ichunk)) < 0) { + pa_log_error("Failed to get memblock from output memblockq"); + return r; + } - gettimeofday(&processor->before, NULL); -#endif + ochunk->index = 0; + ochunk->length = ichunk.length; + ochunk->memblock = pa_memblock_new(processor->core->mempool, ochunk->length); - if (processor->intf->process) - ret = processor->intf->process(processor->priv, rec, ref, out); + recording = pa_memblock_acquire(ichunk.memblock); + reference = pa_memblock_acquire(rchunk.memblock); + output = pa_memblock_acquire(ochunk->memblock); -#ifdef __DEBUG__ - if (write(processor->fdout, out, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write out buffer"); + debug_timestamp_begin(processor); - gettimeofday(&processor->after, NULL); + r = processor->intf->process(processor->priv, recording, reference, output); - pa_log_debug("It takes time(%ld) bytes(%d)", - 1000 * (after.tv_sec-before.tv_sec) + (after.tv_usec-before.tv_usec) / 1000, - processor->buffer_size); -#endif + debug_timestamp_end(processor); + debug_write_file(processor, recording, reference, output); + + pa_memblock_release(ichunk.memblock); + pa_memblock_release(rchunk.memblock); + pa_memblock_release(ochunk->memblock); - return ret; + pa_memblock_unref(rchunk.memblock); + pa_memblockq_drop(processor->reference_memblockq, rchunk.length); + + pa_memblock_unref(ichunk.memblock); + pa_memblockq_drop(processor->output_memblockq, ichunk.length); + + pa_log_debug("Post-process. rec(%" PRIu64 "ms), ref(%" PRIu64 "msms) out(%" PRIu64 "ms), " + "silence(%d), windex:rindex(%" PRId64 ":%" PRId64 ")", + pa_bytes_to_usec(ichunk.length, processor->output_ss) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(rchunk.length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(ochunk->length, processor->output_ss) / PA_USEC_PER_MSEC, + silence, + pa_memblockq_get_write_index(processor->reference_memblockq), + pa_memblockq_get_read_index(processor->reference_memblockq)); + + return r; +} + +int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { + pa_memchunk ochunk; + int r; + + pa_assert(processor); + pa_assert(chunk); + + if (processor->resampler) { + pa_resampler_run(processor->resampler, chunk, &ochunk); + chunk = &ochunk; + } + + if ((r = pa_memblockq_push(processor->reference_memblockq, chunk)) < 0) + pa_log_error("Failed to push chunk to echo memblockq"); + + if (processor->resampler) + pa_memblock_unref(chunk->memblock); + + pa_log_debug("Pushed echo data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", + chunk->length, + pa_bytes_to_usec(chunk->length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, + pa_memblockq_get_nblocks(processor->reference_memblockq), + pa_memblockq_get_write_index(processor->reference_memblockq), + pa_memblockq_get_read_index(processor->reference_memblockq)); + + return r; +} + +void pa_processor_flush(pa_processor *processor) { + pa_assert(processor); + + if (processor->reference_memblockq) + pa_memblockq_flush_read(processor->reference_memblockq); + + if (processor->output_memblockq) + pa_memblockq_flush_read(processor->output_memblockq); } int pa_processor_free(pa_processor *processor) { pa_assert(processor); + pa_assert(processor->priv); pa_assert(processor->intf); - pa_assert(processor->intf->destroy); - -#ifdef __DEBUG__ - if (processor->fdrec) - close(processor->fdrec); - if (processor->fdref) - close(processor->fdref); - if (processor->fdout) - close(processor->fdout); -#endif - if (processor->intf->destroy(processor->priv)) { + if (processor->intf->destroy(processor->priv) < 0) pa_log_error("Failed to destroy processor"); - return -1; - } - pa_xfree(processor->intf); + if (processor->resampler) + pa_resampler_free(processor->resampler); + + if (processor->reference_memblockq) + pa_memblockq_free(processor->reference_memblockq); + + if (processor->output_memblockq) + pa_memblockq_free(processor->output_memblockq); + + debug_close_file(processor); + pa_xfree(processor); return 0; } -size_t pa_processor_get_blocksize(pa_processor *processor) { - pa_assert(processor); +pa_processor_method_t pa_processor_get_method(const char *request_method, const char *default_method) { + const char *selected; + + if (!request_method || !default_method) + return PA_PROCESSOR_SPEEX; + + selected = pa_streq(request_method, "default") ? default_method : request_method; - return processor->nframes; + if (pa_streq(selected, "speex")) + return PA_PROCESSOR_SPEEX; + else if (pa_streq(selected, "adrian")) + return PA_PROCESSOR_ADRIAN; +#ifdef SUPPORT_METHOD_WEBRTC + else if (pa_streq(selected, "webrtc")) + return PA_PROCESSOR_WEBRTC; +#endif + else if (pa_streq(selected, "reference_copy")) + return PA_PROCESSOR_REFERENCE_COPY; + else + return PA_PROCESSOR_SPEEX; +} + +const char *pa_processor_method_to_string(pa_processor_method_t method) { + if (method >= PA_PROCESSOR_METHOD_MAX) + return NULL; + + return method_table[method].name; +} + +#ifdef __DEBUG__ +static void debug_open_file(pa_processor *processor) { + static int n = 1; + char rec[32], ref[32], out[32]; + + snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n); + snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n); + snprintf(out, sizeof(out), "/tmp/out-%d.raw", n); + n += 1; + + unlink(rec); + unlink(ref); + unlink(out); + + processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777); +} + +static void debug_timestamp_begin(pa_processor *processor) { + gettimeofday(&processor->before, NULL); +} + +static void debug_timestamp_end(pa_processor *processor) { + gettimeofday(&processor->after, NULL); + + pa_log_debug("It takes time (%ld)ms.", + 1000 * (processor->after.tv_sec - processor->before.tv_sec) + + (processor->after.tv_usec - processor->before.tv_usec) / 1000); +} + +static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) { + if (rec && write(processor->fdrec, rec, processor->process_bytes) <= 0) + pa_log_error("Failed to write recording buffer"); + + if (ref && write(processor->fdref, ref, processor->reference_process_bytes) <= 0) + pa_log_error("Failed to write reference buffer"); + + if (out && write(processor->fdout, out, processor->process_bytes) <= 0) + pa_log_error("Failed to write ref buffer"); +} + +static void debug_close_file(pa_processor *processor) { + if (processor->fdrec) { + close(processor->fdrec); + processor->fdrec = -1; + } + + if (processor->fdref) { + close(processor->fdref); + processor->fdref = -1; + } + + if (processor->fdout) { + close(processor->fdout); + processor->fdout = -1; + } } +#endif + diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h index fa642ce..fa77e87 100644 --- a/src/echo-cancel/processor.h +++ b/src/echo-cancel/processor.h @@ -1,6 +1,3 @@ -#ifndef foopulseprocessorfoo -#define foopulseprocessorfoo - /*** This file is part of PulseAudio. @@ -22,44 +19,47 @@ USA. ***/ +#ifndef foopulseprocessorfoo +#define foopulseprocessorfoo + #ifdef HAVE_CONFIG_H #include #endif +#include +#include +#include #include -//#define __DEBUG__ - -typedef enum { - PA_PROCESSOR_FLAGS_ECHO_CANCEL, - PA_PROCESSOR_FLAGS_NOISE_SUPPRESSION, -} pa_process_flags_t; - typedef enum { PA_PROCESSOR_SPEEX, PA_PROCESSOR_ADRIAN, +#ifdef SUPPORT_METHOD_WEBRTC PA_PROCESSOR_WEBRTC, -} pa_processor_algo_t; +#endif + PA_PROCESSOR_REFERENCE_COPY, + PA_PROCESSOR_METHOD_MAX, +} pa_processor_method_t; -typedef struct pa_processor_algo pa_processor_algo; typedef struct pa_processor pa_processor; -struct pa_processor { - pa_processor_algo *intf; - void *priv; - void *userdata; - size_t nframes; - size_t framesize; - -#ifdef __DEBUG__ - int fdrec, fdref, fdout; - struct timeval before, after; -#endif -}; - -pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags); -int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out); +pa_processor *pa_processor_new(pa_core *core, + uint32_t process_msec, + pa_sample_spec *output_ss, + pa_channel_map *output_map, + pa_sample_spec *source_ss, + pa_processor_method_t method); +int pa_processor_bind_reference(pa_processor *processor, + uint32_t process_msec, + pa_sample_spec *reference_ss, + pa_channel_map *reference_chmap); +int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency); +int pa_processor_process(pa_processor *processor, pa_memchunk *rec, pa_memchunk *out); +int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk); +void pa_processor_flush(pa_processor *processor); int pa_processor_free(pa_processor *processor); -size_t pa_processor_get_blocksize(pa_processor *processor); +pa_processor_method_t pa_processor_get_method(const char *requset_method, const char *default_method); +const char *pa_processor_method_to_string(pa_processor_method_t method); #endif + diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 0fc047c..5bd3a61 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -279,19 +279,6 @@ static int source_process_msg( return 0; } - case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { - pa_source_output *o = (pa_source_output *)data; - - if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) - break; - - if (u->ec_asyncmsgq) { - pa_asyncmsgq_send(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK, o, 0, NULL); - } - - return 0; - } case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { struct arguments { pa_msgobject *o; -- 2.7.4 From 1a8e6880d70f0705c4c6e5917a34bee3a56baf14 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Thu, 21 Apr 2022 12:52:07 +0900 Subject: [PATCH 11/16] sound-player: Fix INTEGER_OVERFLOW defect [Version] 15.0.11 [Issue Type] Svace Change-Id: I224d3ac72c27a1b184baf72933bbe5618a073d45 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-sound-player.c | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 2611033..9122970 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.10 +Version: 15.0.11 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-sound-player.c b/src/module-sound-player.c index 16212ae..ce505a8 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -711,12 +711,12 @@ static void deinit_ipc(struct userdata *u) { #endif } -static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { +static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { struct userdata *u = userdata; struct ipc_data data; - int ret = 0; - int data_size = 0; - int read_sum = 0; + ssize_t ret = 0; + size_t data_size = 0; + size_t read_sum = 0; int retry_count = 0; pa_assert(io); @@ -728,15 +728,18 @@ static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io } if (events & PA_IO_EVENT_INPUT) { - data_size = sizeof(data); + data_size = sizeof(struct ipc_data); memset(&data, 0, data_size); + while (read_sum != data_size && retry_count < RETRY_NUM) { - ret = read(fd, (void*)(((char *)&data)+read_sum), data_size-read_sum); - if (ret < 0 && errno == EAGAIN) + ret = read(fd, (void *)(((char *)&data) + read_sum), data_size - read_sum); + if (ret < 0) { retry_count++; - else - read_sum += ret; + continue; + } + read_sum += (size_t)ret; } + if (read_sum == data_size) { pa_log_info("name(%s), role(%s), volume_gain_type(%s), method(%s)", data.filename, data.role, data.volume_gain_type, data.method); -- 2.7.4 From 740ec5fd89f85b26072c3c2a0b4f3a125ee009ba Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 21 Apr 2022 17:15:38 +0900 Subject: [PATCH 12/16] tizenaudio-echo-cancel: Fix UNINIT.LOCAL_VAR This patch fixes the svace and convention issues * Fix svace issue * Relace echo with referece [Version] 15.0.12 [Issue Type] SVACE Change-Id: I8ebe7e724c090fc4ce8bfa120b0e596d95723a2d Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 33 ++++++++++++------------- src/echo-cancel/processor.c | 6 ++--- src/echo-cancel/processor.h | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 9122970..6c31737 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.11 +Version: 15.0.12 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 8aa456c..d27a2cd 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -150,8 +150,8 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { } static pa_usec_t get_round_trip_latency(struct userdata *u) { - pa_usec_t sink_latency; - pa_usec_t source_latency; + pa_usec_t sink_latency = 0ULL; + pa_usec_t source_latency = 0ULL; pa_assert(u); pa_assert(u->sink); @@ -272,19 +272,6 @@ static int update_state_by_source(struct userdata *u, bool enable) { return 0; } -static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { - pa_source_output *o = NULL; - - o = find_source_output_by_flags(s); - if (!o) { - pa_log_error("Can't find aec source-output"); - return; - } - - if (pa_processor_push_data(o->thread_info.processor, chunk) < 0) - pa_log_error("Failed to push reference data"); -} - static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) { int ret = -1; @@ -336,8 +323,20 @@ static int process_msg( break; case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: - if (u->enable_in_thread) - pa_source_push_echo(u->source, chunk); + if (u->enable_in_thread) { + pa_source_output *o = NULL; + + pa_assert(u->source); + + o = find_source_output_by_flags(u->source); + if (!o) { + pa_log_error("Can't find aec source-output"); + break; + } + + if (pa_processor_push_reference(o->thread_info.processor, chunk) < 0) + pa_log_error("Failed to push reference data"); + } break; case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE: { diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index a97b99c..5b8916b 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -345,7 +345,7 @@ int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchun pa_assert(ochunk); if ((r = pa_memblockq_push(processor->output_memblockq, chunk)) < 0) { - pa_log_error("Failed to push chunk to echo memblockq"); + pa_log_error("Failed to push chunk to reference memblockq"); return r; } @@ -410,12 +410,12 @@ int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { } if ((r = pa_memblockq_push(processor->reference_memblockq, chunk)) < 0) - pa_log_error("Failed to push chunk to echo memblockq"); + pa_log_error("Failed to push chunk to reference memblockq"); if (processor->resampler) pa_memblock_unref(chunk->memblock); - pa_log_debug("Pushed echo data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", + pa_log_debug("Pushed reference data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", chunk->length, pa_bytes_to_usec(chunk->length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, pa_memblockq_get_nblocks(processor->reference_memblockq), diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h index fa77e87..ea07143 100644 --- a/src/echo-cancel/processor.h +++ b/src/echo-cancel/processor.h @@ -55,7 +55,7 @@ int pa_processor_bind_reference(pa_processor *processor, pa_channel_map *reference_chmap); int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency); int pa_processor_process(pa_processor *processor, pa_memchunk *rec, pa_memchunk *out); -int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk); +int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk); void pa_processor_flush(pa_processor *processor); int pa_processor_free(pa_processor *processor); pa_processor_method_t pa_processor_get_method(const char *requset_method, const char *default_method); -- 2.7.4 From fb4e3bb1fe76f82d3839d19d2a95b7d73042a642 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 14 Apr 2022 15:00:24 +0900 Subject: [PATCH 13/16] tizenaudio-sink/source: Fix crash when pulseaudio exit * Fix pulseaudio crash (pactl exit) * symbol mismatching(pa_processor_push_reference) * aarch64 build warning [Version] 15.0.13 [Issue Type] Bug Change-Id: I0d7f0d44a95cd9667746529ed1f03151ec92e64a Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/processor.c | 2 +- src/module-sound-player.c | 2 +- src/module-tizenaudio-sink.c | 3 +++ src/module-tizenaudio-sink2.c | 3 +++ src/module-tizenaudio-source.c | 3 +++ src/module-tizenaudio-source2.c | 3 +++ 7 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 6c31737..d1d68f6 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.12 +Version: 15.0.13 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index 5b8916b..0052ab8 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -397,7 +397,7 @@ int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchun return r; } -int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { +int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk) { pa_memchunk ochunk; int r; diff --git a/src/module-sound-player.c b/src/module-sound-player.c index ce505a8..cf55d04 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -753,7 +753,7 @@ static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io else pa_log_error("Invalid method!!!"); } else { - pa_log_warn("Fail to read, retry_count(%d), read sum(%d), err(%s)", retry_count, read_sum, pa_cstrerror(errno)); + pa_log_warn("Fail to read, retry_count(%d), read sum(%zu), err(%s)", retry_count, read_sum, pa_cstrerror(errno)); } } diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 6c56de9..0ea7846 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -796,5 +796,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index a81ac70..36675c3 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -654,5 +654,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-source.c b/src/module-tizenaudio-source.c index 4beabf5..e3ed819 100644 --- a/src/module-tizenaudio-source.c +++ b/src/module-tizenaudio-source.c @@ -700,5 +700,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 5bd3a61..a47dbbe 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -619,5 +619,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } -- 2.7.4 From 4fc34cf8fb2339ba0936e6bd3dce38cbb16f2807 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Fri, 29 Apr 2022 10:42:16 +0900 Subject: [PATCH 14/16] tizenaudio-echo-cancel: Fix coverity defect (Dereference null return value) [Version] 15.0.14 [Issue Type] Coverity Change-Id: I69359a1fc8dfd988ab37cc24a9cc4f2f287396eb Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index d1d68f6..21f6ccc 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.13 +Version: 15.0.14 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index d27a2cd..f9e13c0 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -146,6 +146,9 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { break; } + if (!o) + pa_log_error("Failed to find AEC source-output"); + return o ? o : NULL; } @@ -234,10 +237,8 @@ static void set_echo_cancel_state(struct userdata *u, bool enable) { return; o = find_source_output_by_flags(u->source); - if (!o) { - pa_log_error("Failed to find EC source-output"); + if (!o) return; - } broadcast_echo_cancel_state(u, o, enable); u->enable = enable; @@ -302,6 +303,11 @@ static int process_msg( pa_usec_t latency; o = find_source_output_by_flags(u->source); + if (!o) { + u->triggered = false; + return 0; + } + o->post_process = post_process; latency = get_round_trip_latency(u); @@ -329,10 +335,8 @@ static int process_msg( pa_assert(u->source); o = find_source_output_by_flags(u->source); - if (!o) { - pa_log_error("Can't find aec source-output"); + if (!o) break; - } if (pa_processor_push_reference(o->thread_info.processor, chunk) < 0) pa_log_error("Failed to push reference data"); -- 2.7.4 From bcc7b8b7639a80d1c2541ed6ca0dcd1692095c3f Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 30 Nov 2021 11:19:34 +0900 Subject: [PATCH 15/16] Separate tizenaudio-sink2 and module-tizenaudio-sink2 pa_tizenaudio_source2_new/sink2_new were added in order to support usb device playback/capture. These function will be called in module-tizenaudio-sink2/source2 and module-alsa-card. [Version] 15.0.15 [Issue Type] Improvement Change-Id: Id1d2d852f4c8982ac4c622cd2d326589ca46ca69 Signed-off-by: Jaechul Lee --- Makefile.am | 14 +- packaging/pulseaudio-modules-tizen.spec | 3 +- src/module-tizenaudio-sink2.c | 585 +------------------------------ src/module-tizenaudio-source2.c | 550 +----------------------------- src/tizenaudio-sink2.c | 585 +++++++++++++++++++++++++++++++ src/tizenaudio-sink2.h | 38 +++ src/tizenaudio-source2.c | 587 ++++++++++++++++++++++++++++++++ src/tizenaudio-source2.h | 38 +++ 8 files changed, 1284 insertions(+), 1116 deletions(-) create mode 100644 src/tizenaudio-sink2.c create mode 100644 src/tizenaudio-sink2.h create mode 100644 src/tizenaudio-source2.c create mode 100644 src/tizenaudio-source2.h diff --git a/Makefile.am b/Makefile.am index e61e057..ed2843f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,7 @@ pulsemodlibexec_LTLIBRARIES = \ libhal-interface.la \ libprocessor.la \ libcommunicator.la \ + libtizenaudio-util.la \ module-tizenaudio-sink.la \ module-tizenaudio-source.la \ module-tizenaudio-sink2.la \ @@ -78,14 +79,19 @@ module_tizenaudio_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_source_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la module_tizenaudio_source_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source -module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c src/echo-cancel/echo-cancel-def.h +libtizenaudio_util_la_SOURCES = src/tizenaudio-sink2.c src/tizenaudio-sink2.h src/tizenaudio-source2.c src/tizenaudio-source2.h src/echo-cancel/echo-cancel-def.h +libtizenaudio_util_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version +libtizenaudio_util_la_LIBADD = $(AM_LIBADD) $(PA_LIBS) libhal-interface.la +libtizenaudio_util_la_CFLAGS = $(MODULE_CFLAGS) + +module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c module_tizenaudio_sink2_la_LDFLAGS = $(MODULE_LDFLAGS) -module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la +module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libtizenaudio-util.la module_tizenaudio_sink2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_sink2 -module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c src/echo-cancel/echo-cancel-def.h +module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c module_tizenaudio_source2_la_LDFLAGS = $(MODULE_LDFLAGS) -module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la +module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libtizenaudio-util.la module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source2 libprocessor_la_SOURCES = \ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 21f6ccc..0dccc40 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.14 +Version: 15.0.15 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -91,6 +91,7 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-publish.so %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-echo-cancel.so %{_libdir}/pulse-%{module_ver}/modules/libprocessor.so +%{_libdir}/pulse-%{module_ver}/modules/libtizenaudio-util.so %{_libdir}/pulse-%{module_ver}/modules/libhal-interface.so %{_libdir}/pulse-%{module_ver}/modules/libcommunicator.so %{_tmpfilesdir}/pulseaudio.conf diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index 36675c3..0861262 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -23,30 +23,9 @@ #include #endif -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include "hal-interface.h" -#include "echo-cancel/echo-cancel-def.h" +#include "tizenaudio-sink2.h" PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Sink2"); @@ -63,41 +42,6 @@ PA_MODULE_USAGE( "fragments= " "fragment_size= "); -#define DEFAULT_SINK_NAME "tizenaudio-sink2" - -#define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 -#define DEFAULT_FRAGMENTS 4 - -struct userdata { - pa_core *core; - pa_module *module; - pa_sink *sink; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - pa_usec_t timestamp; - - void *pcm_handle; - uint32_t nfrags; - uint32_t frag_size; - - char* card; - char* device; - bool first; - bool echo_on; - - pa_rtpoll_item *rtpoll_item; - - uint64_t write_count; - pa_hal_interface *hal_interface; - - pa_msgobject *ec_object; - pa_asyncmsgq *ec_asyncmsgq; - pa_rtpoll_item *ec_poll_item; -}; - static const char* const valid_modargs[] = { "sink_name", "sink_properties", @@ -111,496 +55,19 @@ static const char* const valid_modargs[] = { NULL }; -static int build_pollfd(struct userdata *u) { - int32_t ret; - struct pollfd *pollfd; - int fd = -1; - - pa_assert(u); - pa_assert(u->pcm_handle); - pa_assert(u->rtpoll); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); - if (ret < 0 || fd < 0) { - pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); - return -1; - } - pollfd->fd = fd; - pollfd->events = POLLOUT | POLLERR | POLLNVAL; - - return 0; -} - -/* Called from IO context */ -static int suspend(struct userdata *u) { - int32_t ret; - pa_assert(u); - pa_assert(u->pcm_handle); - - ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - if (ret) { - pa_log_error("Error closing PCM device %x", ret); - } - u->pcm_handle = NULL; - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -/* Called from IO context */ -static int unsuspend(struct userdata *u) { - pa_sample_spec sample_spec; - int32_t ret; - size_t frame_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - sample_spec = u->sink->sample_spec; - frame_size = pa_frame_size(&sample_spec); - if (frame_size == 0) { - pa_log_error("Unexpected frame size zero!"); - goto fail; - } - - ret = pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_OUT, - &sample_spec, - u->frag_size / frame_size, - u->nfrags, - (void **)&u->pcm_handle); - if (ret) { - pa_log_error("Error opening PCM device %x", ret); - goto fail; - } - - if (build_pollfd(u) < 0) - goto fail; - - u->write_count = 0; - u->first = true; - u->timestamp = pa_rtclock_now(); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - u->pcm_handle = NULL; - } - return -PA_ERR_IO; -} - -/* Called from the IO thread. */ -static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - int r; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* It may be that only the suspend cause is changing, in which case there's - * nothing to do. */ - if (new_state == s->thread_info.state) - return 0; - - switch (new_state) { - case PA_SINK_SUSPENDED: { - pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); - if ((r = suspend(u)) < 0) - return r; - break; - } - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: { - if (s->thread_info.state == PA_SINK_INIT) { - if (build_pollfd(u) < 0) - return -PA_ERR_IO; - } - - if (s->thread_info.state == PA_SINK_SUSPENDED) { - if ((r = unsuspend(u)) < 0) - return r; - } - break; - } - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - case PA_SINK_INVALID_STATE: - break; - } - - return 0; -} - -static int sink_process_msg( - pa_msgobject *o, - int code, - void *data, - int64_t offset, - pa_memchunk *chunk) { - - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - case PA_SINK_MESSAGE_SET_AEC_STATE: { - u->echo_on = !!data; - pa_log_info("EC state changed (%d)", u->echo_on); - return 0; - } - case PA_SINK_MESSAGE_GET_LATENCY: { - int64_t r = 0; - - if (u->pcm_handle) - r = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - pa_rtclock_now(); - - *((int64_t *) data) = r; - - return 0; - } - case PA_SINK_MESSAGE_REBUILD_RTPOLL: { - struct arguments { - pa_msgobject *o; - pa_asyncmsgq *q; - } *args; - - args = (struct arguments *)data; - - if (args) { - u->ec_object = args->o; - u->ec_asyncmsgq = args->q; - u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); - } else { - pa_rtpoll_item_free(u->ec_poll_item); - u->ec_poll_item = NULL; - u->ec_object = NULL; - } - - return 0; - } - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -static void process_rewind(struct userdata *u) { -#if 1 - /* Rewind not supported */ - pa_sink_process_rewind(u->sink, 0); -#else - size_t rewind_nbytes, in_buffer; - pa_usec_t delay; - - pa_assert(u); - - rewind_nbytes = u->sink->thread_info.rewind_nbytes; - - if (!PA_SINK_IS_OPENED(u->sink->thread_info.state) || rewind_nbytes <= 0) - goto do_nothing; - - pa_log_debug("Requested to rewind %lu bytes.", (unsigned long)rewind_nbytes); - - if (u->timestamp <= now) - goto do_nothing; - - delay = u->timestamp - now; - in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); - - if (in_buffer <= 0) - goto do_nothing; - - if (rewind_nbytes > in_buffer) - rewind_nbytes = in_buffer; - - pa_sink_process_rewind(u->sink, rewind_nbytes); - u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); - - pa_log_debug("Rewound %lu bytes.", (unsigned long)rewind_nbytes); - return; - -do_nothing: - pa_sink_process_rewind(u->sink, 0); -#endif -} - -static int process_render(struct userdata *u) { - void *p; - size_t frame_size = pa_frame_size(&u->sink->sample_spec); - size_t frames_to_write = u->frag_size / frame_size; - uint32_t avail = 0; - pa_memchunk chunk; - - pa_assert(u); - - pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); - - if (frames_to_write > avail) - return 0; - - pa_sink_render_full(u->sink, u->frag_size, &chunk); - p = pa_memblock_acquire(chunk.memblock); - - if (pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write)) { - pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); - return -1; - } - - pa_memblock_release(chunk.memblock); - - if (u->echo_on) { - pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL); - } - - pa_memblock_unref(chunk.memblock); - - u->write_count += chunk.length; - - return 0; -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_thread_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - - u->timestamp = pa_rtclock_now(); - - for (;;) { - int ret; - - if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) - process_rewind(u); - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - if (process_render(u)) - goto fail; - - if (u->first) { - pa_log_info("Starting playback."); - pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); - u->first = false; - } - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - struct pollfd *pollfd; - if (u->rtpoll_item) { - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - revents = pollfd->revents; - if (revents & ~POLLOUT) { - pa_log_debug("Poll error 0x%x occured, try recover.", revents); - pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); - u->first = true; - revents = 0; - } - } - } - } - -fail: - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -int pa__init(pa_module*m) { - struct userdata *u = NULL; - pa_sample_spec ss; - pa_channel_map map; +int pa__init(pa_module *m) { pa_modargs *ma = NULL; - pa_sink_new_data data; - uint32_t alternate_sample_rate; - 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); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments."); + pa_log("Failed to parse module arguments"); goto fail; } - ss = m->core->default_sample_spec; - map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log_error("Invalid sample format specification or channel map"); + if (!(m->userdata = pa_tizenaudio_sink2_new(m, ma, __FILE__))) goto fail; - } - - alternate_sample_rate = m->core->alternate_sample_rate; - if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { - pa_log_error("Failed to parse alternate sample rate"); - goto fail; - } - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - u->first = true; - u->timestamp = 0ULL; - u->hal_interface = pa_hal_interface_get(u->core); - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); - goto fail; - } - - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); - u->nfrags = DEFAULT_FRAGMENTS; - if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || - pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { - pa_log_error("fragment_size or fragments are invalid."); - goto fail; - } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); - - pa_sink_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); - pa_sink_new_data_set_sample_spec(&data, &ss); - pa_sink_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio sink")); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); - pa_proplist_sets(data.proplist, "tizen.card", u->card); - pa_proplist_sets(data.proplist, "tizen.device", u->device); - pa_proplist_sets(data.proplist, "tizen.version", "2"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - 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); - goto fail; - } - - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); - pa_sink_new_data_done(&data); - - if (!u->sink) { - pa_log_error("Failed to create sink object."); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - u->sink->userdata = u; - - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_OUT, - &u->sink->sample_spec, - u->frag_size / pa_frame_size(&u->sink->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - - pa_sink_set_max_request(u->sink, buffer_size); - pa_sink_set_max_rewind(u->sink, buffer_size); - - if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) { - pa_log_error("Failed to create thread."); - goto fail; - } - pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(buffer_size, &ss)); - pa_sink_put(u->sink); pa_modargs_free(ma); return 0; @@ -610,52 +77,24 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } int pa__get_n_used(pa_module *m) { - struct userdata *u; + pa_sink *s; pa_assert(m); - pa_assert_se((u = m->userdata)); + pa_assert_se((s = m->userdata)); - return pa_sink_linked_by(u->sink); + return pa_sink_linked_by(s); } -void pa__done(pa_module*m) { - struct userdata *u; +void pa__done(pa_module *m) { + pa_sink *s; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->sink) - pa_sink_unref(u->sink); - - pa_xfree(u->card); - pa_xfree(u->device); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->pcm_handle) { - pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - } - - if (u->hal_interface) - pa_hal_interface_unref(u->hal_interface); - - pa_xfree(u); + if ((s = m->userdata)) + pa_tizenaudio_sink2_free(s); } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index a47dbbe..1df595e 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -23,30 +23,9 @@ #include #endif -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include "hal-interface.h" -#include "echo-cancel/echo-cancel-def.h" +#include "tizenaudio-source2.h" PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Source2"); @@ -63,43 +42,6 @@ PA_MODULE_USAGE( "fragments= " "fragment_size= "); -#define DEFAULT_SOURCE_NAME "tizenaudio-source2" - -#define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 -#define DEFAULT_FRAGMENTS 4 - -struct userdata { - pa_core *core; - pa_module *module; - pa_source *source; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - void *pcm_handle; - uint32_t nfrags; - uint32_t frag_size; - - pa_usec_t timestamp; - - char* card; - char* device; - bool first; - bool echo_on; - - pa_rtpoll_item *rtpoll_item; - - uint64_t read_count; - pa_usec_t latency_time; - pa_hal_interface *hal_interface; - - pa_msgobject *ec_object; - pa_asyncmsgq *ec_asyncmsgq; - pa_rtpoll_item *ec_poll_item; -}; - static const char* const valid_modargs[] = { "source_name", "source_properties", @@ -113,459 +55,19 @@ static const char* const valid_modargs[] = { NULL }; -static int build_pollfd(struct userdata *u) { - int32_t ret; - struct pollfd *pollfd; - int fd = -1; - - pa_assert(u); - pa_assert(u->pcm_handle); - pa_assert(u->rtpoll); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); - if (ret < 0 || fd < 0) { - pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); - return -1; - } - pollfd->fd = fd; - pollfd->events = POLLIN | POLLERR | POLLNVAL; - - return 0; -} - -/* Called from IO context */ -static int suspend(struct userdata *u) { - int32_t ret; - pa_assert(u); - pa_assert(u->pcm_handle); - - ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - if (ret) { - pa_log_error("Error closing PCM device %x", ret); - } - u->pcm_handle = NULL; - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -/* Called from IO context */ -static int unsuspend(struct userdata *u) { - pa_sample_spec sample_spec; - int32_t ret; - size_t frame_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - sample_spec = u->source->sample_spec; - frame_size = pa_frame_size(&sample_spec); - if (frame_size == 0) { - pa_log_error("Unexpected frame size zero!"); - goto fail; - } - - ret = pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_IN, - &sample_spec, - u->frag_size / frame_size, - u->nfrags, - (void **)&u->pcm_handle); - if (ret) { - pa_log_error("Error opening PCM device %x", ret); - goto fail; - } - - if (build_pollfd(u) < 0) - goto fail; - - u->read_count = 0; - u->first = true; - u->timestamp = pa_rtclock_now(); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - u->pcm_handle = NULL; - } - return -PA_ERR_IO; -} - -/* Called from the IO thread. */ -static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - int r; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* It may be that only the suspend cause is changing, in which case there's - * nothing more to do. */ - if (new_state == s->thread_info.state) - return 0; - - switch (new_state) { - case PA_SOURCE_SUSPENDED: { - pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); - if ((r = suspend(u)) < 0) - return r; - break; - } - - case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: { - if (s->thread_info.state == PA_SOURCE_INIT) { - if (build_pollfd(u) < 0) - return -PA_ERR_IO; - } - - if (s->thread_info.state == PA_SOURCE_SUSPENDED) { - if ((r = unsuspend(u)) < 0) - return r; - } - break; - } - - case PA_SOURCE_UNLINKED: - case PA_SOURCE_INIT: - case PA_SOURCE_INVALID_STATE: - break; - } - - return 0; -} - -static int source_process_msg( - pa_msgobject *o, - int code, - void *data, - int64_t offset, - pa_memchunk *chunk) { - - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - case PA_SOURCE_MESSAGE_SET_AEC_STATE: { - u->echo_on = !!data; - pa_log_info("EC state changed (%d)", u->echo_on); - return 0; - } - case PA_SOURCE_MESSAGE_GET_LATENCY: { - uint64_t r = 0; - - if (u->pcm_handle) - r = pa_rtclock_now() - (u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec)); - - *((int64_t *) data) = r; - - return 0; - } - case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { - struct arguments { - pa_msgobject *o; - pa_asyncmsgq *q; - } *args; - - args = (struct arguments *)data; - - if (args) { - u->ec_object = args->o; - u->ec_asyncmsgq = args->q; - u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); - } else { - pa_rtpoll_item_free(u->ec_poll_item); - u->ec_poll_item = NULL; - u->ec_object = NULL; - } - - return 0; - } - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -static int process_render(struct userdata *u) { - void *p; - size_t frame_size = pa_frame_size(&u->source->sample_spec); - size_t frames_to_read = u->frag_size / frame_size; - uint32_t avail = 0; - pa_memchunk chunk; - - pa_assert(u); - - /* Fill the buffer up the latency size */ - - pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); - if (frames_to_read > avail) { - pa_log_debug("not enough avail size. frames_to_read(%zu), avail(%d)", frames_to_read, avail); - return 0; - } - - chunk.length = u->frag_size; - chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); - - p = pa_memblock_acquire(chunk.memblock); - if (pa_hal_interface_pcm_read(u->hal_interface, u->pcm_handle, p, (uint32_t)frames_to_read)) { - pa_log_error("failed to read pcm. p(%p), size(%zu)", p, frames_to_read); - return -1; - } - pa_memblock_release(chunk.memblock); - - chunk.index = 0; - chunk.length = (size_t)frames_to_read * frame_size; - - if (u->echo_on) { - pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL); - } else { - pa_source_post(u->source, &chunk); - } - - pa_memblock_unref(chunk.memblock); - - u->read_count += chunk.length; - - return 0; -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_thread_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - - u->timestamp = pa_rtclock_now(); - - for (;;) { - int ret; - - /* Render some data and drop it immediately */ - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - if (process_render(u)) - goto fail; - - if (u->first) { - pa_log_info("Starting capture."); - pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); - u->first = false; - } - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - struct pollfd *pollfd; - if (u->rtpoll_item) { - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - revents = pollfd->revents; - if (revents & ~POLLIN) { - pa_log_debug("Poll error 0x%x occured, try recover.", revents); - pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); - u->first = true; - revents = 0; - } - } - } - } - -fail: - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -int pa__init(pa_module*m) { - struct userdata *u = NULL; - pa_sample_spec ss; - pa_channel_map map; +int pa__init(pa_module *m) { pa_modargs *ma = NULL; - pa_source_new_data data; - uint32_t alternate_sample_rate; - 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); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments."); + pa_log("Failed to parse module arguments"); goto fail; } - ss = m->core->default_sample_spec; - map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log_error("Invalid sample format specification or channel map"); + if (!(m->userdata = pa_tizenaudio_source2_new(m, ma, __FILE__))) goto fail; - } - - alternate_sample_rate = m->core->alternate_sample_rate; - if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { - pa_log_error("Failed to parse alternate sample rate"); - goto fail; - } - m->userdata = u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - u->first = true; - u->hal_interface = pa_hal_interface_get(u->core); - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); - goto fail; - } - - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); - u->nfrags = DEFAULT_FRAGMENTS; - if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || - pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { - pa_log_error("fragment_size or fragments are invalid."); - goto fail; - } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); - - pa_source_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); - pa_source_new_data_set_sample_spec(&data, &ss); - pa_source_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio source")); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); - pa_proplist_sets(data.proplist, "tizen.card", u->card); - pa_proplist_sets(data.proplist, "tizen.device", u->device); - pa_proplist_sets(data.proplist, "tizen.version", "2"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - 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); - goto fail; - } - - u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); - - if (!u->source) { - pa_log_error("Failed to create source object."); - goto fail; - } - - u->source->parent.process_msg = source_process_msg; - u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; - u->source->userdata = u; - - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_IN, - &u->source->sample_spec, - u->frag_size / pa_frame_size(&u->source->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } - - pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); - pa_source_set_rtpoll(u->source, u->rtpoll); - - u->timestamp = 0ULL; - - if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) { - pa_log_error("Failed to create thread."); - goto fail; - } - pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(buffer_size, &ss)); - pa_source_put(u->source); pa_modargs_free(ma); return 0; @@ -575,52 +77,24 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } int pa__get_n_used(pa_module *m) { - struct userdata *u; + pa_source *s; pa_assert(m); - pa_assert_se((u = m->userdata)); + pa_assert_se((s = m->userdata)); - return pa_source_linked_by(u->source); + return pa_source_linked_by(s); } -void pa__done(pa_module*m) { - struct userdata *u; +void pa__done(pa_module *m) { + pa_source *s; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->source) - pa_source_unlink(u->source); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->source) - pa_source_unref(u->source); - - pa_xfree(u->card); - pa_xfree(u->device); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->pcm_handle) { - pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - } - - if (u->hal_interface) - pa_hal_interface_unref(u->hal_interface); - - pa_xfree(u); + if ((s = m->userdata)) + pa_tizenaudio_source2_free(s); } diff --git a/src/tizenaudio-sink2.c b/src/tizenaudio-sink2.c new file mode 100644 index 0000000..a7ffd93 --- /dev/null +++ b/src/tizenaudio-sink2.c @@ -0,0 +1,585 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" + +#define DEFAULT_SINK_NAME "tizenaudio-sink2" + +#define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_usec_t timestamp; + + void *pcm_handle; + uint32_t nfrags; + uint32_t frag_size; + + char* card; + char* device; + bool first; + bool echo_on; + + pa_rtpoll_item *rtpoll_item; + + uint64_t write_count; + pa_hal_interface *hal_interface; + + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; +}; + +static void userdata_free(struct userdata *u); + +static int build_pollfd(struct userdata *u) { + int32_t ret; + struct pollfd *pollfd; + int fd = -1; + + pa_assert(u); + pa_assert(u->pcm_handle); + pa_assert(u->rtpoll); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); + if (ret < 0 || fd < 0) { + pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); + return -1; + } + pollfd->fd = fd; + pollfd->events = POLLOUT | POLLERR | POLLNVAL; + + return 0; +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int32_t ret; + pa_assert(u); + pa_assert(u->pcm_handle); + + ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + if (ret) { + pa_log_error("Error closing PCM device %x", ret); + } + u->pcm_handle = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +/* Called from IO context */ +static int unsuspend(struct userdata *u) { + pa_sample_spec sample_spec; + int32_t ret; + size_t frame_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + sample_spec = u->sink->sample_spec; + frame_size = pa_frame_size(&sample_spec); + if (frame_size == 0) { + pa_log_error("Unexpected frame size zero!"); + goto fail; + } + + ret = pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_OUT, + &sample_spec, + u->frag_size / frame_size, + u->nfrags, + (void **)&u->pcm_handle); + if (ret) { + pa_log_error("Error opening PCM device %x", ret); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + u->write_count = 0; + u->first = true; + u->timestamp = pa_rtclock_now(); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + u->pcm_handle = NULL; + } + return -PA_ERR_IO; +} + +/* Called from the IO thread. */ +static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + struct userdata *u; + int r; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + /* It may be that only the suspend cause is changing, in which case there's + * nothing to do. */ + if (new_state == s->thread_info.state) + return 0; + + switch (new_state) { + case PA_SINK_SUSPENDED: { + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); + if ((r = suspend(u)) < 0) + return r; + break; + } + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: { + if (s->thread_info.state == PA_SINK_INIT) { + if (build_pollfd(u) < 0) + return -PA_ERR_IO; + } + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + break; + } + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + break; + } + + return 0; +} + +static int sink_process_msg( + pa_msgobject *o, + int code, + void *data, + int64_t offset, + pa_memchunk *chunk) { + + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + case PA_SINK_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } + case PA_SINK_MESSAGE_GET_LATENCY: { + int64_t r = 0; + + if (u->pcm_handle) + r = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - pa_rtclock_now(); + + *((int64_t *) data) = r; + + return 0; + } + case PA_SINK_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void process_rewind(struct userdata *u) { + pa_sink_process_rewind(u->sink, 0); +} + +static int process_render(struct userdata *u) { + void *p; + size_t frame_size = pa_frame_size(&u->sink->sample_spec); + size_t frames_to_write = u->frag_size / frame_size; + uint32_t avail = 0; + pa_memchunk chunk; + + pa_assert(u); + + pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); + if (frames_to_write > avail) + return 0; + + pa_sink_render_full(u->sink, u->frag_size, &chunk); + p = pa_memblock_acquire(chunk.memblock); + + if (pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write)) { + pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); + return -1; + } + + pa_memblock_release(chunk.memblock); + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL); + } + + pa_memblock_unref(chunk.memblock); + + u->write_count += chunk.length; + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_thread_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + process_rewind(u); + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (process_render(u)) + goto fail; + + if (u->first) { + pa_log_info("Starting playback."); + pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); + u->first = false; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + struct pollfd *pollfd; + if (u->rtpoll_item) { + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + revents = pollfd->revents; + if (revents & ~POLLOUT) { + pa_log_debug("Poll error 0x%x occured, try recover.", revents); + pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); + u->first = true; + revents = 0; + } + } + } + } + +fail: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static int parse_to_get_card(const char *modarg_device, char *card) { + const char *name_p; + char *card_p; + + if (!strchr(modarg_device, ',')) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + name_p = modarg_device; + card_p = card; + while (*name_p != ',') + *(card_p++) = *(name_p++); + *card_p = '\0'; + + return 0; +} + +static int parse_to_get_device(const char *modarg_device, char *device) { + const char *comma_p; + char *device_p; + + if (!(comma_p = strchr(modarg_device, ','))) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + comma_p++; + device_p = device; + while (*comma_p != '\0') + *(device_p++) = *(comma_p++); + *device_p = '\0'; + + return 0; +} + +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_sink_new_data data; + uint32_t alternate_sample_rate; + 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); + pa_assert(ma); + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log_error("Invalid sample format specification or channel map"); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log_error("Failed to parse alternate sample rate"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->first = true; + u->timestamp = 0ULL; + u->hal_interface = pa_hal_interface_get(u->core); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { + pa_log_error("device is invalid"); + goto fail; + } + + if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { + pa_log_error("failed to parse device module argument, %s", modarg_device); + goto fail; + } + + u->card = pa_xstrdup(card); + u->device = pa_xstrdup(device); + + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; + if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || + pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { + pa_log_error("fragment_size or fragments are invalid."); + goto fail; + } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + pa_sink_new_data_init(&data); + data.driver = driver; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio sink2")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + pa_proplist_sets(data.proplist, "tizen.card", u->card); + pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(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); + goto fail; + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log_error("Failed to create sink object."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; + u->sink->userdata = u; + + if (pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_OUT, + &u->sink->sample_spec, + u->frag_size / pa_frame_size(&u->sink->sample_spec), + u->nfrags, + (void **)&u->pcm_handle)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + pa_sink_set_max_request(u->sink, buffer_size); + pa_sink_set_max_rewind(u->sink, buffer_size); + + if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) { + pa_log_error("Failed to create thread."); + goto fail; + } + + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(buffer_size, &ss)); + pa_sink_put(u->sink); + + return u->sink; + +fail: + userdata_free(u); + return NULL; +} + +static void userdata_free(struct userdata *u) { + pa_assert(u); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + pa_xfree(u->card); + pa_xfree(u->device); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pcm_handle) { + pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + } + + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + + pa_xfree(u); +} + +void pa_tizenaudio_sink2_free(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se((u = s->userdata)); + + userdata_free(u); +} diff --git a/src/tizenaudio-sink2.h b/src/tizenaudio-sink2.h new file mode 100644 index 0000000..dd9e9e2 --- /dev/null +++ b/src/tizenaudio-sink2.h @@ -0,0 +1,38 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifndef footizenaudiosink2hfoo +#define footizenaudiosink2hfoo + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver); + +void pa_tizenaudio_sink2_free(pa_sink *s); + +#endif + diff --git a/src/tizenaudio-source2.c b/src/tizenaudio-source2.c new file mode 100644 index 0000000..8ddfa2f --- /dev/null +++ b/src/tizenaudio-source2.c @@ -0,0 +1,587 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" + +#define DEFAULT_SOURCE_NAME "tizenaudio-source2" + +#define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + void *pcm_handle; + uint32_t nfrags; + uint32_t frag_size; + + pa_usec_t timestamp; + + char* card; + char* device; + bool first; + bool echo_on; + + pa_rtpoll_item *rtpoll_item; + + uint64_t read_count; + pa_usec_t latency_time; + pa_hal_interface *hal_interface; + + /* for echo-cancel */ + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; +}; + +static void userdata_free(struct userdata *u); + +static int build_pollfd(struct userdata *u) { + int32_t ret; + struct pollfd *pollfd; + int fd = -1; + + pa_assert(u); + pa_assert(u->pcm_handle); + pa_assert(u->rtpoll); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); + if (ret < 0 || fd < 0) { + pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); + return -1; + } + pollfd->fd = fd; + pollfd->events = POLLIN | POLLERR | POLLNVAL; + + return 0; +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int32_t ret; + pa_assert(u); + pa_assert(u->pcm_handle); + + ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + if (ret) { + pa_log_error("Error closing PCM device %x", ret); + } + u->pcm_handle = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +/* Called from IO context */ +static int unsuspend(struct userdata *u) { + pa_sample_spec sample_spec; + int32_t ret; + size_t frame_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + sample_spec = u->source->sample_spec; + frame_size = pa_frame_size(&sample_spec); + if (frame_size == 0) { + pa_log_error("Unexpected frame size zero!"); + goto fail; + } + + ret = pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_IN, + &sample_spec, + u->frag_size / frame_size, + u->nfrags, + (void **)&u->pcm_handle); + if (ret) { + pa_log_error("Error opening PCM device %x", ret); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + u->read_count = 0; + u->first = true; + u->timestamp = pa_rtclock_now(); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + u->pcm_handle = NULL; + } + return -PA_ERR_IO; +} + +/* Called from the IO thread. */ +static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + struct userdata *u; + int r; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + /* It may be that only the suspend cause is changing, in which case there's + * nothing more to do. */ + if (new_state == s->thread_info.state) + return 0; + + switch (new_state) { + case PA_SOURCE_SUSPENDED: { + pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); + if ((r = suspend(u)) < 0) + return r; + break; + } + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: { + if (s->thread_info.state == PA_SOURCE_INIT) { + if (build_pollfd(u) < 0) + return -PA_ERR_IO; + } + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + break; + } + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + case PA_SOURCE_INVALID_STATE: + break; + } + + return 0; +} + +static int source_process_msg( + pa_msgobject *o, + int code, + void *data, + int64_t offset, + pa_memchunk *chunk) { + + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + case PA_SOURCE_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } + case PA_SOURCE_MESSAGE_GET_LATENCY: { + uint64_t r = 0; + + if (u->pcm_handle) + r = pa_rtclock_now() - (u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec)); + + *((int64_t *) data) = r; + + return 0; + } + case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int process_render(struct userdata *u) { + void *p; + size_t frame_size = pa_frame_size(&u->source->sample_spec); + size_t frames_to_read = u->frag_size / frame_size; + uint32_t avail = 0; + pa_memchunk chunk; + + pa_assert(u); + + /* Fill the buffer up the latency size */ + + pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); + if (frames_to_read > avail) { + pa_log_debug("not enough avail size. frames_to_read(%zu), avail(%u)", frames_to_read, avail); + return 0; + } + + chunk.length = u->frag_size; + chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + if (pa_hal_interface_pcm_read(u->hal_interface, u->pcm_handle, p, (uint32_t)frames_to_read)) { + pa_log_error("failed to read pcm. p(%p), size(%zu)", p, frames_to_read); + return -1; + } + pa_memblock_release(chunk.memblock); + + chunk.index = 0; + chunk.length = (size_t)frames_to_read * frame_size; + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL); + } else { + pa_source_post(u->source, &chunk); + } + + pa_memblock_unref(chunk.memblock); + + u->read_count += chunk.length; + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_thread_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + /* Render some data and drop it immediately */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + if (process_render(u)) + goto fail; + + if (u->first) { + pa_log_info("Starting capture."); + pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); + u->first = false; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + struct pollfd *pollfd; + if (u->rtpoll_item) { + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + revents = pollfd->revents; + if (revents & ~POLLIN) { + pa_log_debug("Poll error 0x%x occured, try recover.", revents); + pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); + u->first = true; + revents = 0; + } + } + } + } + +fail: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static int parse_to_get_card(const char *modarg_device, char *card) { + const char *name_p; + char *card_p; + + if (!strchr(modarg_device, ',')) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + name_p = modarg_device; + card_p = card; + while (*name_p != ',') + *(card_p++) = *(name_p++); + *card_p = '\0'; + + return 0; +} + +static int parse_to_get_device(const char *modarg_device, char *device) { + const char *comma_p; + char *device_p; + + if (!(comma_p = strchr(modarg_device, ','))) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + comma_p++; + device_p = device; + while (*comma_p != '\0') + *(device_p++) = *(comma_p++); + *device_p = '\0'; + + return 0; +} + +pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_source_new_data data; + uint32_t alternate_sample_rate; + 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); + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log_error("Invalid sample format specification or channel map"); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log_error("Failed to parse alternate sample rate"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->first = true; + u->hal_interface = pa_hal_interface_get(u->core); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { + pa_log_error("device is invalid"); + goto fail; + } + + if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { + pa_log_error("failed to parse device module argument, %s", modarg_device); + goto fail; + } + + u->card = pa_xstrdup(card); + u->device = pa_xstrdup(device); + + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; + if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || + pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { + pa_log_error("fragment_size or fragments are invalid."); + goto fail; + } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + pa_source_new_data_init(&data); + data.driver = driver; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio source2")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + pa_proplist_sets(data.proplist, "tizen.card", u->card); + pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(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); + goto fail; + } + + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log_error("Failed to create source object."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; + u->source->userdata = u; + + if (pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_IN, + &u->source->sample_spec, + u->frag_size / pa_frame_size(&u->source->sample_spec), + u->nfrags, + (void **)&u->pcm_handle)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->timestamp = 0ULL; + + if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) { + pa_log_error("Failed to create thread."); + goto fail; + } + + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(buffer_size, &ss)); + pa_source_put(u->source); + + return u->source; + +fail: + userdata_free(u); + return NULL; +} + +static void userdata_free(struct userdata *u) { + pa_assert(u); + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + pa_xfree(u->card); + pa_xfree(u->device); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pcm_handle) { + pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + } + + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + + pa_xfree(u); +} + +void pa_tizenaudio_source2_free(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se((u = s->userdata)); + + userdata_free(u); +} diff --git a/src/tizenaudio-source2.h b/src/tizenaudio-source2.h new file mode 100644 index 0000000..a8e67c0 --- /dev/null +++ b/src/tizenaudio-source2.h @@ -0,0 +1,38 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifndef footizenaudiosource2hfoo +#define footizenaudiosource2hfoo + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +pa_sink *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver); + +void pa_tizenaudio_source2_free(pa_source *s); + +#endif + -- 2.7.4 From 1e46c80a9170dca47a380a8de5d70daeb5acb488 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 17 May 2022 15:41:12 +0900 Subject: [PATCH 16/16] Revert "aec-manager: Add audio AEC manager" This reverts commit f49985f02d8d1e49a4e2075f3b9f2d651c571770. [Version] 15.0.16 [Issue Type] Cleanup Change-Id: Ie7a9341ff90acd3aa26ed38d51ce140fffb02c0f --- Makefile.am | 9 -- configure.ac | 12 -- packaging/pulseaudio-modules-tizen.spec | 6 +- src/aec-manager.c | 273 -------------------------------- src/aec-manager.h | 38 ----- src/device-manager.c | 24 +-- src/module-tizenaudio-sink.c | 67 +------- src/module-tizenaudio-source.c | 67 +------- 8 files changed, 14 insertions(+), 482 deletions(-) delete mode 100644 src/aec-manager.c delete mode 100644 src/aec-manager.h diff --git a/Makefile.am b/Makefile.am index ed2843f..12098ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,10 +27,6 @@ AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDFLAGS = $(NODELETE_LDFLAGS) MODULE_CFLAGS = $(AM_CFLAGS) $(PACORE_CFLAGS) $(PA_CFLAGS) -D__TIZEN__ -if ENABLE_AEC -MODULE_CFLAGS += -DSUPPORT_AEC -endif - MODULE_LDFLAGS = $(AM_LDFLAGS) $(PACORE_LDFLAGS) $(PA_LDFLAGS) -module -disable-static -avoid-version MODULE_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS) @@ -139,11 +135,6 @@ module_tizenaudio_policy_la_SOURCES = \ src/device-manager-db.c src/device-manager-db-priv.h \ src/tizen-device.c src/tizen-device.h src/tizen-device-def.c src/tizen-device-def.h \ src/subscribe-observer.c src/subscribe-observer.h - -if ENABLE_AEC -module_tizenaudio_policy_la_SOURCES += src/aec-manager.c src/aec-manager.h -endif - module_tizenaudio_policy_la_LDFLAGS = $(MODULE_LDFLAGS) -L$(pulsemodlibexecdir) module_tizenaudio_policy_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(VCONF_LIBS) $(INIPARSER_LIBS) $(LIBJSON_LIBS) libhal-interface.la libcommunicator.la module_tizenaudio_policy_la_CFLAGS = $(MODULE_CFLAGS) $(DBUS_CFLAGS) $(VCONF_CFLAGS) $(INIPARSER_CFLAGS) $(LIBJSON_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_policy diff --git a/configure.ac b/configure.ac index 170b340..ad76385 100644 --- a/configure.ac +++ b/configure.ac @@ -397,18 +397,6 @@ AC_ARG_ENABLE(acm, AC_HELP_STRING([--enable-acm], [using acm]), AM_CONDITIONAL(ENABLE_ACM, test "x$ENABLE_ACM" = "xyes") dnl end -------------------------------------------------------------------- -dnl use aec ---------------------------------------------------------------- -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") -dnl end -------------------------------------------------------------------- - dnl use webrtc ---------------------------------------------------------------- AC_ARG_ENABLE(webrtc, AC_HELP_STRING([--enable-webrtc], [using webrtc-audio-processing]), [ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 0dccc40..16babe1 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.15 +Version: 15.0.16 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -54,9 +54,7 @@ export LD_AS_NEEDED=0 %reconfigure --prefix=%{_prefix} \ --disable-static \ --enable-acm \ -%if "%{tizen_profile_name}" != "tv" - --enable-aec -%else +%if "%{tizen_profile_name}" == "tv" --enable-vconf-helper %endif # --enable-haltc diff --git a/src/aec-manager.c b/src/aec-manager.c deleted file mode 100644 index c349f71..0000000 --- a/src/aec-manager.c +++ /dev/null @@ -1,273 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2021 Jaechul Lee - - 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 -#endif - -#include -#include -#include -#include -#include - -#include -#include -#include - -#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 \ - "" \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - "" - -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); - if (!dbus_connection_send(conn, reply, NULL)) - pa_log_error("reply send error!"); - - 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; - if (dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) { - if (!dbus_connection_send(conn, r, NULL)) - pa_log_error("reply send error!"); - } else { - pa_log_error("args append error!"); - } - - 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"); -} diff --git a/src/aec-manager.h b/src/aec-manager.h deleted file mode 100644 index d30f788..0000000 --- a/src/aec-manager.h +++ /dev/null @@ -1,38 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2021 Jaechul Lee - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifndef footizenaecmanagerfoo -#define footizenaecmanagerfoo - -typedef struct aec_params { - int rate; - int channels; - int format; - int frag_size; - int nfrags; - char card[32]; - char device[32]; -} aec_params_t; - -int aec_manager_init(pa_core *core, pa_source *source, pa_sink *sink); -void aec_manager_deinit(); - -#endif diff --git a/src/device-manager.c b/src/device-manager.c index 51fdba9..b5d6e9c 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -49,10 +49,6 @@ #include "device-manager-dbus-priv.h" #include "device-manager-db-priv.h" -#ifdef SUPPORT_AEC -#include "aec-manager.h" -#endif - #define SHARED_DEVICE_MANAGER "tizen-device-manager" #define DEVICE_MAP_FILE PA_DEFAULT_CONFIG_DIR"/device-map.json" @@ -2973,8 +2969,6 @@ static pa_hook_result_t device_running_changed_hook_cb(pa_core *c, pa_tz_device_ pa_device_manager* pa_device_manager_get(pa_core *c) { pa_device_manager *dm; - pa_source *source; - pa_sink *sink; pa_assert(c); @@ -3031,17 +3025,12 @@ pa_device_manager* pa_device_manager_get(pa_core *c) { } /* Just for convenience when test*/ - sink = _device_manager_set_default_sink(dm, DEVICE_TYPE_SPEAKER, "normal"); - if (!sink) + if (!_device_manager_set_default_sink(dm, DEVICE_TYPE_SPEAKER, "normal")) { pa_log_warn("Set default sink with speaker(normal) failed"); - - source = _device_manager_set_default_source(dm, DEVICE_TYPE_MIC, "normal"); - if (!source) + } + if (!_device_manager_set_default_source(dm, DEVICE_TYPE_MIC, "normal")) { pa_log_warn("Set default source with mic(normal) failed"); - -#ifdef SUPPORT_AEC - aec_manager_init(dm->core, source, sink); -#endif + } pa_shared_set(c, SHARED_DEVICE_MANAGER, dm); @@ -3119,9 +3108,4 @@ void pa_device_manager_unref(pa_device_manager *dm) { pa_shared_remove(dm->core, SHARED_DEVICE_MANAGER); pa_xfree(dm); - -#ifdef SUPPORT_AEC - aec_manager_deinit(); -#endif - } diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 0ea7846..bb86007 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -47,10 +47,6 @@ #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); @@ -79,13 +75,6 @@ PA_MODULE_USAGE( #define DEVICE_NAME_MAX 30 #define SMALL_AVAIL_LOG_PERIOD 20 -#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; @@ -113,11 +102,6 @@ struct userdata { uint64_t small_avail_count; 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[] = { @@ -180,7 +164,7 @@ static int suspend(struct userdata *u) { u->rtpoll_item = NULL; } - pa_log_info("Device suspended..."); + pa_log_info("Device suspended...[%s,%s]", u->card, u->device); return 0; } @@ -191,9 +175,6 @@ static int unsuspend(struct userdata *u) { int32_t ret; size_t frame_size; - char *card = u->card; - char *device = u->device; - pa_assert(u); pa_assert(!u->pcm_handle); @@ -206,16 +187,9 @@ static int unsuspend(struct userdata *u) { goto fail; } -#ifdef SUPPORT_AEC - if (u->aec_enable) { - card = "Loopback"; - device = "0,0"; - } -#endif - ret = pa_hal_interface_pcm_open(u->hal_interface, - card, - device, + u->card, + u->device, DIRECTION_OUT, &sample_spec, u->frag_size / frame_size, @@ -232,7 +206,7 @@ static int unsuspend(struct userdata *u) { u->write_count = 0; u->first = true; - pa_log_info("Resumed successfully...device(%s:%s)", card, device); + pa_log_info("Resumed successfully..."); return 0; @@ -309,36 +283,6 @@ static int sink_process_msg( *((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); @@ -641,9 +585,6 @@ int pa__init(pa_module*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))) { diff --git a/src/module-tizenaudio-source.c b/src/module-tizenaudio-source.c index e3ed819..746eb74 100644 --- a/src/module-tizenaudio-source.c +++ b/src/module-tizenaudio-source.c @@ -47,10 +47,6 @@ #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); @@ -74,13 +70,6 @@ PA_MODULE_USAGE( #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; @@ -106,11 +95,6 @@ struct userdata { 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[] = { @@ -168,7 +152,7 @@ static int suspend(struct userdata *u) { u->rtpoll_item = NULL; } - pa_log_info("Device suspended..."); + pa_log_info("Device suspended...[%s,%s]", u->card, u->device); return 0; } @@ -179,9 +163,6 @@ static int unsuspend(struct userdata *u) { int32_t ret; size_t frame_size; - char *card = u->card; - char *device = u->device; - pa_assert(u); pa_assert(!u->pcm_handle); @@ -194,16 +175,9 @@ static int unsuspend(struct userdata *u) { goto fail; } -#ifdef SUPPORT_AEC - if (u->aec_enable) { - card = "Loopback"; - device = "1,1"; - } -#endif - ret = pa_hal_interface_pcm_open(u->hal_interface, - card, - device, + u->card, + u->device, DIRECTION_IN, &sample_spec, u->frag_size / frame_size, @@ -220,7 +194,7 @@ static int unsuspend(struct userdata *u) { u->read_count = 0; u->first = true; - pa_log_info("Resumed successfully...device(%s:%s)", card, device); + pa_log_info("Resumed successfully..."); return 0; @@ -291,36 +265,6 @@ static int source_process_msg( *((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); @@ -555,9 +499,6 @@ int pa__init(pa_module*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))) { -- 2.7.4