[pulseaudio-commits] Branch 'next' - 30 commits - src/Makefile.am src/modules

Tanu Kaskinen tanuk at kemper.freedesktop.org
Fri Jan 13 01:41:15 UTC 2017


 src/Makefile.am                         |    8 
 src/modules/raop/base64.c               |  136 --
 src/modules/raop/base64.h               |   32 
 src/modules/raop/module-raop-discover.c |  107 +
 src/modules/raop/module-raop-sink.c     |  630 -----------
 src/modules/raop/raop-client.c          | 1768 ++++++++++++++++++++++++++++++++
 src/modules/raop/raop-client.h          |   83 +
 src/modules/raop/raop-crypto.c          |  164 ++
 src/modules/raop/raop-crypto.h          |   35 
 src/modules/raop/raop-packet-buffer.c   |  161 ++
 src/modules/raop/raop-packet-buffer.h   |   40 
 src/modules/raop/raop-sink.c            |  669 ++++++++++++
 src/modules/raop/raop-sink.h            |   33 
 src/modules/raop/raop-util.c            |  211 +++
 src/modules/raop/raop-util.h            |   39 
 src/modules/raop/raop_client.c          |  588 ----------
 src/modules/raop/raop_client.h          |   42 
 src/modules/rtp/rtsp_client.c           |   91 +
 src/modules/rtp/rtsp_client.h           |   30 
 19 files changed, 3418 insertions(+), 1449 deletions(-)

New commits:
commit 69d7fc8f23b4d26eb759bc070925dba189f506e1
Author: ced2c <cedric.hottier at gmail.com>
Date:   Sun Nov 6 12:54:27 2016 -0600

    raop: Fix #37: OOB access in rtsp_auth_cb
    
    Allocation for Apple-Challenge key is now defined to 16 bytes
    (instead of 16 bits)
    
    This patch fixes Issue #37
    https://github.com/hfujita/pulseaudio-raop2/issues/37

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index d695ce9..94342d2 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -86,6 +86,7 @@
 #define VOLUME_MIN -144.0
 
 #define UDP_DEFAULT_PKT_BUF_SIZE 1000
+#define APPLE_CHALLENGE_LENGTH 16
 
 struct pa_raop_client {
     pa_core *core;
@@ -1190,7 +1191,7 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
     switch (state) {
         case STATE_CONNECT: {
             char *sci = NULL, *sac = NULL;
-            uint16_t rac;
+            uint8_t rac[APPLE_CHALLENGE_LENGTH];
             struct {
                 uint32_t ci1;
                 uint32_t ci2;
@@ -1201,9 +1202,9 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
             sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2);
             pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
 
-            pa_random(&rac, sizeof(rac));
+            pa_random(rac, APPLE_CHALLENGE_LENGTH);
             /* Generate a random Apple-Challenge key */
-            pa_raop_base64_encode(&rac, 8 * sizeof(rac), &sac);
+            pa_raop_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
             rtrim_char(sac, '=');
             pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
 

commit 19e9f4bafbaf09ed485530d8eac10044431b9eaa
Author: ced2c <cedric.hottier at gmail.com>
Date:   Sun Nov 6 12:54:26 2016 -0600

    raop: Fix #36: invalid access to freed object
    
    The RTSP client is not waiting anymore a new header after the
    previous one (which can never occurs if RAOP is disconnected)
    but after sending a command.
    
    This patch fixes Issue #36.
    https://github.com/hfujita/pulseaudio-raop2/issues/36

diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c
index fd5d2d8..34210f9 100644
--- a/src/modules/rtp/rtsp_client.c
+++ b/src/modules/rtp/rtsp_client.c
@@ -236,7 +236,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
 
         pa_log_debug("Full response received. Dispatching");
         headers_read(c);
-        c->waiting = 1;
         goto exit;
     }
 
@@ -481,7 +480,8 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
     pa_log_debug(hdrs);*/
     pa_ioline_puts(c->ioline, hdrs);
     pa_xfree(hdrs);
-
+    /* The command is sent we can configure the rtsp client structure to handle a new answer */
+    c->waiting = 1;
     return 0;
 }
 

commit 1a7abe7b695ec8b68d222d3bc4a3371e9b9b95f7
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:25 2016 -0600

    raop: Fix memory leaks
    
    This patch fixes several memory leaks, and thereby fixes Issue #35.
    (https://github.com/hfujita/pulseaudio-raop2/issues/35)

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index f71ad9d..d695ce9 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -537,6 +537,7 @@ static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_
     return total;
 }
 
+/* Caller has to free the allocated memory region for packet */
 static size_t build_udp_sync_packet(pa_raop_client *c, uint32_t stamp, uint32_t **packet) {
     const size_t size = sizeof(udp_sync_header) + 12;
     const uint32_t delay = 88200;
@@ -570,8 +571,10 @@ static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) {
     size_t size = 0;
 
     size = build_udp_sync_packet(c, stamp, &packet);
-    if (packet != NULL && size > 0)
+    if (packet != NULL && size > 0) {
         written = pa_loop_write(c->udp_cfd, packet, size, NULL);
+        pa_xfree(packet);
+    }
 
     return written;
 }
@@ -606,6 +609,7 @@ static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[
     return written;
 }
 
+/* Caller has to free the allocated memory region for packet */
 static size_t build_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received, uint32_t **packet) {
     const size_t size = sizeof(udp_timing_header) + 24;
     uint32_t *buffer = NULL;
@@ -638,8 +642,10 @@ static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6],
     size_t size = 0;
 
     size = build_udp_timing_packet(c, data, received, &packet);
-    if (packet != NULL && size > 0)
+    if (packet != NULL && size > 0) {
         written = pa_loop_write(c->udp_tfd, packet, size, NULL);
+        pa_xfree(packet);
+    }
 
     return written;
 }
@@ -1366,7 +1372,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
 
     c = pa_xnew0(pa_raop_client, 1);
     c->core = core;
-    c->host = pa_xstrdup(a.path_or_host);
+    c->host = a.path_or_host; /* Will eventually be freed on destruction of c */
     if (a.port > 0)
         c->port = a.port;
     else

commit 75a0304c1c34021312ff5bfec62825961e30a053
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:24 2016 -0600

    raop: Discard data upon getting EAGAIN on a socket
    
    This patch discards audio data when a socket returns EAGAIN.
    This was made based on a suggestion by karlstav
    (https://github.com/karlstav), and is supposed to solve Issue #32.
    (https://github.com/hfujita/pulseaudio-raop2/issues/32)

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 68d188a..f71ad9d 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -471,8 +471,7 @@ static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size
         written = pa_write(c->udp_sfd, buffer, packet->length, NULL);
     if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
         pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
-        pa_memblock_release(packet->memblock);
-        return (ssize_t) packet->length;
+        written = packet->length;
     }
 
     pa_memblock_release(packet->memblock);

commit 89115521bf1fa0c9e0ddfd7b2ceb27aca4d5db77
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:23 2016 -0600

    raop: Disable is_recording flag when tearing down the connection
    
    This patch is based on a similar idea as the previous one -- disabling
    the flag right after the session is getting closed, rather than waiting
    for a response from the server.

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index bbef59d..68d188a 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -1120,8 +1120,6 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
         case STATE_TEARDOWN: {
             pa_log_debug("RAOP: TEARDOWN");
 
-            c->is_recording = false;
-
             if (c->tcp_sfd > 0)
                 pa_close(c->tcp_sfd);
             c->tcp_sfd = -1;
@@ -1631,6 +1629,8 @@ int pa_raop_client_teardown(pa_raop_client *c) {
         return 1;
     }
 
+    c->is_recording = false;
+
     rv = pa_rtsp_teardown(c->rtsp);
     return rv;
 }

commit 8075fbdfefa32317648d8246928fad4c9a0bdf6a
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:22 2016 -0600

    raop: Stop recording when RTSP FLUSH is issued
    
    This patch fixes the issue #31.
    https://github.com/hfujita/pulseaudio-raop2/issues/31
    
    This patch sets c->is_recording = false when the RTSP FLUSH command
    is issued. This avoids a race between the server response and
    the record activation in some cases.

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 52b2dee..bbef59d 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -1114,8 +1114,6 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
         case STATE_FLUSH: {
             pa_log_debug("RAOP: FLUSHED");
 
-            c->is_recording = false;
-
             break;
         }
 
@@ -1614,6 +1612,8 @@ int pa_raop_client_flush(pa_raop_client *c) {
         return 1;
     }
 
+    c->is_recording = false;
+
     rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
     return rv;
 }

commit 081a2a5c9d3cb8f83ff44c5473d1a8d74d4e00c1
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:21 2016 -0600

    raop: Add back initial volume RTSP SET_PARAMETER request on connect
    
    Regression introduced in commit 8c6407f:
    raop: Merge TCP and UDP code paths + refactoring
    
    Anyway, we need to determine if initial volume has to be setup before
    sending RECORD or after:
    
    - Setting it up *before* shouldn't be a problem: sink.c waits for
    CONNECT state, set the volume and client.c triggers RECORD only once
    he's got the SET_PARAMETER reply from server.
    
    - Setting it up *after* seems to be more difficult if we try not to
    send any audio before receiving the SET_PARAMETER reply form server. A
    solution may be to send SET_PARAMETER just after the RECORD server
    response is received and hope that it get processed by server during the
    2sec latency/buffering time...
    
    Attached patch implement that last solution. Works for me, but I cannot
    guaranty it will with your hardware...

diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index 2d8157e..6b707cf 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -95,6 +95,8 @@ enum {
 
 static void userdata_free(struct userdata *u);
 
+static void sink_set_volume_cb(pa_sink *s);
+
 static void raop_state_cb(pa_raop_state_t state, void *userdata) {
     struct userdata *u = userdata;
 
@@ -228,6 +230,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
                         /* Our stream has been suspended so we just flush it... */
                         pa_rtpoll_set_timer_disabled(u->rtpoll);
                         pa_raop_client_flush(u->raop);
+                    } else {
+                        /* Set the initial volume */
+                        sink_set_volume_cb(u->sink);
                     }
 
                     return 0;

commit 57b35e43773822a0ce3d158ba1ce0bb66328eca2
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:20 2016 -0600

    raop: Silently drop out-of-history retransmission request

diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
index 05f9592..72fd729 100644
--- a/src/modules/raop/raop-packet-buffer.c
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -142,13 +142,15 @@ pa_memchunk *pa_raop_packet_buffer_retrieve(pa_raop_packet_buffer *pb, uint16_t
         if (seq < pb->seq) {
             /* Regular case: pb->seq did not wrapped since seq. */
             delta = pb->seq - seq;
-            pa_assert(delta <= pb->count);
         } else {
             /* Tricky case: pb->seq wrapped since seq! */
             delta = pb->seq + (UINT16_MAX - seq);
-            pa_assert(delta <= pb->count);
         }
 
+        /* If the requested packet is too old, do nothing and return */
+        if (delta > pb->count)
+            return NULL;
+
         i = (pb->size + pb->pos - delta) % pb->size;
 
         if (delta < pb->size && pb->packets[i].memblock)

commit b032e30b145fdc8436a685ee4a63adc6e20002e0
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:19 2016 -0600

    raop: Correctly wrap RTP packet sequence number

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 574f10b..52b2dee 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -24,6 +24,7 @@
 #endif
 
 #include <stdlib.h>
+#include <stdint.h>
 #include <string.h>
 #include <errno.h>
 #include <unistd.h>
@@ -316,10 +317,10 @@ static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
     buffer += packet->index / sizeof(uint32_t);
     raw += block->index;
 
-    if (c->seq == 0xFFFF) {
-        pa_log_debug("wrapping sequence number");
-        c->seq = pa_raop_packet_buffer_wrap_seq(c->pbuf, c->seq);
-    } else
+    /* Wrap sequence number to 0 then UINT16_MAX is reached */
+    if (c->seq == UINT16_MAX)
+        c->seq = 0;
+    else
         c->seq++;
 
     memcpy(buffer, tcp_audio_header, sizeof(tcp_audio_header));
@@ -360,13 +361,13 @@ static ssize_t send_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, size
     ssize_t written = -1;
     size_t done = 0;
 
-    if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq, max)))
+    if (!(packet = pa_raop_packet_buffer_retrieve(c->pbuf, c->seq)))
         return -1;
 
     if (packet->length <= 0) {
         pa_assert(block->index == offset);
 
-        if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq + 1, max)))
+        if (!(packet = pa_raop_packet_buffer_prepare(c->pbuf, c->seq + 1, max)))
             return -1;
 
         packet->index = 0;
@@ -427,10 +428,10 @@ static size_t build_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
 
     c->rtptime += length / 4;
 
-    if (c->seq == 0xFFFF) {
-        pa_log_debug("wrapping sequence number");
-        c->seq = pa_raop_packet_buffer_wrap_seq(c->pbuf, c->seq);
-    } else
+    /* Wrap sequence number to 0 then UINT16_MAX is reached */
+    if (c->seq == UINT16_MAX)
+        c->seq = 0;
+    else
         c->seq++;
 
     pa_memblock_release(block->memblock);
@@ -453,11 +454,11 @@ static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size
     /* UDP packet has to be sent at once ! */
     pa_assert(block->index == offset);
 
-    if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq, max)))
+    if (!(packet = pa_raop_packet_buffer_prepare(c->pbuf, c->seq, max)))
         return -1;
 
-    packet->length = max;
     packet->index = sizeof(udp_audio_retrans_header);
+    packet->length = max - sizeof(udp_audio_retrans_header);
     if (!build_udp_audio_packet(c, block, packet))
         return -1;
 
@@ -508,7 +509,7 @@ static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_
         uint8_t *buffer = NULL;
         ssize_t written = -1;
 
-        if (!(packet = pa_raop_packet_buffer_get(c->pbuf, seq + i, 0)))
+        if (!(packet = pa_raop_packet_buffer_retrieve(c->pbuf, seq + i)))
             continue;
 
         if (packet->index > 0) {
diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
index 4c45d18..05f9592 100644
--- a/src/modules/raop/raop-packet-buffer.c
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -25,6 +25,7 @@
 #endif
 
 #include <stdlib.h>
+#include <stdint.h>
 #include <limits.h>
 
 #include <pulse/xmalloc.h>
@@ -37,7 +38,9 @@
 struct pa_raop_packet_buffer {
     pa_memchunk *packets;
     pa_mempool *mempool;
+
     size_t size;
+    size_t count;
 
     uint16_t seq;
     size_t pos;
@@ -49,6 +52,7 @@ pa_raop_packet_buffer *pa_raop_packet_buffer_new(pa_mempool *mempool, const size
     pa_assert(mempool);
     pa_assert(size > 0);
 
+    pb->count = 0;
     pb->size = size;
     pb->mempool = mempool;
     pb->packets = pa_xnew0(pa_memchunk, size);
@@ -80,7 +84,8 @@ void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq) {
     pa_assert(pb->packets);
 
     pb->pos = 0;
-    pb->seq = seq - 1;
+    pb->count = 0;
+    pb->seq = (!seq) ? UINT16_MAX : seq - 1;
     for (i = 0; i < pb->size; i++) {
         if (pb->packets[i].memblock)
             pa_memblock_unref(pb->packets[i].memblock);
@@ -88,23 +93,43 @@ void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq) {
     }
 }
 
-uint16_t pa_raop_packet_buffer_wrap_seq(pa_raop_packet_buffer *pb, uint16_t seq) {
-    int seq_shift;
+pa_memchunk *pa_raop_packet_buffer_prepare(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size) {
+    pa_memchunk *packet = NULL;
+    size_t i;
 
     pa_assert(pb);
+    pa_assert(pb->packets);
+
+    if (seq == 0) {
+        /* 0 means seq reached UINT16_MAX and has been wrapped... */
+        pa_assert(pb->seq == UINT16_MAX);
+        pb->seq = 0;
+    } else {
+        /* ...otherwise, seq MUST have be increased! */
+        pa_assert(seq == pb->seq + 1);
+        pb->seq++;
+    }
+
+    i = (pb->pos + 1) % pb->size;
 
-    if (seq > pb->seq)
-        seq_shift = pb->seq - 1;
-    else
-        seq_shift = seq;
+    if (pb->packets[i].memblock)
+        pa_memblock_unref(pb->packets[i].memblock);
+    pa_memchunk_reset(&pb->packets[i]);
 
-    pb->seq -= seq_shift;
+    pb->packets[i].memblock = pa_memblock_new(pb->mempool, size);
+    pb->packets[i].length = size;
+    pb->packets[i].index = 0;
 
-    return seq - seq_shift;
+    packet = &pb->packets[i];
 
+    if (pb->count < pb->size)
+        pb->count++;
+    pb->pos = i;
+
+    return packet;
 }
 
-pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size) {
+pa_memchunk *pa_raop_packet_buffer_retrieve(pa_raop_packet_buffer *pb, uint16_t seq) {
     pa_memchunk *packet = NULL;
     size_t delta, i;
 
@@ -113,20 +138,21 @@ pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq,
 
     if (seq == pb->seq)
         packet = &pb->packets[pb->pos];
-    else if (seq < pb->seq) {
-        delta = pb->seq - seq;
+    else {
+        if (seq < pb->seq) {
+            /* Regular case: pb->seq did not wrapped since seq. */
+            delta = pb->seq - seq;
+            pa_assert(delta <= pb->count);
+        } else {
+            /* Tricky case: pb->seq wrapped since seq! */
+            delta = pb->seq + (UINT16_MAX - seq);
+            pa_assert(delta <= pb->count);
+        }
+
         i = (pb->size + pb->pos - delta) % pb->size;
-        if (delta < pb->size)
+
+        if (delta < pb->size && pb->packets[i].memblock)
             packet = &pb->packets[i];
-    } else {
-        i = (pb->pos + (seq - pb->seq)) % pb->size;
-        if (pb->packets[i].memblock)
-            pa_memblock_unref(pb->packets[i].memblock);
-        pa_memchunk_reset(&pb->packets[i]);
-        pb->packets[i].memblock = pa_memblock_new(pb->mempool, size);
-        packet = &pb->packets[i];
-        pb->seq = seq;
-        pb->pos = i;
     }
 
     return packet;
diff --git a/src/modules/raop/raop-packet-buffer.h b/src/modules/raop/raop-packet-buffer.h
index cb2bfdf..c410298 100644
--- a/src/modules/raop/raop-packet-buffer.h
+++ b/src/modules/raop/raop-packet-buffer.h
@@ -33,7 +33,8 @@ pa_raop_packet_buffer *pa_raop_packet_buffer_new(pa_mempool *mempool, const size
 void pa_raop_packet_buffer_free(pa_raop_packet_buffer *pb);
 
 void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq);
-pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size);
 
-uint16_t pa_raop_packet_buffer_wrap_seq(pa_raop_packet_buffer *pb, uint16_t seq);
+pa_memchunk *pa_raop_packet_buffer_prepare(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size);
+pa_memchunk *pa_raop_packet_buffer_retrieve(pa_raop_packet_buffer *pb, uint16_t seq);
+
 #endif

commit 1b6ebaf6b3d55f970e6b2abec836099ad66f5876
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:18 2016 -0600

    raop: Add IPv6 support

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index cf415d0..574f10b 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -752,6 +752,7 @@ static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
     if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) {
         sa4.sin_family = af = AF_INET;
         sa4.sin_port = htons(port);
+        sa4.sin_addr.s_addr = INADDR_ANY;
         sa = (struct sockaddr *) &sa4;
         salen = sizeof(sa4);
         sa_port = &sa4.sin_port;
@@ -759,6 +760,7 @@ static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
     } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
         sa6.sin6_family = af = AF_INET6;
         sa6.sin6_port = htons(port);
+        sa6.sin6_addr = in6addr_any;
         sa = (struct sockaddr *) &sa6;
         salen = sizeof(sa6);
         sa_port = &sa6.sin6_port;
@@ -849,11 +851,18 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
             int frames = 0;
             const char *ip;
             char *url;
+            int ipv;
 
             pa_log_debug("RAOP: CONNECTED");
 
             ip = pa_rtsp_localip(c->rtsp);
-            url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
+            if (pa_is_ip6_address(ip)) {
+                ipv = 6;
+                url = pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid);
+            } else {
+                ipv = 4;
+                url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
+            }
             pa_rtsp_set_url(c->rtsp, url);
 
             if (c->protocol == PA_RAOP_PROTOCOL_TCP)
@@ -865,14 +874,14 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
                 case PA_RAOP_ENCRYPTION_NONE: {
                     sdp = pa_sprintf_malloc(
                         "v=0\r\n"
-                        "o=iTunes %s 0 IN IP4 %s\r\n"
+                        "o=iTunes %s 0 IN IP%d %s\r\n"
                         "s=iTunes\r\n"
-                        "c=IN IP4 %s\r\n"
+                        "c=IN IP%d %s\r\n"
                         "t=0 0\r\n"
                         "m=audio 0 RTP/AVP 96\r\n"
                         "a=rtpmap:96 AppleLossless\r\n"
                         "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
-                        c->sid, ip, c->host, frames);
+                        c->sid, ipv, ip, ipv, c->host, frames);
 
                     break;
                 }
@@ -886,16 +895,16 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
 
                     sdp = pa_sprintf_malloc(
                         "v=0\r\n"
-                        "o=iTunes %s 0 IN IP4 %s\r\n"
+                        "o=iTunes %s 0 IN IP%d %s\r\n"
                         "s=iTunes\r\n"
-                        "c=IN IP4 %s\r\n"
+                        "c=IN IP%d %s\r\n"
                         "t=0 0\r\n"
                         "m=audio 0 RTP/AVP 96\r\n"
                         "a=rtpmap:96 AppleLossless\r\n"
                         "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
                         "a=rsaaeskey:%s\r\n"
                         "a=aesiv:%s\r\n",
-                        c->sid, ip, c->host, frames, key, iv);
+                        c->sid, ipv, ip, ipv, c->host, frames, key, iv);
 
                     pa_xfree(key);
                     pa_xfree(iv);
diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c
index 31b8673..fd5d2d8 100644
--- a/src/modules/rtp/rtsp_client.c
+++ b/src/modules/rtp/rtsp_client.c
@@ -330,7 +330,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
             }
         } else if (AF_INET6 == sa.sa.sa_family) {
             if ((res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)))) {
-                c->localip = pa_sprintf_malloc("[%s]", res);
+                c->localip = pa_xstrdup(res);
             }
         }
     }

commit 85d3741c3249a73565d1f67cb11311f951feabce
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:54:17 2016 -0600

    raop: Add address to RAOP device description
    
    Some time one device announces multiple addresses (e.g. IPv4 one
    and IPv6 one). Or some user may own multiple RAOP devices with
    the same model name.
    This patch adds device port to device description so that users
    can distinguish appropriate RAOP sink by its address.

diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c
index bf05dec..c0b4549 100644
--- a/src/modules/raop/module-raop-discover.c
+++ b/src/modules/raop/module-raop-discover.c
@@ -241,19 +241,22 @@ static void resolver_cb(
     avahi_free(device);
     pa_xfree(dname);
 
+    avahi_address_snprint(at, sizeof(at), a);
     if (nicename) {
         args = pa_sprintf_malloc("server=[%s]:%u "
                                  "sink_name=%s "
-                                 "sink_properties='device.description=\"%s\"'",
-                                 avahi_address_snprint(at, sizeof(at), a), port,
+                                 "sink_properties='device.description=\"%s (%s:%u)\"'",
+                                 at, port,
                                  vname,
-                                 nicename);
+                                 nicename, at, port);
         pa_xfree(nicename);
     } else {
         args = pa_sprintf_malloc("server=[%s]:%u "
-                                 "sink_name=%s",
-                                 avahi_address_snprint(at, sizeof(at), a), port,
-                                 vname);
+                                 "sink_name=%s"
+                                 "sink_properties='device.description=\"%s:%u\"'",
+                                 at, port,
+                                 vname,
+                                 at, port);
     }
 
     if (tp != NULL) {

commit 59525476e4aed9614bc8466016aa325aa7c2222a
Author: Stephen Paul Weber <github at singpolyma.net>
Date:   Sun Nov 6 12:54:16 2016 -0600

    raop: Do not flush when RTSP object is not ready
    
    This patch fixes a crash issue reported at
    https://github.com/hfujita/pulseaudio-raop2/issues/9

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 90488e4..cf415d0 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -1596,7 +1596,7 @@ int pa_raop_client_flush(pa_raop_client *c) {
 
     pa_assert(c);
 
-    if (!c->rtsp) {
+    if (!c->rtsp || !pa_rtsp_exec_ready(c->rtsp)) {
         pa_log_debug("Cannot FLUSH, connection not established yet...)");
         return 0;
     } else if (!c->sci) {

commit d08f3aa07355217c2877a0adba09d74e2936327b
Author: Colin Leroy <colin at colino.net>
Date:   Sun Nov 6 12:54:15 2016 -0600

    raop: Fix packet retransmission
    
    Fix UDP header decoding (sequence number and number of packets);
    Fix missing pa_memblock_release() causing assertions after retransmission.

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 8a43a19..90488e4 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -470,6 +470,7 @@ static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size
         written = pa_write(c->udp_sfd, buffer, packet->length, NULL);
     if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
         pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
+        pa_memblock_release(packet->memblock);
         return (ssize_t) packet->length;
     }
 
@@ -525,9 +526,11 @@ static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_
             written = pa_write(c->udp_cfd, buffer, packet->length, NULL);
         if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
             pa_log_debug("Discarding UDP (audio-restransmitted, seq=%d) packet due to EAGAIN", seq + i);
+            pa_memblock_release(packet->memblock);
             continue;
         }
 
+        pa_memblock_release(packet->memblock);
         total +=  written;
     }
 
@@ -582,8 +585,8 @@ static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[
     if (size != 8 || packet[0] != 0x80)
         return 1;
 
-    seq = ntohs((uint16_t) packet[4]);
-    nbp = ntohs((uint16_t) packet[6]);
+    seq = ntohs((uint16_t) (packet[4] | packet[5] << 8));
+    nbp = ntohs((uint16_t) (packet[6] | packet[7] << 8));
     if (nbp <= 0)
         return 1;
 

commit 7f7f177ad2d55c15ae3143a8de674dd90e9f3642
Author: Colin Leroy <colin at colino.net>
Date:   Sun Nov 6 12:54:14 2016 -0600

    raop: fix typos

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 8dc05a4..8a43a19 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -174,14 +174,14 @@ static const uint8_t udp_sync_header[8] = {
     0x00, 0x00, 0x00, 0x00
 };
 
-/* Timming packet header [8x8] (cf. rfc3550):
+/* Timing packet header [8x8] (cf. rfc3550):
  *  [0]   RTP v2: 0x80
  *  [1]   Payload type: 0x53 | Marker bit: 0x80 (always set)
  *  [2,3] Sequence number: 0x0007
  *  [4,7] Timestamp: 0x00000000 (unused) */
 #define PAYLOAD_TIMING_REQUEST  0x52
 #define PAYLOAD_TIMING_REPLY    0x53
-static const uint8_t udp_timming_header[8] = {
+static const uint8_t udp_timing_header[8] = {
     0x80, 0xd3, 0x00, 0x07,
     0x00, 0x00, 0x00, 0x00
 };
@@ -587,7 +587,7 @@ static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[
     if (nbp <= 0)
         return 1;
 
-    /* The market bit is always set (see rfc3550 for packet structure) ! */
+    /* The marker bit is always set (see rfc3550 for packet structure) ! */
     payload = packet[1] ^ 0x80;
     switch (payload) {
         case PAYLOAD_RETRANSMIT_REQUEST:
@@ -604,7 +604,7 @@ static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[
 }
 
 static size_t build_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received, uint32_t **packet) {
-    const size_t size = sizeof(udp_timming_header) + 24;
+    const size_t size = sizeof(udp_timing_header) + 24;
     uint32_t *buffer = NULL;
     uint64_t transmitted = 0;
     struct timeval tv;
@@ -613,7 +613,7 @@ static size_t build_udp_timing_packet(pa_raop_client *c, const uint32_t data[6],
     if (!(buffer = pa_xmalloc0(size)))
         return 0;
 
-    memcpy(buffer, udp_timming_header, sizeof(udp_timming_header));
+    memcpy(buffer, udp_timing_header, sizeof(udp_timing_header));
     /* Copying originate timestamp from the incoming request packet. */
     buffer[2] = data[4];
     buffer[3] = data[5];
@@ -653,9 +653,9 @@ static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[]
         return 0;
 
     rci = timeval_to_ntp(pa_rtclock_get(&tv));
-    data = (uint32_t *) (packet + sizeof(udp_timming_header));
+    data = (uint32_t *) (packet + sizeof(udp_timing_header));
 
-    /* The market bit is always set (see rfc3550 for packet structure) ! */
+    /* The marker bit is always set (see rfc3550 for packet structure) ! */
     payload = packet[1] ^ 0x80;
     switch (payload) {
         case PAYLOAD_TIMING_REQUEST:

commit ff28c7759b17786413f5c0641b438c882da07e7b
Author: Colin Leroy <colin at colino.net>
Date:   Sun Nov 6 12:54:13 2016 -0600

    raop: fix sequence number overflow
    
    Wrap sequence number when we reach uint16_t's max 0xFFFF.

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 81a68a0..8dc05a4 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -316,7 +316,12 @@ static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
     buffer += packet->index / sizeof(uint32_t);
     raw += block->index;
 
-    c->seq++;
+    if (c->seq == 0xFFFF) {
+        pa_log_debug("wrapping sequence number");
+        c->seq = pa_raop_packet_buffer_wrap_seq(c->pbuf, c->seq);
+    } else
+        c->seq++;
+
     memcpy(buffer, tcp_audio_header, sizeof(tcp_audio_header));
     buffer[1] |= htonl((uint32_t) c->seq);
     buffer[2] = htonl(c->rtptime);
@@ -421,7 +426,12 @@ static size_t build_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
     }
 
     c->rtptime += length / 4;
-    c->seq++;
+
+    if (c->seq == 0xFFFF) {
+        pa_log_debug("wrapping sequence number");
+        c->seq = pa_raop_packet_buffer_wrap_seq(c->pbuf, c->seq);
+    } else
+        c->seq++;
 
     pa_memblock_release(block->memblock);
 
diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
index 06e3125..4c45d18 100644
--- a/src/modules/raop/raop-packet-buffer.c
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -88,13 +88,28 @@ void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq) {
     }
 }
 
+uint16_t pa_raop_packet_buffer_wrap_seq(pa_raop_packet_buffer *pb, uint16_t seq) {
+    int seq_shift;
+
+    pa_assert(pb);
+
+    if (seq > pb->seq)
+        seq_shift = pb->seq - 1;
+    else
+        seq_shift = seq;
+
+    pb->seq -= seq_shift;
+
+    return seq - seq_shift;
+
+}
+
 pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size) {
     pa_memchunk *packet = NULL;
     size_t delta, i;
 
     pa_assert(pb);
     pa_assert(pb->packets);
-    pa_assert(seq > 0);
 
     if (seq == pb->seq)
         packet = &pb->packets[pb->pos];
diff --git a/src/modules/raop/raop-packet-buffer.h b/src/modules/raop/raop-packet-buffer.h
index b8c7617..cb2bfdf 100644
--- a/src/modules/raop/raop-packet-buffer.h
+++ b/src/modules/raop/raop-packet-buffer.h
@@ -35,4 +35,5 @@ void pa_raop_packet_buffer_free(pa_raop_packet_buffer *pb);
 void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq);
 pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size);
 
+uint16_t pa_raop_packet_buffer_wrap_seq(pa_raop_packet_buffer *pb, uint16_t seq);
 #endif

commit a97de39ec96a0aa9707eac65d1b2e77e5b574f00
Author: Colin Leroy <colin at colino.net>
Date:   Sun Nov 6 12:54:12 2016 -0600

    raop: Fix sink getting destroyed after one use
    
    It is expected to get disconnected after switching back to a
    different sink.

diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index 23989d1..2d8157e 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -252,7 +252,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
                     if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
                         pa_rtpoll_set_timer_disabled(u->rtpoll);
-                    else
+                    else if (u->sink->thread_info.state != PA_SINK_IDLE)
                         pa_module_unload_request(u->module, true);
 
                     return 0;

commit b4c6e3e05fc3a8229ee266a0d740513e1199c98f
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:11 2016 -0600

    raop: Remove unimplemented code (PCM and AAC)

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 195b00c..81a68a0 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -267,16 +267,6 @@ static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, size_t *size,
     }
 }
 
-static size_t write_PCM_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length) {
-    size_t size = 0;
-
-    pa_memzero(packet, max);
-
-    pa_log("Raw PCM not implemented...");
-
-    return size;
-}
-
 static size_t write_ALAC_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length, bool compress) {
     uint32_t nbs = (*length / 2) / 2;
     uint8_t *ibp, *maxibp;
@@ -315,16 +305,6 @@ static size_t write_ALAC_data(uint8_t *packet, const size_t max, uint8_t *raw, s
     return size;
 }
 
-static size_t write_AAC_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length) {
-    size_t size = 0;
-
-    pa_memzero(packet, max);
-
-    pa_log("AAC encoding not implemented...");
-
-    return size;
-}
-
 static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_memchunk *packet) {
     const size_t head = sizeof(tcp_audio_header);
     uint32_t *buffer = NULL;
@@ -344,12 +324,14 @@ static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
 
     length = block->length;
     size = sizeof(tcp_audio_header);
-    if (c->codec == PA_RAOP_CODEC_PCM)
-        size += write_PCM_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
-    else if (c->codec == PA_RAOP_CODEC_ALAC)
+    if (c->codec == PA_RAOP_CODEC_ALAC)
         size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
-    else
-        size += write_AAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
+    else {
+        pa_log_debug("Only ALAC encoding is supported, sending zeros...");
+        pa_memzero(((uint8_t *) buffer + head), packet->length - head);
+        size += length;
+    }
+
     c->rtptime += length / 4;
 
     pa_memblock_release(block->memblock);
@@ -430,12 +412,14 @@ static size_t build_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_m
 
     length = block->length;
     size = sizeof(udp_audio_header);
-    if (c->codec == PA_RAOP_CODEC_PCM)
-        size += write_PCM_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
-    else if (c->codec == PA_RAOP_CODEC_ALAC)
+    if (c->codec == PA_RAOP_CODEC_ALAC)
         size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
-    else
-        size += write_AAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
+    else {
+        pa_log_debug("Only ALAC encoding is supported, sending zeros...");
+        pa_memzero(((uint8_t *) buffer + head), packet->length - head);
+        size += length;
+    }
+
     c->rtptime += length / 4;
     c->seq++;
 

commit 44d3f874d89e25b301e6bb26558cdfa94a7e39ba
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:10 2016 -0600

    raop: Rework packet's store memory management
    
    This patch switch the packet-buffer to use core memory pool instead of
    manually allocating the room required for storing TCP/UDP packets. Packets
    are now stored using pa_memchunk instead of internal struct. Quite a few
    malloc saved compare to previous design.

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index f1e5970..195b00c 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -65,6 +65,8 @@
 #define FRAMES_PER_TCP_PACKET 4096
 #define FRAMES_PER_UDP_PACKET 352
 
+#define RTX_BUFFERING_SECONDS 4
+
 #define DEFAULT_TCP_AUDIO_PORT   6000
 #define DEFAULT_UDP_AUDIO_PORT   6000
 #define DEFAULT_UDP_CONTROL_PORT 6001
@@ -323,92 +325,102 @@ static size_t write_AAC_data(uint8_t *packet, const size_t max, uint8_t *raw, si
     return size;
 }
 
-static size_t build_tcp_audio_packet(pa_raop_client *c, uint8_t *raw, size_t *index, size_t *length, uint32_t **packet) {
-    const size_t max = sizeof(tcp_audio_header) + 8 + 16384;
+static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_memchunk *packet) {
+    const size_t head = sizeof(tcp_audio_header);
     uint32_t *buffer = NULL;
-    size_t size, head;
+    uint8_t *raw = NULL;
+    size_t length, size;
 
-    *packet = NULL;
-    if (!(buffer = pa_xmalloc0(max)))
-        return 0;
+    raw = pa_memblock_acquire(block->memblock);
+    buffer = pa_memblock_acquire(packet->memblock);
+    buffer += packet->index / sizeof(uint32_t);
+    raw += block->index;
 
-    size = head = sizeof(tcp_audio_header);
+    c->seq++;
     memcpy(buffer, tcp_audio_header, sizeof(tcp_audio_header));
     buffer[1] |= htonl((uint32_t) c->seq);
     buffer[2] = htonl(c->rtptime);
     buffer[3] = htonl(c->ssrc);
 
+    length = block->length;
+    size = sizeof(tcp_audio_header);
     if (c->codec == PA_RAOP_CODEC_PCM)
-        size += write_PCM_data(((uint8_t *) buffer + head), max - head, raw, length);
+        size += write_PCM_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
     else if (c->codec == PA_RAOP_CODEC_ALAC)
-        size += write_ALAC_data(((uint8_t *) buffer + head), max - head, raw, length, false);
+        size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
     else
-        size += write_AAC_data(((uint8_t *) buffer + head), max - head, raw, length);
-    c->rtptime += *length / 4;
+        size += write_AAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
+    c->rtptime += length / 4;
+
+    pa_memblock_release(block->memblock);
 
     buffer[0] |= htonl((uint32_t) size - 4);
     if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
         pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
 
-    *packet = buffer;
+    pa_memblock_release(packet->memblock);
+    packet->length = size;
+
     return size;
 }
 
 static ssize_t send_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
-    static uint32_t * packet = NULL;
-    static size_t size, sent;
+    static int write_type = 0;
+    const size_t max = sizeof(tcp_audio_header) + 8 + 16384;
+    pa_memchunk *packet = NULL;
+    uint8_t *buffer = NULL;
     double progress = 0.0;
-    size_t index, length;
-    uint8_t *raw = NULL;
-    ssize_t written;
+    ssize_t written = -1;
+    size_t done = 0;
+
+    if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq, max)))
+        return -1;
 
-    if (!packet) {
-        index = block->index;
-        length = block->length;
-        raw = pa_memblock_acquire(block->memblock);
+    if (packet->length <= 0) {
+        pa_assert(block->index == offset);
 
-        pa_assert(raw);
-        pa_assert(index == offset);
-        pa_assert(length > 0);
+        if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq + 1, max)))
+            return -1;
 
-        size = build_tcp_audio_packet(c, raw, &index, &length, &packet);
-        sent = 0;
+        packet->index = 0;
+        packet->length = max;
+        if (!build_tcp_audio_packet(c, block, packet))
+            return -1;
     }
 
-    written = -1;
-    if (packet != NULL && size > 0)
-        written = pa_write(c->tcp_sfd, packet + sent, size - sent, NULL);
-    if (block->index == offset)
-        c->seq++;
-    if (sent == 0)
-        pa_memblock_release(block->memblock);
+    buffer = pa_memblock_acquire(packet->memblock);
+
+    pa_assert(buffer);
+
+    buffer += packet->index;
+    if (buffer && packet->length > 0)
+        written = pa_write(c->tcp_sfd, buffer, packet->length, &write_type);
     if (written > 0) {
-        sent += written;
-        progress = (double) sent / (double) size;
-        index = (block->index + block->length) * progress;
-        length = (block->index + block->length) - index;
-        block->length = length;
-        block->index = index;
-    }
+        progress = (double) written / (double) packet->length;
+        packet->length -= written;
+        packet->index += written;
 
-    if ((size - sent) <= 0) {
-        pa_xfree(packet);
-        packet = NULL;
+        done = block->length * progress;
+        block->length -= done;
+        block->index += done;
     }
 
+    pa_memblock_release(packet->memblock);
+
     return written;
 }
 
-static size_t build_udp_audio_packet(pa_raop_client *c, uint8_t *raw, size_t *index, size_t *length, uint32_t **packet) {
-    const size_t max = sizeof(udp_audio_header) + 8 + 1408;
+static size_t build_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_memchunk *packet) {
+    const size_t head = sizeof(udp_audio_header);
     uint32_t *buffer = NULL;
-    size_t size, head;
+    uint8_t *raw = NULL;
+    size_t length, size;
 
-    *packet = NULL;
-    if (!(buffer = pa_xmalloc0(max)))
-        return 0;
+    raw = pa_memblock_acquire(block->memblock);
+    buffer = pa_memblock_acquire(packet->memblock);
+    buffer += packet->index / sizeof(uint32_t);
+    raw += block->index;
 
-    size = head = sizeof(udp_audio_header);
     memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
     if (c->is_first_packet)
         buffer[0] |= htonl((uint32_t) 0x80 << 16);
@@ -416,75 +428,79 @@ static size_t build_udp_audio_packet(pa_raop_client *c, uint8_t *raw, size_t *in
     buffer[1] = htonl(c->rtptime);
     buffer[2] = htonl(c->ssrc);
 
+    length = block->length;
+    size = sizeof(udp_audio_header);
     if (c->codec == PA_RAOP_CODEC_PCM)
-        size += write_PCM_data(((uint8_t *) buffer + head), max - head, raw + *index, length);
+        size += write_PCM_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
     else if (c->codec == PA_RAOP_CODEC_ALAC)
-        size += write_ALAC_data(((uint8_t *) buffer + head), max - head, raw + *index, length, false);
+        size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
     else
-        size += write_AAC_data(((uint8_t *) buffer + head), max - head, raw + *index, length);
-    c->rtptime += *length / 4;
+        size += write_AAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length);
+    c->rtptime += length / 4;
+    c->seq++;
+
+    pa_memblock_release(block->memblock);
 
     if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
         pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
 
-    *index += *length;
-    *length = 0;
-    /* It is meaningless to preseve the partial data -> */
-    *packet = buffer;
+    pa_memblock_release(packet->memblock);
+    packet->length = size;
+
     return size;
 }
 
 static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
-    uint32_t *packet = NULL;
-    size_t index, length, size;
-    uint8_t *raw = NULL;
-    ssize_t written;
+    const size_t max = sizeof(udp_audio_retrans_header) + sizeof(udp_audio_header) + 8 + 1408;
+    pa_memchunk *packet = NULL;
+    uint8_t *buffer = NULL;
+    ssize_t written = -1;
 
-    index = block->index;
-    length = block->length;
-    raw = pa_memblock_acquire(block->memblock);
+    /* UDP packet has to be sent at once ! */
+    pa_assert(block->index == offset);
 
-    pa_assert(raw);
-    /* <- UDP packet has to be sent at once ! */
-    pa_assert(index == offset);
-    pa_assert(length > 0);
+    if (!(packet = pa_raop_packet_buffer_get(c->pbuf, c->seq, max)))
+        return -1;
 
-    written = -1;
-    size = build_udp_audio_packet(c, raw, &index, &length, &packet);
-    if (packet != NULL && size > 0)
-        written = pa_write(c->udp_sfd, packet, size, NULL);
-    if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
-        pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
-    c->seq++;
+    packet->length = max;
+    packet->index = sizeof(udp_audio_retrans_header);
+    if (!build_udp_audio_packet(c, block, packet))
+        return -1;
 
-    /* Store packet for resending in the packet buffer (UDP). */
-    pa_raop_pb_write_packet(c->pbuf, c->seq, raw + block->index, block->length);
-    pa_xfree(packet);
+    buffer = pa_memblock_acquire(packet->memblock);
 
-    pa_memblock_release(block->memblock);
-    block->length = length;
-    block->index = index;
+    pa_assert(buffer);
+
+    buffer += packet->index;
+    if (buffer && packet->length > 0)
+        written = pa_write(c->udp_sfd, buffer, packet->length, NULL);
+    if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
+        return (ssize_t) packet->length;
+    }
+
+    pa_memblock_release(packet->memblock);
+    /* It is meaningless to preseve the partial data */
+    block->index += block->length;
+    block->length = 0;
 
-    if (written < 0)
-        return (ssize_t) size;
     return written;
 }
 
-static size_t rebuild_udp_audio_packet(pa_raop_client *c, uint16_t seq, uint32_t **packet) {
+static size_t rebuild_udp_audio_packet(pa_raop_client *c, uint16_t seq, pa_memchunk *packet) {
     size_t size = sizeof(udp_audio_retrans_header);
     uint32_t *buffer = NULL;
-    uint8_t *data = NULL;
 
-    size += pa_raop_pb_read_packet(c->pbuf, seq, &data);
-    if (size == sizeof(udp_audio_retrans_header))
-        return 0;
-    if (!(buffer = pa_xmalloc0(size)))
-        return 0;
+    buffer = pa_memblock_acquire(packet->memblock);
 
     memcpy(buffer, udp_audio_retrans_header, sizeof(udp_audio_retrans_header));
     buffer[0] |= htonl((uint32_t) seq);
+    size += packet->length;
+
+    pa_memblock_release(packet->memblock);
+    packet->length += sizeof(udp_audio_retrans_header);
+    packet->index -= sizeof(udp_audio_retrans_header);
 
-    *packet = buffer;
     return size;
 }
 
@@ -493,20 +509,32 @@ static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_
     int i = 0;
 
     for (i = 0; i < nbp; i++) {
-        uint32_t * packet = NULL;
-        ssize_t written = 0;
-        size_t size = 0;
+        pa_memchunk *packet = NULL;
+        uint8_t *buffer = NULL;
+        ssize_t written = -1;
+
+        if (!(packet = pa_raop_packet_buffer_get(c->pbuf, seq + i, 0)))
+            continue;
+
+        if (packet->index > 0) {
+            if (!rebuild_udp_audio_packet(c, seq + i, packet))
+                continue;
+        }
+
+        pa_assert(packet->index == 0);
+
+        buffer = pa_memblock_acquire(packet->memblock);
 
-        size = rebuild_udp_audio_packet(c, seq, &packet);
-        if (packet != NULL && size > 0)
-            written = pa_write(c->udp_cfd, packet, size, NULL);
+        pa_assert(buffer);
+
+        if (buffer && packet->length > 0)
+            written = pa_write(c->udp_cfd, buffer, packet->length, NULL);
         if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
-            pa_log_debug("Discarding UDP (audio-restransmitted, seq=%d) packet due to EAGAIN", c->seq);
+            pa_log_debug("Discarding UDP (audio-restransmitted, seq=%d) packet due to EAGAIN", seq + i);
             continue;
         }
 
-        if (written > 0)
-            total +=  written;
+        total +=  written;
     }
 
     return total;
@@ -557,17 +585,13 @@ static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[
     ssize_t written = 0;
 
     /* Control packets are 8 bytes long:  */
-    if (size != 8 || packet[0] != 0x80) {
-        pa_log_debug("Received an invalid control packet...");
+    if (size != 8 || packet[0] != 0x80)
         return 1;
-    }
 
     seq = ntohs((uint16_t) packet[4]);
     nbp = ntohs((uint16_t) packet[6]);
-    if (nbp <= 0) {
-        pa_log_debug("Received an invalid control packet...");
+    if (nbp <= 0)
         return 1;
-    }
 
     /* The market bit is always set (see rfc3550 for packet structure) ! */
     payload = packet[1] ^ 0x80;
@@ -631,10 +655,8 @@ static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[]
     uint64_t rci = 0;
 
     /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
-    if (size != 32 || packet[0] != 0x80) {
-        pa_log_debug("Received an invalid UDP timing packet...");
+    if (size != 32 || packet[0] != 0x80)
         return 0;
-    }
 
     rci = timeval_to_ntp(pa_rtclock_get(&tv));
     data = (uint32_t *) (packet + sizeof(udp_timming_header));
@@ -1061,6 +1083,8 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
             if (alt)
                 pa_atoi(alt, &latency);
 
+            pa_raop_packet_buffer_reset(c->pbuf, c->seq);
+
             pa_random(&ssrc, sizeof(ssrc));
             c->is_first_packet = true;
             c->is_recording = true;
@@ -1093,9 +1117,6 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
 
             c->is_recording = false;
 
-            if (c->pbuf)
-                pa_raop_pb_clear(c->pbuf);
-
             if (c->tcp_sfd > 0)
                 pa_close(c->tcp_sfd);
             c->tcp_sfd = -1;
@@ -1124,9 +1145,6 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
 
             c->is_recording = false;
 
-            if (c->pbuf)
-                pa_raop_pb_clear(c->pbuf);
-
             if (c->tcp_sfd > 0)
                 pa_close(c->tcp_sfd);
             c->tcp_sfd = -1;
@@ -1331,6 +1349,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
 
     pa_parsed_address a;
     pa_sample_spec ss;
+    size_t size = 2;
 
     pa_assert(core);
     pa_assert(host);
@@ -1369,6 +1388,8 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
         c->secret = pa_raop_secret_new();
 
     ss = core->default_sample_spec;
+    if (c->protocol == PA_RAOP_PROTOCOL_UDP)
+        size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET;
 
     c->is_recording = false;
     c->is_first_packet = true;
@@ -1376,7 +1397,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
     c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
     c->sync_count = 0;
 
-    c->pbuf = pa_raop_pb_new(UDP_DEFAULT_PKT_BUF_SIZE);
+    c->pbuf = pa_raop_packet_buffer_new(c->core->mempool, size);
 
     return c;
 }
@@ -1384,7 +1405,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
 void pa_raop_client_free(pa_raop_client *c) {
     pa_assert(c);
 
-    pa_raop_pb_delete(c->pbuf);
+    pa_raop_packet_buffer_free(c->pbuf);
 
     pa_xfree(c->sid);
     pa_xfree(c->sci);
@@ -1686,10 +1707,10 @@ void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uin
 
     if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
         if (fd == c->udp_cfd) {
-            pa_log_debug("Received UDP control packet");
+            pa_log_debug("Received UDP control packet...");
             handle_udp_control_packet(c, packet, size);
         } else if (fd == c->udp_tfd) {
-            pa_log_debug("Received UDP timing packet");
+            pa_log_debug("Received UDP timing packet...");
             handle_udp_timing_packet(c, packet, size);
         }
     }
diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
index d9d8549..06e3125 100644
--- a/src/modules/raop/raop-packet-buffer.c
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -20,152 +20,99 @@
   USA.
 ***/
 
-#include <stdlib.h>
-#include <limits.h>
-
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
+#include <stdlib.h>
+#include <limits.h>
+
+#include <pulse/xmalloc.h>
+
 #include <pulsecore/core-error.h>
-#include "raop-client.h"
+#include <pulsecore/macro.h>
 
 #include "raop-packet-buffer.h"
 
-/* FRAMES_PER_PACKET*2*2 + sizeof(udp_audio_header) + sizeof(ALAC header), unencoded */
-#define PACKET_SIZE_MAX (352*2*2 + 12 + 7) /* FIXME; hardcoded constant ! */
-/* Header room for packet retransmission header */
-#define RETRANS_HEADER_ROOM 4
-
-/* Packet element */
-struct pa_raop_packet_element {
-    uint16_t  seq_num; /* RTP sequence number (in host byte order) */
-    ssize_t   length;  /* Actual packet length */
-    /* Packet data including RTP header */
-    uint8_t   data[PACKET_SIZE_MAX + RETRANS_HEADER_ROOM];
-};
-
-/* Buffer struct */
 struct pa_raop_packet_buffer {
-    size_t   size;          /* max number of packets in buffer */
-    size_t   start;         /* index of oldest packet */
-    size_t   count;         /* number of packets in buffer */
-    uint16_t first_seq_num; /* Sequence number of first packet in buffer */
-    uint16_t latest_seq_num; /* Debug purpose */
-    pa_raop_packet_element *packets; /* Packet element pointer */
+    pa_memchunk *packets;
+    pa_mempool *mempool;
+    size_t size;
+
+    uint16_t seq;
+    size_t pos;
 };
 
-pa_raop_packet_buffer *pa_raop_pb_new(size_t size) {
-    pa_raop_packet_buffer *pb = pa_xmalloc0(sizeof(*pb));
+pa_raop_packet_buffer *pa_raop_packet_buffer_new(pa_mempool *mempool, const size_t size) {
+    pa_raop_packet_buffer *pb = pa_xnew0(pa_raop_packet_buffer, 1);
 
-    pb->size = size;
-    pb->packets = (pa_raop_packet_element *)
-        pa_xmalloc(size * sizeof(pa_raop_packet_element));
+    pa_assert(mempool);
+    pa_assert(size > 0);
 
-    pa_raop_pb_clear(pb);
+    pb->size = size;
+    pb->mempool = mempool;
+    pb->packets = pa_xnew0(pa_memchunk, size);
+    pb->seq = pb->pos = 0;
 
     return pb;
 }
 
-void pa_raop_pb_clear(pa_raop_packet_buffer *pb) {
-    pb->start = 0;
-    pb->count = 0;
-    pb->first_seq_num = 0;
-    pb->latest_seq_num = 0;
-    memset(pb->packets, 0, pb->size * sizeof(pa_raop_packet_element));
-}
-
-void pa_raop_pb_delete(pa_raop_packet_buffer *pb) {
-    pa_xfree(pb->packets);
-    pa_xfree(pb);
-}
-
-static int pb_is_full(pa_raop_packet_buffer *pb) {
-    return pb->count == pb->size;
-}
-
-static int pb_is_empty(pa_raop_packet_buffer *pb) {
-    return pb->count == 0;
-}
-
-static pa_raop_packet_element *pb_prepare_write(pa_raop_packet_buffer *pb, uint16_t seq) {
-    size_t end = (pb->start + pb->count) % pb->size;
-    pa_raop_packet_element *packet;
-
-    /* Set first packet sequence number in buffer if buffer is empty */
-    if (pb_is_empty(pb))
-        pb->first_seq_num = seq;
-    else
-        pa_assert((uint16_t) (pb->latest_seq_num + 1) == seq);
-
-    packet = &pb->packets[end];
-
-    if (pb_is_full(pb)) {
-        pb->start = (pb->start + 1) % pb->size; /* full, overwrite */
+void pa_raop_packet_buffer_free(pa_raop_packet_buffer *pb) {
+    size_t i;
 
-        /* Set first packet sequence number in buffer
-           to new start packet sequence number */
-        pb->first_seq_num = pb->packets[pb->start].seq_num;
-    } else
-        ++ pb->count;
+    pa_assert(pb);
 
-    pb->latest_seq_num = seq;
+    for (i = 0; pb->packets && i < pb->size; i++) {
+        if (pb->packets[i].memblock)
+            pa_memblock_unref(pb->packets[i].memblock);
+        pa_memchunk_reset(&pb->packets[i]);
+    }
 
-    return packet;
+    pa_xfree(pb->packets);
+    pb->packets = NULL;
+    pa_xfree(pb);
 }
 
-/* Write packet data to packet buffer */
-void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length) {
-    pa_raop_packet_element *packet;
+void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq) {
+    size_t i;
 
     pa_assert(pb);
-    pa_assert(packet_data);
-    pa_assert(packet_length <= PACKET_SIZE_MAX);
-
-    packet = pb_prepare_write(pb, seq_num);
-    packet->seq_num = seq_num;
-    packet->length = packet_length + RETRANS_HEADER_ROOM;
-
-    /* Insert RETRANS_HEADER_ROOM bytes in front of packet data,
-       for retransmission header */
-    memset(packet->data, 0, RETRANS_HEADER_ROOM);
-    memcpy(packet->data + RETRANS_HEADER_ROOM, packet_data, packet_length);
-}
-
-/* l < r?, considers wrapping */
-static bool seq_lt(uint16_t l, uint16_t r) {
-    return l - r > USHRT_MAX/2;
+    pa_assert(pb->packets);
+
+    pb->pos = 0;
+    pb->seq = seq - 1;
+    for (i = 0; i < pb->size; i++) {
+        if (pb->packets[i].memblock)
+            pa_memblock_unref(pb->packets[i].memblock);
+        pa_memchunk_reset(&pb->packets[i]);
+    }
 }
 
-/* Random access to packet from buffer by sequence number for (re-)sending. */
-ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data) {
-    uint16_t index = 0; /* Index of requested packet */
-    pa_raop_packet_element *packet;
-
-    /* If the buffer is empty, there is no use in calculating indices */
-    if (pb_is_empty(pb))
-        return -1;
-
-    /* If the requested packet is too old (seq_num below first seq number
-       in buffer) or too young (seq_num greater than current seq number),
-       do nothing and return */
-    if (seq_lt(seq_num, pb->first_seq_num))
-        return -1;
+pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size) {
+    pa_memchunk *packet = NULL;
+    size_t delta, i;
 
-    index = (uint16_t) (seq_num - pb->first_seq_num);
-    if (index >= pb->count)
-        return -1;
-
-    /*  Index of the requested packet in the buffer is calculated
-        using the first sequence number stored in the buffer.
-        The offset (seq_num - first_seq_num) is used to access the array. */
-    packet = &pb->packets[(pb->start + index) % pb->size];
-
-    pa_assert(packet->data[RETRANS_HEADER_ROOM + 2] == (seq_num >> 8));
-    pa_assert(packet->data[RETRANS_HEADER_ROOM + 3] == (seq_num & 0xff));
-    pa_assert(packet_data);
-
-    *packet_data = packet->data;
+    pa_assert(pb);
+    pa_assert(pb->packets);
+    pa_assert(seq > 0);
+
+    if (seq == pb->seq)
+        packet = &pb->packets[pb->pos];
+    else if (seq < pb->seq) {
+        delta = pb->seq - seq;
+        i = (pb->size + pb->pos - delta) % pb->size;
+        if (delta < pb->size)
+            packet = &pb->packets[i];
+    } else {
+        i = (pb->pos + (seq - pb->seq)) % pb->size;
+        if (pb->packets[i].memblock)
+            pa_memblock_unref(pb->packets[i].memblock);
+        pa_memchunk_reset(&pb->packets[i]);
+        pb->packets[i].memblock = pa_memblock_new(pb->mempool, size);
+        packet = &pb->packets[i];
+        pb->seq = seq;
+        pb->pos = i;
+    }
 
-    return packet->length;
+    return packet;
 }
diff --git a/src/modules/raop/raop-packet-buffer.h b/src/modules/raop/raop-packet-buffer.h
index 69a0ce1..b8c7617 100644
--- a/src/modules/raop/raop-packet-buffer.h
+++ b/src/modules/raop/raop-packet-buffer.h
@@ -23,19 +23,16 @@
   USA.
 ***/
 
-struct pa_raop_packet_element;
-typedef struct pa_raop_packet_element pa_raop_packet_element;
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
 
-struct pa_raop_packet_buffer;
 typedef struct pa_raop_packet_buffer pa_raop_packet_buffer;
 
-/* Allocates a new circular packet buffer
-   size: Maximum number of packets to store */
-pa_raop_packet_buffer *pa_raop_pb_new(size_t size);
-void pa_raop_pb_clear(pa_raop_packet_buffer *pb);
-void pa_raop_pb_delete(pa_raop_packet_buffer *pb);
+/* Allocates a new circular packet buffer, size: Maximum number of packets to store */
+pa_raop_packet_buffer *pa_raop_packet_buffer_new(pa_mempool *mempool, const size_t size);
+void pa_raop_packet_buffer_free(pa_raop_packet_buffer *pb);
 
-void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length);
-ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data);
+void pa_raop_packet_buffer_reset(pa_raop_packet_buffer *pb, uint16_t seq);
+pa_memchunk *pa_raop_packet_buffer_get(pa_raop_packet_buffer *pb, uint16_t seq, const size_t size);
 
 #endif
diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index 22b1ccc..23989d1 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -333,7 +333,6 @@ static void thread_func(void *userdata) {
         unsigned int i, nbfds = 0;
         pa_usec_t now, estimated, intvl;
         uint64_t position;
-        ssize_t written;
         size_t index;
         int ret;
 
@@ -399,8 +398,7 @@ static void thread_func(void *userdata) {
         pa_assert(u->memchunk.length > 0);
 
         index = u->memchunk.index;
-        written = pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset);
-        if (written < 0) {
+        if (pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
             if (errno == EINTR) {
                 /* Just try again. */
                 pa_log_debug("Failed to write data to FIFO (EINTR), retrying");

commit 8e573aa986b76d6d50fc7dba5490f63d6c2069ea
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:09 2016 -0600

    raop: Update and standardise source file headers

diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
index 813b161..f1e5970 100644
--- a/src/modules/raop/raop-client.c
+++ b/src/modules/raop/raop-client.c
@@ -2,6 +2,8 @@
   This file is part of PulseAudio.
 
   Copyright 2008 Colin Guthrie
+  Copyright 2013 Hajime Fujita
+  Copyright 2013 Martin Blanchard
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
index ef8ea15..d9d8549 100644
--- a/src/modules/raop/raop-packet-buffer.c
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -1,15 +1,15 @@
 /***
-  Circular buffer for RTP audio packets with random access support
-  by RTP sequence number.
+  This file is part of PulseAudio.
 
-  Copyright 2013 Matthias Wabersich, Hajime Fujita
+  Copyright 2013 Matthias Wabersich
+  Copyright 2013 Hajime Fujita
 
-  This is free software; you can redistribute it and/or modify
+  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.1 of the License,
   or (at your option) any later version.
 
-  This is distributed in the hope that it will be useful, but
+  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.
@@ -18,7 +18,6 @@
   along with PulseAudio; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA.
-
 ***/
 
 #include <stdlib.h>
diff --git a/src/modules/raop/raop-packet-buffer.h b/src/modules/raop/raop-packet-buffer.h
index d8a08a0..69a0ce1 100644
--- a/src/modules/raop/raop-packet-buffer.h
+++ b/src/modules/raop/raop-packet-buffer.h
@@ -1,18 +1,18 @@
-#ifndef RAOP_PACKET_BUFFER_H_INCLUDED
-#define RAOP_PACKET_BUFFER_H_INCLUDED
+#ifndef fooraoppacketbufferfoo
+#define fooraoppacketbufferfoo
 
 /***
-  Circular buffer for RTP audio packets with random access support
-  by RTP sequence number.
+  This file is part of PulseAudio.
 
-  Copyright 2013 Matthias Wabersich, Hajime Fujita
+  Copyright 2013 Matthias Wabersich
+  Copyright 2013 Hajime Fujita
 
-  This is free software; you can redistribute it and/or modify
+  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.1 of the License,
   or (at your option) any later version.
 
-  This is distributed in the hope that it will be useful, but
+  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.
@@ -21,7 +21,6 @@
   along with PulseAudio; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA.
-
 ***/
 
 struct pa_raop_packet_element;
@@ -39,4 +38,4 @@ void pa_raop_pb_delete(pa_raop_packet_buffer *pb);
 void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length);
 ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data);
 
-#endif /* RAOP_PACKET_BUFFER_H_INCLUDED */
+#endif
diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index 4f743be..22b1ccc 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -3,6 +3,7 @@
 
   Copyright 2004-2006 Lennart Poettering
   Copyright 2008 Colin Guthrie
+  Copyright 2013 Hajime Fujita
   Copyright 2013 Martin Blanchard
 
   PulseAudio is free software; you can redistribute it and/or modify
diff --git a/src/modules/raop/raop-util.c b/src/modules/raop/raop-util.c
index 3e3eb00..febc204 100644
--- a/src/modules/raop/raop-util.c
+++ b/src/modules/raop/raop-util.c
@@ -1,8 +1,9 @@
 /***
   This file is part of PulseAudio.
 
+  Copyright 2008 Colin Guthrie
+  Copyright Kungliga Tekniska högskolan
   Copyright 2013 Martin Blanchard
-  Copyright Kungliga Tekniska Høgskolan & 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

commit 6240f672e215b0f76f9e36e1f6fb2c9369bbfc7f
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:08 2016 -0600

    raop: Prefer ALAC encoding to raw PCM if supported by server
    
    ALAC encoding is to be prefered simply because ALAC audio packet reverse-
    engineering and implementation is in better shape than raw PCM. Sending ALAC
    audio does not mean compressing audio and thus linking an external library to
    do so. ALAC packets has the ability to carry uncompressed PCM frames, and
    that's what is implemented at the time.

diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c
index 6e2542a..bf05dec 100644
--- a/src/modules/raop/module-raop-discover.c
+++ b/src/modules/raop/module-raop-discover.c
@@ -200,7 +200,10 @@ static void resolver_cb(
              *  - 1 = ALAC,
              *  - 2 = AAC,
              *  - 3 = AAC ELD. */
-            cn = pa_xstrdup("PCM");
+            if (pa_str_in_list(value, ",", "1"))
+                cn = pa_xstrdup("ALAC");
+            else
+                cn = pa_xstrdup("PCM");
         } else if (pa_streq(key, "md")) {
             /* Supported metadata types:
              *  - 0 = text,

commit fb6f2c639132d5b711d3d4b5f783ab2ed9b2655c
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:07 2016 -0600

    raop: Merge TCP and UDP code paths + refactoring
    
    TCP and UDP implementation are following two diffrent code path while code
    logic is quite the same. This patch merges both code path into a unique one
    and, thus, leads to a big refactoring. Major changes include:
      - moving sink implementation to a separate file (raop-sink.c)
      - move raop-sink.c protocol specific code to raop-client.c
      - modernise RTSP session handling in TCP mode
      - reduce code duplications between TCP and UDP modes
      - introduce authentication support
      - TCP mode does not constantly send silent audio anymore
    
    About authentication: OPTIONS is now issued when the sink is preliminary
    loaded. Client authentication appends at that time and credential is kept
    for the whole sink lifetime. Later RTSP connection will thus look like this:
    ANNOUNCE > 200 OK > SETUP > 200 OK > RECORD > 200 OK (no more OPTIONS). This
    behaviour is similar to iTunes one.
    
    Also this patch includes file name changes to match Pulseaudio naming
    rules, as most of pulseaudio source code files seem to be using '-'
    instead of '_' as a word separator.

diff --git a/src/Makefile.am b/src/Makefile.am
index 97a331b..8979787 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1153,10 +1153,12 @@ librtp_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
 librtp_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon- at PA_MAJORMINOR@.la libpulse.la
 
 libraop_la_SOURCES = \
-        modules/raop/raop_client.c modules/raop/raop_client.h \
-        modules/raop/raop_packet_buffer.h modules/raop/raop_packet_buffer.c \
-        modules/raop/raop_crypto.c modules/raop/raop_crypto.h \
-        modules/raop/raop_util.c modules/raop/raop_util.h
+        modules/raop/raop-util.c modules/raop/raop-util.h \
+        modules/raop/raop-crypto.c modules/raop/raop-crypto.h \
+        modules/raop/raop-packet-buffer.h modules/raop/raop-packet-buffer.c \
+        modules/raop/raop-client.c modules/raop/raop-client.h \
+        modules/raop/raop-sink.c modules/raop/raop-sink.h
+
 libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) -I$(top_srcdir)/src/modules/rtp
 libraop_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
 libraop_la_LIBADD = $(AM_LIBADD) $(OPENSSL_LIBS) libpulsecore- at PA_MAJORMINOR@.la librtp.la libpulsecommon- at PA_MAJORMINOR@.la libpulse.la
diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 564ef99..82fa48d 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -22,107 +22,34 @@
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <sys/ioctl.h>
-
-#ifdef HAVE_LINUX_SOCKIOS_H
-#include <linux/sockios.h>
-#endif
-
-#include <pulse/rtclock.h>
-#include <pulse/timeval.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/sink.h>
 #include <pulsecore/modargs.h>
-#include <pulsecore/log.h>
-#include <pulsecore/socket-client.h>
-#include <pulsecore/thread-mq.h>
-#include <pulsecore/thread.h>
-#include <pulsecore/time-smoother.h>
-#include <pulsecore/poll.h>
+
+#include "raop-sink.h"
 
 #include "module-raop-sink-symdef.h"
-#include "rtp.h"
-#include "sdp.h"
-#include "sap.h"
-#include "raop_client.h"
 
 PA_MODULE_AUTHOR("Colin Guthrie");
 PA_MODULE_DESCRIPTION("RAOP Sink");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
 PA_MODULE_USAGE(
+        "name=<name of the sink, to be prefixed> "
         "sink_name=<name for the sink> "
         "sink_properties=<properties for the sink> "
-        "server=<address>  "
+        "server=<address> "
         "protocol=<transport protocol> "
         "encryption=<encryption type> "
         "codec=<audio codec> "
         "format=<sample format> "
         "rate=<sample rate> "
-        "channels=<number of channels>");
-
-struct userdata {
-    pa_core *core;
-    pa_module *module;
-    pa_sink *sink;
-
-    pa_thread_mq thread_mq;
-    pa_rtpoll *rtpoll;
-    pa_rtpoll_item *rtpoll_item;
-    pa_thread *thread;
-
-    pa_raop_protocol_t protocol;
-
-    pa_memchunk raw_memchunk;
-    pa_memchunk encoded_memchunk;
-
-    void *write_data;
-    size_t write_length, write_index;
-
-    void *read_data;
-    size_t read_length, read_index;
-
-    pa_usec_t latency;
-
-    /*esd_format_t format;*/
-    int32_t rate;
-
-    pa_smoother *smoother;
-
-    int64_t offset;
-    int64_t encoding_overhead;
-    int32_t next_encoding_overhead;
-    double encoding_ratio;
-
-    pa_raop_client *raop;
-
-    size_t block_size;
-
-    /* Members only for the TCP protocol */
-    int tcp_fd;
-
-    /* Members only for the UDP protocol */
-    int udp_control_fd;
-    int udp_timing_fd;
-
-    /* For UDP thread wakeup clock calculation */
-    pa_usec_t udp_playback_start;
-    uint32_t  udp_sent_packets;
-};
+        "channels=<number of channels> "
+        "username=<authentication user name, default: \"iTunes\"> "
+        "password=<authentication password>");
 
 static const char* const valid_modargs[] = {
+    "name",
     "sink_name",
     "sink_properties",
     "server",
@@ -132,859 +59,29 @@ static const char* const valid_modargs[] = {
     "format",
     "rate",
     "channels",
+    "username",
+    "password",
     NULL
 };
 
-enum {
-    SINK_MESSAGE_TCP_PASS_SOCKET = PA_SINK_MESSAGE_MAX,
-    SINK_MESSAGE_TCP_RIP_SOCKET,
-    SINK_MESSAGE_UDP_SETUP,
-    SINK_MESSAGE_UDP_RECORD,
-    SINK_MESSAGE_UDP_DISCONNECTED,
-};
-
-/* Forward declarations: */
-static void sink_set_volume_cb(pa_sink *);
-
-static void tcp_on_connection(int fd, void *userdata) {
-    int so_sndbuf = 0;
-    socklen_t sl = sizeof(int);
-    struct userdata *u = userdata;
-    pa_assert(u);
-
-    pa_assert(u->tcp_fd < 0);
-    u->tcp_fd = fd;
-
-    if (getsockopt(u->tcp_fd, SOL_SOCKET, SO_SNDBUF, &so_sndbuf, &sl) < 0)
-        pa_log_warn("getsockopt(SO_SNDBUF) failed: %s", pa_cstrerror(errno));
-    else {
-        pa_log_debug("SO_SNDBUF is %zu.", (size_t) so_sndbuf);
-        pa_sink_set_max_request(u->sink, PA_MAX((size_t) so_sndbuf, u->block_size));
-    }
-
-    /* Set the initial volume. */
-    sink_set_volume_cb(u->sink);
-
-    pa_log_debug("Connection authenticated, handing fd to IO thread...");
-
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_TCP_PASS_SOCKET, NULL, 0, NULL, NULL);
-}
-
-static void tcp_on_close(void*userdata) {
-    struct userdata *u = userdata;
-    pa_assert(u);
-
-    pa_log_debug("Connection closed, informing IO thread...");
-
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_TCP_RIP_SOCKET, NULL, 0, NULL, NULL);
-}
-
-static pa_usec_t sink_get_latency(const struct userdata *u) {
-    pa_usec_t w, r;
-
-    r = pa_smoother_get(u->smoother, pa_rtclock_now());
-    w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec);
-
-    return w > r ? w - r : 0;
-}
-
-static int tcp_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_SET_STATE:
-
-            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
-
-                case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
-
-                    pa_smoother_pause(u->smoother, pa_rtclock_now());
-
-                    /* Issue a FLUSH if we are connected. */
-                    if (u->tcp_fd >= 0) {
-                        pa_raop_client_flush(u->raop);
-                    }
-                    break;
-
-                case PA_SINK_IDLE:
-                case PA_SINK_RUNNING:
-
-                    if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
-                        pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
-
-                        /* The connection can be closed when idle, so check to
-                         * see if we need to reestablish it. */
-                        if (u->tcp_fd < 0)
-                            pa_raop_client_connect(u->raop);
-                        else
-                            pa_raop_client_flush(u->raop);
-                    }
-
-                    break;
-
-                case PA_SINK_UNLINKED:
-                case PA_SINK_INIT:
-                case PA_SINK_INVALID_STATE:
-                    ;
-            }
-
-            break;
-
-        case PA_SINK_MESSAGE_GET_LATENCY: {
-            *((pa_usec_t*) data) = sink_get_latency(u);
-            return 0;
-        }
-
-        case SINK_MESSAGE_TCP_PASS_SOCKET: {
-            struct pollfd *pollfd;
-
-            pa_assert(!u->rtpoll_item);
-
-            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->tcp_fd;
-            pollfd->events = POLLOUT;
-            /*pollfd->events = */pollfd->revents = 0;
-
-            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
-                /* Our stream has been suspended so we just flush it... */
-                pa_raop_client_flush(u->raop);
-            }
-            return 0;
-        }
-
-        case SINK_MESSAGE_TCP_RIP_SOCKET: {
-            if (u->tcp_fd >= 0) {
-                pa_close(u->tcp_fd);
-                u->tcp_fd = -1;
-            } else
-                /* FIXME */
-                pa_log("We should not get to this state. Cannot rip socket if not connected.");
-
-            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
-
-                pa_log_debug("RTSP control connection closed, but we're suspended so let's not worry about it... we'll open it again later");
-
-                if (u->rtpoll_item)
-                    pa_rtpoll_item_free(u->rtpoll_item);
-                u->rtpoll_item = NULL;
-            } else {
-                /* Question: is this valid here: or should we do some sort of:
-                   return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL);
-                   ?? */
-                pa_module_unload_request(u->module, true);
-            }
-            return 0;
-        }
-    }
-
-    return pa_sink_process_msg(o, code, data, offset, chunk);
-}
-
-static void udp_start_wakeup_clock(struct userdata *u) {
-    pa_usec_t now = pa_rtclock_now();
-
-    u->udp_playback_start = now;
-    u->udp_sent_packets = 0;
-    pa_rtpoll_set_timer_absolute(u->rtpoll, now);
-}
-
-static pa_usec_t udp_next_wakeup_clock(struct userdata *u) {
-    pa_usec_t intvl = pa_bytes_to_usec(u->block_size * u->udp_sent_packets,
-                                       &u->sink->sample_spec);
-    /* FIXME: how long until (u->block_size * u->udp_sent_packets) wraps?? */
-
-    return u->udp_playback_start + intvl;
-}
-
-static int udp_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_SET_STATE:
-            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
-                case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
-                    pa_log_debug("RAOP: SUSPENDED");
-                    pa_smoother_pause(u->smoother, pa_rtclock_now());
-
-                    if (pa_raop_client_udp_is_alive(u->raop)) {
-                        /* Issue a TEARDOWN if we are still connected. */
-                        pa_raop_client_teardown(u->raop);
-                    }
-
-                    break;
-
-                case PA_SINK_IDLE:
-                    pa_log_debug("RAOP: IDLE");
-                    /* Issue a flush if we're comming from running state. */
-                    if (u->sink->thread_info.state == PA_SINK_RUNNING) {
-                        pa_rtpoll_set_timer_disabled(u->rtpoll);
-                        pa_raop_client_flush(u->raop);
-                    }
-
-                    break;
-
-                case PA_SINK_RUNNING:
-                    pa_log_debug("RAOP: RUNNING");
-
-                    pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
-
-                    if (!pa_raop_client_udp_is_alive(u->raop)) {
-                        /* Connecting will trigger a RECORD and start steaming */
-                        pa_raop_client_connect(u->raop);
-                    } else if (!pa_raop_client_udp_can_stream(u->raop)) {
-                        /* RECORD alredy sent, simply start streaming */
-                        pa_raop_client_udp_stream(u->raop);
-                    }
-
-                    udp_start_wakeup_clock(u);
-
-                    break;
-
-                case PA_SINK_UNLINKED:
-                case PA_SINK_INIT:
-                case PA_SINK_INVALID_STATE:
-                    ;
-            }
-
-            break;
-
-        case PA_SINK_MESSAGE_GET_LATENCY: {
-            pa_usec_t r = 0;
-
-            if (pa_raop_client_udp_can_stream(u->raop))
-                r = sink_get_latency(u);
-
-            *((pa_usec_t*) data) = r;
-
-            return 0;
-        }
-
-        case SINK_MESSAGE_UDP_SETUP: {
-            struct pollfd *pollfd;
-
-            u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 2);
-            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
-
-            pollfd->fd = u->udp_control_fd;
-            pollfd->events = POLLIN | POLLPRI;
-            pollfd->revents = 0;
-            pollfd++;
-            pollfd->fd = u->udp_timing_fd;
-            pollfd->events = POLLIN | POLLPRI;
-            pollfd->revents = 0;
-
-            return 0;
-        }
-
-        case SINK_MESSAGE_UDP_RECORD: {
-            udp_start_wakeup_clock(u);
-
-            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
-                /* Our stream has been suspended so we just flush it... */
-                pa_rtpoll_set_timer_disabled(u->rtpoll);
-                pa_raop_client_flush(u->raop);
-            }
-
-            return 0;
-        }
-
-        case SINK_MESSAGE_UDP_DISCONNECTED: {
-            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
-                pa_rtpoll_set_timer_disabled(u->rtpoll);
-                if (u->rtpoll_item)
-                    pa_rtpoll_item_free(u->rtpoll_item);
-                u->rtpoll_item = NULL;
-            } else {
-                /* Question: is this valid here: or should we do some sort of:
-                 * return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL); ?? */
-                pa_module_unload_request(u->module, true);
-            }
-
-            pa_close(u->udp_control_fd);
-            pa_close(u->udp_timing_fd);
-
-            u->udp_control_fd = -1;
-            u->udp_timing_fd = -1;
-
-            return 0;
-        }
-    }
-
-    return pa_sink_process_msg(o, code, data, offset, chunk);
-}
-
-static void sink_set_volume_cb(pa_sink *s) {
-    struct userdata *u = s->userdata;
-    pa_cvolume hw;
-    pa_volume_t v, v_orig;
-    char t[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
-
-    pa_assert(u);
-
-    /* If we're muted we don't need to do anything. */
-    if (s->muted)
-        return;
-
-    /* Calculate the max volume of all channels.
-     * We'll use this as our (single) volume on the APEX device and emulate
-     * any variation in channel volumes in software. */
-    v = pa_cvolume_max(&s->real_volume);
-
-    v_orig = v;
-    v = pa_raop_client_adjust_volume(u->raop, v_orig);
-
-    pa_log_debug("Volume adjusted: orig=%u adjusted=%u", v_orig, v);
-
-    /* Create a pa_cvolume version of our single value. */
-    pa_cvolume_set(&hw, s->sample_spec.channels, v);
-
-    /* Set the real volume based on given original volume. */
-    pa_cvolume_set(&s->real_volume, s->sample_spec.channels, v_orig);
-
-    pa_log_debug("Requested volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &s->real_volume, &s->channel_map, false));
-    pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &hw, &s->channel_map, false));
-    pa_log_debug("Calculated software volume: %s",
-                 pa_cvolume_snprint_verbose(t, sizeof(t), &s->soft_volume, &s->channel_map, true));
-
-    /* Any necessary software volume manipulation is done so set
-     * our hw volume (or v as a single value) on the device. */
-    pa_raop_client_set_volume(u->raop, v);
-}
-
-static void sink_set_mute_cb(pa_sink *s) {
-    struct userdata *u = s->userdata;
-
-    pa_assert(u);
-
-    if (s->muted) {
-        pa_raop_client_set_volume(u->raop, PA_VOLUME_MUTED);
-    } else {
-        sink_set_volume_cb(s);
-    }
-}
-
-static void udp_setup_cb(int control_fd, int timing_fd, void *userdata) {
-    struct userdata *u = userdata;
-
-    pa_assert(control_fd);
-    pa_assert(timing_fd);
-    pa_assert(u);
-
-    u->udp_control_fd = control_fd;
-    u->udp_timing_fd = timing_fd;
-
-    pa_log_debug("Connection authenticated, syncing with server...");
-
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_SETUP, NULL, 0, NULL, NULL);
-}
-
-static void udp_record_cb(void *userdata) {
-    struct userdata *u = userdata;
-
-    pa_assert(u);
-
-    /* Set the initial volume. */
-    sink_set_volume_cb(u->sink);
-
-    pa_log_debug("Synchronization done, pushing job to IO thread...");
-
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_RECORD, NULL, 0, NULL, NULL);
-}
-
-static void udp_disconnected_cb(void *userdata) {
-    struct userdata *u = userdata;
-
-    pa_assert(u);
-
-    /* This callback function is called from both STATE_TEARDOWN and
-       STATE_DISCONNECTED in raop_client.c */
-
-    pa_assert(u);
-
-    pa_log_debug("Connection closed, informing IO thread...");
-
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_DISCONNECTED, NULL, 0, NULL, NULL);
-}
-
-static void tcp_thread_func(struct userdata *u) {
-    int write_type = 0;
-    pa_memchunk silence;
-    uint32_t silence_overhead = 0;
-    double silence_ratio = 0;
-
-    pa_assert(u);
-
-    pa_log_debug("TCP thread starting up");
-
-    pa_thread_mq_install(&u->thread_mq);
-
-    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
-
-    /* Create a chunk of memory that is our encoded silence sample. */
-    pa_memchunk_reset(&silence);
-
-    for (;;) {
-        int ret;
-
-        if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
-            pa_sink_process_rewind(u->sink, 0);
-
-        if (u->rtpoll_item) {
-            struct pollfd *pollfd;
-            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
-
-            /* Render some data and write it to the fifo. */
-            if (/*PA_SINK_IS_OPENED(u->sink->thread_info.state) && */pollfd->revents) {
-                pa_usec_t usec;
-                int64_t n;
-                void *p;
-
-                if (!silence.memblock) {
-                    pa_memchunk silence_tmp;
-
-                    pa_memchunk_reset(&silence_tmp);
-                    silence_tmp.memblock = pa_memblock_new(u->core->mempool, 4096);
-                    silence_tmp.length = 4096;
-                    p = pa_memblock_acquire(silence_tmp.memblock);
-                      memset(p, 0, 4096);
-                    pa_memblock_release(silence_tmp.memblock);
-                    pa_raop_client_encode_sample(u->raop, &silence_tmp, &silence);
-                    pa_assert(0 == silence_tmp.length);
-                    silence_overhead = silence_tmp.length - 4096;
-                    silence_ratio = silence_tmp.length / 4096;
-                    pa_memblock_unref(silence_tmp.memblock);
-                }
-
-                for (;;) {
-                    ssize_t l;
-
-                    if (u->encoded_memchunk.length <= 0) {
-                        if (u->encoded_memchunk.memblock)
-                            pa_memblock_unref(u->encoded_memchunk.memblock);
-                        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
-                            size_t rl;
-
-                            /* We render real data. */
-                            if (u->raw_memchunk.length <= 0) {
-                                if (u->raw_memchunk.memblock)
-                                    pa_memblock_unref(u->raw_memchunk.memblock);
-                                pa_memchunk_reset(&u->raw_memchunk);
-
-                                /* Grab unencoded data. */
-                                pa_sink_render(u->sink, u->block_size, &u->raw_memchunk);
-                            }
-                            pa_assert(u->raw_memchunk.length > 0);
-
-                            /* Encode it. */
-                            rl = u->raw_memchunk.length;
-                            u->encoding_overhead += u->next_encoding_overhead;
-                            pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk);
-                            u->next_encoding_overhead = (u->encoded_memchunk.length - (rl - u->raw_memchunk.length));
-                            u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length);
-                        } else {
-                            /* We render some silence into our memchunk. */
-                            memcpy(&u->encoded_memchunk, &silence, sizeof(pa_memchunk));
-                            pa_memblock_ref(silence.memblock);
-
-                            /* Calculate/store some values to be used with the smoother. */
-                            u->next_encoding_overhead = silence_overhead;
-                            u->encoding_ratio = silence_ratio;
-                        }
-                    }
-                    pa_assert(u->encoded_memchunk.length > 0);
-
-                    p = pa_memblock_acquire(u->encoded_memchunk.memblock);
-                    l = pa_write(u->tcp_fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type);
-                    pa_memblock_release(u->encoded_memchunk.memblock);
-
-                    pa_assert(l != 0);
-
-                    if (l < 0) {
-
-                        if (errno == EINTR)
-                            continue;
-                        else if (errno == EAGAIN) {
-
-                            /* OK, we filled all socket buffers up now. */
-                            goto filled_up;
-
-                        } else {
-                            pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
-                            goto fail;
-                        }
-
-                    } else {
-                        u->offset += l;
-
-                        u->encoded_memchunk.index += l;
-                        u->encoded_memchunk.length -= l;
-
-                        pollfd->revents = 0;
-
-                        if (u->encoded_memchunk.length > 0) {
-                            /* We've completely written the encoded data, so update our overhead. */
-                            u->encoding_overhead += u->next_encoding_overhead;
-
-                            /* OK, we wrote less that we asked for,
-                             * hence we can assume that the socket
-                             * buffers are full now. */
-                            goto filled_up;
-                        }
-                    }
-                }
-
-            filled_up:
-                /* At this spot we know that the socket buffers are
-                 * fully filled up. This is the best time to estimate
-                 * the playback position of the server. */
-
-                n = u->offset - u->encoding_overhead;
-
-#ifdef SIOCOUTQ
-                {
-                    int l;
-                    if (ioctl(u->tcp_fd, SIOCOUTQ, &l) >= 0 && l > 0)
-                        n -= (l / u->encoding_ratio);
-                }
-#endif
-
-                usec = pa_bytes_to_usec(n, &u->sink->sample_spec);
-
-                if (usec > u->latency)
-                    usec -= u->latency;
-                else
-                    usec = 0;
-
-                pa_smoother_put(u->smoother, pa_rtclock_now(), usec);
-            }
-
-            /* Hmm, nothing to do. Let's sleep... */
-            pollfd->events = POLLOUT; /*PA_SINK_IS_OPENED(u->sink->thread_info.state)  ? POLLOUT : 0;*/
-        }
-
-        if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
-            goto fail;
-
-        if (ret == 0)
-            goto finish;
-
-        if (u->rtpoll_item) {
-            struct pollfd* pollfd;
-
-            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
-
-            if (pollfd->revents & ~POLLOUT) {
-                if (u->sink->thread_info.state != PA_SINK_SUSPENDED) {
-                    pa_log("FIFO shutdown.");
-                    goto fail;
-                }
-
-                /* We expect this to happen on occasion if we are not sending data.
-                 * It's perfectly natural and normal and natural. */
-                if (u->rtpoll_item)
-                    pa_rtpoll_item_free(u->rtpoll_item);
-                u->rtpoll_item = NULL;
-            }
-        }
-    }
-
-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:
-    if (silence.memblock)
-        pa_memblock_unref(silence.memblock);
-    pa_log_debug("TCP thread shutting down");
-}
-
-static void udp_thread_func(struct userdata *u) {
-    pa_assert(u);
-
-    pa_log_debug("UDP thread starting up");
-
-    pa_thread_mq_install(&u->thread_mq);
-    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
-
-    for (;;) {
-        pa_usec_t estimated;
-        int32_t overhead = 0;
-        ssize_t written = 0;
-        size_t length = 0;
-        int rv = 0;
-
-        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
-            if (u->sink->thread_info.rewind_requested)
-                pa_sink_process_rewind(u->sink, 0);
-        }
-
-        /* Polling (audio data + control socket + timing socket). */
-        if ((rv = pa_rtpoll_run(u->rtpoll)) < 0)
-            goto fail;
-        else if (rv == 0)
-            goto finish;
-
-        if (!pa_rtpoll_timer_elapsed(u->rtpoll)) {
-            struct pollfd *pollfd;
-            uint8_t packet[32];
-            ssize_t read;
-
-            if (u->rtpoll_item) {
-                pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
-
-                /* Event on the control socket ?? */
-                if (pollfd->revents & POLLIN) {
-                    pollfd->revents = 0;
-                    pa_log_debug("Received control packet.");
-                    read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
-                    pa_raop_client_udp_handle_control_packet(u->raop, packet, read);
-                }
-
-                pollfd++;
-
-                /* Event on the timing port ?? */
-                if (pollfd->revents & POLLIN) {
-                    pollfd->revents = 0;
-                    pa_log_debug("Received timing packet.");
-                    read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
-                    pa_raop_client_udp_handle_timing_packet(u->raop, packet, read);
-                }
-            }
-
-            continue;
-        }
-
-        if (u->sink->thread_info.state != PA_SINK_RUNNING)
-            continue;
-        if (!pa_raop_client_udp_can_stream(u->raop))
-            continue;
-
-        if (u->encoded_memchunk.length <= 0) {
-            if (u->encoded_memchunk.memblock != NULL)
-                pa_memblock_unref(u->encoded_memchunk.memblock);
-
-            if (u->raw_memchunk.length <= 0) {
-                if (u->raw_memchunk.memblock)
-                    pa_memblock_unref(u->raw_memchunk.memblock);
-                pa_memchunk_reset(&u->raw_memchunk);
-
-                /* Grab unencoded audio data from PulseAudio. */
-                pa_sink_render_full(u->sink, u->block_size, &u->raw_memchunk);
-            }
-
-            pa_assert(u->raw_memchunk.length > 0);
-
-            length = u->raw_memchunk.length;
-            pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk);
-            u->encoding_ratio = (double) u->encoded_memchunk.length / (double) (length - u->raw_memchunk.length);
-            overhead = u->encoded_memchunk.length - (length - u->raw_memchunk.length);
-        }
-
-        pa_assert(u->encoded_memchunk.length > 0);
-
-        written = pa_raop_client_udp_send_audio_packet(u->raop,&u->encoded_memchunk);
-        if (written < 0) {
-            pa_log("Failed to send UDP packet: %s", pa_cstrerror(errno));
-            goto fail;
-        }
-
-        u->udp_sent_packets++;
-        /* Sleep until next packet transmission */
-        pa_rtpoll_set_timer_absolute(u->rtpoll, udp_next_wakeup_clock(u));
-
-        u->offset += written;
-        u->encoding_overhead += overhead;
-
-        estimated = pa_bytes_to_usec(u->offset - u->encoding_overhead, &u->sink->sample_spec);
-        pa_smoother_put(u->smoother, pa_rtclock_now(), estimated);
-    }
-
-fail:
-    /* If this was no regular exit, continue processing messages until 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("UDP thread shutting down");
-}
-
-static void thread_func(void *userdata) {
-    struct userdata *u = userdata;
-
-    if (u->protocol == RAOP_TCP)
-        tcp_thread_func(u);
-    else if (u->protocol == RAOP_UDP)
-        udp_thread_func(u);
-    else
-        pa_assert(false);
-
-    return;
-}
-
 int pa__init(pa_module *m) {
-    struct userdata *u = NULL;
-    pa_sample_spec ss;
     pa_modargs *ma = NULL;
-    const char *server, *protocol, *encryption;
-    pa_sink_new_data data;
-    char *t = NULL;
 
     pa_assert(m);
 
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-        pa_log("failed to parse module arguments");
+        pa_log("Failed to parse module arguments");
         goto fail;
     }
 
-    ss.format = PA_SAMPLE_S16NE;
-    ss.channels = 2;
-    ss.rate = m->core->default_sample_spec.rate;
-    if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
-        pa_log("invalid sample format specification");
+    if (!(m->userdata = pa_raop_sink_new(m, ma, __FILE__)))
         goto fail;
-    }
-
-    if ((ss.format != PA_SAMPLE_S16NE) ||
-        (ss.channels > 2)) {
-        pa_log("sample type support is limited to mono/stereo and S16NE sample data");
-        goto fail;
-    }
-
-    u = pa_xnew0(struct userdata, 1);
-    u->core = m->core;
-    u->module = m;
-    m->userdata = u;
-    u->tcp_fd = -1;
-    u->smoother = pa_smoother_new(
-            PA_USEC_PER_SEC,
-            PA_USEC_PER_SEC*2,
-            true,
-            true,
-            10,
-            0,
-            false);
-    pa_memchunk_reset(&u->raw_memchunk);
-    pa_memchunk_reset(&u->encoded_memchunk);
-    u->offset = 0;
-    u->encoding_overhead = 0;
-    u->next_encoding_overhead = 0;
-    u->encoding_ratio = 1.0;
-
-    u->rtpoll = pa_rtpoll_new();
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
-    u->rtpoll_item = NULL;
-
-    /*u->format =
-        (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) |
-        (ss.channels == 2 ? ESD_STEREO : ESD_MONO);*/
-    u->rate = ss.rate;
-    u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss);
-
-    u->read_data = u->write_data = NULL;
-    u->read_index = u->write_index = u->read_length = u->write_length = 0;
-
-    /*u->state = STATE_AUTH;*/
-    u->latency = 0;
-
-    if (!(server = pa_modargs_get_value(ma, "server", NULL))) {
-        pa_log("No server argument given.");
-        goto fail;
-    }
-
-    /* This may be overwriten if sink_name is specified in module arguments. */
-    t = pa_sprintf_malloc("raop_client.%s", server);
-
-    protocol = pa_modargs_get_value(ma, "protocol", NULL);
-    if (protocol == NULL || pa_streq(protocol, "TCP")) {
-        /* Assume TCP by default */
-        u->protocol = RAOP_TCP;
-    }
-    else if (pa_streq(protocol, "UDP")) {
-        u->protocol = RAOP_UDP;
-    } else {
-        pa_log("Unsupported protocol argument given: %s", protocol);
-        goto fail;
-    }
-
-    pa_sink_new_data_init(&data);
-    data.driver = __FILE__;
-    data.module = m;
-    pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", t));
-    pa_sink_new_data_set_sample_spec(&data, &ss);
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server);
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music");
-    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server);
-
-    /* RAOP discover module will eventually overwrite sink_name and others
-       (PA_UPDATE_REPLACE). */
-    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
-        pa_log("Invalid properties");
-        pa_sink_new_data_done(&data);
-        goto fail;
-    }
-
-    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK);
-    pa_xfree(t); t = NULL;
-    pa_sink_new_data_done(&data);
-
-    if (!u->sink) {
-        pa_log("Failed to create sink.");
-        goto fail;
-    }
-
-    if (u->protocol == RAOP_TCP)
-        u->sink->parent.process_msg = tcp_sink_process_msg;
-    else
-        u->sink->parent.process_msg = udp_sink_process_msg;
-    u->sink->userdata = u;
-    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
-    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
-    u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK;
-
-    pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
-    pa_sink_set_rtpoll(u->sink, u->rtpoll);
-
-    if (!(u->raop = pa_raop_client_new(u->core, server, u->protocol))) {
-        pa_log("Failed to connect to server.");
-        goto fail;
-    }
-
-    encryption = pa_modargs_get_value(ma, "encryption", NULL);
-    pa_raop_client_set_encryption(u->raop, !pa_safe_streq(encryption, "none"));
-
-    pa_raop_client_tcp_set_callback(u->raop, tcp_on_connection, u);
-    pa_raop_client_tcp_set_closed_callback(u->raop, tcp_on_close, u);
-
-    if (u->protocol == RAOP_UDP) {
-        /* The number of frames per blocks is not negotiable... */
-        pa_raop_client_udp_get_blocks_size(u->raop, &u->block_size);
-        u->block_size *= pa_frame_size(&ss);
-        pa_sink_set_max_request(u->sink, u->block_size);
-
-        pa_raop_client_udp_set_setup_callback(u->raop, udp_setup_cb, u);
-        pa_raop_client_udp_set_record_callback(u->raop, udp_record_cb, u);
-        pa_raop_client_udp_set_disconnected_callback(u->raop, udp_disconnected_cb, u);
-    }
-
-    if (!(u->thread = pa_thread_new("raop-sink", thread_func, u))) {
-        pa_log("Failed to create thread.");
-        goto fail;
-    }
-
-    pa_sink_put(u->sink);
 
     pa_modargs_free(ma);
 
     return 0;
 
 fail:
-    pa_xfree(t);
 
     if (ma)
         pa_modargs_free(ma);
@@ -995,57 +92,19 @@ fail:
 }
 
 int pa__get_n_used(pa_module *m) {
-    struct userdata *u;
+    pa_sink *sink;
 
     pa_assert(m);
-    pa_assert_se(u = m->userdata);
+    pa_assert_se(sink = m->userdata);
 
-    return pa_sink_linked_by(u->sink);
+    return pa_sink_linked_by(sink);
 }
 
 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->rtpoll_item)
-        pa_rtpoll_item_free(u->rtpoll_item);
-
-    if (u->rtpoll)
-        pa_rtpoll_free(u->rtpoll);
-
-    if (u->raw_memchunk.memblock)
-        pa_memblock_unref(u->raw_memchunk.memblock);
-
-    if (u->encoded_memchunk.memblock)
-        pa_memblock_unref(u->encoded_memchunk.memblock);
-
-    if (u->raop)
-        pa_raop_client_free(u->raop);
-
-    pa_xfree(u->read_data);
-    pa_xfree(u->write_data);
-
-    if (u->smoother)
-        pa_smoother_free(u->smoother);
+    pa_sink *sink;
 
-    if (u->tcp_fd >= 0)
-        pa_close(u->tcp_fd);
+    pa_assert(m);
 
-    pa_xfree(u);
+    if ((sink = m->userdata))
+        pa_raop_sink_free(sink);
 }
diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c
new file mode 100644
index 0000000..813b161
--- /dev/null
+++ b/src/modules/raop/raop-client.c
@@ -0,0 +1,1732 @@
+/***
+  This file is part of PulseAudio.
+
+  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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <math.h>
+
+#ifdef HAVE_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/sample.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/arpa-inet.h>
+#include <pulsecore/socket-client.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/parseaddr.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/random.h>
+#include <pulsecore/poll.h>
+
+#include "raop-client.h"
+#include "raop-packet-buffer.h"
+#include "raop-crypto.h"
+#include "raop-util.h"
+
+#include "rtsp_client.h"
+
+#define DEFAULT_RAOP_PORT 5000
+
+#define FRAMES_PER_TCP_PACKET 4096
+#define FRAMES_PER_UDP_PACKET 352
+
+#define DEFAULT_TCP_AUDIO_PORT   6000
+#define DEFAULT_UDP_AUDIO_PORT   6000
+#define DEFAULT_UDP_CONTROL_PORT 6001
+#define DEFAULT_UDP_TIMING_PORT  6002
+
+#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
+#define DEFAULT_USER_NAME  "iTunes"
+
+#define JACK_STATUS_DISCONNECTED 0
+#define JACK_STATUS_CONNECTED    1
+#define JACK_TYPE_ANALOG         0
+#define JACK_TYPE_DIGITAL        1
+
+#define VOLUME_MAX  0.0
+#define VOLUME_DEF -30.0
+#define VOLUME_MIN -144.0
+
+#define UDP_DEFAULT_PKT_BUF_SIZE 1000
+
+struct pa_raop_client {
+    pa_core *core;
+    char *host;
+    uint16_t port;
+    pa_rtsp_client *rtsp;
+    char *sci, *sid;
+    char *password;
+
+    pa_raop_protocol_t protocol;
+    pa_raop_encryption_t encryption;
+    pa_raop_codec_t codec;
+
+    pa_raop_secret *secret;
+
+    int tcp_sfd;
+
+    int udp_sfd;
+    int udp_cfd;
+    int udp_tfd;
+
+    pa_raop_packet_buffer *pbuf;
+
+    uint16_t seq;
+    uint32_t rtptime;
+    bool is_recording;
+    uint32_t ssrc;
+
+    bool is_first_packet;
+    uint32_t sync_interval;
+    uint32_t sync_count;
+
+    uint8_t jack_type;
+    uint8_t jack_status;
+
+    pa_raop_client_state_cb_t state_callback;
+    void *state_userdata;
+};
+
+/* Audio TCP packet header [16x8] (cf. rfc4571):
+ *  [0,1]   Frame marker; seems always 0x2400
+ *  [2,3]   RTP packet size (following): 0x0000 (to be set)
+ *   [4,5]   RTP v2: 0x80
+ *   [5]     Payload type: 0x60 | Marker bit: 0x80 (always set)
+ *   [6,7]   Sequence number: 0x0000 (to be set)
+ *   [8,11]  Timestamp: 0x00000000 (to be set)
+ *   [12,15] SSRC: 0x00000000 (to be set) */
+#define PAYLOAD_TCP_AUDIO_DATA 0x60
+static const uint8_t tcp_audio_header[16] = {
+    0x24, 0x00, 0x00, 0x00,
+    0x80, 0xe0, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
+};
+
+/* Audio UDP packet header [12x8] (cf. rfc3550):
+ *  [0]    RTP v2: 0x80
+ *  [1]    Payload type: 0x60
+ *  [2,3]  Sequence number: 0x0000 (to be set)
+ *  [4,7]  Timestamp: 0x00000000 (to be set)
+ *  [8,12] SSRC: 0x00000000 (to be set) */
+#define PAYLOAD_UDP_AUDIO_DATA 0x60
+static const uint8_t udp_audio_header[12] = {
+    0x80, 0x60, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
+};
+
+/* Audio retransmission UDP packet header [4x8]:
+ *  [0] RTP v2: 0x80
+ *  [1] Payload type: 0x56 | Marker bit: 0x80 (always set)
+ *  [2] Unknown; seems always 0x01
+ *  [3] Unknown; seems some random number around 0x20~0x40 */
+#define PAYLOAD_RETRANSMIT_REQUEST 0x55
+#define PAYLOAD_RETRANSMIT_REPLY   0x56
+static const uint8_t udp_audio_retrans_header[4] = {
+    0x80, 0xd6, 0x00, 0x00
+};
+
+/* Sync packet header [8x8] (cf. rfc3550):
+ *  [0]   RTP v2: 0x80
+ *  [1]   Payload type: 0x54 | Marker bit: 0x80 (always set)
+ *  [2,3] Sequence number: 0x0007
+ *  [4,7] Timestamp: 0x00000000 (to be set) */
+static const uint8_t udp_sync_header[8] = {
+    0x80, 0xd4, 0x00, 0x07,
+    0x00, 0x00, 0x00, 0x00
+};
+
+/* Timming packet header [8x8] (cf. rfc3550):
+ *  [0]   RTP v2: 0x80
+ *  [1]   Payload type: 0x53 | Marker bit: 0x80 (always set)
+ *  [2,3] Sequence number: 0x0007
+ *  [4,7] Timestamp: 0x00000000 (unused) */
+#define PAYLOAD_TIMING_REQUEST  0x52
+#define PAYLOAD_TIMING_REPLY    0x53
+static const uint8_t udp_timming_header[8] = {
+    0x80, 0xd3, 0x00, 0x07,
+    0x00, 0x00, 0x00, 0x00
+};
+
+/**
+ * Function to trim a given character at the end of a string (no realloc).
+ * @param str Pointer to string
+ * @param rc Character to trim
+ */
+static inline void rtrim_char(char *str, char rc) {
+    char *sp = str + strlen(str) - 1;
+    while (sp >= str && *sp == rc) {
+        *sp = '\0';
+        sp -= 1;
+    }
+}
+
+/**
+ * Function to convert a timeval to ntp timestamp.
+ * @param tv Pointer to the timeval structure
+ * @return The NTP timestamp
+ */
+static inline uint64_t timeval_to_ntp(struct timeval *tv) {
+    uint64_t ntp = 0;
+
+    /* Converting micro seconds to a fraction. */
+    ntp = (uint64_t) tv->tv_usec * UINT32_MAX / PA_USEC_PER_SEC;
+    /* Moving reference from  1 Jan 1970 to 1 Jan 1900 (seconds). */
+    ntp |= (uint64_t) (tv->tv_sec + 0x83aa7e80) << 32;
+
+    return ntp;
+}
+
+/**
+ * Function to write bits into a buffer.
+ * @param buffer Handle to the buffer. It will be incremented if new data requires it.
+ * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB)
+ * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks
+ * @param data The data to write
+ * @param data_bit_len The number of bits from data to write
+ */
+static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, size_t *size, uint8_t data, uint8_t data_bit_len) {
+    int bits_left, bit_overflow;
+    uint8_t bit_data;
+
+    if (!data_bit_len)
+        return;
+
+    /* If bit pos is zero, we will definately use at least one bit from the current byte so size increments. */
+    if (!*bit_pos)
+        *size += 1;
+
+    /* Calc the number of bits left in the current byte of buffer. */
+    bits_left = 7 - *bit_pos  + 1;
+    /* Calc the overflow of bits in relation to how much space we have left... */
+    bit_overflow = bits_left - data_bit_len;
+    if (bit_overflow >= 0) {
+        /* We can fit the new data in our current byte.
+         * As we write from MSB->LSB we need to left shift by the overflow amount. */
+        bit_data = data << bit_overflow;
+        if (*bit_pos)
+            **buffer |= bit_data;
+        else
+            **buffer = bit_data;
+        /* If our data fits exactly into the current byte, we need to increment our pointer. */
+        if (0 == bit_overflow) {
+            /* Do not increment size as it will be incremented on next call as bit_pos is zero. */
+            *buffer += 1;
+            *bit_pos = 0;
+        } else {
+            *bit_pos += data_bit_len;
+        }
+    } else {
+        /* bit_overflow is negative, there for we will need a new byte from our buffer
+         * Firstly fill up what's left in the current byte. */
+        bit_data = data >> -bit_overflow;
+        **buffer |= bit_data;
+        /* Increment our buffer pointer and size counter. */
+        *buffer += 1;
+        *size += 1;
+        **buffer = data << (8 + bit_overflow);
+        *bit_pos = -bit_overflow;
+    }
+}
+
+static size_t write_PCM_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length) {
+    size_t size = 0;
+
+    pa_memzero(packet, max);
+
+    pa_log("Raw PCM not implemented...");
+
+    return size;
+}
+
+static size_t write_ALAC_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length, bool compress) {
+    uint32_t nbs = (*length / 2) / 2;
+    uint8_t *ibp, *maxibp;
+    uint8_t *bp, bpos;
+    size_t size = 0;
+
+    bp = packet;
+    pa_memzero(packet, max);
+    size = bpos = 0;
+
+    bit_writer(&bp, &bpos, &size, 1, 3); /* channel=1, stereo */
+    bit_writer(&bp, &bpos, &size, 0, 4); /* Unknown */
+    bit_writer(&bp, &bpos, &size, 0, 8); /* Unknown */
+    bit_writer(&bp, &bpos, &size, 0, 4); /* Unknown */
+    bit_writer(&bp, &bpos, &size, 1, 1); /* Hassize */
+    bit_writer(&bp, &bpos, &size, 0, 2); /* Unused */
+    bit_writer(&bp, &bpos, &size, 1, 1); /* Is-not-compressed */
+    /* Size of data, integer, big endian. */
+    bit_writer(&bp, &bpos, &size, (nbs >> 24) & 0xff, 8);
+    bit_writer(&bp, &bpos, &size, (nbs >> 16) & 0xff, 8);
+    bit_writer(&bp, &bpos, &size, (nbs >> 8)  & 0xff, 8);
+    bit_writer(&bp, &bpos, &size, (nbs)       & 0xff, 8);
+
+    ibp = raw;
+    maxibp = raw + (4 * nbs) - 4;
+    while (ibp <= maxibp) {
+        /* Byte swap stereo data. */
+        bit_writer(&bp, &bpos, &size, *(ibp + 1), 8);
+        bit_writer(&bp, &bpos, &size, *(ibp + 0), 8);
+        bit_writer(&bp, &bpos, &size, *(ibp + 3), 8);
+        bit_writer(&bp, &bpos, &size, *(ibp + 2), 8);
+        ibp += 4;
+    }
+
+    *length = (ibp - raw);
+    return size;
+}
+
+static size_t write_AAC_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length) {
+    size_t size = 0;
+
+    pa_memzero(packet, max);
+
+    pa_log("AAC encoding not implemented...");
+
+    return size;
+}
+
+static size_t build_tcp_audio_packet(pa_raop_client *c, uint8_t *raw, size_t *index, size_t *length, uint32_t **packet) {
+    const size_t max = sizeof(tcp_audio_header) + 8 + 16384;
+    uint32_t *buffer = NULL;
+    size_t size, head;
+
+    *packet = NULL;
+    if (!(buffer = pa_xmalloc0(max)))
+        return 0;
+
+    size = head = sizeof(tcp_audio_header);
+    memcpy(buffer, tcp_audio_header, sizeof(tcp_audio_header));
+    buffer[1] |= htonl((uint32_t) c->seq);
+    buffer[2] = htonl(c->rtptime);
+    buffer[3] = htonl(c->ssrc);
+
+    if (c->codec == PA_RAOP_CODEC_PCM)
+        size += write_PCM_data(((uint8_t *) buffer + head), max - head, raw, length);
+    else if (c->codec == PA_RAOP_CODEC_ALAC)
+        size += write_ALAC_data(((uint8_t *) buffer + head), max - head, raw, length, false);
+    else
+        size += write_AAC_data(((uint8_t *) buffer + head), max - head, raw, length);
+    c->rtptime += *length / 4;
+
+    buffer[0] |= htonl((uint32_t) size - 4);
+    if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
+        pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
+
+    *packet = buffer;
+    return size;
+}
+
+static ssize_t send_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
+    static uint32_t * packet = NULL;
+    static size_t size, sent;
+    double progress = 0.0;
+    size_t index, length;
+    uint8_t *raw = NULL;
+    ssize_t written;
+
+    if (!packet) {
+        index = block->index;
+        length = block->length;
+        raw = pa_memblock_acquire(block->memblock);
+
+        pa_assert(raw);
+        pa_assert(index == offset);
+        pa_assert(length > 0);
+
+        size = build_tcp_audio_packet(c, raw, &index, &length, &packet);
+        sent = 0;
+    }
+
+    written = -1;
+    if (packet != NULL && size > 0)
+        written = pa_write(c->tcp_sfd, packet + sent, size - sent, NULL);
+    if (block->index == offset)
+        c->seq++;
+    if (sent == 0)
+        pa_memblock_release(block->memblock);
+    if (written > 0) {
+        sent += written;
+        progress = (double) sent / (double) size;
+        index = (block->index + block->length) * progress;
+        length = (block->index + block->length) - index;
+        block->length = length;
+        block->index = index;
+    }
+
+    if ((size - sent) <= 0) {
+        pa_xfree(packet);
+        packet = NULL;
+    }
+
+    return written;
+}
+
+static size_t build_udp_audio_packet(pa_raop_client *c, uint8_t *raw, size_t *index, size_t *length, uint32_t **packet) {
+    const size_t max = sizeof(udp_audio_header) + 8 + 1408;
+    uint32_t *buffer = NULL;
+    size_t size, head;
+
+    *packet = NULL;
+    if (!(buffer = pa_xmalloc0(max)))
+        return 0;
+
+    size = head = sizeof(udp_audio_header);
+    memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
+    if (c->is_first_packet)
+        buffer[0] |= htonl((uint32_t) 0x80 << 16);
+    buffer[0] |= htonl((uint32_t) c->seq);
+    buffer[1] = htonl(c->rtptime);
+    buffer[2] = htonl(c->ssrc);
+
+    if (c->codec == PA_RAOP_CODEC_PCM)
+        size += write_PCM_data(((uint8_t *) buffer + head), max - head, raw + *index, length);
+    else if (c->codec == PA_RAOP_CODEC_ALAC)
+        size += write_ALAC_data(((uint8_t *) buffer + head), max - head, raw + *index, length, false);
+    else
+        size += write_AAC_data(((uint8_t *) buffer + head), max - head, raw + *index, length);
+    c->rtptime += *length / 4;
+
+    if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
+        pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
+
+    *index += *length;
+    *length = 0;
+    /* It is meaningless to preseve the partial data -> */
+    *packet = buffer;
+    return size;
+}
+
+static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
+    uint32_t *packet = NULL;
+    size_t index, length, size;
+    uint8_t *raw = NULL;
+    ssize_t written;
+
+    index = block->index;
+    length = block->length;
+    raw = pa_memblock_acquire(block->memblock);
+
+    pa_assert(raw);
+    /* <- UDP packet has to be sent at once ! */
+    pa_assert(index == offset);
+    pa_assert(length > 0);
+
+    written = -1;
+    size = build_udp_audio_packet(c, raw, &index, &length, &packet);
+    if (packet != NULL && size > 0)
+        written = pa_write(c->udp_sfd, packet, size, NULL);
+    if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+        pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
+    c->seq++;
+
+    /* Store packet for resending in the packet buffer (UDP). */
+    pa_raop_pb_write_packet(c->pbuf, c->seq, raw + block->index, block->length);
+    pa_xfree(packet);
+
+    pa_memblock_release(block->memblock);
+    block->length = length;
+    block->index = index;
+
+    if (written < 0)
+        return (ssize_t) size;
+    return written;
+}
+
+static size_t rebuild_udp_audio_packet(pa_raop_client *c, uint16_t seq, uint32_t **packet) {
+    size_t size = sizeof(udp_audio_retrans_header);
+    uint32_t *buffer = NULL;
+    uint8_t *data = NULL;
+
+    size += pa_raop_pb_read_packet(c->pbuf, seq, &data);
+    if (size == sizeof(udp_audio_retrans_header))
+        return 0;
+    if (!(buffer = pa_xmalloc0(size)))
+        return 0;
+
+    memcpy(buffer, udp_audio_retrans_header, sizeof(udp_audio_retrans_header));
+    buffer[0] |= htonl((uint32_t) seq);
+
+    *packet = buffer;
+    return size;
+}
+
+static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_t nbp) {
+    ssize_t total = 0;
+    int i = 0;
+
+    for (i = 0; i < nbp; i++) {
+        uint32_t * packet = NULL;
+        ssize_t written = 0;
+        size_t size = 0;
+
+        size = rebuild_udp_audio_packet(c, seq, &packet);
+        if (packet != NULL && size > 0)
+            written = pa_write(c->udp_cfd, packet, size, NULL);
+        if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+            pa_log_debug("Discarding UDP (audio-restransmitted, seq=%d) packet due to EAGAIN", c->seq);
+            continue;
+        }
+
+        if (written > 0)
+            total +=  written;
+    }
+
+    return total;
+}
+
+static size_t build_udp_sync_packet(pa_raop_client *c, uint32_t stamp, uint32_t **packet) {
+    const size_t size = sizeof(udp_sync_header) + 12;
+    const uint32_t delay = 88200;
+    uint32_t *buffer = NULL;
+    uint64_t transmitted = 0;
+    struct timeval tv;
+
+    *packet = NULL;
+    if (!(buffer = pa_xmalloc0(size)))
+        return 0;
+
+    memcpy(buffer, udp_sync_header, sizeof(udp_sync_header));
+    if (c->is_first_packet)
+        buffer[0] |= 0x10;
+    stamp -= delay;
+    buffer[1] = htonl(stamp);
+    /* Set the transmited timestamp to current time. */
+    transmitted = timeval_to_ntp(pa_rtclock_get(&tv));
+    buffer[2] = htonl(transmitted >> 32);
+    buffer[3] = htonl(transmitted & 0xffffffff);
+    stamp += delay;
+    buffer[4] = htonl(stamp);
+
+    *packet = buffer;
+    return size;
+}
+
+static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) {
+    uint32_t * packet = NULL;
+    ssize_t written = 0;
+    size_t size = 0;
+
+    size = build_udp_sync_packet(c, stamp, &packet);
+    if (packet != NULL && size > 0)
+        written = pa_loop_write(c->udp_cfd, packet, size, NULL);
+
+    return written;
+}
+
+static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
+    uint8_t payload = 0;
+    uint16_t seq, nbp = 0;
+    ssize_t written = 0;
+
+    /* Control packets are 8 bytes long:  */
+    if (size != 8 || packet[0] != 0x80) {
+        pa_log_debug("Received an invalid control packet...");
+        return 1;
+    }
+
+    seq = ntohs((uint16_t) packet[4]);
+    nbp = ntohs((uint16_t) packet[6]);
+    if (nbp <= 0) {
+        pa_log_debug("Received an invalid control packet...");
+        return 1;
+    }
+
+    /* The market bit is always set (see rfc3550 for packet structure) ! */
+    payload = packet[1] ^ 0x80;
+    switch (payload) {
+        case PAYLOAD_RETRANSMIT_REQUEST:
+            pa_log_debug("Resending %u packets starting at %u", nbp, seq);
+            written = resend_udp_audio_packets(c, seq, nbp);
+            break;
+        case PAYLOAD_RETRANSMIT_REPLY:
+        default:
+            pa_log_debug("Got an unexpected payload type on control channel (%u) !", payload);
+            break;
+    }
+
+    return written;
+}
+
+static size_t build_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received, uint32_t **packet) {
+    const size_t size = sizeof(udp_timming_header) + 24;
+    uint32_t *buffer = NULL;
+    uint64_t transmitted = 0;
+    struct timeval tv;
+
+    *packet = NULL;
+    if (!(buffer = pa_xmalloc0(size)))
+        return 0;
+
+    memcpy(buffer, udp_timming_header, sizeof(udp_timming_header));
+    /* Copying originate timestamp from the incoming request packet. */
+    buffer[2] = data[4];
+    buffer[3] = data[5];
+    /* Set the receive timestamp to reception time. */
+    buffer[4] = htonl(received >> 32);
+    buffer[5] = htonl(received & 0xffffffff);
+    /* Set the transmit timestamp to current time. */
+    transmitted = timeval_to_ntp(pa_rtclock_get(&tv));
+    buffer[6] = htonl(transmitted >> 32);
+    buffer[7] = htonl(transmitted & 0xffffffff);
+
+    *packet = buffer;
+    return size;
+}
+
+static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received) {
+    uint32_t * packet = NULL;
+    ssize_t written = 0;
+    size_t size = 0;
+
+    size = build_udp_timing_packet(c, data, received, &packet);
+    if (packet != NULL && size > 0)
+        written = pa_loop_write(c->udp_tfd, packet, size, NULL);
+
+    return written;
+}
+
+static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
+    const uint32_t * data = NULL;
+    uint8_t payload = 0;
+    struct timeval tv;
+    size_t written = 0;
+    uint64_t rci = 0;
+
+    /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
+    if (size != 32 || packet[0] != 0x80) {
+        pa_log_debug("Received an invalid UDP timing packet...");
+        return 0;
+    }
+
+    rci = timeval_to_ntp(pa_rtclock_get(&tv));
+    data = (uint32_t *) (packet + sizeof(udp_timming_header));
+
+    /* The market bit is always set (see rfc3550 for packet structure) ! */
+    payload = packet[1] ^ 0x80;
+    switch (payload) {
+        case PAYLOAD_TIMING_REQUEST:
+            pa_log_debug("Sending timing packet at %lu", rci);
+            written = send_udp_timing_packet(c, data, rci);
+            break;
+        case PAYLOAD_TIMING_REPLY:
+        default:
+            pa_log_debug("Got an unexpected payload type on timing channel (%u) !", payload);
+            break;
+    }
+
+    return written;
+}
+
+static int connect_udp_socket(pa_raop_client *c, int fd, uint16_t port) {
+    struct sockaddr_in sa4;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 sa6;
+#endif
+    struct sockaddr *sa;
+    socklen_t salen;
+    sa_family_t af;
+
+    pa_zero(sa4);
+#ifdef HAVE_IPV6
+    pa_zero(sa6);
+#endif
+    if (inet_pton(AF_INET, c->host, &sa4.sin_addr) > 0) {
+        sa4.sin_family = af = AF_INET;
+        sa4.sin_port = htons(port);
+        sa = (struct sockaddr *) &sa4;
+        salen = sizeof(sa4);
+#ifdef HAVE_IPV6
+    } else if (inet_pton(AF_INET6, c->host, &sa6.sin6_addr) > 0) {
+        sa6.sin6_family = af = AF_INET6;
+        sa6.sin6_port = htons(port);
+        sa = (struct sockaddr *) &sa6;
+        salen = sizeof(sa6);
+#endif
+    } else {
+        pa_log("Invalid destination '%s'", c->host);
+        goto fail;
+    }
+
+    if (fd < 0 && (fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
+        pa_log("socket() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    /* If the socket queue is full, let's drop packets */
+    pa_make_udp_socket_low_delay(fd);
+    pa_make_fd_nonblock(fd);
+
+    if (connect(fd, sa, salen) < 0) {
+        pa_log("connect() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    pa_log_debug("Connected to %s on port %d (SOCK_DGRAM)", c->host, port);
+    return fd;
+
+fail:
+    if (fd >= 0)
+        pa_close(fd);
+
+    return -1;
+}
+
+static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
+    int fd = -1;
+    uint16_t port;
+    struct sockaddr_in sa4;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 sa6;
+#endif
+    struct sockaddr *sa;
+    uint16_t *sa_port;
+    socklen_t salen;
+    sa_family_t af;
+    int one = 1;
+
+    pa_assert(actual_port);
+
+    port = *actual_port;
+
+    pa_zero(sa4);
+#ifdef HAVE_IPV6
+    pa_zero(sa6);
+#endif
+    if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) {
+        sa4.sin_family = af = AF_INET;
+        sa4.sin_port = htons(port);
+        sa = (struct sockaddr *) &sa4;
+        salen = sizeof(sa4);
+        sa_port = &sa4.sin_port;
+#ifdef HAVE_IPV6
+    } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
+        sa6.sin6_family = af = AF_INET6;
+        sa6.sin6_port = htons(port);
+        sa = (struct sockaddr *) &sa6;
+        salen = sizeof(sa6);
+        sa_port = &sa6.sin6_port;
+#endif
+    } else {
+        pa_log("Could not determine which address family to use");
+        goto fail;
+    }
+
+    if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
+        pa_log("socket() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+#ifdef SO_TIMESTAMP
+    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
+        pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+#else
+    pa_log("SO_TIMESTAMP unsupported on this platform");
+    goto fail;
+#endif
+
+    one = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+        pa_log("setsockopt(SO_REUSEADDR) failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    do {
+        *sa_port = htons(port);
+
+        if (bind(fd, sa, salen) < 0 && errno != EADDRINUSE) {
+            pa_log("bind_socket() failed: %s", pa_cstrerror(errno));
+            goto fail;
+        }
+        break;
+    } while (++port > 0);
+
+    pa_log_debug("Socket bound to port %d (SOCK_DGRAM)", port);
+    *actual_port = port;
+
+    return fd;
+
+fail:
+    if (fd >= 0)
+        pa_close(fd);
+
+    return -1;
+}
+
+static void tcp_connection_cb(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
+    pa_raop_client *c = userdata;
+
+    pa_assert(sc);
+    pa_assert(c);
+
+    pa_socket_client_unref(sc);
+
+    if (!io) {
+        pa_log("Connection failed: %s", pa_cstrerror(errno));
+        return;
+    }
+
+    c->tcp_sfd = pa_iochannel_get_send_fd(io);
+    pa_iochannel_set_noclose(io, true);
+    pa_make_tcp_socket_low_delay(c->tcp_sfd);
+
+    pa_iochannel_free(io);
+
+    pa_log_debug("Connection established (TCP)");
+
+    if (c->state_callback)
+        c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
+}
+
+static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
+    pa_raop_client *c = userdata;
+
+    pa_assert(c);
+    pa_assert(rtsp);
+    pa_assert(rtsp == c->rtsp);
+
+    switch (state) {
+        case STATE_CONNECT: {
+            char *key, *iv, *sdp = NULL;
+            int frames = 0;
+            const char *ip;
+            char *url;
+
+            pa_log_debug("RAOP: CONNECTED");
+
+            ip = pa_rtsp_localip(c->rtsp);
+            url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
+            pa_rtsp_set_url(c->rtsp, url);
+
+            if (c->protocol == PA_RAOP_PROTOCOL_TCP)
+                frames = FRAMES_PER_TCP_PACKET;
+            else if (c->protocol == PA_RAOP_PROTOCOL_UDP)
+                frames = FRAMES_PER_UDP_PACKET;
+
+            switch(c->encryption) {
+                case PA_RAOP_ENCRYPTION_NONE: {
+                    sdp = pa_sprintf_malloc(
+                        "v=0\r\n"
+                        "o=iTunes %s 0 IN IP4 %s\r\n"
+                        "s=iTunes\r\n"
+                        "c=IN IP4 %s\r\n"
+                        "t=0 0\r\n"
+                        "m=audio 0 RTP/AVP 96\r\n"
+                        "a=rtpmap:96 AppleLossless\r\n"
+                        "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
+                        c->sid, ip, c->host, frames);
+
+                    break;
+                }
+
+                case PA_RAOP_ENCRYPTION_RSA:
+                case PA_RAOP_ENCRYPTION_FAIRPLAY:
+                case PA_RAOP_ENCRYPTION_MFISAP:
+                case PA_RAOP_ENCRYPTION_FAIRPLAY_SAP25: {
+                    key = pa_raop_secret_get_key(c->secret);
+                    iv = pa_raop_secret_get_iv(c->secret);
+
+                    sdp = pa_sprintf_malloc(
+                        "v=0\r\n"
+                        "o=iTunes %s 0 IN IP4 %s\r\n"
+                        "s=iTunes\r\n"
+                        "c=IN IP4 %s\r\n"
+                        "t=0 0\r\n"
+                        "m=audio 0 RTP/AVP 96\r\n"
+                        "a=rtpmap:96 AppleLossless\r\n"
+                        "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+                        "a=rsaaeskey:%s\r\n"
+                        "a=aesiv:%s\r\n",
+                        c->sid, ip, c->host, frames, key, iv);
+
+                    pa_xfree(key);
+                    pa_xfree(iv);
+                    break;
+                }
+            }
+
+            pa_rtsp_announce(c->rtsp, sdp);
+
+            pa_xfree(sdp);
+            pa_xfree(url);
+            break;
+        }
+
+        case STATE_OPTIONS: {
+            pa_log_debug("RAOP: OPTIONS (stream cb)");
+
+            break;
+        }
+
+        case STATE_ANNOUNCE: {
+            uint16_t cport = DEFAULT_UDP_CONTROL_PORT;
+            uint16_t tport = DEFAULT_UDP_TIMING_PORT;
+            char *trs = NULL;
+
+            pa_log_debug("RAOP: ANNOUNCE");
+
+            if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
+                trs = pa_sprintf_malloc(
+                    "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
+            } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
+                c->udp_cfd = open_bind_udp_socket(c, &cport);
+                c->udp_tfd  = open_bind_udp_socket(c, &tport);
+                if (c->udp_cfd < 0 || c->udp_tfd < 0)
+                    goto annonce_error;
+
+                trs = pa_sprintf_malloc(
+                    "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;"
+                    "control_port=%d;timing_port=%d",
+                    cport, tport);
+            }
+
+            pa_rtsp_setup(c->rtsp, trs);
+
+            pa_xfree(trs);
+            break;
+
+        annonce_error:
+            if (c->udp_cfd > 0)
+                pa_close(c->udp_cfd);
+            c->udp_cfd = -1;
+            if (c->udp_tfd > 0)
+                pa_close(c->udp_tfd);
+            c->udp_tfd = -1;
+
+            pa_rtsp_client_free(c->rtsp);
+
+            pa_log_error("Aborting RTSP announce, failed creating required sockets");
+
+            c->rtsp = NULL;
+            pa_xfree(trs);
+            break;
+        }
+
+        case STATE_SETUP: {
+            pa_socket_client *sc = NULL;
+            uint32_t sport = DEFAULT_UDP_AUDIO_PORT;
+            uint32_t cport =0, tport = 0;
+            char *ajs, *trs, *token, *pc;
+            const char *token_state = NULL;
+            char delimiters[] = ";";
+
+            pa_log_debug("RAOP: SETUP");
+
+            ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
+            trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
+
+            if (ajs) {
+                c->jack_type = JACK_TYPE_ANALOG;
+                c->jack_status = JACK_STATUS_DISCONNECTED;
+
+                while ((token = pa_split(ajs, delimiters, &token_state))) {
+                    if ((pc = strstr(token, "="))) {
+                      *pc = 0;
+                      if (pa_streq(token, "type") && pa_streq(pc + 1, "digital"))
+                          c->jack_type = JACK_TYPE_DIGITAL;
+                    } else {
+                        if (pa_streq(token, "connected"))
+                            c->jack_status = JACK_STATUS_CONNECTED;
+                    }
+                    pa_xfree(token);
+                }
+
+            } else {
+                pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
+            }
+
+            sport = pa_rtsp_serverport(c->rtsp);
+            if (sport <= 0)
+                goto setup_error;
+
+            token_state = NULL;
+            if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
+                if (!(sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, sport)))
+                    goto setup_error;
+
+                pa_socket_client_ref(sc);
+                pa_socket_client_set_callback(sc, tcp_connection_cb, c);
+
+                pa_socket_client_unref(sc);
+                sc = NULL;
+            } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
+                if (trs) {
+                    /* Now parse out the server port component of the response. */
+                    while ((token = pa_split(trs, delimiters, &token_state))) {
+                        if ((pc = strstr(token, "="))) {
+                        *pc = 0;
+                         if (pa_streq(token, "control_port"))
+                                pa_atou(pc + 1, &cport);
+                            if (pa_streq(token, "timing_port"))
+                                pa_atou(pc + 1, &tport);
+                            *pc = '=';
+                        }
+                        pa_xfree(token);
+                    }
+                } else {
+                    pa_log_warn("\"Transport\" missing in RTSP setup response");
+                }
+
+                if (cport <= 0 || tport <= 0)
+                    goto setup_error;
+
+                if ((c->udp_sfd = connect_udp_socket(c, -1, sport)) <= 0)
+                    goto setup_error;
+                if ((c->udp_cfd = connect_udp_socket(c, c->udp_cfd, cport)) <= 0)
+                    goto setup_error;
+                if ((c->udp_tfd = connect_udp_socket(c, c->udp_tfd, tport)) <= 0)
+                    goto setup_error;
+
+                pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport);
+
+                if (c->state_callback)
+                    c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
+            }
+
+            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
+
+            pa_xfree(trs);
+            pa_xfree(ajs);
+            break;
+
+        setup_error:
+            if (c->tcp_sfd > 0)
+                pa_close(c->tcp_sfd);
+            c->tcp_sfd = -1;
+
+            if (c->udp_sfd > 0)
+                pa_close(c->udp_sfd);
+            c->udp_sfd = -1;
+
+            c->udp_cfd = c->udp_tfd = -1;
+
+            pa_rtsp_client_free(c->rtsp);
+
+            pa_log_error("aborting RTSP setup, failed creating required sockets");
+
+            if (c->state_callback)
+                c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
+
+            c->rtsp = NULL;
+            break;
+        }
+
+        case STATE_RECORD: {
+            int32_t latency = 0;
+            uint32_t ssrc;
+            char *alt;
+
+            pa_log_debug("RAOP: RECORD");
+
+            alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
+            if (alt)
+                pa_atoi(alt, &latency);
+
+            pa_random(&ssrc, sizeof(ssrc));
+            c->is_first_packet = true;
+            c->is_recording = true;
+            c->sync_count = 0;
+            c->ssrc = ssrc;
+
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata);
+
+            pa_xfree(alt);
+            break;
+        }
+
+        case STATE_SET_PARAMETER: {
+            pa_log_debug("RAOP: SET_PARAMETER");
+
+            break;
+        }
+
+        case STATE_FLUSH: {
+            pa_log_debug("RAOP: FLUSHED");
+
+            c->is_recording = false;
+
+            break;
+        }
+
+        case STATE_TEARDOWN: {
+            pa_log_debug("RAOP: TEARDOWN");
+
+            c->is_recording = false;
+
+            if (c->pbuf)
+                pa_raop_pb_clear(c->pbuf);
+
+            if (c->tcp_sfd > 0)
+                pa_close(c->tcp_sfd);
+            c->tcp_sfd = -1;
+
+            if (c->udp_sfd > 0)
+                pa_close(c->udp_sfd);
+            c->udp_sfd = -1;
+
+            /* Polling sockets will be closed by sink */
+            c->udp_cfd = c->udp_tfd = -1;
+            c->tcp_sfd = -1;
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            if (c->state_callback)
+                c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
+
+            break;
+        }
+
+        case STATE_DISCONNECTED: {
+            pa_log_debug("RAOP: DISCONNECTED");
+
+            c->is_recording = false;
+
+            if (c->pbuf)
+                pa_raop_pb_clear(c->pbuf);
+
+            if (c->tcp_sfd > 0)
+                pa_close(c->tcp_sfd);
+            c->tcp_sfd = -1;
+
+            if (c->udp_sfd > 0)
+                pa_close(c->udp_sfd);
+            c->udp_sfd = -1;
+
+            /* Polling sockets will be closed by sink */
+            c->udp_cfd = c->udp_tfd = -1;
+            c->tcp_sfd = -1;
+
+            pa_log_error("RTSP control channel closed (disconnected)");
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
+
+            break;
+        }
+    }
+}
+
+static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
+    pa_raop_client *c = userdata;
+
+    pa_assert(c);
+    pa_assert(rtsp);
+    pa_assert(rtsp == c->rtsp);
+
+    switch (state) {
+        case STATE_CONNECT: {
+            char *sci = NULL, *sac = NULL;
+            uint16_t rac;
+            struct {
+                uint32_t ci1;
+                uint32_t ci2;
+            } rci;
+
+            pa_random(&rci, sizeof(rci));
+            /* Generate a random Client-Instance number */
+            sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2);
+            pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
+
+            pa_random(&rac, sizeof(rac));
+            /* Generate a random Apple-Challenge key */
+            pa_raop_base64_encode(&rac, 8 * sizeof(rac), &sac);
+            rtrim_char(sac, '=');
+            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
+
+            pa_rtsp_options(c->rtsp);
+
+            pa_xfree(sac);
+            pa_xfree(sci);
+            break;
+        }
+
+        case STATE_OPTIONS: {
+            static bool waiting = false;
+            const char *current = NULL;
+            char space[] = " ";
+            char *token,*ath = NULL;
+            char *publ, *wath, *mth, *val;
+            char *realm = NULL, *nonce = NULL, *response = NULL;
+            char comma[] = ",";
+
+            pa_log_debug("RAOP: OPTIONS (auth cb)");
+            /* We do not consider the Apple-Response */
+            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
+
+            if (STATUS_UNAUTHORIZED == status) {
+                wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
+                if (true == waiting) {
+                    pa_xfree(wath);
+                    goto fail;
+                }
+
+                if (wath)
+                    mth = pa_split(wath, space, &current);
+                while ((token = pa_split(wath, comma, &current))) {
+                    val = NULL;
+                    if ((val = strstr(token, "="))) {
+                        if (NULL == realm && val > strstr(token, "realm"))
+                            realm = pa_xstrdup(val + 2);
+                        else if (NULL == nonce && val > strstr(token, "nonce"))
+                            nonce = pa_xstrdup(val + 2);
+                        val = NULL;
+                    }
+
+                    pa_xfree(token);
+                }
+
+                if (pa_safe_streq(mth, "Basic")) {
+                    rtrim_char(realm, '\"');
+
+                    pa_raop_basic_response(DEFAULT_USER_NAME, c->password, &response);
+                    ath = pa_sprintf_malloc("Basic %s",
+                        response);
+
+                    pa_xfree(response);
+                    pa_xfree(realm);
+                } else if (pa_safe_streq(mth, "Digest")) {
+                    rtrim_char(realm, '\"');
+                    rtrim_char(nonce, '\"');
+
+                    pa_raop_digest_response(DEFAULT_USER_NAME, realm, c->password, nonce, "*", &response);
+                    ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"",
+                        DEFAULT_USER_NAME, realm, nonce,
+                        response);
+
+                    pa_xfree(response);
+                    pa_xfree(realm);
+                    pa_xfree(nonce);
+                } else {
+                    pa_log_error("unsupported authentication method: %s", mth);
+                    pa_xfree(wath);
+                    pa_xfree(mth);
+                    goto error;
+                }
+
+                pa_xfree(wath);
+                pa_xfree(mth);
+
+                pa_rtsp_add_header(c->rtsp, "Authorization", ath);
+                pa_xfree(ath);
+
+                waiting = true;
+                pa_rtsp_options(c->rtsp);
+                break;
+            }
+
+            if (STATUS_OK == status) {
+                publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
+                c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
+
+                if (c->password)
+                    pa_xfree(c->password);
+                pa_xfree(publ);
+                c->password = NULL;
+            }
+
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_AUTHENTICATED, c->state_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            waiting = false;
+            break;
+
+        fail:
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            pa_log_error("aborting authentication, wrong password");
+
+            waiting = false;
+            break;
+
+        error:
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            pa_log_error("aborting authentication, unexpected failure");
+
+            waiting = false;
+            break;
+        }
+
+        case STATE_ANNOUNCE:
+        case STATE_SETUP:
+        case STATE_RECORD:
+        case STATE_SET_PARAMETER:
+        case STATE_FLUSH:
+        case STATE_TEARDOWN:
+        case STATE_DISCONNECTED:
+        default: {
+            if (c->state_callback)
+                c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            if (c->sci)
+                pa_xfree(c->sci);
+            c->sci = NULL;
+
+            break;
+        }
+    }
+}
+
+pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol,
+                                   pa_raop_encryption_t encryption, pa_raop_codec_t codec) {
+    pa_raop_client *c;
+
+    pa_parsed_address a;
+    pa_sample_spec ss;
+
+    pa_assert(core);
+    pa_assert(host);
+
+    if (pa_parse_address(host, &a) < 0)
+        return NULL;
+
+    if (a.type == PA_PARSED_ADDRESS_UNIX) {
+        pa_xfree(a.path_or_host);
+        return NULL;
+    }
+
+    c = pa_xnew0(pa_raop_client, 1);
+    c->core = core;
+    c->host = pa_xstrdup(a.path_or_host);
+    if (a.port > 0)
+        c->port = a.port;
+    else
+        c->port = DEFAULT_RAOP_PORT;
+    c->rtsp = NULL;
+    c->sci = c->sid = NULL;
+    c->password = NULL;
+
+    c->protocol = protocol;
+    c->encryption = encryption;
+    c->codec = codec;
+
+    c->tcp_sfd = -1;
+
+    c->udp_sfd = -1;
+    c->udp_cfd = -1;
+    c->udp_tfd = -1;
+
+    c->secret = NULL;
+    if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
+        c->secret = pa_raop_secret_new();
+
+    ss = core->default_sample_spec;
+
+    c->is_recording = false;
+    c->is_first_packet = true;
+    /* Packet sync interval should be around 1s (UDP only) */
+    c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
+    c->sync_count = 0;
+
+    c->pbuf = pa_raop_pb_new(UDP_DEFAULT_PKT_BUF_SIZE);
+
+    return c;
+}
+
+void pa_raop_client_free(pa_raop_client *c) {
+    pa_assert(c);
+
+    pa_raop_pb_delete(c->pbuf);
+
+    pa_xfree(c->sid);
+    pa_xfree(c->sci);
+    if (c->secret)
+        pa_raop_secret_free(c->secret);
+    pa_xfree(c->password);
+    c->sci = c->sid = NULL;
+    c->password = NULL;
+    c->secret = NULL;
+
+    if (c->rtsp)
+        pa_rtsp_client_free(c->rtsp);
+    c->rtsp = NULL;
+
+    pa_xfree(c->host);
+    pa_xfree(c);
+}
+
+int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->rtsp || c->password) {
+        pa_log_debug("Authentication/Connection already in progress...");
+        return 0;
+    }
+
+    c->password = NULL;
+    if (password)
+        c->password = pa_xstrdup(password);
+    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT);
+
+    pa_assert(c->rtsp);
+
+    pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c);
+    rv = pa_rtsp_connect(c->rtsp);
+    return rv;
+}
+
+bool pa_raop_client_is_authenticated(pa_raop_client *c) {
+    pa_assert(c);
+
+    return (c->sci != NULL);
+}
+
+int pa_raop_client_announce(pa_raop_client *c) {
+    uint32_t sid;
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->rtsp) {
+        pa_log_debug("Connection already in progress...");
+        return 0;
+    } else if (!c->sci) {
+        pa_log_debug("ANNOUNCE requires a preliminary authentication");
+        return 1;
+    }
+
+    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT);
+
+    pa_assert(c->rtsp);
+
+    c->sync_count = 0;
+    c->is_recording = false;
+    c->is_first_packet = true;
+    pa_random(&sid, sizeof(sid));
+    c->sid = pa_sprintf_malloc("%u", sid);
+    pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c);
+
+    rv = pa_rtsp_connect(c->rtsp);
+    return rv;
+}
+
+bool pa_raop_client_is_alive(pa_raop_client *c) {
+    pa_assert(c);
+
+    if (!c->rtsp || !c->sci) {
+        pa_log_debug("Not alive, connection not established yet...");
+        return false;
+    }
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            if (c->tcp_sfd > 0)
+                return true;
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            if (c->udp_sfd > 0)
+                return true;
+            break;
+        default:
+            break;
+    }
+
+    return false;
+}
+
+bool pa_raop_client_can_stream(pa_raop_client *c) {
+    pa_assert(c);
+
+    if (!c->rtsp || !c->sci) {
+        pa_log_debug("Can't stream, connection not established yet...");
+        return false;
+    }
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            if (c->tcp_sfd > 0 && c->is_recording)
+                return true;
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            if (c->udp_sfd > 0 && c->is_recording)
+                return true;
+            break;
+        default:
+            break;
+    }
+
+    return false;
+}
+
+int pa_raop_client_stream(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (!c->rtsp || !c->sci) {
+        pa_log_debug("Streaming's impossible, connection not established yet...");
+        return 0;
+    }
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            if (c->tcp_sfd > 0 && !c->is_recording) {
+                c->is_recording = true;
+                c->is_first_packet = true;
+                c->sync_count = 0;
+            }
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            if (c->udp_sfd > 0 && !c->is_recording) {
+                c->is_recording = true;
+                c->is_first_packet = true;
+                c->sync_count = 0;
+            }
+            break;
+        default:
+            rv = 1;
+            break;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
+    char *param;
+    int rv = 0;
+    double db;
+
+    pa_assert(c);
+
+    if (!c->rtsp) {
+        pa_log_debug("Cannot SET_PARAMETER, connection not established yet...");
+        return 0;
+    } else if (!c->sci) {
+        pa_log_debug("SET_PARAMETER requires a preliminary authentication");
+        return 1;
+    }
+
+    db = pa_sw_volume_to_dB(volume);
+    if (db < VOLUME_MIN)
+        db = VOLUME_MIN;
+    else if (db > VOLUME_MAX)
+        db = VOLUME_MAX;
+
+    pa_log_debug("volume=%u db=%.6f", volume, db);
+
+    param = pa_sprintf_malloc("volume: %0.6f\r\n", db);
+    /* We just hit and hope, cannot wait for the callback. */
+    if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp))
+        rv = pa_rtsp_setparameter(c->rtsp, param);
+
+    pa_xfree(param);
+    return rv;
+}
+
+int pa_raop_client_flush(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (!c->rtsp) {
+        pa_log_debug("Cannot FLUSH, connection not established yet...)");
+        return 0;
+    } else if (!c->sci) {
+        pa_log_debug("FLUSH requires a preliminary authentication");
+        return 1;
+    }
+
+    rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
+    return rv;
+}
+
+int pa_raop_client_teardown(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (!c->rtsp) {
+        pa_log_debug("Cannot TEARDOWN, connection not established yet...");
+        return 0;
+    } else if (!c->sci) {
+        pa_log_debug("TEARDOWN requires a preliminary authentication");
+        return 1;
+    }
+
+    rv = pa_rtsp_teardown(c->rtsp);
+    return rv;
+}
+
+void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) {
+    pa_assert(c);
+    pa_assert(frames);
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            *frames = FRAMES_PER_TCP_PACKET;
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            *frames = FRAMES_PER_UDP_PACKET;
+            break;
+        default:
+            *frames = 0;
+            break;
+    }
+}
+
+bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item) {
+    struct pollfd *pollfd = NULL;
+    pa_rtpoll_item *item = NULL;
+    bool oob = true;
+
+    pa_assert(c);
+    pa_assert(poll);
+    pa_assert(poll_item);
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            item = pa_rtpoll_item_new(poll, PA_RTPOLL_NEVER, 1);
+            pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
+            pollfd->fd = c->tcp_sfd;
+            pollfd->events = POLLOUT;
+            pollfd->revents = 0;
+            *poll_item = item;
+            oob = false;
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            item = pa_rtpoll_item_new(poll, PA_RTPOLL_NEVER, 2);
+            pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
+            pollfd->fd = c->udp_cfd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+            pollfd++;
+            pollfd->fd = c->udp_tfd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+            *poll_item = item;
+            oob = true;
+            break;
+        default:
+            *poll_item = NULL;
+            break;
+    }
+
+    return oob;
+}
+
+pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) {
+    double minv, maxv;
+
+    pa_assert(c);
+
+    if (c->protocol != PA_RAOP_PROTOCOL_UDP)
+        return volume;
+
+    maxv = pa_sw_volume_from_dB(0.0);
+    minv = maxv * pow(10.0, VOLUME_DEF / 60.0);
+
+    /* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */
+    return volume - volume * (minv / maxv) + minv;
+}
+
+void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size) {
+    pa_assert(c);
+    pa_assert(fd > 0);
+    pa_assert(packet);
+
+    if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
+        if (fd == c->udp_cfd) {
+            pa_log_debug("Received UDP control packet");
+            handle_udp_control_packet(c, packet, size);
+        } else if (fd == c->udp_tfd) {
+            pa_log_debug("Received UDP timing packet");
+            handle_udp_timing_packet(c, packet, size);
+        }
+    }
+}
+
+ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
+    ssize_t written = 0;
+
+    pa_assert(c);
+    pa_assert(block);
+
+    /* Sync RTP & NTP timestamp if required (UDP). */
+    if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
+        c->sync_count++;
+        if (c->is_first_packet || c->sync_count >= c->sync_interval) {
+            send_udp_sync_packet(c, c->rtptime);
+            c->sync_count = 0;
+        }
+    }
+
+    switch (c->protocol) {
+        case PA_RAOP_PROTOCOL_TCP:
+            written = send_tcp_audio_packet(c, block, offset);
+            break;
+        case PA_RAOP_PROTOCOL_UDP:
+            written = send_udp_audio_packet(c, block, offset);
+            break;
+        default:
+            written = -1;
+            break;
+    }
+
+    c->is_first_packet = false;
+    return written;
+}
+
+void pa_raop_client_set_state_callback(pa_raop_client *c, pa_raop_client_state_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->state_callback = callback;
+    c->state_userdata = userdata;
+}
diff --git a/src/modules/raop/raop-client.h b/src/modules/raop/raop-client.h
new file mode 100644
index 0000000..72e6018
--- /dev/null
+++ b/src/modules/raop/raop-client.h
@@ -0,0 +1,83 @@
+#ifndef fooraopclientfoo
+#define fooraopclientfoo
+
+/***
+  This file is part of PulseAudio.
+
+  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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/volume.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/rtpoll.h>
+
+typedef enum pa_raop_protocol {
+    PA_RAOP_PROTOCOL_TCP,
+    PA_RAOP_PROTOCOL_UDP
+} pa_raop_protocol_t;
+
+typedef enum pa_raop_encryption {
+    PA_RAOP_ENCRYPTION_NONE,
+    PA_RAOP_ENCRYPTION_RSA,
+    PA_RAOP_ENCRYPTION_FAIRPLAY,
+    PA_RAOP_ENCRYPTION_MFISAP,
+    PA_RAOP_ENCRYPTION_FAIRPLAY_SAP25
+} pa_raop_encryption_t;
+
+typedef enum pa_raop_codec {
+    PA_RAOP_CODEC_PCM,
+    PA_RAOP_CODEC_ALAC,
+    PA_RAOP_CODEC_AAC,
+    PA_RAOP_CODEC_AAC_ELD
+} pa_raop_codec_t;
+
+typedef struct pa_raop_client pa_raop_client;
+
+typedef enum pa_raop_state {
+    PA_RAOP_INVALID_STATE,
+    PA_RAOP_AUTHENTICATED,
+    PA_RAOP_CONNECTED,
+    PA_RAOP_RECORDING,
+    PA_RAOP_DISCONNECTED
+} pa_raop_state_t;
+
+pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol,
+                                   pa_raop_encryption_t encryption, pa_raop_codec_t codec);
+void pa_raop_client_free(pa_raop_client *c);
+
+int pa_raop_client_authenticate(pa_raop_client *c, const char *password);
+bool pa_raop_client_is_authenticated(pa_raop_client *c);
+
+int pa_raop_client_announce(pa_raop_client *c);
+bool pa_raop_client_is_alive(pa_raop_client *c);
+bool pa_raop_client_can_stream(pa_raop_client *c);
+int pa_raop_client_stream(pa_raop_client *c);
+int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume);
+int pa_raop_client_flush(pa_raop_client *c);
+int pa_raop_client_teardown(pa_raop_client *c);
+
+void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *size);
+bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item);
+pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
+void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size);
+ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset);
+
+typedef void (*pa_raop_client_state_cb_t)(pa_raop_state_t state, void *userdata);
+void pa_raop_client_set_state_callback(pa_raop_client *c, pa_raop_client_state_cb_t callback, void *userdata);
+
+#endif
diff --git a/src/modules/raop/raop-crypto.c b/src/modules/raop/raop-crypto.c
new file mode 100644
index 0000000..0dd6463
--- /dev/null
+++ b/src/modules/raop/raop-crypto.c
@@ -0,0 +1,164 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+
+  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.1 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 <stdint.h>
+#include <string.h>
+
+#include <openssl/err.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/random.h>
+
+#include "raop-crypto.h"
+#include "raop-util.h"
+
+#define AES_CHUNK_SIZE 16
+
+/* Openssl 1.1.0 broke compatibility. Before 1.1.0 we had to set RSA->n and
+ * RSA->e manually, but after 1.1.0 the RSA struct is opaque and we have to use
+ * RSA_set0_key(). RSA_set0_key() is a new function added in 1.1.0. We could
+ * depend on openssl 1.1.0, but it may take some time before distributions will
+ * be able to upgrade to the new openssl version. To insulate ourselves from
+ * such transition problems, let's implement RSA_set0_key() ourselves if it's
+ * not available. */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
+    r->n = n;
+    r->e = e;
+    return 1;
+}
+#endif
+
+struct pa_raop_secret {
+    uint8_t key[AES_CHUNK_SIZE]; /* Key for aes-cbc */
+    uint8_t iv[AES_CHUNK_SIZE];  /* Initialization vector for cbc */
+    AES_KEY aes;                 /* AES encryption */
+};
+
+static const char rsa_modulus[] =
+    "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
+    "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
+    "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
+    "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
+    "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
+    "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
+
+static const char rsa_exponent[] =
+    "AQAB";
+
+static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
+    uint8_t modules[256];
+    uint8_t exponent[8];
+    int size;
+    RSA *rsa;
+    BIGNUM *n_bn;
+    BIGNUM *e_bn;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    rsa = RSA_new();
+    size = pa_raop_base64_decode(rsa_modulus, modules);
+    n_bn = BN_bin2bn(modules, size, NULL);
+    size = pa_raop_base64_decode(rsa_exponent, exponent);
+    e_bn = BN_bin2bn(exponent, size, NULL);
+    RSA_set0_key(rsa, n_bn, e_bn, NULL);
+
+    size = RSA_public_encrypt(len, data, str, rsa, RSA_PKCS1_OAEP_PADDING);
+
+    RSA_free(rsa);
+    return size;
+}
+
+pa_raop_secret* pa_raop_secret_new(void) {
+    pa_raop_secret *s = pa_xnew0(pa_raop_secret, 1);
+
+    pa_assert(s);
+
+    pa_random(s->key, sizeof(s->key));
+    AES_set_encrypt_key(s->key, 128, &s->aes);
+    pa_random(s->iv, sizeof(s->iv));
+
+    return s;
+}
+
+void pa_raop_secret_free(pa_raop_secret *s) {
+    pa_assert(s);
+
+    pa_xfree(s);
+}
+
+char* pa_raop_secret_get_iv(pa_raop_secret *s) {
+    char *base64_iv = NULL;
+
+    pa_assert(s);
+
+    pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
+
+    return base64_iv;
+}
+
+char* pa_raop_secret_get_key(pa_raop_secret *s) {
+    char *base64_key = NULL;
+    uint8_t rsa_key[512];
+    int size = 0;
+
+    pa_assert(s);
+
+    /* Encrypt our AES public key to send to the device */
+    size = rsa_encrypt(s->key, AES_CHUNK_SIZE, rsa_key);
+    pa_raop_base64_encode(rsa_key, size, &base64_key);
+
+    return base64_key;
+}
+
+int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len) {
+    static uint8_t nv[AES_CHUNK_SIZE];
+    uint8_t *buffer;
+    int i = 0, j;
+
+    pa_assert(s);
+    pa_assert(data);
+
+    memcpy(nv, s->iv, AES_CHUNK_SIZE);
+
+    while (i + AES_CHUNK_SIZE <= len) {
+        buffer = data + i;
+        for (j = 0; j < AES_CHUNK_SIZE; ++j)
+            buffer[j] ^= nv[j];
+
+        AES_encrypt(buffer, buffer, &s->aes);
+
+        memcpy(nv, buffer, AES_CHUNK_SIZE);
+        i += AES_CHUNK_SIZE;
+    }
+
+    return i;
+}
diff --git a/src/modules/raop/raop-crypto.h b/src/modules/raop/raop-crypto.h
new file mode 100644
index 0000000..65f7577
--- /dev/null
+++ b/src/modules/raop/raop-crypto.h
@@ -0,0 +1,35 @@
+#ifndef fooraopcryptofoo
+#define fooraopcryptofoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+
+  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.1 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.
+***/
+
+typedef struct pa_raop_secret pa_raop_secret;
+
+pa_raop_secret* pa_raop_secret_new(void);
+void pa_raop_secret_free(pa_raop_secret *s);
+
+char* pa_raop_secret_get_iv(pa_raop_secret *s);
+char* pa_raop_secret_get_key(pa_raop_secret *s);
+
+int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len);
+
+#endif
diff --git a/src/modules/raop/raop-packet-buffer.c b/src/modules/raop/raop-packet-buffer.c
new file mode 100644
index 0000000..ef8ea15
--- /dev/null
+++ b/src/modules/raop/raop-packet-buffer.c
@@ -0,0 +1,172 @@
+/***
+  Circular buffer for RTP audio packets with random access support
+  by RTP sequence number.
+
+  Copyright 2013 Matthias Wabersich, Hajime Fujita
+
+  This 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.1 of the License,
+  or (at your option) any later version.
+
+  This 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.
+
+***/
+
+#include <stdlib.h>
+#include <limits.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-error.h>
+#include "raop-client.h"
+
+#include "raop-packet-buffer.h"
+
+/* FRAMES_PER_PACKET*2*2 + sizeof(udp_audio_header) + sizeof(ALAC header), unencoded */
+#define PACKET_SIZE_MAX (352*2*2 + 12 + 7) /* FIXME; hardcoded constant ! */
+/* Header room for packet retransmission header */
+#define RETRANS_HEADER_ROOM 4
+
+/* Packet element */
+struct pa_raop_packet_element {
+    uint16_t  seq_num; /* RTP sequence number (in host byte order) */
+    ssize_t   length;  /* Actual packet length */
+    /* Packet data including RTP header */
+    uint8_t   data[PACKET_SIZE_MAX + RETRANS_HEADER_ROOM];
+};
+
+/* Buffer struct */
+struct pa_raop_packet_buffer {
+    size_t   size;          /* max number of packets in buffer */
+    size_t   start;         /* index of oldest packet */
+    size_t   count;         /* number of packets in buffer */
+    uint16_t first_seq_num; /* Sequence number of first packet in buffer */
+    uint16_t latest_seq_num; /* Debug purpose */
+    pa_raop_packet_element *packets; /* Packet element pointer */
+};
+
+pa_raop_packet_buffer *pa_raop_pb_new(size_t size) {
+    pa_raop_packet_buffer *pb = pa_xmalloc0(sizeof(*pb));
+
+    pb->size = size;
+    pb->packets = (pa_raop_packet_element *)
+        pa_xmalloc(size * sizeof(pa_raop_packet_element));
+
+    pa_raop_pb_clear(pb);
+
+    return pb;
+}
+
+void pa_raop_pb_clear(pa_raop_packet_buffer *pb) {
+    pb->start = 0;
+    pb->count = 0;
+    pb->first_seq_num = 0;
+    pb->latest_seq_num = 0;
+    memset(pb->packets, 0, pb->size * sizeof(pa_raop_packet_element));
+}
+
+void pa_raop_pb_delete(pa_raop_packet_buffer *pb) {
+    pa_xfree(pb->packets);
+    pa_xfree(pb);
+}
+
+static int pb_is_full(pa_raop_packet_buffer *pb) {
+    return pb->count == pb->size;
+}
+
+static int pb_is_empty(pa_raop_packet_buffer *pb) {
+    return pb->count == 0;
+}
+
+static pa_raop_packet_element *pb_prepare_write(pa_raop_packet_buffer *pb, uint16_t seq) {
+    size_t end = (pb->start + pb->count) % pb->size;
+    pa_raop_packet_element *packet;
+
+    /* Set first packet sequence number in buffer if buffer is empty */
+    if (pb_is_empty(pb))
+        pb->first_seq_num = seq;
+    else
+        pa_assert((uint16_t) (pb->latest_seq_num + 1) == seq);
+
+    packet = &pb->packets[end];
+
+    if (pb_is_full(pb)) {
+        pb->start = (pb->start + 1) % pb->size; /* full, overwrite */
+
+        /* Set first packet sequence number in buffer
+           to new start packet sequence number */
+        pb->first_seq_num = pb->packets[pb->start].seq_num;
+    } else
+        ++ pb->count;
+
+    pb->latest_seq_num = seq;
+
+    return packet;
+}
+
+/* Write packet data to packet buffer */
+void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length) {
+    pa_raop_packet_element *packet;
+
+    pa_assert(pb);
+    pa_assert(packet_data);
+    pa_assert(packet_length <= PACKET_SIZE_MAX);
+
+    packet = pb_prepare_write(pb, seq_num);
+    packet->seq_num = seq_num;
+    packet->length = packet_length + RETRANS_HEADER_ROOM;
+
+    /* Insert RETRANS_HEADER_ROOM bytes in front of packet data,
+       for retransmission header */
+    memset(packet->data, 0, RETRANS_HEADER_ROOM);
+    memcpy(packet->data + RETRANS_HEADER_ROOM, packet_data, packet_length);
+}
+
+/* l < r?, considers wrapping */
+static bool seq_lt(uint16_t l, uint16_t r) {
+    return l - r > USHRT_MAX/2;
+}
+
+/* Random access to packet from buffer by sequence number for (re-)sending. */
+ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data) {
+    uint16_t index = 0; /* Index of requested packet */
+    pa_raop_packet_element *packet;
+
+    /* If the buffer is empty, there is no use in calculating indices */
+    if (pb_is_empty(pb))
+        return -1;
+
+    /* If the requested packet is too old (seq_num below first seq number
+       in buffer) or too young (seq_num greater than current seq number),
+       do nothing and return */
+    if (seq_lt(seq_num, pb->first_seq_num))
+        return -1;
+
+    index = (uint16_t) (seq_num - pb->first_seq_num);
+    if (index >= pb->count)
+        return -1;
+
+    /*  Index of the requested packet in the buffer is calculated
+        using the first sequence number stored in the buffer.
+        The offset (seq_num - first_seq_num) is used to access the array. */
+    packet = &pb->packets[(pb->start + index) % pb->size];
+
+    pa_assert(packet->data[RETRANS_HEADER_ROOM + 2] == (seq_num >> 8));
+    pa_assert(packet->data[RETRANS_HEADER_ROOM + 3] == (seq_num & 0xff));
+    pa_assert(packet_data);
+
+    *packet_data = packet->data;
+
+    return packet->length;
+}
diff --git a/src/modules/raop/raop-packet-buffer.h b/src/modules/raop/raop-packet-buffer.h
new file mode 100644
index 0000000..d8a08a0
--- /dev/null
+++ b/src/modules/raop/raop-packet-buffer.h
@@ -0,0 +1,42 @@
+#ifndef RAOP_PACKET_BUFFER_H_INCLUDED
+#define RAOP_PACKET_BUFFER_H_INCLUDED
+
+/***
+  Circular buffer for RTP audio packets with random access support
+  by RTP sequence number.
+
+  Copyright 2013 Matthias Wabersich, Hajime Fujita
+
+  This 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.1 of the License,
+  or (at your option) any later version.
+
+  This 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.
+
+***/
+
+struct pa_raop_packet_element;
+typedef struct pa_raop_packet_element pa_raop_packet_element;
+
+struct pa_raop_packet_buffer;
+typedef struct pa_raop_packet_buffer pa_raop_packet_buffer;
+
+/* Allocates a new circular packet buffer
+   size: Maximum number of packets to store */
+pa_raop_packet_buffer *pa_raop_pb_new(size_t size);
+void pa_raop_pb_clear(pa_raop_packet_buffer *pb);
+void pa_raop_pb_delete(pa_raop_packet_buffer *pb);
+
+void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length);
+ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data);
+
+#endif /* RAOP_PACKET_BUFFER_H_INCLUDED */
diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
new file mode 100644
index 0000000..4f743be
--- /dev/null
+++ b/src/modules/raop/raop-sink.c
@@ -0,0 +1,665 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2004-2006 Lennart Poettering
+  Copyright 2008 Colin Guthrie
+  Copyright 2013 Martin Blanchard
+
+  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.1 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_LINUX_SOCKIOS_H
+#include <linux/sockios.h>
+#endif
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/i18n.h>
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/poll.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/time-smoother.h>
+
+#include "raop-sink.h"
+#include "raop-client.h"
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+    pa_sink *sink;
+
+    pa_thread *thread;
+    pa_thread_mq thread_mq;
+    pa_rtpoll *rtpoll;
+    pa_rtpoll_item *rtpoll_item;
+    bool oob;
+
+    pa_raop_client *raop;
+    pa_raop_protocol_t protocol;
+    pa_raop_encryption_t encryption;
+    pa_raop_codec_t codec;
+
+    size_t block_size;
+    pa_memchunk memchunk;
+
+    pa_usec_t delay;
+    pa_usec_t start;
+    pa_smoother *smoother;
+    uint64_t write_count;
+};
+
+enum {
+    PA_SINK_MESSAGE_SET_RAOP_STATE = PA_SINK_MESSAGE_MAX
+};
+
+static void userdata_free(struct userdata *u);
+
+static void raop_state_cb(pa_raop_state_t state, void *userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(u);
+
+    pa_log_debug("State change recieved, informing IO thread...");
+
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL, NULL);
+}
+
+static pa_usec_t sink_get_latency(const struct userdata *u) {
+    pa_usec_t r, now;
+    int64_t latency;
+
+    pa_assert(u);
+    pa_assert(u->smoother);
+
+    now = pa_rtclock_now();
+    now = pa_smoother_get(u->smoother, now);
+
+    latency = pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now;
+    r = latency >= 0 ? (pa_usec_t) latency : 0;
+
+    return r;
+}
+
+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;
+
+    pa_assert(u);
+    pa_assert(u->raop);
+
+    switch (code) {
+        case PA_SINK_MESSAGE_SET_STATE: {
+            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+                case PA_SINK_SUSPENDED: {
+                    pa_log_debug("RAOP: SUSPENDED");
+
+                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+
+                    pa_smoother_pause(u->smoother, pa_rtclock_now());
+                    /* Issue a TEARDOWN if we are still connected */
+                    if (pa_raop_client_is_alive(u->raop)) {
+                        pa_raop_client_teardown(u->raop);
+                    }
+
+                    break;
+                }
+
+                case PA_SINK_IDLE: {
+                    pa_log_debug("RAOP: IDLE");
+
+                    /* Issue a FLUSH if we're comming from running state */
+                    if (u->sink->thread_info.state == PA_SINK_RUNNING) {
+                        pa_rtpoll_set_timer_disabled(u->rtpoll);
+                        pa_raop_client_flush(u->raop);
+                    }
+
+                    break;
+                }
+
+                case PA_SINK_RUNNING: {
+                    pa_usec_t now;
+
+                    pa_log_debug("RAOP: RUNNING");
+
+                    now = pa_rtclock_now();
+                    pa_smoother_resume(u->smoother, now, true);
+
+                    if (!pa_raop_client_is_alive(u->raop)) {
+                        /* Connecting will trigger a RECORD and start steaming */
+                        pa_raop_client_announce(u->raop);
+                    } else if (!pa_raop_client_can_stream(u->raop)) {
+                        /* RECORD alredy sent, simply start streaming */
+                        pa_raop_client_stream(u->raop);
+                        pa_rtpoll_set_timer_absolute(u->rtpoll, now);
+                        u->write_count = 0;
+                        u->start = now;
+                    }
+
+                    break;
+                }
+
+                case PA_SINK_UNLINKED:
+                case PA_SINK_INIT:
+                case PA_SINK_INVALID_STATE:
+                    break;
+            }
+
+            break;
+        }
+
+        case PA_SINK_MESSAGE_GET_LATENCY: {
+            pa_usec_t r = 0;
+
+            if (pa_raop_client_can_stream(u->raop))
+                r = sink_get_latency(u);
+
+            *((pa_usec_t*) data) = r;
+
+            return 0;
+        }
+
+        case PA_SINK_MESSAGE_SET_RAOP_STATE: {
+            switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) {
+                case PA_RAOP_AUTHENTICATED: {
+                    if (!pa_raop_client_is_authenticated(u->raop)) {
+                        pa_module_unload_request(u->module, true);
+                    }
+
+                    return 0;
+                }
+
+                case PA_RAOP_CONNECTED: {
+                    pa_assert(!u->rtpoll_item);
+
+                    u->oob = pa_raop_client_register_pollfd(u->raop, u->rtpoll, &u->rtpoll_item);
+
+                    return 0;
+                }
+
+                case PA_RAOP_RECORDING: {
+                    pa_usec_t now;
+
+                    now = pa_rtclock_now();
+                    pa_rtpoll_set_timer_absolute(u->rtpoll, now);
+                    u->write_count = 0;
+                    u->start = now;
+
+                    if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+                        /* Our stream has been suspended so we just flush it... */
+                        pa_rtpoll_set_timer_disabled(u->rtpoll);
+                        pa_raop_client_flush(u->raop);
+                    }
+
+                    return 0;
+                }
+
+                case PA_RAOP_INVALID_STATE:
+                case PA_RAOP_DISCONNECTED: {
+                    unsigned int nbfds = 0;
+                    struct pollfd *pollfd;
+                    unsigned int i;
+
+                    if (u->rtpoll_item) {
+                        pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
+                        for (i = 0; i < nbfds; i++) {
+                            if (pollfd && pollfd->fd > 0)
+                               pa_close(pollfd->fd);
+                            pollfd++;
+                        }
+                        pa_rtpoll_item_free(u->rtpoll_item);
+                        u->rtpoll_item = NULL;
+                    }
+
+                    if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
+                        pa_rtpoll_set_timer_disabled(u->rtpoll);
+                    else
+                        pa_module_unload_request(u->module, true);
+
+                    return 0;
+                }
+            }
+
+            return 0;
+        }
+    }
+
+    return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static void sink_set_volume_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+    pa_cvolume hw;
+    pa_volume_t v, v_orig;
+    char t[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+    pa_assert(u);
+
+    /* If we're muted we don't need to do anything. */
+    if (s->muted)
+        return;
+
+    /* Calculate the max volume of all channels.
+     * We'll use this as our (single) volume on the APEX device and emulate
+     * any variation in channel volumes in software. */
+    v = pa_cvolume_max(&s->real_volume);
+
+    v_orig = v;
+    v = pa_raop_client_adjust_volume(u->raop, v_orig);
+
+    pa_log_debug("Volume adjusted: orig=%u adjusted=%u", v_orig, v);
+
+    /* Create a pa_cvolume version of our single value. */
+    pa_cvolume_set(&hw, s->sample_spec.channels, v);
+
+    /* Perform any software manipulation of the volume needed. */
+    pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw);
+
+    pa_log_debug("Requested volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &s->real_volume, &s->channel_map, false));
+    pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &hw, &s->channel_map, false));
+    pa_log_debug("Calculated software volume: %s",
+                 pa_cvolume_snprint_verbose(t, sizeof(t), &s->soft_volume, &s->channel_map, true));
+
+    /* Any necessary software volume manipulation is done so set
+     * our hw volume (or v as a single value) on the device. */
+    pa_raop_client_set_volume(u->raop, v);
+}
+
+static void sink_set_mute_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->raop);
+
+    if (s->muted) {
+        pa_raop_client_set_volume(u->raop, PA_VOLUME_MUTED);
+    } else {
+        sink_set_volume_cb(s);
+    }
+}
+
+static void thread_func(void *userdata) {
+    struct userdata *u = userdata;
+    size_t offset = 0;
+
+    pa_assert(u);
+
+    pa_log_debug("Thread starting up");
+
+    pa_thread_mq_install(&u->thread_mq);
+    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+
+    for (;;) {
+        struct pollfd *pollfd = NULL;
+        unsigned int i, nbfds = 0;
+        pa_usec_t now, estimated, intvl;
+        uint64_t position;
+        ssize_t written;
+        size_t index;
+        int ret;
+
+        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+            if (u->sink->thread_info.rewind_requested)
+                pa_sink_process_rewind(u->sink, 0);
+        }
+
+        /* Polling (audio data + control socket + timing socket). */
+        if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
+            goto fail;
+        else if (ret == 0)
+            goto finish;
+
+        if (u->rtpoll_item) {
+            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
+            /* If !oob: streaming driven by pollds (POLLOUT) */
+            if (pollfd && !u->oob && !pollfd->revents) {
+                for (i = 0; i < nbfds; i++) {
+                    pollfd->events = POLLOUT;
+                    pollfd->revents = 0;
+
+                    pollfd++;
+                }
+
+                continue;
+            }
+
+            /* if oob: streaming managed by timing, pollfd for oob sockets */
+            if (pollfd && u->oob && !pa_rtpoll_timer_elapsed(u->rtpoll)) {
+                uint8_t packet[32];
+                ssize_t read;
+
+                for (i = 0; i < nbfds; i++) {
+                    if (pollfd->revents & pollfd->events) {
+                        pollfd->revents = 0;
+                        read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
+                        pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read);
+                    }
+
+                    pollfd++;
+                }
+
+                continue;
+            }
+        }
+
+        if (u->sink->thread_info.state != PA_SINK_RUNNING)
+            continue;
+        if (!pa_raop_client_can_stream(u->raop))
+            continue;
+
+        if (u->memchunk.length <= 0) {
+            if (u->memchunk.memblock)
+                pa_memblock_unref(u->memchunk.memblock);
+            pa_memchunk_reset(&u->memchunk);
+
+            /* Grab unencoded audio data from PulseAudio */
+            pa_sink_render_full(u->sink, u->block_size, &u->memchunk);
+            offset = u->memchunk.index;
+        }
+
+        pa_assert(u->memchunk.length > 0);
+
+        index = u->memchunk.index;
+        written = pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset);
+        if (written < 0) {
+            if (errno == EINTR) {
+                /* Just try again. */
+                pa_log_debug("Failed to write data to FIFO (EINTR), retrying");
+                goto fail;
+            } else if (errno != EAGAIN) {
+                /* Buffer is full, wait for POLLOUT. */
+                pollfd->events = POLLOUT;
+                pollfd->revents = 0;
+            } else {
+                pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+                goto fail;
+            }
+        } else {
+            u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index;
+            position = u->write_count - pa_usec_to_bytes(u->delay, &u->sink->sample_spec);
+
+            now = pa_rtclock_now();
+            estimated = pa_bytes_to_usec(position, &u->sink->sample_spec);
+            pa_smoother_put(u->smoother, now, estimated);
+
+            if (u->oob && !pollfd->revents) {
+                /* Sleep until next packet transmission */
+                intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
+                pa_rtpoll_set_timer_absolute(u->rtpoll, intvl);
+            } else if (!u->oob) {
+                if (u->memchunk.length > 0) {
+                    pollfd->events = POLLOUT;
+                    pollfd->revents = 0;
+                } else {
+                    intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
+                    pa_rtpoll_set_timer_absolute(u->rtpoll, intvl);
+                    pollfd->revents = 0;
+                    pollfd->events = 0;
+                }
+            }
+        }
+    }
+
+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");
+}
+
+pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
+    struct userdata *u = NULL;
+    pa_sample_spec ss;
+    char *thread_name = NULL;
+    const char *server, *protocol, *encryption, *codec;
+    const char /* *username, */ *password;
+    pa_sink_new_data data;
+    const char *name = NULL;
+    char * nick = NULL;
+
+    pa_assert(m);
+    pa_assert(ma);
+
+    ss = m->core->default_sample_spec;
+    if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+        pa_log("Failed to parse sample specification");
+        goto fail;
+    }
+
+    if (!(server = pa_modargs_get_value(ma, "server", NULL))) {
+        pa_log("Failed to parse server argument");
+        goto fail;
+    }
+
+    if (!(protocol = pa_modargs_get_value(ma, "protocol", NULL))) {
+        pa_log("Failed to parse protocol argument");
+        goto fail;
+    }
+
+    u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
+    u->module = m;
+    u->thread = NULL;
+    u->rtpoll = pa_rtpoll_new();
+    u->rtpoll_item = NULL;
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+    u->oob = true;
+
+    u->block_size = 0;
+    pa_memchunk_reset(&u->memchunk);
+
+    u->delay = 0;
+    u->smoother = pa_smoother_new(
+            PA_USEC_PER_SEC,
+            PA_USEC_PER_SEC*2,
+            true,
+            true,
+            10,
+            0,
+            false);
+    u->write_count = 0;
+
+    if (pa_streq(protocol, "TCP")) {
+        u->protocol = PA_RAOP_PROTOCOL_TCP;
+    } else if (pa_streq(protocol, "UDP")) {
+        u->protocol = PA_RAOP_PROTOCOL_UDP;
+    } else {
+        pa_log("Unsupported transport protocol argument: %s", protocol);
+        goto fail;
+    }
+
+    encryption = pa_modargs_get_value(ma, "encryption", NULL);
+    codec = pa_modargs_get_value(ma, "codec", NULL);
+
+    if (!encryption) {
+        u->encryption = PA_RAOP_ENCRYPTION_NONE;
+    } else if (pa_streq(encryption, "none")) {
+        u->encryption = PA_RAOP_ENCRYPTION_NONE;
+    } else if (pa_streq(encryption, "RSA")) {
+        u->encryption = PA_RAOP_ENCRYPTION_RSA;
+    } else {
+        pa_log("Unsupported encryption type argument: %s", encryption);
+        goto fail;
+    }
+
+    if (!codec) {
+        u->codec = PA_RAOP_CODEC_PCM;
+    } else if (pa_streq(codec, "PCM")) {
+        u->codec = PA_RAOP_CODEC_PCM;
+    } else if (pa_streq(codec, "ALAC")) {
+        u->codec = PA_RAOP_CODEC_ALAC;
+    } else {
+        pa_log("Unsupported audio codec argument: %s", codec);
+        goto fail;
+    }
+
+    pa_sink_new_data_init(&data);
+    data.driver = driver;
+    data.module = m;
+
+    if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) {
+        pa_sink_new_data_set_name(&data, name);
+    } else {
+        if ((name = pa_modargs_get_value(ma, "name", NULL)))
+            nick = pa_sprintf_malloc("raop_client.%s", name);
+        if (!nick)
+            nick = pa_sprintf_malloc("raop_client.%s", server);
+        pa_sink_new_data_set_name(&data, nick);
+        pa_xfree(nick);
+    }
+
+    pa_sink_new_data_set_sample_spec(&data, &ss);
+
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music");
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server);
+
+    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+        pa_log("Invalid properties");
+        pa_sink_new_data_done(&data);
+        goto fail;
+    }
+
+    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY | PA_SINK_NETWORK);
+    pa_sink_new_data_done(&data);
+
+    if (!(u->sink)) {
+        pa_log("Failed to create sink object");
+        goto fail;
+    }
+
+    u->sink->parent.process_msg = sink_process_msg;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
+    u->sink->userdata = u;
+
+    pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+    pa_sink_set_rtpoll(u->sink, u->rtpoll);
+
+    u->raop = pa_raop_client_new(u->core, server, u->protocol, u->encryption, u->codec);
+
+    if (!(u->raop)) {
+        pa_log("Failed to create RAOP client object");
+        goto fail;
+    }
+
+    /* The number of frames per blocks is not negotiable... */
+    pa_raop_client_get_frames_per_block(u->raop, &u->block_size);
+    u->block_size *= pa_frame_size(&ss);
+    pa_sink_set_max_request(u->sink, u->block_size);
+
+    pa_raop_client_set_state_callback(u->raop, raop_state_cb, u);
+
+    thread_name = pa_sprintf_malloc("raop-sink-%s", server);
+    if (!(u->thread = pa_thread_new(thread_name, thread_func, u))) {
+        pa_log("Failed to create sink thread");
+        goto fail;
+    }
+    pa_xfree(thread_name);
+    thread_name = NULL;
+
+    pa_sink_put(u->sink);
+
+    /* username = pa_modargs_get_value(ma, "username", NULL); */
+    password = pa_modargs_get_value(ma, "password", NULL);
+    pa_raop_client_authenticate(u->raop, password );
+
+    return u->sink;
+
+fail:
+    pa_xfree(thread_name);
+    pa_xfree(nick);
+
+    if (u)
+        userdata_free(u);
+
+    return NULL;
+}
+
+static void userdata_free(struct userdata *u) {
+    pa_assert(u);
+
+    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);
+    u->sink = NULL;
+
+    if (u->rtpoll_item)
+        pa_rtpoll_item_free(u->rtpoll_item);
+    if (u->rtpoll)
+        pa_rtpoll_free(u->rtpoll);
+    u->rtpoll_item = NULL;
+    u->rtpoll = NULL;
+
+    if (u->memchunk.memblock)
+        pa_memblock_unref(u->memchunk.memblock);
+
+    if (u->raop)
+        pa_raop_client_free(u->raop);
+    u->raop = NULL;
+
+    if (u->smoother)
+        pa_smoother_free(u->smoother);
+    u->smoother = NULL;
+
+    pa_xfree(u);
+}
+
+void pa_raop_sink_free(pa_sink *s) {
+    struct userdata *u;
+
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
+
+    userdata_free(u);
+}
diff --git a/src/modules/raop/raop-sink.h b/src/modules/raop/raop-sink.h
new file mode 100644
index 0000000..dfa2f0c
--- /dev/null
+++ b/src/modules/raop/raop-sink.h
@@ -0,0 +1,33 @@
+#ifndef fooraopsinkfoo
+#define fooraopsinkfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+
+  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.1 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.
+***/
+
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/sink.h>
+
+pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver);
+
+void pa_raop_sink_free(pa_sink *s);
+
+#endif
diff --git a/src/modules/raop/raop-util.c b/src/modules/raop/raop-util.c
new file mode 100644
index 0000000..3e3eb00
--- /dev/null
+++ b/src/modules/raop/raop-util.c
@@ -0,0 +1,210 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+  Copyright Kungliga Tekniska Høgskolan & 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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+  The base64 implementation was originally inspired by a file developed
+  by Kungliga Tekniska högskolan.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/err.h>
+#include <openssl/md5.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "raop-util.h"
+
+#ifndef MD5_DIGEST_LENGTH
+#define MD5_DIGEST_LENGTH 16
+#endif
+
+#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
+
+#define BASE64_DECODE_ERROR 0xffffffff
+
+static const char base64_chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int char_position(char c) {
+    if (c >= 'A' && c <= 'Z')
+        return c - 'A' + 0;
+    if (c >= 'a' && c <= 'z')
+        return c - 'a' + 26;
+    if (c >= '0' && c <= '9')
+        return c - '0' + 52;
+    if (c == '+')
+        return 62;
+    if (c == '/')
+        return 63;
+
+    return -1;
+}
+
+static unsigned int token_decode(const char *token) {
+    unsigned int val = 0;
+    int marker = 0;
+    int i;
+
+    if (strlen(token) < 4)
+        return BASE64_DECODE_ERROR;
+    for (i = 0; i < 4; i++) {
+        val *= 64;
+        if (token[i] == '=')
+            marker++;
+        else if (marker > 0)
+            return BASE64_DECODE_ERROR;
+        else {
+            int lpos = char_position(token[i]);
+            if (lpos < 0)
+                return BASE64_DECODE_ERROR;
+            val += lpos;
+        }
+    }
+
+    if (marker > 2)
+        return BASE64_DECODE_ERROR;
+
+    return (marker << 24) | val;
+}
+
+int pa_raop_base64_encode(const void *data, int len, char **str) {
+    const unsigned char *q;
+    char *p, *s = NULL;
+    int i, c;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    p = s = pa_xnew(char, len * 4 / 3 + 4);
+    q = (const unsigned char *) data;
+    for (i = 0; i < len;) {
+        c = q[i++];
+        c *= 256;
+        if (i < len)
+            c += q[i];
+        i++;
+        c *= 256;
+        if (i < len)
+            c += q[i];
+        i++;
+        p[0] = base64_chars[(c & 0x00fc0000) >> 18];
+        p[1] = base64_chars[(c & 0x0003f000) >> 12];
+        p[2] = base64_chars[(c & 0x00000fc0) >> 6];
+        p[3] = base64_chars[(c & 0x0000003f) >> 0];
+        if (i > len)
+            p[3] = '=';
+        if (i > len + 1)
+            p[2] = '=';
+        p += 4;
+    }
+
+    *p = 0;
+    *str = s;
+    return strlen(s);
+}
+
+int pa_raop_base64_decode(const char *str, void *data) {
+    const char *p;
+    unsigned char *q;
+
+    pa_assert(str);
+    pa_assert(data);
+
+    q = data;
+    for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
+        unsigned int val = token_decode(p);
+        unsigned int marker = (val >> 24) & 0xff;
+        if (val == BASE64_DECODE_ERROR)
+            return -1;
+        *q++ = (val >> 16) & 0xff;
+        if (marker < 2)
+            *q++ = (val >> 8) & 0xff;
+        if (marker < 1)
+            *q++ = val & 0xff;
+    }
+
+    return q - (unsigned char *) data;
+}
+
+int pa_raop_md5_hash(const char *data, int len, char **str) {
+    unsigned char d[MD5_DIGEST_LENGTH];
+    char *s = NULL;
+    int i;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    MD5((unsigned char*) data, len, d);
+    s = pa_xnew(char, MD5_HASH_LENGTH);
+    for (i = 0; i < MD5_DIGEST_LENGTH; i++)
+        sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
+
+    *str = s;
+    s[MD5_HASH_LENGTH] = 0;
+    return strlen(s);
+}
+
+int pa_raop_basic_response(const char *user, const char *pwd, char **str) {
+    char *tmp, *B = NULL;
+
+    pa_assert(str);
+
+    tmp = pa_sprintf_malloc("%s:%s", user, pwd);
+    pa_raop_base64_encode(tmp, strlen(tmp), &B);
+    pa_xfree(tmp);
+
+    *str = B;
+    return strlen(B);
+}
+
+int pa_raop_digest_response(const char *user, const char *realm, const char *password,
+                            const char *nonce, const char *uri, char **str) {
+    char *A1, *HA1, *A2, *HA2;
+    char *tmp, *KD = NULL;
+
+    pa_assert(str);
+
+    A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
+    pa_raop_md5_hash(A1, strlen(A1), &HA1);
+    pa_xfree(A1);
+
+    A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
+    pa_raop_md5_hash(A2, strlen(A2), &HA2);
+    pa_xfree(A2);
+
+    tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
+    pa_raop_md5_hash(tmp, strlen(tmp), &KD);
+    pa_xfree(tmp);
+
+    pa_xfree(HA1);
+    pa_xfree(HA2);
+
+    *str = KD;
+    return strlen(KD);
+}
diff --git a/src/modules/raop/raop-util.h b/src/modules/raop/raop-util.h
new file mode 100644
index 0000000..d3f7566
--- /dev/null
+++ b/src/modules/raop/raop-util.h
@@ -0,0 +1,39 @@
+#ifndef fooraoputilfoo
+#define fooraoputilfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008 Colin Guthrie
+  Copyright Kungliga Tekniska högskolan
+  Copyright 2013 Martin Blanchard
+
+  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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+  This file was originally inspired by a file developed by
+  Kungliga Tekniska högskolan.
+***/
+
+int pa_raop_base64_encode(const void *data, int len, char **str);
+int pa_raop_base64_decode(const char *str, void *data);
+
+int pa_raop_md5_hash(const char *data, int len, char **str);
+
+int pa_raop_basic_response(const char *user, const char *pwd, char **str);
+int pa_raop_digest_response(const char *user, const char *realm, const char *password,
+                            const char *nonce, const char *uri, char **str);
+
+#endif
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
deleted file mode 100644
index 1323cd0..0000000
--- a/src/modules/raop/raop_client.c
+++ /dev/null
@@ -1,1722 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <math.h>
-
-#ifdef HAVE_SYS_FILIO_H
-#include <sys/filio.h>
-#endif
-
-#include <pulse/xmalloc.h>
-#include <pulse/timeval.h>
-#include <pulse/sample.h>
-
-#include <pulsecore/core-error.h>
-#include <pulsecore/core-rtclock.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/iochannel.h>
-#include <pulsecore/arpa-inet.h>
-#include <pulsecore/socket-util.h>
-#include <pulsecore/log.h>
-#include <pulsecore/parseaddr.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/memchunk.h>
-#include <pulsecore/random.h>
-
-#include "raop_client.h"
-
-#include "rtsp_client.h"
-#include "raop_packet_buffer.h"
-#include "raop_crypto.h"
-#include "raop_util.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
-
-#define USER_AGENT "iTunes/11.0.4 (Windows; N)"
-#define USER_NAME "iTunes"
-
-#define DEFAULT_RAOP_PORT 5000
-#define UDP_DEFAULT_AUDIO_PORT 6000
-#define UDP_DEFAULT_CONTROL_PORT 6001
-#define UDP_DEFAULT_TIMING_PORT 6002
-
-#define UDP_DEFAULT_PKT_BUF_SIZE 1000
-
-typedef enum {
-    UDP_PAYLOAD_TIMING_REQUEST = 0x52,
-    UDP_PAYLOAD_TIMING_RESPONSE = 0x53,
-    UDP_PAYLOAD_SYNCHRONIZATION = 0x54,
-    UDP_PAYLOAD_RETRANSMIT_REQUEST = 0x55,
-    UDP_PAYLOAD_RETRANSMIT_REPLY = 0x56,
-    UDP_PAYLOAD_AUDIO_DATA = 0x60
-} pa_raop_udp_payload_type;
-
-struct pa_raop_client {
-    pa_core *core;
-    char *host;
-    uint16_t port;
-    pa_rtsp_client *rtsp;
-    char *sci, *sid;
-    char *pwd;
-
-    uint8_t jack_type;
-    uint8_t jack_status;
-
-    pa_raop_protocol_t protocol;
-
-    int encryption; /* Enable encryption? */
-    pa_raop_secret *aes;
-
-    uint16_t seq;
-    uint32_t rtptime;
-
-    /* Members only for the TCP protocol */
-    pa_socket_client *tcp_sc;
-    int tcp_fd;
-
-    pa_raop_client_cb_t tcp_callback;
-    void *tcp_userdata;
-    pa_raop_client_closed_cb_t tcp_closed_callback;
-    void *tcp_closed_userdata;
-
-    /* Members only for the UDP protocol */
-    uint16_t udp_my_control_port;
-    uint16_t udp_my_timing_port;
-    uint16_t udp_server_control_port;
-    uint16_t udp_server_timing_port;
-
-    int udp_stream_fd;
-    int udp_control_fd;
-    int udp_timing_fd;
-
-    uint32_t udp_ssrc;
-
-    bool is_recording;
-
-    bool udp_first_packet;
-    uint32_t udp_sync_interval;
-    uint32_t udp_sync_count;
-
-    pa_raop_client_auth_cb_t udp_auth_callback;
-    void *udp_auth_userdata;
-
-    pa_raop_client_setup_cb_t udp_setup_callback;
-    void *udp_setup_userdata;
-
-    pa_raop_client_record_cb_t udp_record_callback;
-    void *udp_record_userdata;
-
-    pa_raop_client_disconnected_cb_t udp_disconnected_callback;
-    void *udp_disconnected_userdata;
-
-    pa_raop_packet_buffer *packet_buffer;
-};
-
-/* Timming packet header (8x8):
- *  [0]   RTP v2: 0x80,
- *  [1]   Payload type: 0x53 | marker bit: 0x80,
- *  [2,3] Sequence number: 0x0007,
- *  [4,7] Timestamp: 0x00000000 (unused). */
-static const uint8_t udp_timming_header[8] = {
-    0x80, 0xd3, 0x00, 0x07,
-    0x00, 0x00, 0x00, 0x00
-};
-
-/* Sync packet header (8x8):
- *  [0]   RTP v2: 0x80,
- *  [1]   Payload type: 0x54 | marker bit: 0x80,
- *  [2,3] Sequence number: 0x0007,
- *  [4,7] Timestamp: 0x00000000 (to be set). */
-static const uint8_t udp_sync_header[8] = {
-    0x80, 0xd4, 0x00, 0x07,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t tcp_audio_header[16] = {
-    0x24, 0x00, 0x00, 0x00,
-    0xF0, 0xFF, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00,
-};
-
-/* Audio packet header (12x8):
- *  [0]    RTP v2: 0x80,
- *  [1]    Payload type: 0x60,
- *  [2,3]  Sequence number: 0x0000 (to be set),
- *  [4,7]  Timestamp: 0x00000000 (to be set),
- *  [8,12] SSRC: 0x00000000 (to be set).*/
-static const uint8_t udp_audio_header[12] = {
-    0x80, 0x60, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-/**
- * Function to write bits into a buffer.
- * @param buffer Handle to the buffer. It will be incremented if new data requires it.
- * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB)
- * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks
- * @param data The data to write
- * @param data_bit_len The number of bits from data to write
- */
-static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) {
-    int bits_left, bit_overflow;
-    uint8_t bit_data;
-
-    if (!data_bit_len)
-        return;
-
-    /* If bit pos is zero, we will definately use at least one bit from the current byte so size increments. */
-    if (!*bit_pos)
-        *size += 1;
-
-    /* Calc the number of bits left in the current byte of buffer. */
-    bits_left = 7 - *bit_pos  + 1;
-    /* Calc the overflow of bits in relation to how much space we have left... */
-    bit_overflow = bits_left - data_bit_len;
-    if (bit_overflow >= 0) {
-        /* We can fit the new data in our current byte.
-         * As we write from MSB->LSB we need to left shift by the overflow amount. */
-        bit_data = data << bit_overflow;
-        if (*bit_pos)
-            **buffer |= bit_data;
-        else
-            **buffer = bit_data;
-        /* If our data fits exactly into the current byte, we need to increment our pointer. */
-        if (0 == bit_overflow) {
-            /* Do not increment size as it will be incremented on next call as bit_pos is zero. */
-            *buffer += 1;
-            *bit_pos = 0;
-        } else {
-            *bit_pos += data_bit_len;
-        }
-    } else {
-        /* bit_overflow is negative, there for we will need a new byte from our buffer
-         * Firstly fill up what's left in the current byte. */
-        bit_data = data >> -bit_overflow;
-        **buffer |= bit_data;
-        /* Increment our buffer pointer and size counter. */
-        *buffer += 1;
-        *size += 1;
-        **buffer = data << (8 + bit_overflow);
-        *bit_pos = -bit_overflow;
-    }
-}
-
-static inline void rtrimchar(char *str, char rc) {
-    char *sp = str + strlen(str) - 1;
-    while (sp >= str && *sp == rc) {
-        *sp = '\0';
-        sp -= 1;
-    }
-}
-
-static void tcp_on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
-    pa_raop_client *c = userdata;
-
-    pa_assert(sc);
-    pa_assert(c);
-    pa_assert(c->tcp_sc == sc);
-    pa_assert(c->tcp_fd < 0);
-    pa_assert(c->tcp_callback);
-
-    pa_socket_client_unref(c->tcp_sc);
-    c->tcp_sc = NULL;
-
-    if (!io) {
-        pa_log("Connection failed: %s", pa_cstrerror(errno));
-        return;
-    }
-
-    c->tcp_fd = pa_iochannel_get_send_fd(io);
-
-    pa_iochannel_set_noclose(io, true);
-    pa_iochannel_free(io);
-
-    pa_make_tcp_socket_low_delay(c->tcp_fd);
-
-    pa_log_debug("Connection established");
-    c->tcp_callback(c->tcp_fd, c->tcp_userdata);
-}
-
-static inline uint64_t timeval_to_ntp(struct timeval *tv) {
-    uint64_t ntp = 0;
-
-    /* Converting micro seconds to a fraction. */
-    ntp = (uint64_t) tv->tv_usec * UINT32_MAX / PA_USEC_PER_SEC;
-    /* Moving reference from  1 Jan 1970 to 1 Jan 1900 (seconds). */
-    ntp |= (uint64_t) (tv->tv_sec + 0x83aa7e80) << 32;
-
-    return ntp;
-}
-
-static int connect_udp_socket(pa_raop_client *c, int fd, uint16_t port) {
-    struct sockaddr_in sa4;
-#ifdef HAVE_IPV6
-    struct sockaddr_in6 sa6;
-#endif
-    struct sockaddr *sa;
-    socklen_t salen;
-    sa_family_t af;
-
-    pa_zero(sa4);
-#ifdef HAVE_IPV6
-    pa_zero(sa6);
-#endif
-    if (inet_pton(AF_INET, c->host, &sa4.sin_addr) > 0) {
-        sa4.sin_family = af = AF_INET;
-        sa4.sin_port = htons(port);
-        sa = (struct sockaddr *) &sa4;
-        salen = sizeof(sa4);
-#ifdef HAVE_IPV6
-    } else if (inet_pton(AF_INET6, c->host, &sa6.sin6_addr) > 0) {
-        sa6.sin6_family = af = AF_INET6;
-        sa6.sin6_port = htons(port);
-        sa = (struct sockaddr *) &sa6;
-        salen = sizeof(sa6);
-#endif
-    } else {
-        pa_log("Invalid destination '%s'", c->host);
-        goto fail;
-    }
-
-    if (fd < 0 && (fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
-        pa_log("socket() failed: %s", pa_cstrerror(errno));
-        goto fail;
-    }
-
-    /* If the socket queue is full, let's drop packets */
-    pa_make_udp_socket_low_delay(fd);
-    pa_make_fd_nonblock(fd);
-
-    if (connect(fd, sa, salen) < 0) {
-        pa_log("connect() failed: %s", pa_cstrerror(errno));
-        goto fail;
-    }
-
-    pa_log_debug("Connected to %s on port %d (SOCK_DGRAM)", c->host, port);
-    return fd;
-
-fail:
-    if (fd >= 0)
-        pa_close(fd);
-
-    return -1;
-}
-
-static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
-    int fd = -1;
-    uint16_t port;
-    struct sockaddr_in sa4;
-#ifdef HAVE_IPV6
-    struct sockaddr_in6 sa6;
-#endif
-    struct sockaddr *sa;
-    uint16_t *sa_port;
-    socklen_t salen;
-    sa_family_t af;
-    int one = 1;
-
-    pa_assert(actual_port);
-
-    port = *actual_port;
-
-    pa_zero(sa4);
-#ifdef HAVE_IPV6
-    pa_zero(sa6);
-#endif
-    if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) {
-        sa4.sin_family = af = AF_INET;
-        sa4.sin_port = htons(port);
-        sa = (struct sockaddr *) &sa4;
-        salen = sizeof(sa4);
-        sa_port = &sa4.sin_port;
-#ifdef HAVE_IPV6
-    } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
-        sa6.sin6_family = af = AF_INET6;
-        sa6.sin6_port = htons(port);
-        sa = (struct sockaddr *) &sa6;
-        salen = sizeof(sa6);
-        sa_port = &sa6.sin6_port;
-#endif
-    } else {
-        pa_log("Could not determine which address family to use");
-        goto fail;
-    }
-
-    pa_zero(sa4);
-#ifdef HAVE_IPV6
-    pa_zero(sa6);
-#endif
-
-    if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
-        pa_log("socket() failed: %s", pa_cstrerror(errno));
-        goto fail;
-    }
-
-#ifdef SO_TIMESTAMP
-    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
-        pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
-        goto fail;
-    }
-#else
-    pa_log("SO_TIMESTAMP unsupported on this platform");
-    goto fail;
-#endif
-
-    one = 1;
-    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
-        pa_log("setsockopt(SO_REUSEADDR) failed: %s", pa_cstrerror(errno));
-        goto fail;
-    }
-
-    do {
-        *sa_port = htons(port);
-
-        if (bind(fd, sa, salen) < 0 && errno != EADDRINUSE) {
-            pa_log("bind_socket() failed: %s", pa_cstrerror(errno));
-            goto fail;
-        }
-        break;
-    } while (++port > 0);
-
-    pa_log_debug("Socket bound to port %d (SOCK_DGRAM)", port);
-    *actual_port = port;
-
-    return fd;
-
-fail:
-    if (fd >= 0)
-        pa_close(fd);
-
-    return -1;
-}
-
-static int udp_send_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received) {
-    uint32_t packet[8];
-    struct timeval tv;
-    ssize_t written = 0;
-    uint64_t trs = 0;
-    int rv = 1;
-
-    memcpy(packet, udp_timming_header, sizeof(udp_timming_header));
-    /* Copying originate timestamp from the incoming request packet. */
-    packet[2] = data[4];
-    packet[3] = data[5];
-    /* Set the receive timestamp to reception time. */
-    packet[4] = htonl(received >> 32);
-    packet[5] = htonl(received & 0xffffffff);
-    /* Set the transmit timestamp to current time. */
-    trs = timeval_to_ntp(pa_rtclock_get(&tv));
-    packet[6] = htonl(trs >> 32);
-    packet[7] = htonl(trs & 0xffffffff);
-
-    written = pa_loop_write(c->udp_timing_fd, packet, sizeof(packet), NULL);
-    if (written == sizeof(packet))
-        rv = 0;
-
-    return rv;
-}
-
-static int udp_send_sync_packet(pa_raop_client *c, uint32_t stamp) {
-    const uint32_t delay = 88200;
-    uint32_t packet[5];
-    struct timeval tv;
-    ssize_t written = 0;
-    uint64_t trs = 0;
-    int rv = 1;
-
-    memcpy(packet, udp_sync_header, sizeof(udp_sync_header));
-    if (c->udp_first_packet)
-        packet[0] |= 0x10;
-    stamp -= delay;
-    packet[1] = htonl(stamp);
-    /* Set the transmited timestamp to current time. */
-    trs = timeval_to_ntp(pa_rtclock_get(&tv));
-    packet[2] = htonl(trs >> 32);
-    packet[3] = htonl(trs & 0xffffffff);
-    stamp += delay;
-    packet[4] = htonl(stamp);
-
-    written = pa_loop_write(c->udp_control_fd, packet, sizeof(packet), NULL);
-    if (written == sizeof(packet))
-        rv = 0;
-
-    return rv;
-}
-
-static void udp_build_audio_header(pa_raop_client *c, uint32_t *buffer, size_t size) {
-    pa_assert(size >= sizeof(udp_audio_header));
-
-    memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
-    if (c->udp_first_packet)
-        buffer[0] |= htonl((uint32_t) 0x80 << 16);
-    buffer[0] |= htonl((uint32_t) c->seq);
-    buffer[1] = htonl(c->rtptime);
-    buffer[2] = htonl(c->udp_ssrc);
-}
-
-/* Audio retransmission header:
- * [0]    RTP v2: 0x80
- * [1]    Payload type: 0x56 + 0x80 (marker == on)
- * [2]    Unknown; seems always 0x01
- * [3]    Unknown; seems some random number around 0x20~0x40
- * [4,5]  Original RTP header htons(0x8060)
- * [6,7]  Packet sequence number to be retransmitted
- * [8,11] Original RTP timestamp on the lost packet */
-static void udp_build_retrans_header(uint32_t *buffer, size_t size, uint16_t seq_num) {
-    uint8_t x = 0x30; /* FIXME: what's this?? */
-
-    pa_assert(size >= sizeof(uint32_t) * 2);
-
-    buffer[0] = htonl((uint32_t) 0x80000000
-                      | ((uint32_t) UDP_PAYLOAD_RETRANSMIT_REPLY | 0x80) << 16
-                      | 0x0100
-                      | x);
-    buffer[1] = htonl((uint32_t) 0x80600000 | seq_num);
-}
-
-static ssize_t udp_send_audio_packet(pa_raop_client *c, bool retrans, uint8_t *buffer, size_t size) {
-    ssize_t length;
-    int fd = retrans ? c->udp_control_fd : c->udp_stream_fd;
-
-    length = pa_write(fd, buffer, size, NULL);
-    if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
-        pa_log_debug("Discarding audio packet %d due to EAGAIN", c->seq);
-        length = size;
-    }
-    return length;
-}
-
-static void do_rtsp_announce(pa_raop_client *c) {
-    char *key, *iv, *sac = NULL, *sdp;
-    uint16_t rand_data;
-    const char *ip;
-    char *url;
-
-    ip = pa_rtsp_localip(c->rtsp);
-    /* First of all set the url properly. */
-    url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
-    pa_rtsp_set_url(c->rtsp, url);
-    pa_xfree(url);
-
-    /* UDP protocol does not need "Apple-Challenge" at announce. */
-    if (c->protocol == RAOP_TCP) {
-        pa_random(&rand_data, sizeof(rand_data));
-        pa_raop_base64_encode(&rand_data, 8*sizeof(rand_data), &sac);
-        rtrimchar(sac, '=');
-        pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
-    }
-
-    if (c->encryption) {
-        iv = pa_raop_secret_get_iv(c->aes);
-        rtrimchar(iv, '=');
-        key = pa_raop_secret_get_key(c->aes);
-        rtrimchar(key, '=');
-
-        sdp = pa_sprintf_malloc(
-            "v=0\r\n"
-            "o=iTunes %s 0 IN IP4 %s\r\n"
-            "s=iTunes\r\n"
-            "c=IN IP4 %s\r\n"
-            "t=0 0\r\n"
-            "m=audio 0 RTP/AVP 96\r\n"
-            "a=rtpmap:96 AppleLossless\r\n"
-            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
-            "a=rsaaeskey:%s\r\n"
-            "a=aesiv:%s\r\n",
-            c->sid, ip, c->host,
-            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET,
-            key, iv);
-
-        pa_xfree(iv);
-        pa_xfree(key);
-    } else {
-        sdp = pa_sprintf_malloc(
-            "v=0\r\n"
-            "o=iTunes %s 0 IN IP4 %s\r\n"
-            "s=iTunes\r\n"
-            "c=IN IP4 %s\r\n"
-            "t=0 0\r\n"
-            "m=audio 0 RTP/AVP 96\r\n"
-            "a=rtpmap:96 AppleLossless\r\n"
-            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
-            c->sid, ip, c->host,
-            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET);
-    }
-
-    pa_rtsp_announce(c->rtsp, sdp);
-
-    pa_xfree(sac);
-    pa_xfree(sdp);
-}
-
-static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist* headers, void *userdata) {
-    pa_raop_client* c = userdata;
-    pa_assert(c);
-    pa_assert(rtsp);
-    pa_assert(rtsp == c->rtsp);
-
-    switch (state) {
-        case STATE_CONNECT: {
-            pa_log_debug("RAOP: CONNECTED");
-            do_rtsp_announce(c);
-            break;
-        }
-
-        case STATE_OPTIONS:
-            pa_log_debug("RAOP: OPTIONS");
-            break;
-
-        case STATE_ANNOUNCE:
-            pa_log_debug("RAOP: ANNOUNCED");
-            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
-            pa_rtsp_setup(c->rtsp, NULL);
-            break;
-
-        case STATE_SETUP: {
-            char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
-            pa_log_debug("RAOP: SETUP");
-            if (aj) {
-                char *token, *pc;
-                char delimiters[] = ";";
-                const char* token_state = NULL;
-                c->jack_type = JACK_TYPE_ANALOG;
-                c->jack_status = JACK_STATUS_DISCONNECTED;
-
-                while ((token = pa_split(aj, delimiters, &token_state))) {
-                    if ((pc = strstr(token, "="))) {
-                      *pc = 0;
-                      if (pa_streq(token, "type") && pa_streq(pc+1, "digital")) {
-                          c->jack_type = JACK_TYPE_DIGITAL;
-                      }
-                    } else {
-                        if (pa_streq(token, "connected"))
-                            c->jack_status = JACK_STATUS_CONNECTED;
-                    }
-                    pa_xfree(token);
-                }
-                pa_xfree(aj);
-            } else {
-                pa_log_warn("Audio Jack Status missing");
-            }
-            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
-            break;
-        }
-
-        case STATE_RECORD: {
-            uint32_t port = pa_rtsp_serverport(c->rtsp);
-            pa_log_debug("RAOP: RECORDED");
-
-            if (!(c->tcp_sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, port))) {
-                pa_log("failed to connect to server '%s:%d'", c->host, port);
-                return;
-            }
-            pa_socket_client_set_callback(c->tcp_sc, tcp_on_connection, c);
-            break;
-        }
-
-        case STATE_FLUSH:
-            pa_log_debug("RAOP: FLUSHED");
-            break;
-
-        case STATE_TEARDOWN:
-            pa_log_debug("RAOP: TEARDOWN");
-            break;
-
-        case STATE_SET_PARAMETER:
-            pa_log_debug("RAOP: SET_PARAMETER");
-            break;
-
-        case STATE_DISCONNECTED:
-            pa_assert(c->tcp_closed_callback);
-            pa_assert(c->rtsp);
-
-            pa_log_debug("RTSP control channel closed");
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-            if (c->tcp_fd > 0) {
-                /* We do not close the fd, we leave it to the closed callback to do that */
-                c->tcp_fd = -1;
-            }
-            if (c->tcp_sc) {
-                pa_socket_client_unref(c->tcp_sc);
-                c->tcp_sc = NULL;
-            }
-            pa_xfree(c->sid);
-            c->sid = NULL;
-            c->tcp_closed_callback(c->tcp_closed_userdata);
-            break;
-    }
-}
-
-static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) {
-    pa_raop_client *c = userdata;
-
-    pa_assert(c);
-    pa_assert(rtsp);
-    pa_assert(rtsp == c->rtsp);
-    pa_assert(STATUS_OK == status);
-
-    switch (state) {
-        case STATE_CONNECT: {
-            uint16_t rand;
-            char *sac;
-
-            /* Set the Apple-Challenge key */
-            pa_random(&rand, sizeof(rand));
-            pa_raop_base64_encode(&rand, 8*sizeof(rand), &sac);
-            rtrimchar(sac, '=');
-            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
-
-            pa_rtsp_options(c->rtsp);
-
-            pa_xfree(sac);
-            break;
-        }
-
-        case STATE_OPTIONS: {
-            pa_log_debug("RAOP: OPTIONS");
-
-            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
-            do_rtsp_announce(c);
-            break;
-        }
-
-        case STATE_ANNOUNCE: {
-            char *trs;
-
-            pa_assert(c->udp_control_fd < 0);
-            pa_assert(c->udp_timing_fd < 0);
-
-            c->udp_control_fd = open_bind_udp_socket(c, &c->udp_my_control_port);
-            if (c->udp_control_fd < 0)
-                goto error_announce;
-            c->udp_timing_fd  = open_bind_udp_socket(c, &c->udp_my_timing_port);
-            if (c->udp_timing_fd < 0)
-                goto error_announce;
-
-            trs = pa_sprintf_malloc("RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d",
-                c->udp_my_control_port,
-                c->udp_my_timing_port);
-
-            pa_rtsp_setup(c->rtsp, trs);
-
-            pa_xfree(trs);
-            break;
-
-        error_announce:
-            if (c->udp_control_fd > 0) {
-                pa_close(c->udp_control_fd);
-                c->udp_control_fd = -1;
-            }
-            if (c->udp_timing_fd > 0) {
-                pa_close(c->udp_timing_fd);
-                c->udp_timing_fd = -1;
-            }
-
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
-            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
-            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
-            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
-
-            pa_log_error("aborting RTSP announce, failed creating required sockets");
-        }
-
-        case STATE_SETUP: {
-            uint32_t stream_port = UDP_DEFAULT_AUDIO_PORT;
-            char *ajs, *trs, *token, *pc;
-            char delimiters[] = ";";
-            const char *token_state = NULL;
-            uint32_t port = 0;
-            int ret;
-
-            pa_log_debug("RAOP: SETUP");
-
-            ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
-            trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
-
-            if (ajs) {
-                c->jack_type = JACK_TYPE_ANALOG;
-                c->jack_status = JACK_STATUS_DISCONNECTED;
-
-                while ((token = pa_split(ajs, delimiters, &token_state))) {
-                    if ((pc = strstr(token, "="))) {
-                      *pc = 0;
-                      if (pa_streq(token, "type") && pa_streq(pc + 1, "digital"))
-                          c->jack_type = JACK_TYPE_DIGITAL;
-                    } else {
-                        if (pa_streq(token, "connected"))
-                            c->jack_status = JACK_STATUS_CONNECTED;
-                    }
-                    pa_xfree(token);
-                }
-
-            } else {
-                pa_log_warn("Audio-Jack-Status missing");
-            }
-
-            token_state = NULL;
-
-            if (trs) {
-                /* Now parse out the server port component of the response. */
-                while ((token = pa_split(trs, delimiters, &token_state))) {
-                    if ((pc = strstr(token, "="))) {
-                        *pc = 0;
-                        if (pa_streq(token, "control_port")) {
-                            port = 0;
-                            pa_atou(pc + 1, &port);
-                            c->udp_server_control_port = port;
-                        }
-                        if (pa_streq(token, "timing_port")) {
-                            port = 0;
-                            pa_atou(pc + 1, &port);
-                            c->udp_server_timing_port = port;
-                        }
-                        *pc = '=';
-                    }
-                    pa_xfree(token);
-                }
-            } else {
-                pa_log_warn("Transport missing");
-            }
-
-            pa_xfree(ajs);
-            pa_xfree(trs);
-
-            stream_port = pa_rtsp_serverport(c->rtsp);
-            if (stream_port == 0)
-                goto error;
-            if (c->udp_server_control_port == 0 || c->udp_server_timing_port == 0)
-                goto error;
-
-            pa_log_debug("Using server_port=%d, control_port=%d & timing_port=%d",
-                stream_port,
-                c->udp_server_control_port,
-                c->udp_server_timing_port);
-
-            pa_assert(c->udp_stream_fd < 0);
-            pa_assert(c->udp_control_fd >= 0);
-            pa_assert(c->udp_timing_fd >= 0);
-
-            c->udp_stream_fd = connect_udp_socket(c, -1, stream_port);
-            if (c->udp_stream_fd <= 0)
-                goto error;
-            ret = connect_udp_socket(c, c->udp_control_fd,
-                                     c->udp_server_control_port);
-            if (ret < 0)
-                goto error;
-            ret = connect_udp_socket(c, c->udp_timing_fd,
-                                     c->udp_server_timing_port);
-            if (ret < 0)
-                goto error;
-
-            c->udp_setup_callback(c->udp_control_fd, c->udp_timing_fd, c->udp_setup_userdata);
-            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
-
-            break;
-
-        error:
-            if (c->udp_stream_fd > 0) {
-                pa_close(c->udp_stream_fd);
-                c->udp_stream_fd = -1;
-            }
-            if (c->udp_control_fd > 0) {
-                pa_close(c->udp_control_fd);
-                c->udp_control_fd = -1;
-            }
-            if (c->udp_timing_fd > 0) {
-                pa_close(c->udp_timing_fd);
-                c->udp_timing_fd = -1;
-            }
-
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
-            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
-            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
-            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
-
-            pa_log_error("aborting RTSP setup, failed creating required sockets");
-
-            break;
-        }
-
-        case STATE_RECORD: {
-            int32_t latency = 0;
-            uint32_t rand;
-            char *alt;
-
-            pa_log_debug("RAOP: RECORD");
-
-            alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
-            /* Generate a random synchronization source identifier from this session. */
-            pa_random(&rand, sizeof(rand));
-            c->udp_ssrc = rand;
-
-            if (alt)
-                pa_atoi(alt, &latency);
-
-            c->udp_first_packet = true;
-            c->udp_sync_count = 0;
-
-            c->is_recording = true;
-
-            c->udp_record_callback(c->udp_setup_userdata);
-
-            pa_xfree(alt);
-            break;
-        }
-
-        case STATE_SET_PARAMETER: {
-            pa_log_debug("RAOP: SET_PARAMETER");
-
-            break;
-        }
-
-        case STATE_FLUSH: {
-            pa_log_debug("RAOP: FLUSHED");
-
-            c->is_recording = false;
-
-            break;
-        }
-
-        case STATE_TEARDOWN: {
-            pa_log_debug("RAOP: TEARDOWN");
-            pa_assert(c->udp_disconnected_callback);
-            pa_assert(c->rtsp);
-
-            c->is_recording = false;
-
-            pa_rtsp_disconnect(c->rtsp);
-
-            if (c->udp_stream_fd > 0) {
-                pa_close(c->udp_stream_fd);
-                c->udp_stream_fd = -1;
-            }
-
-            pa_log_debug("RTSP control channel closed (teardown)");
-
-            pa_raop_pb_clear(c->packet_buffer);
-
-            pa_rtsp_client_free(c->rtsp);
-            pa_xfree(c->sid);
-            c->rtsp = NULL;
-            c->sid = NULL;
-
-            /*
-              Callback for cleanup -- e.g. pollfd
-
-              Share the disconnected callback since TEARDOWN event
-              is essentially equivalent to DISCONNECTED.
-              In case some special treatment turns out to be required
-              for TEARDOWN in future, a new callback function may be
-              defined and used.
-            */
-            c->udp_disconnected_callback(c->udp_disconnected_userdata);
-
-            /* Control and timing fds are closed by udp_sink_process_msg,
-               after it disables poll */
-            c->udp_control_fd = -1;
-            c->udp_timing_fd = -1;
-
-            break;
-        }
-
-        case STATE_DISCONNECTED: {
-            pa_log_debug("RAOP: DISCONNECTED");
-            pa_assert(c->udp_disconnected_callback);
-            pa_assert(c->rtsp);
-
-            if (c->udp_stream_fd > 0) {
-                pa_close(c->udp_stream_fd);
-                c->udp_stream_fd = -1;
-            }
-
-            pa_log_debug("RTSP control channel closed (disconnected)");
-
-            pa_raop_pb_clear(c->packet_buffer);
-
-            pa_rtsp_client_free(c->rtsp);
-            pa_xfree(c->sid);
-            c->rtsp = NULL;
-            c->sid = NULL;
-
-            c->udp_disconnected_callback(c->udp_disconnected_userdata);
-            /* Control and timing fds are closed by udp_sink_process_msg,
-               after it disables poll */
-            c->udp_control_fd = -1;
-            c->udp_timing_fd = -1;
-
-            break;
-        }
-    }
-}
-
-static void rtsp_authentication_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) {
-    pa_raop_client *c = userdata;
-
-    pa_assert(c);
-    pa_assert(rtsp);
-    pa_assert(rtsp == c->rtsp);
-
-    switch (state) {
-        case STATE_CONNECT: {
-            char *sci = NULL, *sac = NULL;
-            uint16_t rac;
-            struct {
-                uint32_t ci1;
-                uint32_t ci2;
-            } rci;
-
-            pa_random(&rci, sizeof(rci));
-            /* Generate a random Client-Instance number */
-            sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2);
-            pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
-
-            pa_random(&rac, sizeof(rac));
-            /* Generate a random Apple-Challenge key */
-            pa_raop_base64_encode(&rac, 8*sizeof(rac), &sac);
-            pa_log_debug ("APPLECHALLENGE >> %s | %d", sac, (int) sizeof(rac));
-            rtrimchar(sac, '=');
-            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
-
-            pa_rtsp_options(c->rtsp);
-
-            pa_xfree(sac);
-            pa_xfree(sci);
-            break;
-        }
-
-        case STATE_OPTIONS: {
-            static bool waiting = false;
-            const char *current = NULL;
-            char space[] = " ";
-            char *token,*ath = NULL;
-            char *publ, *wath, *mth, *val;
-            char *realm = NULL, *nonce = NULL, *response = NULL;
-            char comma[] = ",";
-
-            pa_log_debug("RAOP: OPTIONS");
-            /* We do not consider the Apple-Response */
-            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
-
-            if (STATUS_UNAUTHORIZED == status) {
-                wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
-                if (true == waiting) {
-                    pa_xfree(wath);
-                    goto failure;
-                }
-
-                if (wath)
-                    mth = pa_split(wath, space, &current);
-                while ((token = pa_split(wath, comma, &current))) {
-                    val = NULL;
-                    if ((val = strstr(token, "="))) {
-                        if (NULL == realm && val > strstr(token, "realm"))
-                            realm = pa_xstrdup(val + 2);
-                        else if (NULL == nonce && val > strstr(token, "nonce"))
-                            nonce = pa_xstrdup(val + 2);
-                        val = NULL;
-                    }
-
-                    pa_xfree(token);
-                }
-
-                if (pa_safe_streq(mth, "Basic")) {
-                    rtrimchar(realm, '\"');
-
-                    pa_raop_basic_response(USER_NAME, c->pwd, &response);
-                    ath = pa_sprintf_malloc("Basic %s",
-                        response);
-
-                    pa_xfree(response);
-                    pa_xfree(realm);
-                } else if (pa_safe_streq(mth, "Digest")) {
-                    rtrimchar(realm, '\"');
-                    rtrimchar(nonce, '\"');
-
-                    pa_raop_digest_response(USER_NAME, realm, c->pwd, nonce, "*", &response);
-                    ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"",
-                        USER_NAME, realm, nonce,
-                        response);
-
-                    pa_xfree(response);
-                    pa_xfree(realm);
-                    pa_xfree(nonce);
-                } else {
-                    pa_log_error("unsupported authentication method: %s", mth);
-                    pa_xfree(wath);
-                    pa_xfree(mth);
-                    goto error;
-                }
-
-                pa_xfree(wath);
-                pa_xfree(mth);
-
-                pa_rtsp_add_header(c->rtsp, "Authorization", ath);
-                pa_xfree(ath);
-
-                waiting = true;
-                pa_rtsp_options(c->rtsp);
-                break;
-            }
-
-            if (STATUS_OK == status) {
-                publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
-                c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
-
-                if (c->pwd)
-                    pa_xfree(c->pwd);
-                pa_xfree(publ);
-                c->pwd = NULL;
-            }
-
-            if (c->udp_auth_callback)
-                c->udp_auth_callback((int) status, c->udp_auth_userdata);
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            waiting = false;
-            break;
-
-        failure:
-            if (c->udp_auth_callback)
-                c->udp_auth_callback((int) STATUS_UNAUTHORIZED, c->udp_auth_userdata);
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            pa_log_error("aborting RTSP authentication, wrong password");
-
-            waiting = false;
-            break;
-
-        error:
-            if (c->udp_auth_callback)
-                c->udp_auth_callback((int) status, c->udp_auth_userdata);
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            pa_log_error("aborting RTSP authentication, unexpected failure");
-
-            waiting = false;
-            break;
-        }
-
-        case STATE_ANNOUNCE:
-        case STATE_SETUP:
-        case STATE_RECORD:
-        case STATE_SET_PARAMETER:
-        case STATE_FLUSH:
-        case STATE_TEARDOWN:
-        case STATE_DISCONNECTED:
-        default: {
-            if (c->udp_auth_callback)
-                c->udp_auth_callback((int) STATUS_BAD_REQUEST, c->udp_auth_userdata);
-            pa_rtsp_client_free(c->rtsp);
-            c->rtsp = NULL;
-
-            if (c->sci)
-                pa_xfree(c->sci);
-            c->sci = NULL;
-
-            break;
-        }
-    }
-}
-
-pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) {
-    pa_raop_client* c;
-    pa_parsed_address a;
-    pa_sample_spec ss;
-
-    pa_assert(core);
-    pa_assert(host);
-
-    if (pa_parse_address(host, &a) < 0)
-        return NULL;
-
-    if (a.type == PA_PARSED_ADDRESS_UNIX) {
-        pa_xfree(a.path_or_host);
-        return NULL;
-    }
-
-    c = pa_xnew0(pa_raop_client, 1);
-    c->core = core;
-    c->tcp_fd = -1;
-    c->protocol = protocol;
-    c->udp_stream_fd = -1;
-    c->udp_control_fd = -1;
-    c->udp_timing_fd = -1;
-
-    c->encryption = 0;
-    c->aes = NULL;
-
-    c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
-    c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
-    c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
-    c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
-
-    c->host = a.path_or_host;
-    if (a.port)
-        c->port = a.port;
-    else
-        c->port = DEFAULT_RAOP_PORT;
-
-    c->is_recording = false;
-
-    c->udp_first_packet = true;
-
-    ss = core->default_sample_spec;
-    /* Packet sync interval should be around 1s. */
-    c->udp_sync_interval = ss.rate / UDP_FRAMES_PER_PACKET;
-    c->udp_sync_count = 0;
-
-    if (c->protocol == RAOP_TCP) {
-        if (pa_raop_client_connect(c)) {
-            pa_raop_client_free(c);
-            return NULL;
-        }
-    } else
-        c->packet_buffer = pa_raop_pb_new(UDP_DEFAULT_PKT_BUF_SIZE);
-
-    return c;
-}
-
-void pa_raop_client_free(pa_raop_client *c) {
-    pa_assert(c);
-
-    pa_raop_pb_delete(c->packet_buffer);
-
-    if (c->sid)
-        pa_xfree(c->sid);
-    if (c->sci)
-        pa_xfree(c->sci);
-    c->sci = c->sid = NULL;
-
-    if (c->aes)
-        pa_raop_secret_free(c->aes);
-    if (c->rtsp)
-        pa_rtsp_client_free(c->rtsp);
-    c->rtsp = NULL;
-
-    pa_xfree(c->host);
-    pa_xfree(c);
-}
-
-int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
-
-    pa_assert(c);
-
-    if (c->rtsp || c->pwd) {
-        pa_log_debug("Connection already in progress");
-        return 0;
-    }
-
-    c->pwd = NULL;
-    if (password)
-        c->pwd = pa_xstrdup(password);
-    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT);
-
-    pa_assert(c->rtsp);
-
-    pa_rtsp_set_callback(c->rtsp, rtsp_authentication_cb, c);
-    return pa_rtsp_connect(c->rtsp);
-}
-
-int pa_raop_client_connect(pa_raop_client *c) {
-    char *sci;
-    struct {
-        uint32_t a;
-        uint32_t b;
-        uint32_t c;
-    } rand_data;
-
-    pa_assert(c);
-
-    if (c->rtsp) {
-        pa_log_debug("Connection already in progress");
-        return 0;
-    }
-
-    if (c->protocol == RAOP_TCP)
-        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)");
-    else
-        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT);
-
-    /* Generate random instance id. */
-    pa_random(&rand_data, sizeof(rand_data));
-    c->sid = pa_sprintf_malloc("%u", rand_data.a);
-    sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c);
-    pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
-    pa_xfree(sci);
-    if (c->protocol == RAOP_TCP)
-        pa_rtsp_set_callback(c->rtsp, tcp_rtsp_cb, c);
-    else
-        pa_rtsp_set_callback(c->rtsp, udp_rtsp_cb, c);
-
-    c->is_recording = false;
-
-    return pa_rtsp_connect(c->rtsp);
-}
-
-int pa_raop_client_flush(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->rtsp != NULL) {
-        rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
-        c->udp_sync_count = 0;
-    }
-
-    return rv;
-}
-
-int pa_raop_client_teardown(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->rtsp != NULL)
-        rv = pa_rtsp_teardown(c->rtsp);
-
-    return rv;
-}
-
-int pa_raop_client_udp_is_authenticated(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->sci != NULL)
-        rv = 1;
-
-    return rv;
-}
-
-int pa_raop_client_udp_is_alive(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->udp_stream_fd > 0)
-        rv = 1;
-
-    return rv;
-}
-
-int pa_raop_client_udp_can_stream(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->is_recording && c->udp_stream_fd > 0)
-        rv = 1;
-
-    return rv;
-}
-
-int pa_raop_client_udp_stream(pa_raop_client *c) {
-    int rv = 0;
-
-    pa_assert(c);
-
-    if (c->rtsp != NULL && c->udp_stream_fd > 0) {
-        if (!c->is_recording) {
-            c->udp_first_packet = true;
-            c->udp_sync_count = 0;
-            c->is_recording = true;
-         }
-
-        rv = 1;
-    }
-
-    return rv;
-}
-
-int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
-    const uint32_t * data = NULL;
-    uint8_t payload = 0;
-    struct timeval tv;
-    uint64_t rci = 0;
-    int rv = 0;
-
-    pa_assert(c);
-    pa_assert(packet);
-
-    /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps. */
-    if (size != 32 || packet[0] != 0x80)
-    {
-        pa_log_debug("Received an invalid timing packet.");
-        return 1;
-    }
-
-    data = (uint32_t *) (packet + sizeof(udp_timming_header));
-    rci = timeval_to_ntp(pa_rtclock_get(&tv));
-    /* The market bit is always set (see rfc3550 for packet structure) ! */
-    payload = packet[1] ^ 0x80;
-    switch (payload) {
-        case UDP_PAYLOAD_TIMING_REQUEST:
-            rv = udp_send_timing_packet(c, data, rci);
-            break;
-        case UDP_PAYLOAD_TIMING_RESPONSE:
-        default:
-            pa_log_debug("Got an unexpected payload type on timing channel !");
-            return 1;
-    }
-
-    return rv;
-}
-
-static int udp_resend_packets(pa_raop_client *c, uint16_t seq_num, uint16_t num_packets) {
-    int rv = -1;
-    uint8_t *data = NULL;
-    ssize_t len = 0;
-    int i = 0;
-
-    pa_assert(c);
-    pa_assert(num_packets > 0);
-    pa_assert(c->packet_buffer);
-
-    for (i = seq_num; i < seq_num + num_packets; i++) {
-        len = pa_raop_pb_read_packet(c->packet_buffer, i, (uint8_t **) &data);
-
-        if (len > 0) {
-            ssize_t r;
-
-            /* Obtained buffer has a header room for retransmission
-               header */
-            udp_build_retrans_header((uint32_t *) data, len, seq_num);
-            r = udp_send_audio_packet(c, true /* retrans */, data, len);
-            if (r == len)
-                rv = 0;
-            else
-                rv = -1;
-        } else
-            pa_log_debug("Packet not found in retrans buffer: %u", i);
-    }
-
-    return rv;
-}
-
-int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
-    uint8_t payload = 0;
-    int rv = 0;
-
-    uint16_t seq_num;
-    uint16_t num_packets;
-
-    pa_assert(c);
-    pa_assert(packet);
-
-    if ((size != 20 && size != 8) || packet[0] != 0x80)
-    {
-        pa_log_debug("Received an invalid control packet.");
-        return 1;
-    }
-
-    /* The market bit is always set (see rfc3550 for packet structure) ! */
-
-    payload = packet[1] ^ 0x80;
-    switch (payload) {
-        case UDP_PAYLOAD_RETRANSMIT_REQUEST:
-            pa_assert(size == 8);
-
-            /* Requested start sequence number */
-            seq_num = ((uint16_t) packet[4]) << 8;
-            seq_num |= (uint16_t) packet[5];
-            /* Number of requested packets starting at requested seq. number */
-            num_packets = (uint16_t) packet[6] << 8;
-            num_packets |= (uint16_t) packet[7];
-            pa_log_debug("Resending %d packets starting at %d", num_packets, seq_num);
-            rv = udp_resend_packets(c, seq_num, num_packets);
-            break;
-
-        case UDP_PAYLOAD_RETRANSMIT_REPLY:
-            pa_log_debug("Received a retransmit reply packet on control port (this should never happen)");
-            break;
-
-        default:
-            pa_log_debug("Got an unexpected payload type on control channel: %u !", payload);
-            return 1;
-    }
-
-    return rv;
-}
-
-int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size) {
-    int rv = 0;
-
-    pa_assert(c);
-    pa_assert(size);
-
-    *size = UDP_FRAMES_PER_PACKET;
-
-    return rv;
-}
-
-ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block) {
-    uint8_t *buf = NULL;
-    ssize_t len;
-
-    pa_assert(c);
-    pa_assert(block);
-
-    /* Sync RTP & NTP timestamp if required. */
-    if (c->udp_first_packet || c->udp_sync_count >= c->udp_sync_interval) {
-        udp_send_sync_packet(c, c->rtptime);
-        c->udp_sync_count = 0;
-    } else {
-        c->udp_sync_count++;
-    }
-
-    buf = pa_memblock_acquire(block->memblock);
-    pa_assert(buf);
-    pa_assert(block->length > 0);
-    udp_build_audio_header(c, (uint32_t *) (buf + block->index), block->length);
-    len = udp_send_audio_packet(c, false, buf + block->index, block->length);
-
-    /* Store packet for resending in the packet buffer */
-    pa_raop_pb_write_packet(c->packet_buffer, c->seq, buf + block->index,
-                            block->length);
-
-    c->seq++;
-
-    pa_memblock_release(block->memblock);
-
-    if (len > 0) {
-        pa_assert((size_t) len <= block->length);
-        /* UDP packet has to be sent at once, so it is meaningless to
-           preseve the partial data
-           FIXME: This won't happen at least in *NIX systems?? */
-        if (block->length > (size_t) len) {
-            pa_log_warn("Tried to send %zu bytes but managed to send %zu bytes", block->length, len);
-            len = block->length;
-        }
-        block->index += block->length;
-        block->length = 0;
-    }
-
-    if (c->udp_first_packet)
-        c->udp_first_packet = false;
-
-    return len;
-}
-
-void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) {
-    pa_assert(c);
-
-    c->encryption = encryption;
-    if (c->encryption)
-        c->aes = pa_raop_secret_new();
-}
-
-/* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */
-pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) {
-    double minv, maxv;
-
-    if (c->protocol != RAOP_UDP)
-        return volume;
-
-    maxv = pa_sw_volume_from_dB(0.0);
-    minv = maxv * pow(10.0, (double) VOLUME_DEF / 60.0);
-
-    return volume - volume * (minv / maxv) + minv;
-}
-
-int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
-    int rv = 0;
-    double db;
-    char *param;
-
-    pa_assert(c);
-
-    db = pa_sw_volume_to_dB(volume);
-    if (db < VOLUME_MIN)
-        db = VOLUME_MIN;
-    else if (db > VOLUME_MAX)
-        db = VOLUME_MAX;
-
-    pa_log_debug("volume=%u db=%.6f", volume, db);
-
-    param = pa_sprintf_malloc("volume: %0.6f\r\n",  db);
-
-    /* We just hit and hope, cannot wait for the callback. */
-    if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp))
-        rv = pa_rtsp_setparameter(c->rtsp, param);
-    pa_xfree(param);
-
-    return rv;
-}
-
-int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchunk *encoded) {
-    uint16_t len;
-    size_t bufmax;
-    uint8_t *bp, bpos;
-    uint8_t *ibp, *maxibp;
-    int size;
-    uint8_t *b, *p;
-    uint32_t bsize;
-    size_t length;
-    const uint8_t *header;
-    int header_size;
-
-    pa_assert(c);
-    pa_assert(raw);
-    pa_assert(raw->memblock);
-    pa_assert(raw->length > 0);
-    pa_assert(encoded);
-
-    if (c->protocol == RAOP_TCP) {
-        header = tcp_audio_header;
-        header_size = sizeof(tcp_audio_header);
-    } else {
-        header = udp_audio_header;
-        header_size = sizeof(udp_audio_header);
-    }
-
-    /* We have to send 4 byte chunks */
-    bsize = (int)(raw->length / 4);
-    length = bsize * 4;
-
-    /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits. */
-    bufmax = length + header_size + 16;
-    pa_memchunk_reset(encoded);
-    encoded->memblock = pa_memblock_new(c->core->mempool, bufmax);
-    b = pa_memblock_acquire(encoded->memblock);
-    memcpy(b, header, header_size);
-
-    /* Now write the actual samples. */
-    bp = b + header_size;
-    size = bpos = 0;
-    bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */
-    bit_writer(&bp,&bpos,&size,0,4); /* Unknown */
-    bit_writer(&bp,&bpos,&size,0,8); /* Unknown */
-    bit_writer(&bp,&bpos,&size,0,4); /* Unknown */
-    bit_writer(&bp,&bpos,&size,1,1); /* Hassize */
-    bit_writer(&bp,&bpos,&size,0,2); /* Unused */
-    bit_writer(&bp,&bpos,&size,1,1); /* Is-not-compressed */
-
-    /* Size of data, integer, big endian. */
-    bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8);
-    bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8);
-    bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8);
-    bit_writer(&bp,&bpos,&size,(bsize)&0xff,8);
-
-    p = pa_memblock_acquire(raw->memblock);
-    p += raw->index;
-    ibp = p;
-    maxibp = p + raw->length - 4;
-    while (ibp <= maxibp) {
-        /* Byte swap stereo data. */
-        bit_writer(&bp,&bpos,&size,*(ibp+1),8);
-        bit_writer(&bp,&bpos,&size,*(ibp+0),8);
-        bit_writer(&bp,&bpos,&size,*(ibp+3),8);
-        bit_writer(&bp,&bpos,&size,*(ibp+2),8);
-        ibp += 4;
-        raw->index += 4;
-        raw->length -= 4;
-    }
-    if (c->protocol == RAOP_UDP)
-        c->rtptime += (ibp - p) / 4;
-    pa_memblock_release(raw->memblock);
-    encoded->length = header_size + size;
-
-    if (c->protocol == RAOP_TCP) {
-        /* Store the length (endian swapped: make this better). */
-        len = size + header_size - 4;
-        *(b + 2) = len >> 8;
-        *(b + 3) = len & 0xff;
-    }
-
-    if (c->encryption) {
-        /* Encrypt our data. */
-        pa_raop_aes_encrypt(c->aes, (b + header_size), size);
-    }
-
-    /* We're done with the chunk. */
-    pa_memblock_release(encoded->memblock);
-
-    return 0;
-}
-
-void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->tcp_callback = callback;
-    c->tcp_userdata = userdata;
-}
-
-void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->tcp_closed_callback = callback;
-    c->tcp_closed_userdata = userdata;
-}
-
-void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->udp_auth_callback = callback;
-    c->udp_auth_userdata = userdata;
-}
-
-void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->udp_setup_callback = callback;
-    c->udp_setup_userdata = userdata;
-}
-
-void pa_raop_client_udp_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->udp_record_callback = callback;
-    c->udp_record_userdata = userdata;
-}
-
-void pa_raop_client_udp_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata) {
-    pa_assert(c);
-
-    c->udp_disconnected_callback = callback;
-    c->udp_disconnected_userdata = userdata;
-}
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
deleted file mode 100644
index e208349..0000000
--- a/src/modules/raop/raop_client.h
+++ /dev/null
@@ -1,78 +0,0 @@
-#ifndef fooraopclientfoo
-#define fooraopclientfoo
-
-/***
-  This file is part of PulseAudio.
-
-  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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <pulse/volume.h>
-
-#include <pulsecore/core.h>
-#include <pulsecore/memchunk.h>
-
-#define UDP_FRAMES_PER_PACKET 352
-
-typedef enum pa_raop_protocol {
-    RAOP_TCP,
-    RAOP_UDP,
-} pa_raop_protocol_t;
-
-typedef struct pa_raop_client pa_raop_client;
-
-pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol);
-void pa_raop_client_free(pa_raop_client *c);
-
-int pa_raop_client_authenticate (pa_raop_client *c, const char *password);
-int pa_raop_client_connect(pa_raop_client *c);
-int pa_raop_client_flush(pa_raop_client *c);
-int pa_raop_client_teardown(pa_raop_client *c);
-
-int pa_raop_client_udp_is_authenticated(pa_raop_client *c);
-int pa_raop_client_udp_is_alive(pa_raop_client *c);
-int pa_raop_client_udp_can_stream(pa_raop_client *c);
-int pa_raop_client_udp_stream(pa_raop_client *c);
-
-void pa_raop_client_set_encryption(pa_raop_client *c, int encryption);
-pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
-int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume);
-int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchunk *encoded);
-
-int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
-int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
-int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size);
-ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block);
-
-typedef void (*pa_raop_client_cb_t)(int fd, void *userdata);
-void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata);
-
-typedef void (*pa_raop_client_closed_cb_t)(void *userdata);
-void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata);
-
-typedef void (*pa_raop_client_auth_cb_t)(int status, void *userdata);
-void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata);
-
-typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata);
-void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata);
-
-typedef void (*pa_raop_client_record_cb_t)(void *userdata);
-void pa_raop_client_udp_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata);
-
-typedef void (*pa_raop_client_disconnected_cb_t)(void *userdata);
-void pa_raop_client_udp_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata);
-
-#endif
diff --git a/src/modules/raop/raop_crypto.c b/src/modules/raop/raop_crypto.c
deleted file mode 100644
index 4d08e26..0000000
--- a/src/modules/raop/raop_crypto.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2013 Martin Blanchard
-
-  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.1 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 <stdint.h>
-#include <string.h>
-
-#include <openssl/err.h>
-#include <openssl/aes.h>
-#include <openssl/rsa.h>
-
-#include <pulse/xmalloc.h>
-
-#include <pulsecore/macro.h>
-#include <pulsecore/random.h>
-
-#include "raop_crypto.h"
-#include "raop_util.h"
-
-#define AES_CHUNK_SIZE 16
-
-/* Openssl 1.1.0 broke compatibility. Before 1.1.0 we had to set RSA->n and
- * RSA->e manually, but after 1.1.0 the RSA struct is opaque and we have to use
- * RSA_set0_key(). RSA_set0_key() is a new function added in 1.1.0. We could
- * depend on openssl 1.1.0, but it may take some time before distributions will
- * be able to upgrade to the new openssl version. To insulate ourselves from
- * such transition problems, let's implement RSA_set0_key() ourselves if it's
- * not available. */
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
-static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
-    r->n = n;
-    r->e = e;
-    return 1;
-}
-#endif
-
-struct pa_raop_secret {
-    uint8_t key[AES_CHUNK_SIZE]; /* Key for aes-cbc */
-    uint8_t iv[AES_CHUNK_SIZE];  /* Initialization vector for cbc */
-    AES_KEY aes;                 /* AES encryption */
-};
-
-static const char rsa_modulus[] =
-    "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
-    "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
-    "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
-    "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
-    "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
-    "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
-
-static const char rsa_exponent[] =
-    "AQAB";
-
-static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
-    uint8_t modules[256];
-    uint8_t exponent[8];
-    int size;
-    RSA *rsa;
-    BIGNUM *n_bn;
-    BIGNUM *e_bn;
-
-    pa_assert(data);
-    pa_assert(str);
-
-    rsa = RSA_new();
-    size = pa_raop_base64_decode(rsa_modulus, modules);
-    n_bn = BN_bin2bn(modules, size, NULL);
-    size = pa_raop_base64_decode(rsa_exponent, exponent);
-    e_bn = BN_bin2bn(exponent, size, NULL);
-    RSA_set0_key(rsa, n_bn, e_bn, NULL);
-
-    size = RSA_public_encrypt(len, data, str, rsa, RSA_PKCS1_OAEP_PADDING);
-
-    RSA_free(rsa);
-    return size;
-}
-
-pa_raop_secret* pa_raop_secret_new(void) {
-    pa_raop_secret *s = pa_xnew0(pa_raop_secret, 1);
-
-    pa_assert(s);
-
-    pa_random(s->key, sizeof(s->key));
-    AES_set_encrypt_key(s->key, 128, &s->aes);
-    pa_random(s->iv, sizeof(s->iv));
-
-    return s;
-}
-
-void pa_raop_secret_free(pa_raop_secret *s) {
-    pa_assert(s);
-
-    pa_xfree(s);
-}
-
-char* pa_raop_secret_get_iv(pa_raop_secret *s) {
-    char *base64_iv = NULL;
-
-    pa_assert(s);
-
-    pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
-
-    return base64_iv;
-}
-
-char* pa_raop_secret_get_key(pa_raop_secret *s) {
-    char *base64_key = NULL;
-    uint8_t rsa_key[512];
-    int size = 0;
-
-    pa_assert(s);
-
-    /* Encrypt our AES public key to send to the device */
-    size = rsa_encrypt(s->key, AES_CHUNK_SIZE, rsa_key);
-    pa_raop_base64_encode(rsa_key, size, &base64_key);
-
-    return base64_key;
-}
-
-int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len) {
-    static uint8_t nv[AES_CHUNK_SIZE];
-    uint8_t *buffer;
-    int i = 0, j;
-
-    pa_assert(s);
-    pa_assert(data);
-
-    memcpy(nv, s->iv, AES_CHUNK_SIZE);
-
-    while (i + AES_CHUNK_SIZE <= len) {
-        buffer = data + i;
-        for (j = 0; j < AES_CHUNK_SIZE; ++j)
-            buffer[j] ^= nv[j];
-
-        AES_encrypt(buffer, buffer, &s->aes);
-
-        memcpy(nv, buffer, AES_CHUNK_SIZE);
-        i += AES_CHUNK_SIZE;
-    }
-
-    return i;
-}
diff --git a/src/modules/raop/raop_crypto.h b/src/modules/raop/raop_crypto.h
deleted file mode 100644
index 65f7577..0000000
--- a/src/modules/raop/raop_crypto.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#ifndef fooraopcryptofoo
-#define fooraopcryptofoo
-
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2013 Martin Blanchard
-
-  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.1 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.
-***/
-
-typedef struct pa_raop_secret pa_raop_secret;
-
-pa_raop_secret* pa_raop_secret_new(void);
-void pa_raop_secret_free(pa_raop_secret *s);
-
-char* pa_raop_secret_get_iv(pa_raop_secret *s);
-char* pa_raop_secret_get_key(pa_raop_secret *s);
-
-int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len);
-
-#endif
diff --git a/src/modules/raop/raop_packet_buffer.c b/src/modules/raop/raop_packet_buffer.c
deleted file mode 100644
index b8c1bc8..0000000
--- a/src/modules/raop/raop_packet_buffer.c
+++ /dev/null
@@ -1,172 +0,0 @@
-/***
-  Circular buffer for RTP audio packets with random access support
-  by RTP sequence number.
-
-  Copyright 2013 Matthias Wabersich, Hajime Fujita
-
-  This 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.1 of the License,
-  or (at your option) any later version.
-
-  This 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.
-
-***/
-
-#include <stdlib.h>
-#include <limits.h>
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <pulsecore/core-error.h>
-#include "raop_client.h"
-
-#include "raop_packet_buffer.h"
-
-/* FRAMES_PER_PACKET*2*2 + sizeof(udp_audio_header) + sizeof(ALAC header), unencoded */
-#define PACKET_SIZE_MAX (UDP_FRAMES_PER_PACKET*2*2 + 12 + 7)
-/* Header room for packet retransmission header */
-#define RETRANS_HEADER_ROOM 4
-
-/* Packet element */
-struct pa_raop_packet_element {
-    uint16_t  seq_num; /* RTP sequence number (in host byte order) */
-    ssize_t   length;  /* Actual packet length */
-    /* Packet data including RTP header */
-    uint8_t   data[PACKET_SIZE_MAX + RETRANS_HEADER_ROOM];
-};
-
-/* Buffer struct */
-struct pa_raop_packet_buffer {
-    size_t   size;          /* max number of packets in buffer */
-    size_t   start;         /* index of oldest packet */
-    size_t   count;         /* number of packets in buffer */
-    uint16_t first_seq_num; /* Sequence number of first packet in buffer */
-    uint16_t latest_seq_num; /* Debug purpose */
-    pa_raop_packet_element *packets; /* Packet element pointer */
-};
-
-pa_raop_packet_buffer *pa_raop_pb_new(size_t size) {
-    pa_raop_packet_buffer *pb = pa_xmalloc0(sizeof(*pb));
-
-    pb->size = size;
-    pb->packets = (pa_raop_packet_element *)
-        pa_xmalloc(size * sizeof(pa_raop_packet_element));
-
-    pa_raop_pb_clear(pb);
-
-    return pb;
-}
-
-void pa_raop_pb_clear(pa_raop_packet_buffer *pb) {
-    pb->start = 0;
-    pb->count = 0;
-    pb->first_seq_num = 0;
-    pb->latest_seq_num = 0;
-    memset(pb->packets, 0, pb->size * sizeof(pa_raop_packet_element));
-}
-
-void pa_raop_pb_delete(pa_raop_packet_buffer *pb) {
-    pa_xfree(pb->packets);
-    pa_xfree(pb);
-}
-
-static int pb_is_full(pa_raop_packet_buffer *pb) {
-    return pb->count == pb->size;
-}
-
-static int pb_is_empty(pa_raop_packet_buffer *pb) {
-    return pb->count == 0;
-}
-
-static pa_raop_packet_element *pb_prepare_write(pa_raop_packet_buffer *pb, uint16_t seq) {
-    size_t end = (pb->start + pb->count) % pb->size;
-    pa_raop_packet_element *packet;
-
-    /* Set first packet sequence number in buffer if buffer is empty */
-    if (pb_is_empty(pb))
-        pb->first_seq_num = seq;
-    else
-        pa_assert((uint16_t) (pb->latest_seq_num + 1) == seq);
-
-    packet = &pb->packets[end];
-
-    if (pb_is_full(pb)) {
-        pb->start = (pb->start + 1) % pb->size; /* full, overwrite */
-
-        /* Set first packet sequence number in buffer
-           to new start packet sequence number */
-        pb->first_seq_num = pb->packets[pb->start].seq_num;
-    } else
-        ++ pb->count;
-
-    pb->latest_seq_num = seq;
-
-    return packet;
-}
-
-/* Write packet data to packet buffer */
-void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length) {
-    pa_raop_packet_element *packet;
-
-    pa_assert(pb);
-    pa_assert(packet_data);
-    pa_assert(packet_length <= PACKET_SIZE_MAX);
-
-    packet = pb_prepare_write(pb, seq_num);
-    packet->seq_num = seq_num;
-    packet->length = packet_length + RETRANS_HEADER_ROOM;
-
-    /* Insert RETRANS_HEADER_ROOM bytes in front of packet data,
-       for retransmission header */
-    memset(packet->data, 0, RETRANS_HEADER_ROOM);
-    memcpy(packet->data + RETRANS_HEADER_ROOM, packet_data, packet_length);
-}
-
-/* l < r?, considers wrapping */
-static bool seq_lt(uint16_t l, uint16_t r) {
-    return l - r > USHRT_MAX/2;
-}
-
-/* Random access to packet from buffer by sequence number for (re-)sending. */
-ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data) {
-    uint16_t index = 0; /* Index of requested packet */
-    pa_raop_packet_element *packet;
-
-    /* If the buffer is empty, there is no use in calculating indices */
-    if (pb_is_empty(pb))
-        return -1;
-
-    /* If the requested packet is too old (seq_num below first seq number
-       in buffer) or too young (seq_num greater than current seq number),
-       do nothing and return */
-    if (seq_lt(seq_num, pb->first_seq_num))
-        return -1;
-
-    index = (uint16_t) (seq_num - pb->first_seq_num);
-    if (index >= pb->count)
-        return -1;
-
-    /*  Index of the requested packet in the buffer is calculated
-        using the first sequence number stored in the buffer.
-        The offset (seq_num - first_seq_num) is used to access the array. */
-    packet = &pb->packets[(pb->start + index) % pb->size];
-
-    pa_assert(packet->data[RETRANS_HEADER_ROOM + 2] == (seq_num >> 8));
-    pa_assert(packet->data[RETRANS_HEADER_ROOM + 3] == (seq_num & 0xff));
-    pa_assert(packet_data);
-
-    *packet_data = packet->data;
-
-    return packet->length;
-}
diff --git a/src/modules/raop/raop_packet_buffer.h b/src/modules/raop/raop_packet_buffer.h
deleted file mode 100644
index d8a08a0..0000000
--- a/src/modules/raop/raop_packet_buffer.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#ifndef RAOP_PACKET_BUFFER_H_INCLUDED
-#define RAOP_PACKET_BUFFER_H_INCLUDED
-
-/***
-  Circular buffer for RTP audio packets with random access support
-  by RTP sequence number.
-
-  Copyright 2013 Matthias Wabersich, Hajime Fujita
-
-  This 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.1 of the License,
-  or (at your option) any later version.
-
-  This 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.
-
-***/
-
-struct pa_raop_packet_element;
-typedef struct pa_raop_packet_element pa_raop_packet_element;
-
-struct pa_raop_packet_buffer;
-typedef struct pa_raop_packet_buffer pa_raop_packet_buffer;
-
-/* Allocates a new circular packet buffer
-   size: Maximum number of packets to store */
-pa_raop_packet_buffer *pa_raop_pb_new(size_t size);
-void pa_raop_pb_clear(pa_raop_packet_buffer *pb);
-void pa_raop_pb_delete(pa_raop_packet_buffer *pb);
-
-void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length);
-ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data);
-
-#endif /* RAOP_PACKET_BUFFER_H_INCLUDED */
diff --git a/src/modules/raop/raop_util.c b/src/modules/raop/raop_util.c
deleted file mode 100644
index 709ec27..0000000
--- a/src/modules/raop/raop_util.c
+++ /dev/null
@@ -1,210 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2013 Martin Blanchard
-  Copyright Kungliga Tekniska Høgskolan & 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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-/***
-  The base64 implementation was originally inspired by a file developed
-  by Kungliga Tekniska högskolan.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <openssl/err.h>
-#include <openssl/md5.h>
-
-#include <pulse/xmalloc.h>
-
-#include <pulsecore/core-util.h>
-#include <pulsecore/macro.h>
-
-#include "raop_util.h"
-
-#ifndef MD5_DIGEST_LENGTH
-#define MD5_DIGEST_LENGTH 16
-#endif
-
-#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
-
-#define BASE64_DECODE_ERROR 0xffffffff
-
-static const char base64_chars[] =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-static int char_position(char c) {
-    if (c >= 'A' && c <= 'Z')
-        return c - 'A' + 0;
-    if (c >= 'a' && c <= 'z')
-        return c - 'a' + 26;
-    if (c >= '0' && c <= '9')
-        return c - '0' + 52;
-    if (c == '+')
-        return 62;
-    if (c == '/')
-        return 63;
-
-    return -1;
-}
-
-static unsigned int token_decode(const char *token) {
-    unsigned int val = 0;
-    int marker = 0;
-    int i;
-
-    if (strlen(token) < 4)
-        return BASE64_DECODE_ERROR;
-    for (i = 0; i < 4; i++) {
-        val *= 64;
-        if (token[i] == '=')
-            marker++;
-        else if (marker > 0)
-            return BASE64_DECODE_ERROR;
-        else {
-            int lpos = char_position(token[i]);
-            if (lpos < 0)
-                return BASE64_DECODE_ERROR;
-            val += lpos;
-        }
-    }
-
-    if (marker > 2)
-        return BASE64_DECODE_ERROR;
-
-    return (marker << 24) | val;
-}
-
-int pa_raop_base64_encode(const void *data, int len, char **str) {
-    const unsigned char *q;
-    char *p, *s = NULL;
-    int i, c;
-
-    pa_assert(data);
-    pa_assert(str);
-
-    p = s = pa_xnew(char, len * 4 / 3 + 4);
-    q = (const unsigned char *) data;
-    for (i = 0; i < len;) {
-        c = q[i++];
-        c *= 256;
-        if (i < len)
-            c += q[i];
-        i++;
-        c *= 256;
-        if (i < len)
-            c += q[i];
-        i++;
-        p[0] = base64_chars[(c & 0x00fc0000) >> 18];
-        p[1] = base64_chars[(c & 0x0003f000) >> 12];
-        p[2] = base64_chars[(c & 0x00000fc0) >> 6];
-        p[3] = base64_chars[(c & 0x0000003f) >> 0];
-        if (i > len)
-            p[3] = '=';
-        if (i > len + 1)
-            p[2] = '=';
-        p += 4;
-    }
-
-    *p = 0;
-    *str = s;
-    return strlen(s);
-}
-
-int pa_raop_base64_decode(const char *str, void *data) {
-    const char *p;
-    unsigned char *q;
-
-    pa_assert(str);
-    pa_assert(data);
-
-    q = data;
-    for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
-        unsigned int val = token_decode(p);
-        unsigned int marker = (val >> 24) & 0xff;
-        if (val == BASE64_DECODE_ERROR)
-            return -1;
-        *q++ = (val >> 16) & 0xff;
-        if (marker < 2)
-            *q++ = (val >> 8) & 0xff;
-        if (marker < 1)
-            *q++ = val & 0xff;
-    }
-
-    return q - (unsigned char *) data;
-}
-
-int pa_raop_md5_hash(const char *data, int len, char **str) {
-    unsigned char d[MD5_DIGEST_LENGTH];
-    char *s = NULL;
-    int i;
-
-    pa_assert(data);
-    pa_assert(str);
-
-    MD5((unsigned char*) data, len, d);
-    s = pa_xnew(char, MD5_HASH_LENGTH);
-    for (i = 0; i < MD5_DIGEST_LENGTH; i++)
-        sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
-
-    *str = s;
-    s[MD5_HASH_LENGTH] = 0;
-    return strlen(s);
-}
-
-int pa_raop_basic_response(const char *user, const char *pwd, char **str) {
-    char *tmp, *B = NULL;
-
-    pa_assert(str);
-
-    tmp = pa_sprintf_malloc("%s:%s", user, pwd);
-    pa_raop_base64_encode(tmp, strlen(tmp), &B);
-    pa_xfree(tmp);
-
-    *str = B;
-    return strlen(B);
-}
-
-int pa_raop_digest_response(const char *user, const char *realm, const char *password,
-                            const char *nonce, const char *uri, char **str) {
-    char *A1, *HA1, *A2, *HA2;
-    char *tmp, *KD = NULL;
-
-    pa_assert(str);
-
-    A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
-    pa_raop_md5_hash(A1, strlen(A1), &HA1);
-    pa_xfree(A1);
-
-    A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
-    pa_raop_md5_hash(A2, strlen(A2), &HA2);
-    pa_xfree(A2);
-
-    tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
-    pa_raop_md5_hash(tmp, strlen(tmp), &KD);
-    pa_xfree(tmp);
-
-    pa_xfree(HA1);
-    pa_xfree(HA2);
-
-    *str = KD;
-    return strlen(KD);
-}
diff --git a/src/modules/raop/raop_util.h b/src/modules/raop/raop_util.h
deleted file mode 100644
index d3f7566..0000000
--- a/src/modules/raop/raop_util.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef fooraoputilfoo
-#define fooraoputilfoo
-
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008 Colin Guthrie
-  Copyright Kungliga Tekniska högskolan
-  Copyright 2013 Martin Blanchard
-
-  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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-/***
-  This file was originally inspired by a file developed by
-  Kungliga Tekniska högskolan.
-***/
-
-int pa_raop_base64_encode(const void *data, int len, char **str);
-int pa_raop_base64_decode(const char *str, void *data);
-
-int pa_raop_md5_hash(const char *data, int len, char **str);
-
-int pa_raop_basic_response(const char *user, const char *pwd, char **str);
-int pa_raop_digest_response(const char *user, const char *realm, const char *password,
-                            const char *nonce, const char *uri, char **str);
-
-#endif
diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c
index c6d9ac6..31b8673 100644
--- a/src/modules/rtp/rtsp_client.c
+++ b/src/modules/rtp/rtsp_client.c
@@ -58,8 +58,8 @@ struct pa_rtsp_client {
     void *userdata;
     const char *useragent;
 
-    pa_rtsp_state state;
-    pa_rtsp_status status;
+    pa_rtsp_state_t state;
+    pa_rtsp_status_t status;
     uint8_t waiting;
 
     pa_headerlist* headers;
diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h
index 4a35851..4e031d8 100644
--- a/src/modules/rtp/rtsp_client.h
+++ b/src/modules/rtp/rtsp_client.h
@@ -32,7 +32,7 @@
 
 typedef struct pa_rtsp_client pa_rtsp_client;
 
-typedef enum {
+typedef enum pa_rtsp_state {
   STATE_CONNECT,
   STATE_OPTIONS,
   STATE_ANNOUNCE,
@@ -42,17 +42,17 @@ typedef enum {
   STATE_FLUSH,
   STATE_TEARDOWN,
   STATE_DISCONNECTED
-} pa_rtsp_state;
+} pa_rtsp_state_t;
 
-typedef enum {
+typedef enum pa_rtsp_status {
   STATUS_OK             = 200,
   STATUS_BAD_REQUEST    = 400,
   STATUS_UNAUTHORIZED   = 401,
   STATUS_NO_RESPONSE    = 444,
   STATUS_INTERNAL_ERROR = 500
-} pa_rtsp_status;
+} pa_rtsp_status_t;
 
-typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_rtsp_status code, pa_headerlist *headers, void *userdata);
+typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state_t state, pa_rtsp_status_t code, pa_headerlist *headers, void *userdata);
 
 pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostname, uint16_t port, const char *useragent);
 void pa_rtsp_client_free(pa_rtsp_client *c);

commit 1bf7e6c84f34e60fa3b7e693327dde116802ca7f
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:06 2016 -0600

    raop: Add BA (Basic) and DA (Digest) HTTP authentication helpers
    
    RAOP authentication is using standard HTTP challenge-response authentication
    scheme. This patch adds two helper functions that generate the proper hash
    (for both techniques) given a username, a password and session related tokens.

diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index a2ec91e..1323cd0 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -65,6 +65,9 @@
 #define VOLUME_MIN -144
 #define VOLUME_MAX 0
 
+#define USER_AGENT "iTunes/11.0.4 (Windows; N)"
+#define USER_NAME "iTunes"
+
 #define DEFAULT_RAOP_PORT 5000
 #define UDP_DEFAULT_AUDIO_PORT 6000
 #define UDP_DEFAULT_CONTROL_PORT 6001
@@ -85,13 +88,15 @@ struct pa_raop_client {
     pa_core *core;
     char *host;
     uint16_t port;
-    char *sid;
     pa_rtsp_client *rtsp;
-    pa_raop_protocol_t protocol;
+    char *sci, *sid;
+    char *pwd;
 
     uint8_t jack_type;
     uint8_t jack_status;
 
+    pa_raop_protocol_t protocol;
+
     int encryption; /* Enable encryption? */
     pa_raop_secret *aes;
 
@@ -125,6 +130,9 @@ struct pa_raop_client {
     uint32_t udp_sync_interval;
     uint32_t udp_sync_count;
 
+    pa_raop_client_auth_cb_t udp_auth_callback;
+    void *udp_auth_userdata;
+
     pa_raop_client_setup_cb_t udp_setup_callback;
     void *udp_setup_userdata;
 
@@ -576,7 +584,7 @@ static void do_rtsp_announce(pa_raop_client *c) {
     pa_xfree(sdp);
 }
 
-static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) {
+static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist* headers, void *userdata) {
     pa_raop_client* c = userdata;
     pa_assert(c);
     pa_assert(rtsp);
@@ -675,12 +683,13 @@ static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
     }
 }
 
-static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) {
+static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) {
     pa_raop_client *c = userdata;
 
     pa_assert(c);
     pa_assert(rtsp);
     pa_assert(rtsp == c->rtsp);
+    pa_assert(STATUS_OK == status);
 
     switch (state) {
         case STATE_CONNECT: {
@@ -982,6 +991,178 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
     }
 }
 
+static void rtsp_authentication_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) {
+    pa_raop_client *c = userdata;
+
+    pa_assert(c);
+    pa_assert(rtsp);
+    pa_assert(rtsp == c->rtsp);
+
+    switch (state) {
+        case STATE_CONNECT: {
+            char *sci = NULL, *sac = NULL;
+            uint16_t rac;
+            struct {
+                uint32_t ci1;
+                uint32_t ci2;
+            } rci;
+
+            pa_random(&rci, sizeof(rci));
+            /* Generate a random Client-Instance number */
+            sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2);
+            pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
+
+            pa_random(&rac, sizeof(rac));
+            /* Generate a random Apple-Challenge key */
+            pa_raop_base64_encode(&rac, 8*sizeof(rac), &sac);
+            pa_log_debug ("APPLECHALLENGE >> %s | %d", sac, (int) sizeof(rac));
+            rtrimchar(sac, '=');
+            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
+
+            pa_rtsp_options(c->rtsp);
+
+            pa_xfree(sac);
+            pa_xfree(sci);
+            break;
+        }
+
+        case STATE_OPTIONS: {
+            static bool waiting = false;
+            const char *current = NULL;
+            char space[] = " ";
+            char *token,*ath = NULL;
+            char *publ, *wath, *mth, *val;
+            char *realm = NULL, *nonce = NULL, *response = NULL;
+            char comma[] = ",";
+
+            pa_log_debug("RAOP: OPTIONS");
+            /* We do not consider the Apple-Response */
+            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
+
+            if (STATUS_UNAUTHORIZED == status) {
+                wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
+                if (true == waiting) {
+                    pa_xfree(wath);
+                    goto failure;
+                }
+
+                if (wath)
+                    mth = pa_split(wath, space, &current);
+                while ((token = pa_split(wath, comma, &current))) {
+                    val = NULL;
+                    if ((val = strstr(token, "="))) {
+                        if (NULL == realm && val > strstr(token, "realm"))
+                            realm = pa_xstrdup(val + 2);
+                        else if (NULL == nonce && val > strstr(token, "nonce"))
+                            nonce = pa_xstrdup(val + 2);
+                        val = NULL;
+                    }
+
+                    pa_xfree(token);
+                }
+
+                if (pa_safe_streq(mth, "Basic")) {
+                    rtrimchar(realm, '\"');
+
+                    pa_raop_basic_response(USER_NAME, c->pwd, &response);
+                    ath = pa_sprintf_malloc("Basic %s",
+                        response);
+
+                    pa_xfree(response);
+                    pa_xfree(realm);
+                } else if (pa_safe_streq(mth, "Digest")) {
+                    rtrimchar(realm, '\"');
+                    rtrimchar(nonce, '\"');
+
+                    pa_raop_digest_response(USER_NAME, realm, c->pwd, nonce, "*", &response);
+                    ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"",
+                        USER_NAME, realm, nonce,
+                        response);
+
+                    pa_xfree(response);
+                    pa_xfree(realm);
+                    pa_xfree(nonce);
+                } else {
+                    pa_log_error("unsupported authentication method: %s", mth);
+                    pa_xfree(wath);
+                    pa_xfree(mth);
+                    goto error;
+                }
+
+                pa_xfree(wath);
+                pa_xfree(mth);
+
+                pa_rtsp_add_header(c->rtsp, "Authorization", ath);
+                pa_xfree(ath);
+
+                waiting = true;
+                pa_rtsp_options(c->rtsp);
+                break;
+            }
+
+            if (STATUS_OK == status) {
+                publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
+                c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
+
+                if (c->pwd)
+                    pa_xfree(c->pwd);
+                pa_xfree(publ);
+                c->pwd = NULL;
+            }
+
+            if (c->udp_auth_callback)
+                c->udp_auth_callback((int) status, c->udp_auth_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            waiting = false;
+            break;
+
+        failure:
+            if (c->udp_auth_callback)
+                c->udp_auth_callback((int) STATUS_UNAUTHORIZED, c->udp_auth_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            pa_log_error("aborting RTSP authentication, wrong password");
+
+            waiting = false;
+            break;
+
+        error:
+            if (c->udp_auth_callback)
+                c->udp_auth_callback((int) status, c->udp_auth_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            pa_log_error("aborting RTSP authentication, unexpected failure");
+
+            waiting = false;
+            break;
+        }
+
+        case STATE_ANNOUNCE:
+        case STATE_SETUP:
+        case STATE_RECORD:
+        case STATE_SET_PARAMETER:
+        case STATE_FLUSH:
+        case STATE_TEARDOWN:
+        case STATE_DISCONNECTED:
+        default: {
+            if (c->udp_auth_callback)
+                c->udp_auth_callback((int) STATUS_BAD_REQUEST, c->udp_auth_userdata);
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            if (c->sci)
+                pa_xfree(c->sci);
+            c->sci = NULL;
+
+            break;
+        }
+    }
+}
+
 pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) {
     pa_raop_client* c;
     pa_parsed_address a;
@@ -1047,15 +1228,40 @@ void pa_raop_client_free(pa_raop_client *c) {
 
     if (c->sid)
         pa_xfree(c->sid);
+    if (c->sci)
+        pa_xfree(c->sci);
+    c->sci = c->sid = NULL;
+
     if (c->aes)
         pa_raop_secret_free(c->aes);
     if (c->rtsp)
         pa_rtsp_client_free(c->rtsp);
-    pa_xfree(c->host);
+    c->rtsp = NULL;
 
+    pa_xfree(c->host);
     pa_xfree(c);
 }
 
+int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
+
+    pa_assert(c);
+
+    if (c->rtsp || c->pwd) {
+        pa_log_debug("Connection already in progress");
+        return 0;
+    }
+
+    c->pwd = NULL;
+    if (password)
+        c->pwd = pa_xstrdup(password);
+    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT);
+
+    pa_assert(c->rtsp);
+
+    pa_rtsp_set_callback(c->rtsp, rtsp_authentication_cb, c);
+    return pa_rtsp_connect(c->rtsp);
+}
+
 int pa_raop_client_connect(pa_raop_client *c) {
     char *sci;
     struct {
@@ -1074,7 +1280,7 @@ int pa_raop_client_connect(pa_raop_client *c) {
     if (c->protocol == RAOP_TCP)
         c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)");
     else
-        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)");
+        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT);
 
     /* Generate random instance id. */
     pa_random(&rand_data, sizeof(rand_data));
@@ -1116,6 +1322,17 @@ int pa_raop_client_teardown(pa_raop_client *c) {
     return rv;
 }
 
+int pa_raop_client_udp_is_authenticated(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->sci != NULL)
+        rv = 1;
+
+    return rv;
+}
+
 int pa_raop_client_udp_is_alive(pa_raop_client *c) {
     int rv = 0;
 
@@ -1147,7 +1364,6 @@ int pa_raop_client_udp_stream(pa_raop_client *c) {
         if (!c->is_recording) {
             c->udp_first_packet = true;
             c->udp_sync_count = 0;
-
             c->is_recording = true;
          }
 
@@ -1326,6 +1542,14 @@ ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *blo
     return len;
 }
 
+void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) {
+    pa_assert(c);
+
+    c->encryption = encryption;
+    if (c->encryption)
+        c->aes = pa_raop_secret_new();
+}
+
 /* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */
 pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) {
     double minv, maxv;
@@ -1469,12 +1693,11 @@ void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_cl
     c->tcp_closed_userdata = userdata;
 }
 
-void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) {
+void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata) {
     pa_assert(c);
 
-    c->encryption = encryption;
-    if (c->encryption)
-        c->aes = pa_raop_secret_new();
+    c->udp_auth_callback = callback;
+    c->udp_auth_userdata = userdata;
 }
 
 void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) {
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 5d0bb14..e208349 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -37,10 +37,12 @@ typedef struct pa_raop_client pa_raop_client;
 pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol);
 void pa_raop_client_free(pa_raop_client *c);
 
+int pa_raop_client_authenticate (pa_raop_client *c, const char *password);
 int pa_raop_client_connect(pa_raop_client *c);
 int pa_raop_client_flush(pa_raop_client *c);
 int pa_raop_client_teardown(pa_raop_client *c);
 
+int pa_raop_client_udp_is_authenticated(pa_raop_client *c);
 int pa_raop_client_udp_is_alive(pa_raop_client *c);
 int pa_raop_client_udp_can_stream(pa_raop_client *c);
 int pa_raop_client_udp_stream(pa_raop_client *c);
@@ -61,6 +63,8 @@ void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t call
 typedef void (*pa_raop_client_closed_cb_t)(void *userdata);
 void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata);
 
+typedef void (*pa_raop_client_auth_cb_t)(int status, void *userdata);
+void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata);
 
 typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata);
 void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata);
diff --git a/src/modules/raop/raop_util.c b/src/modules/raop/raop_util.c
index c9021ec..709ec27 100644
--- a/src/modules/raop/raop_util.c
+++ b/src/modules/raop/raop_util.c
@@ -35,6 +35,7 @@
 
 #include <pulse/xmalloc.h>
 
+#include <pulsecore/core-util.h>
 #include <pulsecore/macro.h>
 
 #include "raop_util.h"
@@ -168,3 +169,42 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
     s[MD5_HASH_LENGTH] = 0;
     return strlen(s);
 }
+
+int pa_raop_basic_response(const char *user, const char *pwd, char **str) {
+    char *tmp, *B = NULL;
+
+    pa_assert(str);
+
+    tmp = pa_sprintf_malloc("%s:%s", user, pwd);
+    pa_raop_base64_encode(tmp, strlen(tmp), &B);
+    pa_xfree(tmp);
+
+    *str = B;
+    return strlen(B);
+}
+
+int pa_raop_digest_response(const char *user, const char *realm, const char *password,
+                            const char *nonce, const char *uri, char **str) {
+    char *A1, *HA1, *A2, *HA2;
+    char *tmp, *KD = NULL;
+
+    pa_assert(str);
+
+    A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
+    pa_raop_md5_hash(A1, strlen(A1), &HA1);
+    pa_xfree(A1);
+
+    A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
+    pa_raop_md5_hash(A2, strlen(A2), &HA2);
+    pa_xfree(A2);
+
+    tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
+    pa_raop_md5_hash(tmp, strlen(tmp), &KD);
+    pa_xfree(tmp);
+
+    pa_xfree(HA1);
+    pa_xfree(HA2);
+
+    *str = KD;
+    return strlen(KD);
+}
diff --git a/src/modules/raop/raop_util.h b/src/modules/raop/raop_util.h
index dc0b767..d3f7566 100644
--- a/src/modules/raop/raop_util.h
+++ b/src/modules/raop/raop_util.h
@@ -32,4 +32,8 @@ int pa_raop_base64_decode(const char *str, void *data);
 
 int pa_raop_md5_hash(const char *data, int len, char **str);
 
+int pa_raop_basic_response(const char *user, const char *pwd, char **str);
+int pa_raop_digest_response(const char *user, const char *realm, const char *password,
+                            const char *nonce, const char *uri, char **str);
+
 #endif
diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c
index 198417e..c6d9ac6 100644
--- a/src/modules/rtp/rtsp_client.c
+++ b/src/modules/rtp/rtsp_client.c
@@ -59,6 +59,7 @@ struct pa_rtsp_client {
     const char *useragent;
 
     pa_rtsp_state state;
+    pa_rtsp_status status;
     uint8_t waiting;
 
     pa_headerlist* headers;
@@ -119,8 +120,8 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
 }
 
 static void headers_read(pa_rtsp_client *c) {
-    char* token;
     char delimiters[] = ";";
+    char* token;
 
     pa_assert(c);
     pa_assert(c->response_headers);
@@ -165,14 +166,14 @@ static void headers_read(pa_rtsp_client *c) {
     }
 
     /* Call our callback */
-    c->callback(c, c->state, c->response_headers, c->userdata);
+    c->callback(c, c->state, c->status, c->response_headers, c->userdata);
 }
 
 static void line_callback(pa_ioline *line, const char *s, void *userdata) {
+    pa_rtsp_client *c = userdata;
     char *delimpos;
     char *s2, *s2p;
 
-    pa_rtsp_client *c = userdata;
     pa_assert(line);
     pa_assert(c);
     pa_assert(c->callback);
@@ -180,7 +181,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
     if (!s) {
         /* Keep the ioline/iochannel open as they will be freed automatically */
         c->ioline = NULL;
-        c->callback(c, STATE_DISCONNECTED, NULL, c->userdata);
+        c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata);
         return;
     }
 
@@ -191,17 +192,35 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
         *s2p = '\0';
         s2p -= 1;
     }
+
     if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) {
+        if (c->response_headers)
+            pa_headerlist_free(c->response_headers);
+        c->response_headers = pa_headerlist_new();
+
+        c->status = STATUS_OK;
         c->waiting = 0;
+        goto exit;
+    } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
         if (c->response_headers)
             pa_headerlist_free(c->response_headers);
         c->response_headers = pa_headerlist_new();
+
+        c->status = STATUS_UNAUTHORIZED;
+        c->waiting = 0;
         goto exit;
-    }
-    if (c->waiting) {
-        pa_log_warn("Unexpected response: %s", s2);
+    } else if (c->waiting) {
+        pa_log_warn("Unexpected/Unhandled response: %s", s2);
+
+        if (pa_streq(s2, "RTSP/1.0 400 Bad Request"))
+            c->status = STATUS_BAD_REQUEST;
+        else if (pa_streq(s2, "RTSP/1.0 500 Internal Server Error"))
+            c->status = STATUS_INTERNAL_ERROR;
+        else
+            c->status = STATUS_NO_RESPONSE;
         goto exit;
     }
+
     if (!strlen(s2)) {
         /* End of headers */
         /* We will have a header left from our looping iteration, so add it in :) */
@@ -228,7 +247,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
     if (c->last_header && ' ' == s2[0]) {
         pa_assert(c->header_buffer);
 
-        /* Add this line to the buffer (sans the space. */
+        /* Add this line to the buffer (sans the space) */
         pa_strbuf_puts(c->header_buffer, &(s2[1]));
         goto exit;
     }
@@ -270,6 +289,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
 
     /* Save the header name */
     c->last_header = pa_xstrdup(s2);
+
   exit:
     pa_xfree(s2);
 }
@@ -317,7 +337,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
     pa_log_debug("Established RTSP connection from local ip %s", c->localip);
 
     if (c->callback)
-        c->callback(c, c->state, NULL, c->userdata);
+        c->callback(c, c->state, STATUS_OK, NULL, c->userdata);
 }
 
 int pa_rtsp_connect(pa_rtsp_client *c) {
@@ -336,6 +356,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) {
     pa_socket_client_set_callback(c->sc, on_connection, c);
     c->waiting = 1;
     c->state = STATE_CONNECT;
+    c->status = STATUS_NO_RESPONSE;
     return 0;
 }
 
@@ -368,12 +389,25 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) {
     return c->rtp_port;
 }
 
+bool pa_rtsp_exec_ready(const pa_rtsp_client *c) {
+    pa_assert(c);
+
+    return c->url != NULL && c->ioline != NULL;
+}
+
 void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) {
     pa_assert(c);
 
     c->url = pa_xstrdup(url);
 }
 
+bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) {
+    pa_assert(c);
+    pa_assert(key);
+
+    return pa_headerlist_contains(c->headers, key);
+}
+
 void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) {
     pa_assert(c);
     pa_assert(key);
@@ -382,17 +416,18 @@ void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) {
     pa_headerlist_puts(c->headers, key, value);
 }
 
-void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) {
+const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key) {
     pa_assert(c);
     pa_assert(key);
 
-    pa_headerlist_remove(c->headers, key);
+    return pa_headerlist_gets(c->headers, key);
 }
 
-bool pa_rtsp_exec_ready(const pa_rtsp_client *c) {
+void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) {
     pa_assert(c);
+    pa_assert(key);
 
-    return c->url != NULL && c->ioline != NULL;
+    pa_headerlist_remove(c->headers, key);
 }
 
 static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
@@ -527,17 +562,6 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) {
     return rv;
 }
 
-int pa_rtsp_teardown(pa_rtsp_client *c) {
-    int rv;
-
-    pa_assert(c);
-
-    c->state = STATE_TEARDOWN;
-    rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
-
-    return rv;
-}
-
 int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) {
     int rv;
 
@@ -570,3 +594,14 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) {
     pa_headerlist_free(headers);
     return rv;
 }
+
+int pa_rtsp_teardown(pa_rtsp_client *c) {
+    int rv;
+
+    pa_assert(c);
+
+    c->state = STATE_TEARDOWN;
+    rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
+
+    return rv;
+}
diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h
index abc60ee..4a35851 100644
--- a/src/modules/rtp/rtsp_client.h
+++ b/src/modules/rtp/rtsp_client.h
@@ -31,43 +31,53 @@
 #include "headerlist.h"
 
 typedef struct pa_rtsp_client pa_rtsp_client;
+
 typedef enum {
   STATE_CONNECT,
   STATE_OPTIONS,
   STATE_ANNOUNCE,
   STATE_SETUP,
   STATE_RECORD,
+  STATE_SET_PARAMETER,
   STATE_FLUSH,
   STATE_TEARDOWN,
-  STATE_SET_PARAMETER,
   STATE_DISCONNECTED
 } pa_rtsp_state;
-typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist *hl, void *userdata);
+
+typedef enum {
+  STATUS_OK             = 200,
+  STATUS_BAD_REQUEST    = 400,
+  STATUS_UNAUTHORIZED   = 401,
+  STATUS_NO_RESPONSE    = 444,
+  STATUS_INTERNAL_ERROR = 500
+} pa_rtsp_status;
+
+typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_rtsp_status code, pa_headerlist *headers, void *userdata);
 
 pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostname, uint16_t port, const char *useragent);
 void pa_rtsp_client_free(pa_rtsp_client *c);
 
 int pa_rtsp_connect(pa_rtsp_client *c);
 void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata);
-
 void pa_rtsp_disconnect(pa_rtsp_client *c);
 
 const char* pa_rtsp_localip(pa_rtsp_client *c);
 uint32_t pa_rtsp_serverport(pa_rtsp_client *c);
+bool pa_rtsp_exec_ready(const pa_rtsp_client *c);
+
 void pa_rtsp_set_url(pa_rtsp_client *c, const char *url);
+
+bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key);
 void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value);
+const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key);
 void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key);
 
-bool pa_rtsp_exec_ready(const pa_rtsp_client *c);
-
 int pa_rtsp_options(pa_rtsp_client *c);
 int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp);
-
 int pa_rtsp_setup(pa_rtsp_client *c, const char *transport);
 int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime);
-int pa_rtsp_teardown(pa_rtsp_client *c);
-
 int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param);
 int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime);
+int pa_rtsp_teardown(pa_rtsp_client *c);
 
 #endif

commit 66d55a5ce8126fd257900bf67d11de4fe615a37d
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:05 2016 -0600

    raop: Add a MD5 hashing fuction
    
    MD5 hashing will be needed during the authentication process.
    
    Original patch by Martin Blanchard. Patch splitted by
    Hajime Fujita <crisp.fujita at nifty.com>.

diff --git a/src/modules/raop/raop_util.c b/src/modules/raop/raop_util.c
index d24d67d..c9021ec 100644
--- a/src/modules/raop/raop_util.c
+++ b/src/modules/raop/raop_util.c
@@ -30,12 +30,21 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <openssl/err.h>
+#include <openssl/md5.h>
+
 #include <pulse/xmalloc.h>
 
 #include <pulsecore/macro.h>
 
 #include "raop_util.h"
 
+#ifndef MD5_DIGEST_LENGTH
+#define MD5_DIGEST_LENGTH 16
+#endif
+
+#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
+
 #define BASE64_DECODE_ERROR 0xffffffff
 
 static const char base64_chars[] =
@@ -141,3 +150,21 @@ int pa_raop_base64_decode(const char *str, void *data) {
 
     return q - (unsigned char *) data;
 }
+
+int pa_raop_md5_hash(const char *data, int len, char **str) {
+    unsigned char d[MD5_DIGEST_LENGTH];
+    char *s = NULL;
+    int i;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    MD5((unsigned char*) data, len, d);
+    s = pa_xnew(char, MD5_HASH_LENGTH);
+    for (i = 0; i < MD5_DIGEST_LENGTH; i++)
+        sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
+
+    *str = s;
+    s[MD5_HASH_LENGTH] = 0;
+    return strlen(s);
+}
diff --git a/src/modules/raop/raop_util.h b/src/modules/raop/raop_util.h
index 7a8d73e..dc0b767 100644
--- a/src/modules/raop/raop_util.h
+++ b/src/modules/raop/raop_util.h
@@ -30,4 +30,6 @@
 int pa_raop_base64_encode(const void *data, int len, char **str);
 int pa_raop_base64_decode(const char *str, void *data);
 
+int pa_raop_md5_hash(const char *data, int len, char **str);
+
 #endif

commit 3b983ec2a0e4a7df6884db639b3d9154401b51bf
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:04 2016 -0600

    raop: Move base64 implementation to a util file
    
    Base64 implementation is now in a common file called raop_util.c.
    Old Base64 files are removed but copyright is preserved.
    
    Original patch by Martin Blanchard, patch splitted by
    Hajime Fujita <crisp.fujita at nifty.com>.

diff --git a/src/Makefile.am b/src/Makefile.am
index 2b01d3e..97a331b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1154,9 +1154,9 @@ librtp_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon-@
 
 libraop_la_SOURCES = \
         modules/raop/raop_client.c modules/raop/raop_client.h \
+        modules/raop/raop_packet_buffer.h modules/raop/raop_packet_buffer.c \
         modules/raop/raop_crypto.c modules/raop/raop_crypto.h \
-        modules/raop/base64.c modules/raop/base64.h \
-        modules/raop/raop_packet_buffer.h modules/raop/raop_packet_buffer.c
+        modules/raop/raop_util.c modules/raop/raop_util.h
 libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) -I$(top_srcdir)/src/modules/rtp
 libraop_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
 libraop_la_LIBADD = $(AM_LIBADD) $(OPENSSL_LIBS) libpulsecore- at PA_MAJORMINOR@.la librtp.la libpulsecommon- at PA_MAJORMINOR@.la libpulse.la
diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c
deleted file mode 100644
index 073e6a2..0000000
--- a/src/modules/raop/base64.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-/***
-  This file was originally inspired by a file developed by
-  Kungliga Tekniska högskolan
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <pulse/xmalloc.h>
-
-#include "base64.h"
-
-static const char base64_chars[] =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-static int pos(char c) {
-    if (c >= 'A' && c <= 'Z')
-        return c - 'A' + 0;
-    if (c >= 'a' && c <= 'z')
-        return c - 'a' + 26;
-    if (c >= '0' && c <= '9')
-        return c - '0' + 52;
-    if (c == '+')
-        return 62;
-    if (c == '/')
-        return 63;
-
-    return -1;
-}
-
-int pa_base64_encode(const void *data, int size, char **str) {
-    char *s, *p;
-    int i;
-    int c;
-    const unsigned char *q;
-
-    p = s = pa_xnew(char, size * 4 / 3 + 4);
-    q = (const unsigned char *) data;
-    for (i = 0; i < size;) {
-        c = q[i++];
-        c *= 256;
-        if (i < size)
-            c += q[i];
-        i++;
-        c *= 256;
-        if (i < size)
-            c += q[i];
-        i++;
-        p[0] = base64_chars[(c & 0x00fc0000) >> 18];
-        p[1] = base64_chars[(c & 0x0003f000) >> 12];
-        p[2] = base64_chars[(c & 0x00000fc0) >> 6];
-        p[3] = base64_chars[(c & 0x0000003f) >> 0];
-        if (i > size)
-            p[3] = '=';
-        if (i > size + 1)
-            p[2] = '=';
-        p += 4;
-    }
-
-    *p = 0;
-    *str = s;
-
-    return strlen(s);
-}
-
-#define DECODE_ERROR 0xffffffff
-
-static unsigned int token_decode(const char *token) {
-    int i;
-    unsigned int val = 0;
-    int marker = 0;
-
-    if (strlen(token) < 4)
-        return DECODE_ERROR;
-    for (i = 0; i < 4; i++) {
-        val *= 64;
-        if (token[i] == '=')
-            marker++;
-        else if (marker > 0)
-            return DECODE_ERROR;
-        else {
-            int lpos = pos(token[i]);
-            if (lpos < 0)
-                return DECODE_ERROR;
-            val += lpos;
-        }
-    }
-
-    if (marker > 2)
-        return DECODE_ERROR;
-
-    return (marker << 24) | val;
-}
-
-int pa_base64_decode(const char *str, void *data) {
-    const char *p;
-    unsigned char *q;
-
-    q = data;
-    for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
-        unsigned int val = token_decode(p);
-        unsigned int marker = (val >> 24) & 0xff;
-        if (val == DECODE_ERROR)
-            return -1;
-        *q++ = (val >> 16) & 0xff;
-        if (marker < 2)
-            *q++ = (val >> 8) & 0xff;
-        if (marker < 1)
-            *q++ = val & 0xff;
-    }
-
-    return q - (unsigned char *) data;
-}
diff --git a/src/modules/raop/base64.h b/src/modules/raop/base64.h
deleted file mode 100644
index 478fa4d..0000000
--- a/src/modules/raop/base64.h
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef foobase64hfoo
-#define foobase64hfoo
-
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008 Colin Guthrie
-  Copyright Kungliga Tekniska högskolan
-
-  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.1 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, see <http://www.gnu.org/licenses/>.
-***/
-
-/***
-  This file was originally inspired by a file developed by
-  Kungliga Tekniska högskolan
-***/
-
-int pa_base64_encode(const void *data, int size, char **str);
-int pa_base64_decode(const char *str, void *data);
-
-#endif
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index 5c825eb..a2ec91e 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -53,7 +53,7 @@
 #include "rtsp_client.h"
 #include "raop_packet_buffer.h"
 #include "raop_crypto.h"
-#include "base64.h"
+#include "raop_util.h"
 
 #define JACK_STATUS_DISCONNECTED 0
 #define JACK_STATUS_CONNECTED 1
@@ -528,7 +528,7 @@ static void do_rtsp_announce(pa_raop_client *c) {
     /* UDP protocol does not need "Apple-Challenge" at announce. */
     if (c->protocol == RAOP_TCP) {
         pa_random(&rand_data, sizeof(rand_data));
-        pa_base64_encode(&rand_data, sizeof(rand_data), &sac);
+        pa_raop_base64_encode(&rand_data, 8*sizeof(rand_data), &sac);
         rtrimchar(sac, '=');
         pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
     }
@@ -689,7 +689,7 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             /* Set the Apple-Challenge key */
             pa_random(&rand, sizeof(rand));
-            pa_base64_encode(&rand, sizeof(rand), &sac);
+            pa_raop_base64_encode(&rand, 8*sizeof(rand), &sac);
             rtrimchar(sac, '=');
             pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
 
diff --git a/src/modules/raop/raop_crypto.c b/src/modules/raop/raop_crypto.c
index 6cbb840..4d08e26 100644
--- a/src/modules/raop/raop_crypto.c
+++ b/src/modules/raop/raop_crypto.c
@@ -37,7 +37,7 @@
 #include <pulsecore/random.h>
 
 #include "raop_crypto.h"
-#include "base64.h"
+#include "raop_util.h"
 
 #define AES_CHUNK_SIZE 16
 
@@ -85,9 +85,9 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
     pa_assert(str);
 
     rsa = RSA_new();
-    size = pa_base64_decode(rsa_modulus, modules);
+    size = pa_raop_base64_decode(rsa_modulus, modules);
     n_bn = BN_bin2bn(modules, size, NULL);
-    size = pa_base64_decode(rsa_exponent, exponent);
+    size = pa_raop_base64_decode(rsa_exponent, exponent);
     e_bn = BN_bin2bn(exponent, size, NULL);
     RSA_set0_key(rsa, n_bn, e_bn, NULL);
 
@@ -120,7 +120,7 @@ char* pa_raop_secret_get_iv(pa_raop_secret *s) {
 
     pa_assert(s);
 
-    pa_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
+    pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
 
     return base64_iv;
 }
@@ -134,7 +134,7 @@ char* pa_raop_secret_get_key(pa_raop_secret *s) {
 
     /* Encrypt our AES public key to send to the device */
     size = rsa_encrypt(s->key, AES_CHUNK_SIZE, rsa_key);
-    pa_base64_encode(rsa_key, size, &base64_key);
+    pa_raop_base64_encode(rsa_key, size, &base64_key);
 
     return base64_key;
 }
diff --git a/src/modules/raop/raop_util.c b/src/modules/raop/raop_util.c
new file mode 100644
index 0000000..d24d67d
--- /dev/null
+++ b/src/modules/raop/raop_util.c
@@ -0,0 +1,143 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+  Copyright Kungliga Tekniska Høgskolan & 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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+  The base64 implementation was originally inspired by a file developed
+  by Kungliga Tekniska högskolan.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include "raop_util.h"
+
+#define BASE64_DECODE_ERROR 0xffffffff
+
+static const char base64_chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int char_position(char c) {
+    if (c >= 'A' && c <= 'Z')
+        return c - 'A' + 0;
+    if (c >= 'a' && c <= 'z')
+        return c - 'a' + 26;
+    if (c >= '0' && c <= '9')
+        return c - '0' + 52;
+    if (c == '+')
+        return 62;
+    if (c == '/')
+        return 63;
+
+    return -1;
+}
+
+static unsigned int token_decode(const char *token) {
+    unsigned int val = 0;
+    int marker = 0;
+    int i;
+
+    if (strlen(token) < 4)
+        return BASE64_DECODE_ERROR;
+    for (i = 0; i < 4; i++) {
+        val *= 64;
+        if (token[i] == '=')
+            marker++;
+        else if (marker > 0)
+            return BASE64_DECODE_ERROR;
+        else {
+            int lpos = char_position(token[i]);
+            if (lpos < 0)
+                return BASE64_DECODE_ERROR;
+            val += lpos;
+        }
+    }
+
+    if (marker > 2)
+        return BASE64_DECODE_ERROR;
+
+    return (marker << 24) | val;
+}
+
+int pa_raop_base64_encode(const void *data, int len, char **str) {
+    const unsigned char *q;
+    char *p, *s = NULL;
+    int i, c;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    p = s = pa_xnew(char, len * 4 / 3 + 4);
+    q = (const unsigned char *) data;
+    for (i = 0; i < len;) {
+        c = q[i++];
+        c *= 256;
+        if (i < len)
+            c += q[i];
+        i++;
+        c *= 256;
+        if (i < len)
+            c += q[i];
+        i++;
+        p[0] = base64_chars[(c & 0x00fc0000) >> 18];
+        p[1] = base64_chars[(c & 0x0003f000) >> 12];
+        p[2] = base64_chars[(c & 0x00000fc0) >> 6];
+        p[3] = base64_chars[(c & 0x0000003f) >> 0];
+        if (i > len)
+            p[3] = '=';
+        if (i > len + 1)
+            p[2] = '=';
+        p += 4;
+    }
+
+    *p = 0;
+    *str = s;
+    return strlen(s);
+}
+
+int pa_raop_base64_decode(const char *str, void *data) {
+    const char *p;
+    unsigned char *q;
+
+    pa_assert(str);
+    pa_assert(data);
+
+    q = data;
+    for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
+        unsigned int val = token_decode(p);
+        unsigned int marker = (val >> 24) & 0xff;
+        if (val == BASE64_DECODE_ERROR)
+            return -1;
+        *q++ = (val >> 16) & 0xff;
+        if (marker < 2)
+            *q++ = (val >> 8) & 0xff;
+        if (marker < 1)
+            *q++ = val & 0xff;
+    }
+
+    return q - (unsigned char *) data;
+}
diff --git a/src/modules/raop/raop_util.h b/src/modules/raop/raop_util.h
new file mode 100644
index 0000000..7a8d73e
--- /dev/null
+++ b/src/modules/raop/raop_util.h
@@ -0,0 +1,33 @@
+#ifndef fooraoputilfoo
+#define fooraoputilfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008 Colin Guthrie
+  Copyright Kungliga Tekniska högskolan
+  Copyright 2013 Martin Blanchard
+
+  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.1 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, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+  This file was originally inspired by a file developed by
+  Kungliga Tekniska högskolan.
+***/
+
+int pa_raop_base64_encode(const void *data, int len, char **str);
+int pa_raop_base64_decode(const char *str, void *data);
+
+#endif

commit 71bdde771e3cd6a6f258667e463638a7edcc9448
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:03 2016 -0600

    raop: Extract encryption related code into a separate file
    
    That makes the raop_client.c code smaller/cleaner and will simplify
    addition of more crypto related stuffs like authentication.

diff --git a/src/Makefile.am b/src/Makefile.am
index f12324b..2b01d3e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1154,6 +1154,7 @@ librtp_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon-@
 
 libraop_la_SOURCES = \
         modules/raop/raop_client.c modules/raop/raop_client.h \
+        modules/raop/raop_crypto.c modules/raop/raop_crypto.h \
         modules/raop/base64.c modules/raop/base64.h \
         modules/raop/raop_packet_buffer.h modules/raop/raop_packet_buffer.c
 libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) -I$(top_srcdir)/src/modules/rtp
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index 16a59cc..5c825eb 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -32,13 +32,6 @@
 #include <sys/filio.h>
 #endif
 
-/* TODO: Replace OpenSSL with NSS */
-#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 <pulse/timeval.h>
 #include <pulse/sample.h>
@@ -56,12 +49,11 @@
 #include <pulsecore/random.h>
 
 #include "raop_client.h"
-#include "rtsp_client.h"
-#include "base64.h"
 
+#include "rtsp_client.h"
 #include "raop_packet_buffer.h"
-
-#define AES_CHUNKSIZE 16
+#include "raop_crypto.h"
+#include "base64.h"
 
 #define JACK_STATUS_DISCONNECTED 0
 #define JACK_STATUS_CONNECTED 1
@@ -89,21 +81,6 @@ typedef enum {
     UDP_PAYLOAD_AUDIO_DATA = 0x60
 } pa_raop_udp_payload_type;
 
-/* Openssl 1.1.0 broke compatibility. Before 1.1.0 we had to set RSA->n and
- * RSA->e manually, but after 1.1.0 the RSA struct is opaque and we have to use
- * RSA_set0_key(). RSA_set0_key() is a new function added in 1.1.0. We could
- * depend on openssl 1.1.0, but it may take some time before distributions will
- * be able to upgrade to the new openssl version. To insulate ourselves from
- * such transition problems, let's implement RSA_set0_key() ourselves if it's
- * not available. */
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
-static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
-    r->n = n;
-    r->e = e;
-    return 1;
-}
-#endif
-
 struct pa_raop_client {
     pa_core *core;
     char *host;
@@ -115,12 +92,8 @@ struct pa_raop_client {
     uint8_t jack_type;
     uint8_t jack_status;
 
-    /* Encryption Related bits */
     int encryption; /* Enable encryption? */
-    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_raop_secret *aes;
 
     uint16_t seq;
     uint32_t rtptime;
@@ -255,53 +228,6 @@ static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uin
     }
 }
 
-static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) {
-    const char n[] =
-        "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
-        "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
-        "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
-        "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
-        "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
-        "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
-    const char e[] = "AQAB";
-    uint8_t modules[256];
-    uint8_t exponent[8];
-    int size;
-    RSA *rsa;
-    BIGNUM *n_bn;
-    BIGNUM *e_bn;
-
-    rsa = RSA_new();
-    size = pa_base64_decode(n, modules);
-    n_bn = BN_bin2bn(modules, size, NULL);
-    size = pa_base64_decode(e, exponent);
-    e_bn = BN_bin2bn(exponent, size, NULL);
-    RSA_set0_key(rsa, n_bn, e_bn, NULL);
-
-    size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING);
-    RSA_free(rsa);
-    return size;
-}
-
-static int aes_encrypt(pa_raop_client *c, uint8_t *data, int size) {
-    uint8_t *buf;
-    int i=0, j;
-
-    pa_assert(c);
-
-    memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE);
-    while (i+AES_CHUNKSIZE <= size) {
-        buf = data + i;
-        for (j=0; j<AES_CHUNKSIZE; ++j)
-            buf[j] ^= c->aes_nv[j];
-
-        AES_encrypt(buf, buf, &c->aes);
-        memcpy(c->aes_nv, buf, AES_CHUNKSIZE);
-        i += AES_CHUNKSIZE;
-    }
-    return i;
-}
-
 static inline void rtrimchar(char *str, char rc) {
     char *sp = str + strlen(str) - 1;
     while (sp >= str && *sp == rc) {
@@ -588,8 +514,6 @@ static ssize_t udp_send_audio_packet(pa_raop_client *c, bool retrans, uint8_t *b
 }
 
 static void do_rtsp_announce(pa_raop_client *c) {
-    int i;
-    uint8_t rsakey[512];
     char *key, *iv, *sac = NULL, *sdp;
     uint16_t rand_data;
     const char *ip;
@@ -601,22 +525,20 @@ static void do_rtsp_announce(pa_raop_client *c) {
     pa_rtsp_set_url(c->rtsp, url);
     pa_xfree(url);
 
-    /* Now encrypt our aes_public key to send to the device. */
-    i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey);
-    pa_base64_encode(rsakey, i, &key);
-    rtrimchar(key, '=');
-    pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv);
-    rtrimchar(iv, '=');
-
     /* UDP protocol does not need "Apple-Challenge" at announce. */
     if (c->protocol == RAOP_TCP) {
         pa_random(&rand_data, sizeof(rand_data));
-        pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac);
+        pa_base64_encode(&rand_data, sizeof(rand_data), &sac);
         rtrimchar(sac, '=');
         pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
     }
 
-    if (c->encryption)
+    if (c->encryption) {
+        iv = pa_raop_secret_get_iv(c->aes);
+        rtrimchar(iv, '=');
+        key = pa_raop_secret_get_key(c->aes);
+        rtrimchar(key, '=');
+
         sdp = pa_sprintf_malloc(
             "v=0\r\n"
             "o=iTunes %s 0 IN IP4 %s\r\n"
@@ -631,7 +553,10 @@ static void do_rtsp_announce(pa_raop_client *c) {
             c->sid, ip, c->host,
             c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET,
             key, iv);
-    else
+
+        pa_xfree(iv);
+        pa_xfree(key);
+    } else {
         sdp = pa_sprintf_malloc(
             "v=0\r\n"
             "o=iTunes %s 0 IN IP4 %s\r\n"
@@ -643,10 +568,10 @@ static void do_rtsp_announce(pa_raop_client *c) {
             "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
             c->sid, ip, c->host,
             c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET);
+    }
 
     pa_rtsp_announce(c->rtsp, sdp);
-    pa_xfree(key);
-    pa_xfree(iv);
+
     pa_xfree(sac);
     pa_xfree(sdp);
 }
@@ -764,7 +689,7 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             /* Set the Apple-Challenge key */
             pa_random(&rand, sizeof(rand));
-            pa_base64_encode(&rand, AES_CHUNKSIZE, &sac);
+            pa_base64_encode(&rand, sizeof(rand), &sac);
             rtrimchar(sac, '=');
             pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
 
@@ -1081,6 +1006,9 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
     c->udp_control_fd = -1;
     c->udp_timing_fd = -1;
 
+    c->encryption = 0;
+    c->aes = NULL;
+
     c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
     c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
     c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
@@ -1116,11 +1044,15 @@ void pa_raop_client_free(pa_raop_client *c) {
     pa_assert(c);
 
     pa_raop_pb_delete(c->packet_buffer);
-    if (c->rtsp)
-        pa_rtsp_client_free(c->rtsp);
+
     if (c->sid)
         pa_xfree(c->sid);
+    if (c->aes)
+        pa_raop_secret_free(c->aes);
+    if (c->rtsp)
+        pa_rtsp_client_free(c->rtsp);
     pa_xfree(c->host);
+
     pa_xfree(c);
 }
 
@@ -1144,12 +1076,6 @@ int pa_raop_client_connect(pa_raop_client *c) {
     else
         c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)");
 
-    /* Initialise the AES encryption system. */
-    pa_random(c->aes_iv, sizeof(c->aes_iv));
-    pa_random(c->aes_key, sizeof(c->aes_key));
-    memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv));
-    AES_set_encrypt_key(c->aes_key, 128, &c->aes);
-
     /* Generate random instance id. */
     pa_random(&rand_data, sizeof(rand_data));
     c->sid = pa_sprintf_malloc("%u", rand_data.a);
@@ -1520,7 +1446,7 @@ int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchun
 
     if (c->encryption) {
         /* Encrypt our data. */
-        aes_encrypt(c, (b + header_size), size);
+        pa_raop_aes_encrypt(c->aes, (b + header_size), size);
     }
 
     /* We're done with the chunk. */
@@ -1544,7 +1470,11 @@ void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_cl
 }
 
 void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) {
+    pa_assert(c);
+
     c->encryption = encryption;
+    if (c->encryption)
+        c->aes = pa_raop_secret_new();
 }
 
 void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) {
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index d49c146..5d0bb14 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -50,8 +50,7 @@ pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
 int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume);
 int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchunk *encoded);
 
-int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet
-[], ssize_t size);
+int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
 int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
 int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size);
 ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block);
diff --git a/src/modules/raop/raop_crypto.c b/src/modules/raop/raop_crypto.c
new file mode 100644
index 0000000..6cbb840
--- /dev/null
+++ b/src/modules/raop/raop_crypto.c
@@ -0,0 +1,164 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+
+  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.1 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 <stdint.h>
+#include <string.h>
+
+#include <openssl/err.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/random.h>
+
+#include "raop_crypto.h"
+#include "base64.h"
+
+#define AES_CHUNK_SIZE 16
+
+/* Openssl 1.1.0 broke compatibility. Before 1.1.0 we had to set RSA->n and
+ * RSA->e manually, but after 1.1.0 the RSA struct is opaque and we have to use
+ * RSA_set0_key(). RSA_set0_key() is a new function added in 1.1.0. We could
+ * depend on openssl 1.1.0, but it may take some time before distributions will
+ * be able to upgrade to the new openssl version. To insulate ourselves from
+ * such transition problems, let's implement RSA_set0_key() ourselves if it's
+ * not available. */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
+    r->n = n;
+    r->e = e;
+    return 1;
+}
+#endif
+
+struct pa_raop_secret {
+    uint8_t key[AES_CHUNK_SIZE]; /* Key for aes-cbc */
+    uint8_t iv[AES_CHUNK_SIZE];  /* Initialization vector for cbc */
+    AES_KEY aes;                 /* AES encryption */
+};
+
+static const char rsa_modulus[] =
+    "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
+    "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
+    "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
+    "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
+    "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
+    "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
+
+static const char rsa_exponent[] =
+    "AQAB";
+
+static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
+    uint8_t modules[256];
+    uint8_t exponent[8];
+    int size;
+    RSA *rsa;
+    BIGNUM *n_bn;
+    BIGNUM *e_bn;
+
+    pa_assert(data);
+    pa_assert(str);
+
+    rsa = RSA_new();
+    size = pa_base64_decode(rsa_modulus, modules);
+    n_bn = BN_bin2bn(modules, size, NULL);
+    size = pa_base64_decode(rsa_exponent, exponent);
+    e_bn = BN_bin2bn(exponent, size, NULL);
+    RSA_set0_key(rsa, n_bn, e_bn, NULL);
+
+    size = RSA_public_encrypt(len, data, str, rsa, RSA_PKCS1_OAEP_PADDING);
+
+    RSA_free(rsa);
+    return size;
+}
+
+pa_raop_secret* pa_raop_secret_new(void) {
+    pa_raop_secret *s = pa_xnew0(pa_raop_secret, 1);
+
+    pa_assert(s);
+
+    pa_random(s->key, sizeof(s->key));
+    AES_set_encrypt_key(s->key, 128, &s->aes);
+    pa_random(s->iv, sizeof(s->iv));
+
+    return s;
+}
+
+void pa_raop_secret_free(pa_raop_secret *s) {
+    pa_assert(s);
+
+    pa_xfree(s);
+}
+
+char* pa_raop_secret_get_iv(pa_raop_secret *s) {
+    char *base64_iv = NULL;
+
+    pa_assert(s);
+
+    pa_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
+
+    return base64_iv;
+}
+
+char* pa_raop_secret_get_key(pa_raop_secret *s) {
+    char *base64_key = NULL;
+    uint8_t rsa_key[512];
+    int size = 0;
+
+    pa_assert(s);
+
+    /* Encrypt our AES public key to send to the device */
+    size = rsa_encrypt(s->key, AES_CHUNK_SIZE, rsa_key);
+    pa_base64_encode(rsa_key, size, &base64_key);
+
+    return base64_key;
+}
+
+int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len) {
+    static uint8_t nv[AES_CHUNK_SIZE];
+    uint8_t *buffer;
+    int i = 0, j;
+
+    pa_assert(s);
+    pa_assert(data);
+
+    memcpy(nv, s->iv, AES_CHUNK_SIZE);
+
+    while (i + AES_CHUNK_SIZE <= len) {
+        buffer = data + i;
+        for (j = 0; j < AES_CHUNK_SIZE; ++j)
+            buffer[j] ^= nv[j];
+
+        AES_encrypt(buffer, buffer, &s->aes);
+
+        memcpy(nv, buffer, AES_CHUNK_SIZE);
+        i += AES_CHUNK_SIZE;
+    }
+
+    return i;
+}
diff --git a/src/modules/raop/raop_crypto.h b/src/modules/raop/raop_crypto.h
new file mode 100644
index 0000000..65f7577
--- /dev/null
+++ b/src/modules/raop/raop_crypto.h
@@ -0,0 +1,35 @@
+#ifndef fooraopcryptofoo
+#define fooraopcryptofoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 Martin Blanchard
+
+  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.1 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.
+***/
+
+typedef struct pa_raop_secret pa_raop_secret;
+
+pa_raop_secret* pa_raop_secret_new(void);
+void pa_raop_secret_free(pa_raop_secret *s);
+
+char* pa_raop_secret_get_iv(pa_raop_secret *s);
+char* pa_raop_secret_get_key(pa_raop_secret *s);
+
+int pa_raop_aes_encrypt(pa_raop_secret *s, uint8_t *data, int len);
+
+#endif

commit f8070f92ef7e065644578c5e6b3909493e0d4abb
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:02 2016 -0600

    raop: Better playback resume handling
    
    When playback stops, a FLUSH command is send to the server and the sink
    goes to IDLE. If playback resumes quickly, sink goes back to RUNNING
    (without being SUSPENDED) and the sink should just start streaming again.
    This patch implements this behaviour.

diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 4ed0c1b..564ef99 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -332,10 +332,14 @@ static int udp_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t o
 
                     pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
 
-                    if (!pa_raop_client_udp_can_stream(u->raop)) {
-                        /* Connecting will trigger a RECORD */
+                    if (!pa_raop_client_udp_is_alive(u->raop)) {
+                        /* Connecting will trigger a RECORD and start steaming */
                         pa_raop_client_connect(u->raop);
+                    } else if (!pa_raop_client_udp_can_stream(u->raop)) {
+                        /* RECORD alredy sent, simply start streaming */
+                        pa_raop_client_udp_stream(u->raop);
                     }
+
                     udp_start_wakeup_clock(u);
 
                     break;
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index d17cc44..16a59cc 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -952,8 +952,6 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             pa_log_debug("RAOP: RECORD");
 
-            c->is_recording = true;
-
             alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
             /* Generate a random synchronization source identifier from this session. */
             pa_random(&rand, sizeof(rand));
@@ -965,6 +963,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
             c->udp_first_packet = true;
             c->udp_sync_count = 0;
 
+            c->is_recording = true;
+
             c->udp_record_callback(c->udp_setup_userdata);
 
             pa_xfree(alt);
@@ -1168,11 +1168,12 @@ int pa_raop_client_connect(pa_raop_client *c) {
 
 int pa_raop_client_flush(pa_raop_client *c) {
     int rv = 0;
+
     pa_assert(c);
 
     if (c->rtsp != NULL) {
         rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
-        c->udp_sync_count = -1;
+        c->udp_sync_count = 0;
     }
 
     return rv;
@@ -1211,6 +1212,25 @@ int pa_raop_client_udp_can_stream(pa_raop_client *c) {
     return rv;
 }
 
+int pa_raop_client_udp_stream(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->rtsp != NULL && c->udp_stream_fd > 0) {
+        if (!c->is_recording) {
+            c->udp_first_packet = true;
+            c->udp_sync_count = 0;
+
+            c->is_recording = true;
+         }
+
+        rv = 1;
+    }
+
+    return rv;
+}
+
 int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
     const uint32_t * data = NULL;
     uint8_t payload = 0;
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 6ab6d32..d49c146 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -43,6 +43,7 @@ int pa_raop_client_teardown(pa_raop_client *c);
 
 int pa_raop_client_udp_is_alive(pa_raop_client *c);
 int pa_raop_client_udp_can_stream(pa_raop_client *c);
+int pa_raop_client_udp_stream(pa_raop_client *c);
 
 void pa_raop_client_set_encryption(pa_raop_client *c, int encryption);
 pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);

commit cba5914de5b3e0a0e7c3f688edf09fda165f15d9
Author: Martin Blanchard <tchaik at gmx.com>
Date:   Sun Nov 6 12:54:01 2016 -0600

    raop: Do not send audio before RECORD response
    
    This patch prevents audio packets to be sent before the server
    respond to the RECORD command.

diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index c1afedd..4ed0c1b 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -310,7 +310,7 @@ static int udp_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t o
                     pa_log_debug("RAOP: SUSPENDED");
                     pa_smoother_pause(u->smoother, pa_rtclock_now());
 
-                    if (pa_raop_client_udp_can_stream(u->raop)) {
+                    if (pa_raop_client_udp_is_alive(u->raop)) {
                         /* Issue a TEARDOWN if we are still connected. */
                         pa_raop_client_teardown(u->raop);
                     }
@@ -756,10 +756,10 @@ static void udp_thread_func(struct userdata *u) {
             continue;
         }
 
-        if (!pa_raop_client_udp_can_stream(u->raop))
-            continue;
         if (u->sink->thread_info.state != PA_SINK_RUNNING)
             continue;
+        if (!pa_raop_client_udp_can_stream(u->raop))
+            continue;
 
         if (u->encoded_memchunk.length <= 0) {
             if (u->encoded_memchunk.memblock != NULL)
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index 9c31162..d17cc44 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -146,6 +146,8 @@ struct pa_raop_client {
 
     uint32_t udp_ssrc;
 
+    bool is_recording;
+
     bool udp_first_packet;
     uint32_t udp_sync_interval;
     uint32_t udp_sync_count;
@@ -950,6 +952,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             pa_log_debug("RAOP: RECORD");
 
+            c->is_recording = true;
+
             alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
             /* Generate a random synchronization source identifier from this session. */
             pa_random(&rand, sizeof(rand));
@@ -976,6 +980,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
         case STATE_FLUSH: {
             pa_log_debug("RAOP: FLUSHED");
 
+            c->is_recording = false;
+
             break;
         }
 
@@ -984,6 +990,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
             pa_assert(c->udp_disconnected_callback);
             pa_assert(c->rtsp);
 
+            c->is_recording = false;
+
             pa_rtsp_disconnect(c->rtsp);
 
             if (c->udp_stream_fd > 0) {
@@ -1084,6 +1092,8 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
     else
         c->port = DEFAULT_RAOP_PORT;
 
+    c->is_recording = false;
+
     c->udp_first_packet = true;
 
     ss = core->default_sample_spec;
@@ -1151,6 +1161,8 @@ int pa_raop_client_connect(pa_raop_client *c) {
     else
         pa_rtsp_set_callback(c->rtsp, udp_rtsp_cb, c);
 
+    c->is_recording = false;
+
     return pa_rtsp_connect(c->rtsp);
 }
 
@@ -1177,7 +1189,7 @@ int pa_raop_client_teardown(pa_raop_client *c) {
     return rv;
 }
 
-int pa_raop_client_udp_can_stream(pa_raop_client *c) {
+int pa_raop_client_udp_is_alive(pa_raop_client *c) {
     int rv = 0;
 
     pa_assert(c);
@@ -1188,6 +1200,17 @@ int pa_raop_client_udp_can_stream(pa_raop_client *c) {
     return rv;
 }
 
+int pa_raop_client_udp_can_stream(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->is_recording && c->udp_stream_fd > 0)
+        rv = 1;
+
+    return rv;
+}
+
 int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
     const uint32_t * data = NULL;
     uint8_t payload = 0;
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 578e9d0..6ab6d32 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -41,6 +41,7 @@ int pa_raop_client_connect(pa_raop_client *c);
 int pa_raop_client_flush(pa_raop_client *c);
 int pa_raop_client_teardown(pa_raop_client *c);
 
+int pa_raop_client_udp_is_alive(pa_raop_client *c);
 int pa_raop_client_udp_can_stream(pa_raop_client *c);
 
 void pa_raop_client_set_encryption(pa_raop_client *c, int encryption);

commit 6d1ba9c38a7607b896d8450fabca0e13562ac1c0
Author: Matthias Wabersich <pulseaudio at niafc.de>
Date:   Sun Nov 6 12:54:00 2016 -0600

    raop: Packet retransmission support for UDP
    
    This patch adds an RTP audio packet retransmission support and a
    circular buffer implementation for it.
    
    This patch was originally written by Matthias Wabersich [1] and
    later debugged and integrated into the latest tree by Hajime Fujita
    
    [1]: https://bugs.freedesktop.org/show_bug.cgi?id=42804#c44

diff --git a/src/Makefile.am b/src/Makefile.am
index ca702c1..f12324b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1154,7 +1154,8 @@ librtp_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon-@
 
 libraop_la_SOURCES = \
         modules/raop/raop_client.c modules/raop/raop_client.h \
-        modules/raop/base64.c modules/raop/base64.h
+        modules/raop/base64.c modules/raop/base64.h \
+        modules/raop/raop_packet_buffer.h modules/raop/raop_packet_buffer.c
 libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) -I$(top_srcdir)/src/modules/rtp
 libraop_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
 libraop_la_LIBADD = $(AM_LIBADD) $(OPENSSL_LIBS) libpulsecore- at PA_MAJORMINOR@.la librtp.la libpulsecommon- at PA_MAJORMINOR@.la libpulse.la
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index 4e8f474..9c31162 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -59,7 +59,8 @@
 #include "rtsp_client.h"
 #include "base64.h"
 
-#define UDP_FRAMES_PER_PACKET 352
+#include "raop_packet_buffer.h"
+
 #define AES_CHUNKSIZE 16
 
 #define JACK_STATUS_DISCONNECTED 0
@@ -77,6 +78,8 @@
 #define UDP_DEFAULT_CONTROL_PORT 6001
 #define UDP_DEFAULT_TIMING_PORT 6002
 
+#define UDP_DEFAULT_PKT_BUF_SIZE 1000
+
 typedef enum {
     UDP_PAYLOAD_TIMING_REQUEST = 0x52,
     UDP_PAYLOAD_TIMING_RESPONSE = 0x53,
@@ -155,6 +158,8 @@ struct pa_raop_client {
 
     pa_raop_client_disconnected_cb_t udp_disconnected_callback;
     void *udp_disconnected_userdata;
+
+    pa_raop_packet_buffer *packet_buffer;
 };
 
 /* Timming packet header (8x8):
@@ -548,12 +553,35 @@ static void udp_build_audio_header(pa_raop_client *c, uint32_t *buffer, size_t s
     buffer[2] = htonl(c->udp_ssrc);
 }
 
-static ssize_t udp_send_audio_packet(pa_raop_client *c, uint8_t *buffer, size_t size) {
-    ssize_t length;
+/* Audio retransmission header:
+ * [0]    RTP v2: 0x80
+ * [1]    Payload type: 0x56 + 0x80 (marker == on)
+ * [2]    Unknown; seems always 0x01
+ * [3]    Unknown; seems some random number around 0x20~0x40
+ * [4,5]  Original RTP header htons(0x8060)
+ * [6,7]  Packet sequence number to be retransmitted
+ * [8,11] Original RTP timestamp on the lost packet */
+static void udp_build_retrans_header(uint32_t *buffer, size_t size, uint16_t seq_num) {
+    uint8_t x = 0x30; /* FIXME: what's this?? */
+
+    pa_assert(size >= sizeof(uint32_t) * 2);
+
+    buffer[0] = htonl((uint32_t) 0x80000000
+                      | ((uint32_t) UDP_PAYLOAD_RETRANSMIT_REPLY | 0x80) << 16
+                      | 0x0100
+                      | x);
+    buffer[1] = htonl((uint32_t) 0x80600000 | seq_num);
+}
 
-    length = pa_write(c->udp_stream_fd, buffer, size, NULL);
-    c->seq++;
+static ssize_t udp_send_audio_packet(pa_raop_client *c, bool retrans, uint8_t *buffer, size_t size) {
+    ssize_t length;
+    int fd = retrans ? c->udp_control_fd : c->udp_stream_fd;
 
+    length = pa_write(fd, buffer, size, NULL);
+    if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        pa_log_debug("Discarding audio packet %d due to EAGAIN", c->seq);
+        length = size;
+    }
     return length;
 }
 
@@ -965,6 +993,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             pa_log_debug("RTSP control channel closed (teardown)");
 
+            pa_raop_pb_clear(c->packet_buffer);
+
             pa_rtsp_client_free(c->rtsp);
             pa_xfree(c->sid);
             c->rtsp = NULL;
@@ -1001,6 +1031,8 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist
 
             pa_log_debug("RTSP control channel closed (disconnected)");
 
+            pa_raop_pb_clear(c->packet_buffer);
+
             pa_rtsp_client_free(c->rtsp);
             pa_xfree(c->sid);
             c->rtsp = NULL;
@@ -1064,7 +1096,8 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
             pa_raop_client_free(c);
             return NULL;
         }
-    }
+    } else
+        c->packet_buffer = pa_raop_pb_new(UDP_DEFAULT_PKT_BUF_SIZE);
 
     return c;
 }
@@ -1072,6 +1105,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
 void pa_raop_client_free(pa_raop_client *c) {
     pa_assert(c);
 
+    pa_raop_pb_delete(c->packet_buffer);
     if (c->rtsp)
         pa_rtsp_client_free(c->rtsp);
     if (c->sid)
@@ -1188,14 +1222,48 @@ int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t pac
     return rv;
 }
 
+static int udp_resend_packets(pa_raop_client *c, uint16_t seq_num, uint16_t num_packets) {
+    int rv = -1;
+    uint8_t *data = NULL;
+    ssize_t len = 0;
+    int i = 0;
+
+    pa_assert(c);
+    pa_assert(num_packets > 0);
+    pa_assert(c->packet_buffer);
+
+    for (i = seq_num; i < seq_num + num_packets; i++) {
+        len = pa_raop_pb_read_packet(c->packet_buffer, i, (uint8_t **) &data);
+
+        if (len > 0) {
+            ssize_t r;
+
+            /* Obtained buffer has a header room for retransmission
+               header */
+            udp_build_retrans_header((uint32_t *) data, len, seq_num);
+            r = udp_send_audio_packet(c, true /* retrans */, data, len);
+            if (r == len)
+                rv = 0;
+            else
+                rv = -1;
+        } else
+            pa_log_debug("Packet not found in retrans buffer: %u", i);
+    }
+
+    return rv;
+}
+
 int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
     uint8_t payload = 0;
     int rv = 0;
 
+    uint16_t seq_num;
+    uint16_t num_packets;
+
     pa_assert(c);
     pa_assert(packet);
 
-    if (size != 20 || packet[0] != 0x80)
+    if ((size != 20 && size != 8) || packet[0] != 0x80)
     {
         pa_log_debug("Received an invalid control packet.");
         return 1;
@@ -1206,12 +1274,24 @@ int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t pa
     payload = packet[1] ^ 0x80;
     switch (payload) {
         case UDP_PAYLOAD_RETRANSMIT_REQUEST:
-            /* Packet retransmission not implemented yet... */
-            /* rv = ... */
+            pa_assert(size == 8);
+
+            /* Requested start sequence number */
+            seq_num = ((uint16_t) packet[4]) << 8;
+            seq_num |= (uint16_t) packet[5];
+            /* Number of requested packets starting at requested seq. number */
+            num_packets = (uint16_t) packet[6] << 8;
+            num_packets |= (uint16_t) packet[7];
+            pa_log_debug("Resending %d packets starting at %d", num_packets, seq_num);
+            rv = udp_resend_packets(c, seq_num, num_packets);
             break;
+
         case UDP_PAYLOAD_RETRANSMIT_REPLY:
+            pa_log_debug("Received a retransmit reply packet on control port (this should never happen)");
+            break;
+
         default:
-            pa_log_debug("Got an unexpected payload type on control channel !");
+            pa_log_debug("Got an unexpected payload type on control channel: %u !", payload);
             return 1;
     }
 
@@ -1248,7 +1328,14 @@ ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *blo
     pa_assert(buf);
     pa_assert(block->length > 0);
     udp_build_audio_header(c, (uint32_t *) (buf + block->index), block->length);
-    len = udp_send_audio_packet(c, buf + block->index, block->length);
+    len = udp_send_audio_packet(c, false, buf + block->index, block->length);
+
+    /* Store packet for resending in the packet buffer */
+    pa_raop_pb_write_packet(c->packet_buffer, c->seq, buf + block->index,
+                            block->length);
+
+    c->seq++;
+
     pa_memblock_release(block->memblock);
 
     if (len > 0) {
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 36be8dc..578e9d0 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -25,6 +25,8 @@
 #include <pulsecore/core.h>
 #include <pulsecore/memchunk.h>
 
+#define UDP_FRAMES_PER_PACKET 352
+
 typedef enum pa_raop_protocol {
     RAOP_TCP,
     RAOP_UDP,
diff --git a/src/modules/raop/raop_packet_buffer.c b/src/modules/raop/raop_packet_buffer.c
new file mode 100644
index 0000000..b8c1bc8
--- /dev/null
+++ b/src/modules/raop/raop_packet_buffer.c
@@ -0,0 +1,172 @@
+/***
+  Circular buffer for RTP audio packets with random access support
+  by RTP sequence number.
+
+  Copyright 2013 Matthias Wabersich, Hajime Fujita
+
+  This 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.1 of the License,
+  or (at your option) any later version.
+
+  This 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.
+
+***/
+
+#include <stdlib.h>
+#include <limits.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-error.h>
+#include "raop_client.h"
+
+#include "raop_packet_buffer.h"
+
+/* FRAMES_PER_PACKET*2*2 + sizeof(udp_audio_header) + sizeof(ALAC header), unencoded */
+#define PACKET_SIZE_MAX (UDP_FRAMES_PER_PACKET*2*2 + 12 + 7)
+/* Header room for packet retransmission header */
+#define RETRANS_HEADER_ROOM 4
+
+/* Packet element */
+struct pa_raop_packet_element {
+    uint16_t  seq_num; /* RTP sequence number (in host byte order) */
+    ssize_t   length;  /* Actual packet length */
+    /* Packet data including RTP header */
+    uint8_t   data[PACKET_SIZE_MAX + RETRANS_HEADER_ROOM];
+};
+
+/* Buffer struct */
+struct pa_raop_packet_buffer {
+    size_t   size;          /* max number of packets in buffer */
+    size_t   start;         /* index of oldest packet */
+    size_t   count;         /* number of packets in buffer */
+    uint16_t first_seq_num; /* Sequence number of first packet in buffer */
+    uint16_t latest_seq_num; /* Debug purpose */
+    pa_raop_packet_element *packets; /* Packet element pointer */
+};
+
+pa_raop_packet_buffer *pa_raop_pb_new(size_t size) {
+    pa_raop_packet_buffer *pb = pa_xmalloc0(sizeof(*pb));
+
+    pb->size = size;
+    pb->packets = (pa_raop_packet_element *)
+        pa_xmalloc(size * sizeof(pa_raop_packet_element));
+
+    pa_raop_pb_clear(pb);
+
+    return pb;
+}
+
+void pa_raop_pb_clear(pa_raop_packet_buffer *pb) {
+    pb->start = 0;
+    pb->count = 0;
+    pb->first_seq_num = 0;
+    pb->latest_seq_num = 0;
+    memset(pb->packets, 0, pb->size * sizeof(pa_raop_packet_element));
+}
+
+void pa_raop_pb_delete(pa_raop_packet_buffer *pb) {
+    pa_xfree(pb->packets);
+    pa_xfree(pb);
+}
+
+static int pb_is_full(pa_raop_packet_buffer *pb) {
+    return pb->count == pb->size;
+}
+
+static int pb_is_empty(pa_raop_packet_buffer *pb) {
+    return pb->count == 0;
+}
+
+static pa_raop_packet_element *pb_prepare_write(pa_raop_packet_buffer *pb, uint16_t seq) {
+    size_t end = (pb->start + pb->count) % pb->size;
+    pa_raop_packet_element *packet;
+
+    /* Set first packet sequence number in buffer if buffer is empty */
+    if (pb_is_empty(pb))
+        pb->first_seq_num = seq;
+    else
+        pa_assert((uint16_t) (pb->latest_seq_num + 1) == seq);
+
+    packet = &pb->packets[end];
+
+    if (pb_is_full(pb)) {
+        pb->start = (pb->start + 1) % pb->size; /* full, overwrite */
+
+        /* Set first packet sequence number in buffer
+           to new start packet sequence number */
+        pb->first_seq_num = pb->packets[pb->start].seq_num;
+    } else
+        ++ pb->count;
+
+    pb->latest_seq_num = seq;
+
+    return packet;
+}
+
+/* Write packet data to packet buffer */
+void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length) {
+    pa_raop_packet_element *packet;
+
+    pa_assert(pb);
+    pa_assert(packet_data);
+    pa_assert(packet_length <= PACKET_SIZE_MAX);
+
+    packet = pb_prepare_write(pb, seq_num);
+    packet->seq_num = seq_num;
+    packet->length = packet_length + RETRANS_HEADER_ROOM;
+
+    /* Insert RETRANS_HEADER_ROOM bytes in front of packet data,
+       for retransmission header */
+    memset(packet->data, 0, RETRANS_HEADER_ROOM);
+    memcpy(packet->data + RETRANS_HEADER_ROOM, packet_data, packet_length);
+}
+
+/* l < r?, considers wrapping */
+static bool seq_lt(uint16_t l, uint16_t r) {
+    return l - r > USHRT_MAX/2;
+}
+
+/* Random access to packet from buffer by sequence number for (re-)sending. */
+ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data) {
+    uint16_t index = 0; /* Index of requested packet */
+    pa_raop_packet_element *packet;
+
+    /* If the buffer is empty, there is no use in calculating indices */
+    if (pb_is_empty(pb))
+        return -1;
+
+    /* If the requested packet is too old (seq_num below first seq number
+       in buffer) or too young (seq_num greater than current seq number),
+       do nothing and return */
+    if (seq_lt(seq_num, pb->first_seq_num))
+        return -1;
+
+    index = (uint16_t) (seq_num - pb->first_seq_num);
+    if (index >= pb->count)
+        return -1;
+
+    /*  Index of the requested packet in the buffer is calculated
+        using the first sequence number stored in the buffer.
+        The offset (seq_num - first_seq_num) is used to access the array. */
+    packet = &pb->packets[(pb->start + index) % pb->size];
+
+    pa_assert(packet->data[RETRANS_HEADER_ROOM + 2] == (seq_num >> 8));
+    pa_assert(packet->data[RETRANS_HEADER_ROOM + 3] == (seq_num & 0xff));
+    pa_assert(packet_data);
+
+    *packet_data = packet->data;
+
+    return packet->length;
+}
diff --git a/src/modules/raop/raop_packet_buffer.h b/src/modules/raop/raop_packet_buffer.h
new file mode 100644
index 0000000..d8a08a0
--- /dev/null
+++ b/src/modules/raop/raop_packet_buffer.h
@@ -0,0 +1,42 @@
+#ifndef RAOP_PACKET_BUFFER_H_INCLUDED
+#define RAOP_PACKET_BUFFER_H_INCLUDED
+
+/***
+  Circular buffer for RTP audio packets with random access support
+  by RTP sequence number.
+
+  Copyright 2013 Matthias Wabersich, Hajime Fujita
+
+  This 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.1 of the License,
+  or (at your option) any later version.
+
+  This 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.
+
+***/
+
+struct pa_raop_packet_element;
+typedef struct pa_raop_packet_element pa_raop_packet_element;
+
+struct pa_raop_packet_buffer;
+typedef struct pa_raop_packet_buffer pa_raop_packet_buffer;
+
+/* Allocates a new circular packet buffer
+   size: Maximum number of packets to store */
+pa_raop_packet_buffer *pa_raop_pb_new(size_t size);
+void pa_raop_pb_clear(pa_raop_packet_buffer *pb);
+void pa_raop_pb_delete(pa_raop_packet_buffer *pb);
+
+void pa_raop_pb_write_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, const uint8_t *packet_data, ssize_t packet_length);
+ssize_t pa_raop_pb_read_packet(pa_raop_packet_buffer *pb, uint16_t seq_num, uint8_t **packet_data);
+
+#endif /* RAOP_PACKET_BUFFER_H_INCLUDED */

commit e68bd23d4195ce239f8452c0ca6c6717e9059297
Author: Martin Blanchard <tinram at gmx.fr>
Date:   Sun Nov 6 12:53:59 2016 -0600

    raop: Parse server capabilities on discovery
    
    During the discovery phase, raop servers send their capabilities
    (supported encryption, audio codec...). These should be passed to the
    raop sink via module's arguments.
    
    Original patch written by Martin Blanchard, then modified by Hajime
    Fujita <crisp.fujita at nifty.com> based on review comments by
    Anton Lundin <glance at acc.umu.se>.
    Now resolver_cb always dtrdup()s string blocks given by Avahi,
    to make the code easier to maintain.

diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c
index 1ced777..6e2542a 100644
--- a/src/modules/raop/module-raop-discover.c
+++ b/src/modules/raop/module-raop-discover.c
@@ -139,6 +139,9 @@ static void resolver_cb(
     struct userdata *u = userdata;
     struct tunnel *tnl;
     char *device = NULL, *nicename, *dname, *vname, *args;
+    char *tp = NULL, *et = NULL, *cn = NULL;
+    char *ch = NULL, *ss = NULL, *sr = NULL;
+    char *t = NULL;
     char at[AVAHI_ADDRESS_STR_MAX];
     AvahiStringList *l;
     pa_module *m;
@@ -169,6 +172,51 @@ static void resolver_cb(
         if (pa_streq(key, "device")) {
             device = value;
             value = NULL;
+        } else if (pa_streq(key, "tp")) {
+            /* Transport protocol:
+             *  - TCP = only TCP,
+             *  - UDP = only UDP,
+             *  - TCP,UDP = both supported (UDP should be prefered) */
+            if (pa_str_in_list(value, ",", "UDP"))
+                tp = pa_xstrdup("UDP");
+            else if (pa_str_in_list(value, ",", "TCP"))
+                tp = pa_xstrdup("TCP");
+            else
+                tp = pa_xstrdup(value);
+        } else if (pa_streq(key, "et")) {
+            /* Supported encryption types:
+             *  - 0 = none,
+             *  - 1 = RSA,
+             *  - 2 = FairPlay,
+             *  - 3 = MFiSAP,
+             *  - 4 = FairPlay SAPv2.5. */
+             if (pa_str_in_list(value, ",", "1"))
+                 et = pa_xstrdup("RSA");
+             else
+                 et = pa_xstrdup("none");
+        } else if (pa_streq(key, "cn")) {
+            /* Suported audio codecs:
+             *  - 0 = PCM,
+             *  - 1 = ALAC,
+             *  - 2 = AAC,
+             *  - 3 = AAC ELD. */
+            cn = pa_xstrdup("PCM");
+        } else if (pa_streq(key, "md")) {
+            /* Supported metadata types:
+             *  - 0 = text,
+             *  - 1 = artwork,
+             *  - 2 = progress. */
+        } else if (pa_streq(key, "pw")) {
+            /* Requires password ? (true/false) */
+        } else if (pa_streq(key, "ch")) {
+            /* Number of channels */
+            ch = pa_xstrdup(value);
+        } else if (pa_streq(key, "ss")) {
+            /* Sample size */
+            ss = pa_xstrdup(value);
+        } else if (pa_streq(key, "sr")) {
+            /* Sample rate */
+            sr = pa_xstrdup(value);
         }
 
         avahi_free(key);
@@ -176,9 +224,9 @@ static void resolver_cb(
     }
 
     if (device)
-        dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
+        dname = pa_sprintf_malloc("raop_output.%s.%s", host_name, device);
     else
-        dname = pa_sprintf_malloc("raop.%s", host_name);
+        dname = pa_sprintf_malloc("raop_output.%s", host_name);
 
     if (!(vname = pa_namereg_make_valid_name(dname))) {
         pa_log("Cannot construct valid device name from '%s'.", dname);
@@ -205,6 +253,43 @@ static void resolver_cb(
                                  vname);
     }
 
+    if (tp != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s protocol=%s", args, tp);
+        pa_xfree(tp);
+        pa_xfree(t);
+    }
+    if (et != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s encryption=%s", args, et);
+        pa_xfree(et);
+        pa_xfree(t);
+    }
+    if (cn != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s codec=%s", args, cn);
+        pa_xfree(cn);
+        pa_xfree(t);
+    }
+    if (ch != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s channels=%s", args, ch);
+        pa_xfree(ch);
+        pa_xfree(t);
+    }
+    if (ss != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s format=%s", args, ss);
+        pa_xfree(ss);
+        pa_xfree(t);
+    }
+    if (sr != NULL) {
+        t = args;
+        args = pa_sprintf_malloc("%s rate=%s", args, sr);
+        pa_xfree(sr);
+        pa_xfree(t);
+    }
+
     pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
 
     if ((m = pa_module_load(u->core, "module-raop-sink", args))) {

commit 535a01417c50ae6342d4dd11cb424027377f732e
Author: Hajime Fujita <crisp.fujita at nifty.com>
Date:   Sun Nov 6 12:53:58 2016 -0600

    raop: Add UDP protocol handling
    
    There are two versions in the RAOP protocol; one uses TCP and the
    other uses UDP. Current raop implementation only supports TCP
    version.
    
    This patch adds an initial UDP protocol support for RAOP.
    It is based on Martin Blanchard's work
    (http://repo.or.cz/w/pulseaudio-raopUDP.git/shortlog/refs/heads/raop)
    which is inspired by Christophe Fergeau's work
    (https://github.com/zx2c4/pulseaudio-raop2).
    
    Matrin's modifications were edited by Hajime Fujita, so that it
    would support both TCP and UDP protocol in a single module.
    
    Also this patch includes a fix that was found thanks to Matthias,
    who reported that his ALAC
    codec support fixed the issue.
    https://bugs.freedesktop.org/show_bug.cgi?id=42804#c30

diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 6fc3d94..c1afedd 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -66,12 +66,13 @@ PA_MODULE_USAGE(
         "sink_name=<name for the sink> "
         "sink_properties=<properties for the sink> "
         "server=<address>  "
+        "protocol=<transport protocol> "
+        "encryption=<encryption type> "
+        "codec=<audio codec> "
         "format=<sample format> "
         "rate=<sample rate> "
         "channels=<number of channels>");
 
-#define DEFAULT_SINK_NAME "raop"
-
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -82,6 +83,8 @@ struct userdata {
     pa_rtpoll_item *rtpoll_item;
     pa_thread *thread;
 
+    pa_raop_protocol_t protocol;
+
     pa_memchunk raw_memchunk;
     pa_memchunk encoded_memchunk;
 
@@ -97,7 +100,6 @@ struct userdata {
     int32_t rate;
 
     pa_smoother *smoother;
-    int fd;
 
     int64_t offset;
     int64_t encoding_overhead;
@@ -107,12 +109,26 @@ struct userdata {
     pa_raop_client *raop;
 
     size_t block_size;
+
+    /* Members only for the TCP protocol */
+    int tcp_fd;
+
+    /* Members only for the UDP protocol */
+    int udp_control_fd;
+    int udp_timing_fd;
+
+    /* For UDP thread wakeup clock calculation */
+    pa_usec_t udp_playback_start;
+    uint32_t  udp_sent_packets;
 };
 
 static const char* const valid_modargs[] = {
     "sink_name",
     "sink_properties",
     "server",
+    "protocol",
+    "encryption",
+    "codec",
     "format",
     "rate",
     "channels",
@@ -120,23 +136,26 @@ static const char* const valid_modargs[] = {
 };
 
 enum {
-    SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX,
-    SINK_MESSAGE_RIP_SOCKET
+    SINK_MESSAGE_TCP_PASS_SOCKET = PA_SINK_MESSAGE_MAX,
+    SINK_MESSAGE_TCP_RIP_SOCKET,
+    SINK_MESSAGE_UDP_SETUP,
+    SINK_MESSAGE_UDP_RECORD,
+    SINK_MESSAGE_UDP_DISCONNECTED,
 };
 
 /* Forward declarations: */
 static void sink_set_volume_cb(pa_sink *);
 
-static void on_connection(int fd, void *userdata) {
+static void tcp_on_connection(int fd, void *userdata) {
     int so_sndbuf = 0;
     socklen_t sl = sizeof(int);
     struct userdata *u = userdata;
     pa_assert(u);
 
-    pa_assert(u->fd < 0);
-    u->fd = fd;
+    pa_assert(u->tcp_fd < 0);
+    u->tcp_fd = fd;
 
-    if (getsockopt(u->fd, SOL_SOCKET, SO_SNDBUF, &so_sndbuf, &sl) < 0)
+    if (getsockopt(u->tcp_fd, SOL_SOCKET, SO_SNDBUF, &so_sndbuf, &sl) < 0)
         pa_log_warn("getsockopt(SO_SNDBUF) failed: %s", pa_cstrerror(errno));
     else {
         pa_log_debug("SO_SNDBUF is %zu.", (size_t) so_sndbuf);
@@ -148,19 +167,28 @@ static void on_connection(int fd, void *userdata) {
 
     pa_log_debug("Connection authenticated, handing fd to IO thread...");
 
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL);
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_TCP_PASS_SOCKET, NULL, 0, NULL, NULL);
 }
 
-static void on_close(void*userdata) {
+static void tcp_on_close(void*userdata) {
     struct userdata *u = userdata;
     pa_assert(u);
 
     pa_log_debug("Connection closed, informing IO thread...");
 
-    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RIP_SOCKET, NULL, 0, NULL, NULL);
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_TCP_RIP_SOCKET, NULL, 0, NULL, NULL);
+}
+
+static pa_usec_t sink_get_latency(const struct userdata *u) {
+    pa_usec_t w, r;
+
+    r = pa_smoother_get(u->smoother, pa_rtclock_now());
+    w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec);
+
+    return w > r ? w - r : 0;
 }
 
-static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+static int tcp_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) {
@@ -175,8 +203,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
                     pa_smoother_pause(u->smoother, pa_rtclock_now());
 
                     /* Issue a FLUSH if we are connected. */
-                    if (u->fd >= 0) {
-                        pa_raop_flush(u->raop);
+                    if (u->tcp_fd >= 0) {
+                        pa_raop_client_flush(u->raop);
                     }
                     break;
 
@@ -188,10 +216,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
                         /* The connection can be closed when idle, so check to
                          * see if we need to reestablish it. */
-                        if (u->fd < 0)
-                            pa_raop_connect(u->raop);
+                        if (u->tcp_fd < 0)
+                            pa_raop_client_connect(u->raop);
                         else
-                            pa_raop_flush(u->raop);
+                            pa_raop_client_flush(u->raop);
                     }
 
                     break;
@@ -205,37 +233,32 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             break;
 
         case PA_SINK_MESSAGE_GET_LATENCY: {
-            pa_usec_t w, r;
-
-            r = pa_smoother_get(u->smoother, pa_rtclock_now());
-            w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec);
-
-            *((pa_usec_t*) data) = w > r ? w - r : 0;
+            *((pa_usec_t*) data) = sink_get_latency(u);
             return 0;
         }
 
-        case SINK_MESSAGE_PASS_SOCKET: {
+        case SINK_MESSAGE_TCP_PASS_SOCKET: {
             struct pollfd *pollfd;
 
             pa_assert(!u->rtpoll_item);
 
             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->fd = u->tcp_fd;
             pollfd->events = POLLOUT;
             /*pollfd->events = */pollfd->revents = 0;
 
             if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
                 /* Our stream has been suspended so we just flush it... */
-                pa_raop_flush(u->raop);
+                pa_raop_client_flush(u->raop);
             }
             return 0;
         }
 
-        case SINK_MESSAGE_RIP_SOCKET: {
-            if (u->fd >= 0) {
-                pa_close(u->fd);
-                u->fd = -1;
+        case SINK_MESSAGE_TCP_RIP_SOCKET: {
+            if (u->tcp_fd >= 0) {
+                pa_close(u->tcp_fd);
+                u->tcp_fd = -1;
             } else
                 /* FIXME */
                 pa_log("We should not get to this state. Cannot rip socket if not connected.");
@@ -260,10 +283,140 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
+static void udp_start_wakeup_clock(struct userdata *u) {
+    pa_usec_t now = pa_rtclock_now();
+
+    u->udp_playback_start = now;
+    u->udp_sent_packets = 0;
+    pa_rtpoll_set_timer_absolute(u->rtpoll, now);
+}
+
+static pa_usec_t udp_next_wakeup_clock(struct userdata *u) {
+    pa_usec_t intvl = pa_bytes_to_usec(u->block_size * u->udp_sent_packets,
+                                       &u->sink->sample_spec);
+    /* FIXME: how long until (u->block_size * u->udp_sent_packets) wraps?? */
+
+    return u->udp_playback_start + intvl;
+}
+
+static int udp_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_SET_STATE:
+            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+                case PA_SINK_SUSPENDED:
+                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+                    pa_log_debug("RAOP: SUSPENDED");
+                    pa_smoother_pause(u->smoother, pa_rtclock_now());
+
+                    if (pa_raop_client_udp_can_stream(u->raop)) {
+                        /* Issue a TEARDOWN if we are still connected. */
+                        pa_raop_client_teardown(u->raop);
+                    }
+
+                    break;
+
+                case PA_SINK_IDLE:
+                    pa_log_debug("RAOP: IDLE");
+                    /* Issue a flush if we're comming from running state. */
+                    if (u->sink->thread_info.state == PA_SINK_RUNNING) {
+                        pa_rtpoll_set_timer_disabled(u->rtpoll);
+                        pa_raop_client_flush(u->raop);
+                    }
+
+                    break;
+
+                case PA_SINK_RUNNING:
+                    pa_log_debug("RAOP: RUNNING");
+
+                    pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
+
+                    if (!pa_raop_client_udp_can_stream(u->raop)) {
+                        /* Connecting will trigger a RECORD */
+                        pa_raop_client_connect(u->raop);
+                    }
+                    udp_start_wakeup_clock(u);
+
+                    break;
+
+                case PA_SINK_UNLINKED:
+                case PA_SINK_INIT:
+                case PA_SINK_INVALID_STATE:
+                    ;
+            }
+
+            break;
+
+        case PA_SINK_MESSAGE_GET_LATENCY: {
+            pa_usec_t r = 0;
+
+            if (pa_raop_client_udp_can_stream(u->raop))
+                r = sink_get_latency(u);
+
+            *((pa_usec_t*) data) = r;
+
+            return 0;
+        }
+
+        case SINK_MESSAGE_UDP_SETUP: {
+            struct pollfd *pollfd;
+
+            u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 2);
+            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+            pollfd->fd = u->udp_control_fd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+            pollfd++;
+            pollfd->fd = u->udp_timing_fd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+
+            return 0;
+        }
+
+        case SINK_MESSAGE_UDP_RECORD: {
+            udp_start_wakeup_clock(u);
+
+            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+                /* Our stream has been suspended so we just flush it... */
+                pa_rtpoll_set_timer_disabled(u->rtpoll);
+                pa_raop_client_flush(u->raop);
+            }
+
+            return 0;
+        }
+
+        case SINK_MESSAGE_UDP_DISCONNECTED: {
+            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+                pa_rtpoll_set_timer_disabled(u->rtpoll);
+                if (u->rtpoll_item)
+                    pa_rtpoll_item_free(u->rtpoll_item);
+                u->rtpoll_item = NULL;
+            } else {
+                /* Question: is this valid here: or should we do some sort of:
+                 * return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL); ?? */
+                pa_module_unload_request(u->module, true);
+            }
+
+            pa_close(u->udp_control_fd);
+            pa_close(u->udp_timing_fd);
+
+            u->udp_control_fd = -1;
+            u->udp_timing_fd = -1;
+
+            return 0;
+        }
+    }
+
+    return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
 static void sink_set_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     pa_cvolume hw;
-    pa_volume_t v;
+    pa_volume_t v, v_orig;
     char t[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
 
     pa_assert(u);
@@ -277,11 +430,16 @@ static void sink_set_volume_cb(pa_sink *s) {
      * any variation in channel volumes in software. */
     v = pa_cvolume_max(&s->real_volume);
 
+    v_orig = v;
+    v = pa_raop_client_adjust_volume(u->raop, v_orig);
+
+    pa_log_debug("Volume adjusted: orig=%u adjusted=%u", v_orig, v);
+
     /* Create a pa_cvolume version of our single value. */
     pa_cvolume_set(&hw, s->sample_spec.channels, v);
 
-    /* Perform any software manipulation of the volume needed. */
-    pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw);
+    /* Set the real volume based on given original volume. */
+    pa_cvolume_set(&s->real_volume, s->sample_spec.channels, v_orig);
 
     pa_log_debug("Requested volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &s->real_volume, &s->channel_map, false));
     pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &hw, &s->channel_map, false));
@@ -305,8 +463,50 @@ static void sink_set_mute_cb(pa_sink *s) {
     }
 }
 
-static void thread_func(void *userdata) {
+static void udp_setup_cb(int control_fd, int timing_fd, void *userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(control_fd);
+    pa_assert(timing_fd);
+    pa_assert(u);
+
+    u->udp_control_fd = control_fd;
+    u->udp_timing_fd = timing_fd;
+
+    pa_log_debug("Connection authenticated, syncing with server...");
+
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_SETUP, NULL, 0, NULL, NULL);
+}
+
+static void udp_record_cb(void *userdata) {
     struct userdata *u = userdata;
+
+    pa_assert(u);
+
+    /* Set the initial volume. */
+    sink_set_volume_cb(u->sink);
+
+    pa_log_debug("Synchronization done, pushing job to IO thread...");
+
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_RECORD, NULL, 0, NULL, NULL);
+}
+
+static void udp_disconnected_cb(void *userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(u);
+
+    /* This callback function is called from both STATE_TEARDOWN and
+       STATE_DISCONNECTED in raop_client.c */
+
+    pa_assert(u);
+
+    pa_log_debug("Connection closed, informing IO thread...");
+
+    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UDP_DISCONNECTED, NULL, 0, NULL, NULL);
+}
+
+static void tcp_thread_func(struct userdata *u) {
     int write_type = 0;
     pa_memchunk silence;
     uint32_t silence_overhead = 0;
@@ -314,7 +514,7 @@ static void thread_func(void *userdata) {
 
     pa_assert(u);
 
-    pa_log_debug("Thread starting up");
+    pa_log_debug("TCP thread starting up");
 
     pa_thread_mq_install(&u->thread_mq);
 
@@ -394,7 +594,7 @@ static void thread_func(void *userdata) {
                     pa_assert(u->encoded_memchunk.length > 0);
 
                     p = pa_memblock_acquire(u->encoded_memchunk.memblock);
-                    l = pa_write(u->fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type);
+                    l = pa_write(u->tcp_fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type);
                     pa_memblock_release(u->encoded_memchunk.memblock);
 
                     pa_assert(l != 0);
@@ -443,7 +643,7 @@ static void thread_func(void *userdata) {
 #ifdef SIOCOUTQ
                 {
                     int l;
-                    if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0)
+                    if (ioctl(u->tcp_fd, SIOCOUTQ, &l) >= 0 && l > 0)
                         n -= (l / u->encoding_ratio);
                 }
 #endif
@@ -497,15 +697,139 @@ fail:
 finish:
     if (silence.memblock)
         pa_memblock_unref(silence.memblock);
-    pa_log_debug("Thread shutting down");
+    pa_log_debug("TCP thread shutting down");
+}
+
+static void udp_thread_func(struct userdata *u) {
+    pa_assert(u);
+
+    pa_log_debug("UDP thread starting up");
+
+    pa_thread_mq_install(&u->thread_mq);
+    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+
+    for (;;) {
+        pa_usec_t estimated;
+        int32_t overhead = 0;
+        ssize_t written = 0;
+        size_t length = 0;
+        int rv = 0;
+
+        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+            if (u->sink->thread_info.rewind_requested)
+                pa_sink_process_rewind(u->sink, 0);
+        }
+
+        /* Polling (audio data + control socket + timing socket). */
+        if ((rv = pa_rtpoll_run(u->rtpoll)) < 0)
+            goto fail;
+        else if (rv == 0)
+            goto finish;
+
+        if (!pa_rtpoll_timer_elapsed(u->rtpoll)) {
+            struct pollfd *pollfd;
+            uint8_t packet[32];
+            ssize_t read;
+
+            if (u->rtpoll_item) {
+                pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+                /* Event on the control socket ?? */
+                if (pollfd->revents & POLLIN) {
+                    pollfd->revents = 0;
+                    pa_log_debug("Received control packet.");
+                    read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
+                    pa_raop_client_udp_handle_control_packet(u->raop, packet, read);
+                }
+
+                pollfd++;
+
+                /* Event on the timing port ?? */
+                if (pollfd->revents & POLLIN) {
+                    pollfd->revents = 0;
+                    pa_log_debug("Received timing packet.");
+                    read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
+                    pa_raop_client_udp_handle_timing_packet(u->raop, packet, read);
+                }
+            }
+
+            continue;
+        }
+
+        if (!pa_raop_client_udp_can_stream(u->raop))
+            continue;
+        if (u->sink->thread_info.state != PA_SINK_RUNNING)
+            continue;
+
+        if (u->encoded_memchunk.length <= 0) {
+            if (u->encoded_memchunk.memblock != NULL)
+                pa_memblock_unref(u->encoded_memchunk.memblock);
+
+            if (u->raw_memchunk.length <= 0) {
+                if (u->raw_memchunk.memblock)
+                    pa_memblock_unref(u->raw_memchunk.memblock);
+                pa_memchunk_reset(&u->raw_memchunk);
+
+                /* Grab unencoded audio data from PulseAudio. */
+                pa_sink_render_full(u->sink, u->block_size, &u->raw_memchunk);
+            }
+
+            pa_assert(u->raw_memchunk.length > 0);
+
+            length = u->raw_memchunk.length;
+            pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk);
+            u->encoding_ratio = (double) u->encoded_memchunk.length / (double) (length - u->raw_memchunk.length);
+            overhead = u->encoded_memchunk.length - (length - u->raw_memchunk.length);
+        }
+
+        pa_assert(u->encoded_memchunk.length > 0);
+
+        written = pa_raop_client_udp_send_audio_packet(u->raop,&u->encoded_memchunk);
+        if (written < 0) {
+            pa_log("Failed to send UDP packet: %s", pa_cstrerror(errno));
+            goto fail;
+        }
+
+        u->udp_sent_packets++;
+        /* Sleep until next packet transmission */
+        pa_rtpoll_set_timer_absolute(u->rtpoll, udp_next_wakeup_clock(u));
+
+        u->offset += written;
+        u->encoding_overhead += overhead;
+
+        estimated = pa_bytes_to_usec(u->offset - u->encoding_overhead, &u->sink->sample_spec);
+        pa_smoother_put(u->smoother, pa_rtclock_now(), estimated);
+    }
+
+fail:
+    /* If this was no regular exit, continue processing messages until 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("UDP thread shutting down");
+}
+
+static void thread_func(void *userdata) {
+    struct userdata *u = userdata;
+
+    if (u->protocol == RAOP_TCP)
+        tcp_thread_func(u);
+    else if (u->protocol == RAOP_UDP)
+        udp_thread_func(u);
+    else
+        pa_assert(false);
+
+    return;
 }
 
 int pa__init(pa_module *m) {
     struct userdata *u = NULL;
     pa_sample_spec ss;
     pa_modargs *ma = NULL;
-    const char *server;
+    const char *server, *protocol, *encryption;
     pa_sink_new_data data;
+    char *t = NULL;
 
     pa_assert(m);
 
@@ -532,7 +856,7 @@ int pa__init(pa_module *m) {
     u->core = m->core;
     u->module = m;
     m->userdata = u;
-    u->fd = -1;
+    u->tcp_fd = -1;
     u->smoother = pa_smoother_new(
             PA_USEC_PER_SEC,
             PA_USEC_PER_SEC*2,
@@ -569,15 +893,32 @@ int pa__init(pa_module *m) {
         goto fail;
     }
 
+    /* This may be overwriten if sink_name is specified in module arguments. */
+    t = pa_sprintf_malloc("raop_client.%s", server);
+
+    protocol = pa_modargs_get_value(ma, "protocol", NULL);
+    if (protocol == NULL || pa_streq(protocol, "TCP")) {
+        /* Assume TCP by default */
+        u->protocol = RAOP_TCP;
+    }
+    else if (pa_streq(protocol, "UDP")) {
+        u->protocol = RAOP_UDP;
+    } else {
+        pa_log("Unsupported protocol argument given: %s", protocol);
+        goto fail;
+    }
+
     pa_sink_new_data_init(&data);
     data.driver = __FILE__;
     data.module = m;
-    pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
+    pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", t));
     pa_sink_new_data_set_sample_spec(&data, &ss);
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server);
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music");
     pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server);
 
+    /* RAOP discover module will eventually overwrite sink_name and others
+       (PA_UPDATE_REPLACE). */
     if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
         pa_log("Invalid properties");
         pa_sink_new_data_done(&data);
@@ -585,6 +926,7 @@ int pa__init(pa_module *m) {
     }
 
     u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK);
+    pa_xfree(t); t = NULL;
     pa_sink_new_data_done(&data);
 
     if (!u->sink) {
@@ -592,7 +934,10 @@ int pa__init(pa_module *m) {
         goto fail;
     }
 
-    u->sink->parent.process_msg = sink_process_msg;
+    if (u->protocol == RAOP_TCP)
+        u->sink->parent.process_msg = tcp_sink_process_msg;
+    else
+        u->sink->parent.process_msg = udp_sink_process_msg;
     u->sink->userdata = u;
     pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
@@ -601,13 +946,27 @@ int pa__init(pa_module *m) {
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
 
-    if (!(u->raop = pa_raop_client_new(u->core, server))) {
+    if (!(u->raop = pa_raop_client_new(u->core, server, u->protocol))) {
         pa_log("Failed to connect to server.");
         goto fail;
     }
 
-    pa_raop_client_set_callback(u->raop, on_connection, u);
-    pa_raop_client_set_closed_callback(u->raop, on_close, u);
+    encryption = pa_modargs_get_value(ma, "encryption", NULL);
+    pa_raop_client_set_encryption(u->raop, !pa_safe_streq(encryption, "none"));
+
+    pa_raop_client_tcp_set_callback(u->raop, tcp_on_connection, u);
+    pa_raop_client_tcp_set_closed_callback(u->raop, tcp_on_close, u);
+
+    if (u->protocol == RAOP_UDP) {
+        /* The number of frames per blocks is not negotiable... */
+        pa_raop_client_udp_get_blocks_size(u->raop, &u->block_size);
+        u->block_size *= pa_frame_size(&ss);
+        pa_sink_set_max_request(u->sink, u->block_size);
+
+        pa_raop_client_udp_set_setup_callback(u->raop, udp_setup_cb, u);
+        pa_raop_client_udp_set_record_callback(u->raop, udp_record_cb, u);
+        pa_raop_client_udp_set_disconnected_callback(u->raop, udp_disconnected_cb, u);
+    }
 
     if (!(u->thread = pa_thread_new("raop-sink", thread_func, u))) {
         pa_log("Failed to create thread.");
@@ -621,6 +980,8 @@ int pa__init(pa_module *m) {
     return 0;
 
 fail:
+    pa_xfree(t);
+
     if (ma)
         pa_modargs_free(ma);
 
@@ -679,8 +1040,8 @@ void pa__done(pa_module *m) {
     if (u->smoother)
         pa_smoother_free(u->smoother);
 
-    if (u->fd >= 0)
-        pa_close(u->fd);
+    if (u->tcp_fd >= 0)
+        pa_close(u->tcp_fd);
 
     pa_xfree(u);
 }
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index 31e0fdc..4e8f474 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -26,6 +26,7 @@
 #include <errno.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
+#include <math.h>
 
 #ifdef HAVE_SYS_FILIO_H
 #include <sys/filio.h>
@@ -39,10 +40,14 @@
 #include <openssl/engine.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/sample.h>
 
 #include <pulsecore/core-error.h>
+#include <pulsecore/core-rtclock.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/iochannel.h>
+#include <pulsecore/arpa-inet.h>
 #include <pulsecore/socket-util.h>
 #include <pulsecore/log.h>
 #include <pulsecore/parseaddr.h>
@@ -54,6 +59,7 @@
 #include "rtsp_client.h"
 #include "base64.h"
 
+#define UDP_FRAMES_PER_PACKET 352
 #define AES_CHUNKSIZE 16
 
 #define JACK_STATUS_DISCONNECTED 0
@@ -66,7 +72,19 @@
 #define VOLUME_MIN -144
 #define VOLUME_MAX 0
 
-#define RAOP_PORT 5000
+#define DEFAULT_RAOP_PORT 5000
+#define UDP_DEFAULT_AUDIO_PORT 6000
+#define UDP_DEFAULT_CONTROL_PORT 6001
+#define UDP_DEFAULT_TIMING_PORT 6002
+
+typedef enum {
+    UDP_PAYLOAD_TIMING_REQUEST = 0x52,
+    UDP_PAYLOAD_TIMING_RESPONSE = 0x53,
+    UDP_PAYLOAD_SYNCHRONIZATION = 0x54,
+    UDP_PAYLOAD_RETRANSMIT_REQUEST = 0x55,
+    UDP_PAYLOAD_RETRANSMIT_REPLY = 0x56,
+    UDP_PAYLOAD_AUDIO_DATA = 0x60
+} pa_raop_udp_payload_type;
 
 /* Openssl 1.1.0 broke compatibility. Before 1.1.0 we had to set RSA->n and
  * RSA->e manually, but after 1.1.0 the RSA struct is opaque and we have to use
@@ -89,26 +107,93 @@ struct pa_raop_client {
     uint16_t port;
     char *sid;
     pa_rtsp_client *rtsp;
+    pa_raop_protocol_t protocol;
 
     uint8_t jack_type;
     uint8_t jack_status;
 
     /* Encryption Related bits */
+    int encryption; /* Enable encryption? */
     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_socket_client *sc;
-    int fd;
-
     uint16_t seq;
     uint32_t rtptime;
 
-    pa_raop_client_cb_t callback;
-    void *userdata;
-    pa_raop_client_closed_cb_t closed_callback;
-    void *closed_userdata;
+    /* Members only for the TCP protocol */
+    pa_socket_client *tcp_sc;
+    int tcp_fd;
+
+    pa_raop_client_cb_t tcp_callback;
+    void *tcp_userdata;
+    pa_raop_client_closed_cb_t tcp_closed_callback;
+    void *tcp_closed_userdata;
+
+    /* Members only for the UDP protocol */
+    uint16_t udp_my_control_port;
+    uint16_t udp_my_timing_port;
+    uint16_t udp_server_control_port;
+    uint16_t udp_server_timing_port;
+
+    int udp_stream_fd;
+    int udp_control_fd;
+    int udp_timing_fd;
+
+    uint32_t udp_ssrc;
+
+    bool udp_first_packet;
+    uint32_t udp_sync_interval;
+    uint32_t udp_sync_count;
+
+    pa_raop_client_setup_cb_t udp_setup_callback;
+    void *udp_setup_userdata;
+
+    pa_raop_client_record_cb_t udp_record_callback;
+    void *udp_record_userdata;
+
+    pa_raop_client_disconnected_cb_t udp_disconnected_callback;
+    void *udp_disconnected_userdata;
+};
+
+/* Timming packet header (8x8):
+ *  [0]   RTP v2: 0x80,
+ *  [1]   Payload type: 0x53 | marker bit: 0x80,
+ *  [2,3] Sequence number: 0x0007,
+ *  [4,7] Timestamp: 0x00000000 (unused). */
+static const uint8_t udp_timming_header[8] = {
+    0x80, 0xd3, 0x00, 0x07,
+    0x00, 0x00, 0x00, 0x00
+};
+
+/* Sync packet header (8x8):
+ *  [0]   RTP v2: 0x80,
+ *  [1]   Payload type: 0x54 | marker bit: 0x80,
+ *  [2,3] Sequence number: 0x0007,
+ *  [4,7] Timestamp: 0x00000000 (to be set). */
+static const uint8_t udp_sync_header[8] = {
+    0x80, 0xd4, 0x00, 0x07,
+    0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t tcp_audio_header[16] = {
+    0x24, 0x00, 0x00, 0x00,
+    0xF0, 0xFF, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+};
+
+/* Audio packet header (12x8):
+ *  [0]    RTP v2: 0x80,
+ *  [1]    Payload type: 0x60,
+ *  [2,3]  Sequence number: 0x0000 (to be set),
+ *  [4,7]  Timestamp: 0x00000000 (to be set),
+ *  [8,12] SSRC: 0x00000000 (to be set).*/
+static const uint8_t udp_audio_header[12] = {
+    0x80, 0x60, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00
 };
 
 /**
@@ -218,84 +303,334 @@ static inline void rtrimchar(char *str, char rc) {
     }
 }
 
-static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
+static void tcp_on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
     pa_raop_client *c = userdata;
 
     pa_assert(sc);
     pa_assert(c);
-    pa_assert(c->sc == sc);
-    pa_assert(c->fd < 0);
-    pa_assert(c->callback);
+    pa_assert(c->tcp_sc == sc);
+    pa_assert(c->tcp_fd < 0);
+    pa_assert(c->tcp_callback);
 
-    pa_socket_client_unref(c->sc);
-    c->sc = NULL;
+    pa_socket_client_unref(c->tcp_sc);
+    c->tcp_sc = NULL;
 
     if (!io) {
         pa_log("Connection failed: %s", pa_cstrerror(errno));
         return;
     }
 
-    c->fd = pa_iochannel_get_send_fd(io);
+    c->tcp_fd = pa_iochannel_get_send_fd(io);
 
     pa_iochannel_set_noclose(io, true);
     pa_iochannel_free(io);
 
-    pa_make_tcp_socket_low_delay(c->fd);
+    pa_make_tcp_socket_low_delay(c->tcp_fd);
 
     pa_log_debug("Connection established");
-    c->callback(c->fd, c->userdata);
+    c->tcp_callback(c->tcp_fd, c->tcp_userdata);
 }
 
-static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) {
-    pa_raop_client *c = userdata;
+static inline uint64_t timeval_to_ntp(struct timeval *tv) {
+    uint64_t ntp = 0;
+
+    /* Converting micro seconds to a fraction. */
+    ntp = (uint64_t) tv->tv_usec * UINT32_MAX / PA_USEC_PER_SEC;
+    /* Moving reference from  1 Jan 1970 to 1 Jan 1900 (seconds). */
+    ntp |= (uint64_t) (tv->tv_sec + 0x83aa7e80) << 32;
+
+    return ntp;
+}
+
+static int connect_udp_socket(pa_raop_client *c, int fd, uint16_t port) {
+    struct sockaddr_in sa4;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 sa6;
+#endif
+    struct sockaddr *sa;
+    socklen_t salen;
+    sa_family_t af;
+
+    pa_zero(sa4);
+#ifdef HAVE_IPV6
+    pa_zero(sa6);
+#endif
+    if (inet_pton(AF_INET, c->host, &sa4.sin_addr) > 0) {
+        sa4.sin_family = af = AF_INET;
+        sa4.sin_port = htons(port);
+        sa = (struct sockaddr *) &sa4;
+        salen = sizeof(sa4);
+#ifdef HAVE_IPV6
+    } else if (inet_pton(AF_INET6, c->host, &sa6.sin6_addr) > 0) {
+        sa6.sin6_family = af = AF_INET6;
+        sa6.sin6_port = htons(port);
+        sa = (struct sockaddr *) &sa6;
+        salen = sizeof(sa6);
+#endif
+    } else {
+        pa_log("Invalid destination '%s'", c->host);
+        goto fail;
+    }
+
+    if (fd < 0 && (fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
+        pa_log("socket() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    /* If the socket queue is full, let's drop packets */
+    pa_make_udp_socket_low_delay(fd);
+    pa_make_fd_nonblock(fd);
+
+    if (connect(fd, sa, salen) < 0) {
+        pa_log("connect() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    pa_log_debug("Connected to %s on port %d (SOCK_DGRAM)", c->host, port);
+    return fd;
+
+fail:
+    if (fd >= 0)
+        pa_close(fd);
+
+    return -1;
+}
+
+static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
+    int fd = -1;
+    uint16_t port;
+    struct sockaddr_in sa4;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 sa6;
+#endif
+    struct sockaddr *sa;
+    uint16_t *sa_port;
+    socklen_t salen;
+    sa_family_t af;
+    int one = 1;
+
+    pa_assert(actual_port);
+
+    port = *actual_port;
+
+    pa_zero(sa4);
+#ifdef HAVE_IPV6
+    pa_zero(sa6);
+#endif
+    if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) {
+        sa4.sin_family = af = AF_INET;
+        sa4.sin_port = htons(port);
+        sa = (struct sockaddr *) &sa4;
+        salen = sizeof(sa4);
+        sa_port = &sa4.sin_port;
+#ifdef HAVE_IPV6
+    } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
+        sa6.sin6_family = af = AF_INET6;
+        sa6.sin6_port = htons(port);
+        sa = (struct sockaddr *) &sa6;
+        salen = sizeof(sa6);
+        sa_port = &sa6.sin6_port;
+#endif
+    } else {
+        pa_log("Could not determine which address family to use");
+        goto fail;
+    }
+
+    pa_zero(sa4);
+#ifdef HAVE_IPV6
+    pa_zero(sa6);
+#endif
+
+    if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
+        pa_log("socket() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+#ifdef SO_TIMESTAMP
+    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
+        pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+#else
+    pa_log("SO_TIMESTAMP unsupported on this platform");
+    goto fail;
+#endif
+
+    one = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+        pa_log("setsockopt(SO_REUSEADDR) failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    do {
+        *sa_port = htons(port);
+
+        if (bind(fd, sa, salen) < 0 && errno != EADDRINUSE) {
+            pa_log("bind_socket() failed: %s", pa_cstrerror(errno));
+            goto fail;
+        }
+        break;
+    } while (++port > 0);
+
+    pa_log_debug("Socket bound to port %d (SOCK_DGRAM)", port);
+    *actual_port = port;
+
+    return fd;
+
+fail:
+    if (fd >= 0)
+        pa_close(fd);
+
+    return -1;
+}
+
+static int udp_send_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received) {
+    uint32_t packet[8];
+    struct timeval tv;
+    ssize_t written = 0;
+    uint64_t trs = 0;
+    int rv = 1;
+
+    memcpy(packet, udp_timming_header, sizeof(udp_timming_header));
+    /* Copying originate timestamp from the incoming request packet. */
+    packet[2] = data[4];
+    packet[3] = data[5];
+    /* Set the receive timestamp to reception time. */
+    packet[4] = htonl(received >> 32);
+    packet[5] = htonl(received & 0xffffffff);
+    /* Set the transmit timestamp to current time. */
+    trs = timeval_to_ntp(pa_rtclock_get(&tv));
+    packet[6] = htonl(trs >> 32);
+    packet[7] = htonl(trs & 0xffffffff);
+
+    written = pa_loop_write(c->udp_timing_fd, packet, sizeof(packet), NULL);
+    if (written == sizeof(packet))
+        rv = 0;
+
+    return rv;
+}
+
+static int udp_send_sync_packet(pa_raop_client *c, uint32_t stamp) {
+    const uint32_t delay = 88200;
+    uint32_t packet[5];
+    struct timeval tv;
+    ssize_t written = 0;
+    uint64_t trs = 0;
+    int rv = 1;
+
+    memcpy(packet, udp_sync_header, sizeof(udp_sync_header));
+    if (c->udp_first_packet)
+        packet[0] |= 0x10;
+    stamp -= delay;
+    packet[1] = htonl(stamp);
+    /* Set the transmited timestamp to current time. */
+    trs = timeval_to_ntp(pa_rtclock_get(&tv));
+    packet[2] = htonl(trs >> 32);
+    packet[3] = htonl(trs & 0xffffffff);
+    stamp += delay;
+    packet[4] = htonl(stamp);
+
+    written = pa_loop_write(c->udp_control_fd, packet, sizeof(packet), NULL);
+    if (written == sizeof(packet))
+        rv = 0;
+
+    return rv;
+}
+
+static void udp_build_audio_header(pa_raop_client *c, uint32_t *buffer, size_t size) {
+    pa_assert(size >= sizeof(udp_audio_header));
+
+    memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
+    if (c->udp_first_packet)
+        buffer[0] |= htonl((uint32_t) 0x80 << 16);
+    buffer[0] |= htonl((uint32_t) c->seq);
+    buffer[1] = htonl(c->rtptime);
+    buffer[2] = htonl(c->udp_ssrc);
+}
+
+static ssize_t udp_send_audio_packet(pa_raop_client *c, uint8_t *buffer, size_t size) {
+    ssize_t length;
+
+    length = pa_write(c->udp_stream_fd, buffer, size, NULL);
+    c->seq++;
+
+    return length;
+}
+
+static void do_rtsp_announce(pa_raop_client *c) {
+    int i;
+    uint8_t rsakey[512];
+    char *key, *iv, *sac = NULL, *sdp;
+    uint16_t rand_data;
+    const char *ip;
+    char *url;
+
+    ip = pa_rtsp_localip(c->rtsp);
+    /* First of all set the url properly. */
+    url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
+    pa_rtsp_set_url(c->rtsp, url);
+    pa_xfree(url);
+
+    /* Now encrypt our aes_public key to send to the device. */
+    i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey);
+    pa_base64_encode(rsakey, i, &key);
+    rtrimchar(key, '=');
+    pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv);
+    rtrimchar(iv, '=');
+
+    /* UDP protocol does not need "Apple-Challenge" at announce. */
+    if (c->protocol == RAOP_TCP) {
+        pa_random(&rand_data, sizeof(rand_data));
+        pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac);
+        rtrimchar(sac, '=');
+        pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
+    }
+
+    if (c->encryption)
+        sdp = pa_sprintf_malloc(
+            "v=0\r\n"
+            "o=iTunes %s 0 IN IP4 %s\r\n"
+            "s=iTunes\r\n"
+            "c=IN IP4 %s\r\n"
+            "t=0 0\r\n"
+            "m=audio 0 RTP/AVP 96\r\n"
+            "a=rtpmap:96 AppleLossless\r\n"
+            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+            "a=rsaaeskey:%s\r\n"
+            "a=aesiv:%s\r\n",
+            c->sid, ip, c->host,
+            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET,
+            key, iv);
+    else
+        sdp = pa_sprintf_malloc(
+            "v=0\r\n"
+            "o=iTunes %s 0 IN IP4 %s\r\n"
+            "s=iTunes\r\n"
+            "c=IN IP4 %s\r\n"
+            "t=0 0\r\n"
+            "m=audio 0 RTP/AVP 96\r\n"
+            "a=rtpmap:96 AppleLossless\r\n"
+            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
+            c->sid, ip, c->host,
+            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET);
+
+    pa_rtsp_announce(c->rtsp, sdp);
+    pa_xfree(key);
+    pa_xfree(iv);
+    pa_xfree(sac);
+    pa_xfree(sdp);
+}
+
+static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) {
+    pa_raop_client* c = userdata;
     pa_assert(c);
     pa_assert(rtsp);
     pa_assert(rtsp == c->rtsp);
 
     switch (state) {
         case STATE_CONNECT: {
-            int i;
-            uint8_t rsakey[512];
-            char *key, *iv, *sac, *sdp;
-            uint16_t rand_data;
-            const char *ip;
-            char *url;
-
             pa_log_debug("RAOP: CONNECTED");
-            ip = pa_rtsp_localip(c->rtsp);
-            /* First of all set the url properly. */
-            url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
-            pa_rtsp_set_url(c->rtsp, url);
-            pa_xfree(url);
-
-            /* Now encrypt our aes_public key to send to the device. */
-            i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey);
-            pa_base64_encode(rsakey, i, &key);
-            rtrimchar(key, '=');
-            pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv);
-            rtrimchar(iv, '=');
-
-            pa_random(&rand_data, sizeof(rand_data));
-            pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac);
-            rtrimchar(sac, '=');
-            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
-            sdp = pa_sprintf_malloc(
-                "v=0\r\n"
-                "o=iTunes %s 0 IN IP4 %s\r\n"
-                "s=iTunes\r\n"
-                "c=IN IP4 %s\r\n"
-                "t=0 0\r\n"
-                "m=audio 0 RTP/AVP 96\r\n"
-                "a=rtpmap:96 AppleLossless\r\n"
-                "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n"
-                "a=rsaaeskey:%s\r\n"
-                "a=aesiv:%s\r\n",
-                c->sid, ip, c->host, key, iv);
-            pa_rtsp_announce(c->rtsp, sdp);
-            pa_xfree(key);
-            pa_xfree(iv);
-            pa_xfree(sac);
-            pa_xfree(sdp);
+            do_rtsp_announce(c);
             break;
         }
 
@@ -343,11 +678,11 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *he
             uint32_t port = pa_rtsp_serverport(c->rtsp);
             pa_log_debug("RAOP: RECORDED");
 
-            if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, port))) {
+            if (!(c->tcp_sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, port))) {
                 pa_log("failed to connect to server '%s:%d'", c->host, port);
                 return;
             }
-            pa_socket_client_set_callback(c->sc, on_connection, c);
+            pa_socket_client_set_callback(c->tcp_sc, tcp_on_connection, c);
             break;
         }
 
@@ -364,30 +699,328 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *he
             break;
 
         case STATE_DISCONNECTED:
-            pa_assert(c->closed_callback);
+            pa_assert(c->tcp_closed_callback);
             pa_assert(c->rtsp);
 
             pa_log_debug("RTSP control channel closed");
             pa_rtsp_client_free(c->rtsp);
             c->rtsp = NULL;
-            if (c->fd > 0) {
-                /* We do not close the fd, we leave it to the closed callback to do that. */
-                c->fd = -1;
+            if (c->tcp_fd > 0) {
+                /* We do not close the fd, we leave it to the closed callback to do that */
+                c->tcp_fd = -1;
             }
-            if (c->sc) {
-                pa_socket_client_unref(c->sc);
-                c->sc = NULL;
+            if (c->tcp_sc) {
+                pa_socket_client_unref(c->tcp_sc);
+                c->tcp_sc = NULL;
             }
             pa_xfree(c->sid);
             c->sid = NULL;
-            c->closed_callback(c->closed_userdata);
+            c->tcp_closed_callback(c->tcp_closed_userdata);
             break;
     }
 }
 
-pa_raop_client* pa_raop_client_new(pa_core *core, const char *host) {
-    pa_parsed_address a;
+static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) {
+    pa_raop_client *c = userdata;
+
+    pa_assert(c);
+    pa_assert(rtsp);
+    pa_assert(rtsp == c->rtsp);
+
+    switch (state) {
+        case STATE_CONNECT: {
+            uint16_t rand;
+            char *sac;
+
+            /* Set the Apple-Challenge key */
+            pa_random(&rand, sizeof(rand));
+            pa_base64_encode(&rand, AES_CHUNKSIZE, &sac);
+            rtrimchar(sac, '=');
+            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
+
+            pa_rtsp_options(c->rtsp);
+
+            pa_xfree(sac);
+            break;
+        }
+
+        case STATE_OPTIONS: {
+            pa_log_debug("RAOP: OPTIONS");
+
+            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
+            do_rtsp_announce(c);
+            break;
+        }
+
+        case STATE_ANNOUNCE: {
+            char *trs;
+
+            pa_assert(c->udp_control_fd < 0);
+            pa_assert(c->udp_timing_fd < 0);
+
+            c->udp_control_fd = open_bind_udp_socket(c, &c->udp_my_control_port);
+            if (c->udp_control_fd < 0)
+                goto error_announce;
+            c->udp_timing_fd  = open_bind_udp_socket(c, &c->udp_my_timing_port);
+            if (c->udp_timing_fd < 0)
+                goto error_announce;
+
+            trs = pa_sprintf_malloc("RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d",
+                c->udp_my_control_port,
+                c->udp_my_timing_port);
+
+            pa_rtsp_setup(c->rtsp, trs);
+
+            pa_xfree(trs);
+            break;
+
+        error_announce:
+            if (c->udp_control_fd > 0) {
+                pa_close(c->udp_control_fd);
+                c->udp_control_fd = -1;
+            }
+            if (c->udp_timing_fd > 0) {
+                pa_close(c->udp_timing_fd);
+                c->udp_timing_fd = -1;
+            }
+
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
+            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
+            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
+            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
+
+            pa_log_error("aborting RTSP announce, failed creating required sockets");
+        }
+
+        case STATE_SETUP: {
+            uint32_t stream_port = UDP_DEFAULT_AUDIO_PORT;
+            char *ajs, *trs, *token, *pc;
+            char delimiters[] = ";";
+            const char *token_state = NULL;
+            uint32_t port = 0;
+            int ret;
+
+            pa_log_debug("RAOP: SETUP");
+
+            ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
+            trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
+
+            if (ajs) {
+                c->jack_type = JACK_TYPE_ANALOG;
+                c->jack_status = JACK_STATUS_DISCONNECTED;
+
+                while ((token = pa_split(ajs, delimiters, &token_state))) {
+                    if ((pc = strstr(token, "="))) {
+                      *pc = 0;
+                      if (pa_streq(token, "type") && pa_streq(pc + 1, "digital"))
+                          c->jack_type = JACK_TYPE_DIGITAL;
+                    } else {
+                        if (pa_streq(token, "connected"))
+                            c->jack_status = JACK_STATUS_CONNECTED;
+                    }
+                    pa_xfree(token);
+                }
+
+            } else {
+                pa_log_warn("Audio-Jack-Status missing");
+            }
+
+            token_state = NULL;
+
+            if (trs) {
+                /* Now parse out the server port component of the response. */
+                while ((token = pa_split(trs, delimiters, &token_state))) {
+                    if ((pc = strstr(token, "="))) {
+                        *pc = 0;
+                        if (pa_streq(token, "control_port")) {
+                            port = 0;
+                            pa_atou(pc + 1, &port);
+                            c->udp_server_control_port = port;
+                        }
+                        if (pa_streq(token, "timing_port")) {
+                            port = 0;
+                            pa_atou(pc + 1, &port);
+                            c->udp_server_timing_port = port;
+                        }
+                        *pc = '=';
+                    }
+                    pa_xfree(token);
+                }
+            } else {
+                pa_log_warn("Transport missing");
+            }
+
+            pa_xfree(ajs);
+            pa_xfree(trs);
+
+            stream_port = pa_rtsp_serverport(c->rtsp);
+            if (stream_port == 0)
+                goto error;
+            if (c->udp_server_control_port == 0 || c->udp_server_timing_port == 0)
+                goto error;
+
+            pa_log_debug("Using server_port=%d, control_port=%d & timing_port=%d",
+                stream_port,
+                c->udp_server_control_port,
+                c->udp_server_timing_port);
+
+            pa_assert(c->udp_stream_fd < 0);
+            pa_assert(c->udp_control_fd >= 0);
+            pa_assert(c->udp_timing_fd >= 0);
+
+            c->udp_stream_fd = connect_udp_socket(c, -1, stream_port);
+            if (c->udp_stream_fd <= 0)
+                goto error;
+            ret = connect_udp_socket(c, c->udp_control_fd,
+                                     c->udp_server_control_port);
+            if (ret < 0)
+                goto error;
+            ret = connect_udp_socket(c, c->udp_timing_fd,
+                                     c->udp_server_timing_port);
+            if (ret < 0)
+                goto error;
+
+            c->udp_setup_callback(c->udp_control_fd, c->udp_timing_fd, c->udp_setup_userdata);
+            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
+
+            break;
+
+        error:
+            if (c->udp_stream_fd > 0) {
+                pa_close(c->udp_stream_fd);
+                c->udp_stream_fd = -1;
+            }
+            if (c->udp_control_fd > 0) {
+                pa_close(c->udp_control_fd);
+                c->udp_control_fd = -1;
+            }
+            if (c->udp_timing_fd > 0) {
+                pa_close(c->udp_timing_fd);
+                c->udp_timing_fd = -1;
+            }
+
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
+            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
+            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
+            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
+
+            pa_log_error("aborting RTSP setup, failed creating required sockets");
+
+            break;
+        }
+
+        case STATE_RECORD: {
+            int32_t latency = 0;
+            uint32_t rand;
+            char *alt;
+
+            pa_log_debug("RAOP: RECORD");
+
+            alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
+            /* Generate a random synchronization source identifier from this session. */
+            pa_random(&rand, sizeof(rand));
+            c->udp_ssrc = rand;
+
+            if (alt)
+                pa_atoi(alt, &latency);
+
+            c->udp_first_packet = true;
+            c->udp_sync_count = 0;
+
+            c->udp_record_callback(c->udp_setup_userdata);
+
+            pa_xfree(alt);
+            break;
+        }
+
+        case STATE_SET_PARAMETER: {
+            pa_log_debug("RAOP: SET_PARAMETER");
+
+            break;
+        }
+
+        case STATE_FLUSH: {
+            pa_log_debug("RAOP: FLUSHED");
+
+            break;
+        }
+
+        case STATE_TEARDOWN: {
+            pa_log_debug("RAOP: TEARDOWN");
+            pa_assert(c->udp_disconnected_callback);
+            pa_assert(c->rtsp);
+
+            pa_rtsp_disconnect(c->rtsp);
+
+            if (c->udp_stream_fd > 0) {
+                pa_close(c->udp_stream_fd);
+                c->udp_stream_fd = -1;
+            }
+
+            pa_log_debug("RTSP control channel closed (teardown)");
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            /*
+              Callback for cleanup -- e.g. pollfd
+
+              Share the disconnected callback since TEARDOWN event
+              is essentially equivalent to DISCONNECTED.
+              In case some special treatment turns out to be required
+              for TEARDOWN in future, a new callback function may be
+              defined and used.
+            */
+            c->udp_disconnected_callback(c->udp_disconnected_userdata);
+
+            /* Control and timing fds are closed by udp_sink_process_msg,
+               after it disables poll */
+            c->udp_control_fd = -1;
+            c->udp_timing_fd = -1;
+
+            break;
+        }
+
+        case STATE_DISCONNECTED: {
+            pa_log_debug("RAOP: DISCONNECTED");
+            pa_assert(c->udp_disconnected_callback);
+            pa_assert(c->rtsp);
+
+            if (c->udp_stream_fd > 0) {
+                pa_close(c->udp_stream_fd);
+                c->udp_stream_fd = -1;
+            }
+
+            pa_log_debug("RTSP control channel closed (disconnected)");
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            c->udp_disconnected_callback(c->udp_disconnected_userdata);
+            /* Control and timing fds are closed by udp_sink_process_msg,
+               after it disables poll */
+            c->udp_control_fd = -1;
+            c->udp_timing_fd = -1;
+
+            break;
+        }
+    }
+}
+
+pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) {
     pa_raop_client* c;
+    pa_parsed_address a;
+    pa_sample_spec ss;
 
     pa_assert(core);
     pa_assert(host);
@@ -402,17 +1035,35 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host) {
 
     c = pa_xnew0(pa_raop_client, 1);
     c->core = core;
-    c->fd = -1;
+    c->tcp_fd = -1;
+    c->protocol = protocol;
+    c->udp_stream_fd = -1;
+    c->udp_control_fd = -1;
+    c->udp_timing_fd = -1;
+
+    c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT;
+    c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT;
+    c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT;
+    c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT;
 
     c->host = a.path_or_host;
     if (a.port)
         c->port = a.port;
     else
-        c->port = RAOP_PORT;
+        c->port = DEFAULT_RAOP_PORT;
 
-    if (pa_raop_connect(c)) {
-        pa_raop_client_free(c);
-        return NULL;
+    c->udp_first_packet = true;
+
+    ss = core->default_sample_spec;
+    /* Packet sync interval should be around 1s. */
+    c->udp_sync_interval = ss.rate / UDP_FRAMES_PER_PACKET;
+    c->udp_sync_count = 0;
+
+    if (c->protocol == RAOP_TCP) {
+        if (pa_raop_client_connect(c)) {
+            pa_raop_client_free(c);
+            return NULL;
+        }
     }
 
     return c;
@@ -429,7 +1080,7 @@ void pa_raop_client_free(pa_raop_client *c) {
     pa_xfree(c);
 }
 
-int pa_raop_connect(pa_raop_client *c) {
+int pa_raop_client_connect(pa_raop_client *c) {
     char *sci;
     struct {
         uint32_t a;
@@ -444,7 +1095,10 @@ int pa_raop_connect(pa_raop_client *c) {
         return 0;
     }
 
-    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)");
+    if (c->protocol == RAOP_TCP)
+        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)");
+    else
+        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)");
 
     /* Initialise the AES encryption system. */
     pa_random(c->aes_iv, sizeof(c->aes_iv));
@@ -458,20 +1112,179 @@ int pa_raop_connect(pa_raop_client *c) {
     sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c);
     pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
     pa_xfree(sci);
-    pa_rtsp_set_callback(c->rtsp, rtsp_cb, c);
+    if (c->protocol == RAOP_TCP)
+        pa_rtsp_set_callback(c->rtsp, tcp_rtsp_cb, c);
+    else
+        pa_rtsp_set_callback(c->rtsp, udp_rtsp_cb, c);
 
     return pa_rtsp_connect(c->rtsp);
 }
 
-int pa_raop_flush(pa_raop_client *c) {
+int pa_raop_client_flush(pa_raop_client *c) {
+    int rv = 0;
     pa_assert(c);
 
-    pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
-    return 0;
+    if (c->rtsp != NULL) {
+        rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
+        c->udp_sync_count = -1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_teardown(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->rtsp != NULL)
+        rv = pa_rtsp_teardown(c->rtsp);
+
+    return rv;
+}
+
+int pa_raop_client_udp_can_stream(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->udp_stream_fd > 0)
+        rv = 1;
+
+    return rv;
+}
+
+int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
+    const uint32_t * data = NULL;
+    uint8_t payload = 0;
+    struct timeval tv;
+    uint64_t rci = 0;
+    int rv = 0;
+
+    pa_assert(c);
+    pa_assert(packet);
+
+    /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps. */
+    if (size != 32 || packet[0] != 0x80)
+    {
+        pa_log_debug("Received an invalid timing packet.");
+        return 1;
+    }
+
+    data = (uint32_t *) (packet + sizeof(udp_timming_header));
+    rci = timeval_to_ntp(pa_rtclock_get(&tv));
+    /* The market bit is always set (see rfc3550 for packet structure) ! */
+    payload = packet[1] ^ 0x80;
+    switch (payload) {
+        case UDP_PAYLOAD_TIMING_REQUEST:
+            rv = udp_send_timing_packet(c, data, rci);
+            break;
+        case UDP_PAYLOAD_TIMING_RESPONSE:
+        default:
+            pa_log_debug("Got an unexpected payload type on timing channel !");
+            return 1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
+    uint8_t payload = 0;
+    int rv = 0;
+
+    pa_assert(c);
+    pa_assert(packet);
+
+    if (size != 20 || packet[0] != 0x80)
+    {
+        pa_log_debug("Received an invalid control packet.");
+        return 1;
+    }
+
+    /* The market bit is always set (see rfc3550 for packet structure) ! */
+
+    payload = packet[1] ^ 0x80;
+    switch (payload) {
+        case UDP_PAYLOAD_RETRANSMIT_REQUEST:
+            /* Packet retransmission not implemented yet... */
+            /* rv = ... */
+            break;
+        case UDP_PAYLOAD_RETRANSMIT_REPLY:
+        default:
+            pa_log_debug("Got an unexpected payload type on control channel !");
+            return 1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size) {
+    int rv = 0;
+
+    pa_assert(c);
+    pa_assert(size);
+
+    *size = UDP_FRAMES_PER_PACKET;
+
+    return rv;
+}
+
+ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block) {
+    uint8_t *buf = NULL;
+    ssize_t len;
+
+    pa_assert(c);
+    pa_assert(block);
+
+    /* Sync RTP & NTP timestamp if required. */
+    if (c->udp_first_packet || c->udp_sync_count >= c->udp_sync_interval) {
+        udp_send_sync_packet(c, c->rtptime);
+        c->udp_sync_count = 0;
+    } else {
+        c->udp_sync_count++;
+    }
+
+    buf = pa_memblock_acquire(block->memblock);
+    pa_assert(buf);
+    pa_assert(block->length > 0);
+    udp_build_audio_header(c, (uint32_t *) (buf + block->index), block->length);
+    len = udp_send_audio_packet(c, buf + block->index, block->length);
+    pa_memblock_release(block->memblock);
+
+    if (len > 0) {
+        pa_assert((size_t) len <= block->length);
+        /* UDP packet has to be sent at once, so it is meaningless to
+           preseve the partial data
+           FIXME: This won't happen at least in *NIX systems?? */
+        if (block->length > (size_t) len) {
+            pa_log_warn("Tried to send %zu bytes but managed to send %zu bytes", block->length, len);
+            len = block->length;
+        }
+        block->index += block->length;
+        block->length = 0;
+    }
+
+    if (c->udp_first_packet)
+        c->udp_first_packet = false;
+
+    return len;
+}
+
+/* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */
+pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) {
+    double minv, maxv;
+
+    if (c->protocol != RAOP_UDP)
+        return volume;
+
+    maxv = pa_sw_volume_from_dB(0.0);
+    minv = maxv * pow(10.0, (double) VOLUME_DEF / 60.0);
+
+    return volume - volume * (minv / maxv) + minv;
 }
 
 int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
-    int rv;
+    int rv = 0;
     double db;
     char *param;
 
@@ -483,10 +1296,13 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
     else if (db > VOLUME_MAX)
         db = VOLUME_MAX;
 
+    pa_log_debug("volume=%u db=%.6f", volume, db);
+
     param = pa_sprintf_malloc("volume: %0.6f\r\n",  db);
 
     /* We just hit and hope, cannot wait for the callback. */
-    rv = pa_rtsp_setparameter(c->rtsp, param);
+    if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp))
+        rv = pa_rtsp_setparameter(c->rtsp, param);
     pa_xfree(param);
 
     return rv;
@@ -501,21 +1317,23 @@ int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchun
     uint8_t *b, *p;
     uint32_t bsize;
     size_t length;
-    static uint8_t header[] = {
-        0x24, 0x00, 0x00, 0x00,
-        0xF0, 0xFF, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00,
-    };
-    int header_size = sizeof(header);
+    const uint8_t *header;
+    int header_size;
 
     pa_assert(c);
-    pa_assert(c->fd > 0);
     pa_assert(raw);
     pa_assert(raw->memblock);
     pa_assert(raw->length > 0);
     pa_assert(encoded);
 
+    if (c->protocol == RAOP_TCP) {
+        header = tcp_audio_header;
+        header_size = sizeof(tcp_audio_header);
+    } else {
+        header = udp_audio_header;
+        header_size = sizeof(udp_audio_header);
+    }
+
     /* We have to send 4 byte chunks */
     bsize = (int)(raw->length / 4);
     length = bsize * 4;
@@ -544,7 +1362,9 @@ int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchun
     bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8);
     bit_writer(&bp,&bpos,&size,(bsize)&0xff,8);
 
-    ibp = p = pa_memblock_acquire(raw->memblock);
+    p = pa_memblock_acquire(raw->memblock);
+    p += raw->index;
+    ibp = p;
     maxibp = p + raw->length - 4;
     while (ibp <= maxibp) {
         /* Byte swap stereo data. */
@@ -556,16 +1376,22 @@ int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchun
         raw->index += 4;
         raw->length -= 4;
     }
+    if (c->protocol == RAOP_UDP)
+        c->rtptime += (ibp - p) / 4;
     pa_memblock_release(raw->memblock);
     encoded->length = header_size + size;
 
-    /* Store the length (endian swapped: make this better). */
-    len = size + header_size - 4;
-    *(b + 2) = len >> 8;
-    *(b + 3) = len & 0xff;
+    if (c->protocol == RAOP_TCP) {
+        /* Store the length (endian swapped: make this better). */
+        len = size + header_size - 4;
+        *(b + 2) = len >> 8;
+        *(b + 3) = len & 0xff;
+    }
 
-    /* Encrypt our data. */
-    aes_encrypt(c, (b + header_size), size);
+    if (c->encryption) {
+        /* Encrypt our data. */
+        aes_encrypt(c, (b + header_size), size);
+    }
 
     /* We're done with the chunk. */
     pa_memblock_release(encoded->memblock);
@@ -573,16 +1399,41 @@ int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchun
     return 0;
 }
 
-void pa_raop_client_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata) {
+void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->tcp_callback = callback;
+    c->tcp_userdata = userdata;
+}
+
+void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->tcp_closed_callback = callback;
+    c->tcp_closed_userdata = userdata;
+}
+
+void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) {
+    c->encryption = encryption;
+}
+
+void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->udp_setup_callback = callback;
+    c->udp_setup_userdata = userdata;
+}
+
+void pa_raop_client_udp_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata) {
     pa_assert(c);
 
-    c->callback = callback;
-    c->userdata = userdata;
+    c->udp_record_callback = callback;
+    c->udp_record_userdata = userdata;
 }
 
-void pa_raop_client_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata) {
+void pa_raop_client_udp_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata) {
     pa_assert(c);
 
-    c->closed_callback = callback;
-    c->closed_userdata = userdata;
+    c->udp_disconnected_callback = callback;
+    c->udp_disconnected_userdata = userdata;
 }
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 6ba32e9..36be8dc 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -20,23 +20,52 @@
   along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <pulse/volume.h>
+
 #include <pulsecore/core.h>
+#include <pulsecore/memchunk.h>
+
+typedef enum pa_raop_protocol {
+    RAOP_TCP,
+    RAOP_UDP,
+} pa_raop_protocol_t;
 
 typedef struct pa_raop_client pa_raop_client;
 
-pa_raop_client* pa_raop_client_new(pa_core *core, const char *host);
+pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol);
 void pa_raop_client_free(pa_raop_client *c);
 
-int pa_raop_connect(pa_raop_client *c);
-int pa_raop_flush(pa_raop_client *c);
+int pa_raop_client_connect(pa_raop_client *c);
+int pa_raop_client_flush(pa_raop_client *c);
+int pa_raop_client_teardown(pa_raop_client *c);
 
+int pa_raop_client_udp_can_stream(pa_raop_client *c);
+
+void pa_raop_client_set_encryption(pa_raop_client *c, int encryption);
+pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
 int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume);
 int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchunk *encoded);
 
+int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet
+[], ssize_t size);
+int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
+int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size);
+ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block);
+
 typedef void (*pa_raop_client_cb_t)(int fd, void *userdata);
-void pa_raop_client_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata);
+void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata);
 
 typedef void (*pa_raop_client_closed_cb_t)(void *userdata);
-void pa_raop_client_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata);
+void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata);
+
+
+typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata);
+void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata);
+
+typedef void (*pa_raop_client_record_cb_t)(void *userdata);
+void pa_raop_client_udp_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata);
+
+typedef void (*pa_raop_client_disconnected_cb_t)(void *userdata);
+void pa_raop_client_udp_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata);
 
 #endif



More information about the pulseaudio-commits mailing list