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