From: Jaechul Lee Date: Tue, 30 Nov 2021 02:19:34 +0000 (+0900) Subject: Separate tizenaudio-sink2 and module-tizenaudio-sink2 X-Git-Tag: submit/tizen/20220513.073133^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=bcc7b8b7639a80d1c2541ed6ca0dcd1692095c3f;p=platform%2Fcore%2Fmultimedia%2Fpulseaudio-modules-tizen.git 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 --- 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 +