[pulseaudio-discuss] [PATCH 12/25] raop: Add UDP protocol handling

Hajime Fujita crisp.fujita at nifty.com
Sat Sep 7 09:35:01 PDT 2013


From: Martin Blanchard <tinram at gmx.fr>

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.
---
 src/modules/raop/module-raop-sink.c | 312 ++++++++++++++-
 src/modules/raop/raop_client.c      | 780 ++++++++++++++++++++++++++++++++++--
 src/modules/raop/raop_client.h      |  23 +-
 3 files changed, 1073 insertions(+), 42 deletions(-)

diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 050441c..d9cbe7b 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -102,7 +102,6 @@ struct userdata {
     int32_t rate;
 
     pa_smoother *smoother;
-    int fd;
 
     int64_t offset;
     int64_t encoding_overhead;
@@ -112,6 +111,13 @@ struct userdata {
     pa_raop_client *raop;
 
     size_t block_size;
+
+    /* Members only for the TCP protocol */
+    int fd;
+
+    /* Members only for the UDP protocol */
+    int control_fd;
+    int timing_fd;
 };
 
 static const char* const valid_modargs[] = {
@@ -129,7 +135,10 @@ static const char* const valid_modargs[] = {
 
 enum {
     SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX,
-    SINK_MESSAGE_RIP_SOCKET
+    SINK_MESSAGE_RIP_SOCKET,
+    SINK_MESSAGE_SETUP,
+    SINK_MESSAGE_RECORD,
+    SINK_MESSAGE_DISCONNECTED,
 };
 
 /* Forward declarations: */
@@ -177,7 +186,7 @@ static pa_usec_t sink_get_latency(const struct userdata *u) {
     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) {
@@ -272,6 +281,115 @@ 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 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_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:
+                    if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
+                        pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
+                    pa_log_debug("RAOP: RUNNING");
+                    if (!pa_raop_client_can_stream(u->raop)) {
+                        /* Connecting will trigger a RECORD */
+                        pa_raop_client_connect(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 r = 0;
+
+            if (pa_raop_client_can_stream(u->raop))
+                r = sink_get_latency(u);
+
+            *((pa_usec_t*) data) = r;
+
+            return 0;
+        }
+
+        case SINK_MESSAGE_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->control_fd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+            pollfd++;
+            pollfd->fd = u->timing_fd;
+            pollfd->events = POLLIN | POLLPRI;
+            pollfd->revents = 0;
+
+            u->control_fd = -1;
+            u->timing_fd = -1;
+            return 0;
+        }
+
+        case SINK_MESSAGE_RECORD: {
+            pa_rtpoll_set_timer_relative(u->rtpoll, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
+
+            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_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);
+            }
+
+            u->control_fd = -1;
+            u->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;
@@ -317,8 +435,45 @@ static void sink_set_mute_cb(pa_sink *s) {
     }
 }
 
-static void thread_func(void *userdata) {
+static void raop_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->control_fd = control_fd;
+    u->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_SETUP, NULL, 0, NULL, NULL);
+}
+
+static void raop_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_RECORD, NULL, 0, NULL, NULL);
+}
+
+static void raop_disconnected_cb(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_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;
@@ -326,7 +481,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);
 
