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 <polyp/xmalloc.h>
33 #include <polyp/util.h>
35 #include <polypcore/autoload.h>
36 #include <polypcore/sink.h>
37 #include <polypcore/source.h>
38 #include <polypcore/native-common.h>
39 #include <polypcore/core-util.h>
40 #include <polypcore/log.h>
41 #include <polypcore/core-subscribe.h>
42 #include <polypcore/dynarray.h>
43 #include <polypcore/modargs.h>
45 #include "../polypcore/endianmacros.h"
47 #include "howl-wrap.h"
49 #include "module-zeroconf-publish-symdef.h"
51 PA_MODULE_AUTHOR("Lennart Poettering")
52 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
53 PA_MODULE_VERSION(PACKAGE_VERSION
)
54 PA_MODULE_USAGE("port=<IP port number>")
56 #define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
57 #define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
58 #define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
60 static const char* const valid_modargs
[] = {
68 int published
; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
72 pa_namereg_type_t type
;
78 pa_namereg_type_t type
;
85 pa_howl_wrapper
*howl_wrapper
;
87 pa_dynarray
*sink_dynarray
, *source_dynarray
, *autoload_dynarray
;
88 pa_subscription
*subscription
;
91 sw_discovery_oid server_oid
;
94 static sw_result
publish_reply(sw_discovery discovery
, sw_discovery_publish_status status
, sw_discovery_oid oid
, sw_opaque extra
) {
98 static void get_service_data(struct userdata
*u
, struct service
*s
, pa_sample_spec
*ret_ss
, char **ret_description
) {
99 assert(u
&& s
&& s
->loaded
.valid
&& ret_ss
&& ret_description
);
101 if (s
->loaded
.type
== PA_NAMEREG_SINK
) {
102 pa_sink
*sink
= pa_idxset_get_by_index(u
->core
->sinks
, s
->loaded
.index
);
104 *ret_ss
= sink
->sample_spec
;
105 *ret_description
= sink
->description
;
106 } else if (s
->loaded
.type
== PA_NAMEREG_SOURCE
) {
107 pa_source
*source
= pa_idxset_get_by_index(u
->core
->sources
, s
->loaded
.index
);
109 *ret_ss
= source
->sample_spec
;
110 *ret_description
= source
->description
;
115 static void txt_record_server_data(pa_core
*c
, sw_text_record t
) {
119 sw_text_record_add_key_and_string_value(t
, "server-version", PACKAGE_NAME
" "PACKAGE_VERSION
);
120 sw_text_record_add_key_and_string_value(t
, "user-name", pa_get_user_name(s
, sizeof(s
)));
121 sw_text_record_add_key_and_string_value(t
, "fqdn", pa_get_fqdn(s
, sizeof(s
)));
122 snprintf(s
, sizeof(s
), "0x%08x", c
->cookie
);
123 sw_text_record_add_key_and_string_value(t
, "cookie", s
);
126 static int publish_service(struct userdata
*u
, struct service
*s
) {
134 if ((s
->published
== 1 && s
->loaded
.valid
) ||
135 (s
->published
== 2 && s
->autoload
.valid
&& !s
->loaded
.valid
))
139 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
143 snprintf(t
, sizeof(t
), "Networked Audio Device %s on %s", s
->name
, pa_get_host_name(hn
, sizeof(hn
)));
145 if (sw_text_record_init(&txt
) != SW_OKAY
) {
146 pa_log(__FILE__
": sw_text_record_init() failed");
151 sw_text_record_add_key_and_string_value(txt
, "device", s
->name
);
153 txt_record_server_data(u
->core
, txt
);
155 if (s
->loaded
.valid
) {
156 char z
[64], *description
;
159 get_service_data(u
, s
, &ss
, &description
);
161 snprintf(z
, sizeof(z
), "%u", ss
.rate
);
162 sw_text_record_add_key_and_string_value(txt
, "rate", z
);
163 snprintf(z
, sizeof(z
), "%u", ss
.channels
);
164 sw_text_record_add_key_and_string_value(txt
, "channels", z
);
165 sw_text_record_add_key_and_string_value(txt
, "format", pa_sample_format_to_string(ss
.format
));
167 sw_text_record_add_key_and_string_value(txt
, "description", description
);
169 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
170 s
->loaded
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
171 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
172 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
173 pa_log(__FILE__
": failed to register sink on zeroconf.");
178 } else if (s
->autoload
.valid
) {
180 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
181 s
->autoload
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
182 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
183 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
184 pa_log(__FILE__
": failed to register sink on zeroconf.");
196 /* Remove this service */
197 pa_hashmap_remove(u
->services
, s
->name
);
203 sw_text_record_fina(txt
);
208 static struct service
*get_service(struct userdata
*u
, const char *name
) {
211 if ((s
= pa_hashmap_get(u
->services
, name
)))
214 s
= pa_xmalloc(sizeof(struct service
));
216 s
->name
= pa_xstrdup(name
);
217 s
->loaded
.valid
= s
->autoload
.valid
= 0;
219 pa_hashmap_put(u
->services
, s
->name
, s
);
224 static int publish_sink(struct userdata
*u
, pa_sink
*s
) {
228 svc
= get_service(u
, s
->name
);
229 if (svc
->loaded
.valid
)
232 svc
->loaded
.valid
= 1;
233 svc
->loaded
.type
= PA_NAMEREG_SINK
;
234 svc
->loaded
.index
= s
->index
;
236 pa_dynarray_put(u
->sink_dynarray
, s
->index
, svc
);
238 return publish_service(u
, svc
);
241 static int publish_source(struct userdata
*u
, pa_source
*s
) {
245 svc
= get_service(u
, s
->name
);
246 if (svc
->loaded
.valid
)
249 svc
->loaded
.valid
= 1;
250 svc
->loaded
.type
= PA_NAMEREG_SOURCE
;
251 svc
->loaded
.index
= s
->index
;
253 pa_dynarray_put(u
->source_dynarray
, s
->index
, svc
);
255 return publish_service(u
, svc
);
258 static int publish_autoload(struct userdata
*u
, pa_autoload_entry
*s
) {
262 svc
= get_service(u
, s
->name
);
263 if (svc
->autoload
.valid
)
266 svc
->autoload
.valid
= 1;
267 svc
->autoload
.type
= s
->type
;
268 svc
->autoload
.index
= s
->index
;
270 pa_dynarray_put(u
->autoload_dynarray
, s
->index
, svc
);
272 return publish_service(u
, svc
);
275 static int remove_sink(struct userdata
*u
, uint32_t idx
) {
277 assert(u
&& idx
!= PA_INVALID_INDEX
);
279 if (!(svc
= pa_dynarray_get(u
->sink_dynarray
, idx
)))
282 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SINK
)
285 svc
->loaded
.valid
= 0;
286 pa_dynarray_put(u
->sink_dynarray
, idx
, NULL
);
288 return publish_service(u
, svc
);
291 static int remove_source(struct userdata
*u
, uint32_t idx
) {
293 assert(u
&& idx
!= PA_INVALID_INDEX
);
295 if (!(svc
= pa_dynarray_get(u
->source_dynarray
, idx
)))
298 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SOURCE
)
301 svc
->loaded
.valid
= 0;
302 pa_dynarray_put(u
->source_dynarray
, idx
, NULL
);
304 return publish_service(u
, svc
);
307 static int remove_autoload(struct userdata
*u
, uint32_t idx
) {
309 assert(u
&& idx
!= PA_INVALID_INDEX
);
311 if (!(svc
= pa_dynarray_get(u
->autoload_dynarray
, idx
)))
314 if (!svc
->autoload
.valid
)
317 svc
->autoload
.valid
= 0;
318 pa_dynarray_put(u
->autoload_dynarray
, idx
, NULL
);
320 return publish_service(u
, svc
);
323 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
324 struct userdata
*u
= userdata
;
327 switch (t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
)
328 case PA_SUBSCRIPTION_EVENT_SINK
: {
329 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
332 if ((sink
= pa_idxset_get_by_index(c
->sinks
, idx
))) {
333 if (publish_sink(u
, sink
) < 0)
336 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
337 if (remove_sink(u
, idx
) < 0)
343 case PA_SUBSCRIPTION_EVENT_SOURCE
:
345 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
348 if ((source
= pa_idxset_get_by_index(c
->sources
, idx
))) {
349 if (publish_source(u
, source
) < 0)
352 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
353 if (remove_source(u
, idx
) < 0)
359 case PA_SUBSCRIPTION_EVENT_AUTOLOAD
:
360 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
361 pa_autoload_entry
*autoload
;
363 if ((autoload
= pa_idxset_get_by_index(c
->autoload_idxset
, idx
))) {
364 if (publish_autoload(u
, autoload
) < 0)
367 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
368 if (remove_autoload(u
, idx
) < 0)
378 if (u
->subscription
) {
379 pa_subscription_free(u
->subscription
);
380 u
->subscription
= NULL
;
384 int pa__init(pa_core
*c
, pa_module
*m
) {
386 uint32_t idx
, port
= PA_NATIVE_DEFAULT_PORT
;
389 pa_autoload_entry
*autoload
;
390 pa_modargs
*ma
= NULL
;
391 char t
[256], hn
[256];
395 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
396 pa_log(__FILE__
": failed to parse module arguments.");
400 if (pa_modargs_get_value_u32(ma
, "port", &port
) < 0 || port
== 0 || port
>= 0xFFFF) {
401 pa_log(__FILE__
": invalid port specified.");
405 m
->userdata
= u
= pa_xmalloc(sizeof(struct userdata
));
407 u
->port
= (uint16_t) port
;
409 if (!(u
->howl_wrapper
= pa_howl_wrapper_get(c
)))
412 u
->services
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
413 u
->sink_dynarray
= pa_dynarray_new();
414 u
->source_dynarray
= pa_dynarray_new();
415 u
->autoload_dynarray
= pa_dynarray_new();
417 u
->subscription
= pa_subscription_new(c
,
418 PA_SUBSCRIPTION_MASK_SINK
|
419 PA_SUBSCRIPTION_MASK_SOURCE
|
420 PA_SUBSCRIPTION_MASK_AUTOLOAD
, subscribe_callback
, u
);
422 for (sink
= pa_idxset_first(c
->sinks
, &idx
); sink
; sink
= pa_idxset_next(c
->sinks
, &idx
))
423 if (publish_sink(u
, sink
) < 0)
426 for (source
= pa_idxset_first(c
->sources
, &idx
); source
; source
= pa_idxset_next(c
->sources
, &idx
))
427 if (publish_source(u
, source
) < 0)
430 if (c
->autoload_idxset
)
431 for (autoload
= pa_idxset_first(c
->autoload_idxset
, &idx
); autoload
; autoload
= pa_idxset_next(c
->autoload_idxset
, &idx
))
432 if (publish_autoload(u
, autoload
) < 0)
435 snprintf(t
, sizeof(t
), "Networked Audio Server on %s", pa_get_host_name(hn
, sizeof(hn
)));
437 if (sw_text_record_init(&txt
) != SW_OKAY
) {
438 pa_log(__FILE__
": sw_text_record_init() failed");
443 txt_record_server_data(u
->core
, txt
);
445 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
447 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
448 publish_reply
, u
, &u
->server_oid
) != SW_OKAY
) {
449 pa_log(__FILE__
": failed to register server on zeroconf.");
453 sw_text_record_fina(txt
);
465 sw_text_record_fina(txt
);
470 static void service_free(void *p
, void *userdata
) {
471 struct service
*s
= p
;
472 struct userdata
*u
= userdata
;
474 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
479 void pa__done(pa_core
*c
, pa_module
*m
) {
483 if (!(u
= m
->userdata
))
487 pa_hashmap_free(u
->services
, service_free
, u
);
489 if (u
->sink_dynarray
)
490 pa_dynarray_free(u
->sink_dynarray
, NULL
, NULL
);
491 if (u
->source_dynarray
)
492 pa_dynarray_free(u
->source_dynarray
, NULL
, NULL
);
493 if (u
->autoload_dynarray
)
494 pa_dynarray_free(u
->autoload_dynarray
, NULL
, NULL
);
497 pa_subscription_free(u
->subscription
);
500 pa_howl_wrapper_unref(u
->howl_wrapper
);