Separate tizenaudio-sink2 and module-tizenaudio-sink2 45/274345/4 accepted/tizen/unified/20220525.010010 submit/tizen/20220513.073133 submit/tizen/20220518.023609
authorJaechul Lee <jcsing.lee@samsung.com>
Tue, 30 Nov 2021 02:19:34 +0000 (11:19 +0900)
committerJaechul Lee <jcsing.lee@samsung.com>
Mon, 2 May 2022 04:24:16 +0000 (13:24 +0900)
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 <jcsing.lee@samsung.com>
Makefile.am
packaging/pulseaudio-modules-tizen.spec
src/module-tizenaudio-sink2.c
src/module-tizenaudio-source2.c
src/tizenaudio-sink2.c [new file with mode: 0644]
src/tizenaudio-sink2.h [new file with mode: 0644]
src/tizenaudio-source2.c [new file with mode: 0644]
src/tizenaudio-source2.h [new file with mode: 0644]

index e61e057..ed2843f 100644 (file)
@@ -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 = \
index 21f6ccc..0dccc40 100644 (file)
@@ -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
index 36675c3..0861262 100644 (file)
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include <pulse/rtclock.h>
-#include <pulse/timeval.h>
-#include <pulse/xmalloc.h>
-#include <pulse/util.h>
-
-#include <pulsecore/i18n.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/sink.h>
-#include <pulsecore/module.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/modargs.h>
 #include <pulsecore/log.h>
-#include <pulsecore/thread.h>
-#include <pulsecore/thread-mq.h>
-#include <pulsecore/rtpoll.h>
-#include <pulsecore/poll.h>
 
-#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=<number of fragments> "
         "fragment_size=<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);
 }
index a47dbbe..1df595e 100644 (file)
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include <pulse/rtclock.h>
-#include <pulse/timeval.h>
-#include <pulse/xmalloc.h>
-#include <pulse/util.h>
-
-#include <pulsecore/i18n.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/source.h>
-#include <pulsecore/module.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/modargs.h>
 #include <pulsecore/log.h>
-#include <pulsecore/thread.h>
-#include <pulsecore/thread-mq.h>
-#include <pulsecore/rtpoll.h>
-#include <pulsecore/poll.h>
 
-#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=<number of fragments> "
         "fragment_size=<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 (file)
index 0000000..a7ffd93
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/poll.h>
+
+#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 (file)
index 0000000..dd9e9e2
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/sink.h>
+
+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 (file)
index 0000000..8ddfa2f
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/poll.h>
+
+#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 (file)
index 0000000..a8e67c0
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/source.h>
+
+pa_sink *pa_tizenaudio_source2_new(pa_module *m, pa_modargs *ma, const char *driver);
+
+void pa_tizenaudio_source2_free(pa_source *s);
+
+#endif
+