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>
34 #include <polypcore/autoload.h>
35 #include <polypcore/sink.h>
36 #include <polypcore/source.h>
37 #include <polypcore/native-common.h>
38 #include <polypcore/util.h>
39 #include <polypcore/log.h>
40 #include <polypcore/core-subscribe.h>
41 #include <polypcore/dynarray.h>
42 #include <polypcore/modargs.h>
44 #include "../polypcore/endianmacros.h"
46 #include "howl-wrap.h"
48 #include "module-zeroconf-publish-symdef.h"
50 PA_MODULE_AUTHOR("Lennart Poettering")
51 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
52 PA_MODULE_VERSION(PACKAGE_VERSION
)
53 PA_MODULE_USAGE("port=<IP port number>")
55 #define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
56 #define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
57 #define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
59 static const char* const valid_modargs
[] = {
67 int published
; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
71 pa_namereg_type_t type
;
77 pa_namereg_type_t type
;
84 pa_howl_wrapper
*howl_wrapper
;
86 pa_dynarray
*sink_dynarray
, *source_dynarray
, *autoload_dynarray
;
87 pa_subscription
*subscription
;
90 sw_discovery_oid server_oid
;
93 static sw_result
publish_reply(sw_discovery discovery
, sw_discovery_publish_status status
, sw_discovery_oid oid
, sw_opaque extra
) {
97 static void get_service_data(struct userdata
*u
, struct service
*s
, pa_sample_spec
*ret_ss
, char **ret_description
) {
98 assert(u
&& s
&& s
->loaded
.valid
&& ret_ss
&& ret_description
);
100 if (s
->loaded
.type
== PA_NAMEREG_SINK
) {
101 pa_sink
*sink
= pa_idxset_get_by_index(u
->core
->sinks
, s
->loaded
.index
);
103 *ret_ss
= sink
->sample_spec
;
104 *ret_description
= sink
->description
;
105 } else if (s
->loaded
.type
== PA_NAMEREG_SOURCE
) {
106 pa_source
*source
= pa_idxset_get_by_index(u
->core
->sources
, s
->loaded
.index
);
108 *ret_ss
= source
->sample_spec
;
109 *ret_description
= source
->description
;
114 static void txt_record_server_data(pa_core
*c
, sw_text_record t
) {
118 sw_text_record_add_key_and_string_value(t
, "server-version", PACKAGE_NAME
" "PACKAGE_VERSION
);
119 sw_text_record_add_key_and_string_value(t
, "user-name", pa_get_user_name(s
, sizeof(s
)));
120 sw_text_record_add_key_and_string_value(t
, "fqdn", pa_get_fqdn(s
, sizeof(s
)));
121 snprintf(s
, sizeof(s
), "0x%08x", c
->cookie
);
122 sw_text_record_add_key_and_string_value(t
, "cookie", s
);
125 static int publish_service(struct userdata
*u
, struct service
*s
) {
133 if ((s
->published
== 1 && s
->loaded
.valid
) ||
134 (s
->published
== 2 && s
->autoload
.valid
&& !s
->loaded
.valid
))
138 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
142 snprintf(t
, sizeof(t
), "Networked Audio Device %s on %s", s
->name
, pa_get_host_name(hn
, sizeof(hn
)));
144 if (sw_text_record_init(&txt
) != SW_OKAY
) {
145 pa_log(__FILE__
": sw_text_record_init() failed");
150 sw_text_record_add_key_and_string_value(txt
, "device", s
->name
);
152 txt_record_server_data(u
->core
, txt
);
154 if (s
->loaded
.valid
) {
155 char z
[64], *description
;
158 get_service_data(u
, s
, &ss
, &description
);
160 snprintf(z
, sizeof(z
), "%u", ss
.rate
);
161 sw_text_record_add_key_and_string_value(txt
, "rate", z
);
162 snprintf(z
, sizeof(z
), "%u", ss
.channels
);
163 sw_text_record_add_key_and_string_value(txt
, "channels", z
);
164 sw_text_record_add_key_and_string_value(txt
, "format", pa_sample_format_to_string(ss
.format
));
166 sw_text_record_add_key_and_string_value(txt
, "description", description
);
168 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
169 s
->loaded
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
170 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
171 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
172 pa_log(__FILE__
": failed to register sink on zeroconf.");
177 } else if (s
->autoload
.valid
) {
179 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
180 s
->autoload
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
181 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
182 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
183 pa_log(__FILE__
": failed to register sink on zeroconf.");
195 /* Remove this service */
196 pa_hashmap_remove(u
->services
, s
->name
);
202 sw_text_record_fina(txt
);
207 static struct service
*get_service(struct userdata
*u
, const char *name
) {
210 if ((s
= pa_hashmap_get(u
->services
, name
)))
213 s
= pa_xmalloc(sizeof(struct service
));
215 s
->name
= pa_xstrdup(name
);
216 s
->loaded
.valid
= s
->autoload
.valid
= 0;
218 pa_hashmap_put(u
->services
, s
->name
, s
);
223 static int publish_sink(struct userdata
*u
, pa_sink
*s
) {
227 svc
= get_service(u
, s
->name
);
228 if (svc
->loaded
.valid
)
231 svc
->loaded
.valid
= 1;
232 svc
->loaded
.type
= PA_NAMEREG_SINK
;
233 svc
->loaded
.index
= s
->index
;
235 pa_dynarray_put(u
->sink_dynarray
, s
->index
, svc
);
237 return publish_service(u
, svc
);
240 static int publish_source(struct userdata
*u
, pa_source
*s
) {
244 svc
= get_service(u
, s
->name
);
245 if (svc
->loaded
.valid
)
248 svc
->loaded
.valid
= 1;
249 svc
->loaded
.type
= PA_NAMEREG_SOURCE
;
250 svc
->loaded
.index
= s
->index
;
252 pa_dynarray_put(u
->source_dynarray
, s
->index
, svc
);
254 return publish_service(u
, svc
);
257 static int publish_autoload(struct userdata
*u
, pa_autoload_entry
*s
) {
261 svc
= get_service(u
, s
->name
);
262 if (svc
->autoload
.valid
)
265 svc
->autoload
.valid
= 1;
266 svc
->autoload
.type
= s
->type
;
267 svc
->autoload
.index
= s
->index
;
269 pa_dynarray_put(u
->autoload_dynarray
, s
->index
, svc
);
271 return publish_service(u
, svc
);
274 static int remove_sink(struct userdata
*u
, uint32_t idx
) {
276 assert(u
&& idx
!= PA_INVALID_INDEX
);
278 if (!(svc
= pa_dynarray_get(u
->sink_dynarray
, idx
)))
281 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SINK
)
284 svc
->loaded
.valid
= 0;
285 pa_dynarray_put(u
->sink_dynarray
, idx
, NULL
);
287 return publish_service(u
, svc
);
290 static int remove_source(struct userdata
*u
, uint32_t idx
) {
292 assert(u
&& idx
!= PA_INVALID_INDEX
);
294 if (!(svc
= pa_dynarray_get(u
->source_dynarray
, idx
)))
297 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SOURCE
)
300 svc
->loaded
.valid
= 0;
301 pa_dynarray_put(u
->source_dynarray
, idx
, NULL
);
303 return publish_service(u
, svc
);
306 static int remove_autoload(struct userdata
*u
, uint32_t idx
) {
308 assert(u
&& idx
!= PA_INVALID_INDEX
);
310 if (!(svc
= pa_dynarray_get(u
->autoload_dynarray
, idx
)))
313 if (!svc
->autoload
.valid
)
316 svc
->autoload
.valid
= 0;
317 pa_dynarray_put(u
->autoload_dynarray
, idx
, NULL
);
319 return publish_service(u
, svc
);
322 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
323 struct userdata
*u
= userdata
;
326 switch (t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
)
327 case PA_SUBSCRIPTION_EVENT_SINK
: {
328 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
331 if ((sink
= pa_idxset_get_by_index(c
->sinks
, idx
))) {
332 if (publish_sink(u
, sink
) < 0)
335 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
336 if (remove_sink(u
, idx
) < 0)
342 case PA_SUBSCRIPTION_EVENT_SOURCE
:
344 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
347 if ((source
= pa_idxset_get_by_index(c
->sources
, idx
))) {
348 if (publish_source(u
, source
) < 0)
351 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
352 if (remove_source(u
, idx
) < 0)
358 case PA_SUBSCRIPTION_EVENT_AUTOLOAD
:
359 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
360 pa_autoload_entry
*autoload
;
362 if ((autoload
= pa_idxset_get_by_index(c
->autoload_idxset
, idx
))) {
363 if (publish_autoload(u
, autoload
) < 0)
366 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
367 if (remove_autoload(u
, idx
) < 0)
377 if (u
->subscription
) {
378 pa_subscription_free(u
->subscription
);
379 u
->subscription
= NULL
;
383 int pa__init(pa_core
*c
, pa_module
*m
) {
385 uint32_t idx
, port
= PA_NATIVE_DEFAULT_PORT
;
388 pa_autoload_entry
*autoload
;
389 pa_modargs
*ma
= NULL
;
390 char t
[256], hn
[256];
394 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
395 pa_log(__FILE__
": failed to parse module arguments.");
399 if (pa_modargs_get_value_u32(ma
, "port", &port
) < 0 || port
== 0 || port
>= 0xFFFF) {
400 pa_log(__FILE__
": invalid port specified.");
404 m
->userdata
= u
= pa_xmalloc(sizeof(struct userdata
));
406 u
->port
= (uint16_t) port
;
408 if (!(u
->howl_wrapper
= pa_howl_wrapper_get(c
)))
411 u
->services
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
412 u
->sink_dynarray
= pa_dynarray_new();
413 u
->source_dynarray
= pa_dynarray_new();
414 u
->autoload_dynarray
= pa_dynarray_new();
416 u
->subscription
= pa_subscription_new(c
,
417 PA_SUBSCRIPTION_MASK_SINK
|
418 PA_SUBSCRIPTION_MASK_SOURCE
|
419 PA_SUBSCRIPTION_MASK_AUTOLOAD
, subscribe_callback
, u
);
421 for (sink
= pa_idxset_first(c
->sinks
, &idx
); sink
; sink
= pa_idxset_next(c
->sinks
, &idx
))
422 if (publish_sink(u
, sink
) < 0)
425 for (source
= pa_idxset_first(c
->sources
, &idx
); source
; source
= pa_idxset_next(c
->sources
, &idx
))
426 if (publish_source(u
, source
) < 0)
429 if (c
->autoload_idxset
)
430 for (autoload
= pa_idxset_first(c
->autoload_idxset
, &idx
); autoload
; autoload
= pa_idxset_next(c
->autoload_idxset
, &idx
))
431 if (publish_autoload(u
, autoload
) < 0)
434 snprintf(t
, sizeof(t
), "Networked Audio Server on %s", pa_get_host_name(hn
, sizeof(hn
)));
436 if (sw_text_record_init(&txt
) != SW_OKAY
) {
437 pa_log(__FILE__
": sw_text_record_init() failed");
442 txt_record_server_data(u
->core
, txt
);
444 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
446 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
447 publish_reply
, u
, &u
->server_oid
) != SW_OKAY
) {
448 pa_log(__FILE__
": failed to register server on zeroconf.");
452 sw_text_record_fina(txt
);
464 sw_text_record_fina(txt
);
469 static void service_free(void *p
, void *userdata
) {
470 struct service
*s
= p
;
471 struct userdata
*u
= userdata
;
473 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
478 void pa__done(pa_core
*c
, pa_module
*m
) {
482 if (!(u
= m
->userdata
))
486 pa_hashmap_free(u
->services
, service_free
, u
);
488 if (u
->sink_dynarray
)
489 pa_dynarray_free(u
->sink_dynarray
, NULL
, NULL
);
490 if (u
->source_dynarray
)
491 pa_dynarray_free(u
->source_dynarray
, NULL
, NULL
);
492 if (u
->autoload_dynarray
)
493 pa_dynarray_free(u
->autoload_dynarray
, NULL
, NULL
);
496 pa_subscription_free(u
->subscription
);
499 pa_howl_wrapper_unref(u
->howl_wrapper
);