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