From e6a83316b8fd44bebd82fd9da0c7164c80c0cda0 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 11:33:13 +0900 Subject: [PATCH 01/16] Fix coverity issues The issue of coverity(1286103) was fixed [Version] 15.0.6 [Issue Type] Coverity Change-Id: I3e4b5ca2e925642cc4574136b07a3c736b9a2e69 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 11f3e2f..7972173 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.5 +Version: 15.0.6 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index a80eb9f..51216b2 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -869,7 +869,11 @@ int pa__init(pa_module *m) { pa_log_error("Failed to parse module arguments."); return -1; } - pa_modargs_get_value_u32(ma, "blocksize", &blocksize); + + if (pa_modargs_get_value_u32(ma, "blocksize", &blocksize) < 0) { + pa_log_info("Failed to get blocksize"); + goto fail; + } m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; -- 2.7.4 From cfaa0eda8c44f26728045d9c685f762b38510868 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 24 Mar 2022 16:57:45 +0900 Subject: [PATCH 02/16] Add default_method to module parameters Use default method that is configured at init function if source-output's 'echo_cancel' property is set to 'default' [Version] 15.0.7 [Issue Type] Improvement Change-Id: I137282f954cd01b1eb5e08aedd3f2a5a075e74d6 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 61 +++++++++++++++---------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 7972173..a350f73 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.6 +Version: 15.0.7 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 51216b2..f6571d1 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -46,6 +46,7 @@ PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( + "method= " "blocksize= "); typedef struct echo_cancel pa_echo_cancel; @@ -67,6 +68,7 @@ struct userdata { bool enable; uint32_t n_source_output; size_t blocksize; + char *default_method; pa_thread *thread; pa_thread_mq thread_mq; @@ -92,8 +94,10 @@ PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject); #define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) #define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL) #define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output) +#define DEFAULT_AEC_METHOD "speex" static const char* const valid_modargs[] = { + "method", "blocksize", NULL, }; @@ -113,26 +117,28 @@ static int proplist_get_fragment_size(pa_proplist *p, size_t *size) { return 0; } -static pa_processor_algo_t pa_processor_get_algo(pa_source_output *o) { - const char *algo = pa_proplist_gets(o->proplist, "echo"); +static int pa_processor_get_method(pa_source_output *o, const char *default_method, pa_processor_algo_t *method) { + const char *selected, *requested; - if (!algo) { - pa_log_warn("Use default processor(speex)"); - return PA_PROCESSOR_SPEEX; - } + pa_assert(method); + pa_assert(default_method); - if (pa_streq(algo, "adrian")) - return PA_PROCESSOR_ADRIAN; - else if (pa_streq(algo, "speex")) - return PA_PROCESSOR_SPEEX; - else if (pa_streq(algo, "webrtc")) - return PA_PROCESSOR_WEBRTC; - else if (pa_streq(algo, "auto")) - return PA_PROCESSOR_WEBRTC; - else { - pa_log_warn("invalid algo(%s), Use default processor(speex)", algo); - return PA_PROCESSOR_SPEEX; - } + requested = pa_proplist_gets(o->proplist, "echo_cancel"); + if (!requested) + return -1; + + selected = pa_streq(requested, "default") ? default_method : requested; + + if (pa_streq(selected, "webrtc")) + *method = PA_PROCESSOR_WEBRTC; + else if (pa_streq(selected, "speex")) + *method = PA_PROCESSOR_SPEEX; + else if (pa_streq(selected, "adrian")) + *method = PA_PROCESSOR_ADRIAN; + else + *method = PA_PROCESSOR_SPEEX; + + return 0; } static pa_source_output *find_source_output_by_flags(pa_source *s) { @@ -561,13 +567,14 @@ static int process_msg( static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *echo = pa_proplist_gets(data->proplist, "echo"); + const char *echo_cancel; pa_assert(c); - pa_assert(data); pa_assert(u); + pa_assert(data); - if (!echo) + echo_cancel = pa_proplist_gets(data->proplist, "echo_cancel"); + if (!echo_cancel) return PA_HOOK_OK; if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { @@ -653,7 +660,7 @@ static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; size_t blocksize = u->blocksize; - pa_processor_algo_t backend; + pa_processor_algo_t method; pa_assert(c); pa_assert(o); @@ -690,10 +697,14 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo if (o->thread_info.resampler) blocksize = pa_resampler_result(o->thread_info.resampler, blocksize); - backend = pa_processor_get_algo(o); + if (pa_processor_get_method(o, u->default_method, &method)) { + pa_log_error("Can't find method"); + goto fail; + } + o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec), &o->sample_spec, - backend, PA_PROCESSOR_FLAGS_ECHO_CANCEL); + method, PA_PROCESSOR_FLAGS_ECHO_CANCEL); if (!o->thread_info.processor) { pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled"); goto fail; @@ -879,6 +890,7 @@ int pa__init(pa_module *m) { u->core = m->core; u->m = m; u->blocksize = blocksize; + u->default_method = pa_xstrdup(pa_modargs_get_value(ma, "method", DEFAULT_AEC_METHOD)); u->echo_cancel = pa_msgobject_new(pa_echo_cancel); u->echo_cancel->parent.process_msg = process_msg; @@ -988,6 +1000,7 @@ void pa__done(pa_module *m) { pa_thread_mq_done(&u->thread_mq); + pa_xfree(u->default_method); pa_xfree(u); } -- 2.7.4 From 9a4141161da544772bc31c1226f96ce55d026bae Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 14:00:35 +0900 Subject: [PATCH 03/16] tones: Fix coverity issues The issue of coverity(1286168) was fixed [Version] 15.0.8 [Issue Type] Coverity Change-Id: I5eaaa3e59050963ad207634bae5a68631b9b286e Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/tones.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index a350f73..28cd7d8 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.7 +Version: 15.0.8 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/tones.h b/src/tones.h index 9616d14..10dd13f 100644 --- a/src/tones.h +++ b/src/tones.h @@ -23,9 +23,9 @@ #define footoneplayerfoo #include -#define TONE_ELEMENTS_ROW 14 +#define TONE_ELEMENTS_ROW 14 #define TONE_ELEMENTS_COLUMN 6 -#define TONE_ELEMENTS TONE_ELEMENTS_ROW * TONE_ELEMENTS_COLUMN +#define TONE_ELEMENTS (TONE_ELEMENTS_ROW * TONE_ELEMENTS_COLUMN) /* need to improve: 18KiB approximately */ static const uint16_t tones[][TONE_ELEMENTS] = { -- 2.7.4 From a9c80a3b213030772331d5373c367e15d4d54e71 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 28 Mar 2022 11:20:50 +0900 Subject: [PATCH 04/16] tizenaudio-echo-cancel: Find reference sink by proplist [Version] 15.0.9 [Issue Type] New Feature Change-Id: I28c7c2cb5a569f7f1159ee1a6aa2a4ede54ed0cc Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 43 ++++++++++++++----------- src/stream-manager-priv.h | 1 + src/stream-manager.c | 40 +++++++++++++++++++++++ 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 28cd7d8..38eda7d 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.8 +Version: 15.0.9 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index f6571d1..2f95314 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -123,7 +123,7 @@ static int pa_processor_get_method(pa_source_output *o, const char *default_meth pa_assert(method); pa_assert(default_method); - requested = pa_proplist_gets(o->proplist, "echo_cancel"); + requested = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); if (!requested) return -1; @@ -567,14 +567,14 @@ static int process_msg( static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *echo_cancel; + const char *method; pa_assert(c); pa_assert(u); pa_assert(data); - echo_cancel = pa_proplist_gets(data->proplist, "echo_cancel"); - if (!echo_cancel) + method = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); + if (!method) return PA_HOOK_OK; if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { @@ -589,24 +589,28 @@ static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_da return PA_HOOK_OK; } -static int find_reference_sink(struct userdata *u, pa_source_output *o) { - const char *sink_name; +static pa_sink *find_reference_sink_by_proplist(pa_core *c, pa_proplist *p) { + const char *ref_idx; + int32_t idx; + pa_sink *s; - pa_assert(u); - pa_assert(o); + pa_assert(c); + pa_assert(p); - sink_name = pa_proplist_gets(o->proplist, "reference_sink"); - if (!sink_name) - return -1; + ref_idx = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK); + if (!ref_idx) + return NULL; - u->sink = pa_namereg_get(u->core, sink_name, PA_NAMEREG_SINK); - if (!u->sink) - return -1; + if (pa_atoi(ref_idx, &idx) < 0) + return NULL; - pa_log_debug("Requested AEC source(%s), sink(%s)", - u->source->name, u->sink ? u->sink->name : ""); + s = pa_idxset_get_by_index(c->sinks, idx); + if (!s) + return NULL; - return 0; + pa_log_info("Found reference sink(%d, %s)", s->index, s->name); + + return s; } static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) { @@ -676,7 +680,8 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo } u->source = o->source; - if (find_reference_sink(u, o)) { + u->sink = find_reference_sink_by_proplist(c, o->proplist); + if (!u->sink) { pa_log_error("Can't find reference sink for AEC"); goto fail; } @@ -924,6 +929,8 @@ int pa__init(pa_module *m) { pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u); + /* source_output_new_cb must be called after new_cb callback in stream manager. + * because stream-manager converts the device_id to the index of the sink */ u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_LATE, (pa_hook_cb_t) source_output_new_cb, u); diff --git a/src/stream-manager-priv.h b/src/stream-manager-priv.h index e3d9298..b7c019f 100644 --- a/src/stream-manager-priv.h +++ b/src/stream-manager-priv.h @@ -76,6 +76,7 @@ typedef enum _process_command_type { PROCESS_COMMAND_REMOVE_STREAM, PROCESS_COMMAND_UPDATE_BUFFER_ATTR, PROCESS_COMMAND_APPLY_FILTER, + PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE, } process_command_type_t; typedef enum _notify_command_type { diff --git a/src/stream-manager.c b/src/stream-manager.c index e50e13b..36351ed 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -93,6 +93,7 @@ static const char* process_command_type_str[] = { [PROCESS_COMMAND_REMOVE_STREAM] = "REMOVE_STREAM", [PROCESS_COMMAND_UPDATE_BUFFER_ATTR] = "UPDATE_BUFFER_ATTR", [PROCESS_COMMAND_APPLY_FILTER] = "APPLY_FILTER", + [PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE] = "SET_AEC_REFERENCE", }; static const char* notify_command_type_str[] = { @@ -2487,6 +2488,44 @@ process_stream_result_t process_stream(pa_stream_manager *m, void *stream, strea } break; } + case PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE: { + int32_t id; + pa_tz_device *device; + const char *ref_device; + pa_proplist *p = ((pa_source_output_new_data*)stream)->proplist; + pa_sink *s; + + result = PROCESS_STREAM_RESULT_SKIP; + + ref_device = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_DEVICE); + if (!ref_device) + break; + + result = PROCESS_STREAM_RESULT_STOP; + + if (pa_atoi(ref_device, &id) < 0) { + pa_log_error("Can't convert ref_device(%s) to integer", ref_device); + break; + } + + device = pa_device_manager_get_device_by_id(m->dm, id); + if (!device) { + pa_log_error("Can't find device by id(%d)", id); + break; + } + + s = pa_tz_device_get_sink(device, "normal"); + if (!s) { + pa_log_error("Can't find sink by device"); + break; + } + + pa_proplist_setf(p, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_SINK, "%d", s->index); + + result = PROCESS_STREAM_RESULT_OK; + + break; + } } finish: @@ -2733,6 +2772,7 @@ static pa_hook_result_t source_output_new_cb(pa_core *core, pa_source_output_new process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_BUFFER_ATTR, true); process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_UPDATE_VOLUME, true); process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED, true); + process_stream(m, new_data, STREAM_SOURCE_OUTPUT, PROCESS_COMMAND_SET_AEC_REFERENCE_DEVICE, true); return PA_HOOK_OK; } -- 2.7.4 From edbaef30fbd6be4f8dcc1d67c937e138ac1da07b Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 29 Mar 2022 17:06:43 +0900 Subject: [PATCH 05/16] tizenaudio-echo-cancel: Support reference raw copy functionality processing echo-cancellation in module-tizenaudio-echo-cancel moved to processor modules for supporting reference copy. * created reference copy * removed delayq [Version] 15.0.10 [Issue Type] New Feature Change-Id: Id4529e7f6225b1c3f1d6e499bc915aaef1a940c5 Signed-off-by: Jaechul Lee --- Makefile.am | 3 +- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/algo_reference_copy.c | 106 ++++ src/echo-cancel/module-tizenaudio-echo-cancel.c | 623 +++++++----------------- src/echo-cancel/processor.c | 545 +++++++++++++++++---- src/echo-cancel/processor.h | 56 +-- src/module-tizenaudio-source2.c | 13 - 7 files changed, 765 insertions(+), 583 deletions(-) create mode 100644 src/echo-cancel/algo_reference_copy.c diff --git a/Makefile.am b/Makefile.am index c41e653..e61e057 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,6 +90,7 @@ module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_t libprocessor_la_SOURCES = \ src/echo-cancel/algo_speex.c \ + src/echo-cancel/algo_reference_copy.c \ src/echo-cancel/algo_adrian.c \ src/echo-cancel/adrian-aec.c \ src/echo-cancel/processor.c \ @@ -102,7 +103,7 @@ libprocessor_la_CFLAGS = $(AM_CFLAGS) $(PA_CFLAGS) $(LIBSPEEX_CFLAGS) if ENABLE_WEBRTC libprocessor_la_SOURCES += src/echo-cancel/algo_webrtc.cpp libprocessor_la_LIBADD += $(WEBRTC_LIBS) -libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -std=c++17 +libprocessor_la_CPPFLAGS = $(WEBRTC_CFLAGS) $(PA_CFLAGS) -DSUPPORT_METHOD_WEBRTC -std=c++17 endif module_tizenaudio_echo_cancel_la_SOURCES = src/echo-cancel/module-tizenaudio-echo-cancel.c src/echo-cancel/echo-cancel-def.h diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 38eda7d..2611033 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.9 +Version: 15.0.10 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/algo_reference_copy.c b/src/echo-cancel/algo_reference_copy.c new file mode 100644 index 0000000..2d9cf0b --- /dev/null +++ b/src/echo-cancel/algo_reference_copy.c @@ -0,0 +1,106 @@ +/*** + This file is part of PulseAudio. + + Copyright 2022 Jaechul Lee + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +struct reference_copy { + size_t nframes; + pa_sample_spec ss; + int reference_channels; +}; + +void *reference_copy_create(size_t nframes, pa_sample_spec *ss) { + struct reference_copy *rc; + + pa_assert(ss); + + rc = pa_xnew0(struct reference_copy, 1); + rc->nframes = nframes; + rc->ss = *ss; + + return rc; +} + +int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out) { + struct reference_copy *rc = priv; + size_t rec_bytes, ref_bytes, actual_bytes, total_bytes; + int8_t *dst = out; + + pa_assert(rc); + pa_assert(rec); + pa_assert(ref); + pa_assert(out); + + rec_bytes = pa_frame_size(&rc->ss); + ref_bytes = rc->reference_channels * pa_sample_size(&rc->ss); + actual_bytes = rec_bytes - ref_bytes; + total_bytes = rec_bytes * rc->nframes; + + while ((dst - out) < total_bytes) { + memcpy(dst, rec, actual_bytes); + memcpy(dst + actual_bytes, ref, ref_bytes); + dst += rec_bytes; + rec += rec_bytes; + ref += ref_bytes; + } + + return 0; +} + +int32_t reference_copy_destroy(void *priv) { + struct reference_copy *rc = priv; + + pa_assert(rc); + + pa_xfree(rc); + + return 0; +} + +int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map) { + struct reference_copy *rc = priv; + int channels; + + pa_assert(rc); + pa_assert(source_ss); + pa_assert(sample_spec); + pa_assert(map); + + channels = rc->ss.channels - source_ss->channels; + if (channels <= 0) + return -1; + + *sample_spec = rc->ss; + sample_spec->channels = rc->reference_channels = channels; + + pa_channel_map_init_auto(map, channels, PA_CHANNEL_MAP_AIFF); + + return 0; +} diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 2f95314..8aa456c 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -46,8 +46,9 @@ PA_MODULE_DESCRIPTION("Tizen Audio Echo Cancel"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( - "method= " - "blocksize= "); + "method= "); + +#define DEFAULT_PROCESS_MSEC 10 typedef struct echo_cancel pa_echo_cancel; struct userdata { @@ -67,7 +68,7 @@ struct userdata { bool enable; uint32_t n_source_output; - size_t blocksize; + char *default_method; pa_thread *thread; @@ -76,8 +77,8 @@ struct userdata { pa_echo_cancel *echo_cancel; /* use in thread */ - pa_memblockq *delayq; bool enable_in_thread; + bool triggered; pa_asyncmsgq *asyncmsgq_sink; pa_asyncmsgq *asyncmsgq_source; @@ -92,51 +93,42 @@ PA_DEFINE_PRIVATE_CLASS(pa_echo_cancel, pa_msgobject); #define PA_ECHO_CANCEL(o) (pa_echo_cancel_cast(o)) #define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) -#define CHECK_FLAGS_AEC(x) (x & PA_SOURCE_OUTPUT_ECHO_CANCEL) -#define CHECK_COUNT_SOURCE_OUTPUT_AEC(x) (x->n_source_output) #define DEFAULT_AEC_METHOD "speex" static const char* const valid_modargs[] = { "method", - "blocksize", NULL, }; -static int proplist_get_fragment_size(pa_proplist *p, size_t *size) { - const char *fragsize; - uint32_t blocksize; +static int proplist_get_fragment_size_usec(pa_proplist *p, pa_sample_spec *sample_spec, pa_usec_t *usec) { + const char *prop_fragsize; + uint32_t fragsize; + + pa_assert(p); + pa_assert(sample_spec); + pa_assert(usec); - if (!(fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE))) + if (!(prop_fragsize = pa_proplist_gets(p, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE))) return -1; - if (pa_atou(fragsize, &blocksize)) + if (pa_atou(prop_fragsize, &fragsize)) return -1; - *size = blocksize; + *usec = pa_bytes_to_usec(fragsize, sample_spec); return 0; } -static int pa_processor_get_method(pa_source_output *o, const char *default_method, pa_processor_algo_t *method) { - const char *selected, *requested; +static int proplist_get_method(pa_proplist *p, const char *default_method, pa_processor_method_t *method) { + const char *m; + pa_assert(p); pa_assert(method); - pa_assert(default_method); - requested = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); - if (!requested) + if (!(m = pa_proplist_gets(p, PA_PROP_MEDIA_ECHO_CANCEL_METHOD))) return -1; - selected = pa_streq(requested, "default") ? default_method : requested; - - if (pa_streq(selected, "webrtc")) - *method = PA_PROCESSOR_WEBRTC; - else if (pa_streq(selected, "speex")) - *method = PA_PROCESSOR_SPEEX; - else if (pa_streq(selected, "adrian")) - *method = PA_PROCESSOR_ADRIAN; - else - *method = PA_PROCESSOR_SPEEX; + *method = pa_processor_get_method(m, default_method); return 0; } @@ -150,38 +142,13 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { pa_source_output_assert_ref(o); - if (CHECK_FLAGS_AEC(o->flags)) + if (o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL) break; } return o ? o : NULL; } -static void free_source_output_extra_resource(pa_source_output *o) { - pa_assert(o); - - if (o->thread_info.processor) { - pa_processor_free(o->thread_info.processor); - o->thread_info.processor = NULL; - } - - if (o->thread_info.resampler2) { - pa_resampler_free(o->thread_info.resampler2); - o->thread_info.resampler2 = NULL; - } - - if (o->thread_info.echo) { - pa_memblockq_free(o->thread_info.echo); - o->thread_info.echo = NULL; - } -} - -static void free_source_output_extra_resource_by_source(pa_source *s) { - pa_assert(s); - - free_source_output_extra_resource(find_source_output_by_flags(s)); -} - static pa_usec_t get_round_trip_latency(struct userdata *u) { pa_usec_t sink_latency; pa_usec_t source_latency; @@ -189,65 +156,14 @@ static pa_usec_t get_round_trip_latency(struct userdata *u) { pa_assert(u); pa_assert(u->sink); - sink_latency = pa_sink_get_latency(u->sink); - source_latency = pa_source_get_latency(u->source); + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_latency, 0, NULL); + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_latency, 0, NULL); - pa_log_info("sink latency (%llu), source latency(%llu)", sink_latency, source_latency); + pa_log_info("sink latency (%" PRIu64 "), source latency(%" PRIu64 ")", sink_latency, source_latency); return sink_latency + source_latency; } -static int setup_delayq_latency(struct userdata *u, pa_usec_t latency) { - int64_t write_index, read_index; - size_t bytes, blocksize, n; - pa_memchunk silence; - - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) - return -1; - - if (u->delayq) - pa_memblockq_free(u->delayq); - - u->delayq = pa_memblockq_new("echo reference delay", - 0, - MEMBLOCKQ_MAXLENGTH, - 0, - &u->sink->sample_spec, - 0, - blocksize, - 0, - NULL); - - bytes = pa_usec_to_bytes(latency, &u->sink->sample_spec); - n = (bytes + blocksize - 1) / blocksize; - - pa_silence_memchunk_get( - &u->sink->core->silence_cache, - u->sink->core->mempool, - &silence, - &u->sink->sample_spec, - blocksize); - - if (!silence.memblock) - return -1; - - write_index = pa_memblockq_get_write_index(u->delayq); - read_index = pa_memblockq_get_read_index(u->delayq); - - pa_memblockq_flush_write(u->delayq, true); - while (n-- > 0) - pa_memblockq_push(u->delayq, &silence); - - pa_log_info("push n(%d) blocks. write_index(%llu->%llu), read_index(%llu->%llu)", - pa_memblockq_get_nblocks(u->delayq), - write_index, pa_memblockq_get_write_index(u->delayq), - read_index, pa_memblockq_get_read_index(u->delayq)); - - pa_memblock_unref(silence.memblock); - - return 0; -} - static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsgq *q) { struct arguments { pa_msgobject *o; @@ -277,48 +193,65 @@ static int send_rebuild_rtpoll(pa_msgobject *dst, pa_msgobject *src, pa_asyncmsg } /* Call from main thread */ -static void set_aec_state(struct userdata *u, bool enable) { +static void broadcast_echo_cancel_state(struct userdata *u, pa_source_output *o, bool enable) { void *v[2]; - pa_usec_t latency = 0ULL; pa_assert(u); pa_assert(u->source); - pa_assert(u->sink); /* not allow sink null */ - - pa_log_info("set_aec state %d -> %d", u->enable, enable); - - if (u->enable == enable) - return; + pa_assert(u->sink); - latency = enable ? get_round_trip_latency(u) : 0ULL; + if (enable) { + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink); + } v[0] = (void *)enable; - v[1] = &latency; + v[1] = o; - /* There is a race condition between the source thread and the render thread. - * pa_source_post function can be overlapped at the same time */ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->echo_cancel), PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE, (void *)v, 0, NULL); - if (u->sink) - pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), - PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)enable, 0, NULL, NULL); + if (!enable) { + send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); + send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + } +} + +static void set_echo_cancel_state(struct userdata *u, bool enable) { + pa_source_output *o; + + pa_assert(u); + pa_assert(u->source); + pa_assert(u->sink); + + if (u->enable == enable) + return; + + o = find_source_output_by_flags(u->source); + if (!o) { + pa_log_error("Failed to find EC source-output"); + return; + } + + broadcast_echo_cancel_state(u, o, enable); u->enable = enable; - pa_log_info("AEC state updated. enable(%d)", u->enable); + pa_log_info("AEC state is changed. enable(%d)", u->enable); } static int update_state_by_sink(struct userdata *u, bool enable) { pa_assert(u); - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) == 0) + if (u->n_source_output == 0) return 0; - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); return 0; } @@ -329,37 +262,18 @@ static int update_state_by_source(struct userdata *u, bool enable) { if (enable) { if (u->n_source_output++ == 0) { if (!u->sink || PA_SINK_IS_RUNNING(u->sink->state)) - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); } } else { if (--u->n_source_output == 0) - set_aec_state(u, enable); + set_echo_cancel_state(u, enable); } return 0; } -static int unlink_source_output_in_thread(pa_source_output *o) { - pa_assert(o); - - free_source_output_extra_resource(o); - - /* source-output should be remove by render thread to prevent race condition. - * 1. invoke REMOVE message - * 2. receive the message in tizenaudio-source - * 3. send the message to tizenaudio-echo-cancel - * 4. remove source-output in render thread - */ - pa_source_process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); - - return 0; -} - static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { pa_source_output *o = NULL; - pa_memchunk ochunk; - bool nf = false; - int r; o = find_source_output_by_flags(s); if (!o) { @@ -367,129 +281,19 @@ static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { return; } - if (o->thread_info.resampler2) { - pa_resampler_run(o->thread_info.resampler2, chunk, &ochunk); - chunk = &ochunk; - nf = true; - } - - r = pa_memblockq_push(o->thread_info.echo, chunk); - if (r != 0) - pa_log_error("Failed to push chunk to memblockq"); - - if (nf) - pa_memblock_unref(chunk->memblock); - - pa_log_debug("Pushed echo data. index(%u) size(%llums), nblocks(%d) index(%lld:%lld)", - o->index, - pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_memblockq_get_nblocks(o->thread_info.echo), - pa_memblockq_get_write_index(o->thread_info.echo), - pa_memblockq_get_read_index(o->thread_info.echo)); -} - -static void flush_echo_memblockq(pa_source *s) { - pa_source_output *o; - - o = find_source_output_by_flags(s); - if (!o) { - pa_log_error("Can't find aec source-output"); - return; - } - - pa_memblockq_flush_write(o->thread_info.echo, true); + if (pa_processor_push_data(o->thread_info.processor, chunk) < 0) + pa_log_error("Failed to push reference data"); } static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) { int ret = -1; - int8_t *rec, *ref, *out; - size_t blocksize; - - pa_memchunk tchunk; - - if (!o->thread_info.processor) { - pa_log_error("Failed to get processor"); - return ret; - } - - /* - * pre-condition - * sink block >= source block >= blocksize * n - * if blocksize is not described blocksize, It should be same as source's fragment size - * - * chunk must be processed every data. - */ - /* reference exist */ - if (o->thread_info.echo) { - size_t block_bytes, length, n; - - /* echo queue is not ready that means reference is not started */ - if (pa_memblockq_is_empty(o->thread_info.echo)) - return ret; - - blocksize = pa_processor_get_blocksize(o->thread_info.processor); - block_bytes = blocksize * pa_frame_size(&o->sample_spec); - - if (chunk->length % block_bytes) { - pa_log_warn("Skip to process aec. chunk size must be multiple of blocksize"); - return -1; - } - - n = chunk->length / block_bytes; - length = n * block_bytes; - - if (!(ret = pa_memblockq_peek_fixed_size(o->thread_info.echo, length, &tchunk))) { - int i; - - ochunk->index = 0; - ochunk->length = length; - ochunk->memblock = pa_memblock_new(o->core->mempool, length); - - rec = pa_memblock_acquire(chunk->memblock); - ref = pa_memblock_acquire(tchunk.memblock); - out = pa_memblock_acquire(ochunk->memblock); /* TODO: buffer can be shared rec buffer */ - - for (i=0; ithread_info.processor, - rec + (i * block_bytes), - ref + (i * block_bytes), - out + (i * block_bytes)); - - pa_memblock_release(chunk->memblock); - pa_memblock_release(tchunk.memblock); - pa_memblock_release(ochunk->memblock); - - pa_log_debug("Post-process. i(%u), rec(%llums), ref(%llums) " - "block(%llums), process(%llums) * n(%d) " - "silence(%d), index(%lld:%lld)", - o->index, - pa_bytes_to_usec(chunk->length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(tchunk.length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(length, &o->sample_spec) / PA_USEC_PER_MSEC, - pa_bytes_to_usec(block_bytes, &o->sample_spec) / PA_USEC_PER_MSEC, - n, - pa_memblock_is_silence(tchunk.memblock), - pa_memblockq_get_write_index(o->thread_info.echo), - pa_memblockq_get_read_index(o->thread_info.echo)); - - pa_memblock_unref(tchunk.memblock); - pa_memblockq_drop(o->thread_info.echo, tchunk.length); - } - } else { - /* no reference case like audio_share */ - rec = pa_memblock_acquire(chunk->memblock); - - ochunk->index = 0; - ochunk->length = chunk->length; - ochunk->memblock = pa_memblock_new(o->core->mempool, chunk->length); - out = pa_memblock_acquire(ochunk->memblock); - - pa_processor_process(o->thread_info.processor, rec, NULL, out); + pa_assert(o); + pa_assert(chunk); + pa_assert(ochunk); - pa_memblock_release(chunk->memblock); - pa_memblock_release(ochunk->memblock); - } + if ((ret = pa_processor_process(o->thread_info.processor, chunk, ochunk)) < 0) + pa_log_error("Failed to process data"); return ret; } @@ -504,86 +308,80 @@ static int process_msg( struct userdata *u = PA_ECHO_CANCEL(o)->u; - /* thread that pushes ref data should be called in render thread because of thread safe */ - switch (code) { - case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: { - /* a few pcm data will get lost. */ - if (!u->enable_in_thread) - return 0; + /* trigger resolves a race condition related to post_process between source and render thread */ + if (u->triggered) { + if (code == PA_ECHO_CANCEL_MESSAGE_PUSH_DATA || code == PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO) { + pa_source_output *o = NULL; + pa_usec_t latency; - pa_source_post(u->source, chunk); + o = find_source_output_by_flags(u->source); + o->post_process = post_process; - return 0; - } - case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: { - pa_memchunk ochunk; + latency = get_round_trip_latency(u); + + if (pa_processor_setup_reference_memblockq_padding(o->thread_info.processor, latency) < 0) + pa_log_warn("Failed to setup reference memblockq padding"); - if (!u->enable_in_thread) - return 0; + u->triggered = false; - pa_memblockq_push(u->delayq, chunk); + pa_log_info("Triggered Echo-Cancellation. index(%d), latency(%" PRIu64 ") usec", o->index, latency); + } + } - if (!pa_memblockq_peek(u->delayq, &ochunk)) { - pa_source_push_echo(u->source, &ochunk); + /* thread that pushes ref data should be called in render thread because of thread safe */ + switch (code) { + case PA_ECHO_CANCEL_MESSAGE_PUSH_DATA: + if (u->enable_in_thread) + pa_source_post(u->source, chunk); - pa_memblock_unref(ochunk.memblock); - pa_memblockq_drop(u->delayq, ochunk.length); - } + break; + case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: + if (u->enable_in_thread) + pa_source_push_echo(u->source, chunk); - return 0; - } - case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE : { + break; + case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE: { void **v = (void **)data; - pa_usec_t latency; + bool enable = (bool)v[0]; + pa_source_output *o = (pa_source_output *)v[1]; - u->enable_in_thread = !!(int)v[0]; - latency = *(pa_usec_t *)v[1]; + u->enable_in_thread = enable; - if (u->enable_in_thread) { - if (setup_delayq_latency(u, latency)) { - pa_log_error("Failed to init delayq"); - return 0; - } + if (enable) { + u->triggered = true; } else { - if (u->delayq) { - pa_memblockq_free(u->delayq); - u->delayq = NULL; - } - - flush_echo_memblockq(u->source); + pa_processor_flush(o->thread_info.processor); + o->post_process = NULL; } - pa_log_info("EC state change (%d)", u->enable_in_thread); - - return 0; + break; } - case PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK: - unlink_source_output_in_thread((pa_source_output *)data); - return 0; default: - return 0; + break; } + + return 0; } static pa_hook_result_t source_output_new_cb(pa_core *c, pa_source_output_new_data *data, void *userdata) { struct userdata *u = (struct userdata *)userdata; - const char *method; + pa_processor_method_t method; pa_assert(c); pa_assert(u); pa_assert(data); - method = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ECHO_CANCEL_METHOD); - if (!method) - return PA_HOOK_OK; - - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { - pa_log_error("Not allow multi aec instance"); + if (proplist_get_method(data->proplist, u->default_method, &method) < 0) return PA_HOOK_OK; - } - data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL; + /* TODO: source-output can be moved */ data->flags |= PA_SOURCE_OUTPUT_DONT_MOVE; + data->flags |= PA_SOURCE_OUTPUT_ECHO_CANCEL; + + if (method == PA_PROCESSOR_REFERENCE_COPY) + data->flags |= PA_SOURCE_OUTPUT_NO_REMAP; + + pa_log_info("echo-cancel source-output will be created. method(%d)", method); // TODO:add check limitation VARIOUS_RATE? return PA_HOOK_OK; @@ -613,72 +411,31 @@ static pa_sink *find_reference_sink_by_proplist(pa_core *c, pa_proplist *p) { return s; } -static int check_latency_validation(struct userdata *u, pa_sink *sink, pa_source *source) { - pa_usec_t sink_usec; - pa_usec_t source_usec; - pa_usec_t block_usec; - size_t blocksize; - - if (proplist_get_fragment_size(source->proplist, &blocksize)) { - pa_log_debug("Failed to get blocksize from source"); - return -1; - } - - source_usec = pa_bytes_to_usec(blocksize, &source->sample_spec); - block_usec = u->blocksize ? pa_bytes_to_usec(u->blocksize, &source->sample_spec) : source_usec; - - /* - * limitation - * sink block >= source block >= blocksize * n - */ - if (source_usec < block_usec) { - pa_log_debug("Need to check period size. source >= block * n. " - "source(%llums), block_usec(%llums)", - source_usec / PA_USEC_PER_MSEC, - block_usec / PA_USEC_PER_MSEC); - return -1; - } - - if (!u->sink) - return 0; - - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { - pa_log_debug("Failed to get blocksize from sink"); - return -1; - } - - sink_usec = pa_bytes_to_usec(blocksize, &u->sink->sample_spec); - - if (source_usec > sink_usec || sink_usec < block_usec) { - pa_log_debug("Need to check period size. sink >= source >= block * n. " - "source(%llums) sink(%llums) block_usec(%llums)", - source_usec / PA_USEC_PER_MSEC, - sink_usec / PA_USEC_PER_MSEC, - block_usec / PA_USEC_PER_MSEC); - return -1; - } - - return 0; -} - static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; - size_t blocksize = u->blocksize; - pa_processor_algo_t method; + pa_processor_method_t method; + pa_usec_t process_usec; + pa_usec_t sink_process_usec; + int r; pa_assert(c); pa_assert(o); pa_assert(u); pa_assert(o->source); - if (!CHECK_FLAGS_AEC(o->flags)) + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) return PA_HOOK_OK; - if (CHECK_COUNT_SOURCE_OUTPUT_AEC(u) > 0) { + if (u->n_source_output > 0) { pa_log_error("Not allow multi aec instance"); goto fail; } + if (proplist_get_method(o->proplist, u->default_method, &method) < 0) { + pa_log_error("Failed to get method"); + goto fail; + } + u->source = o->source; u->sink = find_reference_sink_by_proplist(c, o->proplist); if (!u->sink) { @@ -686,83 +443,46 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo goto fail; } - if (check_latency_validation(u, u->sink, u->source)) { - pa_log_error("Failed to check latency validation"); + /* Get period size of sink and source */ + if (proplist_get_fragment_size_usec(u->source->proplist, &u->source->sample_spec, &process_usec) < 0) { + pa_log_error("Failed to get fragment usec"); goto fail; } - /* Use the sources fragment size if blocksize is not specified */ - if (!blocksize) { - if (proplist_get_fragment_size(u->source->proplist, &blocksize)) { - pa_log_error("Failed to get blocksize"); - goto fail; - } + if (proplist_get_fragment_size_usec(u->sink->proplist, &u->sink->sample_spec, &sink_process_usec) < 0) { + pa_log_error("Failed to get fragment usec"); + goto fail; } - if (o->thread_info.resampler) - blocksize = pa_resampler_result(o->thread_info.resampler, blocksize); - - if (pa_processor_get_method(o, u->default_method, &method)) { - pa_log_error("Can't find method"); + if (sink_process_usec < process_usec) { + pa_log_error("sink process usec should be bigger than source"); goto fail; } - o->thread_info.processor = pa_processor_new(blocksize / pa_frame_size(&o->sample_spec), + o->thread_info.processor = pa_processor_new(c, process_usec / PA_USEC_PER_MSEC, &o->sample_spec, - method, PA_PROCESSOR_FLAGS_ECHO_CANCEL); + &o->channel_map, + &u->source->sample_spec, + method); if (!o->thread_info.processor) { pa_log_error("Failed to create pa_processor. echo-cancellation will be disabled"); goto fail; } - if (u->sink) { - if (proplist_get_fragment_size(u->sink->proplist, &blocksize)) { - pa_log_error("Failed to get blocksize"); - goto fail; - } - - if (!pa_sample_spec_equal(&u->sink->sample_spec, &o->sample_spec)) { - pa_resampler *resampler2; - - resampler2 = pa_resampler_new( - c->mempool, - &u->sink->sample_spec, &u->sink->channel_map, - &o->sample_spec, &o->channel_map, - c->lfe_crossover_freq, - c->resample_method, 0); - - if (!resampler2) { - pa_log_error("Failed to allocate resampler2 for echo-cancel"); - goto fail; - } - - o->thread_info.resampler2 = resampler2; - blocksize = pa_resampler_result(o->thread_info.resampler2, blocksize); - - pa_log_info("Use resampler2. blocksize(%d) bytes", blocksize); - } - - o->thread_info.echo = pa_memblockq_new("echo reference", - 0, - MEMBLOCKQ_MAXLENGTH, - 0, - &o->sample_spec, - 0, - blocksize, - 0, - &o->source->silence); - - if (!o->thread_info.echo) { - pa_log_error("Failed to alloc memblockq"); - goto fail; - } + r = pa_processor_bind_reference(o->thread_info.processor, + sink_process_usec / PA_USEC_PER_MSEC, + &u->sink->sample_spec, + &u->sink->channel_map); + if (r < 0) { + pa_log_error("Failed to bind reference source"); + goto fail; } - o->post_process = post_process; - - /* connect to sink and source */ - send_rebuild_rtpoll(PA_MSGOBJECT(u->source), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_source); - send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), PA_MSGOBJECT(u->echo_cancel), u->asyncmsgq_sink); + pa_log_info("echo-cancel source-output(%u) created. process_msec(%u), sink_process_msec(%u), method(%s)", + o->index, + (uint32_t)(process_usec / PA_USEC_PER_MSEC), + (uint32_t)(sink_process_usec / PA_USEC_PER_MSEC), + pa_processor_method_to_string(method)); update_state_by_source(u, true); @@ -770,30 +490,45 @@ static pa_hook_result_t source_output_put_cb(pa_core *c, pa_source_output *o, vo fail: o->flags &= ~PA_SOURCE_OUTPUT_ECHO_CANCEL; // TODO: need to consider DONT_MOVE define - free_source_output_extra_resource(o); + if (o->thread_info.processor) + pa_processor_free(o->thread_info.processor); return PA_HOOK_OK; } /* Call from main thread */ -static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) { +static pa_hook_result_t source_output_unlink_cb(pa_core *c, pa_source_output *o, void *userdata) { struct userdata *u = (struct userdata *)userdata; pa_assert(c); pa_assert(o); pa_assert(u); - if (!CHECK_FLAGS_AEC(o->flags)) + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) return PA_HOOK_OK; update_state_by_source(u, false); - send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); - send_rebuild_rtpoll(PA_MSGOBJECT(u->sink), NULL, NULL); + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_post_cb(pa_core *c, pa_source_output *o, void *userdata) { + struct userdata *u = (struct userdata *)userdata; + + pa_assert(c); + pa_assert(o); + pa_assert(u); + + if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) + return PA_HOOK_OK; + + pa_processor_free(o->thread_info.processor); u->source = NULL; u->sink = NULL; + pa_log_info("echo-cancel source-output(%u) is unlinked", o->index); + return PA_HOOK_OK; } @@ -877,7 +612,6 @@ fail: int pa__init(pa_module *m) { pa_modargs *ma = NULL; struct userdata *u = NULL; - uint32_t blocksize = 0; pa_assert(m); @@ -886,15 +620,9 @@ int pa__init(pa_module *m) { return -1; } - if (pa_modargs_get_value_u32(ma, "blocksize", &blocksize) < 0) { - pa_log_info("Failed to get blocksize"); - goto fail; - } - m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->m = m; - u->blocksize = blocksize; u->default_method = pa_xstrdup(pa_modargs_get_value(ma, "method", DEFAULT_AEC_METHOD)); u->echo_cancel = pa_msgobject_new(pa_echo_cancel); @@ -925,6 +653,10 @@ int pa__init(pa_module *m) { pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, u); + u->source_output_unlink_slot = + pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], + PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_cb, u); + u->source_output_unlink_post_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_post_cb, u); @@ -989,19 +721,20 @@ void pa__done(pa_module *m) { if (u->asyncmsgq_source) { if (u->source) { + pa_source_output *o; + pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_SET_AEC_STATE, (void *)false, 0, NULL, NULL); send_rebuild_rtpoll(PA_MSGOBJECT(u->source), NULL, NULL); - free_source_output_extra_resource_by_source(u->source); + + if ((o = find_source_output_by_flags(u->source))) + pa_processor_free(o->thread_info.processor); } pa_asyncmsgq_unref(u->asyncmsgq_source); } - if (u->delayq) - pa_memblockq_free(u->delayq); - if (u->rtpoll) pa_rtpoll_free(u->rtpoll); diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index 3681347..a97b99c 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -24,11 +24,16 @@ #endif #include +#include +#include #include #include +#include +#include #include "processor.h" +//#define __DEBUG__ #ifdef __DEBUG__ #include #include @@ -37,10 +42,42 @@ #include #endif -struct pa_processor_algo { +#define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) + +typedef struct pa_processor_method_interface pa_processor_method_interface; +struct pa_processor { + pa_core *core; + pa_processor_method_interface *intf; + pa_processor_method_t method; + void *priv; + + size_t process_frames; + pa_usec_t process_usec; + pa_usec_t reference_process_usec; + size_t process_bytes; + size_t reference_process_bytes; + + pa_sample_spec *output_ss; + pa_channel_map *output_chmap; + pa_sample_spec *source_ss; + pa_sample_spec reference_memblockq_ss; + + pa_resampler *resampler; + pa_memblockq *reference_memblockq; + pa_memblockq *output_memblockq; + +#ifdef __DEBUG__ + int fdrec, fdref, fdout; + struct timeval before, after; +#endif +}; + +struct pa_processor_method_interface { + const char *name; void *(*create)(size_t nframes, pa_sample_spec *ss); int32_t (*process)(void *priv, int8_t *rec, int8_t *ref, int8_t *out); int32_t (*destroy)(void *priv); + int32_t (*change_reference_spec)(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map); }; extern void *adrian_create(size_t nframes, pa_sample_spec *ss); @@ -51,145 +88,463 @@ extern void *speex_create(size_t nframes, pa_sample_spec *ss); extern int32_t speex_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); extern int32_t speex_destroy(void *priv); +#ifdef SUPPORT_METHOD_WEBRTC extern void *webrtc_audio_create(size_t nframes, pa_sample_spec *ss); extern int32_t webrtc_audio_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); extern int32_t webrtc_audio_destroy(void *priv); +#endif -pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags) { - pa_processor *processor = NULL; +extern void *reference_copy_create(size_t nframes, pa_sample_spec *ss); +extern int32_t reference_copy_process(void *priv, int8_t *rec, int8_t *ref, int8_t *out); +extern int32_t reference_copy_destroy(void *priv); +extern int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ss, pa_sample_spec *sample_spec, pa_channel_map *map); - pa_assert(ss); +#ifdef __DEBUG__ +static void debug_open_file(pa_processor *processor); +static void debug_timestamp_begin(pa_processor *processor); +static void debug_timestamp_end(pa_processor *processor); +static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out); +static void debug_close_file(pa_processor *processor); +#else +#define debug_open_file(x) +#define debug_timestamp_begin(x) +#define debug_timestamp_end(x) +#define debug_write_file(x, a, b, c) +#define debug_close_file(x) +#endif - if (ss->format != PA_SAMPLE_S16LE) { - pa_log_error("Not supported format(%d)", ss->format); +static struct pa_processor_method_interface method_table[PA_PROCESSOR_METHOD_MAX] = { + { + "speex", + speex_create, + speex_process, + speex_destroy, + NULL, + }, + { + "adrian", + adrian_create, + adrian_process, + adrian_destroy, + NULL, + }, +#ifdef SUPPORT_METHOD_WEBRTC + { + "webrtc", + webrtc_audio_create, + webrtc_audio_process, + webrtc_audio_destroy, + NULL, + }, +#endif + { + "reference_copy", + reference_copy_create, + reference_copy_process, + reference_copy_destroy, + reference_copy_change_reference_spec, + }, +}; + +static size_t pa_processor_usec_to_frame(pa_usec_t usec, pa_sample_spec *spec) { + pa_assert(spec); + + return pa_usec_to_bytes(usec, spec) / pa_frame_size(spec); +} + +pa_processor *pa_processor_new(pa_core *core, + uint32_t process_msec, + pa_sample_spec *output_ss, + pa_channel_map *output_map, + pa_sample_spec *source_ss, + pa_processor_method_t method) { + pa_processor *processor; + pa_memchunk silence; + + pa_assert(core); + pa_assert(output_ss); + pa_assert(output_map); + pa_assert(source_ss); + pa_assert(method < PA_PROCESSOR_METHOD_MAX); + + processor = pa_xnew0(pa_processor, 1); + processor->intf = &method_table[method]; + processor->core = core; + processor->process_usec = process_msec * PA_USEC_PER_MSEC; + processor->output_ss = output_ss; + processor->output_chmap = output_map; + processor->source_ss = source_ss; + processor->method = method; + processor->process_frames = pa_processor_usec_to_frame(processor->process_usec, processor->output_ss); + processor->process_bytes = pa_usec_to_bytes(processor->process_usec, processor->output_ss); + + if (!(processor->priv = processor->intf->create(processor->process_frames, output_ss))) { + pa_log_error("Failed to create processor. rate(%d), channels(%d).", output_ss->rate, output_ss->channels); + pa_xfree(processor); return NULL; } - processor = pa_xnew0(pa_processor, 1); - processor->intf = pa_xnew0(pa_processor_algo, 1); - processor->nframes = nframes; - processor->framesize = pa_frame_size(ss); - - switch (backend) { - case PA_PROCESSOR_SPEEX: - processor->intf->create = speex_create; - processor->intf->process = speex_process; - processor->intf->destroy = speex_destroy; - break; - case PA_PROCESSOR_ADRIAN: - processor->intf->create = adrian_create; - processor->intf->process = adrian_process; - processor->intf->destroy = adrian_destroy; - break; - case PA_PROCESSOR_WEBRTC: - processor->intf->create = webrtc_audio_create; - processor->intf->process = webrtc_audio_process; - processor->intf->destroy = webrtc_audio_destroy; - break; - default: - pa_log_error("Invalid backend(%d)", backend); - goto fail; + pa_silence_memchunk_get(&core->silence_cache, core->mempool, &silence, output_ss, 0); + processor->output_memblockq = pa_memblockq_new("source-output memblockq", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + processor->output_ss, + 0, + pa_usec_to_bytes(processor->process_usec, output_ss), + 0, + &silence); + pa_memblock_unref(silence.memblock); + + pa_log_info("Created processor. memblockq rate(%d), channels(%d), process_msec(%u), " + "process_bytes(%zu), method(%s), source rate(%d), channels(%d)", + output_ss->rate, + output_ss->channels, + process_msec, + pa_usec_to_bytes(processor->process_usec, output_ss), + method_table[method].name, + source_ss->rate, + source_ss->channels); + + debug_open_file(processor); + + return processor; +} + +int pa_processor_bind_reference(pa_processor *processor, + uint32_t process_msec, + pa_sample_spec *reference_ss, + pa_channel_map *reference_chmap) { + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_memchunk silence; + + pa_assert(processor); + pa_assert(processor->intf); + pa_assert(processor->output_ss); + pa_assert(processor->output_chmap); + pa_assert(reference_ss); + pa_assert(reference_chmap); + + processor->reference_process_usec = process_msec * PA_USEC_PER_MSEC; + if (processor->reference_process_usec < processor->process_usec) { + pa_log_error("Failed to bind reference. ref_usec(%" PRId64 "), process_usec(%" PRId64 ")", + processor->reference_process_usec, processor->process_usec); + return -1; } - pa_log_info("Use backend(%d) nframes(%zu) framesize(%d)", - backend, processor->nframes, processor->framesize); + sample_spec = *processor->output_ss; + channel_map = *processor->output_chmap; - if (!(processor->priv = processor->intf->create(nframes, ss))) { - pa_log_error("Failed to create processor"); - goto fail; + /* select reference memblockq sample_spec and channelmap */ + if (processor->intf->change_reference_spec) { + if (processor->intf->change_reference_spec(processor->priv, processor->source_ss, &sample_spec, &channel_map) < 0) { + pa_log_error("Failed to get reference info"); + return -1; + } } -#ifdef __DEBUG__ - { - static int n = 1; - char rec[32], ref[32], out[32]; + /* Create resampler */ + if (!pa_sample_spec_equal(reference_ss, &sample_spec)) { + if (processor->resampler) + pa_resampler_free(processor->resampler); + + processor->resampler = pa_resampler_new(processor->core->mempool, + reference_ss, reference_chmap, + &sample_spec, &channel_map, + processor->core->lfe_crossover_freq, + processor->core->resample_method, 0); + if (!processor->resampler) { + pa_log_error("Failed to allocate reference resampler"); + return -1; + } + } - snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n); - snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n); - snprintf(out, sizeof(out), "/tmp/out-%d.raw", n); - n += 1; + processor->reference_memblockq_ss = sample_spec; + processor->reference_process_bytes = pa_usec_to_bytes(processor->reference_process_usec, &processor->reference_memblockq_ss); - unlink(rec); - unlink(ref); - unlink(out); + /* Create memblockq */ + pa_silence_memchunk_get(&processor->core->silence_cache, processor->core->mempool, &silence, &sample_spec, 0); - processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777); - processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777); - processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777); - } -#endif + if (processor->reference_memblockq) + pa_memblockq_free(processor->reference_memblockq); - return processor; + processor->reference_memblockq = pa_memblockq_new("reference memblockq", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &processor->reference_memblockq_ss, + 0, + processor->reference_process_bytes, + 0, + &silence); + pa_memblock_unref(silence.memblock); -fail: - pa_xfree(processor->intf); - pa_xfree(processor); + pa_log_debug("Created reference memblockq rate(%d), channels(%d), msec(%u), bytes(%zu)", + sample_spec.rate, + sample_spec.channels, + process_msec, + processor->reference_process_bytes); - return NULL; + return 0; } -int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) { - int ret = -1; +int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency) { + pa_memchunk silence; + int64_t write_index, read_index; + size_t n, bytes; pa_assert(processor); + + bytes = pa_usec_to_bytes(latency, &processor->reference_memblockq_ss); + n = (bytes + (processor->reference_process_bytes - 1)) / processor->reference_process_bytes; + + pa_silence_memchunk_get( + &processor->core->silence_cache, + processor->core->mempool, + &silence, + &processor->reference_memblockq_ss, + processor->reference_process_bytes); + + write_index = pa_memblockq_get_write_index(processor->reference_memblockq); + read_index = pa_memblockq_get_read_index(processor->reference_memblockq); + + for (; n > 0; n--) + pa_memblockq_push(processor->reference_memblockq, &silence); + + pa_memblock_unref(silence.memblock); + + pa_log_info("push n(%u) silence blocks. ref_process_bytes(%zu) " + "write_index(%" PRId64 "->%" PRId64 "), read_index(%" PRId64 "->%" PRId64 ")", + pa_memblockq_get_nblocks(processor->reference_memblockq), + processor->reference_process_bytes, + write_index, pa_memblockq_get_write_index(processor->reference_memblockq), + read_index, pa_memblockq_get_read_index(processor->reference_memblockq)); + + return 0; +} + +// TODO naming +int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchunk *ochunk) { + int r = -1; + int8_t *recording = NULL; + int8_t *reference = NULL; + int8_t *output = NULL; + bool silence = false; + + pa_memchunk ichunk, rchunk; + + pa_assert(processor); + pa_assert(processor->output_memblockq); + pa_assert(processor->reference_memblockq); pa_assert(processor->intf); - pa_assert(rec); - pa_assert(out); + pa_assert(processor->process_bytes > 0ULL); + pa_assert(processor->reference_process_bytes > 0ULL); + pa_assert(chunk); + pa_assert(ochunk); + + if ((r = pa_memblockq_push(processor->output_memblockq, chunk)) < 0) { + pa_log_error("Failed to push chunk to echo memblockq"); + return r; + } -#ifdef __DEBUG__ - if (write(processor->fdrec, rec, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write rec buffer"); + if ((r = pa_memblockq_peek_fixed_size(processor->reference_memblockq, processor->reference_process_bytes, &rchunk)) < 0) { + pa_log_error("Failed to get memblock from reference memblockq"); + return r; + } + silence = pa_memblock_is_silence(rchunk.memblock); - if (write(processor->fdref, ref, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write ref buffer"); + if ((r = pa_memblockq_peek_fixed_size(processor->output_memblockq, processor->process_bytes, &ichunk)) < 0) { + pa_log_error("Failed to get memblock from output memblockq"); + return r; + } - gettimeofday(&processor->before, NULL); -#endif + ochunk->index = 0; + ochunk->length = ichunk.length; + ochunk->memblock = pa_memblock_new(processor->core->mempool, ochunk->length); - if (processor->intf->process) - ret = processor->intf->process(processor->priv, rec, ref, out); + recording = pa_memblock_acquire(ichunk.memblock); + reference = pa_memblock_acquire(rchunk.memblock); + output = pa_memblock_acquire(ochunk->memblock); -#ifdef __DEBUG__ - if (write(processor->fdout, out, processor->nframes * processor->framesize) <= 0) - pa_log_error("Failed to write out buffer"); + debug_timestamp_begin(processor); - gettimeofday(&processor->after, NULL); + r = processor->intf->process(processor->priv, recording, reference, output); - pa_log_debug("It takes time(%ld) bytes(%d)", - 1000 * (after.tv_sec-before.tv_sec) + (after.tv_usec-before.tv_usec) / 1000, - processor->buffer_size); -#endif + debug_timestamp_end(processor); + debug_write_file(processor, recording, reference, output); + + pa_memblock_release(ichunk.memblock); + pa_memblock_release(rchunk.memblock); + pa_memblock_release(ochunk->memblock); - return ret; + pa_memblock_unref(rchunk.memblock); + pa_memblockq_drop(processor->reference_memblockq, rchunk.length); + + pa_memblock_unref(ichunk.memblock); + pa_memblockq_drop(processor->output_memblockq, ichunk.length); + + pa_log_debug("Post-process. rec(%" PRIu64 "ms), ref(%" PRIu64 "msms) out(%" PRIu64 "ms), " + "silence(%d), windex:rindex(%" PRId64 ":%" PRId64 ")", + pa_bytes_to_usec(ichunk.length, processor->output_ss) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(rchunk.length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, + pa_bytes_to_usec(ochunk->length, processor->output_ss) / PA_USEC_PER_MSEC, + silence, + pa_memblockq_get_write_index(processor->reference_memblockq), + pa_memblockq_get_read_index(processor->reference_memblockq)); + + return r; +} + +int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { + pa_memchunk ochunk; + int r; + + pa_assert(processor); + pa_assert(chunk); + + if (processor->resampler) { + pa_resampler_run(processor->resampler, chunk, &ochunk); + chunk = &ochunk; + } + + if ((r = pa_memblockq_push(processor->reference_memblockq, chunk)) < 0) + pa_log_error("Failed to push chunk to echo memblockq"); + + if (processor->resampler) + pa_memblock_unref(chunk->memblock); + + pa_log_debug("Pushed echo data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", + chunk->length, + pa_bytes_to_usec(chunk->length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, + pa_memblockq_get_nblocks(processor->reference_memblockq), + pa_memblockq_get_write_index(processor->reference_memblockq), + pa_memblockq_get_read_index(processor->reference_memblockq)); + + return r; +} + +void pa_processor_flush(pa_processor *processor) { + pa_assert(processor); + + if (processor->reference_memblockq) + pa_memblockq_flush_read(processor->reference_memblockq); + + if (processor->output_memblockq) + pa_memblockq_flush_read(processor->output_memblockq); } int pa_processor_free(pa_processor *processor) { pa_assert(processor); + pa_assert(processor->priv); pa_assert(processor->intf); - pa_assert(processor->intf->destroy); - -#ifdef __DEBUG__ - if (processor->fdrec) - close(processor->fdrec); - if (processor->fdref) - close(processor->fdref); - if (processor->fdout) - close(processor->fdout); -#endif - if (processor->intf->destroy(processor->priv)) { + if (processor->intf->destroy(processor->priv) < 0) pa_log_error("Failed to destroy processor"); - return -1; - } - pa_xfree(processor->intf); + if (processor->resampler) + pa_resampler_free(processor->resampler); + + if (processor->reference_memblockq) + pa_memblockq_free(processor->reference_memblockq); + + if (processor->output_memblockq) + pa_memblockq_free(processor->output_memblockq); + + debug_close_file(processor); + pa_xfree(processor); return 0; } -size_t pa_processor_get_blocksize(pa_processor *processor) { - pa_assert(processor); +pa_processor_method_t pa_processor_get_method(const char *request_method, const char *default_method) { + const char *selected; + + if (!request_method || !default_method) + return PA_PROCESSOR_SPEEX; + + selected = pa_streq(request_method, "default") ? default_method : request_method; - return processor->nframes; + if (pa_streq(selected, "speex")) + return PA_PROCESSOR_SPEEX; + else if (pa_streq(selected, "adrian")) + return PA_PROCESSOR_ADRIAN; +#ifdef SUPPORT_METHOD_WEBRTC + else if (pa_streq(selected, "webrtc")) + return PA_PROCESSOR_WEBRTC; +#endif + else if (pa_streq(selected, "reference_copy")) + return PA_PROCESSOR_REFERENCE_COPY; + else + return PA_PROCESSOR_SPEEX; +} + +const char *pa_processor_method_to_string(pa_processor_method_t method) { + if (method >= PA_PROCESSOR_METHOD_MAX) + return NULL; + + return method_table[method].name; +} + +#ifdef __DEBUG__ +static void debug_open_file(pa_processor *processor) { + static int n = 1; + char rec[32], ref[32], out[32]; + + snprintf(rec, sizeof(rec), "/tmp/rec-%d.raw", n); + snprintf(ref, sizeof(ref), "/tmp/ref-%d.raw", n); + snprintf(out, sizeof(out), "/tmp/out-%d.raw", n); + n += 1; + + unlink(rec); + unlink(ref); + unlink(out); + + processor->fdrec = open(rec, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdref = open(ref, O_RDWR | O_CREAT | O_TRUNC, 777); + processor->fdout = open(out, O_RDWR | O_CREAT | O_TRUNC, 777); +} + +static void debug_timestamp_begin(pa_processor *processor) { + gettimeofday(&processor->before, NULL); +} + +static void debug_timestamp_end(pa_processor *processor) { + gettimeofday(&processor->after, NULL); + + pa_log_debug("It takes time (%ld)ms.", + 1000 * (processor->after.tv_sec - processor->before.tv_sec) + + (processor->after.tv_usec - processor->before.tv_usec) / 1000); +} + +static void debug_write_file(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out) { + if (rec && write(processor->fdrec, rec, processor->process_bytes) <= 0) + pa_log_error("Failed to write recording buffer"); + + if (ref && write(processor->fdref, ref, processor->reference_process_bytes) <= 0) + pa_log_error("Failed to write reference buffer"); + + if (out && write(processor->fdout, out, processor->process_bytes) <= 0) + pa_log_error("Failed to write ref buffer"); +} + +static void debug_close_file(pa_processor *processor) { + if (processor->fdrec) { + close(processor->fdrec); + processor->fdrec = -1; + } + + if (processor->fdref) { + close(processor->fdref); + processor->fdref = -1; + } + + if (processor->fdout) { + close(processor->fdout); + processor->fdout = -1; + } } +#endif + diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h index fa642ce..fa77e87 100644 --- a/src/echo-cancel/processor.h +++ b/src/echo-cancel/processor.h @@ -1,6 +1,3 @@ -#ifndef foopulseprocessorfoo -#define foopulseprocessorfoo - /*** This file is part of PulseAudio. @@ -22,44 +19,47 @@ USA. ***/ +#ifndef foopulseprocessorfoo +#define foopulseprocessorfoo + #ifdef HAVE_CONFIG_H #include #endif +#include +#include +#include #include -//#define __DEBUG__ - -typedef enum { - PA_PROCESSOR_FLAGS_ECHO_CANCEL, - PA_PROCESSOR_FLAGS_NOISE_SUPPRESSION, -} pa_process_flags_t; - typedef enum { PA_PROCESSOR_SPEEX, PA_PROCESSOR_ADRIAN, +#ifdef SUPPORT_METHOD_WEBRTC PA_PROCESSOR_WEBRTC, -} pa_processor_algo_t; +#endif + PA_PROCESSOR_REFERENCE_COPY, + PA_PROCESSOR_METHOD_MAX, +} pa_processor_method_t; -typedef struct pa_processor_algo pa_processor_algo; typedef struct pa_processor pa_processor; -struct pa_processor { - pa_processor_algo *intf; - void *priv; - void *userdata; - size_t nframes; - size_t framesize; - -#ifdef __DEBUG__ - int fdrec, fdref, fdout; - struct timeval before, after; -#endif -}; - -pa_processor *pa_processor_new(size_t nframes, pa_sample_spec *ss, pa_processor_algo_t backend, pa_process_flags_t flags); -int pa_processor_process(pa_processor *processor, int8_t *rec, int8_t *ref, int8_t *out); +pa_processor *pa_processor_new(pa_core *core, + uint32_t process_msec, + pa_sample_spec *output_ss, + pa_channel_map *output_map, + pa_sample_spec *source_ss, + pa_processor_method_t method); +int pa_processor_bind_reference(pa_processor *processor, + uint32_t process_msec, + pa_sample_spec *reference_ss, + pa_channel_map *reference_chmap); +int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency); +int pa_processor_process(pa_processor *processor, pa_memchunk *rec, pa_memchunk *out); +int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk); +void pa_processor_flush(pa_processor *processor); int pa_processor_free(pa_processor *processor); -size_t pa_processor_get_blocksize(pa_processor *processor); +pa_processor_method_t pa_processor_get_method(const char *requset_method, const char *default_method); +const char *pa_processor_method_to_string(pa_processor_method_t method); #endif + diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 0fc047c..5bd3a61 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -279,19 +279,6 @@ static int source_process_msg( return 0; } - case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { - pa_source_output *o = (pa_source_output *)data; - - if (!(o->flags & PA_SOURCE_OUTPUT_ECHO_CANCEL)) - break; - - if (u->ec_asyncmsgq) { - pa_asyncmsgq_send(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_SOURCE_OUTPUT_UNLINK, o, 0, NULL); - } - - return 0; - } case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { struct arguments { pa_msgobject *o; -- 2.7.4 From 1a8e6880d70f0705c4c6e5917a34bee3a56baf14 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Thu, 21 Apr 2022 12:52:07 +0900 Subject: [PATCH 06/16] sound-player: Fix INTEGER_OVERFLOW defect [Version] 15.0.11 [Issue Type] Svace Change-Id: I224d3ac72c27a1b184baf72933bbe5618a073d45 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/module-sound-player.c | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 2611033..9122970 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.10 +Version: 15.0.11 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/module-sound-player.c b/src/module-sound-player.c index 16212ae..ce505a8 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -711,12 +711,12 @@ static void deinit_ipc(struct userdata *u) { #endif } -static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { +static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { struct userdata *u = userdata; struct ipc_data data; - int ret = 0; - int data_size = 0; - int read_sum = 0; + ssize_t ret = 0; + size_t data_size = 0; + size_t read_sum = 0; int retry_count = 0; pa_assert(io); @@ -728,15 +728,18 @@ static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io } if (events & PA_IO_EVENT_INPUT) { - data_size = sizeof(data); + data_size = sizeof(struct ipc_data); memset(&data, 0, data_size); + while (read_sum != data_size && retry_count < RETRY_NUM) { - ret = read(fd, (void*)(((char *)&data)+read_sum), data_size-read_sum); - if (ret < 0 && errno == EAGAIN) + ret = read(fd, (void *)(((char *)&data) + read_sum), data_size - read_sum); + if (ret < 0) { retry_count++; - else - read_sum += ret; + continue; + } + read_sum += (size_t)ret; } + if (read_sum == data_size) { pa_log_info("name(%s), role(%s), volume_gain_type(%s), method(%s)", data.filename, data.role, data.volume_gain_type, data.method); -- 2.7.4 From 740ec5fd89f85b26072c3c2a0b4f3a125ee009ba Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 21 Apr 2022 17:15:38 +0900 Subject: [PATCH 07/16] tizenaudio-echo-cancel: Fix UNINIT.LOCAL_VAR This patch fixes the svace and convention issues * Fix svace issue * Relace echo with referece [Version] 15.0.12 [Issue Type] SVACE Change-Id: I8ebe7e724c090fc4ce8bfa120b0e596d95723a2d Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 33 ++++++++++++------------- src/echo-cancel/processor.c | 6 ++--- src/echo-cancel/processor.h | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 9122970..6c31737 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.11 +Version: 15.0.12 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index 8aa456c..d27a2cd 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -150,8 +150,8 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { } static pa_usec_t get_round_trip_latency(struct userdata *u) { - pa_usec_t sink_latency; - pa_usec_t source_latency; + pa_usec_t sink_latency = 0ULL; + pa_usec_t source_latency = 0ULL; pa_assert(u); pa_assert(u->sink); @@ -272,19 +272,6 @@ static int update_state_by_source(struct userdata *u, bool enable) { return 0; } -static void pa_source_push_echo(pa_source *s, pa_memchunk *chunk) { - pa_source_output *o = NULL; - - o = find_source_output_by_flags(s); - if (!o) { - pa_log_error("Can't find aec source-output"); - return; - } - - if (pa_processor_push_data(o->thread_info.processor, chunk) < 0) - pa_log_error("Failed to push reference data"); -} - static int post_process(pa_source_output *o, pa_memchunk *chunk, pa_memchunk *ochunk) { int ret = -1; @@ -336,8 +323,20 @@ static int process_msg( break; case PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO: - if (u->enable_in_thread) - pa_source_push_echo(u->source, chunk); + if (u->enable_in_thread) { + pa_source_output *o = NULL; + + pa_assert(u->source); + + o = find_source_output_by_flags(u->source); + if (!o) { + pa_log_error("Can't find aec source-output"); + break; + } + + if (pa_processor_push_reference(o->thread_info.processor, chunk) < 0) + pa_log_error("Failed to push reference data"); + } break; case PA_ECHO_CANCEL_MESSAGE_SET_AEC_STATE: { diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index a97b99c..5b8916b 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -345,7 +345,7 @@ int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchun pa_assert(ochunk); if ((r = pa_memblockq_push(processor->output_memblockq, chunk)) < 0) { - pa_log_error("Failed to push chunk to echo memblockq"); + pa_log_error("Failed to push chunk to reference memblockq"); return r; } @@ -410,12 +410,12 @@ int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { } if ((r = pa_memblockq_push(processor->reference_memblockq, chunk)) < 0) - pa_log_error("Failed to push chunk to echo memblockq"); + pa_log_error("Failed to push chunk to reference memblockq"); if (processor->resampler) pa_memblock_unref(chunk->memblock); - pa_log_debug("Pushed echo data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", + pa_log_debug("Pushed reference data. bytes(%zu), msec(%" PRIu64 "ms), nblocks(%d) index(%" PRId64 ":%" PRId64 ")", chunk->length, pa_bytes_to_usec(chunk->length, &processor->reference_memblockq_ss) / PA_USEC_PER_MSEC, pa_memblockq_get_nblocks(processor->reference_memblockq), diff --git a/src/echo-cancel/processor.h b/src/echo-cancel/processor.h index fa77e87..ea07143 100644 --- a/src/echo-cancel/processor.h +++ b/src/echo-cancel/processor.h @@ -55,7 +55,7 @@ int pa_processor_bind_reference(pa_processor *processor, pa_channel_map *reference_chmap); int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_usec_t latency); int pa_processor_process(pa_processor *processor, pa_memchunk *rec, pa_memchunk *out); -int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk); +int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk); void pa_processor_flush(pa_processor *processor); int pa_processor_free(pa_processor *processor); pa_processor_method_t pa_processor_get_method(const char *requset_method, const char *default_method); -- 2.7.4 From fb4e3bb1fe76f82d3839d19d2a95b7d73042a642 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 14 Apr 2022 15:00:24 +0900 Subject: [PATCH 08/16] tizenaudio-sink/source: Fix crash when pulseaudio exit * Fix pulseaudio crash (pactl exit) * symbol mismatching(pa_processor_push_reference) * aarch64 build warning [Version] 15.0.13 [Issue Type] Bug Change-Id: I0d7f0d44a95cd9667746529ed1f03151ec92e64a Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/processor.c | 2 +- src/module-sound-player.c | 2 +- src/module-tizenaudio-sink.c | 3 +++ src/module-tizenaudio-sink2.c | 3 +++ src/module-tizenaudio-source.c | 3 +++ src/module-tizenaudio-source2.c | 3 +++ 7 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 6c31737..d1d68f6 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.12 +Version: 15.0.13 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index 5b8916b..0052ab8 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -397,7 +397,7 @@ int pa_processor_process(pa_processor *processor, pa_memchunk *chunk, pa_memchun return r; } -int pa_processor_push_data(pa_processor *processor, pa_memchunk *chunk) { +int pa_processor_push_reference(pa_processor *processor, pa_memchunk *chunk) { pa_memchunk ochunk; int r; diff --git a/src/module-sound-player.c b/src/module-sound-player.c index ce505a8..cf55d04 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -753,7 +753,7 @@ static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io else pa_log_error("Invalid method!!!"); } else { - pa_log_warn("Fail to read, retry_count(%d), read sum(%d), err(%s)", retry_count, read_sum, pa_cstrerror(errno)); + pa_log_warn("Fail to read, retry_count(%d), read sum(%zu), err(%s)", retry_count, read_sum, pa_cstrerror(errno)); } } diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 6c56de9..0ea7846 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -796,5 +796,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index a81ac70..36675c3 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -654,5 +654,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-source.c b/src/module-tizenaudio-source.c index 4beabf5..e3ed819 100644 --- a/src/module-tizenaudio-source.c +++ b/src/module-tizenaudio-source.c @@ -700,5 +700,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 5bd3a61..a47dbbe 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -619,5 +619,8 @@ void pa__done(pa_module*m) { pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); } + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + pa_xfree(u); } -- 2.7.4 From 4fc34cf8fb2339ba0936e6bd3dce38cbb16f2807 Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Fri, 29 Apr 2022 10:42:16 +0900 Subject: [PATCH 09/16] tizenaudio-echo-cancel: Fix coverity defect (Dereference null return value) [Version] 15.0.14 [Issue Type] Coverity Change-Id: I69359a1fc8dfd988ab37cc24a9cc4f2f287396eb Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/module-tizenaudio-echo-cancel.c | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index d1d68f6..21f6ccc 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.13 +Version: 15.0.14 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index d27a2cd..f9e13c0 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -146,6 +146,9 @@ static pa_source_output *find_source_output_by_flags(pa_source *s) { break; } + if (!o) + pa_log_error("Failed to find AEC source-output"); + return o ? o : NULL; } @@ -234,10 +237,8 @@ static void set_echo_cancel_state(struct userdata *u, bool enable) { return; o = find_source_output_by_flags(u->source); - if (!o) { - pa_log_error("Failed to find EC source-output"); + if (!o) return; - } broadcast_echo_cancel_state(u, o, enable); u->enable = enable; @@ -302,6 +303,11 @@ static int process_msg( pa_usec_t latency; o = find_source_output_by_flags(u->source); + if (!o) { + u->triggered = false; + return 0; + } + o->post_process = post_process; latency = get_round_trip_latency(u); @@ -329,10 +335,8 @@ static int process_msg( pa_assert(u->source); o = find_source_output_by_flags(u->source); - if (!o) { - pa_log_error("Can't find aec source-output"); + if (!o) break; - } if (pa_processor_push_reference(o->thread_info.processor, chunk) < 0) pa_log_error("Failed to push reference data"); -- 2.7.4 From bcc7b8b7639a80d1c2541ed6ca0dcd1692095c3f Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 30 Nov 2021 11:19:34 +0900 Subject: [PATCH 10/16] Separate tizenaudio-sink2 and module-tizenaudio-sink2 pa_tizenaudio_source2_new/sink2_new were added in order to support usb device playback/capture. These function will be called in module-tizenaudio-sink2/source2 and module-alsa-card. [Version] 15.0.15 [Issue Type] Improvement Change-Id: Id1d2d852f4c8982ac4c622cd2d326589ca46ca69 Signed-off-by: Jaechul Lee --- Makefile.am | 14 +- packaging/pulseaudio-modules-tizen.spec | 3 +- src/module-tizenaudio-sink2.c | 585 +------------------------------ src/module-tizenaudio-source2.c | 550 +----------------------------- src/tizenaudio-sink2.c | 585 +++++++++++++++++++++++++++++++ src/tizenaudio-sink2.h | 38 +++ src/tizenaudio-source2.c | 587 ++++++++++++++++++++++++++++++++ src/tizenaudio-source2.h | 38 +++ 8 files changed, 1284 insertions(+), 1116 deletions(-) create mode 100644 src/tizenaudio-sink2.c create mode 100644 src/tizenaudio-sink2.h create mode 100644 src/tizenaudio-source2.c create mode 100644 src/tizenaudio-source2.h diff --git a/Makefile.am b/Makefile.am index e61e057..ed2843f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,7 @@ pulsemodlibexec_LTLIBRARIES = \ libhal-interface.la \ libprocessor.la \ libcommunicator.la \ + libtizenaudio-util.la \ module-tizenaudio-sink.la \ module-tizenaudio-source.la \ module-tizenaudio-sink2.la \ @@ -78,14 +79,19 @@ module_tizenaudio_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_tizenaudio_source_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la module_tizenaudio_source_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source -module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c src/echo-cancel/echo-cancel-def.h +libtizenaudio_util_la_SOURCES = src/tizenaudio-sink2.c src/tizenaudio-sink2.h src/tizenaudio-source2.c src/tizenaudio-source2.h src/echo-cancel/echo-cancel-def.h +libtizenaudio_util_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version +libtizenaudio_util_la_LIBADD = $(AM_LIBADD) $(PA_LIBS) libhal-interface.la +libtizenaudio_util_la_CFLAGS = $(MODULE_CFLAGS) + +module_tizenaudio_sink2_la_SOURCES = src/module-tizenaudio-sink2.c module_tizenaudio_sink2_la_LDFLAGS = $(MODULE_LDFLAGS) -module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la +module_tizenaudio_sink2_la_LIBADD = $(MODULE_LIBADD) libtizenaudio-util.la module_tizenaudio_sink2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_sink2 -module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c src/echo-cancel/echo-cancel-def.h +module_tizenaudio_source2_la_SOURCES = src/module-tizenaudio-source2.c module_tizenaudio_source2_la_LDFLAGS = $(MODULE_LDFLAGS) -module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libhal-interface.la +module_tizenaudio_source2_la_LIBADD = $(MODULE_LIBADD) libtizenaudio-util.la module_tizenaudio_source2_la_CFLAGS = $(MODULE_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_source2 libprocessor_la_SOURCES = \ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 21f6ccc..0dccc40 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.14 +Version: 15.0.15 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -91,6 +91,7 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-publish.so %{_libdir}/pulse-%{module_ver}/modules/module-tizenaudio-echo-cancel.so %{_libdir}/pulse-%{module_ver}/modules/libprocessor.so +%{_libdir}/pulse-%{module_ver}/modules/libtizenaudio-util.so %{_libdir}/pulse-%{module_ver}/modules/libhal-interface.so %{_libdir}/pulse-%{module_ver}/modules/libcommunicator.so %{_tmpfilesdir}/pulseaudio.conf diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index 36675c3..0861262 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -23,30 +23,9 @@ #include #endif -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include "hal-interface.h" -#include "echo-cancel/echo-cancel-def.h" +#include "tizenaudio-sink2.h" PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Sink2"); @@ -63,41 +42,6 @@ PA_MODULE_USAGE( "fragments= " "fragment_size= "); -#define DEFAULT_SINK_NAME "tizenaudio-sink2" - -#define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 -#define DEFAULT_FRAGMENTS 4 - -struct userdata { - pa_core *core; - pa_module *module; - pa_sink *sink; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - pa_usec_t timestamp; - - void *pcm_handle; - uint32_t nfrags; - uint32_t frag_size; - - char* card; - char* device; - bool first; - bool echo_on; - - pa_rtpoll_item *rtpoll_item; - - uint64_t write_count; - pa_hal_interface *hal_interface; - - pa_msgobject *ec_object; - pa_asyncmsgq *ec_asyncmsgq; - pa_rtpoll_item *ec_poll_item; -}; - static const char* const valid_modargs[] = { "sink_name", "sink_properties", @@ -111,496 +55,19 @@ static const char* const valid_modargs[] = { NULL }; -static int build_pollfd(struct userdata *u) { - int32_t ret; - struct pollfd *pollfd; - int fd = -1; - - pa_assert(u); - pa_assert(u->pcm_handle); - pa_assert(u->rtpoll); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); - if (ret < 0 || fd < 0) { - pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); - return -1; - } - pollfd->fd = fd; - pollfd->events = POLLOUT | POLLERR | POLLNVAL; - - return 0; -} - -/* Called from IO context */ -static int suspend(struct userdata *u) { - int32_t ret; - pa_assert(u); - pa_assert(u->pcm_handle); - - ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - if (ret) { - pa_log_error("Error closing PCM device %x", ret); - } - u->pcm_handle = NULL; - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -/* Called from IO context */ -static int unsuspend(struct userdata *u) { - pa_sample_spec sample_spec; - int32_t ret; - size_t frame_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - sample_spec = u->sink->sample_spec; - frame_size = pa_frame_size(&sample_spec); - if (frame_size == 0) { - pa_log_error("Unexpected frame size zero!"); - goto fail; - } - - ret = pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_OUT, - &sample_spec, - u->frag_size / frame_size, - u->nfrags, - (void **)&u->pcm_handle); - if (ret) { - pa_log_error("Error opening PCM device %x", ret); - goto fail; - } - - if (build_pollfd(u) < 0) - goto fail; - - u->write_count = 0; - u->first = true; - u->timestamp = pa_rtclock_now(); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - u->pcm_handle = NULL; - } - return -PA_ERR_IO; -} - -/* Called from the IO thread. */ -static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - int r; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* It may be that only the suspend cause is changing, in which case there's - * nothing to do. */ - if (new_state == s->thread_info.state) - return 0; - - switch (new_state) { - case PA_SINK_SUSPENDED: { - pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); - if ((r = suspend(u)) < 0) - return r; - break; - } - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: { - if (s->thread_info.state == PA_SINK_INIT) { - if (build_pollfd(u) < 0) - return -PA_ERR_IO; - } - - if (s->thread_info.state == PA_SINK_SUSPENDED) { - if ((r = unsuspend(u)) < 0) - return r; - } - break; - } - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - case PA_SINK_INVALID_STATE: - break; - } - - return 0; -} - -static int sink_process_msg( - pa_msgobject *o, - int code, - void *data, - int64_t offset, - pa_memchunk *chunk) { - - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - case PA_SINK_MESSAGE_SET_AEC_STATE: { - u->echo_on = !!data; - pa_log_info("EC state changed (%d)", u->echo_on); - return 0; - } - case PA_SINK_MESSAGE_GET_LATENCY: { - int64_t r = 0; - - if (u->pcm_handle) - r = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - pa_rtclock_now(); - - *((int64_t *) data) = r; - - return 0; - } - case PA_SINK_MESSAGE_REBUILD_RTPOLL: { - struct arguments { - pa_msgobject *o; - pa_asyncmsgq *q; - } *args; - - args = (struct arguments *)data; - - if (args) { - u->ec_object = args->o; - u->ec_asyncmsgq = args->q; - u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); - } else { - pa_rtpoll_item_free(u->ec_poll_item); - u->ec_poll_item = NULL; - u->ec_object = NULL; - } - - return 0; - } - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -static void process_rewind(struct userdata *u) { -#if 1 - /* Rewind not supported */ - pa_sink_process_rewind(u->sink, 0); -#else - size_t rewind_nbytes, in_buffer; - pa_usec_t delay; - - pa_assert(u); - - rewind_nbytes = u->sink->thread_info.rewind_nbytes; - - if (!PA_SINK_IS_OPENED(u->sink->thread_info.state) || rewind_nbytes <= 0) - goto do_nothing; - - pa_log_debug("Requested to rewind %lu bytes.", (unsigned long)rewind_nbytes); - - if (u->timestamp <= now) - goto do_nothing; - - delay = u->timestamp - now; - in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); - - if (in_buffer <= 0) - goto do_nothing; - - if (rewind_nbytes > in_buffer) - rewind_nbytes = in_buffer; - - pa_sink_process_rewind(u->sink, rewind_nbytes); - u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); - - pa_log_debug("Rewound %lu bytes.", (unsigned long)rewind_nbytes); - return; - -do_nothing: - pa_sink_process_rewind(u->sink, 0); -#endif -} - -static int process_render(struct userdata *u) { - void *p; - size_t frame_size = pa_frame_size(&u->sink->sample_spec); - size_t frames_to_write = u->frag_size / frame_size; - uint32_t avail = 0; - pa_memchunk chunk; - - pa_assert(u); - - pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); - - if (frames_to_write > avail) - return 0; - - pa_sink_render_full(u->sink, u->frag_size, &chunk); - p = pa_memblock_acquire(chunk.memblock); - - if (pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write)) { - pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); - return -1; - } - - pa_memblock_release(chunk.memblock); - - if (u->echo_on) { - pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL); - } - - pa_memblock_unref(chunk.memblock); - - u->write_count += chunk.length; - - return 0; -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_thread_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - - u->timestamp = pa_rtclock_now(); - - for (;;) { - int ret; - - if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) - process_rewind(u); - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - if (process_render(u)) - goto fail; - - if (u->first) { - pa_log_info("Starting playback."); - pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); - u->first = false; - } - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - struct pollfd *pollfd; - if (u->rtpoll_item) { - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - revents = pollfd->revents; - if (revents & ~POLLOUT) { - pa_log_debug("Poll error 0x%x occured, try recover.", revents); - pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); - u->first = true; - revents = 0; - } - } - } - } - -fail: - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -int pa__init(pa_module*m) { - struct userdata *u = NULL; - pa_sample_spec ss; - pa_channel_map map; +int pa__init(pa_module *m) { pa_modargs *ma = NULL; - pa_sink_new_data data; - uint32_t alternate_sample_rate; - const char *modarg_device; - char card[DEVICE_NAME_MAX]; - char device[DEVICE_NAME_MAX]; - size_t frame_size, buffer_size, period_frames, buffer_frames; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments."); + pa_log("Failed to parse module arguments"); goto fail; } - ss = m->core->default_sample_spec; - map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log_error("Invalid sample format specification or channel map"); + if (!(m->userdata = pa_tizenaudio_sink2_new(m, ma, __FILE__))) goto fail; - } - - alternate_sample_rate = m->core->alternate_sample_rate; - if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { - pa_log_error("Failed to parse alternate sample rate"); - goto fail; - } - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - u->first = true; - u->timestamp = 0ULL; - u->hal_interface = pa_hal_interface_get(u->core); - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); - goto fail; - } - - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); - u->nfrags = DEFAULT_FRAGMENTS; - if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || - pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { - pa_log_error("fragment_size or fragments are invalid."); - goto fail; - } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); - - pa_sink_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); - pa_sink_new_data_set_sample_spec(&data, &ss); - pa_sink_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio sink")); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); - pa_proplist_sets(data.proplist, "tizen.card", u->card); - pa_proplist_sets(data.proplist, "tizen.device", u->device); - pa_proplist_sets(data.proplist, "tizen.version", "2"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - buffer_size = u->frag_size * u->nfrags; - buffer_frames = buffer_size / frame_size; - period_frames = u->frag_size / frame_size; - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); - - if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log_error("Invalid properties."); - pa_sink_new_data_done(&data); - goto fail; - } - - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); - pa_sink_new_data_done(&data); - - if (!u->sink) { - pa_log_error("Failed to create sink object."); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - u->sink->userdata = u; - - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_OUT, - &u->sink->sample_spec, - u->frag_size / pa_frame_size(&u->sink->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - - pa_sink_set_max_request(u->sink, buffer_size); - pa_sink_set_max_rewind(u->sink, buffer_size); - - if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) { - pa_log_error("Failed to create thread."); - goto fail; - } - pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(buffer_size, &ss)); - pa_sink_put(u->sink); pa_modargs_free(ma); return 0; @@ -610,52 +77,24 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } int pa__get_n_used(pa_module *m) { - struct userdata *u; + pa_sink *s; pa_assert(m); - pa_assert_se((u = m->userdata)); + pa_assert_se((s = m->userdata)); - return pa_sink_linked_by(u->sink); + return pa_sink_linked_by(s); } -void pa__done(pa_module*m) { - struct userdata *u; +void pa__done(pa_module *m) { + pa_sink *s; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->sink) - pa_sink_unref(u->sink); - - pa_xfree(u->card); - pa_xfree(u->device); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->pcm_handle) { - pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - } - - if (u->hal_interface) - pa_hal_interface_unref(u->hal_interface); - - pa_xfree(u); + if ((s = m->userdata)) + pa_tizenaudio_sink2_free(s); } diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index a47dbbe..1df595e 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -23,30 +23,9 @@ #include #endif -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include "hal-interface.h" -#include "echo-cancel/echo-cancel-def.h" +#include "tizenaudio-source2.h" PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Source2"); @@ -63,43 +42,6 @@ PA_MODULE_USAGE( "fragments= " "fragment_size= "); -#define DEFAULT_SOURCE_NAME "tizenaudio-source2" - -#define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 -#define DEFAULT_FRAGMENTS 4 - -struct userdata { - pa_core *core; - pa_module *module; - pa_source *source; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - void *pcm_handle; - uint32_t nfrags; - uint32_t frag_size; - - pa_usec_t timestamp; - - char* card; - char* device; - bool first; - bool echo_on; - - pa_rtpoll_item *rtpoll_item; - - uint64_t read_count; - pa_usec_t latency_time; - pa_hal_interface *hal_interface; - - pa_msgobject *ec_object; - pa_asyncmsgq *ec_asyncmsgq; - pa_rtpoll_item *ec_poll_item; -}; - static const char* const valid_modargs[] = { "source_name", "source_properties", @@ -113,459 +55,19 @@ static const char* const valid_modargs[] = { NULL }; -static int build_pollfd(struct userdata *u) { - int32_t ret; - struct pollfd *pollfd; - int fd = -1; - - pa_assert(u); - pa_assert(u->pcm_handle); - pa_assert(u->rtpoll); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); - if (ret < 0 || fd < 0) { - pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); - return -1; - } - pollfd->fd = fd; - pollfd->events = POLLIN | POLLERR | POLLNVAL; - - return 0; -} - -/* Called from IO context */ -static int suspend(struct userdata *u) { - int32_t ret; - pa_assert(u); - pa_assert(u->pcm_handle); - - ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - if (ret) { - pa_log_error("Error closing PCM device %x", ret); - } - u->pcm_handle = NULL; - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -/* Called from IO context */ -static int unsuspend(struct userdata *u) { - pa_sample_spec sample_spec; - int32_t ret; - size_t frame_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - sample_spec = u->source->sample_spec; - frame_size = pa_frame_size(&sample_spec); - if (frame_size == 0) { - pa_log_error("Unexpected frame size zero!"); - goto fail; - } - - ret = pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_IN, - &sample_spec, - u->frag_size / frame_size, - u->nfrags, - (void **)&u->pcm_handle); - if (ret) { - pa_log_error("Error opening PCM device %x", ret); - goto fail; - } - - if (build_pollfd(u) < 0) - goto fail; - - u->read_count = 0; - u->first = true; - u->timestamp = pa_rtclock_now(); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - u->pcm_handle = NULL; - } - return -PA_ERR_IO; -} - -/* Called from the IO thread. */ -static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - int r; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* It may be that only the suspend cause is changing, in which case there's - * nothing more to do. */ - if (new_state == s->thread_info.state) - return 0; - - switch (new_state) { - case PA_SOURCE_SUSPENDED: { - pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); - if ((r = suspend(u)) < 0) - return r; - break; - } - - case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: { - if (s->thread_info.state == PA_SOURCE_INIT) { - if (build_pollfd(u) < 0) - return -PA_ERR_IO; - } - - if (s->thread_info.state == PA_SOURCE_SUSPENDED) { - if ((r = unsuspend(u)) < 0) - return r; - } - break; - } - - case PA_SOURCE_UNLINKED: - case PA_SOURCE_INIT: - case PA_SOURCE_INVALID_STATE: - break; - } - - return 0; -} - -static int source_process_msg( - pa_msgobject *o, - int code, - void *data, - int64_t offset, - pa_memchunk *chunk) { - - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - case PA_SOURCE_MESSAGE_SET_AEC_STATE: { - u->echo_on = !!data; - pa_log_info("EC state changed (%d)", u->echo_on); - return 0; - } - case PA_SOURCE_MESSAGE_GET_LATENCY: { - uint64_t r = 0; - - if (u->pcm_handle) - r = pa_rtclock_now() - (u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec)); - - *((int64_t *) data) = r; - - return 0; - } - case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { - struct arguments { - pa_msgobject *o; - pa_asyncmsgq *q; - } *args; - - args = (struct arguments *)data; - - if (args) { - u->ec_object = args->o; - u->ec_asyncmsgq = args->q; - u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); - } else { - pa_rtpoll_item_free(u->ec_poll_item); - u->ec_poll_item = NULL; - u->ec_object = NULL; - } - - return 0; - } - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -static int process_render(struct userdata *u) { - void *p; - size_t frame_size = pa_frame_size(&u->source->sample_spec); - size_t frames_to_read = u->frag_size / frame_size; - uint32_t avail = 0; - pa_memchunk chunk; - - pa_assert(u); - - /* Fill the buffer up the latency size */ - - pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); - if (frames_to_read > avail) { - pa_log_debug("not enough avail size. frames_to_read(%zu), avail(%d)", frames_to_read, avail); - return 0; - } - - chunk.length = u->frag_size; - chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); - - p = pa_memblock_acquire(chunk.memblock); - if (pa_hal_interface_pcm_read(u->hal_interface, u->pcm_handle, p, (uint32_t)frames_to_read)) { - pa_log_error("failed to read pcm. p(%p), size(%zu)", p, frames_to_read); - return -1; - } - pa_memblock_release(chunk.memblock); - - chunk.index = 0; - chunk.length = (size_t)frames_to_read * frame_size; - - if (u->echo_on) { - pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, - PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL); - } else { - pa_source_post(u->source, &chunk); - } - - pa_memblock_unref(chunk.memblock); - - u->read_count += chunk.length; - - return 0; -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_thread_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - - u->timestamp = pa_rtclock_now(); - - for (;;) { - int ret; - - /* Render some data and drop it immediately */ - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - if (process_render(u)) - goto fail; - - if (u->first) { - pa_log_info("Starting capture."); - pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); - u->first = false; - } - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - struct pollfd *pollfd; - if (u->rtpoll_item) { - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - revents = pollfd->revents; - if (revents & ~POLLIN) { - pa_log_debug("Poll error 0x%x occured, try recover.", revents); - pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); - u->first = true; - revents = 0; - } - } - } - } - -fail: - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -int pa__init(pa_module*m) { - struct userdata *u = NULL; - pa_sample_spec ss; - pa_channel_map map; +int pa__init(pa_module *m) { pa_modargs *ma = NULL; - pa_source_new_data data; - uint32_t alternate_sample_rate; - const char *modarg_device; - char card[DEVICE_NAME_MAX]; - char device[DEVICE_NAME_MAX]; - size_t frame_size, buffer_size, period_frames, buffer_frames; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments."); + pa_log("Failed to parse module arguments"); goto fail; } - ss = m->core->default_sample_spec; - map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log_error("Invalid sample format specification or channel map"); + if (!(m->userdata = pa_tizenaudio_source2_new(m, ma, __FILE__))) goto fail; - } - - alternate_sample_rate = m->core->alternate_sample_rate; - if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { - pa_log_error("Failed to parse alternate sample rate"); - goto fail; - } - m->userdata = u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - u->first = true; - u->hal_interface = pa_hal_interface_get(u->core); - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); - goto fail; - } - - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); - u->nfrags = DEFAULT_FRAGMENTS; - if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || - pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { - pa_log_error("fragment_size or fragments are invalid."); - goto fail; - } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); - - pa_source_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); - pa_source_new_data_set_sample_spec(&data, &ss); - pa_source_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio source")); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); - pa_proplist_sets(data.proplist, "tizen.card", u->card); - pa_proplist_sets(data.proplist, "tizen.device", u->device); - pa_proplist_sets(data.proplist, "tizen.version", "2"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - buffer_size = u->frag_size * u->nfrags; - buffer_frames = buffer_size / frame_size; - period_frames = u->frag_size / frame_size; - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); - - if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log_error("Invalid properties."); - pa_source_new_data_done(&data); - goto fail; - } - - u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); - - if (!u->source) { - pa_log_error("Failed to create source object."); - goto fail; - } - - u->source->parent.process_msg = source_process_msg; - u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; - u->source->userdata = u; - - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_IN, - &u->source->sample_spec, - u->frag_size / pa_frame_size(&u->source->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } - - pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); - pa_source_set_rtpoll(u->source, u->rtpoll); - - u->timestamp = 0ULL; - - if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) { - pa_log_error("Failed to create thread."); - goto fail; - } - pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(buffer_size, &ss)); - pa_source_put(u->source); pa_modargs_free(ma); return 0; @@ -575,52 +77,24 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } int pa__get_n_used(pa_module *m) { - struct userdata *u; + pa_source *s; pa_assert(m); - pa_assert_se((u = m->userdata)); + pa_assert_se((s = m->userdata)); - return pa_source_linked_by(u->source); + return pa_source_linked_by(s); } -void pa__done(pa_module*m) { - struct userdata *u; +void pa__done(pa_module *m) { + pa_source *s; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->source) - pa_source_unlink(u->source); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->source) - pa_source_unref(u->source); - - pa_xfree(u->card); - pa_xfree(u->device); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->pcm_handle) { - pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); - pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); - } - - if (u->hal_interface) - pa_hal_interface_unref(u->hal_interface); - - pa_xfree(u); + if ((s = m->userdata)) + pa_tizenaudio_source2_free(s); } diff --git a/src/tizenaudio-sink2.c b/src/tizenaudio-sink2.c new file mode 100644 index 0000000..a7ffd93 --- /dev/null +++ b/src/tizenaudio-sink2.c @@ -0,0 +1,585 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" + +#define DEFAULT_SINK_NAME "tizenaudio-sink2" + +#define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_usec_t timestamp; + + void *pcm_handle; + uint32_t nfrags; + uint32_t frag_size; + + char* card; + char* device; + bool first; + bool echo_on; + + pa_rtpoll_item *rtpoll_item; + + uint64_t write_count; + pa_hal_interface *hal_interface; + + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; +}; + +static void userdata_free(struct userdata *u); + +static int build_pollfd(struct userdata *u) { + int32_t ret; + struct pollfd *pollfd; + int fd = -1; + + pa_assert(u); + pa_assert(u->pcm_handle); + pa_assert(u->rtpoll); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); + if (ret < 0 || fd < 0) { + pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); + return -1; + } + pollfd->fd = fd; + pollfd->events = POLLOUT | POLLERR | POLLNVAL; + + return 0; +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int32_t ret; + pa_assert(u); + pa_assert(u->pcm_handle); + + ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + if (ret) { + pa_log_error("Error closing PCM device %x", ret); + } + u->pcm_handle = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +/* Called from IO context */ +static int unsuspend(struct userdata *u) { + pa_sample_spec sample_spec; + int32_t ret; + size_t frame_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + sample_spec = u->sink->sample_spec; + frame_size = pa_frame_size(&sample_spec); + if (frame_size == 0) { + pa_log_error("Unexpected frame size zero!"); + goto fail; + } + + ret = pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_OUT, + &sample_spec, + u->frag_size / frame_size, + u->nfrags, + (void **)&u->pcm_handle); + if (ret) { + pa_log_error("Error opening PCM device %x", ret); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + u->write_count = 0; + u->first = true; + u->timestamp = pa_rtclock_now(); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + u->pcm_handle = NULL; + } + return -PA_ERR_IO; +} + +/* Called from the IO thread. */ +static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + struct userdata *u; + int r; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + /* It may be that only the suspend cause is changing, in which case there's + * nothing to do. */ + if (new_state == s->thread_info.state) + return 0; + + switch (new_state) { + case PA_SINK_SUSPENDED: { + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); + if ((r = suspend(u)) < 0) + return r; + break; + } + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: { + if (s->thread_info.state == PA_SINK_INIT) { + if (build_pollfd(u) < 0) + return -PA_ERR_IO; + } + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + break; + } + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + break; + } + + return 0; +} + +static int sink_process_msg( + pa_msgobject *o, + int code, + void *data, + int64_t offset, + pa_memchunk *chunk) { + + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + case PA_SINK_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } + case PA_SINK_MESSAGE_GET_LATENCY: { + int64_t r = 0; + + if (u->pcm_handle) + r = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - pa_rtclock_now(); + + *((int64_t *) data) = r; + + return 0; + } + case PA_SINK_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void process_rewind(struct userdata *u) { + pa_sink_process_rewind(u->sink, 0); +} + +static int process_render(struct userdata *u) { + void *p; + size_t frame_size = pa_frame_size(&u->sink->sample_spec); + size_t frames_to_write = u->frag_size / frame_size; + uint32_t avail = 0; + pa_memchunk chunk; + + pa_assert(u); + + pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); + if (frames_to_write > avail) + return 0; + + pa_sink_render_full(u->sink, u->frag_size, &chunk); + p = pa_memblock_acquire(chunk.memblock); + + if (pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write)) { + pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); + return -1; + } + + pa_memblock_release(chunk.memblock); + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO, NULL, 0, &chunk, NULL); + } + + pa_memblock_unref(chunk.memblock); + + u->write_count += chunk.length; + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_thread_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + process_rewind(u); + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (process_render(u)) + goto fail; + + if (u->first) { + pa_log_info("Starting playback."); + pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); + u->first = false; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + struct pollfd *pollfd; + if (u->rtpoll_item) { + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + revents = pollfd->revents; + if (revents & ~POLLOUT) { + pa_log_debug("Poll error 0x%x occured, try recover.", revents); + pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); + u->first = true; + revents = 0; + } + } + } + } + +fail: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static int parse_to_get_card(const char *modarg_device, char *card) { + const char *name_p; + char *card_p; + + if (!strchr(modarg_device, ',')) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + name_p = modarg_device; + card_p = card; + while (*name_p != ',') + *(card_p++) = *(name_p++); + *card_p = '\0'; + + return 0; +} + +static int parse_to_get_device(const char *modarg_device, char *device) { + const char *comma_p; + char *device_p; + + if (!(comma_p = strchr(modarg_device, ','))) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + comma_p++; + device_p = device; + while (*comma_p != '\0') + *(device_p++) = *(comma_p++); + *device_p = '\0'; + + return 0; +} + +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_sink_new_data data; + uint32_t alternate_sample_rate; + const char *modarg_device; + char card[DEVICE_NAME_MAX]; + char device[DEVICE_NAME_MAX]; + size_t frame_size, buffer_size, period_frames, buffer_frames; + + pa_assert(m); + pa_assert(ma); + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log_error("Invalid sample format specification or channel map"); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log_error("Failed to parse alternate sample rate"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->first = true; + u->timestamp = 0ULL; + u->hal_interface = pa_hal_interface_get(u->core); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { + pa_log_error("device is invalid"); + goto fail; + } + + if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { + pa_log_error("failed to parse device module argument, %s", modarg_device); + goto fail; + } + + u->card = pa_xstrdup(card); + u->device = pa_xstrdup(device); + + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; + if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || + pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { + pa_log_error("fragment_size or fragments are invalid."); + goto fail; + } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + pa_sink_new_data_init(&data); + data.driver = driver; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio sink2")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + pa_proplist_sets(data.proplist, "tizen.card", u->card); + pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log_error("Invalid properties."); + pa_sink_new_data_done(&data); + goto fail; + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log_error("Failed to create sink object."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; + u->sink->userdata = u; + + if (pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_OUT, + &u->sink->sample_spec, + u->frag_size / pa_frame_size(&u->sink->sample_spec), + u->nfrags, + (void **)&u->pcm_handle)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + pa_sink_set_max_request(u->sink, buffer_size); + pa_sink_set_max_rewind(u->sink, buffer_size); + + if (!(u->thread = pa_thread_new("tizenaudio-sink2", thread_func, u))) { + pa_log_error("Failed to create thread."); + goto fail; + } + + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(buffer_size, &ss)); + pa_sink_put(u->sink); + + return u->sink; + +fail: + userdata_free(u); + return NULL; +} + +static void userdata_free(struct userdata *u) { + pa_assert(u); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + pa_xfree(u->card); + pa_xfree(u->device); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pcm_handle) { + pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + } + + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + + pa_xfree(u); +} + +void pa_tizenaudio_sink2_free(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se((u = s->userdata)); + + userdata_free(u); +} diff --git a/src/tizenaudio-sink2.h b/src/tizenaudio-sink2.h new file mode 100644 index 0000000..dd9e9e2 --- /dev/null +++ b/src/tizenaudio-sink2.h @@ -0,0 +1,38 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifndef footizenaudiosink2hfoo +#define footizenaudiosink2hfoo + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver); + +void pa_tizenaudio_sink2_free(pa_sink *s); + +#endif + diff --git a/src/tizenaudio-source2.c b/src/tizenaudio-source2.c new file mode 100644 index 0000000..8ddfa2f --- /dev/null +++ b/src/tizenaudio-source2.c @@ -0,0 +1,587 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal-interface.h" +#include "echo-cancel/echo-cancel-def.h" + +#define DEFAULT_SOURCE_NAME "tizenaudio-source2" + +#define DEVICE_NAME_MAX 30 +#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENTS 4 + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + void *pcm_handle; + uint32_t nfrags; + uint32_t frag_size; + + pa_usec_t timestamp; + + char* card; + char* device; + bool first; + bool echo_on; + + pa_rtpoll_item *rtpoll_item; + + uint64_t read_count; + pa_usec_t latency_time; + pa_hal_interface *hal_interface; + + /* for echo-cancel */ + pa_msgobject *ec_object; + pa_asyncmsgq *ec_asyncmsgq; + pa_rtpoll_item *ec_poll_item; +}; + +static void userdata_free(struct userdata *u); + +static int build_pollfd(struct userdata *u) { + int32_t ret; + struct pollfd *pollfd; + int fd = -1; + + pa_assert(u); + pa_assert(u->pcm_handle); + pa_assert(u->rtpoll); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + ret = pa_hal_interface_pcm_get_fd(u->hal_interface, u->pcm_handle, &fd); + if (ret < 0 || fd < 0) { + pa_log_error("Failed to get fd(%d) of PCM device %d", fd, ret); + return -1; + } + pollfd->fd = fd; + pollfd->events = POLLIN | POLLERR | POLLNVAL; + + return 0; +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int32_t ret; + pa_assert(u); + pa_assert(u->pcm_handle); + + ret = pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + if (ret) { + pa_log_error("Error closing PCM device %x", ret); + } + u->pcm_handle = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +/* Called from IO context */ +static int unsuspend(struct userdata *u) { + pa_sample_spec sample_spec; + int32_t ret; + size_t frame_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + sample_spec = u->source->sample_spec; + frame_size = pa_frame_size(&sample_spec); + if (frame_size == 0) { + pa_log_error("Unexpected frame size zero!"); + goto fail; + } + + ret = pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_IN, + &sample_spec, + u->frag_size / frame_size, + u->nfrags, + (void **)&u->pcm_handle); + if (ret) { + pa_log_error("Error opening PCM device %x", ret); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + u->read_count = 0; + u->first = true; + u->timestamp = pa_rtclock_now(); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + u->pcm_handle = NULL; + } + return -PA_ERR_IO; +} + +/* Called from the IO thread. */ +static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + struct userdata *u; + int r; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + /* It may be that only the suspend cause is changing, in which case there's + * nothing more to do. */ + if (new_state == s->thread_info.state) + return 0; + + switch (new_state) { + case PA_SOURCE_SUSPENDED: { + pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); + if ((r = suspend(u)) < 0) + return r; + break; + } + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: { + if (s->thread_info.state == PA_SOURCE_INIT) { + if (build_pollfd(u) < 0) + return -PA_ERR_IO; + } + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + break; + } + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + case PA_SOURCE_INVALID_STATE: + break; + } + + return 0; +} + +static int source_process_msg( + pa_msgobject *o, + int code, + void *data, + int64_t offset, + pa_memchunk *chunk) { + + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + case PA_SOURCE_MESSAGE_SET_AEC_STATE: { + u->echo_on = !!data; + pa_log_info("EC state changed (%d)", u->echo_on); + return 0; + } + case PA_SOURCE_MESSAGE_GET_LATENCY: { + uint64_t r = 0; + + if (u->pcm_handle) + r = pa_rtclock_now() - (u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec)); + + *((int64_t *) data) = r; + + return 0; + } + case PA_SOURCE_MESSAGE_REBUILD_RTPOLL: { + struct arguments { + pa_msgobject *o; + pa_asyncmsgq *q; + } *args; + + args = (struct arguments *)data; + + if (args) { + u->ec_object = args->o; + u->ec_asyncmsgq = args->q; + u->ec_poll_item = pa_rtpoll_item_new_asyncmsgq_write(u->rtpoll, PA_RTPOLL_EARLY, args->q); + } else { + pa_rtpoll_item_free(u->ec_poll_item); + u->ec_poll_item = NULL; + u->ec_object = NULL; + } + + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int process_render(struct userdata *u) { + void *p; + size_t frame_size = pa_frame_size(&u->source->sample_spec); + size_t frames_to_read = u->frag_size / frame_size; + uint32_t avail = 0; + pa_memchunk chunk; + + pa_assert(u); + + /* Fill the buffer up the latency size */ + + pa_hal_interface_pcm_available(u->hal_interface, u->pcm_handle, &avail); + if (frames_to_read > avail) { + pa_log_debug("not enough avail size. frames_to_read(%zu), avail(%u)", frames_to_read, avail); + return 0; + } + + chunk.length = u->frag_size; + chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + if (pa_hal_interface_pcm_read(u->hal_interface, u->pcm_handle, p, (uint32_t)frames_to_read)) { + pa_log_error("failed to read pcm. p(%p), size(%zu)", p, frames_to_read); + return -1; + } + pa_memblock_release(chunk.memblock); + + chunk.index = 0; + chunk.length = (size_t)frames_to_read * frame_size; + + if (u->echo_on) { + pa_asyncmsgq_post(u->ec_asyncmsgq, u->ec_object, + PA_ECHO_CANCEL_MESSAGE_PUSH_DATA, NULL, 0, &chunk, NULL); + } else { + pa_source_post(u->source, &chunk); + } + + pa_memblock_unref(chunk.memblock); + + u->read_count += chunk.length; + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_thread_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + /* Render some data and drop it immediately */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + if (process_render(u)) + goto fail; + + if (u->first) { + pa_log_info("Starting capture."); + pa_hal_interface_pcm_start(u->hal_interface, u->pcm_handle); + u->first = false; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + struct pollfd *pollfd; + if (u->rtpoll_item) { + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + revents = pollfd->revents; + if (revents & ~POLLIN) { + pa_log_debug("Poll error 0x%x occured, try recover.", revents); + pa_hal_interface_pcm_recover(u->hal_interface, u->pcm_handle, revents); + u->first = true; + revents = 0; + } + } + } + } + +fail: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static int parse_to_get_card(const char *modarg_device, char *card) { + const char *name_p; + char *card_p; + + if (!strchr(modarg_device, ',')) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + name_p = modarg_device; + card_p = card; + while (*name_p != ',') + *(card_p++) = *(name_p++); + *card_p = '\0'; + + return 0; +} + +static int parse_to_get_device(const char *modarg_device, char *device) { + const char *comma_p; + char *device_p; + + if (!(comma_p = strchr(modarg_device, ','))) { + pa_log_error("Failed to parse device argument : no comma"); + return -1; + } + + comma_p++; + device_p = device; + while (*comma_p != '\0') + *(device_p++) = *(comma_p++); + *device_p = '\0'; + + return 0; +} + +pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_source_new_data data; + uint32_t alternate_sample_rate; + const char *modarg_device; + char card[DEVICE_NAME_MAX]; + char device[DEVICE_NAME_MAX]; + size_t frame_size, buffer_size, period_frames, buffer_frames; + + pa_assert(m); + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log_error("Invalid sample format specification or channel map"); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log_error("Failed to parse alternate sample rate"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->first = true; + u->hal_interface = pa_hal_interface_get(u->core); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { + pa_log_error("device is invalid"); + goto fail; + } + + if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { + pa_log_error("failed to parse device module argument, %s", modarg_device); + goto fail; + } + + u->card = pa_xstrdup(card); + u->device = pa_xstrdup(device); + + u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); + u->nfrags = DEFAULT_FRAGMENTS; + if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || + pa_modargs_get_value_u32(ma, "fragments", &u->nfrags) < 0) { + pa_log_error("fragment_size or fragments are invalid."); + goto fail; + } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + pa_source_new_data_init(&data); + data.driver = driver; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio source2")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + pa_proplist_sets(data.proplist, "tizen.card", u->card); + pa_proplist_sets(data.proplist, "tizen.device", u->device); + pa_proplist_sets(data.proplist, "tizen.version", "2"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); + + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log_error("Invalid properties."); + pa_source_new_data_done(&data); + goto fail; + } + + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log_error("Failed to create source object."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; + u->source->userdata = u; + + if (pa_hal_interface_pcm_open(u->hal_interface, + u->card, + u->device, + DIRECTION_IN, + &u->source->sample_spec, + u->frag_size / pa_frame_size(&u->source->sample_spec), + u->nfrags, + (void **)&u->pcm_handle)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->timestamp = 0ULL; + + if (!(u->thread = pa_thread_new("tizenaudio-source2", thread_func, u))) { + pa_log_error("Failed to create thread."); + goto fail; + } + + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(buffer_size, &ss)); + pa_source_put(u->source); + + return u->source; + +fail: + userdata_free(u); + return NULL; +} + +static void userdata_free(struct userdata *u) { + pa_assert(u); + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + pa_xfree(u->card); + pa_xfree(u->device); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pcm_handle) { + pa_hal_interface_pcm_stop(u->hal_interface, u->pcm_handle); + pa_hal_interface_pcm_close(u->hal_interface, u->pcm_handle); + } + + if (u->hal_interface) + pa_hal_interface_unref(u->hal_interface); + + pa_xfree(u); +} + +void pa_tizenaudio_source2_free(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se((u = s->userdata)); + + userdata_free(u); +} diff --git a/src/tizenaudio-source2.h b/src/tizenaudio-source2.h new file mode 100644 index 0000000..a8e67c0 --- /dev/null +++ b/src/tizenaudio-source2.h @@ -0,0 +1,38 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifndef footizenaudiosource2hfoo +#define footizenaudiosource2hfoo + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +pa_sink *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver); + +void pa_tizenaudio_source2_free(pa_source *s); + +#endif + -- 2.7.4 From 1e46c80a9170dca47a380a8de5d70daeb5acb488 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Tue, 17 May 2022 15:41:12 +0900 Subject: [PATCH 11/16] Revert "aec-manager: Add audio AEC manager" This reverts commit f49985f02d8d1e49a4e2075f3b9f2d651c571770. [Version] 15.0.16 [Issue Type] Cleanup Change-Id: Ie7a9341ff90acd3aa26ed38d51ce140fffb02c0f --- Makefile.am | 9 -- configure.ac | 12 -- packaging/pulseaudio-modules-tizen.spec | 6 +- src/aec-manager.c | 273 -------------------------------- src/aec-manager.h | 38 ----- src/device-manager.c | 24 +-- src/module-tizenaudio-sink.c | 67 +------- src/module-tizenaudio-source.c | 67 +------- 8 files changed, 14 insertions(+), 482 deletions(-) delete mode 100644 src/aec-manager.c delete mode 100644 src/aec-manager.h diff --git a/Makefile.am b/Makefile.am index ed2843f..12098ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,10 +27,6 @@ AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDFLAGS = $(NODELETE_LDFLAGS) MODULE_CFLAGS = $(AM_CFLAGS) $(PACORE_CFLAGS) $(PA_CFLAGS) -D__TIZEN__ -if ENABLE_AEC -MODULE_CFLAGS += -DSUPPORT_AEC -endif - MODULE_LDFLAGS = $(AM_LDFLAGS) $(PACORE_LDFLAGS) $(PA_LDFLAGS) -module -disable-static -avoid-version MODULE_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS) @@ -139,11 +135,6 @@ module_tizenaudio_policy_la_SOURCES = \ src/device-manager-db.c src/device-manager-db-priv.h \ src/tizen-device.c src/tizen-device.h src/tizen-device-def.c src/tizen-device-def.h \ src/subscribe-observer.c src/subscribe-observer.h - -if ENABLE_AEC -module_tizenaudio_policy_la_SOURCES += src/aec-manager.c src/aec-manager.h -endif - module_tizenaudio_policy_la_LDFLAGS = $(MODULE_LDFLAGS) -L$(pulsemodlibexecdir) module_tizenaudio_policy_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(VCONF_LIBS) $(INIPARSER_LIBS) $(LIBJSON_LIBS) libhal-interface.la libcommunicator.la module_tizenaudio_policy_la_CFLAGS = $(MODULE_CFLAGS) $(DBUS_CFLAGS) $(VCONF_CFLAGS) $(INIPARSER_CFLAGS) $(LIBJSON_CFLAGS) -DPA_MODULE_NAME=module_tizenaudio_policy diff --git a/configure.ac b/configure.ac index 170b340..ad76385 100644 --- a/configure.ac +++ b/configure.ac @@ -397,18 +397,6 @@ AC_ARG_ENABLE(acm, AC_HELP_STRING([--enable-acm], [using acm]), AM_CONDITIONAL(ENABLE_ACM, test "x$ENABLE_ACM" = "xyes") dnl end -------------------------------------------------------------------- -dnl use aec ---------------------------------------------------------------- -AC_ARG_ENABLE(aec, AC_HELP_STRING([--enable-aec], [using aec]), -[ - case "${enableval}" in - yes) ENABLE_AEC=yes ;; - no) ENABLE_AEC=no ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-aec) ;; - esac - ],[USE_AEC=no]) -AM_CONDITIONAL(ENABLE_AEC, test "x$ENABLE_AEC" = "xyes") -dnl end -------------------------------------------------------------------- - dnl use webrtc ---------------------------------------------------------------- AC_ARG_ENABLE(webrtc, AC_HELP_STRING([--enable-webrtc], [using webrtc-audio-processing]), [ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 0dccc40..16babe1 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -2,7 +2,7 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 15.0.15 +Version: 15.0.16 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -54,9 +54,7 @@ export LD_AS_NEEDED=0 %reconfigure --prefix=%{_prefix} \ --disable-static \ --enable-acm \ -%if "%{tizen_profile_name}" != "tv" - --enable-aec -%else +%if "%{tizen_profile_name}" == "tv" --enable-vconf-helper %endif # --enable-haltc diff --git a/src/aec-manager.c b/src/aec-manager.c deleted file mode 100644 index c349f71..0000000 --- a/src/aec-manager.c +++ /dev/null @@ -1,273 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2021 Jaechul Lee - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "aec-manager.h" - -#define AEC_MANAGER_OBJECT_PATH "/org/pulseaudio/AecManager" -#define AEC_MANAGER_INTERFACE "org.pulseaudio.AecManager" - -#define AEC_MANAGER_METHOD_NAME_ON "On" -#define AEC_MANAGER_METHOD_NAME_OFF "Off" -#define AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS "GetSinkParam" -#define AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS "GetSourceParam" - -#define AEC_UNIX_SOCKET_PATH "/tmp/.aec.socket" - -static struct aec_manager { - pa_dbus_connection *dbus_conn; - pa_source *source; /* builtin-mic */ - pa_sink *sink; /* builtin-spk */ -} am; - -enum { - SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX, - SINK_MESSAGE_GET_AEC_PARAMS, -}; - -enum { - SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX, - SOURCE_MESSAGE_GET_AEC_PARAMS, -}; - -#define AEC_MANAGER_INTROSPECT_XML \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "" \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - "" - -enum method_handler_index { - METHOD_HANDLER_ON, - METHOD_HANDLER_OFF, - METHOD_HANDLER_GET_SINK_PARAMS, - METHOD_HANDLER_GET_SOURCE_PARAMS, - METHOD_HANDLER_MAX -}; - -static void handle_method_on(DBusConnection *conn, DBusMessage *msg, void *userdata); -static void handle_method_off(DBusConnection *conn, DBusMessage *msg, void *userdata); -static void handle_method_get_sink_params(DBusConnection *conn, DBusMessage *msg, void *userdata); -static void handle_method_get_source_params(DBusConnection *conn, DBusMessage *msg, void *userdata); - -static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { - [METHOD_HANDLER_ON] = { - .method_name = AEC_MANAGER_METHOD_NAME_ON, - .receive_cb = handle_method_on }, - [METHOD_HANDLER_OFF] = { - .method_name = AEC_MANAGER_METHOD_NAME_OFF, - .receive_cb = handle_method_off }, - [METHOD_HANDLER_GET_SINK_PARAMS] = { - .method_name = AEC_MANAGER_METHOD_NAME_GET_SINK_PARAMS, - .receive_cb = handle_method_get_sink_params }, - [METHOD_HANDLER_GET_SOURCE_PARAMS] = { - .method_name = AEC_MANAGER_METHOD_NAME_GET_SOURCE_PARAMS, - .receive_cb = handle_method_get_source_params }, -}; - -static void send_get_param_reply(DBusConnection *conn, DBusMessage *msg, aec_params_t* param) { - DBusMessage *reply = NULL; - DBusMessageIter msg_iter; - - const char *ptr1 = param->card; - const char *ptr2 = param->device; - - reply = dbus_message_new_method_return(msg); - if (!reply) { - pa_log_error("Failed to alloc reply"); - return; - } - - dbus_message_iter_init_append(reply, &msg_iter); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->rate); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->channels); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->format); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->frag_size); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, ¶m->nfrags); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr1); - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &ptr2); - if (!dbus_connection_send(conn, reply, NULL)) - pa_log_error("reply send error!"); - - dbus_message_unref(reply); -} - -static void handle_method_on(DBusConnection *conn, DBusMessage *msg, void *userdata) { - pa_asyncmsgq_post(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink), - SINK_MESSAGE_SET_AEC_STATE, (void *)TRUE, 0, NULL, NULL); - pa_asyncmsgq_post(am.source->asyncmsgq, PA_MSGOBJECT(am.source), - SOURCE_MESSAGE_SET_AEC_STATE, (void *)TRUE, 0, NULL, NULL); - - pa_dbus_send_empty_reply(conn, msg); -} - -static void handle_method_off(DBusConnection *conn, DBusMessage *msg, void *userdata) { - pa_asyncmsgq_post(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink), - SINK_MESSAGE_SET_AEC_STATE, (void *)FALSE, 0, NULL, NULL); - pa_asyncmsgq_post(am.source->asyncmsgq, PA_MSGOBJECT(am.source), - SOURCE_MESSAGE_SET_AEC_STATE, (void *)FALSE, 0, NULL, NULL); - - pa_dbus_send_empty_reply(conn, msg); -} - -static void handle_method_get_sink_params(DBusConnection *conn, DBusMessage *msg, void *userdata) { - aec_params_t param; - - pa_asyncmsgq_send(am.sink->asyncmsgq, PA_MSGOBJECT(am.sink), - SINK_MESSAGE_GET_AEC_PARAMS, ¶m, 0, NULL); - - send_get_param_reply(conn, msg, ¶m); -} - -static void handle_method_get_source_params(DBusConnection *conn, DBusMessage *msg, void *userdata) { - aec_params_t param; - - pa_asyncmsgq_send(am.source->asyncmsgq, PA_MSGOBJECT(am.source), - SOURCE_MESSAGE_GET_AEC_PARAMS, ¶m, 0, NULL); - - send_get_param_reply(conn, msg, ¶m); -} - -static DBusHandlerResult method_handler_for_vt(DBusConnection *conn, DBusMessage *m, void *userdata) { - const char *path, *interface, *member; - DBusMessage *r = NULL; - - pa_assert(conn); - pa_assert(m); - - path = dbus_message_get_path(m); - interface = dbus_message_get_interface(m); - member = dbus_message_get_member(m); - - pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); - - if (!pa_safe_streq(path, AEC_MANAGER_OBJECT_PATH)) - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { - r = dbus_message_new_method_return(m); - if (r) { - const char *xml = AEC_MANAGER_INTROSPECT_XML; - if (dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) { - if (!dbus_connection_send(conn, r, NULL)) - pa_log_error("reply send error!"); - } else { - pa_log_error("args append error!"); - } - - dbus_message_unref(r); - } - } else { - int i; - for (i = 0; i < METHOD_HANDLER_MAX; i++) { - if (dbus_message_is_method_call(m, AEC_MANAGER_INTERFACE, - method_handlers[i].method_name)) - method_handlers[i].receive_cb(conn, m, NULL); - } - } - - return DBUS_HANDLER_RESULT_HANDLED; -} - -int aec_manager_init(pa_core *core, pa_source *source, pa_sink *sink) { - DBusError err; - pa_dbus_connection *conn = NULL; - static const DBusObjectPathVTable vtable = { - .message_function = method_handler_for_vt, - }; - - if (!source || !sink) { - pa_log_error("AEC init failed. source(%p)/sink(%p) is null.", source, sink); - return -1; - } - - dbus_error_init(&err); - if (!(conn = pa_dbus_bus_get(core, DBUS_BUS_SYSTEM, &err)) || dbus_error_is_set(&err)) { - if (conn) - pa_dbus_connection_unref(conn); - - dbus_error_free(&err); - pa_log_error("Unable to contact D-Bus system bus: %s: %s", err.name, err.message); - return -1; - } - - am.dbus_conn = conn; - if (!dbus_connection_register_object_path(pa_dbus_connection_get(conn), - AEC_MANAGER_OBJECT_PATH, &vtable, NULL)) { - pa_dbus_connection_unref(conn); - pa_log_error("Failed to register object path"); - return -1; - } - - am.source = source; - am.sink = sink; - - pa_log_info("AEC init success"); - - return 0; -} - -void aec_manager_deinit(void) { - if (am.dbus_conn) - pa_dbus_connection_unref(am.dbus_conn); - - pa_log_info("AEC deinit success"); -} diff --git a/src/aec-manager.h b/src/aec-manager.h deleted file mode 100644 index d30f788..0000000 --- a/src/aec-manager.h +++ /dev/null @@ -1,38 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2021 Jaechul Lee - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifndef footizenaecmanagerfoo -#define footizenaecmanagerfoo - -typedef struct aec_params { - int rate; - int channels; - int format; - int frag_size; - int nfrags; - char card[32]; - char device[32]; -} aec_params_t; - -int aec_manager_init(pa_core *core, pa_source *source, pa_sink *sink); -void aec_manager_deinit(); - -#endif diff --git a/src/device-manager.c b/src/device-manager.c index 51fdba9..b5d6e9c 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -49,10 +49,6 @@ #include "device-manager-dbus-priv.h" #include "device-manager-db-priv.h" -#ifdef SUPPORT_AEC -#include "aec-manager.h" -#endif - #define SHARED_DEVICE_MANAGER "tizen-device-manager" #define DEVICE_MAP_FILE PA_DEFAULT_CONFIG_DIR"/device-map.json" @@ -2973,8 +2969,6 @@ static pa_hook_result_t device_running_changed_hook_cb(pa_core *c, pa_tz_device_ pa_device_manager* pa_device_manager_get(pa_core *c) { pa_device_manager *dm; - pa_source *source; - pa_sink *sink; pa_assert(c); @@ -3031,17 +3025,12 @@ pa_device_manager* pa_device_manager_get(pa_core *c) { } /* Just for convenience when test*/ - sink = _device_manager_set_default_sink(dm, DEVICE_TYPE_SPEAKER, "normal"); - if (!sink) + if (!_device_manager_set_default_sink(dm, DEVICE_TYPE_SPEAKER, "normal")) { pa_log_warn("Set default sink with speaker(normal) failed"); - - source = _device_manager_set_default_source(dm, DEVICE_TYPE_MIC, "normal"); - if (!source) + } + if (!_device_manager_set_default_source(dm, DEVICE_TYPE_MIC, "normal")) { pa_log_warn("Set default source with mic(normal) failed"); - -#ifdef SUPPORT_AEC - aec_manager_init(dm->core, source, sink); -#endif + } pa_shared_set(c, SHARED_DEVICE_MANAGER, dm); @@ -3119,9 +3108,4 @@ void pa_device_manager_unref(pa_device_manager *dm) { pa_shared_remove(dm->core, SHARED_DEVICE_MANAGER); pa_xfree(dm); - -#ifdef SUPPORT_AEC - aec_manager_deinit(); -#endif - } diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c index 0ea7846..bb86007 100644 --- a/src/module-tizenaudio-sink.c +++ b/src/module-tizenaudio-sink.c @@ -47,10 +47,6 @@ #include "hal-interface.h" -#ifdef SUPPORT_AEC -#include "aec-manager.h" -#endif - PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Sink"); PA_MODULE_VERSION(PACKAGE_VERSION); @@ -79,13 +75,6 @@ PA_MODULE_USAGE( #define DEVICE_NAME_MAX 30 #define SMALL_AVAIL_LOG_PERIOD 20 -#ifdef SUPPORT_AEC -enum { - PA_SINK_MESSAGE_SET_AEC_STATE = PA_SINK_MESSAGE_MAX, - PA_SINK_MESSAGE_GET_AEC_PARAMS, -}; -#endif - struct userdata { pa_core *core; pa_module *module; @@ -113,11 +102,6 @@ struct userdata { uint64_t small_avail_count; uint64_t write_count; pa_hal_interface *hal_interface; - -#ifdef SUPPORT_AEC - bool aec_enable; - pa_sample_spec ss; -#endif }; static const char* const valid_modargs[] = { @@ -180,7 +164,7 @@ static int suspend(struct userdata *u) { u->rtpoll_item = NULL; } - pa_log_info("Device suspended..."); + pa_log_info("Device suspended...[%s,%s]", u->card, u->device); return 0; } @@ -191,9 +175,6 @@ static int unsuspend(struct userdata *u) { int32_t ret; size_t frame_size; - char *card = u->card; - char *device = u->device; - pa_assert(u); pa_assert(!u->pcm_handle); @@ -206,16 +187,9 @@ static int unsuspend(struct userdata *u) { goto fail; } -#ifdef SUPPORT_AEC - if (u->aec_enable) { - card = "Loopback"; - device = "0,0"; - } -#endif - ret = pa_hal_interface_pcm_open(u->hal_interface, - card, - device, + u->card, + u->device, DIRECTION_OUT, &sample_spec, u->frag_size / frame_size, @@ -232,7 +206,7 @@ static int unsuspend(struct userdata *u) { u->write_count = 0; u->first = true; - pa_log_info("Resumed successfully...device(%s:%s)", card, device); + pa_log_info("Resumed successfully..."); return 0; @@ -309,36 +283,6 @@ static int sink_process_msg( *((pa_usec_t*)data) = latency; return 0; } -#ifdef SUPPORT_AEC - case PA_SINK_MESSAGE_SET_AEC_STATE: { - pa_sink *s = PA_SINK(o); - bool enable = (bool)data; - - if (u->aec_enable == enable) - return 0; - - pa_log_info("AEC enable(%d)", enable); - - u->aec_enable = enable; - if (s->thread_info.state == PA_SINK_RUNNING) { - suspend(u); - unsuspend(u); - } - - return 0; - } - case PA_SINK_MESSAGE_GET_AEC_PARAMS: { - aec_params_t *params = (aec_params_t *)data; - params->rate = u->ss.rate; - params->channels = u->ss.channels; - params->format = u->ss.format; - params->frag_size = u->frag_size; - params->nfrags = u->nfrags; - snprintf(params->card, DEVICE_NAME_MAX, "%s", u->card); - snprintf(params->device, DEVICE_NAME_MAX, "%s", u->device); - return 0; - } -#endif } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -641,9 +585,6 @@ int pa__init(pa_module*m) { u->first = true; u->hal_interface = pa_hal_interface_get(u->core); u->rtpoll = pa_rtpoll_new(); -#ifdef SUPPORT_AEC - u->ss = ss; -#endif pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { diff --git a/src/module-tizenaudio-source.c b/src/module-tizenaudio-source.c index e3ed819..746eb74 100644 --- a/src/module-tizenaudio-source.c +++ b/src/module-tizenaudio-source.c @@ -47,10 +47,6 @@ #include "hal-interface.h" -#ifdef SUPPORT_AEC -#include "aec-manager.h" -#endif - PA_MODULE_AUTHOR("Tizen"); PA_MODULE_DESCRIPTION("Tizen Audio Source"); PA_MODULE_VERSION(PACKAGE_VERSION); @@ -74,13 +70,6 @@ PA_MODULE_USAGE( #define DEVICE_NAME_MAX 30 -#ifdef SUPPORT_AEC -enum { - PA_SOURCE_MESSAGE_SET_AEC_STATE = PA_SOURCE_MESSAGE_MAX, - PA_SOURCE_MESSAGE_GET_AEC_PARAMS, -}; -#endif - struct userdata { pa_core *core; pa_module *module; @@ -106,11 +95,6 @@ struct userdata { uint64_t read_count; pa_usec_t latency_time; pa_hal_interface *hal_interface; - -#ifdef SUPPORT_AEC - bool aec_enable; - pa_sample_spec ss; -#endif }; static const char* const valid_modargs[] = { @@ -168,7 +152,7 @@ static int suspend(struct userdata *u) { u->rtpoll_item = NULL; } - pa_log_info("Device suspended..."); + pa_log_info("Device suspended...[%s,%s]", u->card, u->device); return 0; } @@ -179,9 +163,6 @@ static int unsuspend(struct userdata *u) { int32_t ret; size_t frame_size; - char *card = u->card; - char *device = u->device; - pa_assert(u); pa_assert(!u->pcm_handle); @@ -194,16 +175,9 @@ static int unsuspend(struct userdata *u) { goto fail; } -#ifdef SUPPORT_AEC - if (u->aec_enable) { - card = "Loopback"; - device = "1,1"; - } -#endif - ret = pa_hal_interface_pcm_open(u->hal_interface, - card, - device, + u->card, + u->device, DIRECTION_IN, &sample_spec, u->frag_size / frame_size, @@ -220,7 +194,7 @@ static int unsuspend(struct userdata *u) { u->read_count = 0; u->first = true; - pa_log_info("Resumed successfully...device(%s:%s)", card, device); + pa_log_info("Resumed successfully..."); return 0; @@ -291,36 +265,6 @@ static int source_process_msg( *((pa_usec_t*)data) = u->timestamp > now ? 0ULL : now - u->timestamp; return 0; } -#ifdef SUPPORT_AEC - case PA_SOURCE_MESSAGE_SET_AEC_STATE: { - pa_source *s = PA_SOURCE(o); - bool enable = (bool)data; - - if (u->aec_enable == enable) - return 0; - - pa_log_info("AEC enable(%d)", enable); - - u->aec_enable = enable; - if (s->thread_info.state == PA_SOURCE_RUNNING) { - suspend(u); - unsuspend(u); - } - - return 0; - } - case PA_SOURCE_MESSAGE_GET_AEC_PARAMS: { - aec_params_t *params = (aec_params_t *)data; - params->rate = u->ss.rate; - params->channels = u->ss.channels; - params->format = u->ss.format; - params->frag_size = u->frag_size; - params->nfrags = u->nfrags; - snprintf(params->card, DEVICE_NAME_MAX, "%s", u->card); - snprintf(params->device, DEVICE_NAME_MAX, "%s", u->device); - return 0; - } -#endif } return pa_source_process_msg(o, code, data, offset, chunk); @@ -555,9 +499,6 @@ int pa__init(pa_module*m) { u->first = true; u->hal_interface = pa_hal_interface_get(u->core); u->rtpoll = pa_rtpoll_new(); -#ifdef SUPPORT_AEC - u->ss = ss; -#endif pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { -- 2.7.4 From 9a497c1dcc1e95e276769e7f01a1117dcde3b80f Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Thu, 16 Jun 2022 23:48:35 +0900 Subject: [PATCH 12/16] device-manager: avoid registering the built-in device if not loaded properly Previously, it was shown that the built-in device exists even module is not loaded correctly. Now, this change fixed that mismatch. [Version] 15.0.17 [Issue Type] Bug Change-Id: I6c78231e62c75ac759964697ee2e079a80bb58c3 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 16babe1..994fa15 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.16 +Version: 15.0.17 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/device-manager.c b/src/device-manager.c index b5d6e9c..4ba4450 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -2015,6 +2015,7 @@ static int _load_type_devices(device_type_info *type_info, bool is_playback, pa_ const char *device_string, *params; bool first_one = true; void *device; + bool loaded = false; pa_assert(dm); pa_assert(type_info); @@ -2035,6 +2036,7 @@ static int _load_type_devices(device_type_info *type_info, bool is_playback, pa_ } PA_HASHMAP_FOREACH_KV(role, device_string, pcm_devices, state) { + pa_log_debug("k:role(%s), v:device_string(%s)", role, device_string); /* skip duplicate load */ if (is_playback && _core_get_sink(dm->core, device_string, NULL)) { pa_log_debug("Already loaded %s", device_string); @@ -2065,6 +2067,12 @@ static int _load_type_devices(device_type_info *type_info, bool is_playback, pa_ first_one = false; } pa_log_info("load device success %s %s %s", device_string, params, role); + loaded = true; + } + + if (!loaded) { + pa_log_error("Returning error due to no device loaded in success"); + return -1; } return 0; @@ -2204,7 +2212,7 @@ static int load_builtin_devices(pa_device_manager *dm) { type = type_info->type; - pa_log_info("type_info : %s", type); + pa_log_info("[%u] type_info : %s", type_idx, type); detected_type = _device_get_detected(dm, type, NULL); if (detected_type == DEVICE_DISCONNECTED) { pa_log_info("Not detected yet"); @@ -2219,23 +2227,25 @@ static int load_builtin_devices(pa_device_manager *dm) { _load_type_devices(type_info, true, dm); } handle_device_connected(dm, type, NULL, NULL, detected_type, type_info); - } else if (device_type_is_use_external_card(type) == false) { - dm_device_direction_t direction; - direction = device_type_get_static_direction(type); + } else if (!device_type_is_use_external_card(type)) { + dm_device_direction_t direction = device_type_get_static_direction(type); if (direction == DM_DEVICE_DIRECTION_NONE) { pa_log_warn("Wrong direction"); continue; } - if (direction & DM_DEVICE_DIRECTION_OUT) - _load_type_devices(type_info, true, dm); - if (direction & DM_DEVICE_DIRECTION_IN) - _load_type_devices(type_info, false, dm); - handle_device_connected(dm, type, NULL, NULL, detected_type, type_info); + + if ( _load_type_devices(type_info, (direction & DM_DEVICE_DIRECTION_OUT), dm) == 0) + handle_device_connected(dm, type, NULL, NULL, detected_type, type_info); + else + pa_log_warn("type %s failed....", type); + } else { pa_log_warn("Invalid case"); } } + pa_log_debug("Load Builtin Devices Done"); + return 0; } -- 2.7.4 From 1f8f4353d5463431b54d5ca119d60006df466fff Mon Sep 17 00:00:00 2001 From: Seungbae Shin Date: Mon, 13 Jun 2022 21:37:50 +0900 Subject: [PATCH 13/16] device-manager: Add builtin tv-mic support [Version] 15.0.18 [Issue Type] Improvement Change-Id: If35aa1bffa27588629124e97bf7249688782d492 --- packaging/pulseaudio-modules-tizen.spec | 4 +- src/device-manager.c | 80 +++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 994fa15..6ec8a2b 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.17 +Version: 15.0.18 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -45,7 +45,7 @@ PulseAudio module-acm-sink for sending PCM data to ACM core. %build export CFLAGS="%{optflags} -fno-strict-aliasing -D__TIZEN__ -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\" " %if "%{tizen_profile_name}" == "tv" - export CFLAGS+=" -DTIZEN_TV"; + export CFLAGS+=" -DTIZEN_TV -D__TIZEN_TV_BUILTIN_MIC__"; %else export CFLAGS+=" -D__TIZEN_INTERNAL_BT_SCO__" %endif diff --git a/src/device-manager.c b/src/device-manager.c index 4ba4450..acd3c38 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -99,6 +99,9 @@ typedef enum dm_device_class_type { DM_DEVICE_CLASS_BT, DM_DEVICE_CLASS_NULL, DM_DEVICE_CLASS_ACM, +#ifdef __TIZEN_TV_BUILTIN_MIC__ + DM_DEVICE_CLASS_TVMIC, +#endif DM_DEVICE_CLASS_MAX, } dm_device_class_t; @@ -235,7 +238,14 @@ static const device_module_t module_table[] = { .module_name = { NULL, "module-acm-sink" }, .device_string = "acm", .custom_device_get_func = NULL + }, +#ifdef __TIZEN_TV_BUILTIN_MIC__ + [DM_DEVICE_CLASS_TVMIC] = { + .module_name = { "module-tizentv-builtin-source", NULL }, + .device_string = "tvmic", + .custom_device_get_func = NULL } +#endif }; void simple_device_dump(pa_log_level_t level, const char *prefix, int id, const char *type, const char *name, int direction, int state) { @@ -322,6 +332,10 @@ static dm_device_class_t device_string_get_class(const char *device_string) { return DM_DEVICE_CLASS_TIZEN; else if (device_string == strstr(device_string, "acm")) return DM_DEVICE_CLASS_ACM; +#ifdef __TIZEN_TV_BUILTIN_MIC__ + else if (device_string == strstr(device_string, "tvmic")) + return DM_DEVICE_CLASS_TVMIC; +#endif return DM_DEVICE_CLASS_NONE; } @@ -461,6 +475,18 @@ static bool pulse_device_is_rtsp(pa_object *pdevice) { return pa_safe_streq(pa_proplist_gets(prop, PA_PROP_DEVICE_API), DEVICE_API_RTSP); } +#ifdef __TIZEN_TV_BUILTIN_MIC__ +static bool pulse_device_is_tvmic(pa_object *pdevice) { + if (!pdevice) + return false; + + if (pa_sink_isinstance(pdevice)) + return false; + + return pa_safe_streq(PA_SOURCE(pdevice)->module->name, module_table[DM_DEVICE_CLASS_TVMIC].module_name[0]); +} +#endif + #ifndef __TIZEN_INTERNAL_BT_SCO__ static bool pulse_device_is_btsco(pa_object *pdevice) { pa_proplist *prop = pulse_device_get_proplist(pdevice); @@ -529,6 +555,10 @@ static dm_device_class_t pulse_device_get_class(pa_object *pdevice) { return DM_DEVICE_CLASS_BT; else if (pulse_device_is_acm(pdevice)) return DM_DEVICE_CLASS_ACM; +#ifdef __TIZEN_TV_BUILTIN_MIC__ + else if (pulse_device_is_tvmic(pdevice)) + return DM_DEVICE_CLASS_TVMIC; +#endif return DM_DEVICE_CLASS_NONE; } @@ -805,28 +835,32 @@ static int build_params_to_load_module(const char *device_string, const char *pa pa_assert(params); pa_assert(target); - if ((device_class != DM_DEVICE_CLASS_ALSA) && - (device_class != DM_DEVICE_CLASS_TIZEN) && - (device_class != DM_DEVICE_CLASS_ACM)) - return -1; - - if (device_class == DM_DEVICE_CLASS_ACM) { + switch (device_class) { + case DM_DEVICE_CLASS_ACM: +#ifdef __TIZEN_TV_BUILTIN_MIC__ + /* fall-through */ + case DM_DEVICE_CLASS_TVMIC: +#endif snprintf(target, DEVICE_PARAM_STRING_MAX, "%s ", params); - return 0; - } + break; + + case DM_DEVICE_CLASS_ALSA: + /* fall-through */ + case DM_DEVICE_CLASS_TIZEN: + if (device_string_get_name(device_string, device_name) < 0) { + pa_log_error("Invalid device string '%s'", device_string); + return -1; + } + + snprintf(target, DEVICE_PARAM_STRING_MAX, "device=%s%s %s", + (device_class == DM_DEVICE_CLASS_ALSA) ? "hw:" : "", device_name, params); + break; - if (device_string_get_name(device_string, device_name) < 0) { - pa_log_error("Invalid device string '%s'", device_string); + default: + pa_log_error("unexpected device_class(%d)", device_class); return -1; } - if (device_class == DM_DEVICE_CLASS_ALSA) - snprintf(target, DEVICE_PARAM_STRING_MAX, "device=hw:%s ", device_name); - else if (device_class == DM_DEVICE_CLASS_TIZEN) - snprintf(target, DEVICE_PARAM_STRING_MAX, "device=%s ", device_name); - - strncat(target, params, DEVICE_PARAM_STRING_MAX - strlen(target)); - return 0; } @@ -1783,7 +1817,11 @@ 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_bt_pulse_device(PA_OBJECT(source), true, dm); return PA_HOOK_OK; - } else if (pulse_device_is_alsa(PA_OBJECT(source)) || pulse_device_is_tizenaudio(PA_OBJECT(source))) { + } else if (pulse_device_is_alsa(PA_OBJECT(source)) || +#ifdef __TIZEN_TV_BUILTIN_MIC__ + pulse_device_is_tvmic(PA_OBJECT(source)) || +#endif + pulse_device_is_tizenaudio(PA_OBJECT(source))) { pulse_device_set_use_internal_codec(PA_OBJECT(source), true); handle_internal_pulse_device(PA_OBJECT(source), true, dm); return PA_HOOK_OK; @@ -1831,7 +1869,11 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc pa_log_warn("tunnel but not remote....ignore this"); } return PA_HOOK_OK; - } else if (pulse_device_is_alsa(PA_OBJECT(source)) || pulse_device_is_tizenaudio(PA_OBJECT(source))) { + } else if (pulse_device_is_alsa(PA_OBJECT(source)) || +#ifdef __TIZEN_TV_BUILTIN_MIC__ + pulse_device_is_tvmic(PA_OBJECT(source)) || +#endif + pulse_device_is_tizenaudio(PA_OBJECT(source))) { handle_internal_pulse_device(PA_OBJECT(source), false, dm); return PA_HOOK_OK; } else if (pulse_device_is_rtsp(PA_OBJECT(source))) { -- 2.7.4 From d072587ace032bdf2c0bd706365631967d4572dd Mon Sep 17 00:00:00 2001 From: Sangchul Lee Date: Tue, 21 Jun 2022 17:05:56 +0900 Subject: [PATCH 14/16] Rename __TIZEN_INTERNAL_BT_SCO__ to __TIZEN_TV_EXTERNAL_BT_SCO__ The 'external bt sco' feature is only for tv binary. So, the preprocessor definition name is changed to have __TIZEN_TV_ prefix as well as to invert the meaning of the previous name. [Version] 15.0.19 [Issue Type] Rename Change-Id: Iff1847574b925d8fb72efc3687f1926d3e110b8e Signed-off-by: Sangchul Lee --- packaging/pulseaudio-modules-tizen.spec | 6 ++---- src/device-manager-dbus.c | 22 ++++++++++---------- src/device-manager.c | 18 ++++++++--------- src/module-tizenaudio-policy.c | 36 ++++++++++++++++----------------- src/tizen-device.c | 8 ++++---- src/tizen-device.h | 4 ++-- 6 files changed, 46 insertions(+), 48 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 6ec8a2b..5a5282f 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.18 +Version: 15.0.19 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -45,9 +45,7 @@ PulseAudio module-acm-sink for sending PCM data to ACM core. %build export CFLAGS="%{optflags} -fno-strict-aliasing -D__TIZEN__ -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\" " %if "%{tizen_profile_name}" == "tv" - export CFLAGS+=" -DTIZEN_TV -D__TIZEN_TV_BUILTIN_MIC__"; -%else - export CFLAGS+=" -D__TIZEN_INTERNAL_BT_SCO__" +export CFLAGS+=" -DTIZEN_TV -D__TIZEN_TV_EXTERNAL_BT_SCO__ -D__TIZEN_TV_BUILTIN_MIC__"; %endif export LD_AS_NEEDED=0 diff --git a/src/device-manager-dbus.c b/src/device-manager-dbus.c index 8c067af..7752ce6 100644 --- a/src/device-manager-dbus.c +++ b/src/device-manager-dbus.c @@ -219,7 +219,7 @@ static void handle_dump_device_list(DBusConnection *conn, DBusMessage *msg, void static void handle_test_device_status_change(DBusConnection *conn, DBusMessage *msg, void *userdata); static void handle_set_acm_mode(DBusConnection *conn, DBusMessage *msg, void *userdata); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static int method_call_bt_get_name(DBusConnection *conn, const char *device_path, char **name); #endif @@ -433,7 +433,7 @@ static int _translate_external_value(const char *type, int value, device_detecte return 0; } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static int handle_bluez_headset_property_changed(DBusConnection *c, DBusMessage *s, pa_device_manager *dm) { DBusMessageIter msg_iter, variant_iter; char *property_name; @@ -507,7 +507,7 @@ static int handle_bluez_headset_property_changed(DBusConnection *c, DBusMessage return 0; } -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_BT_SCO__ */ static DBusHandlerResult dbus_filter_device_detect_handler(DBusConnection *c, DBusMessage *s, void *userdata) { DBusError error; @@ -554,7 +554,7 @@ static DBusHandlerResult dbus_filter_device_detect_handler(DBusConnection *c, DB goto fail; } handle_device_status_changed(dm, DEVICE_TYPE_FORWARDING, NULL, NULL, detected); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ } else if (dbus_message_is_signal(s, DBUS_INTERFACE_BLUEZ_HEADSET, "PropertyChanged")) { if (handle_bluez_headset_property_changed(c, s, dm) < 0) goto fail; @@ -639,7 +639,7 @@ static bool device_is_match_with_mask(pa_tz_device *device, int mask) { device_is_match_type(device, mask & DEVICE_TYPE_FLAGS)); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static int method_call_bt_get_name(DBusConnection *conn, const char *device_path, char **name) { const char *intf = DBUS_INTERFACE_BLUEZ_DEVICE, *prop = "Alias"; DBusMessage *msg, *reply; @@ -687,7 +687,7 @@ static int method_call_bt_get_name(DBusConnection *conn, const char *device_path dbus_message_unref(reply); return 0; } -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_BT_SCO__ */ static void array_iter_append(DBusMessageIter *array_iter, pa_idxset *device_list, int mask) { uint32_t device_idx; @@ -732,7 +732,7 @@ static void array_iter_append(DBusMessageIter *array_iter, pa_idxset *device_lis } } -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ static int include_device_filter_func(const void *d, const void *include_device_type) { pa_tz_device *device = (pa_tz_device *)d; @@ -750,14 +750,14 @@ static int exclude_device_filter_func(const void *d, const void *exclude_device_ return (int)!pa_safe_streq(pa_tz_device_get_type(device), (const char *)exclude_device_type); } -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_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__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ pa_idxset *idxset1, *idxset2; #endif @@ -776,7 +776,7 @@ static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage * 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__ +#ifndef __TIZEN_TV_EXTERNAL_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 */ @@ -788,7 +788,7 @@ static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage * pa_idxset_free(idxset1, NULL); pa_idxset_free(idxset2, NULL); -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_BT_SCO__ */ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &array_iter)); diff --git a/src/device-manager.c b/src/device-manager.c index acd3c38..f541685 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -85,7 +85,7 @@ #define DEVICE_API_RAOP "raop" #define DEVICE_API_TUNNEL "tunnel" #define DEVICE_API_RTSP "rtsp" -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ #define DEVICE_API_BTSCO "btsco" #endif #define DEVICE_BUS_USB "usb" @@ -487,7 +487,7 @@ static bool pulse_device_is_tvmic(pa_object *pdevice) { } #endif -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ static bool pulse_device_is_btsco(pa_object *pdevice) { pa_proplist *prop = pulse_device_get_proplist(pdevice); @@ -1198,7 +1198,7 @@ static const char* pulse_device_get_system_id(pa_object *pdevice) { return pa_proplist_gets(prop, PA_PROP_DEVICE_DESCRIPTION); else if (pulse_device_is_rtsp(pdevice)) return pa_proplist_gets(prop, PA_PROP_DEVICE_DESCRIPTION); -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ else if (pulse_device_is_btsco(pdevice)) return pa_proplist_gets(prop, PA_PROP_DEVICE_DESCRIPTION); #endif @@ -1620,7 +1620,7 @@ static void handle_rtsp_pulse_device(pa_object *pdevice, bool is_loaded, pa_devi } } -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ static void handle_external_btsco_pulse_device(pa_object *pdevice, bool is_loaded, pa_device_manager *dm) { const char *name, *system_id; dm_device_direction_t direction; @@ -1662,7 +1662,7 @@ static void handle_external_btsco_pulse_device(pa_object *pdevice, bool is_loade pa_tz_device_free(device); } } -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_BT_SCO__ */ static void handle_internal_pulse_device(pa_object *pdevice, bool is_loaded, pa_device_manager *dm) { pa_tz_device *device; @@ -1737,7 +1737,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, pa_dev pulse_device_set_use_internal_codec(PA_OBJECT(sink), true); handle_internal_pulse_device(PA_OBJECT(sink), true, dm); return PA_HOOK_OK; -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ } else if (pulse_device_is_btsco(PA_OBJECT(sink))) { pulse_device_set_use_internal_codec(PA_OBJECT(sink), false); handle_external_btsco_pulse_device(PA_OBJECT(sink), true, dm); @@ -1787,7 +1787,7 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, pa_ } else if (pulse_device_is_alsa(PA_OBJECT(sink)) || pulse_device_is_tizenaudio(PA_OBJECT(sink))) { handle_internal_pulse_device(PA_OBJECT(sink), false, dm); return PA_HOOK_OK; -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ } else if (pulse_device_is_btsco(PA_OBJECT(sink))) { handle_external_btsco_pulse_device(PA_OBJECT(sink), false, dm); return PA_HOOK_OK; @@ -1829,7 +1829,7 @@ 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_rtsp_pulse_device(PA_OBJECT(source), true, dm); return PA_HOOK_OK; -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ } else if (pulse_device_is_btsco(PA_OBJECT(source))) { pulse_device_set_use_internal_codec(PA_OBJECT(source), false); handle_external_btsco_pulse_device(PA_OBJECT(source), true, dm); @@ -1878,7 +1878,7 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc return PA_HOOK_OK; } else if (pulse_device_is_rtsp(PA_OBJECT(source))) { handle_rtsp_pulse_device(PA_OBJECT(source), false, dm); -#ifndef __TIZEN_INTERNAL_BT_SCO__ +#ifdef __TIZEN_TV_EXTERNAL_BT_SCO__ } else if (pulse_device_is_btsco(PA_OBJECT(source))) { handle_external_btsco_pulse_device(PA_OBJECT(source), false, dm); return PA_HOOK_OK; diff --git a/src/module-tizenaudio-policy.c b/src/module-tizenaudio-policy.c index bb331e0..81dbb4e 100644 --- a/src/module-tizenaudio-policy.c +++ b/src/module-tizenaudio-policy.c @@ -85,7 +85,7 @@ struct userdata; #define LOOPBACK_DEFAULT_LATENCY_MSEC 40 #define LOOPBACK_DEFAULT_ADJUST_SEC 3 -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ #define TIMED_BT_SCO_CLOSE_USEC 3000000 #endif @@ -185,7 +185,7 @@ struct userdata { int32_t latency_msec; int32_t adjust_sec; } loopback_args; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ pa_time_event *time_event_bt_sco_close; #endif }; @@ -212,7 +212,7 @@ static void __load_dump_config(struct userdata *u) iniparser_freedict(dict); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ /* threre is only one sco connected device */ static pa_tz_device* _get_sco_connected_device(pa_device_manager *dm) { pa_idxset *device_list; @@ -696,7 +696,7 @@ static bool skip_device(const char *stream_role, const char *device_type) return false; } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static bool skip_bt_sco_device(struct userdata *u, const char *stream_role, const char *device_type) { pa_assert(u); pa_assert(stream_role); @@ -827,7 +827,7 @@ static void select_device_by_auto_or_auto_all_routing(struct userdata *u, pa_str if (!pa_safe_streq(device_type, dm_device_type) || !IS_AVAILABLE_DIRECTION(data->stream_type, dm_device_direction)) continue; pa_log_info(" ** found a matched device: type[%-16s], direction[0x%x]", dm_device_type, dm_device_direction); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (skip_bt_sco_device(u, data->stream_role, dm_device_type)) continue; #endif @@ -901,7 +901,7 @@ static void select_device_by_auto_last_connected_routing(struct userdata *u, pa_ 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; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (skip_bt_sco_device(u, data->stream_role, dm_device_type)) continue; #endif @@ -1149,7 +1149,7 @@ static void reset_route(struct userdata *u, stream_type_t stream_type) { pa_assert(u); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ /* update BT SCO: close */ update_bt_sco_state(u, false, true, NULL); #endif @@ -1305,7 +1305,7 @@ static void fill_device_info(hal_route_info *route_info, const char *type, uint3 route_info->num_of_devices -1, type, direction, id); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static pa_hook_result_t update_combine_sink_and_bt_sco(struct userdata *u, pa_stream_manager_hook_data_for_route *data, pa_tz_device *device, const char *stream_role, const char *dm_device_type, pa_sink **combine_sink_arg1, pa_sink **combine_sink_arg2) { @@ -1329,7 +1329,7 @@ static pa_hook_result_t update_combine_sink(struct userdata *u, pa_stream_manage else pa_log_error("[ROUTE][AUTO] could not get sink"); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (pa_safe_streq(dm_device_type, DEVICE_TYPE_BT_SCO)) { if (IS_ROLE_AVAILABLE_BT_SCO_OPEN(stream_role)) { if (update_bt_sco_state(u, true, false, stream_role)) { @@ -1350,7 +1350,7 @@ static pa_hook_result_t update_combine_sink(struct userdata *u, pa_stream_manage void *s = NULL; stream_route_type_t route_type; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ update_bt_sco_state(u, false, false, NULL); #endif @@ -1436,7 +1436,7 @@ static int32_t update_route_by_preemptive_device(struct userdata *u, pa_stream_m /* if it needs to skip it, keep going to next device for proper UCM setting */ if (skip_device(data->stream_role, dm_device_type)) return -1; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (skip_bt_sco_device(u, data->stream_role, dm_device_type)) return -1; #endif @@ -1492,7 +1492,7 @@ static pa_hook_result_t handle_auto_or_auto_all_routing(struct userdata *u, pa_s /* if it needs to skip it, keep going to next device for proper UCM setting */ if (skip_device(data->stream_role, dm_device_type)) continue; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (skip_bt_sco_device(u, data->stream_role, dm_device_type)) continue; #endif @@ -1513,14 +1513,14 @@ update_auto_active_dev: else pa_proplist_sets(GET_STREAM_PROPLIST(data->stream, data->stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ return update_combine_sink_and_bt_sco(u, data, *device, route_info->role, dm_device_type, NULL, NULL); #else return update_combine_sink(u, data, *device, route_info->role, dm_device_type, NULL, NULL); #endif } else if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) { -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ update_combine_sink_and_bt_sco(u, data, *device, NULL, NULL, &combine_sink_arg1, &combine_sink_arg2); #else update_combine_sink(u, data, *device, NULL, NULL, &combine_sink_arg1, &combine_sink_arg2); @@ -1575,7 +1575,7 @@ static pa_hook_result_t handle_auto_last_connected_routing(struct userdata *u, p continue; if ((use_internal_codec = pa_tz_device_is_use_internal_codec(*device))) { /* if it needs to skip it, keep going to next device for proper UCM setting */ -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (skip_device(data->stream_role, dm_device_type) || skip_bt_sco_device(u, data->stream_role, dm_device_type)) #else @@ -1622,7 +1622,7 @@ update_auto_active_dev: pa_log_error("[ROUTE][AUTO_LAST_CONN] could not get sink"); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (pa_safe_streq(dm_device_type, DEVICE_TYPE_BT_SCO)) { if (IS_ROLE_AVAILABLE_BT_SCO_OPEN(route_info->role)) { if (update_bt_sco_state(u, true, false, route_info->role)) { @@ -1674,7 +1674,7 @@ static pa_hook_result_t handle_manual_routing(struct userdata *u, pa_stream_mana pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", dm_device_type, dm_device_direction); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ /* Check for availability for opening Bluetooth SCO */ if (pa_safe_streq(dm_device_type, DEVICE_TYPE_BT_SCO) && IS_ROLE_AVAILABLE_BT_SCO_OPEN(route_info->role)) { /* update BT SCO: open */ @@ -2016,7 +2016,7 @@ void pa__done(pa_module *m) if (!(u = m->userdata)) return; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ bt_sco_close(u, false); #endif diff --git a/src/tizen-device.c b/src/tizen-device.c index cbeae10..0f8a481 100644 --- a/src/tizen-device.c +++ b/src/tizen-device.c @@ -145,7 +145,7 @@ static char* _device_get_info_str(pa_tz_device *device) { pa_strbuf_printf(buf, " Product ID : %04x\n", device->product_id); pa_strbuf_printf(buf, " Specified Stream role : %s\n", device->specified_stream_role); } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ if (device_type_is_equal(device->type, DEVICE_TYPE_BT_SCO)) pa_strbuf_printf(buf, " SCO opened : %s\n", pa_yes_no(device->sco_opened)); #endif @@ -494,7 +494,7 @@ pa_tz_device* pa_tz_device_new(pa_tz_device_new_data *data) { device->state = DM_DEVICE_STATE_DEACTIVATED; device->creation_time = pa_rtclock_now(); device->use_internal_codec = data->use_internal_codec; -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ device->sco_opened = false; #endif device->is_running = false; @@ -836,7 +836,7 @@ pa_intset* pa_tz_device_get_stream_list(pa_tz_device *device) { return stream_id_set; } -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ static int method_call_bt_sco_enable_pcm(pa_dbus_connection *conn, bool enable) { DBusMessage *msg, *reply; DBusError err; @@ -1086,5 +1086,5 @@ int pa_tz_device_sco_get_status(pa_tz_device *device, dm_device_bt_sco_status_t pa_log_info("BT SCO (%u) Get Status, %d", device->id, *status); return 0; } -#endif /* __TIZEN_INTERNAL_BT_SCO__ */ +#endif /* __TIZEN_TV_EXTERNAL_BT_SCO__ */ diff --git a/src/tizen-device.h b/src/tizen-device.h index a7649ae..a809eed 100644 --- a/src/tizen-device.h +++ b/src/tizen-device.h @@ -59,8 +59,8 @@ struct pa_tz_device { /* Will be true, if this device uses internal codec(card), * false, if this device uses external card(bt-a2dp, usb */ bool use_internal_codec; +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ /* If this is sco device, this can be used to */ -#ifdef __TIZEN_INTERNAL_BT_SCO__ bool sco_opened; #endif @@ -155,7 +155,7 @@ bool pa_tz_device_is_use_internal_codec(pa_tz_device *device); bool pa_tz_device_is_all_suspended(pa_tz_device *device); pa_intset* pa_tz_device_get_stream_list(pa_tz_device *device); -#ifdef __TIZEN_INTERNAL_BT_SCO__ +#ifndef __TIZEN_TV_EXTERNAL_BT_SCO__ /* Only for BT SCO device */ int pa_tz_device_sco_enable_pcm(pa_tz_device *device, bool enable); int pa_tz_device_sco_open(pa_tz_device *device); -- 2.7.4 From 1677d8a0ef35572e10cb8e8ebd4f7a5814c845de Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Fri, 3 Dec 2021 11:15:30 +0900 Subject: [PATCH 15/16] Support external device playback and capture on sink2/source2 module-alsa-card tries to use 'device_id' that is from udev but tizenaudio-sink2/source2 can't load devices with device_id. This patch makes devices load with device_id and device_string both. [Version] 15.0.20 [Issue Type] New feature Change-Id: I2dcd7b4abdc88abacec5ba997050217c0c8bfc48 Signed-off-by: Jaechul Lee --- Makefile.am | 39 +++--- packaging/pulseaudio-modules-tizen.spec | 2 +- src/hal-interface.c | 4 +- src/hal-interface.h | 2 +- src/module-tizenaudio-sink2.c | 2 +- src/module-tizenaudio-source2.c | 2 +- src/tizenaudio-sink2.c | 175 ++++++++++++++++---------- src/tizenaudio-sink2.h | 12 +- src/tizenaudio-source2.c | 177 ++++++++++++++++---------- src/tizenaudio-source2.h | 12 +- src/tizenaudio-util.c | 217 ++++++++++++++++++++++++++++++++ src/tizenaudio-util.h | 54 ++++++++ 12 files changed, 534 insertions(+), 164 deletions(-) create mode 100644 src/tizenaudio-util.c create mode 100644 src/tizenaudio-util.h diff --git a/Makefile.am b/Makefile.am index 12098ed..b3d0913 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,21 +31,21 @@ MODULE_LDFLAGS = $(AM_LDFLAGS) $(PACORE_LDFLAGS) $(PA_LDFLAGS) -module -disable- MODULE_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS) pulsemodlibexec_LTLIBRARIES = \ - libhal-interface.la \ - libprocessor.la \ - libcommunicator.la \ - libtizenaudio-util.la \ - module-tizenaudio-sink.la \ - module-tizenaudio-source.la \ - module-tizenaudio-sink2.la \ - module-tizenaudio-source2.la \ - 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 + libhal-interface.la \ + libprocessor.la \ + libcommunicator.la \ + libtizenaudio-util.la \ + module-tizenaudio-sink.la \ + module-tizenaudio-source.la \ + module-tizenaudio-sink2.la \ + module-tizenaudio-source2.la \ + 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 endif @@ -75,7 +75,14 @@ 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 -libtizenaudio_util_la_SOURCES = src/tizenaudio-sink2.c src/tizenaudio-sink2.h src/tizenaudio-source2.c src/tizenaudio-source2.h src/echo-cancel/echo-cancel-def.h +libtizenaudio_util_la_SOURCES = \ + src/tizenaudio-sink2.c \ + src/tizenaudio-sink2.h \ + src/tizenaudio-source2.c \ + src/tizenaudio-source2.h \ + src/tizenaudio-util.c \ + src/tizenaudio-util.h \ + src/echo-cancel/echo-cancel-def.h libtizenaudio_util_la_LDFLAGS = $(AM_LDFLAGS) $(PA_LDFLAGS) -avoid-version libtizenaudio_util_la_LIBADD = $(AM_LIBADD) $(PA_LIBS) libhal-interface.la libtizenaudio_util_la_CFLAGS = $(MODULE_CFLAGS) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 5a5282f..cd1cfef 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.19 +Version: 15.0.20 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/hal-interface.c b/src/hal-interface.c index e108f38..6837508 100644 --- a/src/hal-interface.c +++ b/src/hal-interface.c @@ -433,11 +433,11 @@ int32_t pa_hal_interface_pcm_recover(pa_hal_interface *h, pcm_handle pcm_h, int } int32_t pa_hal_interface_pcm_get_params(pa_hal_interface *h, pcm_handle pcm_h, uint32_t direction, - void **sample_spec, uint32_t *period_size, uint32_t *periods) { + void *sample_spec, uint32_t *period_size, uint32_t *periods) { audio_return_e hal_ret = AUDIO_RET_OK; pa_assert(h); - pa_assert(*sample_spec); + pa_assert(sample_spec); pa_assert(period_size); pa_assert(periods); diff --git a/src/hal-interface.h b/src/hal-interface.h index ebfca2f..800ff6c 100644 --- a/src/hal-interface.h +++ b/src/hal-interface.h @@ -111,7 +111,7 @@ int32_t pa_hal_interface_pcm_write(pa_hal_interface *h, pcm_handle pcm_h, const int32_t pa_hal_interface_pcm_read(pa_hal_interface *h, pcm_handle pcm_h, void *buffer, uint32_t frames); int32_t pa_hal_interface_pcm_get_fd(pa_hal_interface *h, pcm_handle pcm_h, int *fd); int32_t pa_hal_interface_pcm_recover(pa_hal_interface *h, pcm_handle pcm_h, int err); -int32_t pa_hal_interface_pcm_get_params(pa_hal_interface *h, pcm_handle pcm_h, uint32_t direction, void **sample_spec, uint32_t *period_size, uint32_t *periods); +int32_t pa_hal_interface_pcm_get_params(pa_hal_interface *h, pcm_handle pcm_h, uint32_t direction, void *sample_spec, uint32_t *period_size, uint32_t *periods); int32_t pa_hal_interface_pcm_set_params(pa_hal_interface *h, pcm_handle pcm_h, uint32_t direction, void *sample_spec, uint32_t period_size, uint32_t periods); int32_t pa_hal_interface_add_message_callback(pa_hal_interface *h, hal_message_callback callback, void *user_data); int32_t pa_hal_interface_remove_message_callback(pa_hal_interface *h, hal_message_callback callback); diff --git a/src/module-tizenaudio-sink2.c b/src/module-tizenaudio-sink2.c index 0861262..49410e0 100644 --- a/src/module-tizenaudio-sink2.c +++ b/src/module-tizenaudio-sink2.c @@ -65,7 +65,7 @@ int pa__init(pa_module *m) { goto fail; } - if (!(m->userdata = pa_tizenaudio_sink2_new(m, ma, __FILE__))) + if (!(m->userdata = pa_tizenaudio_sink2_new(m, ma, __FILE__, NULL, NULL, NULL, NULL))) goto fail; pa_modargs_free(ma); diff --git a/src/module-tizenaudio-source2.c b/src/module-tizenaudio-source2.c index 1df595e..cb821fb 100644 --- a/src/module-tizenaudio-source2.c +++ b/src/module-tizenaudio-source2.c @@ -65,7 +65,7 @@ int pa__init(pa_module *m) { goto fail; } - if (!(m->userdata = pa_tizenaudio_source2_new(m, ma, __FILE__))) + if (!(m->userdata = pa_tizenaudio_source2_new(m, ma, __FILE__, NULL, NULL, NULL, NULL))) goto fail; pa_modargs_free(ma); diff --git a/src/tizenaudio-sink2.c b/src/tizenaudio-sink2.c index a7ffd93..ff0dbc7 100644 --- a/src/tizenaudio-sink2.c +++ b/src/tizenaudio-sink2.c @@ -46,12 +46,13 @@ #include #include "hal-interface.h" +#include "tizenaudio-util.h" #include "echo-cancel/echo-cancel-def.h" #define DEFAULT_SINK_NAME "tizenaudio-sink2" #define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENT_MSEC 10 #define DEFAULT_FRAGMENTS 4 struct userdata { @@ -377,59 +378,39 @@ finish: pa_log_debug("Thread shutting down"); } -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver) { +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_card *c, + char **device_string, + pa_sample_spec *sample_spec, + pa_channel_map *channel_map) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; pa_sink_new_data data; uint32_t alternate_sample_rate; const char *modarg_device; - char card[DEVICE_NAME_MAX]; - char device[DEVICE_NAME_MAX]; + const char *modarg_device_id; size_t frame_size, buffer_size, period_frames, buffer_frames; + pa_pcm_params_t param; + + uint32_t period_size; + uint32_t periods; pa_assert(m); pa_assert(ma); + /* TODO: It doesn't need to handle sample_spec, channels_map yet because it comes from UCM */ ss = m->core->default_sample_spec; map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + + if (channel_map && channel_map->channels != ss.channels) + ss.channels = channel_map->channels; + + pa_channel_map_init_extend(&map, ss.channels, PA_CHANNEL_MAP_ALSA); + + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { pa_log_error("Invalid sample format specification or channel map"); goto fail; } @@ -449,19 +430,13 @@ pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *drive u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); + modarg_device_id = pa_modargs_get_value(ma, "device_id", NULL); + modarg_device = pa_modargs_get_value(ma, "device", NULL); + if (!modarg_device_id && !modarg_device) { + pa_log_error("Failed to get device id or device string"); goto fail; } - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); u->nfrags = DEFAULT_FRAGMENTS; if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || @@ -469,11 +444,83 @@ pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *drive pa_log_error("fragment_size or fragments are invalid."); goto fail; } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + + param.direction = DIRECTION_OUT; + param.ss = ss; + param.period_size = u->frag_size / pa_frame_size(&ss); + param.periods = u->nfrags; + + pa_log_info("Try to open device with rate(%d), channels(%d), format(%s), period_size(%u), periods(%u)", + ss.rate, ss.channels, pa_sample_format_to_string(ss.format), param.period_size, param.periods); + + if (modarg_device_id && device_string) { + if (pa_tz_util_pcm_open_by_dev_id( + u->hal_interface, + modarg_device_id, + device_string, + ¶m, + (void **)&u->pcm_handle, + &u->card, + &u->device)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + } else if (modarg_device) { + if (pa_tz_util_pcm_open_by_device( + u->hal_interface, + modarg_device, + ¶m, + (void **)&u->pcm_handle, + &u->card, + &u->device)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + } else { + pa_log_error("Device name doesn't exist"); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, ¶m.ss)) { + pa_sample_spec _ss; + char requested_spec[PA_SAMPLE_SPEC_SNPRINT_MAX]; + char selected_spec[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + if (pa_hal_interface_pcm_get_params(u->hal_interface, u->pcm_handle, 0, (void *)&_ss, &period_size, &periods) < 0) { + pa_log_error("Failed to get pcm params"); + goto fail; + } + + frame_size = pa_frame_size(¶m.ss); + u->frag_size = period_size * frame_size; + /* TODO: can't trust periods value from hal */ + u->nfrags = param.periods; + + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + + pa_log_info("Device doesn't support sample_spec(%s). It was changed to (%s)", + pa_sample_spec_snprint(requested_spec, PA_SAMPLE_SPEC_SNPRINT_MAX, &ss), + pa_sample_spec_snprint(selected_spec, PA_SAMPLE_SPEC_SNPRINT_MAX, ¶m.ss)); + + ss = param.ss; + } + + pa_log_info("Device opened. card(%s), device(%s) rate(%d), channels(%d), format(%s), " + "frag_size(%u), nfrags(%u), period_size(%u), periods(%u)", + u->card, u->device, ss.rate, ss.channels, pa_sample_format_to_string(ss.format), + u->frag_size, u->nfrags, param.period_size, param.periods); pa_sink_new_data_init(&data); data.driver = driver; data.module = m; + data.card = c; pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); @@ -483,14 +530,12 @@ pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *drive pa_proplist_sets(data.proplist, "tizen.device", u->device); pa_proplist_sets(data.proplist, "tizen.version", "2"); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - buffer_size = (size_t)(u->frag_size * u->nfrags); - buffer_frames = buffer_size / frame_size; - period_frames = u->frag_size / frame_size; pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); + if (pa_modargs_get_value(ma, "device_id", NULL)) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "usb"); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log_error("Invalid properties."); pa_sink_new_data_done(&data); @@ -509,17 +554,7 @@ pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *drive u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; u->sink->userdata = u; - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_OUT, - &u->sink->sample_spec, - u->frag_size / pa_frame_size(&u->sink->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); @@ -538,7 +573,9 @@ pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *drive return u->sink; fail: - userdata_free(u); + if (u) + userdata_free(u); + return NULL; } diff --git a/src/tizenaudio-sink2.h b/src/tizenaudio-sink2.h index dd9e9e2..854ea9a 100644 --- a/src/tizenaudio-sink2.h +++ b/src/tizenaudio-sink2.h @@ -29,8 +29,16 @@ #include #include #include - -pa_sink *pa_tizenaudio_sink2_new(pa_module *m, pa_modargs *ma, const char *driver); +#include +#include + +pa_sink *pa_tizenaudio_sink2_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_card *c, + char **device_string, + pa_sample_spec *sample_spec, + pa_channel_map *channel_map); void pa_tizenaudio_sink2_free(pa_sink *s); diff --git a/src/tizenaudio-source2.c b/src/tizenaudio-source2.c index 8ddfa2f..846b086 100644 --- a/src/tizenaudio-source2.c +++ b/src/tizenaudio-source2.c @@ -46,12 +46,13 @@ #include #include "hal-interface.h" +#include "tizenaudio-util.h" #include "echo-cancel/echo-cancel-def.h" #define DEFAULT_SOURCE_NAME "tizenaudio-source2" #define DEVICE_NAME_MAX 30 -#define DEFAULT_FRAGMENT_MSEC 20 +#define DEFAULT_FRAGMENT_MSEC 10 #define DEFAULT_FRAGMENTS 4 struct userdata { @@ -382,58 +383,39 @@ finish: pa_log_debug("Thread shutting down"); } -static int parse_to_get_card(const char *modarg_device, char *card) { - const char *name_p; - char *card_p; - - if (!strchr(modarg_device, ',')) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - name_p = modarg_device; - card_p = card; - while (*name_p != ',') - *(card_p++) = *(name_p++); - *card_p = '\0'; - - return 0; -} - -static int parse_to_get_device(const char *modarg_device, char *device) { - const char *comma_p; - char *device_p; - - if (!(comma_p = strchr(modarg_device, ','))) { - pa_log_error("Failed to parse device argument : no comma"); - return -1; - } - - comma_p++; - device_p = device; - while (*comma_p != '\0') - *(device_p++) = *(comma_p++); - *device_p = '\0'; - - return 0; -} - -pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver) { +pa_source *pa_tizenaudio_source2_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_card *c, + char **device_string, + pa_sample_spec *sample_spec, + pa_channel_map *channel_map) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; pa_source_new_data data; uint32_t alternate_sample_rate; const char *modarg_device; - char card[DEVICE_NAME_MAX]; - char device[DEVICE_NAME_MAX]; + const char *modarg_device_id; size_t frame_size, buffer_size, period_frames, buffer_frames; + pa_pcm_params_t param; + + uint32_t period_size; + uint32_t periods; pa_assert(m); + pa_assert(ma); + /* TODO: It doesn't need to handle sample_spec, channels_map yet because it comes from UCM */ ss = m->core->default_sample_spec; map = m->core->default_channel_map; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + + if (channel_map && channel_map->channels != ss.channels) + ss.channels = channel_map->channels; + + pa_channel_map_init_extend(&map, ss.channels, PA_CHANNEL_MAP_ALSA); + + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { pa_log_error("Invalid sample format specification or channel map"); goto fail; } @@ -452,19 +434,13 @@ pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *d u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - if (!(modarg_device = pa_modargs_get_value(ma, "device", NULL))) { - pa_log_error("device is invalid"); + modarg_device_id = pa_modargs_get_value(ma, "device_id", NULL); + modarg_device = pa_modargs_get_value(ma, "device", NULL); + if (!modarg_device_id && !modarg_device) { + pa_log_error("Failed to get device id or device string"); goto fail; } - if (parse_to_get_card(modarg_device, card) || parse_to_get_device(modarg_device, device)) { - pa_log_error("failed to parse device module argument, %s", modarg_device); - goto fail; - } - - u->card = pa_xstrdup(card); - u->device = pa_xstrdup(device); - u->frag_size = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGMENT_MSEC * PA_USEC_PER_MSEC, &ss); u->nfrags = DEFAULT_FRAGMENTS; if (pa_modargs_get_value_u32(ma, "fragment_size", &u->frag_size) < 0 || @@ -472,11 +448,83 @@ pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *d pa_log_error("fragment_size or fragments are invalid."); goto fail; } - pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", u->card, u->device, u->frag_size, u->nfrags); + + frame_size = pa_frame_size(&ss); + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + + param.direction = DIRECTION_IN; + param.ss = ss; + param.period_size = u->frag_size / pa_frame_size(&ss); + param.periods = u->nfrags; + + pa_log_info("Try to open device with rate(%d), channels(%d), format(%s), period_size(%u), periods(%u)", + ss.rate, ss.channels, pa_sample_format_to_string(ss.format), param.period_size, param.periods); + + if (modarg_device_id && device_string) { + if (pa_tz_util_pcm_open_by_dev_id( + u->hal_interface, + modarg_device_id, + device_string, + ¶m, + (void **)&u->pcm_handle, + &u->card, + &u->device)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + } else if (modarg_device) { + if (pa_tz_util_pcm_open_by_device( + u->hal_interface, + modarg_device, + ¶m, + (void **)&u->pcm_handle, + &u->card, + &u->device)) { + pa_log_error("Error opening PCM device"); + goto fail; + } + } else { + pa_log_error("Device name doesn't exist"); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, ¶m.ss)) { + pa_sample_spec _ss; + char requested_spec[PA_SAMPLE_SPEC_SNPRINT_MAX]; + char selected_spec[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + if (pa_hal_interface_pcm_get_params(u->hal_interface, u->pcm_handle, 0, (void *)&_ss, &period_size, &periods) < 0) { + pa_log_error("Failed to get pcm params"); + goto fail; + } + + frame_size = pa_frame_size(¶m.ss); + u->frag_size = period_size * frame_size; + /* TODO: can't trust periods value from hal */ + u->nfrags = param.periods; + + buffer_size = (size_t)(u->frag_size * u->nfrags); + buffer_frames = buffer_size / frame_size; + period_frames = u->frag_size / frame_size; + + pa_log_info("Device doesn't support sample_spec(%s). It was changed to (%s)", + pa_sample_spec_snprint(requested_spec, PA_SAMPLE_SPEC_SNPRINT_MAX, &ss), + pa_sample_spec_snprint(selected_spec, PA_SAMPLE_SPEC_SNPRINT_MAX, ¶m.ss)); + + ss = param.ss; + } + + pa_log_info("Device opened. card(%s), device(%s) rate(%d), channels(%d), format(%s), " + "frag_size(%u), nfrags(%u), period_size(%u), periods(%u)", + u->card, u->device, ss.rate, ss.channels, pa_sample_format_to_string(ss.format), + u->frag_size, u->nfrags, period_size, param.periods); pa_source_new_data_init(&data); data.driver = driver; data.module = m; + data.card = c; pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); pa_source_new_data_set_sample_spec(&data, &ss); pa_source_new_data_set_channel_map(&data, &map); @@ -486,14 +534,12 @@ pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *d pa_proplist_sets(data.proplist, "tizen.device", u->device); pa_proplist_sets(data.proplist, "tizen.version", "2"); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "tizen"); - - frame_size = pa_frame_size(&ss); - buffer_size = (size_t)(u->frag_size * u->nfrags); - buffer_frames = buffer_size / frame_size; - period_frames = u->frag_size / frame_size; pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%zu", buffer_frames * frame_size); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%zu", period_frames * frame_size); + if (pa_modargs_get_value(ma, "device_id", NULL)) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "usb"); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log_error("Invalid properties."); pa_source_new_data_done(&data); @@ -512,17 +558,8 @@ pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *d u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; u->source->userdata = u; - if (pa_hal_interface_pcm_open(u->hal_interface, - u->card, - u->device, - DIRECTION_IN, - &u->source->sample_spec, - u->frag_size / pa_frame_size(&u->source->sample_spec), - u->nfrags, - (void **)&u->pcm_handle)) { - pa_log_error("Error opening PCM device"); - goto fail; - } + pa_log_info("card(%s) device(%s) fragment_size(%u), fragments(%u)", + u->card, u->device, u->frag_size, u->nfrags); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); @@ -540,7 +577,9 @@ pa_source *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *d return u->source; fail: - userdata_free(u); + if (u) + userdata_free(u); + return NULL; } diff --git a/src/tizenaudio-source2.h b/src/tizenaudio-source2.h index a8e67c0..c1cdcae 100644 --- a/src/tizenaudio-source2.h +++ b/src/tizenaudio-source2.h @@ -29,8 +29,16 @@ #include #include #include - -pa_sink *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver); +#include +#include + +pa_source *pa_tizenaudio_source2_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_card *c, + char **device_string, + pa_sample_spec *sample_spec, + pa_channel_map *channel_map); void pa_tizenaudio_source2_free(pa_source *s); diff --git a/src/tizenaudio-util.c b/src/tizenaudio-util.c new file mode 100644 index 0000000..17be479 --- /dev/null +++ b/src/tizenaudio-util.c @@ -0,0 +1,217 @@ +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include "hal-interface.h" +#include "tizenaudio-util.h" + +#define CARD_NAME_MAX 32 +#define DEVICE_NAME_MAX 32 + +/* + * Extract card string from the device_string that contains device name like 'hw:1,2' + * card variable will have '1' after calling this function. + */ +static int parse_to_get_card(const char *device_string, char *card, int n, const char *def) { + char *name_p; + char *card_p; + char *sep; + int i; + + /* device_string contains various device name such as 'ALSA,0', 'hw:0,0" and "hw:1,1,1" */ + if (!(sep = strchr(device_string, ':'))) { + + /* It doesn't have a prefix like 'hw:', 'hdmi:' */ + if ((sep = strchr(device_string, ','))) { + size_t size = sep - device_string; + + memcpy(card, device_string, size); + card[size] = '\0'; + + return 0; + } + + /* Can't find card number. Use default */ + if (!def) + return -1; + + snprintf(card, n, "%s", def); + } else { + name_p = ++sep; + card_p = card; + + for (i = 0; i < n; i++) { + if (*name_p == ',' || *name_p == '\0') + break; + + *(card_p++) = *(name_p++); + } + + *card_p = '\0'; + } + + return 0; +} + +/* + * Extract card string from the device_string that contains device name like 'hw:1,2' + * card variable will have '2' after calling this function. + */ +static int parse_to_get_device(const char *device_string, char *device, int n, const char *def) { + char *device_p; + char *sep; + + if (!(sep = strchr(device_string, ','))) { + if (!def) + return -1; + + n = snprintf(device, n, "%s", def); + } else { + sep++; + device_p = device; + + while (*sep != '\0') + *(device_p++) = *(sep++); + + *device_p = '\0'; + } + + return 0; +} + +int32_t pa_hal_interface_pcm_open_by_dev_id(pa_hal_interface *h, + const char *dev_id, + char **device_string, + pa_pcm_params_t *param, + pcm_handle *pcm_h, + char **card, + char **device) { + char **i; + char _card[DEVICE_NAME_MAX]; + char _device[DEVICE_NAME_MAX]; + + pa_assert(dev_id); + pa_assert(device_string); + pa_assert(device_string[0]); + + pa_assert(card); + pa_assert(device); + + for (i = device_string; *i; i++) { + char *d; + + pa_log_debug("Try to open device(%s), id(%s)", *i, dev_id); + + if (!(d = pa_replace(*i, "%f", dev_id))) { + pa_log_error("Error parsing i(%s) dev_id(%s)", *i, dev_id); + continue; + } + + if (parse_to_get_device(d, _device, DEVICE_NAME_MAX, "0") < 0) { + pa_log_error("Failed to parse device_string. It doesn't contain device"); + pa_xfree(d); + continue; + } + + if (parse_to_get_card(d, _card, CARD_NAME_MAX, "0") < 0) { + pa_log_error("Failed to parse card"); + pa_xfree(d); + continue; + } + + if (pa_hal_interface_pcm_open(h, + _card, + _device, + param->direction, + ¶m->ss, + param->period_size, + param->periods, + (void **)pcm_h)) { + pa_log_error("Failed to open PCM device (%s)", d); + pa_xfree(d); + continue; + } + + *card = pa_xstrdup(_card); + *device = pa_xstrdup(_device); + + pa_log_debug("card(%s) device(%s) opened successfully", *card, *device); + + pa_xfree(d); + + return 0; + } + + pa_log_error("Error opening PCM device"); + + return -1; +} + +int32_t pa_hal_interface_pcm_open_by_device(pa_hal_interface *h, + const char* dev, + pa_pcm_params_t *param, + pcm_handle *pcm_h, + char **card, + char **device) { + char _card[DEVICE_NAME_MAX]; + char _device[DEVICE_NAME_MAX]; + + pa_assert(dev); + + pa_log_debug("Try to open device(%s)", dev); + + if (parse_to_get_device(dev, _device, DEVICE_NAME_MAX, NULL) < 0) { + pa_log_error("Failed to parse device. dev(%s)", dev); + return -1; + } + + if (parse_to_get_card(dev, _card, CARD_NAME_MAX, NULL) < 0) { + pa_log_error("Failed to parse card. dev(%s)", dev); + return -1; + } + + if (pa_hal_interface_pcm_open(h, + _card, + _device, + param->direction, + ¶m->ss, + param->period_size, + param->periods, + (void **)pcm_h)) { + pa_log_error("Failed to open PCM device (%s) card(%s), device(%s)", dev, _card, _device); + return -1; + } + + *card = pa_xstrdup(_card); + *device = pa_xstrdup(_device); + + pa_log_debug("card(%s) device(%s) opened successfully", *card, *device); + + return 0; +} diff --git a/src/tizenaudio-util.h b/src/tizenaudio-util.h new file mode 100644 index 0000000..ea74fb9 --- /dev/null +++ b/src/tizenaudio-util.h @@ -0,0 +1,54 @@ +#ifndef footizenaudioutilhfoo +#define footizenaudioutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +typedef struct pa_pcm_params { + io_direction_t direction; + pa_sample_spec ss; + uint32_t period_size; + uint32_t periods; +} pa_pcm_params_t; + + +int32_t pa_tz_util_pcm_open_by_dev_id(pa_hal_interface *h, + const char *dev_id, + char **device_string, + pa_pcm_params_t *param, + pcm_handle *pcm_h, + char **card, + char **device); + +int32_t pa_tz_util_pcm_open_by_device(pa_hal_interface *h, + const char* dev, + pa_pcm_params_t *param, + pcm_handle *pcm_h, + char **card, + char **device); +#endif + -- 2.7.4 From 524ffee0ef91c855368f0be0a70d335114d37c7c Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Thu, 2 Jun 2022 11:20:43 +0900 Subject: [PATCH 16/16] Improve latency difference between reference and capture source * Not allow negative latency * Added mchstreamer specific code * Fixed build warning on aarch64 [Version] 15.0.21 [Issue Type] Improvement Change-Id: Idd6c4337ec6567e52dd86bbca5c194a897804b43 Signed-off-by: Jaechul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/echo-cancel/algo_reference_copy.c | 11 ++++++++++- src/echo-cancel/module-tizenaudio-echo-cancel.c | 2 +- src/echo-cancel/processor.c | 16 ++++++++++++++-- src/tizenaudio-sink2.c | 13 ++++++++++--- src/tizenaudio-source2.c | 9 +++++++-- 6 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index cd1cfef..1b6d631 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.20 +Version: 15.0.21 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ diff --git a/src/echo-cancel/algo_reference_copy.c b/src/echo-cancel/algo_reference_copy.c index 2d9cf0b..7b397ec 100644 --- a/src/echo-cancel/algo_reference_copy.c +++ b/src/echo-cancel/algo_reference_copy.c @@ -94,13 +94,22 @@ int32_t reference_copy_change_reference_spec(void *priv, pa_sample_spec *source_ pa_assert(map); channels = rc->ss.channels - source_ss->channels; - if (channels <= 0) + if (channels <= 0) { + pa_log_error("No empty channels. reference copy will be disabled"); return -1; + } + + /* TODO: temporary code for supporting mchstreamer. reference copy uses the last 2ch of its channels */ + if (rc->ss.channels == 16) + channels = 2; *sample_spec = rc->ss; sample_spec->channels = rc->reference_channels = channels; pa_channel_map_init_auto(map, channels, PA_CHANNEL_MAP_AIFF); + pa_log_info("reference will be copied to empty channels(%d). source-output ch(%d), source ch(%d)", + channels, rc->ss.channels, source_ss->channels); + return 0; } diff --git a/src/echo-cancel/module-tizenaudio-echo-cancel.c b/src/echo-cancel/module-tizenaudio-echo-cancel.c index f9e13c0..7697368 100644 --- a/src/echo-cancel/module-tizenaudio-echo-cancel.c +++ b/src/echo-cancel/module-tizenaudio-echo-cancel.c @@ -298,7 +298,7 @@ static int process_msg( /* trigger resolves a race condition related to post_process between source and render thread */ if (u->triggered) { - if (code == PA_ECHO_CANCEL_MESSAGE_PUSH_DATA || code == PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO) { + if (code == PA_ECHO_CANCEL_MESSAGE_PUSH_ECHO) { pa_source_output *o = NULL; pa_usec_t latency; diff --git a/src/echo-cancel/processor.c b/src/echo-cancel/processor.c index 0052ab8..dbce5fa 100644 --- a/src/echo-cancel/processor.c +++ b/src/echo-cancel/processor.c @@ -43,6 +43,7 @@ #endif #define MEMBLOCKQ_MAXLENGTH (16 * 1024 * 1024) +#define MEMBLOCKQ_PUSH_REFERENCE_BLOCK_ALIGN typedef struct pa_processor_method_interface pa_processor_method_interface; struct pa_processor { @@ -298,14 +299,24 @@ int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_u pa_assert(processor); bytes = pa_usec_to_bytes(latency, &processor->reference_memblockq_ss); - n = (bytes + (processor->reference_process_bytes - 1)) / processor->reference_process_bytes; +#ifdef MEMBLOCKQ_PUSH_REFERENCE_BLOCK_ALIGN + n = (bytes + (processor->reference_process_bytes - 1)) / processor->reference_process_bytes; pa_silence_memchunk_get( &processor->core->silence_cache, processor->core->mempool, &silence, &processor->reference_memblockq_ss, processor->reference_process_bytes); +#else + n = 1; + pa_silence_memchunk_get( + &processor->core->silence_cache, + processor->core->mempool, + &silence, + &processor->reference_memblockq_ss, + bytes); +#endif write_index = pa_memblockq_get_write_index(processor->reference_memblockq); read_index = pa_memblockq_get_read_index(processor->reference_memblockq); @@ -315,9 +326,10 @@ int pa_processor_setup_reference_memblockq_padding(pa_processor *processor, pa_u pa_memblock_unref(silence.memblock); - pa_log_info("push n(%u) silence blocks. ref_process_bytes(%zu) " + pa_log_info("push n(%u) silence blocks. latency(%" PRId64 "), ref_process_bytes(%zu) " "write_index(%" PRId64 "->%" PRId64 "), read_index(%" PRId64 "->%" PRId64 ")", pa_memblockq_get_nblocks(processor->reference_memblockq), + latency, processor->reference_process_bytes, write_index, pa_memblockq_get_write_index(processor->reference_memblockq), read_index, pa_memblockq_get_read_index(processor->reference_memblockq)); diff --git a/src/tizenaudio-sink2.c b/src/tizenaudio-sink2.c index ff0dbc7..f8601ac 100644 --- a/src/tizenaudio-sink2.c +++ b/src/tizenaudio-sink2.c @@ -169,7 +169,6 @@ static int unsuspend(struct userdata *u) { u->write_count = 0; u->first = true; - u->timestamp = pa_rtclock_now(); pa_log_info("Resumed successfully..."); @@ -245,8 +244,13 @@ static int sink_process_msg( case PA_SINK_MESSAGE_GET_LATENCY: { int64_t r = 0; - if (u->pcm_handle) - r = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - pa_rtclock_now(); + if (u->pcm_handle) { + pa_usec_t now = pa_rtclock_now(); + pa_usec_t write_latency = u->timestamp + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec); + + if (now < write_latency) + r = write_latency - now; + } *((int64_t *) data) = r; @@ -297,6 +301,9 @@ static int process_render(struct userdata *u) { pa_sink_render_full(u->sink, u->frag_size, &chunk); p = pa_memblock_acquire(chunk.memblock); + if (u->first) + u->timestamp = pa_rtclock_now(); + if (pa_hal_interface_pcm_write(u->hal_interface, u->pcm_handle, (const char*)p + chunk.index, (uint32_t)frames_to_write)) { pa_log_error("failed to write pcm. p(%p), size(%zu)", p, frames_to_write); return -1; diff --git a/src/tizenaudio-source2.c b/src/tizenaudio-source2.c index 846b086..948e8f0 100644 --- a/src/tizenaudio-source2.c +++ b/src/tizenaudio-source2.c @@ -248,8 +248,13 @@ static int source_process_msg( case PA_SOURCE_MESSAGE_GET_LATENCY: { uint64_t r = 0; - if (u->pcm_handle) - r = pa_rtclock_now() - (u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec)); + if (u->pcm_handle) { + pa_usec_t now = pa_rtclock_now(); + pa_usec_t read_latency = u->timestamp + pa_bytes_to_usec(u->read_count, &u->source->sample_spec); + + if (now > read_latency) + r = now - read_latency; + } *((int64_t *) data) = r; -- 2.7.4