4 This file is part of polypaudio.
6 polypaudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 polypaudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with polypaudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
32 #include <polypcore/xmalloc.h>
33 #include <polypcore/autoload.h>
34 #include <polypcore/sink.h>
35 #include <polypcore/source.h>
36 #include <polypcore/native-common.h>
37 #include <polypcore/util.h>
38 #include <polypcore/log.h>
39 #include <polypcore/core-subscribe.h>
40 #include <polypcore/dynarray.h>
41 #include <polypcore/modargs.h>
43 #include "../polypcore/endianmacros.h"
45 #include "howl-wrap.h"
47 #include "module-zeroconf-publish-symdef.h"
49 PA_MODULE_AUTHOR("Lennart Poettering")
50 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
51 PA_MODULE_VERSION(PACKAGE_VERSION
)
52 PA_MODULE_USAGE("port=<IP port number>")
54 #define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
55 #define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
56 #define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
58 static const char* const valid_modargs
[] = {
66 int published
; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
70 pa_namereg_type_t type
;
76 pa_namereg_type_t type
;
83 pa_howl_wrapper
*howl_wrapper
;
85 pa_dynarray
*sink_dynarray
, *source_dynarray
, *autoload_dynarray
;
86 pa_subscription
*subscription
;
89 sw_discovery_oid server_oid
;
92 static sw_result
publish_reply(sw_discovery discovery
, sw_discovery_publish_status status
, sw_discovery_oid oid
, sw_opaque extra
) {
96 static void get_service_data(struct userdata
*u
, struct service
*s
, pa_sample_spec
*ret_ss
, char **ret_description
) {
97 assert(u
&& s
&& s
->loaded
.valid
&& ret_ss
&& ret_description
);
99 if (s
->loaded
.type
== PA_NAMEREG_SINK
) {
100 pa_sink
*sink
= pa_idxset_get_by_index(u
->core
->sinks
, s
->loaded
.index
);
102 *ret_ss
= sink
->sample_spec
;
103 *ret_description
= sink
->description
;
104 } else if (s
->loaded
.type
== PA_NAMEREG_SOURCE
) {
105 pa_source
*source
= pa_idxset_get_by_index(u
->core
->sources
, s
->loaded
.index
);
107 *ret_ss
= source
->sample_spec
;
108 *ret_description
= source
->description
;
113 static void txt_record_server_data(pa_core
*c
, sw_text_record t
) {
117 sw_text_record_add_key_and_string_value(t
, "server-version", PACKAGE_NAME
" "PACKAGE_VERSION
);
118 sw_text_record_add_key_and_string_value(t
, "user-name", pa_get_user_name(s
, sizeof(s
)));
119 sw_text_record_add_key_and_string_value(t
, "fqdn", pa_get_fqdn(s
, sizeof(s
)));
120 snprintf(s
, sizeof(s
), "0x%08x", c
->cookie
);
121 sw_text_record_add_key_and_string_value(t
, "cookie", s
);
124 static int publish_service(struct userdata
*u
, struct service
*s
) {
132 if ((s
->published
== 1 && s
->loaded
.valid
) ||
133 (s
->published
== 2 && s
->autoload
.valid
&& !s
->loaded
.valid
))
137 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
141 snprintf(t
, sizeof(t
), "Networked Audio Device %s on %s", s
->name
, pa_get_host_name(hn
, sizeof(hn
)));
143 if (sw_text_record_init(&txt
) != SW_OKAY
) {
144 pa_log(__FILE__
": sw_text_record_init() failed");
149 sw_text_record_add_key_and_string_value(txt
, "device", s
->name
);
151 txt_record_server_data(u
->core
, txt
);
153 if (s
->loaded
.valid
) {
154 char z
[64], *description
;
157 get_service_data(u
, s
, &ss
, &description
);
159 snprintf(z
, sizeof(z
), "%u", ss
.rate
);
160 sw_text_record_add_key_and_string_value(txt
, "rate", z
);
161 snprintf(z
, sizeof(z
), "%u", ss
.channels
);
162 sw_text_record_add_key_and_string_value(txt
, "channels", z
);
163 sw_text_record_add_key_and_string_value(txt
, "format", pa_sample_format_to_string(ss
.format
));
165 sw_text_record_add_key_and_string_value(txt
, "description", description
);
167 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
168 s
->loaded
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
169 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
170 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
171 pa_log(__FILE__
": failed to register sink on zeroconf.");
176 } else if (s
->autoload
.valid
) {
178 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
179 s
->autoload
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
180 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
181 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
182 pa_log(__FILE__
": failed to register sink on zeroconf.");
194 /* Remove this service */
195 pa_hashmap_remove(u
->services
, s
->name
);
201 sw_text_record_fina(txt
);
206 static struct service
*get_service(struct userdata
*u
, const char *name
) {
209 if ((s
= pa_hashmap_get(u
->services
, name
)))
212 s
= pa_xmalloc(sizeof(struct service
));
214 s
->name
= pa_xstrdup(name
);
215 s
->loaded
.valid
= s
->autoload
.valid
= 0;
217 pa_hashmap_put(u
->services
, s
->name
, s
);
222 static int publish_sink(struct userdata
*u
, pa_sink
*s
) {
226 svc
= get_service(u
, s
->name
);
227 if (svc
->loaded
.valid
)
230 svc
->loaded
.valid
= 1;
231 svc
->loaded
.type
= PA_NAMEREG_SINK
;
232 svc
->loaded
.index
= s
->index
;
234 pa_dynarray_put(u
->sink_dynarray
, s
->index
, svc
);
236 return publish_service(u
, svc
);
239 static int publish_source(struct userdata
*u
, pa_source
*s
) {
243 svc
= get_service(u
, s
->name
);
244 if (svc
->loaded
.valid
)
247 svc
->loaded
.valid
= 1;
248 svc
->loaded
.type
= PA_NAMEREG_SOURCE
;
249 svc
->loaded
.index
= s
->index
;
251 pa_dynarray_put(u
->source_dynarray
, s
->index
, svc
);
253 return publish_service(u
, svc
);
256 static int publish_autoload(struct userdata
*u
, pa_autoload_entry
*s
) {
260 svc
= get_service(u
, s
->name
);
261 if (svc
->autoload
.valid
)
264 svc
->autoload
.valid
= 1;
265 svc
->autoload
.type
= s
->type
;
266 svc
->autoload
.index
= s
->index
;
268 pa_dynarray_put(u
->autoload_dynarray
, s
->index
, svc
);
270 return publish_service(u
, svc
);
273 static int remove_sink(struct userdata
*u
, uint32_t idx
) {
275 assert(u
&& idx
!= PA_INVALID_INDEX
);
277 if (!(svc
= pa_dynarray_get(u
->sink_dynarray
, idx
)))
280 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SINK
)
283 svc
->loaded
.valid
= 0;
284 pa_dynarray_put(u
->sink_dynarray
, idx
, NULL
);
286 return publish_service(u
, svc
);
289 static int remove_source(struct userdata
*u
, uint32_t idx
) {
291 assert(u
&& idx
!= PA_INVALID_INDEX
);
293 if (!(svc
= pa_dynarray_get(u
->source_dynarray
, idx
)))
296 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SOURCE
)
299 svc
->loaded
.valid
= 0;
300 pa_dynarray_put(u
->source_dynarray
, idx
, NULL
);
302 return publish_service(u
, svc
);
305 static int remove_autoload(struct userdata
*u
, uint32_t idx
) {
307 assert(u
&& idx
!= PA_INVALID_INDEX
);
309 if (!(svc
= pa_dynarray_get(u
->autoload_dynarray
, idx
)))
312 if (!svc
->autoload
.valid
)
315 svc
->autoload
.valid
= 0;
316 pa_dynarray_put(u
->autoload_dynarray
, idx
, NULL
);
318 return publish_service(u
, svc
);
321 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
322 struct userdata
*u
= userdata
;
325 switch (t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
)
326 case PA_SUBSCRIPTION_EVENT_SINK
: {
327 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
330 if ((sink
= pa_idxset_get_by_index(c
->sinks
, idx
))) {
331 if (publish_sink(u
, sink
) < 0)
334 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
335 if (remove_sink(u
, idx
) < 0)
341 case PA_SUBSCRIPTION_EVENT_SOURCE
:
343 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
346 if ((source
= pa_idxset_get_by_index(c
->sources
, idx
))) {
347 if (publish_source(u
, source
) < 0)
350 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
351 if (remove_source(u
, idx
) < 0)
357 case PA_SUBSCRIPTION_EVENT_AUTOLOAD
:
358 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
359 pa_autoload_entry
*autoload
;
361 if ((autoload
= pa_idxset_get_by_index(c
->autoload_idxset
, idx
))) {
362 if (publish_autoload(u
, autoload
) < 0)
365 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
366 if (remove_autoload(u
, idx
) < 0)
376 if (u
->subscription
) {
377 pa_subscription_free(u
->subscription
);
378 u
->subscription
= NULL
;
382 int pa__init(pa_core
*c
, pa_module
*m
) {
384 uint32_t idx
, port
= PA_NATIVE_DEFAULT_PORT
;
387 pa_autoload_entry
*autoload
;
388 pa_modargs
*ma
= NULL
;
389 char t
[256], hn
[256];
393 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
394 pa_log(__FILE__
": failed to parse module arguments.");
398 if (pa_modargs_get_value_u32(ma
, "port", &port
) < 0 || port
== 0 || port
>= 0xFFFF) {
399 pa_log(__FILE__
": invalid port specified.");
403 m
->userdata
= u
= pa_xmalloc(sizeof(struct userdata
));
405 u
->port
= (uint16_t) port
;
407 if (!(u
->howl_wrapper
= pa_howl_wrapper_get(c
)))
410 u
->services
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
411 u
->sink_dynarray
= pa_dynarray_new();
412 u
->source_dynarray
= pa_dynarray_new();
413 u
->autoload_dynarray
= pa_dynarray_new();
415 u
->subscription
= pa_subscription_new(c
,
416 PA_SUBSCRIPTION_MASK_SINK
|
417 PA_SUBSCRIPTION_MASK_SOURCE
|
418 PA_SUBSCRIPTION_MASK_AUTOLOAD
, subscribe_callback
, u
);
420 for (sink
= pa_idxset_first(c
->sinks
, &idx
); sink
; sink
= pa_idxset_next(c
->sinks
, &idx
))
421 if (publish_sink(u
, sink
) < 0)
424 for (source
= pa_idxset_first(c
->sources
, &idx
); source
; source
= pa_idxset_next(c
->sources
, &idx
))
425 if (publish_source(u
, source
) < 0)
428 if (c
->autoload_idxset
)
429 for (autoload
= pa_idxset_first(c
->autoload_idxset
, &idx
); autoload
; autoload
= pa_idxset_next(c
->autoload_idxset
, &idx
))
430 if (publish_autoload(u
, autoload
) < 0)
433 snprintf(t
, sizeof(t
), "Networked Audio Server on %s", pa_get_host_name(hn
, sizeof(hn
)));
435 if (sw_text_record_init(&txt
) != SW_OKAY
) {
436 pa_log(__FILE__
": sw_text_record_init() failed");
441 txt_record_server_data(u
->core
, txt
);
443 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
445 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
446 publish_reply
, u
, &u
->server_oid
) != SW_OKAY
) {
447 pa_log(__FILE__
": failed to register server on zeroconf.");
451 sw_text_record_fina(txt
);
463 sw_text_record_fina(txt
);
468 static void service_free(void *p
, void *userdata
) {
469 struct service
*s
= p
;
470 struct userdata
*u
= userdata
;
472 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
477 void pa__done(pa_core
*c
, pa_module
*m
) {
481 if (!(u
= m
->userdata
))
485 pa_hashmap_free(u
->services
, service_free
, u
);
487 if (u
->sink_dynarray
)
488 pa_dynarray_free(u
->sink_dynarray
, NULL
, NULL
);
489 if (u
->source_dynarray
)
490 pa_dynarray_free(u
->source_dynarray
, NULL
, NULL
);
491 if (u
->autoload_dynarray
)
492 pa_dynarray_free(u
->autoload_dynarray
, NULL
, NULL
);
495 pa_subscription_free(u
->subscription
);
498 pa_howl_wrapper_unref(u
->howl_wrapper
);