Merge branch 'airplay2' into 'master'

Airplay2

See merge request pulseaudio/pulseaudio!505
This commit is contained in:
Cedric GESTES 2025-10-02 12:02:56 +00:00
commit f7944cbbb5
7 changed files with 336 additions and 121 deletions

View file

@ -112,6 +112,7 @@ struct pa_raop_client {
int udp_sfd; int udp_sfd;
int udp_cfd; int udp_cfd;
int udp_tfd; int udp_tfd;
int udp_tport;
pa_raop_packet_buffer *pbuf; pa_raop_packet_buffer *pbuf;
@ -129,6 +130,8 @@ struct pa_raop_client {
pa_raop_client_state_cb_t state_callback; pa_raop_client_state_cb_t state_callback;
void *state_userdata; void *state_userdata;
pa_volume_t initial_volume;
}; };
/* Audio TCP packet header [16x8] (cf. rfc4571): /* Audio TCP packet header [16x8] (cf. rfc4571):
@ -884,7 +887,7 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
char *url; char *url;
int ipv; int ipv;
pa_log_debug("RAOP: CONNECTED"); pa_log_debug("RAOP: CONNECT");
ip = pa_rtsp_localip(c->rtsp); ip = pa_rtsp_localip(c->rtsp);
if (pa_is_ip6_address(ip)) { if (pa_is_ip6_address(ip)) {
@ -966,28 +969,35 @@ connect_finish:
break; break;
} }
case STATE_ANNOUNCE: { case STATE_ANNOUNCE:
uint16_t cport = DEFAULT_UDP_CONTROL_PORT; case STATE_AUTH_SETUP: {
uint16_t tport = DEFAULT_UDP_TIMING_PORT;
char *trs = NULL; 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) { if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
trs = pa_sprintf_malloc( trs = pa_sprintf_malloc(
"RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
c->udp_cfd = open_bind_udp_socket(c, &cport); if (state == STATE_ANNOUNCE) {
c->udp_tfd = open_bind_udp_socket(c, &tport); c->udp_cfd = open_bind_udp_socket(c, &cport);
if (c->udp_cfd < 0 || c->udp_tfd < 0) c->udp_tfd = open_bind_udp_socket(c, &tport);
goto annonce_error; if (c->udp_cfd < 0 || c->udp_tfd < 0)
goto annonce_error;
}
trs = pa_sprintf_malloc( trs = pa_sprintf_malloc(
"RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;" "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;"
"control_port=%d;timing_port=%d", "control_port=%d;timing_port=%d",
cport, tport); 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_rtsp_setup(c->rtsp, trs);
pa_xfree(trs); pa_xfree(trs);
@ -1011,128 +1021,153 @@ connect_finish:
} }
case STATE_SETUP: { case STATE_SETUP: {
pa_socket_client *sc = NULL; if (status == STATUS_FORBIDDEN) {
uint32_t sport = DEFAULT_UDP_AUDIO_PORT; struct {
uint32_t cport =0, tport = 0; uint32_t ci1;
char *ajs, *token, *pc, *trs; uint32_t ci2;
const char *token_state = NULL; } rci;
char delimiters[] = ";"; 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")); // 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 (ajs) { if (pa_rtsp_auth_setup(
c->jack_type = JACK_TYPE_ANALOG; c->rtsp,
c->jack_status = JACK_STATUS_DISCONNECTED; "\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) {
while ((token = pa_split(ajs, delimiters, &token_state))) { pa_log_error("RAOP: Unable to POST auth-setup");
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 { break;
pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
} }
sport = pa_rtsp_serverport(c->rtsp); if (status == STATUS_OK) {
if (sport <= 0) pa_socket_client *sc = NULL;
goto setup_error; 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; pa_log_debug("RAOP: SETUP");
if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
if (!(sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, sport)))
goto setup_error;
pa_socket_client_ref(sc); ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
pa_socket_client_set_callback(sc, tcp_connection_cb, c);
pa_socket_client_unref(sc); if (ajs) {
sc = NULL; c->jack_type = JACK_TYPE_ANALOG;
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { c->jack_status = JACK_STATUS_DISCONNECTED;
trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
if (trs) { while ((token = pa_split(ajs, delimiters, &token_state))) {
/* Now parse out the server port component of the response. */
while ((token = pa_split(trs, delimiters, &token_state))) {
if ((pc = strstr(token, "="))) { if ((pc = strstr(token, "="))) {
*pc = 0; *pc = 0;
if (pa_streq(token, "control_port")) { if (pa_streq(token, "type") && pa_streq(pc + 1, "digital"))
if (pa_atou(pc + 1, &cport) < 0) c->jack_type = JACK_TYPE_DIGITAL;
goto setup_error_parse; } else {
} if (pa_streq(token, "connected"))
if (pa_streq(token, "timing_port")) { c->jack_status = JACK_STATUS_CONNECTED;
if (pa_atou(pc + 1, &tport) < 0)
goto setup_error_parse;
}
*pc = '=';
} }
pa_xfree(token); pa_xfree(token);
} }
pa_xfree(trs);
} else { } 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; goto setup_error;
if ((c->udp_sfd = connect_udp_socket(c, -1, sport)) <= 0) token_state = NULL;
goto setup_error; if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
if ((c->udp_cfd = connect_udp_socket(c, c->udp_cfd, cport)) <= 0) if (!(sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, sport)))
goto setup_error; 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_socket_client_ref(sc);
pa_socket_client_set_callback(sc, tcp_connection_cb, c);
/* Send an initial UDP packet so a connection tracking firewall pa_socket_client_unref(sc);
* knows the src_ip:src_port <-> dest_ip:dest_port relation sc = NULL;
* and accepts the incoming timing packets. } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
*/ trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
send_initial_udp_timing_packet(c);
pa_log_debug("Sent initial timing packet to UDP port %d", tport); 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) 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; break;
} }
@ -1167,6 +1202,13 @@ connect_finish:
case STATE_SET_PARAMETER: { case STATE_SET_PARAMETER: {
pa_log_debug("RAOP: 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; break;
} }
@ -1188,7 +1230,7 @@ connect_finish:
c->udp_sfd = -1; c->udp_sfd = -1;
/* Polling sockets will be closed by sink */ /* 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; c->tcp_sfd = -1;
pa_rtsp_client_free(c->rtsp); pa_rtsp_client_free(c->rtsp);
@ -1216,8 +1258,9 @@ connect_finish:
c->udp_sfd = -1; c->udp_sfd = -1;
/* Polling sockets will be closed by sink */ /* 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; c->tcp_sfd = -1;
c->initial_volume = 0;
pa_log_error("RTSP control channel closed (disconnected)"); pa_log_error("RTSP control channel closed (disconnected)");
@ -1474,6 +1517,8 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
c->udp_sfd = -1; c->udp_sfd = -1;
c->udp_cfd = -1; c->udp_cfd = -1;
c->udp_tfd = -1; c->udp_tfd = -1;
c->udp_tport = -1;
c->initial_volume = 0;
c->secret = NULL; c->secret = NULL;
if (c->encryption != PA_RAOP_ENCRYPTION_NONE) if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
@ -1657,6 +1702,10 @@ int pa_raop_client_stream(pa_raop_client *c) {
return rv; 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) { int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
char *param; char *param;
int rv = 0; int rv = 0;
@ -1856,3 +1905,28 @@ void pa_raop_client_set_state_callback(pa_raop_client *c, pa_raop_client_state_c
c->state_callback = callback; c->state_callback = callback;
c->state_userdata = userdata; 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);
}

View file

@ -53,6 +53,7 @@ typedef enum pa_raop_state {
PA_RAOP_AUTHENTICATED, PA_RAOP_AUTHENTICATED,
PA_RAOP_CONNECTED, PA_RAOP_CONNECTED,
PA_RAOP_RECORDING, PA_RAOP_RECORDING,
PA_RAOP_VOLUME_INIT,
PA_RAOP_DISCONNECTED PA_RAOP_DISCONNECTED
} pa_raop_state_t; } pa_raop_state_t;
@ -82,5 +83,8 @@ 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); 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_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 #endif

View file

@ -122,6 +122,8 @@ static void userdata_free(struct userdata *u);
static void sink_set_volume_cb(pa_sink *s); 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) { static void raop_state_cb(pa_raop_state_t state, void *userdata) {
struct userdata *u = 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: { case PA_RAOP_CONNECTED: {
pa_volume_t initial_volume;
pa_assert(!u->rtpoll_item); pa_assert(!u->rtpoll_item);
u->oob = pa_raop_client_register_pollfd(u->raop, u->rtpoll, &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; return 0;
} }
case PA_RAOP_RECORDING: { case PA_RAOP_VOLUME_INIT: {
pa_usec_t now; pa_usec_t now;
now = pa_rtclock_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_rtpoll_set_timer_disabled(u->rtpoll);
pa_raop_client_flush(u->raop); pa_raop_client_flush(u->raop);
} else { } else {
/* Set the initial volume */ pa_raop_client_send_progress(u->raop);
sink_set_volume_cb(u->sink);
pa_sink_process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, data, offset, chunk);
} }
return 0; 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_INVALID_STATE:
case PA_RAOP_DISCONNECTED: { 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; 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) { static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u = s->userdata; struct userdata *u = s->userdata;
pa_cvolume hw; pa_cvolume hw;
@ -394,8 +424,12 @@ static void sink_set_volume_cb(pa_sink *s) {
/* Create a pa_cvolume version of our single value. */ /* Create a pa_cvolume version of our single value. */
pa_cvolume_set(&hw, s->sample_spec.channels, v); pa_cvolume_set(&hw, s->sample_spec.channels, v);
/* Perform any software manipulation of the volume needed. */ /* Perform any software manipulation of the volume needed.
pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw); * 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("Requested volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &s->real_volume, &s->channel_map, false));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &hw, &s->channel_map, false)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint_verbose(t, sizeof(t), &hw, &s->channel_map, false));
@ -494,8 +528,20 @@ static void thread_func(void *userdata) {
goto fail; goto fail;
} }
if (pollfd->revents & pollfd->events) { if (pollfd->revents & pollfd->events) {
struct sockaddr_in srcaddr;
socklen_t addrlen;
pollfd->revents = 0; 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); pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read);
if (pa_raop_client_is_timing_fd(u->raop, pollfd->fd)) { if (pa_raop_client_is_timing_fd(u->raop, pollfd->fd)) {
last_timing = pa_rtclock_now(); last_timing = pa_rtclock_now();

View file

@ -71,6 +71,7 @@ struct pa_rtsp_client {
char *last_header; char *last_header;
pa_strbuf *header_buffer; pa_strbuf *header_buffer;
pa_headerlist* response_headers; pa_headerlist* response_headers;
int content_length;
char *localip; char *localip;
char *url; char *url;
@ -146,7 +147,7 @@ static void headers_read(pa_rtsp_client *c) {
pa_assert(c->callback); pa_assert(c->callback);
/* Deal with a SETUP response */ /* 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* token_state = NULL;
const char* pc = NULL; const char* pc = NULL;
c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session")); 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); 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) { static void line_callback(pa_ioline *line, const char *s, void *userdata) {
pa_rtsp_client *c = userdata; pa_rtsp_client *c = userdata;
char *delimpos; char *delimpos;
@ -218,6 +225,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
c->status = STATUS_OK; c->status = STATUS_OK;
c->waiting = 0; c->waiting = 0;
c->content_length = 0;
goto exit; goto exit;
} else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
if (c->response_headers) 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->status = STATUS_UNAUTHORIZED;
c->waiting = 0; c->waiting = 0;
goto exit; 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) { } else if (c->waiting) {
pa_log_warn("Unexpected/Unhandled response: %s", s2); 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"); 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; 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 /* This is not a continuation header so let's dump the full
header/value into our proplist */ header/value into our proplist */
pa_headerlist_puts(c->response_headers, c->last_header, tmp); 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(tmp);
pa_xfree(c->last_header); pa_xfree(c->last_header);
c->last_header = NULL; c->last_header = NULL;
@ -537,6 +561,22 @@ int pa_rtsp_options(pa_rtsp_client *c) {
return rv; 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 pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) {
int rv; int rv;

View file

@ -37,6 +37,7 @@ typedef enum pa_rtsp_state {
STATE_OPTIONS, STATE_OPTIONS,
STATE_ANNOUNCE, STATE_ANNOUNCE,
STATE_SETUP, STATE_SETUP,
STATE_AUTH_SETUP,
STATE_RECORD, STATE_RECORD,
STATE_SET_PARAMETER, STATE_SET_PARAMETER,
STATE_FLUSH, STATE_FLUSH,
@ -48,6 +49,7 @@ typedef enum pa_rtsp_status {
STATUS_OK = 200, STATUS_OK = 200,
STATUS_BAD_REQUEST = 400, STATUS_BAD_REQUEST = 400,
STATUS_UNAUTHORIZED = 401, STATUS_UNAUTHORIZED = 401,
STATUS_FORBIDDEN = 403,
STATUS_NO_RESPONSE = 444, STATUS_NO_RESPONSE = 444,
STATUS_INTERNAL_ERROR = 500 STATUS_INTERNAL_ERROR = 500
} pa_rtsp_status_t; } 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_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_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime);
int pa_rtsp_teardown(pa_rtsp_client *c); int pa_rtsp_teardown(pa_rtsp_client *c);
int pa_rtsp_auth_setup(pa_rtsp_client *c, const char* auth_key);
#endif #endif

View file

@ -51,13 +51,16 @@ struct pa_ioline {
size_t wbuf_length, wbuf_index, wbuf_valid_length; size_t wbuf_length, wbuf_index, wbuf_valid_length;
char *rbuf; 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_ioline_cb_t callback;
pa_stream_cb_t streamcallback;
size_t streamcallback_maxbyte;
void *userdata; void *userdata;
pa_ioline_drain_cb_t drain_callback; pa_ioline_drain_cb_t drain_callback;
void *drain_userdata; void *drain_userdata;
void *stream_userdata;
bool dead:1; bool dead:1;
bool defer_close: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->rbuf_length = l->rbuf_index = l->rbuf_valid_length = 0;
l->callback = NULL; l->callback = NULL;
l->streamcallback = NULL;
l->streamcallback_maxbyte = 0;
l->userdata = NULL; l->userdata = NULL;
l->drain_callback = 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; 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) { void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) {
pa_assert(l); pa_assert(l);
pa_assert(PA_REFCNT_VALUE(l) >= 1); 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) if (l->callback)
l->callback(l, pa_strip_nl(p), l->userdata); 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; skip = 0;
} }
@ -285,6 +308,7 @@ static int do_read(pa_ioline *l) {
while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) { while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) {
ssize_t r; ssize_t r;
size_t len; size_t len;
size_t byte_count;
len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; 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; 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 */ // Not in a "else" clause, since we may have enabled block mode in callback called in scan_for_lines
scan_for_lines(l, l->rbuf_valid_length - (size_t) r); 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; return 0;

View file

@ -31,6 +31,7 @@
typedef struct pa_ioline pa_ioline; typedef struct pa_ioline pa_ioline;
typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); 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); typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata);
pa_ioline* pa_ioline_new(pa_iochannel *io); 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 */ /* 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); 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 */ /* 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); void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata);