From 324cb705d7953ea4f7a23c5d0ac08d1bbdd961a1 Mon Sep 17 00:00:00 2001 From: jungsup lee Date: Wed, 8 Sep 2021 13:54:40 +0900 Subject: [PATCH 01/16] device-manager-dbus: always place bt-sco device at first in device list [Version] 13.0.72 [Issue Type] Improvement Change-Id: I15efd7b12c48a3278ad64e9fdb7a2f409cf4183f --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager-dbus.c | 94 ++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 741064d..b58e363 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.71 +Version: 13.0.72 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager-dbus.c b/src/device-manager-dbus.c index 359d4f4..8c067af 100644 --- a/src/device-manager-dbus.c +++ b/src/device-manager-dbus.c @@ -689,35 +689,22 @@ static int method_call_bt_get_name(DBusConnection *conn, const char *device_path } #endif /* __TIZEN_INTERNAL_BT_SCO__ */ -static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { - pa_device_manager *dm = (pa_device_manager *)userdata; - DBusMessage *reply = NULL; - DBusMessageIter msg_iter, array_iter, device_iter; - pa_tz_device *device; - dm_device_state_t state; +static void array_iter_append(DBusMessageIter *array_iter, pa_idxset *device_list, int mask) { uint32_t device_idx; + dm_device_state_t state; dbus_int32_t device_id, direction; - int mask; char *type, *name; dbus_int32_t vendor_id, product_id; dbus_bool_t is_running; + pa_tz_device *device; + DBusMessageIter device_iter; - pa_assert(conn); - pa_assert(msg); - pa_assert(dm); - - pa_assert_se((reply = dbus_message_new_method_return(msg))); - - pa_assert_se(dbus_message_get_args(msg, NULL, - DBUS_TYPE_INT32, &mask, - DBUS_TYPE_INVALID)); - - pa_log_info("Get connected device list (mask : %d)", mask); + pa_assert(array_iter); - dbus_message_iter_init_append(reply, &msg_iter); - pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "(isiisiib)", &array_iter)); + if (!device_list) + return; - PA_IDXSET_FOREACH(device, dm->device_list, device_idx) { + PA_IDXSET_FOREACH(device, device_list, device_idx) { device_id = (dbus_int32_t)pa_tz_device_get_id(device); state = pa_tz_device_get_state(device); direction = pa_tz_device_get_direction(device); @@ -727,9 +714,9 @@ static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage * product_id = (dbus_int32_t) pa_tz_device_get_product_id(device); product_id = (dbus_int32_t) pa_tz_device_get_product_id(device); is_running = (dbus_bool_t) pa_tz_device_is_running(device); - if (device_is_match_with_mask(device, mask)) { + if (device_is_match_with_mask(device, mask)) { simple_device_dump(PA_LOG_INFO, "[MATCH]", device_id, type, name, direction, state); - pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &device_iter)); + pa_assert_se(dbus_message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &device_iter)); dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &device_id); dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &direction); @@ -738,11 +725,70 @@ static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage * dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &vendor_id); dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &product_id); dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_BOOLEAN, &is_running); - pa_assert_se(dbus_message_iter_close_container(&array_iter, &device_iter)); + pa_assert_se(dbus_message_iter_close_container(array_iter, &device_iter)); } else { simple_device_dump(PA_LOG_INFO, "[UNMATCH]", device_id, type, name, direction, state); } } +} + +#ifndef __TIZEN_INTERNAL_BT_SCO__ +static int include_device_filter_func(const void *d, const void *include_device_type) { + pa_tz_device *device = (pa_tz_device *)d; + + pa_assert(device); + pa_assert(include_device_type); + + return (int)pa_safe_streq(pa_tz_device_get_type(device), (const char *)include_device_type); +} + +static int exclude_device_filter_func(const void *d, const void *exclude_device_type) { + pa_tz_device *device = (pa_tz_device *)d; + + pa_assert(device); + pa_assert(exclude_device_type); + + return (int)!pa_safe_streq(pa_tz_device_get_type(device), (const char *)exclude_device_type); +} +#endif /* __TIZEN_INTERNAL_BT_SCO__ */ + +static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_device_manager *dm = (pa_device_manager *)userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, array_iter; + int mask; +#ifndef __TIZEN_INTERNAL_BT_SCO__ + pa_idxset *idxset1, *idxset2; +#endif + + pa_assert(conn); + pa_assert(msg); + pa_assert(dm); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_INT32, &mask, + DBUS_TYPE_INVALID)); + + pa_log_info("Get connected device list (mask : %d)", mask); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "(isiisiib)", &array_iter)); + +#ifdef __TIZEN_INTERNAL_BT_SCO__ + array_iter_append(&array_iter, dm->device_list, mask); +#else + /* divide into two groups and merge them because dbus message doesn't support sorting or prepend */ + idxset1 = pa_idxset_filtered_copy(dm->device_list, NULL, include_device_filter_func, DEVICE_TYPE_BT_SCO); + idxset2 = pa_idxset_filtered_copy(dm->device_list, NULL, exclude_device_filter_func, DEVICE_TYPE_BT_SCO); + + array_iter_append(&array_iter, idxset1, mask); + array_iter_append(&array_iter, idxset2, mask); + + pa_idxset_free(idxset1, NULL); + pa_idxset_free(idxset2, NULL); +#endif /* __TIZEN_INTERNAL_BT_SCO__ */ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &array_iter)); -- 2.7.4 From 64302e3895000bd0d1d1e9c6a068463ab049f1be Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Mon, 13 Sep 2021 09:16:49 +0900 Subject: [PATCH 02/16] device-manager: Fix svace defect (DEREF_OF_NULL.RET.PROC.STAT) [Version] 13.0.73 [Issue Type] Svace Change-Id: If07027bfc1f37a296aedf440e03fa04040c42ad1 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index b58e363..2092eeb 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.72 +Version: 13.0.73 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index 4e3d132..16e5641 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -610,7 +610,7 @@ static char* device_class_get_module_name(dm_device_class_t device_class, const class = pa_split(device_string, ":", &state); - if (!pa_streq(class, prefix)) { + if (!pa_safe_streq(class, prefix)) { if (!pa_atoi(class + strlen(prefix), &v)) snprintf(version, sizeof(version), "%d", v); else -- 2.7.4 From 5a9800395039ec6f9f11ddac6d2e8d9dfaad8307 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Wed, 8 Sep 2021 13:49:32 +0900 Subject: [PATCH 03/16] tizenaudio-sink2/source2: Fix frames_to_write calculation [Version] 13.0.74 [Issue Type] Bug fix Change-Id: Ida9fcab0c380fc71125f64dc90d8a186840f40ae Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-tizenaudio-sink.c | 2 +- src/module-tizenaudio-sink2.c | 4 ++-- src/module-tizenaudio-source2.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 2092eeb..39724b8 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.73 +Version: 13.0.74 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 62a12d5..de01053 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -430,7 +430,7 @@ static int process_render(struct userdata *u, pa_usec_t now) { /* Write pcm every 10ms */ frames_to_write = pa_usec_to_bytes((10 * PA_USEC_PER_MSEC), &u->sink->sample_spec) / frame_size; #else - frames_to_write = (u->frag_size / u->nfrags); + frames_to_write = u->frag_size / frame_size; #endif if (frames_to_write > avail) break; diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index e4e61e9..fe064fb 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -312,7 +312,7 @@ do_nothing: 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 / u->nfrags; + size_t frames_to_write = u->frag_size / frame_size; uint32_t avail = 0; pa_memchunk chunk; @@ -324,7 +324,7 @@ static int process_render(struct userdata *u) { return 0; } - pa_sink_render_full(u->sink, frames_to_write * frame_size, &chunk); + 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)) { diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 333bdbb..55bc60a 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -275,7 +275,7 @@ static int source_process_msg( 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 / u->nfrags; + size_t frames_to_read = u->frag_size / frame_size; uint32_t avail = 0; pa_memchunk chunk; @@ -289,7 +289,7 @@ static int process_render(struct userdata *u) { return 0; } - chunk.length = frames_to_read * frame_size; + chunk.length = u->frag_size; chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); p = pa_memblock_acquire(chunk.memblock); -- 2.7.4 From 449b0c5375917f12473d396f3a9f2080a6baef95 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Thu, 23 Sep 2021 21:25:03 +0900 Subject: [PATCH 04/16] sound-player: fix memory leak in case of stopping playback eos signal with the cause must be sent to client to make proper subscription release [Version] 13.0.75 [Issue Type] Bug fix Change-Id: Id318f1a4d18eafeea4e343eef6c02ee63ca2960f --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-sound-player.c | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 39724b8..137d868 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.74 +Version: 13.0.75 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-sound-player.c b/src/module-sound-player.c index 5e60af2..16212ae 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -80,6 +80,7 @@ static void handle_simple_stop_all(DBusConnection *conn, DBusMessage *msg, void static void handle_sample_play(DBusConnection *conn, DBusMessage *msg, void *userdata); static void handle_sound_play(DBusConnection *conn, DBusMessage *msg, void *userdata); static void handle_sound_stop(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void send_signal_for_eos(DBusConnection *conn, int32_t stream_idx, dbus_bool_t stopped_by_user); enum method_handler_index { METHOD_HANDLER_SIMPLE_PLAY, @@ -537,10 +538,12 @@ static void handle_sound_stop(DBusConnection *conn, DBusMessage *msg, void *user pa_sink_input_kill(si); pa_dbus_send_empty_reply(conn, msg); + + send_signal_for_eos(conn, stream_idx, TRUE); } static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) { - int idx = 0; + int idx = 0; pa_assert(conn); pa_assert(msg); @@ -588,16 +591,21 @@ static DBusHandlerResult method_handler_for_vt(DBusConnection *c, DBusMessage *m return DBUS_HANDLER_RESULT_HANDLED; } -static void send_signal_for_eos(struct userdata *u, int32_t stream_idx) { +static void send_signal_for_eos(DBusConnection *conn, int32_t stream_idx, dbus_bool_t stopped_by_user) { DBusMessage *signal_msg = NULL; - pa_assert(u); + pa_assert(conn); - pa_log_info("Send EOS signal for stream_idx(%d)", stream_idx); + pa_log_info("Send EOS signal for stream_idx(%d), stopped_by_user(%d)", stream_idx, stopped_by_user); - pa_assert_se((signal_msg = dbus_message_new_signal(SOUND_PLAYER_OBJECT_PATH, SOUND_PLAYER_INTERFACE, SOUND_PLAYER_SIGNAL_EOS))); - pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_INT32, &stream_idx, DBUS_TYPE_INVALID)); - pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->dbus_conn), signal_msg, NULL)); + pa_assert_se((signal_msg = dbus_message_new_signal(SOUND_PLAYER_OBJECT_PATH, + SOUND_PLAYER_INTERFACE, + SOUND_PLAYER_SIGNAL_EOS))); + pa_assert_se(dbus_message_append_args(signal_msg, + DBUS_TYPE_INT32, &stream_idx, + DBUS_TYPE_BOOLEAN, &stopped_by_user, + DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(conn, signal_msg, NULL)); dbus_message_unref(signal_msg); } #endif @@ -758,8 +766,10 @@ fail: static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { int32_t *stream_idx = NULL; uint32_t idx = 0; + pa_core_assert_ref(core); pa_sink_input_assert_ref(i); + pa_assert(u); pa_log_debug("start sink_input_unlink_cb, i(%p, index:%u)", i, i->index); @@ -768,7 +778,7 @@ static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, st if (*stream_idx == (int32_t)(i->index)) { #ifndef USE_DBUS_PROTOCOL /* Send EOS signal for this stream */ - send_signal_for_eos(u, *stream_idx); + send_signal_for_eos(pa_dbus_connection_get(u->dbus_conn), *stream_idx, FALSE); #endif pa_idxset_remove_by_data(u->stream_idxs, stream_idx, NULL); pa_xfree(stream_idx); -- 2.7.4 From 4666f7e663bc2e9c1d1caeade4fdfc81c989c139 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Fri, 24 Sep 2021 21:27:12 +0900 Subject: [PATCH 05/16] Fix aarch64 build warnings Fixes following warnings: - cast to pointer from integer of different size [-Wint-to-pointer-cast] - format '%llu' expects argument of type 'long long unsigned int', but argument 9 has type 'pa_usec_t' {aka 'long unsigned int'} [-Wformat=] - format '%d' expects argument of type 'int', but argument 7 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] [Version] 13.0.76 [Issue Type] Build Change-Id: I55a043c48107c3a3c6bf71cbb8b81f54c504180d --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-tizenaudio-policy.c | 16 ++++++++-------- src/module-tizenaudio-sink2.c | 4 ++-- src/module-tizenaudio-source2.c | 4 ++-- src/stream-manager-dbus.c | 14 +++++++------- src/stream-manager.c | 34 ++++++++++++++++----------------- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 137d868..49718ab 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.75 +Version: 13.0.76 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-tizenaudio-policy.c b/src/module-tizenaudio-policy.c index 44b1894..bb331e0 100644 --- a/src/module-tizenaudio-policy.c +++ b/src/module-tizenaudio-policy.c @@ -468,10 +468,10 @@ static void update_loopback_module_args(struct userdata* u, int32_t parent_id, p return; } - loopback = pa_hashmap_get(u->loopback_modules, (const void*)parent_id); + loopback = pa_hashmap_get(u->loopback_modules, PA_INT_TO_PTR(parent_id)); if (!loopback) { loopback = pa_xnew0(loopback_module, 1); - pa_hashmap_put(u->loopback_modules, (void*)parent_id, loopback); + pa_hashmap_put(u->loopback_modules, PA_INT_TO_PTR(parent_id), loopback); } if (sink) loopback->sink = sink; @@ -495,7 +495,7 @@ static void load_loopback_module_by_parent_id(struct userdata* u, int32_t parent return; } - loopback = pa_hashmap_get(u->loopback_modules, (const void*)parent_id); + loopback = pa_hashmap_get(u->loopback_modules, PA_INT_TO_PTR(parent_id)); if (!loopback) { pa_log_error("could not find loopback module for parent_id(%d)", parent_id); return; @@ -540,7 +540,7 @@ static void unload_loopback_modules_by_device_disconnect(struct userdata* u, pa_ ((sink && (loopback->sink == sink)) || (source && (loopback->source == source)))) { pa_log_info(" -- unload module-loopback(%p) for parent_id(%d)", loopback->module, parent_id); - pa_hashmap_remove_and_free(u->loopback_modules, (const void*)parent_id); + pa_hashmap_remove_and_free(u->loopback_modules, PA_INT_TO_PTR(parent_id)); } } } @@ -557,7 +557,7 @@ static void unload_loopback_modules_by_stream_disconnect(struct userdata* u, int if (!i && !o) return; - loopback = pa_hashmap_get(u->loopback_modules, (const void*)parent_id); + loopback = pa_hashmap_get(u->loopback_modules, PA_INT_TO_PTR(parent_id)); if (!loopback) return; @@ -582,7 +582,7 @@ static void unload_loopback_modules_by_stream_disconnect(struct userdata* u, int unload: pa_log_info(" -- unload module-loopback(%p) for parent_id(%d)", loopback->module, parent_id); - pa_hashmap_remove_and_free(u->loopback_modules, (const void*)parent_id); + pa_hashmap_remove_and_free(u->loopback_modules, PA_INT_TO_PTR(parent_id)); } static pa_sink *load_combine_sink_module(struct userdata *u, const char *combine_sink_name, pa_sink *sink1, pa_sink *sink2, pa_sink_input *stream) @@ -897,7 +897,7 @@ static void select_device_by_auto_last_connected_routing(struct userdata *u, pa_ dm_device_direction = pa_tz_device_get_direction(device); dm_device_id = pa_tz_device_get_id(device); creation_time = pa_tz_device_get_creation_time(device); - pa_log_debug(" -- type[%-16s], direction[0x%x], id[%u], creation_time[%llu]", + pa_log_debug(" -- type[%-16s], direction[0x%x], id[%u], creation_time[%" PRIu64 "]", dm_device_type, dm_device_direction, dm_device_id, creation_time); if (!pa_safe_streq(device_type, dm_device_type) || !IS_AVAILABLE_DIRECTION(data->stream_type, dm_device_direction)) continue; @@ -1567,7 +1567,7 @@ static pa_hook_result_t handle_auto_last_connected_routing(struct userdata *u, p dm_device_direction = pa_tz_device_get_direction(*device); dm_device_id = pa_tz_device_get_id(*device); creation_time = pa_tz_device_get_creation_time(*device); - pa_log_debug(" -- type[%-16s], direction[0x%x], id[%u], creation_time[%llu]", + pa_log_debug(" -- type[%-16s], direction[0x%x], id[%u], creation_time[%" PRIu64 "]", dm_device_type, dm_device_direction, dm_device_id, creation_time); if (!pa_safe_streq(device_type, dm_device_type) || !IS_AVAILABLE_DIRECTION(data->stream_type, dm_device_direction)) continue; diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index fe064fb..a5a5871 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -320,7 +320,7 @@ static int process_render(struct userdata *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(%d), avail(%d)", frames_to_write, avail); + pa_log_debug("not enough avail size. frames_to_write(%zu), avail(%d)", frames_to_write, avail); return 0; } @@ -328,7 +328,7 @@ static int process_render(struct userdata *u) { 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(%d)", p, frames_to_write); + pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); return -1; } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 55bc60a..3df3aef 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -285,7 +285,7 @@ static int process_render(struct userdata *u) { 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(%d), avail(%d)", frames_to_read, avail); + pa_log_debug("not enough avail size. frames_to_read(%zu), avail(%d)", frames_to_read, avail); return 0; } @@ -294,7 +294,7 @@ static int process_render(struct userdata *u) { 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(%d)", p, 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); diff --git a/src/stream-manager-dbus.c b/src/stream-manager-dbus.c index f10e221..096ff5c 100644 --- a/src/stream-manager-dbus.c +++ b/src/stream-manager-dbus.c @@ -602,7 +602,7 @@ static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *m pa_log_info("id[%u], in_device_list[%p]:length[%u], out_device_list[%p]:length[%u]", id, in_device_list, list_len_in, out_device_list, list_len_out); - sp = pa_hashmap_get(m->stream_parents, (const void*)id); + sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(id)); if (!sp) { pa_log_error("could not find matching client for this parent_id[%u]", id); ret = RET_MSG_ERROR_INTERNAL; @@ -677,7 +677,7 @@ static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *ms DBUS_TYPE_INVALID)); pa_log_info("id[%u], name[%s], value[%d]", id, name, value); - sp = pa_hashmap_get(m->stream_parents, (const void*)id); + sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(id)); if (sp) { if (name) { route_option.name = name; @@ -774,7 +774,7 @@ static void handle_set_stream_preferred_device(DBusConnection *conn, DBusMessage DBUS_TYPE_INVALID)); pa_log_info("stream parent id[%u], device direction[%s], device_id[%u]", sp_id, device_direction, device_id); - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)sp_id))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(sp_id)))) { pa_log_error("could not find matching client for this parent_id[%u]", sp_id); ret = RET_MSG_ERROR_INTERNAL; goto finish; @@ -904,7 +904,7 @@ static void handle_get_stream_preferred_device(DBusConnection *conn, DBusMessage pa_assert_se((reply = dbus_message_new_method_return(msg))); - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)sp_id))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(sp_id)))) { pa_log_error("could not find matching client for this parent_id[%u]", sp_id); ret = RET_MSG_ERROR_INTERNAL; goto finish; @@ -1637,7 +1637,7 @@ static void handle_update_focus_status(DBusConnection *conn, DBusMessage *msg, v DBUS_TYPE_INVALID)); pa_log_info("id[%u], acquired_focus_status[0x%x]", id, acquired_focus_status); - if ((sp = pa_hashmap_get(m->stream_parents, (const void*)id))) { + if ((sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(id)))) { if (sp->focus_status != acquired_focus_status) { /* need to update */ prev_status = sp->focus_status; @@ -2099,7 +2099,7 @@ static void handle_activate_ducking(DBusConnection *conn, DBusMessage *msg, void id, enable, target_stream, duration, ratio); /* get stream_ducking */ - sd = pa_hashmap_get(m->stream_duckings, (const void*)id); + sd = pa_hashmap_get(m->stream_duckings, PA_UINT_TO_PTR(id)); if (!sd) { pa_log_error("no matched stream ducking for id[%u]", id); ret_msg = RET_MSG_ERROR_INTERNAL; @@ -2219,7 +2219,7 @@ static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, voi pa_assert_se((reply = dbus_message_new_method_return(msg))); /* get stream_ducking */ - sd = pa_hashmap_get(m->stream_duckings, (const void *)id); + sd = pa_hashmap_get(m->stream_duckings, PA_UINT_TO_PTR(id)); if (!sd) { pa_log_error("no matched stream ducking for id[%u]", id); ret_msg = RET_MSG_ERROR_INTERNAL; diff --git a/src/stream-manager.c b/src/stream-manager.c index fe45e37..cb12cb9 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -376,7 +376,7 @@ static stream_parent* get_stream_parent(pa_stream_manager *m, pa_object *stream) return NULL; } - return pa_hashmap_get(m->stream_parents, (const void*)parent_id_u); + return pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_id_u)); } static pa_idxset* get_route_devices(pa_stream_manager *m, pa_object *stream) { @@ -1203,7 +1203,7 @@ static bool update_focus_status_of_stream(pa_stream_manager *m, void *stream, st else p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); if (p_idx && !pa_atou(p_idx, &parent_idx)) { - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)))) { pa_log_error("could not find matching client for this parent_id(%u)", parent_idx); return false; } @@ -1233,7 +1233,7 @@ static void update_preferred_device_role(pa_stream_manager *m, void *stream, str if (!p_idx_str || pa_atou(p_idx_str, &parent_idx)) return; - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)))) { pa_log_error("could not find matching client for this parent_id(%u)", parent_idx); return; } @@ -1258,7 +1258,7 @@ static void get_preferred_device_type_and_role(pa_stream_manager *m, void *strea if (!p_idx_str || pa_atou(p_idx_str, &parent_idx)) { return; } - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)))) { pa_log_warn("could not find matching client for this parent_id(%u)", parent_idx); return; } @@ -1283,7 +1283,7 @@ static bool update_stream_parent_info(pa_stream_manager *m, process_command_type p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); if (p_idx && !pa_atou(p_idx, &parent_idx)) { pa_log_debug("p_idx(%s), idx(%u)", p_idx, parent_idx); - sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); + sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)); if (sp) { uint32_t idx = GET_STREAM_INDEX(stream, type); if (command == PROCESS_COMMAND_ADD_STREAM) { @@ -1662,7 +1662,7 @@ static void fill_device_info_to_hook_data(pa_stream_manager *m, void *hook_data, break; } /* find parent idx, it's device info. and it's stream idxs */ - if ((sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx))) { + if ((sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)))) { select_data->idx_manual_devices = (type == STREAM_SINK_INPUT) ? (sp->idx_route_out_devices) : (sp->idx_route_in_devices); break; } @@ -1699,7 +1699,7 @@ static void fill_device_info_to_hook_data(pa_stream_manager *m, void *hook_data, pa_log_warn(" -- could not get the parent id of this stream, but keep going..."); break; } - if (!(sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx))) { + if (!(sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(parent_idx)))) { pa_log_warn(" -- failed to get the stream parent of idx(%u)", parent_idx); break; } @@ -2930,7 +2930,7 @@ static void find_next_device_for_auto_route(pa_stream_manager *m, stream_route_t } } *next_device = latest_device; - pa_log_debug("found next device[%p], creation_time[%llu]", *next_device, latest_creation_time); + pa_log_debug("found next device[%p], creation_time[%" PRIu64 "]", *next_device, latest_creation_time); } pa_log_debug("next_device is [%p] for stream_role[%s]/route_type[%d]/stream_type[%d]", *next_device, stream_role, route_type, stream_type); @@ -3427,19 +3427,19 @@ static void mute_sink_inputs_as_device_disconnection(pa_stream_manager *m, uint3 return; } apply_volume_factor_to_streams(streams_of_disconnected_device, &applied_streams); - pa_hashmap_put(m->muted_streams, (void*)event_id, applied_streams); + pa_hashmap_put(m->muted_streams, PA_UINT_TO_PTR(event_id), applied_streams); /* If PA_COMMUNICATOR_HOOK_EVENT_FULLY_HANDLED is not called for some reason, * volume factor should be removed forcedly. */ if (!m->time_event_for_unmute) m->time_event_for_unmute = pa_core_rttime_new(m->core, pa_rtclock_now() + TIMED_UNMUTE_USEC, timed_unmute_cb, m); } else { - if (!(applied_streams = pa_hashmap_get(m->muted_streams, (void*)event_id))) { + if (!(applied_streams = pa_hashmap_get(m->muted_streams, PA_UINT_TO_PTR(event_id)))) { pa_log_debug("could not find applied_streams for event_id(%u)", event_id); return; } clear_volume_factor_from_streams(applied_streams); - pa_hashmap_remove(m->muted_streams, (void*)event_id); + pa_hashmap_remove(m->muted_streams, PA_UINT_TO_PTR(event_id)); } return; @@ -3640,7 +3640,7 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t sp->idx_source_outputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); sp->idx_route_in_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); sp->idx_route_out_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - pa_hashmap_put(m->stream_parents, (void*)idx, sp); + pa_hashmap_put(m->stream_parents, PA_UINT_TO_PTR(idx), sp); pa_log_debug(" - add sp(%p), idx(%u)", sp, idx); return; } else if (pa_safe_streq(name, STREAM_MANAGER_CLIENT_DUCKING)) { @@ -3648,16 +3648,16 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t sd = pa_xmalloc0(sizeof(stream_ducking)); sd->idx_ducking_streams = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); sd->trigger_index = idx; - pa_hashmap_put(m->stream_duckings, (void *)idx, sd); + pa_hashmap_put(m->stream_duckings, PA_UINT_TO_PTR(idx), sd); pa_log_debug(" - add sd(%p), trigger_index(%u)", sd, idx); return; } } else if (t == (PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE)) { /* try to find stream parent with idx */ - sp = pa_hashmap_get(m->stream_parents, (const void*)idx); + sp = pa_hashmap_get(m->stream_parents, PA_UINT_TO_PTR(idx)); if (sp) { pa_log_debug(" - remove sp(%p), idx(%u)", sp, idx); - pa_hashmap_remove(m->stream_parents, (const void*)idx); + pa_hashmap_remove(m->stream_parents, PA_UINT_TO_PTR(idx)); pa_idxset_free(sp->idx_sink_inputs, NULL); pa_idxset_free(sp->idx_source_outputs, NULL); if (sp->idx_route_in_devices) @@ -3669,7 +3669,7 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t } /* try to find sd with idx */ - sd = pa_hashmap_get(m->stream_duckings, (const void*)idx); + sd = pa_hashmap_get(m->stream_duckings, PA_UINT_TO_PTR(idx)); if (sd) { uint32_t ducking_idx = 0; pa_sink_input *stream = NULL; @@ -3698,7 +3698,7 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t } pa_idxset_free(sd->idx_ducking_streams, NULL); - pa_hashmap_remove(m->stream_duckings, (const void*)idx); + pa_hashmap_remove(m->stream_duckings, PA_UINT_TO_PTR(idx)); pa_xfree(sd); return; } -- 2.7.4 From 0a3c62ee00b3f4810a903278066b53897adf739a Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 27 Sep 2021 14:58:18 +0900 Subject: [PATCH 06/16] Fix DEREF_OF_NULL.RET.PROC.STAT [Version] 13.0.77 [Issue Type] VD svace DF210918-00186 Change-Id: I21b72e6fd96ab07521cb807bc26c44e148c00162 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 49718ab..0130020 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.76 +Version: 13.0.77 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index 16e5641..a57fc46 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -609,6 +609,10 @@ static char* device_class_get_module_name(dm_device_class_t device_class, const const char *state = NULL, *prefix = "tizen"; class = pa_split(device_string, ":", &state); + if (!class) { + pa_log_error("Failed to parse device_string %s", device_string); + return NULL; + } if (!pa_safe_streq(class, prefix)) { if (!pa_atoi(class + strlen(prefix), &v)) -- 2.7.4 From 2b6a54f1afcca09e1a1210145e6b4af4a4f52505 Mon Sep 17 00:00:00 2001 From: jungsup lee Date: Thu, 14 Oct 2021 17:33:19 +0900 Subject: [PATCH 07/16] tizenaudio-sink: Print log when snd_pcm_avail() return small value continuously [Version] 13.0.78 [Issue Type] Debugging Change-Id: I982d1ee7cf7a5d0546902aec9f7a498278b4014a --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-tizenaudio-sink.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 0130020..88816a2 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.77 +Version: 13.0.78 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index de01053..a941adf 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -76,7 +76,8 @@ PA_MODULE_USAGE( /* Sink device consumes that amount of maximum buffer at every request */ #define DEFAULT_MAX_REQUEST_USEC (PA_USEC_PER_SEC * 0.032) -#define DEVICE_NAME_MAX 30 +#define DEVICE_NAME_MAX 30 +#define SMALL_AVAIL_LOG_PERIOD 20 #ifdef SUPPORT_AEC enum { @@ -109,6 +110,7 @@ struct userdata { pa_rtpoll_item *rtpoll_item; + uint64_t small_avail_count; uint64_t write_count; pa_hal_interface *hal_interface; @@ -432,10 +434,18 @@ static int process_render(struct userdata *u, pa_usec_t now) { #else frames_to_write = u->frag_size / frame_size; #endif - if (frames_to_write > avail) + if (frames_to_write > avail) { + u->small_avail_count++; + + if ((u->small_avail_count % SMALL_AVAIL_LOG_PERIOD) == 0) + pa_log_error("avail is too small : %u(repeat count : %llu)", avail, u->small_avail_count); + break; + } } + u->small_avail_count = 0; + pa_sink_render_full(u->sink, frames_to_write * frame_size, &chunk); p = pa_memblock_acquire(chunk.memblock); -- 2.7.4 From b64118838111be16086491b900eab5ff3e742d61 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Thu, 21 Oct 2021 11:33:28 +0900 Subject: [PATCH 08/16] Fix coverity issues (CHECKED_RETURN / PW.PARAMETER_HIDDEN) + Fix minor aarch64 build warning [Version] 13.0.79 [Issue Type] Build Change-Id: Ie7ed49cf73beb95ce5db332d6595aa6c6def27ab --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/aec-manager.c | 12 +++++++++--- src/module-tizenaudio-sink.c | 2 +- src/stream-manager.c | 6 +++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 88816a2..dc69171 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.78 +Version: 13.0.79 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/aec-manager.c b/src/aec-manager.c index 8ed96b5..c349f71 100644 --- a/src/aec-manager.c +++ b/src/aec-manager.c @@ -144,7 +144,8 @@ static void send_get_param_reply(DBusConnection *conn, DBusMessage *msg, aec_par dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->nfrags); dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr1); dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr2); - dbus_connection_send(conn, reply, NULL); + if (!dbus_connection_send(conn, reply, NULL)) + pa_log_error("reply send error!"); dbus_message_unref(reply); } @@ -205,8 +206,13 @@ static DBusHandlerResult method_handler_for_vt(DBusConnection *conn, DBusMessage r = dbus_message_new_method_return(m); if (r) { const char *xml = AEC_MANAGER_INTROSPECT_XML; - dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID); - dbus_connection_send((conn), r, NULL); + 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 { diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index a941adf..9dc3226 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -438,7 +438,7 @@ static int process_render(struct userdata *u, pa_usec_t now) { u->small_avail_count++; if ((u->small_avail_count % SMALL_AVAIL_LOG_PERIOD) == 0) - pa_log_error("avail is too small : %u(repeat count : %llu)", avail, u->small_avail_count); + pa_log_error("avail is too small : %u(repeat count : %" PRIu64 ")", avail, u->small_avail_count); break; } diff --git a/src/stream-manager.c b/src/stream-manager.c index cb12cb9..4b8b1a1 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -3172,11 +3172,11 @@ static void check_and_move_streams_by_device_connection_change(pa_stream_manager if (is_connected) { uint32_t preemptive_device_id = 0; if (!pa_stream_manager_get_preemptive_device_id(m, stream_type, stream_role, &preemptive_device_id)) { - pa_tz_device *device = pa_device_manager_get_device_by_id(m->dm, preemptive_device_id); - if (device) { + pa_tz_device *_device = pa_device_manager_get_device_by_id(m->dm, preemptive_device_id); + if (_device) { pa_log_debug("Skip moving this stream[%s, idx:%u] set to the preemptive device(%s, id:%u)", stream_role, (stream_type == STREAM_SINK_INPUT) ? PA_SINK_INPUT(s)->index : PA_SOURCE_OUTPUT(s)->index, - device->type, preemptive_device_id); + _device->type, preemptive_device_id); continue; } } -- 2.7.4 From 7ee613ac6b48f52d15f98a688a144ae7a379afb4 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Wed, 27 Oct 2021 19:48:01 +0900 Subject: [PATCH 09/16] device-manager: use container dedicated device-map json [Version] 13.0.80 [Issue Type] Container Change-Id: If81c7aed340369e451980142d7c677ff22053f98 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index dc69171..8520637 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.79 +Version: 13.0.80 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index a57fc46..7cb46c9 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -56,6 +56,7 @@ #define SHARED_DEVICE_MANAGER "tizen-device-manager" #define DEVICE_MAP_FILE PA_DEFAULT_CONFIG_DIR"/device-map.json" +#define DEVICE_MAP_FILE_CONTAINER PA_DEFAULT_CONFIG_DIR"/device-map-container.json" #define DEVICE_STR_MAX 40 #define DEVICE_DIRECTION_MAX 3 #define DEVICE_MODULE_STRING_MAX 256 @@ -2439,17 +2440,22 @@ fail: return NULL; } +static const char * get_device_map_json() +{ + return (access("/run/systemd/container", F_OK) == 0) ? DEVICE_MAP_FILE_CONTAINER : DEVICE_MAP_FILE; +} + static struct device_file_map *parse_device_file_map() { struct device_file_map *file_map = NULL; json_object *o, *device_files_o; json_object *playback_devices_o = NULL, *capture_devices_o = NULL; + const char *device_map_file = get_device_map_json(); pa_log_info("\nParse device files"); - o = json_object_from_file(DEVICE_MAP_FILE); - + o = json_object_from_file(device_map_file); if (o == NULL) { - pa_log_error("Read device-map file failed"); + pa_log_error("Read %s file failed", device_map_file); return NULL; } @@ -2522,12 +2528,12 @@ static pa_idxset* parse_device_type_infos() { int device_type_num = 0; int device_type_idx = 0; device_type_info *type_info = NULL; - //pa_hashmap *type_infos = NULL; pa_idxset *type_infos = NULL; + const char *device_map_file = get_device_map_json(); - o = json_object_from_file(DEVICE_MAP_FILE); + o = json_object_from_file(device_map_file); if (o == NULL) { - pa_log_error("Read device-map file failed"); + pa_log_error("Read %s file failed", device_map_file); return NULL; } -- 2.7.4 From 2ec3aa4311bf6dcf887f9541c7e811acb8f05e3d Mon Sep 17 00:00:00 2001 From: Jeongmo Yang Date: Tue, 28 Dec 2021 14:36:01 +0900 Subject: [PATCH 10/16] stream-manager: Deactivate ducking for audio HAL when stream ducking is removed Previously, deactivation ducking for audio HAL could be missed. [Version] 13.0.81 [Issue Type] Bug fix Change-Id: Ide1206dc98f1dd354cb23e6c8cf06361a1d815e3 Signed-off-by: Jeongmo Yang --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/stream-manager.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 8520637..57aea54 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.80 +Version: 13.0.81 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/stream-manager.c b/src/stream-manager.c index 4b8b1a1..e50e13b 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -3673,11 +3673,16 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t if (sd) { uint32_t ducking_idx = 0; pa_sink_input *stream = NULL; + hal_ducking_activation_info ducking_activation_info = { + sd->target_role, sd->duration, sd->ratio, false + }; - pa_log_info(" - remove sd(%p), trigger_index(%u)", sd, idx); + pa_log_info(" - remove sd(%p) state(%d), trigger_index(%u)", sd, sd->state, idx); + + if (sd->state == STREAM_DUCKING_STATE_UNDUCKED || sd->state == STREAM_DUCKING_STATE_UNDUCKING) + goto skip_unducking; PA_IDXSET_FOREACH(stream, sd->idx_ducking_streams, ducking_idx) { - hal_ducking_activation_info ducking_activation_info; /* Note: It is added temporarily to find missing index of idx_ducking_streams */ if (!pa_idxset_get_by_data(m->core->sink_inputs, stream, NULL)) { pa_log_error("could not find stream(%p), skip it", stream); @@ -3687,16 +3692,11 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t pa_log_info(" -- remove volume ramp, key(%s) from remained stream(idx:%u)", sd->vol_key, stream->index); pa_sink_input_remove_volume_factor(stream, sd->vol_key); pa_sink_input_remove_volume_ramp_factor(stream, sd->vol_key, true); - - /* notify ducking activation */ - ducking_activation_info.target_role = sd->target_role; - ducking_activation_info.duration = sd->duration; - ducking_activation_info.ratio = sd->ratio; - ducking_activation_info.is_activated = false; - - pa_hal_interface_notify_ducking_activation_changed(m->hal, &ducking_activation_info); } + pa_hal_interface_notify_ducking_activation_changed(m->hal, &ducking_activation_info); + + skip_unducking: pa_idxset_free(sd->idx_ducking_streams, NULL); pa_hashmap_remove(m->stream_duckings, PA_UINT_TO_PTR(idx)); pa_xfree(sd); -- 2.7.4 From e11d1a025b12ff11401a614bc7b3a41949beefa6 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Mon, 27 Dec 2021 14:56:49 +0900 Subject: [PATCH 11/16] Fix for pulseaudio 15.0 upgrade [Version] 15.0.0 [Issue Type] Core Upgrade Change-Id: Ie55fa522a3d1e08c8e898ce626a1a199c0f994e6 --- Makefile.am | 2 +- configure.ac | 2 +- packaging/pulseaudio-modules-tizen.spec | 32 +++++++++++++++++--------------- src/device-manager-db.c | 14 +++++++------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6869eaa..260a272 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ ACLOCAL_AMFLAGS = -I m4 -pulsemodlibexecdir= $(libdir)/pulse-13.0/modules +pulsemodlibexecdir= $(libdir)/pulse-15.0/modules pulselibexecdir=$(libexecdir)/pulse AM_CFLAGS = \ diff --git a/configure.ac b/configure.ac index d1720a3..acaaf3d 100644 --- a/configure.ac +++ b/configure.ac @@ -22,7 +22,7 @@ AC_PREREQ(2.63) -AC_INIT([pulseaudio-module-tizen],[0.1]) +AC_INIT([pulseaudio-module-tizen],[15.0]) AC_CONFIG_SRCDIR([src/module-tizenaudio-policy.c]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([config.h]) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 57aea54..2194382 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,8 @@ +%define module_ver 15.0 + Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.81 +Version: 15.0.0 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -76,21 +78,21 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %manifest %{name}.manifest %defattr(-,root,root,-) %license LICENSE.LGPL-2.1+ -%{_libdir}/pulse-13.0/modules/module-poweroff.so -%{_libdir}/pulse-13.0/modules/module-sound-player.so -%{_libdir}/pulse-13.0/modules/module-tone-player.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-policy.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-sink.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-source.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-sink2.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-source2.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-discover.so -%{_libdir}/pulse-13.0/modules/module-tizenaudio-publish.so -%{_libdir}/pulse-13.0/modules/libhal-interface.so -%{_libdir}/pulse-13.0/modules/libcommunicator.so +%{_libdir}/pulse-%{module_ver}/modules/module-poweroff.so +%{_libdir}/pulse-%{module_ver}/modules/module-sound-player.so +%{_libdir}/pulse-%{module_ver}/modules/module-tone-player.so +%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-policy.so +%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-sink.so +%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-source.so +%{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-sink2.so +%{_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/libhal-interface.so +%{_libdir}/pulse-%{module_ver}/modules/libcommunicator.so %{_tmpfilesdir}/pulseaudio.conf %if "%{tizen_profile_name}" == "tv" -%{_libdir}/pulse-13.0/modules/module-vconf.so +%{_libdir}/pulse-%{module_ver}/modules/module-vconf.so %{_libexecdir}/pulse/vconf-helper %endif %{_libdir}/ladspa/*.so @@ -100,5 +102,5 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %manifest %{name}.manifest %defattr(-,root,root,-) %license LICENSE.LGPL-2.1+ -%{_libdir}/pulse-13.0/modules/module-acm-sink.so +%{_libdir}/pulse-%{module_ver}/modules/module-acm-sink.so diff --git a/src/device-manager-db.c b/src/device-manager-db.c index f1cfaa8..40d0350 100644 --- a/src/device-manager-db.c +++ b/src/device-manager-db.c @@ -36,23 +36,23 @@ static void dump_prefer_entry(prefer_entry *e) { } int32_t init_database(pa_device_manager *dm) { - char *db_path; + char *state_path; pa_assert(dm); - if (!(db_path = pa_state_path("device-preferences", true))) { + if (!(state_path = pa_state_path(NULL, true))) { pa_log_error("failed to get path for database"); return -1; } - if (!(dm->database = pa_database_open(db_path, true))) { - pa_log_error("failed to open database '%s': %s", db_path, pa_cstrerror(errno)); - pa_xfree(db_path); + if (!(dm->database = pa_database_open(state_path, "device-preferences", true, true))) { + pa_log_error("failed to open database '%s': %s", state_path, pa_cstrerror(errno)); + pa_xfree(state_path); return -1; } - pa_log_info("database file '%s'", db_path); + pa_log_info("database file '%s'", state_path); - pa_xfree(db_path); + pa_xfree(state_path); return 0; } -- 2.7.4 From 290a87181918376a0a1130885010358ab9106668 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Tue, 22 Feb 2022 19:34:43 +0900 Subject: [PATCH 12/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 13/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 14/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 15/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 16/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