From: Seungbae Shin Date: Mon, 23 Mar 2020 11:22:54 +0000 (+0900) Subject: Add module-tizenaudio-discover / module-tizenaudio-publish for remote audio feature X-Git-Tag: submit/tizen/20200814.044955~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2c1bdbb11d7fb3568f53c3f5996edde35a1ac5ed;p=platform%2Fcore%2Fmultimedia%2Fpulseaudio-modules-tizen.git Add module-tizenaudio-discover / module-tizenaudio-publish for remote audio feature module-tizenaudio-discover : based on module-zeroconf-discover with mDNSResponder porting module-tizenaudio-publish : based on module-bonjour-publish with minor changes [Version] 13.0.21 [Issue Type] New feature Change-Id: Id47dad038bfae487bbe57a09aaece674b30f9008 --- diff --git a/Makefile.am b/Makefile.am index 9b59496..07985dc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,8 @@ pulsemodlibexec_LTLIBRARIES = \ module-tizenaudio-sink.la \ module-tizenaudio-source.la \ module-tizenaudio-policy.la \ + module-tizenaudio-discover.la \ + module-tizenaudio-publish.la \ module-sound-player.la \ module-tone-player.la \ module-poweroff.la @@ -115,6 +117,16 @@ module_acm_sink_la_LIBADD = $(MODULE_LIBADD) module_acm_sink_la_CFLAGS = $(MODULE_CFLAGS) endif +module_tizenaudio_discover_la_SOURCES = src/module-tizenaudio-discover.c +module_tizenaudio_discover_la_LDFLAGS = $(MODULE_LDFLAGS) +module_tizenaudio_discover_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS) +module_tizenaudio_discover_la_CFLAGS = $(MODULE_CFLAGS) $(DNSSD_CFLAGS) + +module_tizenaudio_publish_la_SOURCES = src/module-tizenaudio-publish.c +module_tizenaudio_publish_la_LDFLAGS = $(MODULE_LDFLAGS) +module_tizenaudio_publish_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS) +module_tizenaudio_publish_la_CFLAGS = $(AM_CFLAGS) $(DNSSD_CFLAGS) + if ENABLE_VCONF_HELPER pulsemodlibexec_LTLIBRARIES += module-vconf.la diff --git a/configure.ac b/configure.ac index f38d675..8557992 100644 --- a/configure.ac +++ b/configure.ac @@ -361,6 +361,10 @@ PKG_CHECK_MODULES(VCONF, vconf) AC_SUBST(VCONF_CFLAGS) AC_SUBST(VCONF_LIBS) +PKG_CHECK_MODULES(DNSSD, dns_sd) +AC_SUBST(DNSSD_CFLAGS) +AC_SUBST(DNSSD_LIBS) + dnl use hal tc ------------------------------------------------------------ AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]), [ diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 3944878..c3749d4 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -1,6 +1,6 @@ Name: pulseaudio-modules-tizen Summary: Pulseaudio modules for Tizen -Version: 13.0.20 +Version: 13.0.21 Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ @@ -20,6 +20,7 @@ BuildRequires: mm-hal-interface-devel BuildRequires: pkgconfig(libpulse) BuildRequires: pkgconfig(pulsecore) BuildRequires: pkgconfig(libsystemd) +BuildRequires: pkgconfig(dns_sd) BuildRequires: pulseaudio BuildRequires: m4 Requires(post): /sbin/ldconfig @@ -74,6 +75,8 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf %{_libdir}/pulse-13.0/modules/module-tizenaudio-policy.so %{_libdir}/pulse-13.0/modules/module-tizenaudio-sink.so %{_libdir}/pulse-13.0/modules/module-tizenaudio-source.so +%{_libdir}/pulse-13.0/modules/module-tizenaudio-discover.so +%{_libdir}/pulse-13.0/modules/module-tizenaudio-publish.so %{_libdir}/pulse-13.0/modules/libhal-interface.so %{_libdir}/pulse-13.0/modules/libcommunicator.so %{_tmpfilesdir}/pulseaudio.conf diff --git a/src/module-tizenaudio-discover.c b/src/module-tizenaudio-discover.c new file mode 100644 index 0000000..ac88209 --- /dev/null +++ b/src/module-tizenaudio-discover.c @@ -0,0 +1,733 @@ +/*** + This file is part of PulseAudio. + + Copyright 2020 Seungbae Shin + based on module-zeroconf-discover.c + + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +PA_MODULE_AUTHOR("Seungbae Shin"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_pulse-source._tcp" +#define MAX_IP_ADDR 32 +#define MAX_RECORD_LEN 256 + +static const char * const valid_modargs[] = { + NULL +}; + +typedef struct dns_request_data { + DNSServiceRef sd_ref; + pa_io_event *io; +} dns_request_data_t; + +typedef struct service_data { + struct userdata *u; + + int protocol; + dns_request_data_t resolve; + dns_request_data_t addr; + + /* browse */ + int flags; + unsigned int interface; + char *service_name; + char *service_type; + char *domain; + + /* resolve */ + char *host_name; + unsigned short port; + + char *device; + pa_sample_spec ss; + pa_channel_map cm; + bool channel_map_set; + + /* address */ + char *ip_address; +} service_data_t; + +struct tunnel { + unsigned int interface; + int protocol; + char *name, *type, *domain; + uint32_t module_index; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + dns_request_data_t browse_sink; + dns_request_data_t browse_source; + + pa_hashmap *tunnels; +}; + +static unsigned tunnel_hash(const void *p) { + const struct tunnel *t = p; + + return + (unsigned) t->interface + + (unsigned) t->protocol + + pa_idxset_string_hash_func(t->name) + + pa_idxset_string_hash_func(t->type) + + pa_idxset_string_hash_func(t->domain); +} + +static int tunnel_compare(const void *a, const void *b) { + const struct tunnel *ta = a, *tb = b; + int r; + + if (ta->interface != tb->interface) + return 1; + if (ta->protocol != tb->protocol) + return 1; + if ((r = strcmp(ta->name, tb->name))) + return r; + if ((r = strcmp(ta->type, tb->type))) + return r; + if ((r = strcmp(ta->domain, tb->domain))) + return r; + + return 0; +} + +static struct tunnel *tunnel_new( + unsigned int interface, int protocol, + const char *name, const char *type, const char *domain) { + struct tunnel *t; + t = pa_xnew0(struct tunnel, 1); + t->interface = interface; + t->protocol = protocol; + t->name = pa_xstrdup(name); + t->type = pa_xstrdup(type); + t->domain = pa_xstrdup(domain); + t->module_index = PA_IDXSET_INVALID; + return t; +} + +static void tunnel_free(struct tunnel *t) { + pa_assert(t); + pa_xfree(t->name); + pa_xfree(t->type); + pa_xfree(t->domain); + pa_xfree(t); +} + +static service_data_t *service_data_new() { + return pa_xnew0(service_data_t, 1); +} + +static void service_data_free(service_data_t *data) { + pa_assert(data); + + pa_xfree(data->service_name); + pa_xfree(data->service_type); + pa_xfree(data->domain); + pa_xfree(data->host_name); + pa_xfree(data->ip_address); + pa_xfree(data->device); + + pa_xfree(data); +} + + +static int request_set_io_callback(pa_mainloop_api *m_api, dns_request_data_t *request, pa_io_event_cb_t cb, void *user_data) { + int fd = -1; + + pa_assert(request); + pa_assert(request->sd_ref); + + fd = DNSServiceRefSockFD(request->sd_ref); + + if (fd < 0) { + pa_log_error("DNSServiceRefSockFD(%p) error", request->sd_ref); + return -1; + } + + request->io = m_api->io_new(m_api, fd, + PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, + cb, user_data); + + pa_log_info("io(%p) with cb(%p) added for fd(%d)", request->io, cb, fd); + + return 0; +} + +static void request_clear(pa_mainloop_api *m_api, dns_request_data_t *request) { + if (request->sd_ref) { + DNSServiceRefDeallocate(request->sd_ref); + request->sd_ref = NULL; + } + + if (request->io) { + m_api->io_free(request->io); + request->io = NULL; + } +} + +static char *get_ip_str(const struct sockaddr *sa) { + char addr_str[MAX_IP_ADDR] = { 0, }; + + switch(sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), addr_str, MAX_IP_ADDR); + break; + + case AF_INET6: + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), addr_str, MAX_IP_ADDR); + break; + + default: + pa_strlcpy(addr_str, "Unknown AF", MAX_IP_ADDR); + break; + } + + return pa_xstrdup(addr_str); +} + +static void parse_txt_record(service_data_t *data, + const unsigned char *txt_record, + unsigned short txt_len) { + char record_str[MAX_RECORD_LEN] = { 0, }; + int record_len; + const char *key_str = NULL; + const char *val_str = NULL; + const char *state = NULL; + size_t val_len; + const char *delimiter = "="; + const unsigned char *cur = txt_record; + + pa_assert(data); + pa_assert(txt_record); + pa_assert(txt_len > 0); + + while (cur < txt_record + txt_len) { + record_len = *cur; + state = NULL; + + pa_snprintf(record_str, record_len + 1, "%s", ++cur); + + key_str = pa_split_in_place(record_str, delimiter, &val_len, &state); + if (pa_strneq(key_str, "device", val_len)) { + data->device = pa_split(record_str, delimiter, &state); + } else if (pa_strneq(key_str, "rate", val_len)) { + data->ss.rate = (uint32_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state)); + } else if (pa_strneq(key_str, "channels", val_len)) { + data->ss.channels = (uint8_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state)); + } else if (pa_strneq(key_str, "format", val_len)) { + val_str = pa_split_in_place(record_str, delimiter, &val_len, &state); + data->ss.format = pa_parse_sample_format(val_str); + } else if (pa_strneq(key_str, "channel_map", val_len)) { + val_str = pa_split_in_place(record_str, delimiter, &val_len, &state); + pa_channel_map_parse(&data->cm, val_str); + } + + cur += record_len; + } + pa_log_info("-----------------------------------"); + pa_log_info("device : [%s]", data->device); + pa_log_info("rate : [%d]", data->ss.rate); + pa_log_info("channels : [%d]", data->ss.channels); + pa_log_info("format : [%s]", pa_sample_format_to_string(data->ss.format)); + pa_log_info("channel_map : [%s]", pa_channel_map_to_pretty_name(&data->cm)); + pa_log_info("-----------------------------------"); +} + +static void tunnel_add(service_data_t *data) { + struct tunnel *tnl; + char *dname, *module_name, *args; + const char *t; + char cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_module *m; + + pa_assert(data); + pa_assert(data->u); + + tnl = tunnel_new(data->interface, data->protocol, data->service_name, data->service_type, data->domain); + + data->ss = data->u->core->default_sample_spec; + data->cm = data->u->core->default_channel_map; + + if (!data->channel_map_set && data->cm.channels != data->ss.channels) + pa_channel_map_init_extend(&data->cm, data->ss.channels, PA_CHANNEL_MAP_DEFAULT); + + if (!pa_sample_spec_valid(&data->ss)) { + pa_log("Service '%s' contains an invalid sample specification.", data->service_name); + goto finish; + } + + if (!pa_channel_map_valid(&data->cm) || data->cm.channels != data->ss.channels) { + pa_log("Service '%s' contains an invalid channel map.", data->service_name); + goto finish; + } + + if (data->device) + dname = pa_sprintf_malloc("tunnel.%s.%s", data->host_name, data->device); + else + dname = pa_sprintf_malloc("tunnel.%s", data->host_name); + + if (!pa_namereg_is_valid_name(dname)) { + pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); + pa_xfree(dname); + goto finish; + } + + t = strstr(data->service_type, "sink") ? "sink" : "source"; + + module_name = pa_sprintf_malloc("module-tunnel-%s", t); + args = pa_sprintf_malloc("server=[%s]:%u " + "%s=%s " + "format=%s " + "channels=%u " + "rate=%u " + "%s_name=%s " + "channel_map=%s", + data->ip_address, + data->port, + t, data->device, + pa_sample_format_to_string(data->ss.format), + data->ss.channels, + data->ss.rate, + t, dname, + pa_channel_map_snprint(cmt, sizeof(cmt), &data->cm)); + + pa_log_debug("Loading %s with arguments '%s'", module_name, args); + + if (pa_module_load(&m, data->u->core, module_name, args) >= 0) { + tnl->module_index = m->index; + pa_hashmap_put(data->u->tunnels, tnl, tnl); + pa_log_info("Module(%u) loaded, Tunnel(%p) added success", m->index, tnl); + tnl = NULL; + } else { + pa_log_error("Failed to load Module(%s)", module_name); + } + + pa_xfree(module_name); + pa_xfree(dname); + pa_xfree(args); + +finish: + + if (tnl) + tunnel_free(tnl); +} + + +static void dnssd_getaddrinfo_reply_cb(DNSServiceRef sd_ref, + unsigned int flags, unsigned int if_index, + DNSServiceErrorType error_code, const char *host_name, + const struct sockaddr *address, unsigned int ttl, + void *user_data) { + service_data_t *data = (service_data_t *)user_data; + + pa_assert(data); + pa_assert(data->u); + + if (flags & kDNSServiceFlagsMoreComing) { + pa_log_warn("More results are queued, No need to send callback to " + "application at this stage"); + return; + } + + if (data->interface != if_index) + pa_log_warn("diff interface %d <=> %d", data->interface, if_index); + if (!pa_safe_streq(data->host_name, host_name)) + pa_log_warn("diff host_name %s <=> %s", data->host_name, host_name); + + data->ip_address = get_ip_str(address); + + pa_log_info("-----------------------------------"); + pa_log_info("Address Reply : ref(%p), userdata(%p), error(%d), flags(0x%x)", + sd_ref, user_data, error_code, flags); + pa_log_info(" Interface Index : %d", if_index); + pa_log_info(" Host Name : %s", data->host_name); + pa_log_info(" IP Address : %s", data->ip_address); + pa_log_info(" TTL : %d", ttl); + + pa_log_info("-----------------------------------"); + + tunnel_add(data); + + /* cleanup addr */ + request_clear(data->u->module->core->mainloop, &data->addr); + + /* cleanup data */ + service_data_free(data); +} + +static void io_addr_event_callback(pa_mainloop_api *io, pa_io_event *e, + int fd, pa_io_event_flags_t events, + void *user_data) { + service_data_t *data = (service_data_t *)user_data; + DNSServiceErrorType err; + + pa_assert(io); + pa_assert(data); + pa_assert(data->u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log_warn("Lost connection fd : %d", fd); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + err = DNSServiceProcessResult(data->addr.sd_ref); + if (err == 0) + pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->addr.sd_ref, fd); + else + pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->addr.sd_ref, fd, err); + } + + return; + +fail: + request_clear(data->u->module->core->mainloop, &data->addr); +} + +static int dnssd_getaddrinfo(service_data_t *data) { + int ret; + + ret = DNSServiceGetAddrInfo(&(data->addr.sd_ref), data->flags, data->interface, + data->protocol, data->host_name, + dnssd_getaddrinfo_reply_cb, data); + if (ret != kDNSServiceErr_NoError) { + pa_log_error("DNSServiceGetAddrInfo() error(%d)", ret); + return -1; + } + + pa_log_info("DNSServiceGetAddrInfo(%p) success", data->addr.sd_ref); + + return request_set_io_callback(data->u->module->core->mainloop, &data->addr, io_addr_event_callback, data); +} + +static void dnssd_resolve_reply_cb(DNSServiceRef sd_ref, unsigned int flags, + unsigned int if_index, DNSServiceErrorType error_code, + const char *fullname, const char *host_name, + unsigned short port, unsigned short txt_len, + const unsigned char *txt_record, void *user_data) { + int ret; + service_data_t *data = (service_data_t *)user_data; + + pa_assert(data); + pa_assert(data->u); + + if (flags & kDNSServiceFlagsMoreComing) { + pa_log_warn("More results are queued"); + return; + } + + if (data->interface != if_index) + pa_log_warn("diff interface %d <=> %d", data->interface, if_index); + + data->flags = flags; + data->host_name = pa_xstrdup(host_name); + data->port = ntohs(port); + + pa_log_info("-----------------------------------"); + pa_log_info("Resolve Reply : ref(%p), userdata(%p), error(%d), flags(0x%x)", + sd_ref, user_data, error_code, flags); + pa_log_info(" Interface Index : %d", if_index); + pa_log_info(" Full Name : %s", fullname); + pa_log_info(" Host Name : %s", data->host_name); + pa_log_info(" Port : %d", data->port); + pa_log_info(" Text Record : [%p][%d]", txt_record, txt_len); + pa_log_info("-----------------------------------"); + + parse_txt_record(data, txt_record, txt_len); + + /* cleanup resolve */ + request_clear(data->u->module->core->mainloop, &data->resolve); + + /* Get Address Info details and send browse callback */ + ret = dnssd_getaddrinfo(data); + if (ret < 0) { + pa_log_error("failed to get address info!"); + service_data_free(data); + } +} + + +static void io_resolve_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, + pa_io_event_flags_t events, void *user_data) { + service_data_t *data = (service_data_t *)user_data; + DNSServiceErrorType err; + + pa_assert(io); + pa_assert(data); + pa_assert(data->u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log_warn("Lost connection fd : %d", fd); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + err = DNSServiceProcessResult(data->resolve.sd_ref); + if (err == 0) + pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->resolve.sd_ref, fd); + else + pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->resolve.sd_ref, fd, err); + } + + return; + +fail: + request_clear(data->u->module->core->mainloop, &data->resolve); +} + + +static int dnssd_resolve_dns_service(struct userdata *u, + unsigned int flags, unsigned int if_index, int protocol, + const char *service_name, const char *service_type, + const char *domain) { + int ret; + + service_data_t *data = service_data_new(); + data->u = u; + data->flags = flags; + data->interface = if_index; + data->protocol = protocol; + data->service_name = pa_xstrdup(service_name); + data->service_type = pa_xstrdup(service_type); + data->domain = pa_xstrdup(domain); + + ret = DNSServiceResolve(&(data->resolve.sd_ref), + flags, if_index, + service_name, service_type, + domain, + dnssd_resolve_reply_cb, + data); + if (ret != kDNSServiceErr_NoError) { + pa_log_error("DNSServiceResolve() error(%d)", ret); + goto fail; + } + + pa_log_info("DNSServiceResolve(%p) success", data->resolve.sd_ref); + + ret = request_set_io_callback(u->module->core->mainloop, &data->resolve, io_resolve_event_callback, data); + if (ret < 0) + goto fail; + + return 0; + +fail: + service_data_free(data); + return -1; +} + +static void dnssd_browse_reply_cb(DNSServiceRef sd_ref, unsigned int flags, + unsigned int if_index, DNSServiceErrorType error_code, + const char *service_name, const char *service_type, + const char *domain, void *user_data) { + struct userdata *u = user_data; + struct tunnel *t; + int protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6; + + pa_log_info("-----------------------------------"); + pa_log_info("Browse Reply : ref(%p), userdata(%p), error(%d), flags(0x%x)", + sd_ref, user_data, error_code, flags); + pa_log_info(" Interface Index : %d", if_index); + pa_log_info(" Service Name : %s", service_name); + pa_log_info(" Service Type : %s", service_type); + pa_log_info(" Domain : %s", domain); + pa_log_info("-----------------------------------"); + + t = tunnel_new(if_index, protocol, service_name, service_type, domain); + + if (flags & kDNSServiceFlagsAdd) { + if (!pa_hashmap_get(u->tunnels, t)) + dnssd_resolve_dns_service(u, flags, if_index, protocol, + service_name, service_type, domain); + else + pa_log_warn("already exists %p", t); + } else { + struct tunnel *t2; + + if ((t2 = pa_hashmap_get(u->tunnels, t))) { + pa_module_unload_request_by_index(u->core, t2->module_index, true); + pa_hashmap_remove(u->tunnels, t2); + pa_log_info("Module(%u) Unloaded, Tunnel(%p) removed success", t2->module_index, t2); + tunnel_free(t2); + } else { + pa_log_warn("Not found %p", t); + } + } +} + +static void io_browse_handle_event(pa_mainloop_api *io, pa_io_event *e, int fd, + pa_io_event_flags_t events, struct userdata *u, dns_request_data_t *request) { + DNSServiceErrorType err; + + pa_assert(io); + pa_assert(u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log_warn("Lost connection fd : %d", fd); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + err = DNSServiceProcessResult(request->sd_ref); + if (err == 0) + pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", request->sd_ref, fd); + else + pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", request->sd_ref, fd, err); + } + + return; + +fail: + request_clear(u->module->core->mainloop, request); + pa_module_unload_request(u->module, true); +} + +static void io_browse_sink_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, + pa_io_event_flags_t events, void *user_data) { + struct userdata *u = user_data; + + io_browse_handle_event(io, e, fd, events, u, &u->browse_sink); +} + +static void io_browse_source_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, + pa_io_event_flags_t events, void *user_data) { + struct userdata *u = user_data; + + io_browse_handle_event(io, e, fd, events, u, &u->browse_source); +} + +static int dnssd_browse_dns_service(struct userdata *u, const char *service_type) { + int ret; + dns_request_data_t *request; + pa_io_event_cb_t cb; + + if (pa_safe_streq(service_type, SERVICE_TYPE_SINK)) { + request = &u->browse_sink; + cb = io_browse_sink_event_callback; + } else if (pa_safe_streq(service_type, SERVICE_TYPE_SOURCE)) { + request = &u->browse_source; + cb = io_browse_source_event_callback; + } else { + pa_log_error("invalid service type %s", pa_strnull(service_type)); + return -1; + } + + ret = DNSServiceBrowse(&request->sd_ref, 0, + kDNSServiceInterfaceIndexAny, service_type, + NULL, dnssd_browse_reply_cb, + u); + if (ret != kDNSServiceErr_NoError) { + pa_log_error("DNSServiceBrowse() with %s error(%d)", service_type, ret); + return -1; + } + + pa_log_info("DNSServiceBrowse(%p) success", request->sd_ref); + + ret = request_set_io_callback(u->module->core->mainloop, request, cb, u); + if (ret < 0) { + request_clear(u->module->core->mainloop, request); + return -1; + } + + return 0; +} + +void pa__done(pa_module *m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->tunnels) { + struct tunnel *t; + + while ((t = pa_hashmap_steal_first(u->tunnels))) { + pa_module_unload_request_by_index(u->core, t->module_index, true); + tunnel_free(t); + } + + pa_hashmap_free(u->tunnels); + } + + request_clear(u->module->core->mainloop, &u->browse_sink); + request_clear(u->module->core->mainloop, &u->browse_source); + + pa_xfree(u); +} + +int pa__init(pa_module *m) { + + struct userdata *u; + pa_modargs *ma = NULL; + int ret; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); + + ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SINK); + if (ret < 0) + goto fail; + + ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SOURCE); + if (ret < 0) + goto fail; + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} diff --git a/src/module-tizenaudio-publish.c b/src/module-tizenaudio-publish.c new file mode 100644 index 0000000..480b98f --- /dev/null +++ b/src/module-tizenaudio-publish.c @@ -0,0 +1,513 @@ +/*** + This file is part of PulseAudio. + + Copyright 2020 Seungbae Shin + based on module-bonjour-publish.c + + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PA_MODULE_AUTHOR("Seungbae Shin"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_pulse-source._tcp" +#define SERVICE_TYPE_SERVER "_pulse-server._tcp" + +static const char* const valid_modargs[] = { + NULL +}; + +enum service_subtype { + SUBTYPE_HARDWARE, + SUBTYPE_VIRTUAL, + SUBTYPE_MONITOR +}; + +struct service { + struct userdata *userdata; + DNSServiceRef service; + DNSRecordRef rec, rec2; + char *service_name; + pa_object *device; + enum service_subtype subtype; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hashmap *services; + char *service_name; + + pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; + + pa_native_protocol *native; + DNSServiceRef main_service; +}; + +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) { + pa_assert(s); + pa_assert(ret_ss); + pa_assert(ret_proplist); + pa_assert(ret_subtype); + + if (pa_sink_isinstance(s->device)) { + pa_sink *sink = PA_SINK(s->device); + + *ret_ss = sink->sample_spec; + *ret_map = sink->channel_map; + *ret_name = sink->name; + *ret_proplist = sink->proplist; + *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; + + } else if (pa_source_isinstance(s->device)) { + pa_source *source = PA_SOURCE(s->device); + + *ret_ss = source->sample_spec; + *ret_map = source->channel_map; + *ret_name = source->name; + *ret_proplist = source->proplist; + *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); + + } else { + pa_assert_not_reached(); + } +} + +static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) { + char s[128]; + char *t; + + pa_assert(c); + + TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION); + + t = pa_get_user_name_malloc(); + TXTRecordSetValue(txt, "user-name", strlen(t), t); + pa_xfree(t); + + t = pa_machine_id(); + TXTRecordSetValue(txt, "machine-id", strlen(t), t); + pa_xfree(t); + + t = pa_uname_string(); + TXTRecordSetValue(txt, "uname", strlen(t), t); + pa_xfree(t); + + t = pa_get_fqdn(s, sizeof(s)); + TXTRecordSetValue(txt, "fqdn", strlen(t), t); + + snprintf(s, sizeof(s), "0x%08x", c->cookie); + TXTRecordSetValue(txt, "cookie", strlen(s), s); +} + +static void service_free(struct service *s); + +static void dns_service_register_reply(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context) { + struct service *s = context; + + pa_assert(s); + + switch (errorCode) { + case kDNSServiceErr_NameConflict: + pa_log("DNS service reported kDNSServiceErr_NameConflict\n"); + service_free(s); + break; + + case kDNSServiceErr_NoError: + /* fall through */ + default: + break; + } +} + +static uint16_t compute_port(struct userdata *u) { + pa_strlist *i; + + pa_assert(u); + + for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) { + pa_parsed_address a; + + if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && + (a.type == PA_PARSED_ADDRESS_TCP4 || + a.type == PA_PARSED_ADDRESS_TCP6 || + a.type == PA_PARSED_ADDRESS_TCP_AUTO) && + a.port > 0) { + + pa_xfree(a.path_or_host); + return htons(a.port); + } + + pa_xfree(a.path_or_host); + } + + return htons(PA_NATIVE_DEFAULT_PORT); +} + +static int publish_service(struct service *s) { + int r = -1; + TXTRecordRef txt; + DNSServiceErrorType err; + const char *name = NULL, *t; + pa_proplist *proplist = NULL; + pa_sample_spec ss; + pa_channel_map map; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64]; + enum service_subtype subtype; + + const char * const subtype_text[] = { + [SUBTYPE_HARDWARE] = "hardware", + [SUBTYPE_VIRTUAL] = "virtual", + [SUBTYPE_MONITOR] = "monitor" + }; + + pa_assert(s); + + if (s->service) { + DNSServiceRefDeallocate(s->service); + s->service = NULL; + } + + TXTRecordCreate(&txt, 0, NULL); + + txt_record_server_data(s->userdata->core, &txt); + + get_service_data(s, &ss, &map, &name, &proplist, &subtype); + TXTRecordSetValue(&txt, "device", strlen(name), name); + + snprintf(tmp, sizeof(tmp), "%u", ss.rate); + TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp); + + snprintf(tmp, sizeof(tmp), "%u", ss.channels); + TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp); + + t = pa_sample_format_to_string(ss.format); + TXTRecordSetValue(&txt, "format", strlen(t), t); + + t = pa_channel_map_snprint(cm, sizeof(cm), &map); + TXTRecordSetValue(&txt, "channel_map", strlen(t), t); + + t = subtype_text[subtype]; + TXTRecordSetValue(&txt, "subtype", strlen(t), t); + + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION))) + TXTRecordSetValue(&txt, "description", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME))) + TXTRecordSetValue(&txt, "icon-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME))) + TXTRecordSetValue(&txt, "vendor-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME))) + TXTRecordSetValue(&txt, "product-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS))) + TXTRecordSetValue(&txt, "class", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR))) + TXTRecordSetValue(&txt, "form-factor", strlen(t), t); + + err = DNSServiceRegister(&s->service, + 0, /* flags */ + kDNSServiceInterfaceIndexAny, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, /* domain */ + NULL, /* host */ + compute_port(s->userdata), + TXTRecordGetLength(&txt), + TXTRecordGetBytesPtr(&txt), + dns_service_register_reply, s); + + if (err != kDNSServiceErr_NoError) { + pa_log("DNSServiceRegister() returned err %d", err); + goto finish; + } + + pa_log_debug("Successfully registered mDNS services for >%s<.", s->service_name); + return 0; + +finish: + + /* Remove this service */ + if (r < 0) + service_free(s); + + TXTRecordDeallocate(&txt); + + return r; +} + +static struct service *get_service(struct userdata *u, pa_object *device) { + struct service *s; + char *hn, *un; + const char *n; + + pa_assert(u); + pa_object_assert_ref(device); + + if ((s = pa_hashmap_get(u->services, device))) + return s; + + s = pa_xnew0(struct service, 1); + s->userdata = u; + s->device = device; + + if (pa_sink_isinstance(device)) { + if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SINK(device)->name; + } else { + if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SOURCE(device)->name; + } + + hn = pa_get_host_name_malloc(); + un = pa_get_user_name_malloc(); + + s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1); + + pa_xfree(un); + pa_xfree(hn); + + pa_hashmap_put(u->services, s->device, s); + + return s; +} + +static void service_free(struct service *s) { + pa_assert(s); + + pa_hashmap_remove(s->userdata->services, s->device); + + if (s->service) + DNSServiceRefDeallocate(s->service); + + pa_xfree(s->service_name); + pa_xfree(s); +} + +static bool shall_ignore(pa_object *o) { + pa_object_assert_ref(o); + + if (pa_sink_isinstance(o)) + return !!(PA_SINK(o)->flags & PA_SINK_NETWORK); + + if (pa_source_isinstance(o)) + return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK); + + pa_assert_not_reached(); +} + +static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) { + pa_assert(c); + pa_object_assert_ref(o); + + if (!shall_ignore(o)) + publish_service(get_service(u, o)); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct service *s; + + pa_assert(c); + pa_object_assert_ref(o); + + if ((s = pa_hashmap_get(u->services, o))) + service_free(s); + + return PA_HOOK_OK; +} + +static int publish_main_service(struct userdata *u) { + DNSServiceErrorType err; + TXTRecordRef txt; + + pa_assert(u); + + if (u->main_service) { + DNSServiceRefDeallocate(u->main_service); + u->main_service = NULL; + } + + TXTRecordCreate(&txt, 0, NULL); + txt_record_server_data(u->core, &txt); + + err = DNSServiceRegister(&u->main_service, + 0, /* flags */ + kDNSServiceInterfaceIndexAny, + u->service_name, + SERVICE_TYPE_SERVER, + NULL, /* domain */ + NULL, /* host */ + compute_port(u), + TXTRecordGetLength(&txt), + TXTRecordGetBytesPtr(&txt), + NULL, NULL); + + if (err != kDNSServiceErr_NoError) { + pa_log("DNSServiceRegister() returned err %d", err); + return err; + } + + TXTRecordDeallocate(&txt); + + return 0; +} + +static int publish_all_services(struct userdata *u) { + pa_sink *sink; + pa_source *source; + uint32_t idx; + + pa_assert(u); + + pa_log_debug("Publishing services in mDNS"); + + for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx))) + if (!shall_ignore(PA_OBJECT(sink))) + publish_service(get_service(u, PA_OBJECT(sink))); + + for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx))) + if (!shall_ignore(PA_OBJECT(source))) + publish_service(get_service(u, PA_OBJECT(source))); + + return publish_main_service(u); +} + +static void unpublish_all_services(struct userdata *u) { + void *state = NULL; + struct service *s; + + pa_assert(u); + + pa_log_debug("Unpublishing services in mDNS"); + + while ((s = pa_hashmap_iterate(u->services, &state, NULL))) + service_free(s); + + if (u->main_service) + DNSServiceRefDeallocate(u->main_service); +} + +void pa__done(pa_module *m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + unpublish_all_services(u); + + if (u->services) + pa_hashmap_free(u->services); + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->sink_changed_slot) + pa_hook_slot_free(u->sink_changed_slot); + if (u->source_changed_slot) + pa_hook_slot_free(u->source_changed_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->native) + pa_native_protocol_unref(u->native); + + pa_xfree(u->service_name); + pa_xfree(u); +} + +int pa__init(pa_module *m) { + struct userdata *u; + pa_modargs *ma = NULL; + char *hn, *un; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->native = pa_native_protocol_get(u->core); + + u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); + + un = pa_get_user_name_malloc(); + hn = pa_get_host_name_malloc(); + u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1); + pa_xfree(un); + pa_xfree(hn); + + if (publish_all_services(u) != 0) + goto fail; + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +}