diff options
| author | Colin Guthrie <pulse@colin.guthr.ie> | 2008-05-01 23:51:45 +0000 | 
|---|---|---|
| committer | Colin Guthrie <pulse@colin.guthr.ie> | 2008-10-08 20:32:06 +0100 | 
| commit | 6570620cc3717eb82acd19788538fda3786c7b99 (patch) | |
| tree | 12913ab57005194972dc70a57234c667fd1a9b68 | |
| parent | fef102e35ae4adff8ab000c628cb659c337af51d (diff) | |
Start the raop sink. It's based on pipe sink and isn't anywhere near finished. It does however compile.
git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2335 fefdeb5f-60dc-0310-8127-8f9354f1896f
| -rw-r--r-- | src/Makefile.am | 14 | ||||
| -rw-r--r-- | src/modules/module-raop-sink.c | 417 | 
2 files changed, 430 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index f2771980..831e4565 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1004,7 +1004,13 @@ libsocket_util_la_SOURCES = \  libsocket_util_la_LDFLAGS = -avoid-version  libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) libpulsecore.la -librtp_la_SOURCES = modules/rtp/rtp.c modules/rtp/rtp.h modules/rtp/sdp.c modules/rtp/sdp.h modules/rtp/sap.c modules/rtp/sap.h +librtp_la_SOURCES = \ +		modules/rtp/rtp.c modules/rtp/rtp.h \ +		modules/rtp/sdp.c modules/rtp/sdp.h \ +		modules/rtp/sap.c modules/rtp/sap.h \ +		modules/rtp/rtsp.c modules/rtp/rtsp.h \ +		modules/rtp/headerlist.c modules/rtp/headerlist.h \ +		modules/rtp/base64.c modules/rtp/base64.h  librtp_la_LDFLAGS = -avoid-version  librtp_la_LIBADD = $(AM_LIBADD) libpulsecore.la @@ -1053,6 +1059,7 @@ modlibexec_LTLIBRARIES += \  		module-remap-sink.la \  		module-ladspa-sink.la \  		module-esound-sink.la \ +		module-raop-sink.la \  		module-tunnel-sink.la \  		module-tunnel-source.la \  		module-position-event-sounds.la @@ -1199,6 +1206,7 @@ SYMDEF_FILES = \  		modules/module-esound-compat-spawnfd-symdef.h \  		modules/module-esound-compat-spawnpid-symdef.h \  		modules/module-match-symdef.h \ +		modules/module-raop-sink-symdef.h \  		modules/module-tunnel-sink-symdef.h \  		modules/module-tunnel-source-symdef.h \  		modules/module-null-sink-symdef.h \ @@ -1367,6 +1375,10 @@ module_match_la_SOURCES = modules/module-match.c  module_match_la_LDFLAGS = -module -avoid-version  module_match_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_raop_sink_la_SOURCES = modules/module-raop-sink.c +module_raop_sink_la_LDFLAGS = -module -avoid-version +module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la +  module_tunnel_sink_la_SOURCES = modules/module-tunnel.c  module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS)  module_tunnel_sink_la_LDFLAGS = -module -avoid-version diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c new file mode 100644 index 00000000..ad10d78f --- /dev/null +++ b/src/modules/module-raop-sink.c @@ -0,0 +1,417 @@ +/* $Id$ */ + +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 Lennart Poettering +  Copyright 2008      Colin Guthrie + +  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 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, 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 <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <sys/ioctl.h> +#include <poll.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/aes.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/random.h> +#include <pulsecore/rtpoll.h> + +#include "rtp.h" +#include "sdp.h" +#include "sap.h" +#include "rtsp.h" +#include "base64.h" + + +#include "module-raop-sink-symdef.h" + +#define JACK_STATUS_DISCONNECTED 0 +#define JACK_STATUS_CONNECTED 1 + +#define JACK_TYPE_ANALOG 0 +#define JACK_TYPE_DIGITAL 1 + +#define VOLUME_DEF -30 +#define VOLUME_MIN -144 +#define VOLUME_MAX 0 + + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airport)"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( +        "server=<address> " +        "sink_name=<name for the sink> " +        "format=<sample format> " +        "channels=<number of channels> " +        "rate=<sample rate>" +        "channel_map=<channel map>"); + +#define DEFAULT_SINK_NAME "airtunes" +#define AES_CHUNKSIZE 16 + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_sink *sink; + +    pa_thread *thread; +    pa_thread_mq thread_mq; +    pa_rtpoll *rtpoll; + +    char *server_name; + +    // Encryption Related bits +    AES_KEY aes; +    uint8_t aes_iv[AES_CHUNKSIZE]; // initialization vector for aes-cbc +    uint8_t aes_nv[AES_CHUNKSIZE]; // next vector for aes-cbc +    uint8_t aes_key[AES_CHUNKSIZE]; // key for aes-cbc + +    pa_rtsp_context *rtsp; +    //pa_socket_client *client; +    pa_memchunk memchunk; + +    pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { +    "server", +    "rate", +    "format", +    "channels", +    "sink_name", +    "channel_map", +    NULL +}; + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { +    char n[] = +        "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" +        "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" +        "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" +        "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" +        "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" +        "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; +    char e[] = "AQAB"; +    uint8_t modules[256]; +    uint8_t exponent[8]; +    int size; +    RSA *rsa; + +    rsa = RSA_new(); +    size = pa_base64_decode(n, modules); +    rsa->n = BN_bin2bn(modules, size, NULL); +    size = pa_base64_decode(e, exponent); +    rsa->e = BN_bin2bn(exponent, size, NULL); + +    size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); +    RSA_free(rsa); +    return size; +} + +static int aes_encrypt(struct userdata *u, uint8_t *data, int size) +{ +    uint8_t *buf; +    int i=0, j; + +    pa_assert(u); + +    memcpy(u->aes_nv, u->aes_iv, AES_CHUNKSIZE); +    while (i+AES_CHUNKSIZE <= size) { +        buf = data + i; +        for (j=0; j<AES_CHUNKSIZE; ++j) +            buf[j] ^= u->aes_nv[j]; + +        AES_encrypt(buf, buf, &u->aes); +        memcpy(u->aes_nv, buf, AES_CHUNKSIZE); +        i += AES_CHUNKSIZE; +    } +    return i; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { +    struct userdata *u = PA_SINK(o)->userdata; + +    switch (code) { + +        case PA_SINK_MESSAGE_GET_LATENCY: { +            size_t n = 0; +            //int l; + +#ifdef TIOCINQ +            /* +            if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) +                n = (size_t) l; +            */ +#endif + +            n += u->memchunk.length; + +            *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); +            break; +        } +    } + +    return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { +    struct userdata *u = userdata; +    //int write_type = 0; + +    pa_assert(u); + +    pa_log_debug("Thread starting up"); + +    pa_thread_mq_install(&u->thread_mq); +    pa_rtpoll_install(u->rtpoll); + +    for (;;) { +        struct pollfd *pollfd; +        int ret; + +        pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + +        /* Render some data and write it to the fifo */ +        if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { +            ssize_t l; +            void *p; + +            if (u->memchunk.length <= 0) +                pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + +            pa_assert(u->memchunk.length > 0); + +            p = pa_memblock_acquire(u->memchunk.memblock); +            //l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); +            // Fake the length of the "write". +            l = u->memchunk.length; +            pa_memblock_release(u->memchunk.memblock); + +            pa_assert(l != 0); + +            if (l < 0) { + +                if (errno == EINTR) +                    continue; +                else if (errno != EAGAIN) { +                    pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); +                    goto fail; +                } + +            } else { + +                u->memchunk.index += l; +                u->memchunk.length -= l; + +                if (u->memchunk.length <= 0) { +                    pa_memblock_unref(u->memchunk.memblock); +                    pa_memchunk_reset(&u->memchunk); +                } + +                pollfd->revents = 0; +            } +        } + +        /* Hmm, nothing to do. Let's sleep */ +        pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + +        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) +            goto fail; + +        if (ret == 0) +            goto finish; + +        pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + +        if (pollfd->revents & ~POLLOUT) { +            pa_log("FIFO shutdown."); +            goto fail; +        } +    } + +fail: +    /* If this was no regular exit from the loop we have to continue +     * processing messages until we received PA_MESSAGE_SHUTDOWN */ +    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: +    pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { +    struct userdata *u; +    //struct stat st; +    pa_sample_spec ss; +    pa_channel_map map; +    pa_modargs *ma; +    char *t; +    struct pollfd *pollfd; + +    pa_assert(m); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments."); +        goto fail; +    } + +    ss = m->core->default_sample_spec; +    if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { +        pa_log("Invalid sample format specification or channel map"); +        goto fail; +    } + +    u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    m->userdata = u; + +    // Initialise the AES encryption system +    pa_random_seed(); +    pa_random(u->aes_iv, sizeof(u->aes_iv)); +    pa_random(u->aes_key, sizeof(u->aes_key)); +    memcpy(u->aes_nv, u->aes_iv, sizeof(u->aes_nv)); +    AES_set_encrypt_key(u->aes_key, 128, &u->aes); + +    pa_memchunk_reset(&u->memchunk); +    pa_thread_mq_init(&u->thread_mq, m->core->mainloop); +    u->rtpoll = pa_rtpoll_new(); +    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + +    u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)); + +    // Open a connection to the server... this is just to connect and test.... +    /* +    mkfifo(u->filename, 0666); +    if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { +        pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); +        goto fail; +    } + +    pa_make_fd_cloexec(u->fd); +    pa_make_fd_nonblock(u->fd); + +    if (fstat(u->fd, &st) < 0) { +        pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); +        goto fail; +    } + +    if (!S_ISFIFO(st.st_mode)) { +        pa_log("'%s' is not a FIFO.", u->filename); +        goto fail; +    } +    */ +    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { +        pa_log("Failed to create sink."); +        goto fail; +    } + +    u->sink->parent.process_msg = sink_process_msg; +    u->sink->userdata = u; +    u->sink->flags = PA_SINK_LATENCY; + +    pa_sink_set_module(u->sink, m); +    pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); +    pa_sink_set_rtpoll(u->sink, u->rtpoll); +    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", u->server_name)); +    pa_xfree(t); + +    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); +    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); +    //pollfd->fd = u->fd; +    pollfd->events = pollfd->revents = 0; + +    if (!(u->thread = pa_thread_new(thread_func, u))) { +        pa_log("Failed to create thread."); +        goto fail; +    } + +    pa_sink_put(u->sink); + +    pa_modargs_free(ma); + +    return 0; + +fail: +    if (ma) +        pa_modargs_free(ma); + +    pa__done(m); + +    return -1; +} + +void pa__done(pa_module*m) { +    struct userdata *u; + +    pa_assert(m); + +    if (!(u = m->userdata)) +        return; + +    if (u->sink) +        pa_sink_unlink(u->sink); + +    if (u->thread) { +        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); +        pa_thread_free(u->thread); +    } + +    pa_thread_mq_done(&u->thread_mq); + +    if (u->sink) +        pa_sink_unref(u->sink); + +    if (u->memchunk.memblock) +       pa_memblock_unref(u->memchunk.memblock); + +    if (u->rtpoll_item) +        pa_rtpoll_item_free(u->rtpoll_item); + +    if (u->rtpoll) +        pa_rtpoll_free(u->rtpoll); + +    pa_xfree(u->server_name); +    pa_xfree(u); +}  | 