@@ -509,7 +664,130 @@ 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, true)) < 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_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_handle_timing_packet(u->raop, packet, read);
+                }
+            }
+
+            continue;
+        }
+
+        if (!pa_raop_client_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);
+
+        pa_raop_client_send_audio_packet(u->raop, &u->encoded_memchunk, &written);
+        pa_rtpoll_set_timer_relative(u->rtpoll, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
+
+        pa_assert(written != 0);
+
+        if (written < 0) {
+            pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+            goto fail;
+        }
+
+        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) {
@@ -590,8 +868,6 @@ int pa__init(pa_module *m) {
     }
     else if (pa_streq(protocol, "UDP")) {
         u->protocol = RAOP_UDP;
-        pa_log("UDP is not supported in this module version.");
-        goto fail;
     } else {
         pa_log("Unsupported protocol argument given: %s", protocol);
         goto fail;
@@ -606,6 +882,8 @@ int pa__init(pa_module *m) {
     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);
@@ -621,7 +899,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);
@@ -630,7 +911,7 @@ 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, u->protocol))) {
+    if (!(u->raop = pa_raop_client_new(u->core, server, u->protocol, ss))) {
         pa_log("Failed to connect to server.");
         goto fail;
     }
@@ -638,6 +919,17 @@ int pa__init(pa_module *m) {
     pa_raop_client_set_callback(u->raop, on_connection, u);
     pa_raop_client_set_closed_callback(u->raop, on_close, u);
 
+    if (u->protocol == RAOP_UDP) {
+        /* The number of frames per blocks is not negotiable... */
+        pa_raop_client_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_set_setup_callback(u->raop, raop_setup_cb, u);
+        pa_raop_client_set_record_callback(u->raop, raop_record_cb, u);
+        pa_raop_client_set_disconnected_callback(u->raop, raop_disconnected_cb, u);
+    }
+
     if (!(u->thread = pa_thread_new("raop-sink", thread_func, u))) {
         pa_log("Failed to create thread.");
         goto fail;
diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c
index f0be62d..1ca8421 100644
--- a/src/modules/raop/raop_client.c
+++ b/src/modules/raop/raop_client.c
@@ -41,10 +41,13 @@
 #include <openssl/engine.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.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>
@@ -56,6 +59,7 @@
 #include "rtsp_client.h"
 #include "base64.h"
 
+#define FRAMES_PER_PACKET 352
 #define AES_CHUNKSIZE 16
 
 #define JACK_STATUS_DISCONNECTED 0
@@ -69,6 +73,19 @@
 #define VOLUME_MAX 0
 
 #define RAOP_PORT 5000
+#define DEFAULT_RAOP_PORT 5000
+#define DEFAULT_AUDIO_PORT 6000
+#define DEFAULT_CONTROL_PORT 6001
+#define DEFAULT_TIMING_PORT 6002
+
+typedef enum {
+    PAYLOAD_TIMING_REQUEST = 0x52,
+    PAYLOAD_TIMING_RESPONSE = 0x53,
+    PAYLOAD_SYNCHRONIZATION = 0x54,
+    PAYLOAD_RETRANSMIT_REQUEST = 0x55,
+    PAYLOAD_RETRANSMIT_REPLY = 0x56,
+    PAYLOAD_AUDIO_DATA = 0x60
+} pa_raop_payload_type;
 
 struct pa_raop_client {
     pa_core *core;
@@ -87,16 +104,79 @@ struct pa_raop_client {
     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;
 
+    /* Members only for the TCP protocol */
+    pa_socket_client *sc;
+    int fd;
+
     pa_raop_client_cb_t callback;
     void *userdata;
     pa_raop_client_closed_cb_t closed_callback;
     void *closed_userdata;
+
+    /* Members only for the UDP protocol */
+    uint16_t control_port;
+    uint16_t timing_port;
+
+    int stream_fd;
+    int control_fd;
+    int timing_fd;
+
+    uint32_t ssrc;
+
+    bool first_packet;
+    uint32_t sync_interval;
+    uint32_t sync_count;
+
+    pa_raop_client_setup_cb_t setup_callback;
+    void *setup_userdata;
+
+    pa_raop_client_record_cb_t record_callback;
+    void *record_userdata;
+
+    pa_raop_client_disconnected_cb_t disconnected_callback;
+    void *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 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 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
 };
 
 /**
@@ -231,6 +311,212 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
     c->callback(c->fd, 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 bind_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;
+    int one = 1;
+
+    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_INET;
+        sa4.sin_port = htons(port);
+        sa = (struct sockaddr *) &sa4;
+        salen = sizeof(sa4);
+#ifdef HAVE_IPV6
+    } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
+        sa6.sin6_family = 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;
+    }
+
+    one = 1;
+#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;
+    }
+
+    if (bind(fd, sa, salen) < 0) {
+        pa_log("bind() failed: %s", pa_cstrerror(errno));
+        goto fail;
+    }
+
+    return fd;
+
+fail:
+    return -1;
+}
+
+static int open_udp_socket(pa_raop_client *c, uint16_t port, uint16_t should_bind) {
+    struct sockaddr_in sa4;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 sa6;
+#endif
+    struct sockaddr *sa;
+    socklen_t salen;
+    sa_family_t af;
+    int fd = -1;
+
+    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 = 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 (should_bind) {
+        if (bind_socket(c, fd, port) < 0) {
+            pa_log("bind_socket() failed: %s", pa_cstrerror(errno));
+            goto fail;
+        }
+    }
+
+    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 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, timming_header, sizeof(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->timing_fd, packet, sizeof(packet), NULL);
+    if (written == sizeof(packet))
+        rv = 0;
+
+    return rv;
+}
+
+static int 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, sync_header, sizeof(sync_header));
+    if (c->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->control_fd, packet, sizeof(packet), NULL);
+    if (written == sizeof(packet))
+        rv = 0;
+
+    return rv;
+}
+
+static int send_audio_packet(pa_raop_client *c, uint32_t *buffer, size_t size, ssize_t *written) {
+    ssize_t length = 0;
+    int rv = 1;
+
+    memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
+    if (c->first_packet)
+        buffer[0] |= ((uint32_t) 0x80) << 8;
+    buffer[0] |= htonl((uint32_t) c->seq);
+    buffer[1] = htonl(c->rtptime);
+    buffer[2] = htonl(c->ssrc);
+
+    length = pa_loop_write(c->stream_fd, buffer, size, NULL);
+    if (length == ((ssize_t) size))
+        rv = 0;
+
+    if (written != NULL)
+        *written = length;
+    c->seq++;
+
+    return rv;
+}
+
 static void do_rtsp_announce(pa_raop_client *c) {
     int i;
     uint8_t rsakey[512];
@@ -271,7 +557,7 @@ static void do_rtsp_announce(pa_raop_client *c) {
         "a=rsaaeskey:%s\r\n"
         "a=aesiv:%s\r\n",
         c->sid, ip, c->host,
-        4096,
+        c->protocol == RAOP_TCP ? 4096 : FRAMES_PER_PACKET,
         key, iv);
     pa_rtsp_announce(c->rtsp, sdp);
     pa_xfree(key);
@@ -280,8 +566,8 @@ static void do_rtsp_announce(pa_raop_client *c) {
     pa_xfree(sdp);
 }
 
-static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) {
-    pa_raop_client *c = userdata;
+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);
@@ -361,7 +647,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *he
             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. */
+                /* We do not close the fd, we leave it to the closed callback to do that */
                 c->fd = -1;
             }
             if (c->sc) {
@@ -375,14 +661,264 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *he
     }
 }
 
+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;
+
+            trs = pa_sprintf_malloc("RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d",
+                c->control_port,
+                c->timing_port);
+
+            pa_rtsp_setup(c->rtsp, trs);
+
+            pa_xfree(trs);
+            break;
+        }
+
+        case STATE_SETUP: {
+            uint32_t stream_port = DEFAULT_AUDIO_PORT;
+            char *ajs, *trs, *token, *pc;
+            char delimiters[] = ";";
+            const char *token_state = NULL;
+            uint32_t port = 0;
+
+            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->control_port = port;
+                        }
+                        if (pa_streq(token, "timing_port")) {
+                            port = 0;
+                            pa_atou(pc + 1, &port);
+                            c->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->control_port == 0 || c->timing_port == 0)
+                goto error;
+
+            pa_log_debug("Using server_port=%d, control_port=%d & timing_port=%d",
+                stream_port,
+                c->control_port,
+                c->timing_port);
+
+            c->stream_fd = open_udp_socket(c, stream_port, 0);
+            c->control_fd = open_udp_socket(c, c->control_port, 1);
+            c->timing_fd = open_udp_socket(c, c->timing_port, 1);
+
+            if (c->stream_fd <= 0)
+                goto error;
+            if (c->control_fd <= 0 || c->timing_fd <= 0)
+                goto error;
+
+            c->setup_callback(c->control_fd, c->timing_fd, c->setup_userdata);
+            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
+
+            break;
+
+        error:
+            if (c->stream_fd > 0) {
+                pa_close(c->stream_fd);
+                c->stream_fd = -1;
+            }
+            if (c->control_fd > 0) {
+                pa_close(c->control_fd);
+                c->control_fd = -1;
+            }
+            if (c->timing_fd > 0) {
+                pa_close(c->timing_fd);
+                c->timing_fd = -1;
+            }
+
+            pa_rtsp_client_free(c->rtsp);
+            c->rtsp = NULL;
+
+            c->control_port = DEFAULT_CONTROL_PORT;
+            c->timing_port = 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->ssrc = rand;
+
+            if (alt)
+                pa_atoi(alt, &latency);
+
+            c->first_packet = true;
+            c->sync_count = 0;
+
+            c->record_callback(c->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_rtsp_disconnect(c->rtsp);
+
+            if (c->stream_fd > 0) {
+                pa_close(c->stream_fd);
+                c->stream_fd = -1;
+            }
+            if (c->control_fd > 0) {
+                pa_close(c->control_fd);
+                c->control_fd = -1;
+            }
+            if (c->timing_fd > 0) {
+                pa_close(c->timing_fd);
+                c->timing_fd = -1;
+            }
+
+            pa_log_debug("RTSP control channel closed");
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            break;
+        }
+
+        case STATE_DISCONNECTED: {
+            pa_assert(c->disconnected_callback);
+            pa_assert(c->rtsp);
+
+            if (c->stream_fd > 0) {
+                pa_close(c->stream_fd);
+                c->stream_fd = -1;
+            }
+            if (c->control_fd > 0) {
+                pa_close(c->control_fd);
+                c->control_fd = -1;
+            }
+            if (c->timing_fd > 0) {
+                pa_close(c->timing_fd);
+                c->timing_fd = -1;
+            }
+
+            pa_log_debug("RTSP control channel closed");
+
+            pa_rtsp_client_free(c->rtsp);
+            pa_xfree(c->sid);
+            c->rtsp = NULL;
+            c->sid = NULL;
+
+            c->disconnected_callback(c->disconnected_userdata);
+
+            break;
+        }
+    }
+}
+
 pa_raop_client* pa_raop_client_new(pa_core *core, const char *host,
-                                   pa_raop_protocol_t protocol) {
+                                   pa_raop_protocol_t protocol,
+                                   pa_sample_spec spec) {
     pa_parsed_address a;
     pa_raop_client *c = pa_xnew0(pa_raop_client, 1);
 
     pa_assert(core);
     pa_assert(host);
-    pa_assert(protocol == RAOP_TCP);
 
     if (pa_parse_address(host, &a) < 0 || a.type == PA_PARSED_ADDRESS_UNIX)
         return NULL;
@@ -390,16 +926,29 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host,
     c->core = core;
     c->fd = -1;
     c->protocol = protocol;
+    c->stream_fd = -1;
+    c->control_fd = -1;
+    c->timing_fd = -1;
+
+    c->control_port = DEFAULT_CONTROL_PORT;
+    c->timing_port = DEFAULT_TIMING_PORT;
 
     c->host = pa_xstrdup(a.path_or_host);
     if (a.port)
         c->port = a.port;
     else
-        c->port = RAOP_PORT;
+        c->port = DEFAULT_RAOP_PORT;
 
-    if (pa_raop_client_connect(c)) {
-        pa_raop_client_free(c);
-        return NULL;
+    c->first_packet = true;
+    /* Packet sync interval should be around 1s. */
+    c->sync_interval = spec.rate / FRAMES_PER_PACKET;
+    c->sync_count = 0;
+
+    if (c->protocol == RAOP_TCP) {
+        if (pa_raop_client_connect(c)) {
+            pa_raop_client_free(c);
+            return NULL;
+        }
     }
 
     return c;
@@ -431,7 +980,10 @@ int pa_raop_client_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));
@@ -445,20 +997,157 @@ int pa_raop_client_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_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->sync_count = -1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_teardown(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    /* This should be followed by a STATE_DISCONNECTED event
+     * which will take care of cleaning up everything */
+    if (c->rtsp != NULL)
+        rv = pa_rtsp_teardown(c->rtsp);
+
+    return rv;
+}
+
+int pa_raop_client_can_stream(pa_raop_client *c) {
+    int rv = 0;
+
+    pa_assert(c);
+
+    if (c->stream_fd > 0)
+        rv = 1;
+
+    return rv;
+}
+
+int pa_raop_client_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(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 PAYLOAD_TIMING_REQUEST:
+            rv = send_timing_packet(c, data, rci);
+            break;
+        case PAYLOAD_TIMING_RESPONSE:
+        default:
+            pa_log_debug("Got an unexpected payload type on timing channel !");
+            return 1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_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 PAYLOAD_RETRANSMIT_REQUEST:
+            /* Packet retransmission not implemented yet... */
+            /* rv = ... */
+            break;
+        case PAYLOAD_RETRANSMIT_REPLY:
+        default:
+            pa_log_debug("Got an unexpected payload type on control channel !");
+            return 1;
+    }
+
+    return rv;
+}
+
+int pa_raop_client_get_blocks_size(pa_raop_client *c, size_t *size) {
+    int rv = 0;
+
+    pa_assert(c);
+    pa_assert(size);
+
+    *size = FRAMES_PER_PACKET;
+
+    return rv;
+}
+
+int pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, ssize_t *written) {
+    uint32_t *buf = NULL;
+    ssize_t length = 0;
+    int rv = 0;
+
+    pa_assert(c);
+    pa_assert(block);
+
+    /* Sync RTP & NTP timestamp if required. */
+    if (c->first_packet || c->sync_count >= c->sync_interval) {
+        send_sync_packet(c, c->rtptime);
+        c->sync_count = 0;
+    } else {
+        c->sync_count++;
+    }
+
+    buf = (uint32_t *) pa_memblock_acquire(block->memblock);
+    if (buf != NULL && block->length > 0)
+        rv = send_audio_packet(c, buf + block->index, block->length, &length);
+    pa_memblock_release(block->memblock);
+    block->index += length;
+    block->length -= length;
+    if (written != NULL)
+        *written = length;
+
+    if (c->first_packet)
+        c->first_packet = false;
+
+    return rv;
 }
 
 int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
