From b961f83002fc9d98f4e255686648078918a76761 Mon Sep 17 00:00:00 2001 From: ckdo Date: Fri, 20 Dec 2019 21:16:01 +0100 Subject: [PATCH 1/4] ioline: add a block mode --- src/pulsecore/ioline.c | 50 +++++++++++++++++++++++++++++++++++++++--- src/pulsecore/ioline.h | 4 ++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index dfc5a7336..c55a5d5ce 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -51,13 +51,16 @@ struct pa_ioline { size_t wbuf_length, wbuf_index, wbuf_valid_length; char *rbuf; - size_t rbuf_length, rbuf_index, rbuf_valid_length; + size_t rbuf_length, rbuf_index, rbuf_valid_length, block_byte_count, rbuf_blockindex; pa_ioline_cb_t callback; + pa_stream_cb_t streamcallback; + size_t streamcallback_maxbyte; void *userdata; pa_ioline_drain_cb_t drain_callback; void *drain_userdata; + void *stream_userdata; bool dead:1; bool defer_close:1; @@ -81,6 +84,8 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) { l->rbuf_length = l->rbuf_index = l->rbuf_valid_length = 0; l->callback = NULL; + l->streamcallback = NULL; + l->streamcallback_maxbyte = 0; l->userdata = NULL; l->drain_callback = NULL; @@ -207,6 +212,20 @@ void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata l->userdata = userdata; } +void pa_ioline_set_streamcallback(pa_ioline*l, pa_stream_cb_t callback, size_t maxbyte, void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (l->dead) + return; + + l->streamcallback = callback; + l->stream_userdata = userdata; + l->streamcallback_maxbyte=maxbyte; + l->block_byte_count = 0; + l->rbuf_blockindex = l->rbuf_index; +} + void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); @@ -268,6 +287,10 @@ static void scan_for_lines(pa_ioline *l, size_t skip) { if (l->callback) l->callback(l, pa_strip_nl(p), l->userdata); + // If we switched to block mode during the callback, exit + if (l->streamcallback_maxbyte !=0){ + return; + } skip = 0; } @@ -285,6 +308,7 @@ static int do_read(pa_ioline *l) { while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) { ssize_t r; size_t len; + size_t byte_count; len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; @@ -332,9 +356,29 @@ static int do_read(pa_ioline *l) { } l->rbuf_valid_length += (size_t) r; + // If in block mode, append until we reach the size + if (l->streamcallback_maxbyte == 0){ + /* Look if a line has been terminated in the newly read data */ + scan_for_lines(l, l->rbuf_valid_length - (size_t) r); + byte_count = l->rbuf_valid_length; + } else { + byte_count = r; + } - /* Look if a line has been terminated in the newly read data */ - scan_for_lines(l, l->rbuf_valid_length - (size_t) r); + // Not in a "else" clause, since we may have enabled block mode in callback called in scan_for_lines + if (l->streamcallback_maxbyte > 0) { + l->block_byte_count += byte_count; + + if (l->block_byte_count >= l->streamcallback_maxbyte) { + l->rbuf_index += l->streamcallback_maxbyte; + l->rbuf_valid_length -= l->streamcallback_maxbyte; + l->streamcallback_maxbyte = 0; + if (l->rbuf_valid_length == 0) + l->rbuf_index = 0; + if (l->streamcallback) + l->streamcallback(l, l->stream_userdata); + } + } } return 0; diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index 7b6dff32f..1e5ce3aae 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -31,6 +31,7 @@ typedef struct pa_ioline pa_ioline; typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); +typedef void (*pa_stream_cb_t)(pa_ioline*io, void *userdata); typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata); pa_ioline* pa_ioline_new(pa_iochannel *io); @@ -47,6 +48,9 @@ void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR( /* Set the callback function that is called for every received line */ void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata); +/* Set the callback function that is called once maxbyte has been reached */ +void pa_ioline_set_streamcallback(pa_ioline*io, pa_stream_cb_t callback, size_t maxbyte, void *userdata); + /* Set the callback function that is called when everything has been written */ void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata); From 96cbee1230415d2eaff571ec82f0346d4f28d29d Mon Sep 17 00:00:00 2001 From: ckdo Date: Tue, 16 Feb 2021 20:19:22 +0200 Subject: [PATCH 2/4] raop: Add support for newest Airplay2 devices Changed rtsp_client to support response with body Support timing packet requests earlier (required by newest AirTunes) Send progress with SET_PARAMETER (required by newest AirTunes) --- src/modules/raop/raop-client.c | 277 ++++++++++++++++++++------------- src/modules/raop/raop-client.h | 2 + src/modules/rtp/rtsp_client.c | 44 +++++- src/modules/rtp/rtsp_client.h | 3 + 4 files changed, 215 insertions(+), 111 deletions(-) diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c index 2dc3145f2..18f8626dc 100644 --- a/src/modules/raop/raop-client.c +++ b/src/modules/raop/raop-client.c @@ -112,6 +112,7 @@ struct pa_raop_client { int udp_sfd; int udp_cfd; int udp_tfd; + int udp_tport; pa_raop_packet_buffer *pbuf; @@ -884,7 +885,7 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_ char *url; int ipv; - pa_log_debug("RAOP: CONNECTED"); + pa_log_debug("RAOP: CONNECT"); ip = pa_rtsp_localip(c->rtsp); if (pa_is_ip6_address(ip)) { @@ -966,28 +967,35 @@ connect_finish: break; } - case STATE_ANNOUNCE: { - uint16_t cport = DEFAULT_UDP_CONTROL_PORT; - uint16_t tport = DEFAULT_UDP_TIMING_PORT; + case STATE_ANNOUNCE: + case STATE_AUTH_SETUP: { char *trs = NULL; - pa_log_debug("RAOP: ANNOUNCE"); + uint16_t cport = DEFAULT_UDP_CONTROL_PORT; + uint16_t tport = DEFAULT_UDP_TIMING_PORT; + + pa_log_debug("RAOP: ANNOUNCE or AUTH-SETUP"); 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; - + if (state == STATE_ANNOUNCE) { + 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); } + if (state == STATE_ANNOUNCE) + if (c->state_callback) + c->state_callback(PA_RAOP_CONNECTED, c->state_userdata); + pa_rtsp_setup(c->rtsp, trs); pa_xfree(trs); @@ -1011,128 +1019,153 @@ connect_finish: } case STATE_SETUP: { - pa_socket_client *sc = NULL; - uint32_t sport = DEFAULT_UDP_AUDIO_PORT; - uint32_t cport =0, tport = 0; - char *ajs, *token, *pc, *trs; - const char *token_state = NULL; - char delimiters[] = ";"; + if (status == STATUS_FORBIDDEN) { + struct { + uint32_t ci1; + uint32_t ci2; + } rci; + pa_log_debug("RAOP: SETUP - FORBIDDEN"); - pa_log_debug("RAOP: SETUP"); + pa_random(&rci, sizeof(rci)); + c->sci = pa_sprintf_malloc("%08x%08x", rci.ci1, rci.ci2); + pa_rtsp_add_header(c->rtsp, "Client-Instance", c->sci); - ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); - - 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); + // Airplay 2 devices require additional authentication step + // It is actually useful to authenticate AirTunes server, not the device (see pa_rtsp_auth_setup for more details) + if (pa_rtsp_auth_setup( + c->rtsp, + "\x01\x4e\xea\xd0\x4e\xa9\x2e\x47\x69\xd2\xe1\xfb\xd0\x96\x81\xd5\x94\xa8\xef\x18\x45\x4a\x24\xae\xaf\xb3\x14\x97\x0d\xa0\xb5\xa3\x49" + ) < 0) { + pa_log_error("RAOP: Unable to POST auth-setup"); } - } else { - pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response"); + break; } - sport = pa_rtsp_serverport(c->rtsp); - if (sport <= 0) - goto setup_error; + if (status == STATUS_OK) { + pa_socket_client *sc = NULL; + uint32_t sport = DEFAULT_UDP_AUDIO_PORT; + uint32_t cport = 0, tport = 0; + char *ajs, *token, *pc, *trs; + const char *token_state = NULL; + char delimiters[] = ";"; - 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_log_debug("RAOP: SETUP"); - pa_socket_client_ref(sc); - pa_socket_client_set_callback(sc, tcp_connection_cb, c); + ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); - pa_socket_client_unref(sc); - sc = NULL; - } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { - trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport")); + if (ajs) { + c->jack_type = JACK_TYPE_ANALOG; + c->jack_status = JACK_STATUS_DISCONNECTED; - if (trs) { - /* Now parse out the server port component of the response. */ - while ((token = pa_split(trs, delimiters, &token_state))) { + while ((token = pa_split(ajs, delimiters, &token_state))) { if ((pc = strstr(token, "="))) { *pc = 0; - if (pa_streq(token, "control_port")) { - if (pa_atou(pc + 1, &cport) < 0) - goto setup_error_parse; - } - if (pa_streq(token, "timing_port")) { - if (pa_atou(pc + 1, &tport) < 0) - goto setup_error_parse; - } - *pc = '='; + 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(trs); + } else { - pa_log_warn("\"Transport\" missing in RTSP setup response"); + pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response"); } - if (cport <= 0 || tport <= 0) + sport = pa_rtsp_serverport(c->rtsp); + if (sport <= 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; + 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_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport); + pa_socket_client_ref(sc); + pa_socket_client_set_callback(sc, tcp_connection_cb, c); - /* Send an initial UDP packet so a connection tracking firewall - * knows the src_ip:src_port <-> dest_ip:dest_port relation - * and accepts the incoming timing packets. - */ - send_initial_udp_timing_packet(c); - pa_log_debug("Sent initial timing packet to UDP port %d", tport); + pa_socket_client_unref(sc); + sc = NULL; + } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { + trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport")); + + 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")) { + if (pa_atou(pc + 1, &cport) < 0) + goto setup_error_parse; + } + if (pa_streq(token, "timing_port")) { + if (pa_atou(pc + 1, &tport) < 0) + goto setup_error_parse; + } + *pc = '='; + } + pa_xfree(token); + } + pa_xfree(trs); + } else { + pa_log_warn("\"Transport\" missing in RTSP setup response"); + } + + if (cport <= 0 || tport <= 0) + goto setup_error; + + /* Send an initial UDP packet so a connection tracking firewall + * knows the src_ip:src_port <-> dest_ip:dest_port relation + * and accepts the incoming timing packets. + */ + send_initial_udp_timing_packet(c); + pa_log_debug("Sent initial timing packet to UDP port %d", tport); + + 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); + + } + + pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); + + pa_xfree(ajs); + break; + + + setup_error_parse: + pa_log("Failed parsing server port components"); + pa_xfree(token); + pa_xfree(trs); + /* fall-thru */ + 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_CONNECTED, c->state_userdata); + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); + + c->rtsp = NULL; + break; } - - pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); - - pa_xfree(ajs); - break; - - setup_error_parse: - pa_log("Failed parsing server port components"); - pa_xfree(token); - pa_xfree(trs); - /* fall-thru */ - 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; } @@ -1188,7 +1221,7 @@ connect_finish: c->udp_sfd = -1; /* Polling sockets will be closed by sink */ - c->udp_cfd = c->udp_tfd = -1; + c->udp_cfd = c->udp_tfd = c->udp_tport = -1; c->tcp_sfd = -1; pa_rtsp_client_free(c->rtsp); @@ -1216,7 +1249,7 @@ connect_finish: c->udp_sfd = -1; /* Polling sockets will be closed by sink */ - c->udp_cfd = c->udp_tfd = -1; + c->udp_cfd = c->udp_tfd = c->udp_tport = -1; c->tcp_sfd = -1; pa_log_error("RTSP control channel closed (disconnected)"); @@ -1474,6 +1507,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot c->udp_sfd = -1; c->udp_cfd = -1; c->udp_tfd = -1; + c->udp_tport = -1; c->secret = NULL; if (c->encryption != PA_RAOP_ENCRYPTION_NONE) @@ -1856,3 +1890,28 @@ void pa_raop_client_set_state_callback(pa_raop_client *c, pa_raop_client_state_c c->state_callback = callback; c->state_userdata = userdata; } + +void pa_raop_client_set_tport(pa_raop_client *c, int udp_tport) { + pa_assert(c); + + if (c->udp_tport < 0) { + c->udp_tport = udp_tport; + if ((c->udp_tfd = connect_udp_socket(c, c->udp_tfd, udp_tport)) <= 0) { + pa_log_error("RAOP: Error while connecting the UDP timing port"); + } + } +} + +void pa_raop_client_send_progress (pa_raop_client *c){ + char *param; + + pa_assert(c); + + param = pa_sprintf_malloc("progress: %s/%s/%s\r\n", "0","0","0"); + /* We just hit and hope, cannot wait for the callback. */ + if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp)) + pa_rtsp_setparameter(c->rtsp, param); + + pa_xfree(param); + +} diff --git a/src/modules/raop/raop-client.h b/src/modules/raop/raop-client.h index faec01e65..aab22d98d 100644 --- a/src/modules/raop/raop-client.h +++ b/src/modules/raop/raop-client.h @@ -82,5 +82,7 @@ ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, 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); +void pa_raop_client_set_tport(pa_raop_client *c, int udp_tport); +void pa_raop_client_send_progress (pa_raop_client *c); #endif diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 9fd386abe..9c418d5f8 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -71,6 +71,7 @@ struct pa_rtsp_client { char *last_header; pa_strbuf *header_buffer; pa_headerlist* response_headers; + int content_length; char *localip; char *url; @@ -146,7 +147,7 @@ static void headers_read(pa_rtsp_client *c) { pa_assert(c->callback); /* Deal with a SETUP response */ - if (STATE_SETUP == c->state) { + if (STATE_SETUP == c->state && c->status == STATUS_OK) { const char* token_state = NULL; const char* pc = NULL; c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session")); @@ -187,6 +188,12 @@ static void headers_read(pa_rtsp_client *c) { c->callback(c, c->state, c->status, c->response_headers, c->userdata); } +static void stream_callback(pa_ioline *line, void *userdata) { + pa_rtsp_client *c = userdata; + + headers_read(c); +} + static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_rtsp_client *c = userdata; char *delimpos; @@ -218,6 +225,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { c->status = STATUS_OK; c->waiting = 0; + c->content_length = 0; goto exit; } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { if (c->response_headers) @@ -227,6 +235,14 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { c->status = STATUS_UNAUTHORIZED; c->waiting = 0; goto exit; + } else if (c->waiting && pa_streq(s2, "RTSP/1.0 403 Forbidden")) { + if (c->response_headers) + pa_headerlist_free(c->response_headers); + c->response_headers = pa_headerlist_new(); + + c->status = STATUS_FORBIDDEN; + c->waiting = 0; + goto exit; } else if (c->waiting) { pa_log_warn("Unexpected/Unhandled response: %s", s2); @@ -253,7 +269,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } pa_log_debug("Full response received. Dispatching"); - headers_read(c); + + if (c->content_length!=0) { + pa_ioline_set_streamcallback(line, stream_callback, c->content_length, c); + } else { + headers_read(c); + } + goto exit; } @@ -274,6 +296,8 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { /* This is not a continuation header so let's dump the full header/value into our proplist */ pa_headerlist_puts(c->response_headers, c->last_header, tmp); + if (pa_headerlist_gets(c->response_headers, "Content-Length")) + pa_atoi(pa_headerlist_gets(c->response_headers, "Content-Length"), &c->content_length); pa_xfree(tmp); pa_xfree(c->last_header); c->last_header = NULL; @@ -537,6 +561,22 @@ int pa_rtsp_options(pa_rtsp_client *c) { return rv; } +int pa_rtsp_auth_setup(pa_rtsp_client *c, const char* key) { + char *url; + int rv; + + pa_assert(c); + + url = c->url; + c->state = STATE_AUTH_SETUP; + + c->url = (char *)"/auth-setup"; + rv = rtsp_exec(c, "POST", "application/octet-stream", key, 1, NULL); + + c->url = url; + return rv; +} + int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) { int rv; diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index 259308581..ee3e83615 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -37,6 +37,7 @@ typedef enum pa_rtsp_state { STATE_OPTIONS, STATE_ANNOUNCE, STATE_SETUP, + STATE_AUTH_SETUP, STATE_RECORD, STATE_SET_PARAMETER, STATE_FLUSH, @@ -48,6 +49,7 @@ typedef enum pa_rtsp_status { STATUS_OK = 200, STATUS_BAD_REQUEST = 400, STATUS_UNAUTHORIZED = 401, + STATUS_FORBIDDEN = 403, STATUS_NO_RESPONSE = 444, STATUS_INTERNAL_ERROR = 500 } pa_rtsp_status_t; @@ -79,5 +81,6 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime); int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param); int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime); int pa_rtsp_teardown(pa_rtsp_client *c); +int pa_rtsp_auth_setup(pa_rtsp_client *c, const char* auth_key); #endif From e8c459b4f7c8596857a925643181c28589d99cad Mon Sep 17 00:00:00 2001 From: ckdo Date: Tue, 16 Feb 2021 20:19:30 +0200 Subject: [PATCH 3/4] raop: set initial volume before sending RTP packets --- src/modules/raop/raop-client.c | 15 ++++++++++ src/modules/raop/raop-client.h | 2 ++ src/modules/raop/raop-sink.c | 52 ++++++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c index 18f8626dc..90094c70f 100644 --- a/src/modules/raop/raop-client.c +++ b/src/modules/raop/raop-client.c @@ -130,6 +130,8 @@ struct pa_raop_client { pa_raop_client_state_cb_t state_callback; void *state_userdata; + + pa_volume_t initial_volume; }; /* Audio TCP packet header [16x8] (cf. rfc4571): @@ -1200,6 +1202,13 @@ connect_finish: case STATE_SET_PARAMETER: { pa_log_debug("RAOP: SET_PARAMETER"); + if (c->initial_volume != 0){ + c->initial_volume = 0; + // We just have set initial volume, so raise PA_RAOP_VOLUME_INIT to chain + if (c->state_callback) + c->state_callback((int) PA_RAOP_VOLUME_INIT, c->state_userdata); + } + break; } @@ -1251,6 +1260,7 @@ connect_finish: /* Polling sockets will be closed by sink */ c->udp_cfd = c->udp_tfd = c->udp_tport = -1; c->tcp_sfd = -1; + c->initial_volume = 0; pa_log_error("RTSP control channel closed (disconnected)"); @@ -1508,6 +1518,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot c->udp_cfd = -1; c->udp_tfd = -1; c->udp_tport = -1; + c->initial_volume = 0; c->secret = NULL; if (c->encryption != PA_RAOP_ENCRYPTION_NONE) @@ -1691,6 +1702,10 @@ int pa_raop_client_stream(pa_raop_client *c) { return rv; } +void pa_raop_client_set_initial_volume(pa_raop_client *c, pa_volume_t initial_volume) { + c->initial_volume = initial_volume; +} + int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) { char *param; int rv = 0; diff --git a/src/modules/raop/raop-client.h b/src/modules/raop/raop-client.h index aab22d98d..3a98d61a1 100644 --- a/src/modules/raop/raop-client.h +++ b/src/modules/raop/raop-client.h @@ -53,6 +53,7 @@ typedef enum pa_raop_state { PA_RAOP_AUTHENTICATED, PA_RAOP_CONNECTED, PA_RAOP_RECORDING, + PA_RAOP_VOLUME_INIT, PA_RAOP_DISCONNECTED } pa_raop_state_t; @@ -83,6 +84,7 @@ ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, 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); void pa_raop_client_set_tport(pa_raop_client *c, int udp_tport); +void pa_raop_client_set_initial_volume(pa_raop_client *c, pa_volume_t initial_volume); void pa_raop_client_send_progress (pa_raop_client *c); #endif diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c index 4f62b29ad..ed487bb2e 100644 --- a/src/modules/raop/raop-sink.c +++ b/src/modules/raop/raop-sink.c @@ -122,6 +122,8 @@ static void userdata_free(struct userdata *u); static void sink_set_volume_cb(pa_sink *s); +static pa_volume_t pa_raop_sink_get_hw_volume(pa_sink *s); + static void raop_state_cb(pa_raop_state_t state, void *userdata) { struct userdata *u = userdata; @@ -213,14 +215,18 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse } case PA_RAOP_CONNECTED: { + pa_volume_t initial_volume; + pa_assert(!u->rtpoll_item); u->oob = pa_raop_client_register_pollfd(u->raop, u->rtpoll, &u->rtpoll_item); + initial_volume = pa_raop_sink_get_hw_volume(u->sink); + pa_raop_client_set_initial_volume(u->raop, initial_volume); return 0; } - case PA_RAOP_RECORDING: { + case PA_RAOP_VOLUME_INIT: { pa_usec_t now; now = pa_rtclock_now(); @@ -234,13 +240,18 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_rtpoll_set_timer_disabled(u->rtpoll); pa_raop_client_flush(u->raop); } else { - /* Set the initial volume */ - sink_set_volume_cb(u->sink); - pa_sink_process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, data, offset, chunk); + pa_raop_client_send_progress(u->raop); } return 0; } + case PA_RAOP_RECORDING: { + /* Set the initial volume */ + sink_set_volume_cb(u->sink); + pa_sink_process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, data, offset, chunk); + + return 0; + } case PA_RAOP_INVALID_STATE: case PA_RAOP_DISCONNECTED: { @@ -369,6 +380,25 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, return 0; } +static pa_volume_t pa_raop_sink_get_hw_volume(pa_sink *s){ + struct userdata *u = s->userdata; + pa_volume_t v, v_orig; + + pa_assert(u); + + /* 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); + + return v; +} + static void sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; pa_cvolume hw; @@ -494,8 +524,20 @@ static void thread_func(void *userdata) { goto fail; } if (pollfd->revents & pollfd->events) { + struct sockaddr_in srcaddr; + socklen_t addrlen; + pollfd->revents = 0; - read = pa_read(pollfd->fd, packet, sizeof(packet), NULL); + // read = pa_read(pollfd->fd, packet, sizeof(packet), NULL); + // Newest Airplay devices does not provide response to SETUP request if we do not respond + // to timing request packets immediatly after the setup request + // To do this we use the source port of incoming packets + // TBD: Code rework (move this in raop client?) + Ipv6 Support + addrlen = sizeof(struct sockaddr_in); + read = recvfrom(pollfd->fd, packet, sizeof(packet), 0, (struct sockaddr *)&srcaddr, &addrlen); + + pa_raop_client_set_tport(u->raop, htons(srcaddr.sin_port)); + pa_log_debug("Source: %d", htons(srcaddr.sin_port)); pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read); if (pa_raop_client_is_timing_fd(u->raop, pollfd->fd)) { last_timing = pa_rtclock_now(); From 1f121979df5153fe788fdfb08143b37b4c9c3864 Mon Sep 17 00:00:00 2001 From: ckdo Date: Sat, 13 Jul 2019 20:55:30 +0200 Subject: [PATCH 4/4] raop: Improve volume management --- src/modules/raop/raop-sink.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c index ed487bb2e..14c299c0c 100644 --- a/src/modules/raop/raop-sink.c +++ b/src/modules/raop/raop-sink.c @@ -424,8 +424,12 @@ static void sink_set_volume_cb(pa_sink *s) { /* 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); + /* Perform any software manipulation of the volume needed. + * Given our hw volume as a reference, soft volume is applied only if channel volumes are different each other + * so that we keep volume control without latency in the most common cases + * Scaling real volume keep relative volume between channels */ + s->soft_volume = s->real_volume; + pa_cvolume_scale(&s->soft_volume, PA_VOLUME_NORM); 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));