[pulseaudio-discuss] [PATCH v6 09/37] raop: Add UDP protocol handling

Hajime Fujita crisp.fujita at nifty.com
Wed Feb 10 05:56:18 UTC 2016


Arun Raghavan wrote:
> On Sun, 2016-01-31 at 22:16 -0600, Hajime Fujita wrote:
>> From: Hajime Fujita <crisp.fujita at nifty.com>
>>
>> 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
>> ---
> 
> Ideally this patch should come before "raop: Parse server capabilities
> on discovery" as it introduces modargs that the previous commit uses.
> If there are conflicts on rebase though, I'm okay with not reordering.

Makes sense. Actually I am able to reorder.


Thanks,
Hajime

> 
>>  src/modules/raop/module-raop-sink.c |  457 +++++++++++++--
>>  src/modules/raop/raop_client.c      | 1063 +++++++++++++++++++++++++++++++----
>>  src/modules/raop/raop_client.h      |   39 +-
>>  3 files changed, 1400 insertions(+), 159 deletions(-)
>>
>> diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
>> index 6fc3d94..1eadde2 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= "
>>          "sink_properties= "
>>          "server=  "
>> +        "protocol= "
>> +        "encryption= "
>> +        "codec= "
>>          "format= "
>>          "rate= "
>>          "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);
> 
> Yes, this needs to be done via a message on the main thread, and not in
> the I/O thread.
> 
> -- Arun
> 
>> +            }
>> +
>> +            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, 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_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 a5dd29c..c0be2ec 100644
>> --- a/src/modules/raop/raop_client.c
>> +++ b/src/modules/raop/raop_client.c
>> @@ -26,6 +26,7 @@
>>  #include 
>>  #include 
>>  #include 
>> +#include 
>>  
>>  #ifdef HAVE_SYS_FILIO_H
>>  #include 
>> @@ -39,10 +40,14 @@
>>  #include 
>>  
>>  #include 
>> +#include 
>> +#include 
>>  
>>  #include 
>> +#include 
>>  #include 
>>  #include 
>> +#include 
>>  #include 
>>  #include 
>>  #include 
>> @@ -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;
>>  
>>  struct pa_raop_client {
>>      pa_core *core;
>> @@ -74,26 +92,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
>>  };
>>  
>>  /**
>> @@ -200,84 +285,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;
>>          }
>>  
>> @@ -325,11 +660,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;
>>          }
>>  
>> @@ -346,30 +681,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);
>> @@ -384,17 +1017,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;
>> @@ -411,7 +1062,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;
>> @@ -426,7 +1077,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));
>> @@ -440,20 +1094,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;
>>  
>> @@ -465,10 +1278,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;
>> @@ -483,21 +1299,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;
>> @@ -526,7 +1344,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. */
>> @@ -538,16 +1358,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);
>> @@ -555,16 +1381,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 
>> +
>>  #include 
>> +#include 
>> +
>> +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
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
> 



More information about the pulseaudio-discuss mailing list