Arun Raghavan wrote: > On Sun, 2016-01-31 at 22:16 -0600, Hajime Fujita wrote: >> From: Martin Blanchard <tchaik at gmx.com> >> >> RAOP authentication (password) is based on BA and DA HTTP authentication >> schemes. This patch adds the RSTP client the ability to specify the caller >> of server response status. Tracking the '401 Unauthorized' status allow >> the RAOP client to respond the server challenge authentication request. >> This patch adds the core implementation but does not introduce the >> authentication scheme in the RAOP connection process yet. >> --- >> src/modules/raop/raop_client.c | 245 +++++- >> src/modules/raop/raop_client.c.orig | 1495 +++++++++++++++++++++++++++++++++++ > > Please remove this file from this commit rather than adding it and > removing it later. Oops. Good catch. Thank you, Hajime > > -- Arun > >> src/modules/raop/raop_client.h | 4 + >> src/modules/rtp/rtsp_client.c | 83 +- >> src/modules/rtp/rtsp_client.h | 26 +- >> 5 files changed, 1810 insertions(+), 43 deletions(-) >> create mode 100644 src/modules/raop/raop_client.c.orig >> >> diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c >> index a2ec91e..1323cd0 100644 >> --- a/src/modules/raop/raop_client.c >> +++ b/src/modules/raop/raop_client.c >> @@ -65,6 +65,9 @@ >> #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 >> @@ -85,13 +88,15 @@ struct pa_raop_client { >> pa_core *core; >> char *host; >> uint16_t port; >> - char *sid; >> pa_rtsp_client *rtsp; >> - pa_raop_protocol_t protocol; >> + 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; >> >> @@ -125,6 +130,9 @@ struct pa_raop_client { >> 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; >> >> @@ -576,7 +584,7 @@ static void do_rtsp_announce(pa_raop_client *c) { >> pa_xfree(sdp); >> } >> >> -static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { >> +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); >> @@ -675,12 +683,13 @@ static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist >> } >> } >> >> -static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) { >> +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: { >> @@ -982,6 +991,178 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist >> } >> } >> >> +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; >> @@ -1047,15 +1228,40 @@ void pa_raop_client_free(pa_raop_client *c) { >> >> 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); >> - pa_xfree(c->host); >> + 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 { >> @@ -1074,7 +1280,7 @@ int pa_raop_client_connect(pa_raop_client *c) { >> 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;)"); >> + 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)); >> @@ -1116,6 +1322,17 @@ int pa_raop_client_teardown(pa_raop_client *c) { >> 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; >> >> @@ -1147,7 +1364,6 @@ int pa_raop_client_udp_stream(pa_raop_client *c) { >> if (!c->is_recording) { >> c->udp_first_packet = true; >> c->udp_sync_count = 0; >> - >> c->is_recording = true; >> } >> >> @@ -1326,6 +1542,14 @@ ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *blo >> 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; >> @@ -1469,12 +1693,11 @@ void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_cl >> c->tcp_closed_userdata = userdata; >> } >> >> -void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { >> +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->encryption = encryption; >> - if (c->encryption) >> - c->aes = pa_raop_secret_new(); >> + 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) { >> diff --git a/src/modules/raop/raop_client.c.orig b/src/modules/raop/raop_client.c.orig >> new file mode 100644 >> index 0000000..56f4ccf >> --- /dev/null >> +++ b/src/modules/raop/raop_client.c.orig >> @@ -0,0 +1,1495 @@ >> +/*** >> + 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, write to the Free Software >> + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 >> + USA. >> +***/ >> + >> +#ifdef HAVE_CONFIG_H >> +#include >> +#endif >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#ifdef HAVE_SYS_FILIO_H >> +#include >> +#endif >> + >> +#include >> +#include >> +#include >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 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; >> + char *sid; >> + pa_rtsp_client *rtsp; >> + pa_raop_protocol_t protocol; >> + >> + uint8_t jack_type; >> + uint8_t jack_status; >> + >> + 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_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, 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_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_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_raop_base64_encode(&rand, 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; >> + } >> + } >> +} >> + >> +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 || a.type == PA_PARSED_ADDRESS_UNIX) >> + 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 = pa_xstrdup(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->aes) >> + pa_raop_secret_free(c->aes); >> + if (c->rtsp) >> + pa_rtsp_client_free(c->rtsp); >> + pa_xfree(c->host); >> + >> + pa_xfree(c); >> +} >> + >> +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, "iTunes/7.6.2 (Windows; N;)"); >> + >> + /* 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_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; >> +} >> + >> +/* 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_set_encryption(pa_raop_client *c, int encryption) { >> + pa_assert(c); >> + >> + c->encryption = encryption; >> + if (c->encryption) >> + c->aes = pa_raop_secret_new(); >> +} >> + >> +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 >> index 5d0bb14..e208349 100644 >> --- a/src/modules/raop/raop_client.h >> +++ b/src/modules/raop/raop_client.h >> @@ -37,10 +37,12 @@ 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); >> @@ -61,6 +63,8 @@ void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t call >> 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); >> diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c >> index 198417e..c6d9ac6 100644 >> --- a/src/modules/rtp/rtsp_client.c >> +++ b/src/modules/rtp/rtsp_client.c >> @@ -59,6 +59,7 @@ struct pa_rtsp_client { >> const char *useragent; >> >> pa_rtsp_state state; >> + pa_rtsp_status status; >> uint8_t waiting; >> >> pa_headerlist* headers; >> @@ -119,8 +120,8 @@ void pa_rtsp_client_free(pa_rtsp_client *c) { >> } >> >> static void headers_read(pa_rtsp_client *c) { >> - char* token; >> char delimiters[] = ";"; >> + char* token; >> >> pa_assert(c); >> pa_assert(c->response_headers); >> @@ -165,14 +166,14 @@ static void headers_read(pa_rtsp_client *c) { >> } >> >> /* Call our callback */ >> - c->callback(c, c->state, c->response_headers, c->userdata); >> + c->callback(c, c->state, c->status, c->response_headers, c->userdata); >> } >> >> static void line_callback(pa_ioline *line, const char *s, void *userdata) { >> + pa_rtsp_client *c = userdata; >> char *delimpos; >> char *s2, *s2p; >> >> - pa_rtsp_client *c = userdata; >> pa_assert(line); >> pa_assert(c); >> pa_assert(c->callback); >> @@ -180,7 +181,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >> if (!s) { >> /* Keep the ioline/iochannel open as they will be freed automatically */ >> c->ioline = NULL; >> - c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); >> + c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata); >> return; >> } >> >> @@ -191,17 +192,35 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >> *s2p = '\0'; >> s2p -= 1; >> } >> + >> if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) { >> + if (c->response_headers) >> + pa_headerlist_free(c->response_headers); >> + c->response_headers = pa_headerlist_new(); >> + >> + c->status = STATUS_OK; >> c->waiting = 0; >> + goto exit; >> + } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { >> if (c->response_headers) >> pa_headerlist_free(c->response_headers); >> c->response_headers = pa_headerlist_new(); >> + >> + c->status = STATUS_UNAUTHORIZED; >> + c->waiting = 0; >> goto exit; >> - } >> - if (c->waiting) { >> - pa_log_warn("Unexpected response: %s", s2); >> + } else if (c->waiting) { >> + pa_log_warn("Unexpected/Unhandled response: %s", s2); >> + >> + if (pa_streq(s2, "RTSP/1.0 400 Bad Request")) >> + c->status = STATUS_BAD_REQUEST; >> + else if (pa_streq(s2, "RTSP/1.0 500 Internal Server Error")) >> + c->status = STATUS_INTERNAL_ERROR; >> + else >> + c->status = STATUS_NO_RESPONSE; >> goto exit; >> } >> + >> if (!strlen(s2)) { >> /* End of headers */ >> /* We will have a header left from our looping iteration, so add it in :) */ >> @@ -228,7 +247,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >> if (c->last_header && ' ' == s2[0]) { >> pa_assert(c->header_buffer); >> >> - /* Add this line to the buffer (sans the space. */ >> + /* Add this line to the buffer (sans the space) */ >> pa_strbuf_puts(c->header_buffer, &(s2[1])); >> goto exit; >> } >> @@ -270,6 +289,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >> >> /* Save the header name */ >> c->last_header = pa_xstrdup(s2); >> + >> exit: >> pa_xfree(s2); >> } >> @@ -317,7 +337,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata >> pa_log_debug("Established RTSP connection from local ip %s", c->localip); >> >> if (c->callback) >> - c->callback(c, c->state, NULL, c->userdata); >> + c->callback(c, c->state, STATUS_OK, NULL, c->userdata); >> } >> >> int pa_rtsp_connect(pa_rtsp_client *c) { >> @@ -336,6 +356,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) { >> pa_socket_client_set_callback(c->sc, on_connection, c); >> c->waiting = 1; >> c->state = STATE_CONNECT; >> + c->status = STATUS_NO_RESPONSE; >> return 0; >> } >> >> @@ -368,12 +389,25 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) { >> return c->rtp_port; >> } >> >> +bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { >> + pa_assert(c); >> + >> + return c->url != NULL && c->ioline != NULL; >> +} >> + >> void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) { >> pa_assert(c); >> >> c->url = pa_xstrdup(url); >> } >> >> +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) { >> + pa_assert(c); >> + pa_assert(key); >> + >> + return pa_headerlist_contains(c->headers, key); >> +} >> + >> void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { >> pa_assert(c); >> pa_assert(key); >> @@ -382,17 +416,18 @@ void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { >> pa_headerlist_puts(c->headers, key, value); >> } >> >> -void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { >> +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key) { >> pa_assert(c); >> pa_assert(key); >> >> - pa_headerlist_remove(c->headers, key); >> + return pa_headerlist_gets(c->headers, key); >> } >> >> -bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { >> +void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { >> pa_assert(c); >> + pa_assert(key); >> >> - return c->url != NULL && c->ioline != NULL; >> + pa_headerlist_remove(c->headers, key); >> } >> >> static int rtsp_exec(pa_rtsp_client *c, const char *cmd, >> @@ -527,17 +562,6 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) { >> return rv; >> } >> >> -int pa_rtsp_teardown(pa_rtsp_client *c) { >> - int rv; >> - >> - pa_assert(c); >> - >> - c->state = STATE_TEARDOWN; >> - rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); >> - >> - return rv; >> -} >> - >> int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) { >> int rv; >> >> @@ -570,3 +594,14 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { >> pa_headerlist_free(headers); >> return rv; >> } >> + >> +int pa_rtsp_teardown(pa_rtsp_client *c) { >> + int rv; >> + >> + pa_assert(c); >> + >> + c->state = STATE_TEARDOWN; >> + rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); >> + >> + return rv; >> +} >> diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h >> index abc60ee..4a35851 100644 >> --- a/src/modules/rtp/rtsp_client.h >> +++ b/src/modules/rtp/rtsp_client.h >> @@ -31,43 +31,53 @@ >> #include "headerlist.h" >> >> typedef struct pa_rtsp_client pa_rtsp_client; >> + >> typedef enum { >> STATE_CONNECT, >> STATE_OPTIONS, >> STATE_ANNOUNCE, >> STATE_SETUP, >> STATE_RECORD, >> + STATE_SET_PARAMETER, >> STATE_FLUSH, >> STATE_TEARDOWN, >> - STATE_SET_PARAMETER, >> STATE_DISCONNECTED >> } pa_rtsp_state; >> -typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist *hl, void *userdata); >> + >> +typedef enum { >> + STATUS_OK = 200, >> + STATUS_BAD_REQUEST = 400, >> + STATUS_UNAUTHORIZED = 401, >> + STATUS_NO_RESPONSE = 444, >> + STATUS_INTERNAL_ERROR = 500 >> +} pa_rtsp_status; >> + >> +typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_rtsp_status 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); >> >> int pa_rtsp_connect(pa_rtsp_client *c); >> void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); >> - >> void pa_rtsp_disconnect(pa_rtsp_client *c); >> >> const char* pa_rtsp_localip(pa_rtsp_client *c); >> uint32_t pa_rtsp_serverport(pa_rtsp_client *c); >> +bool pa_rtsp_exec_ready(const pa_rtsp_client *c); >> + >> void pa_rtsp_set_url(pa_rtsp_client *c, const char *url); >> + >> +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key); >> void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value); >> +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key); >> void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key); >> >> -bool pa_rtsp_exec_ready(const pa_rtsp_client *c); >> - >> int pa_rtsp_options(pa_rtsp_client *c); >> int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp); >> - >> int pa_rtsp_setup(pa_rtsp_client *c, const char *transport); >> int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime); >> -int pa_rtsp_teardown(pa_rtsp_client *c); >> - >> 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); >> >> #endif > _______________________________________________ > pulseaudio-discuss mailing list > pulseaudio-discuss at lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >