diff options
| -rw-r--r-- | configure.ac | 2 | ||||
| -rw-r--r-- | polyp/alsa-util.c | 9 | ||||
| -rw-r--r-- | polyp/howl-wrap.c | 116 | ||||
| -rw-r--r-- | polyp/howl-wrap.h | 37 | ||||
| -rw-r--r-- | polyp/module-match.c | 2 | ||||
| -rw-r--r-- | polyp/module-zeroconf-publish.c | 415 | 
6 files changed, 574 insertions, 7 deletions
diff --git a/configure.ac b/configure.ac index 5a4a4646..a54f383f 100644 --- a/configure.ac +++ b/configure.ac @@ -166,7 +166,7 @@ fi  # LYNX documentation generation  AC_ARG_ENABLE(lynx, -        AS_HELP_STRING(--disable-lynx,Turn off lynx usage for documentation generation), +        AC_HELP_STRING(--disable-lynx,Turn off lynx usage for documentation generation),  [case "${enableval}" in    yes) lynx=yes ;;    no)  lynx=no ;; diff --git a/polyp/alsa-util.c b/polyp/alsa-util.c index b6b9ac11..2894c9e8 100644 --- a/polyp/alsa-util.c +++ b/polyp/alsa-util.c @@ -29,6 +29,7 @@  #include "alsa-util.h"  #include "sample.h"  #include "xmalloc.h" +#include "log.h"  /* Set the hardware parameters of the given ALSA device. Returns the   * selected fragment settings in *period and *period_size */ @@ -61,14 +62,12 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, const struct pa_sample_spec *ss      if (snd_pcm_prepare(pcm_handle) < 0)          goto finish; -    if (snd_pcm_hw_params_current(pcm_handle, hwparams) < 0) -        goto finish; -          if (snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size) < 0 ||          snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL) < 0)          goto finish; - -    assert(buffer_size > 0 && *period_size > 0); +     +    assert(buffer_size > 0); +    assert(*period_size > 0);      *periods = buffer_size / *period_size;      assert(*periods > 0); diff --git a/polyp/howl-wrap.c b/polyp/howl-wrap.c new file mode 100644 index 00000000..af050930 --- /dev/null +++ b/polyp/howl-wrap.c @@ -0,0 +1,116 @@ +/* $Id$ */ + +/*** +  This file is part of polypaudio. +  +  polypaudio 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 of the +  License, or (at your option) any later version. +  +  polypaudio 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 polypaudio; if not, write to the Free Software +  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +  USA. +***/ + +#include <assert.h> + +#include "howl-wrap.h" +#include "log.h" +#include "xmalloc.h" +#include "props.h" + +#define HOWL_PROPERTY "howl" + +struct pa_howl_wrapper { +    struct pa_core *core; +    int ref; + +    struct pa_io_event *io_event; +    sw_discovery discovery; + +}; + +static void howl_io_event(struct pa_mainloop_api*m, struct pa_io_event *e, int fd, enum pa_io_event_flags f, void *userdata) { +    struct pa_howl_wrapper *w = userdata; +    assert(m && e && fd >= 0 && w && w->ref >= 1); + +    if (f & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) +        goto fail; + +    if (sw_discovery_read_socket(w->discovery) != SW_OKAY) +        goto fail; + +    return; + +fail: +    pa_log(__FILE__": howl connection died.\n"); +    w->core->mainloop->io_free(w->io_event); +    w->io_event = NULL; +} + +static struct pa_howl_wrapper* howl_wrapper_new(struct pa_core *c) { +    struct pa_howl_wrapper *h; +    sw_discovery session; +    assert(c); + +    if (sw_discovery_init(&session) != SW_OKAY) { +        pa_log("sw_discovery_init() failed.\n"); +        return NULL; +    } + +    h = pa_xmalloc(sizeof(struct pa_howl_wrapper)); +    h->core = c; +    h->ref = 1; +    h->discovery = session; + +    h->io_event = c->mainloop->io_new(c->mainloop, sw_discovery_socket(session), PA_IO_EVENT_INPUT, howl_io_event, h); + +    return h; +} + +static void howl_wrapper_free(struct pa_howl_wrapper *h) { +    assert(h); + +    sw_discovery_fina(h->discovery); + +    if (h->io_event) +        h->core->mainloop->io_free(h->io_event); + +    pa_xfree(h); +} + +struct pa_howl_wrapper* pa_howl_wrapper_get(struct pa_core *c) { +    struct pa_howl_wrapper *h; +    assert(c); +     +    if ((h = pa_property_get(c, HOWL_PROPERTY))) +        return pa_howl_wrapper_ref(h); + +    return howl_wrapper_new(c); +} + +struct pa_howl_wrapper* pa_howl_wrapper_ref(struct pa_howl_wrapper *h) { +    assert(h && h->ref >= 1); +    h->ref++; +    return h; +} + +void pa_howl_wrapper_unref(struct pa_howl_wrapper *h) { +    assert(h && h->ref >= 1); +    if (!(--h->ref)) +        howl_wrapper_free(h); +} + +sw_discovery pa_howl_wrapper_get_discovery(struct pa_howl_wrapper *h) { +    assert(h && h->ref >= 1); + +    return h->discovery; +} + diff --git a/polyp/howl-wrap.h b/polyp/howl-wrap.h new file mode 100644 index 00000000..feb54556 --- /dev/null +++ b/polyp/howl-wrap.h @@ -0,0 +1,37 @@ +#ifndef foohowlwrapperhfoo +#define foohowlwrapperhfoo + +/* $Id$ */ + +/*** +  This file is part of polypaudio. +  +  polypaudio 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 of the +  License, or (at your option) any later version. +  +  polypaudio 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 polypaudio; if not, write to the Free Software +  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +  USA. +***/ + +#include <howl.h> + +#include "core.h" + +struct pa_howl_wrapper; + +struct pa_howl_wrapper* pa_howl_wrapper_get(struct pa_core *c); +struct pa_howl_wrapper* pa_howl_wrapper_ref(struct pa_howl_wrapper *h); +void pa_howl_wrapper_unref(struct pa_howl_wrapper *h); + +sw_discovery pa_howl_wrapper_get_discovery(struct pa_howl_wrapper *h); + +#endif diff --git a/polyp/module-match.c b/polyp/module-match.c index 7fdc5f3e..9d969b31 100644 --- a/polyp/module-match.c +++ b/polyp/module-match.c @@ -171,7 +171,7 @@ static void callback(struct pa_core *c, enum pa_subscription_event_type t, uint3      for (r = u->rules; r; r = r->next) {          if (!regexec(&r->regex, si->name, 0, NULL, 0)) { -            pa_log(__FILE__": changing volume of sink input '%s' to 0x%03x\n", si->name, r->volume); +            pa_log_debug(__FILE__": changing volume of sink input '%s' to 0x%03x\n", si->name, r->volume);              pa_sink_input_set_volume(si, r->volume);          }      } diff --git a/polyp/module-zeroconf-publish.c b/polyp/module-zeroconf-publish.c new file mode 100644 index 00000000..6eee143b --- /dev/null +++ b/polyp/module-zeroconf-publish.c @@ -0,0 +1,415 @@ +/* $Id$ */ + +/*** +  This file is part of polypaudio. +  +  polypaudio 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 of the +  License, or (at your option) any later version. +  +  polypaudio 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 polypaudio; 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 <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "module-zeroconf-publish-symdef.h" +#include "howl-wrap.h" +#include "xmalloc.h" +#include "autoload.h" +#include "sink.h" +#include "source.h" +#include "native-common.h" +#include "util.h" +#include "log.h" +#include "subscribe.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher") +PA_MODULE_VERSION(PACKAGE_VERSION) + +#define SERVICE_NAME_SINK "_polypaudio-sink._tcp" +#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp" +#define SERVICE_NAME_SERVER "_polypaudio-server._tcp" + +struct service { +    sw_discovery_oid oid; +    char *name; +    int published; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */ + +    struct { +        int valid; +        enum pa_namereg_type type; +        uint32_t index; +    } loaded; + +    struct { +        int valid; +        enum pa_namereg_type type; +        uint32_t index; +    } autoload; +}; + +struct userdata { +    struct pa_core *core; +    struct pa_howl_wrapper *howl_wrapper; +    struct pa_hashmap *services; +    struct pa_subscription *subscription; +}; + +static sw_result publish_reply(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) { +    return SW_OKAY; +} + +static void get_service_sample_spec(struct userdata *u, struct service *s, struct pa_sample_spec *ret_ss) { +    assert(u && s && s->loaded.valid && ret_ss); + +    if (s->loaded.type == PA_NAMEREG_SINK) { +        struct pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index); +        assert(sink); +        *ret_ss = sink->sample_spec; +    } else if (s->loaded.type == PA_NAMEREG_SOURCE) { +        struct pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index); +        assert(source); +        *ret_ss = source->sample_spec; +    } else +        assert(0); +} + +static int publish_service(struct userdata *u, struct service *s) { +    assert(u && s); +    char t[256]; +    char hn[256]; +    int r = -1; +    sw_text_record txt; +    int free_txt = 0; +        +    if ((s->published == 1 && s->loaded.valid) || +        (s->published == 2 && s->autoload.valid && !s->loaded.valid)) +        return 0; + +    if (s->published) { +        sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid); +        s->published = 0; +    } + +    snprintf(t, sizeof(t), "%s@%s", s->name, pa_get_host_name(hn, sizeof(hn)));    + +    if (sw_text_record_init(&txt) != SW_OKAY) { +        pa_log(__FILE__": sw_text_record_init() failed\n"); +        goto finish; +    } +    free_txt = 1; + +    sw_text_record_add_key_and_string_value(txt, "device", s->name); +     +    if (s->loaded.valid) { +        char z[64]; +        struct pa_sample_spec ss; + +        get_service_sample_spec(u, s, &ss); +             +        snprintf(z, sizeof(z), "%u", ss.rate); +        sw_text_record_add_key_and_string_value(txt, "rate", z); +        snprintf(z, sizeof(z), "%u", ss.channels); +        sw_text_record_add_key_and_string_value(txt, "channels", z); +        sw_text_record_add_key_and_string_value(txt, "format", pa_sample_format_to_string(ss.format)); +         +        if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t, +                                 s->loaded.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE, +                                 NULL, NULL, PA_NATIVE_DEFAULT_PORT, sw_text_record_bytes(txt), sw_text_record_len(txt), +                                 publish_reply, s, &s->oid) != SW_OKAY) { +            pa_log(__FILE__": failed to register sink on zeroconf.\n"); +            goto finish; +        } + +        s->published = 1; +    } else if (s->autoload.valid) { + +        if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t, +                                 s->autoload.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE, +                                 NULL, NULL, PA_NATIVE_DEFAULT_PORT, sw_text_record_bytes(txt), sw_text_record_len(txt), +                                 publish_reply, s, &s->oid) != SW_OKAY) { +            pa_log(__FILE__": failed to register sink on zeroconf.\n"); +            goto finish; +        } + +        s->published = 2; +    } + +    r = 0; +     +finish: + +    if (!s->published) { +        /* Remove this service */ +        pa_hashmap_remove(u->services, s->name); +        pa_xfree(s->name); +        pa_xfree(s); +    } + +    if (free_txt) +        sw_text_record_fina(txt); +     +    return r; +} + +struct service *get_service(struct userdata *u, const char *name) { +    struct service *s; +     +    if ((s = pa_hashmap_get(u->services, name))) +        return s; +     +    s = pa_xmalloc(sizeof(struct service)); +    s->published = 0; +    s->name = pa_xstrdup(name); +    s->loaded.valid = s->autoload.valid = 0; + +    pa_hashmap_put(u->services, s->name, s); + +    return s; +} + +static int publish_sink(struct userdata *u, struct pa_sink *s) { +    struct service *svc; +    assert(u && s); + +    svc = get_service(u, s->name); +    if (svc->loaded.valid) +        return 0; + +    svc->loaded.valid = 1; +    svc->loaded.type = PA_NAMEREG_SINK; +    svc->loaded.index = s->index; + +    return publish_service(u, svc); +} + +static int publish_source(struct userdata *u, struct pa_source *s) { +    struct service *svc; +    assert(u && s); + +    svc = get_service(u, s->name); +    if (svc->loaded.valid) +        return 0; + +    svc->loaded.valid = 1; +    svc->loaded.type = PA_NAMEREG_SOURCE; +    svc->loaded.index = s->index; +     +    return publish_service(u, svc); +} + +static int publish_autoload(struct userdata *u, struct pa_autoload_entry *s) { +    struct service *svc; +    assert(u && s); + +    svc = get_service(u, s->name); +    if (svc->autoload.valid) +        return 0; + +    svc->autoload.valid = 1; +    svc->autoload.type = s->type; +    svc->autoload.index = s->index; +     +    return publish_service(u, svc); +} + +static int remove_sink(struct userdata *u, struct pa_sink *s) { +    struct service *svc; +    assert(u && s); + +    if (!(svc = pa_hashmap_get(u->services, s->name))) +        return 0; + +    if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK) +        return 0; + +    svc->loaded.valid = 0; +    return publish_service(u, svc); +} + +static int remove_source(struct userdata *u, struct pa_source *s) { +    struct service *svc; +    assert(u && s); +     +    if (!(svc = pa_hashmap_get(u->services, s->name))) +        return 0; + +    if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE) +        return 0; + +    svc->loaded.valid = 0; +    return publish_service(u, svc); +} + +static int remove_autoload(struct userdata *u, struct pa_autoload_entry *s) { +    struct service *svc; +    assert(u && s); +     +    if (!(svc = pa_hashmap_get(u->services, s->name))) +        return 0; + +    if (!svc->autoload.valid || svc->autoload.type != s->type) +        return 0; + +    svc->autoload.valid = 0; +    return publish_service(u, svc); +} + +static void subscribe_callback(struct pa_core *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) { +    struct userdata *u = userdata; +    assert(u && c); + +    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { +        case PA_SUBSCRIPTION_EVENT_SINK: { +            struct pa_sink *sink; + +            pa_log("subscribe: %x\n", t); +     +     + +             +            if ((sink = pa_idxset_get_by_index(c->sinks, index))) { +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { +                    pa_log("add\n"); +                    if (publish_sink(u, sink) < 0) +                        goto fail; +                } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { +                    pa_log("remove\n"); + + +                    if (remove_sink(u, sink) < 0) +                        goto fail; +                } +            } +         +            break; +        } + +        case PA_SUBSCRIPTION_EVENT_SOURCE: { +            struct pa_source *source; + +            if ((source = pa_idxset_get_by_index(c->sources, index))) { +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { +                    if (publish_source(u, source) < 0) +                        goto fail; +                } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { +                    if (remove_source(u, source) < 0) +                        goto fail; +                } +            } +             +            break; +        } + +        case PA_SUBSCRIPTION_EVENT_AUTOLOAD: { +            struct pa_autoload_entry *autoload; +             +            if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, index))) { +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { +                    if (publish_autoload(u, autoload) < 0) +                        goto fail; +                } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { +                    if (remove_autoload(u, autoload) < 0) +                        goto fail; +                } +            } +             +            break; +        } +    } + +    return; + +fail: +    if (u->subscription) { +        pa_subscription_free(u->subscription); +        u->subscription = NULL; +    } +} + +int pa__init(struct pa_core *c, struct pa_module*m) { +    struct userdata *u; +    uint32_t index; +    struct pa_sink *sink; +    struct pa_source *source; +    struct pa_autoload_entry *autoload; + +    m->userdata = u = pa_xmalloc(sizeof(struct userdata)); +    u->core = c; + +    if (!(u->howl_wrapper = pa_howl_wrapper_get(c))) +        goto fail; + +    u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + +    u->subscription = pa_subscription_new(c, +                                          PA_SUBSCRIPTION_MASK_SINK| +                                          PA_SUBSCRIPTION_MASK_SOURCE| +                                          PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u); + +    for (sink = pa_idxset_first(c->sinks, &index); sink; sink = pa_idxset_next(c->sinks, &index)) +        if (publish_sink(u, sink) < 0) +            goto fail; + +    for (source = pa_idxset_first(c->sources, &index); source; source = pa_idxset_next(c->sources, &index)) +        if (publish_source(u, source) < 0) +            goto fail; + +    if (c->autoload_idxset) +        for (autoload = pa_idxset_first(c->autoload_idxset, &index); autoload; autoload = pa_idxset_next(c->autoload_idxset, &index)) +            if (publish_autoload(u, autoload) < 0) +                goto fail; + +    return 0; +     +fail: +    pa__done(c, m); +    return -1; +} + +static void service_free(void *p, void *userdata) { +    struct service *s = p; +    struct userdata *u = userdata; +    assert(s && u); +    sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid); +    pa_xfree(s->name); +    pa_xfree(s); +} + +void pa__done(struct pa_core *c, struct pa_module*m) { +    struct userdata*u; +    assert(c && m); + +    if (!(u = m->userdata)) +        return; + +    if (u->services) +        pa_hashmap_free(u->services, service_free, u); + +    if (u->subscription) +        pa_subscription_free(u->subscription); +     +    if (u->howl_wrapper) +        pa_howl_wrapper_unref(u->howl_wrapper); +     +    pa_xfree(u); +} +  | 