-    int rv;
+    int rv = 0;
     double db;
     char *param;
 
@@ -473,7 +1162,8 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
     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)
+        rv = pa_rtsp_setparameter(c->rtsp, param);
     pa_xfree(param);
 
     return rv;
@@ -488,21 +1178,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;
@@ -543,13 +1235,17 @@ 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);
@@ -573,3 +1269,25 @@ void pa_raop_client_set_closed_callback(pa_raop_client *c, pa_raop_client_closed
     c->closed_callback = callback;
     c->closed_userdata = userdata;
 }
+
+
+void pa_raop_client_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->setup_callback = callback;
+    c->setup_userdata = userdata;
+}
+
+void pa_raop_client_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->record_callback = callback;
+    c->record_userdata = userdata;
+}
+
+void pa_raop_client_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata) {
+    pa_assert(c);
+
+    c->disconnected_callback = callback;
+    c->disconnected_userdata = userdata;
+}
diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h
index 3ecf8d9..97847d9 100644
--- a/src/modules/raop/raop_client.h
+++ b/src/modules/raop/raop_client.h
@@ -21,8 +21,11 @@
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA.
 ***/
+#include <pulse/sample.h>
+#include <pulse/volume.h>
 
 #include <pulsecore/core.h>
+#include <pulsecore/memchunk.h>
 
 typedef enum pa_raop_protocol {
     RAOP_TCP,
@@ -31,19 +34,37 @@ typedef enum pa_raop_protocol {
 
 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);
+pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol, pa_sample_spec spec);
 void pa_raop_client_free(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_can_stream(pa_raop_client *c);
 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_handle_timing_packet(pa_raop_client *c, const uint8_t packet
+[], ssize_t size);
+int pa_raop_client_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size);
+int pa_raop_client_get_blocks_size(pa_raop_client *c, size_t *size);
+int pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, ssize_t *written);
+
 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);
 
 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);
 
+
+typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata);
+void pa_raop_client_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_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_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata);
+
 #endif
-- 
1.8.1.2



More information about the pulseaudio-discuss mailing list