Start the raop sink. It's based on pipe sink and isn't anywhere near finished. It...
authorColin Guthrie <pulse@colin.guthr.ie>
Thu, 1 May 2008 23:51:45 +0000 (23:51 +0000)
committerColin Guthrie <pulse@colin.guthr.ie>
Wed, 8 Oct 2008 19:32:06 +0000 (20:32 +0100)
git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2335 fefdeb5f-60dc-0310-8127-8f9354f1896f

src/Makefile.am
src/modules/module-raop-sink.c [new file with mode: 0644]

index f277198..831e456 100644 (file)
@@ -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 (file)
index 0000000..ad10d78
--- /dev/null
@@ -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);
+}