mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-04 13:29:59 -05:00
Merge branch 'fix-raop' into 'master'
raop: fix auth, reconnect, apple tv compat See merge request pulseaudio/pulseaudio!820
This commit is contained in:
commit
16203c5733
17 changed files with 780 additions and 603 deletions
|
|
@ -49,7 +49,7 @@ struct userdata {
|
||||||
pa_ioline *line;
|
pa_ioline *line;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||||
struct userdata *u = userdata;
|
struct userdata *u = userdata;
|
||||||
pa_module *m = NULL;
|
pa_module *m = NULL;
|
||||||
unsigned devnum;
|
unsigned devnum;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ libraop_sources = [
|
||||||
'raop-crypto.c',
|
'raop-crypto.c',
|
||||||
'raop-packet-buffer.c',
|
'raop-packet-buffer.c',
|
||||||
'raop-sink.c',
|
'raop-sink.c',
|
||||||
'raop-util.c',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
libraop_headers = [
|
libraop_headers = [
|
||||||
|
|
@ -11,7 +10,7 @@ libraop_headers = [
|
||||||
'raop-crypto.h',
|
'raop-crypto.h',
|
||||||
'raop-packet-buffer.h',
|
'raop-packet-buffer.h',
|
||||||
'raop-sink.h',
|
'raop-sink.h',
|
||||||
'raop-util.h',
|
'raop-common.h',
|
||||||
]
|
]
|
||||||
|
|
||||||
# FIXME: meson doesn't support multiple RPATH arguments currently
|
# FIXME: meson doesn't support multiple RPATH arguments currently
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
#include <pulsecore/namereg.h>
|
#include <pulsecore/namereg.h>
|
||||||
#include <pulsecore/avahi-wrap.h>
|
#include <pulsecore/avahi-wrap.h>
|
||||||
|
|
||||||
#include "raop-util.h"
|
#include "raop-common.h"
|
||||||
|
|
||||||
PA_MODULE_AUTHOR("Colin Guthrie");
|
PA_MODULE_AUTHOR("Colin Guthrie");
|
||||||
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
|
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,13 @@
|
||||||
#include <pulsecore/random.h>
|
#include <pulsecore/random.h>
|
||||||
#include <pulsecore/poll.h>
|
#include <pulsecore/poll.h>
|
||||||
|
|
||||||
#include <modules/rtp/rtsp_client.h>
|
#include <modules/rtp/rtsp-client.h>
|
||||||
|
#include <modules/rtp/rtsp-util.h>
|
||||||
|
|
||||||
#include "raop-client.h"
|
#include "raop-client.h"
|
||||||
#include "raop-packet-buffer.h"
|
#include "raop-packet-buffer.h"
|
||||||
#include "raop-crypto.h"
|
#include "raop-crypto.h"
|
||||||
#include "raop-util.h"
|
#include "raop-common.h"
|
||||||
|
|
||||||
#define DEFAULT_RAOP_PORT 5000
|
#define DEFAULT_RAOP_PORT 5000
|
||||||
|
|
||||||
|
|
@ -98,8 +99,10 @@ struct pa_raop_client {
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
pa_rtsp_client *rtsp;
|
pa_rtsp_client *rtsp;
|
||||||
char *sci, *sid;
|
char *sci, *sid;
|
||||||
char *password;
|
const char *password;
|
||||||
|
bool waiting;
|
||||||
bool autoreconnect;
|
bool autoreconnect;
|
||||||
|
bool has_post;
|
||||||
|
|
||||||
pa_raop_protocol_t protocol;
|
pa_raop_protocol_t protocol;
|
||||||
pa_raop_encryption_t encryption;
|
pa_raop_encryption_t encryption;
|
||||||
|
|
@ -113,11 +116,13 @@ struct pa_raop_client {
|
||||||
int udp_cfd;
|
int udp_cfd;
|
||||||
int udp_tfd;
|
int udp_tfd;
|
||||||
|
|
||||||
|
uint16_t udp_tport;
|
||||||
|
|
||||||
pa_raop_packet_buffer *pbuf;
|
pa_raop_packet_buffer *pbuf;
|
||||||
|
|
||||||
uint16_t seq;
|
uint16_t seq;
|
||||||
uint32_t rtptime;
|
uint32_t rtptime;
|
||||||
bool is_recording;
|
bool is_streaming;
|
||||||
uint32_t ssrc;
|
uint32_t ssrc;
|
||||||
|
|
||||||
bool is_first_packet;
|
bool is_first_packet;
|
||||||
|
|
@ -193,19 +198,6 @@ static const uint8_t udp_timing_header[8] = {
|
||||||
0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to trim a given character at the end of a string (no realloc).
|
|
||||||
* @param str Pointer to string
|
|
||||||
* @param rc Character to trim
|
|
||||||
*/
|
|
||||||
static inline void rtrim_char(char *str, char rc) {
|
|
||||||
char *sp = str + strlen(str) - 1;
|
|
||||||
while (sp >= str && *sp == rc) {
|
|
||||||
*sp = '\0';
|
|
||||||
sp -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to convert a timeval to ntp timestamp.
|
* Function to convert a timeval to ntp timestamp.
|
||||||
* @param tv Pointer to the timeval structure
|
* @param tv Pointer to the timeval structure
|
||||||
|
|
@ -584,7 +576,7 @@ static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) {
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
static ssize_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||||
uint8_t payload = 0;
|
uint8_t payload = 0;
|
||||||
uint16_t seq, nbp = 0;
|
uint16_t seq, nbp = 0;
|
||||||
ssize_t written = 0;
|
ssize_t written = 0;
|
||||||
|
|
@ -655,11 +647,22 @@ static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6],
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
static char *get_rtsp_url(pa_raop_client *c) {
|
||||||
|
pa_assert(c->rtsp);
|
||||||
|
pa_assert(c->sid);
|
||||||
|
const char *ip = pa_rtsp_localip(c->rtsp);
|
||||||
|
if (pa_is_ip6_address(ip)) {
|
||||||
|
return pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid);
|
||||||
|
} else {
|
||||||
|
return pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||||
const uint32_t * data = NULL;
|
const uint32_t * data = NULL;
|
||||||
uint8_t payload = 0;
|
uint8_t payload = 0;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
size_t written = 0;
|
ssize_t written = 0;
|
||||||
uint64_t rci = 0;
|
uint64_t rci = 0;
|
||||||
|
|
||||||
/* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
|
/* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
|
||||||
|
|
@ -673,6 +676,10 @@ static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[]
|
||||||
payload = packet[1] ^ 0x80;
|
payload = packet[1] ^ 0x80;
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case PAYLOAD_TIMING_REQUEST:
|
case PAYLOAD_TIMING_REQUEST:
|
||||||
|
if (c->has_post && !c->waiting) {
|
||||||
|
pa_log_debug("Sending keep-alive");
|
||||||
|
pa_rtsp_post(c->rtsp, "/feedback");
|
||||||
|
}
|
||||||
pa_log_debug("Sending timing packet at %" PRIu64 , rci);
|
pa_log_debug("Sending timing packet at %" PRIu64 , rci);
|
||||||
written = send_udp_timing_packet(c, data, rci);
|
written = send_udp_timing_packet(c, data, rci);
|
||||||
break;
|
break;
|
||||||
|
|
@ -798,6 +805,10 @@ static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If the socket queue is full, let's drop packets */
|
||||||
|
pa_make_udp_socket_low_delay(fd);
|
||||||
|
pa_make_fd_nonblock(fd);
|
||||||
|
|
||||||
#ifdef SO_TIMESTAMP
|
#ifdef SO_TIMESTAMP
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
|
||||||
pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
|
pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
|
||||||
|
|
@ -869,32 +880,13 @@ static void tcp_connection_cb(pa_socket_client *sc, pa_iochannel *io, void *user
|
||||||
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
|
static char *get_sdp(pa_raop_client *c) {
|
||||||
pa_raop_client *c = userdata;
|
|
||||||
|
|
||||||
pa_assert(c);
|
|
||||||
pa_assert(rtsp);
|
|
||||||
pa_assert(rtsp == c->rtsp);
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case STATE_CONNECT: {
|
|
||||||
char *key, *iv, *sdp = NULL;
|
char *key, *iv, *sdp = NULL;
|
||||||
int frames = 0;
|
int frames = 0;
|
||||||
const char *ip;
|
const char *ip;
|
||||||
char *url;
|
|
||||||
int ipv;
|
int ipv;
|
||||||
|
|
||||||
pa_log_debug("RAOP: CONNECTED");
|
|
||||||
|
|
||||||
ip = pa_rtsp_localip(c->rtsp);
|
ip = pa_rtsp_localip(c->rtsp);
|
||||||
if (pa_is_ip6_address(ip)) {
|
ipv = pa_is_ip6_address(ip) ? 6 : 4;
|
||||||
ipv = 6;
|
|
||||||
url = pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid);
|
|
||||||
} else {
|
|
||||||
ipv = 4;
|
|
||||||
url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
|
|
||||||
}
|
|
||||||
pa_rtsp_set_url(c->rtsp, url);
|
|
||||||
|
|
||||||
if (c->protocol == PA_RAOP_PROTOCOL_TCP)
|
if (c->protocol == PA_RAOP_PROTOCOL_TCP)
|
||||||
frames = FRAMES_PER_TCP_PACKET;
|
frames = FRAMES_PER_TCP_PACKET;
|
||||||
|
|
@ -924,11 +916,7 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
|
||||||
key = pa_raop_secret_get_key(c->secret);
|
key = pa_raop_secret_get_key(c->secret);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
pa_log("pa_raop_secret_get_key() failed.");
|
pa_log("pa_raop_secret_get_key() failed.");
|
||||||
pa_rtsp_disconnect(rtsp);
|
break;
|
||||||
/* FIXME: This is an unrecoverable failure. We should notify
|
|
||||||
* the pa_raop_client owner so that it could shut itself
|
|
||||||
* down. */
|
|
||||||
goto connect_finish;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iv = pa_raop_secret_get_iv(c->secret);
|
iv = pa_raop_secret_get_iv(c->secret);
|
||||||
|
|
@ -952,48 +940,116 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_rtsp_announce(c->rtsp, sdp);
|
return sdp;
|
||||||
|
}
|
||||||
|
|
||||||
connect_finish:
|
static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
|
||||||
pa_xfree(sdp);
|
pa_raop_client *c = userdata;
|
||||||
|
|
||||||
|
pa_assert(c);
|
||||||
|
pa_assert(rtsp);
|
||||||
|
pa_assert(rtsp == c->rtsp);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case STATE_CONNECT: {
|
||||||
|
char *url, *sdp;
|
||||||
|
|
||||||
|
pa_log_debug("RAOP: CONNECTED");
|
||||||
|
|
||||||
|
url = get_rtsp_url(c);
|
||||||
|
pa_rtsp_set_url(c->rtsp, url);
|
||||||
pa_xfree(url);
|
pa_xfree(url);
|
||||||
|
|
||||||
|
if (!(sdp = get_sdp(c)))
|
||||||
|
goto connect_error;
|
||||||
|
|
||||||
|
/* We might need to re-authenticate, so reset this */
|
||||||
|
c->waiting = false;
|
||||||
|
pa_rtsp_announce(c->rtsp, sdp);
|
||||||
|
pa_xfree(sdp);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
connect_error:
|
||||||
|
pa_log_error("Failed to get RTSP SDP");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case STATE_OPTIONS: {
|
case STATE_OPTIONS: {
|
||||||
pa_log_debug("RAOP: OPTIONS (stream cb)");
|
pa_log_debug("RAOP: OPTIONS (stream cb)");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case STATE_ANNOUNCE: {
|
case STATE_ANNOUNCE: {
|
||||||
uint16_t cport = DEFAULT_UDP_CONTROL_PORT;
|
uint16_t cport = DEFAULT_UDP_CONTROL_PORT;
|
||||||
uint16_t tport = DEFAULT_UDP_TIMING_PORT;
|
uint16_t tport = DEFAULT_UDP_TIMING_PORT;
|
||||||
char *trs = NULL;
|
char *sdp, *trs = NULL;
|
||||||
|
|
||||||
pa_log_debug("RAOP: ANNOUNCE");
|
pa_log_debug("RAOP: ANNOUNCE");
|
||||||
|
|
||||||
if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
|
/* The authentication might need to be refreshed on a reconnect even
|
||||||
|
* though we authenticated in OPTIONS previously */
|
||||||
|
if (STATUS_UNAUTHORIZED == status) {
|
||||||
|
if (c->waiting)
|
||||||
|
goto announce_fail;
|
||||||
|
if (!(sdp = get_sdp(c)))
|
||||||
|
goto announce_error;
|
||||||
|
c->waiting = true;
|
||||||
|
pa_rtsp_announce(c->rtsp, sdp);
|
||||||
|
pa_xfree(sdp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (STATUS_OK != status)
|
||||||
|
goto announce_error;
|
||||||
|
|
||||||
|
switch (c->protocol) {
|
||||||
|
case 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) {
|
break;
|
||||||
|
case PA_RAOP_PROTOCOL_UDP:
|
||||||
c->udp_cfd = open_bind_udp_socket(c, &cport);
|
c->udp_cfd = open_bind_udp_socket(c, &cport);
|
||||||
c->udp_tfd = open_bind_udp_socket(c, &tport);
|
c->udp_tfd = open_bind_udp_socket(c, &tport);
|
||||||
if (c->udp_cfd < 0 || c->udp_tfd < 0)
|
if (c->udp_cfd < 0 || c->udp_tfd < 0)
|
||||||
goto annonce_error;
|
goto announce_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);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pa_assert(trs);
|
||||||
|
|
||||||
|
/* Don't send a keep-alive until we're ready */
|
||||||
|
c->waiting = true;
|
||||||
|
c->udp_tport = 0;
|
||||||
pa_rtsp_setup(c->rtsp, trs);
|
pa_rtsp_setup(c->rtsp, trs);
|
||||||
|
|
||||||
|
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
||||||
|
}
|
||||||
|
|
||||||
pa_xfree(trs);
|
pa_xfree(trs);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
annonce_error:
|
announce_fail:
|
||||||
|
pa_log_error("Aborting RTSP announce, authentication failed");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
break;
|
||||||
|
|
||||||
|
announce_error:
|
||||||
|
pa_log_error("Aborting RTSP announce, failed creating required sockets");
|
||||||
|
|
||||||
if (c->udp_cfd >= 0)
|
if (c->udp_cfd >= 0)
|
||||||
pa_close(c->udp_cfd);
|
pa_close(c->udp_cfd);
|
||||||
c->udp_cfd = -1;
|
c->udp_cfd = -1;
|
||||||
|
|
@ -1001,12 +1057,10 @@ connect_finish:
|
||||||
pa_close(c->udp_tfd);
|
pa_close(c->udp_tfd);
|
||||||
c->udp_tfd = -1;
|
c->udp_tfd = -1;
|
||||||
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
pa_raop_client_disconnect(c);
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
|
||||||
pa_log_error("Aborting RTSP announce, failed creating required sockets");
|
|
||||||
|
|
||||||
c->rtsp = NULL;
|
|
||||||
pa_xfree(trs);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1014,13 +1068,17 @@ connect_finish:
|
||||||
pa_socket_client *sc = NULL;
|
pa_socket_client *sc = NULL;
|
||||||
uint32_t sport = DEFAULT_UDP_AUDIO_PORT;
|
uint32_t sport = DEFAULT_UDP_AUDIO_PORT;
|
||||||
uint32_t cport =0, tport = 0;
|
uint32_t cport =0, tport = 0;
|
||||||
char *ajs, *token, *pc, *trs;
|
const char *ajs, *trs;
|
||||||
|
char *token = NULL, *pc;
|
||||||
const char *token_state = NULL;
|
const char *token_state = NULL;
|
||||||
char delimiters[] = ";";
|
char delimiters[] = ";";
|
||||||
|
|
||||||
pa_log_debug("RAOP: SETUP");
|
pa_log_debug("RAOP: SETUP");
|
||||||
|
|
||||||
ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
|
if (STATUS_OK != status)
|
||||||
|
goto setup_error;
|
||||||
|
|
||||||
|
ajs = pa_headerlist_gets(headers, "Audio-Jack-Status");
|
||||||
|
|
||||||
if (ajs) {
|
if (ajs) {
|
||||||
c->jack_type = JACK_TYPE_ANALOG;
|
c->jack_type = JACK_TYPE_ANALOG;
|
||||||
|
|
@ -1037,7 +1095,6 @@ connect_finish:
|
||||||
}
|
}
|
||||||
pa_xfree(token);
|
pa_xfree(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
|
pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
|
||||||
}
|
}
|
||||||
|
|
@ -1057,7 +1114,7 @@ connect_finish:
|
||||||
pa_socket_client_unref(sc);
|
pa_socket_client_unref(sc);
|
||||||
sc = NULL;
|
sc = NULL;
|
||||||
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||||
trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
|
trs = pa_headerlist_gets(headers, "Transport");
|
||||||
|
|
||||||
if (trs) {
|
if (trs) {
|
||||||
/* Now parse out the server port component of the response. */
|
/* Now parse out the server port component of the response. */
|
||||||
|
|
@ -1076,11 +1133,13 @@ connect_finish:
|
||||||
}
|
}
|
||||||
pa_xfree(token);
|
pa_xfree(token);
|
||||||
}
|
}
|
||||||
pa_xfree(trs);
|
|
||||||
} else {
|
} else {
|
||||||
pa_log_warn("\"Transport\" missing in RTSP setup response");
|
pa_log_warn("\"Transport\" missing in RTSP setup response");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tport)
|
||||||
|
tport = c->udp_tport;
|
||||||
|
|
||||||
if (cport <= 0 || tport <= 0)
|
if (cport <= 0 || tport <= 0)
|
||||||
goto setup_error;
|
goto setup_error;
|
||||||
|
|
||||||
|
|
@ -1093,57 +1152,43 @@ connect_finish:
|
||||||
|
|
||||||
pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport);
|
pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport);
|
||||||
|
|
||||||
|
if (!c->udp_tport) {
|
||||||
/* Send an initial UDP packet so a connection tracking firewall
|
/* Send an initial UDP packet so a connection tracking firewall
|
||||||
* knows the src_ip:src_port <-> dest_ip:dest_port relation
|
* knows the src_ip:src_port <-> dest_ip:dest_port relation
|
||||||
* and accepts the incoming timing packets.
|
* and accepts the incoming timing packets.
|
||||||
*/
|
*/
|
||||||
send_initial_udp_timing_packet(c);
|
send_initial_udp_timing_packet(c);
|
||||||
pa_log_debug("Sent initial timing packet to UDP port %d", tport);
|
pa_log_debug("Sent initial timing packet to UDP port %d", tport);
|
||||||
|
}
|
||||||
if (c->state_callback)
|
|
||||||
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
|
pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
|
||||||
|
|
||||||
pa_xfree(ajs);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
setup_error_parse:
|
setup_error_parse:
|
||||||
pa_log("Failed parsing server port components");
|
pa_log("Failed parsing server port components");
|
||||||
pa_xfree(token);
|
pa_xfree(token);
|
||||||
pa_xfree(trs);
|
|
||||||
/* fall-thru */
|
/* fall-thru */
|
||||||
setup_error:
|
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");
|
pa_log_error("aborting RTSP setup, failed creating required sockets");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
if (c->state_callback)
|
if (c->state_callback)
|
||||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
|
||||||
c->rtsp = NULL;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case STATE_RECORD: {
|
case STATE_RECORD: {
|
||||||
int32_t latency = 0;
|
int32_t latency = 0;
|
||||||
uint32_t ssrc;
|
uint32_t ssrc;
|
||||||
char *alt;
|
const char *alt;
|
||||||
|
|
||||||
pa_log_debug("RAOP: RECORD");
|
pa_log_debug("RAOP: RECORD");
|
||||||
|
|
||||||
alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
|
if (STATUS_OK != status)
|
||||||
|
goto record_error;
|
||||||
|
|
||||||
|
alt = pa_headerlist_gets(headers, "Audio-Latency");
|
||||||
if (alt) {
|
if (alt) {
|
||||||
if (pa_atoi(alt, &latency) < 0)
|
if (pa_atoi(alt, &latency) < 0)
|
||||||
pa_log("Failed to parse audio latency");
|
pa_log("Failed to parse audio latency");
|
||||||
|
|
@ -1152,15 +1197,23 @@ connect_finish:
|
||||||
pa_raop_packet_buffer_reset(c->pbuf, c->seq);
|
pa_raop_packet_buffer_reset(c->pbuf, c->seq);
|
||||||
|
|
||||||
pa_random(&ssrc, sizeof(ssrc));
|
pa_random(&ssrc, sizeof(ssrc));
|
||||||
c->is_first_packet = true;
|
|
||||||
c->is_recording = true;
|
|
||||||
c->sync_count = 0;
|
|
||||||
c->ssrc = ssrc;
|
c->ssrc = ssrc;
|
||||||
|
|
||||||
|
pa_raop_client_stream(c);
|
||||||
|
|
||||||
if (c->state_callback)
|
if (c->state_callback)
|
||||||
c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata);
|
c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata);
|
||||||
|
|
||||||
pa_xfree(alt);
|
/* Now the keep-alive can be sent */
|
||||||
|
c->waiting = false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
record_error:
|
||||||
|
pa_log_error("aborting RTSP record due to error");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1170,6 +1223,12 @@ connect_finish:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case STATE_POST: {
|
||||||
|
pa_log_debug("RAOP: POST");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case STATE_FLUSH: {
|
case STATE_FLUSH: {
|
||||||
pa_log_debug("RAOP: FLUSHED");
|
pa_log_debug("RAOP: FLUSHED");
|
||||||
|
|
||||||
|
|
@ -1179,23 +1238,7 @@ connect_finish:
|
||||||
case STATE_TEARDOWN: {
|
case STATE_TEARDOWN: {
|
||||||
pa_log_debug("RAOP: TEARDOWN");
|
pa_log_debug("RAOP: TEARDOWN");
|
||||||
|
|
||||||
if (c->tcp_sfd >= 0)
|
pa_raop_client_disconnect(c);
|
||||||
pa_close(c->tcp_sfd);
|
|
||||||
c->tcp_sfd = -1;
|
|
||||||
|
|
||||||
if (c->udp_sfd >= 0)
|
|
||||||
pa_close(c->udp_sfd);
|
|
||||||
c->udp_sfd = -1;
|
|
||||||
|
|
||||||
/* Polling sockets will be closed by sink */
|
|
||||||
c->udp_cfd = c->udp_tfd = -1;
|
|
||||||
c->tcp_sfd = -1;
|
|
||||||
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
pa_xfree(c->sid);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
c->sid = NULL;
|
|
||||||
|
|
||||||
if (c->state_callback)
|
if (c->state_callback)
|
||||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
|
||||||
|
|
@ -1205,29 +1248,10 @@ connect_finish:
|
||||||
case STATE_DISCONNECTED: {
|
case STATE_DISCONNECTED: {
|
||||||
pa_log_debug("RAOP: DISCONNECTED");
|
pa_log_debug("RAOP: DISCONNECTED");
|
||||||
|
|
||||||
c->is_recording = false;
|
|
||||||
|
|
||||||
if (c->tcp_sfd >= 0)
|
|
||||||
pa_close(c->tcp_sfd);
|
|
||||||
c->tcp_sfd = -1;
|
|
||||||
|
|
||||||
if (c->udp_sfd >= 0)
|
|
||||||
pa_close(c->udp_sfd);
|
|
||||||
c->udp_sfd = -1;
|
|
||||||
|
|
||||||
/* Polling sockets will be closed by sink */
|
|
||||||
c->udp_cfd = c->udp_tfd = -1;
|
|
||||||
c->tcp_sfd = -1;
|
|
||||||
|
|
||||||
pa_log_error("RTSP control channel closed (disconnected)");
|
pa_log_error("RTSP control channel closed (disconnected)");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
pa_xfree(c->sid);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
c->sid = NULL;
|
|
||||||
|
|
||||||
if (c->state_callback)
|
if (c->state_callback)
|
||||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1257,127 +1281,60 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
||||||
|
|
||||||
pa_random(rac, APPLE_CHALLENGE_LENGTH);
|
pa_random(rac, APPLE_CHALLENGE_LENGTH);
|
||||||
/* Generate a random Apple-Challenge key */
|
/* Generate a random Apple-Challenge key */
|
||||||
pa_raop_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
|
pa_rtsp_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
|
||||||
rtrim_char(sac, '=');
|
pa_rtsp_rtrim_char(sac, '=');
|
||||||
pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
|
pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
|
||||||
|
|
||||||
|
/* Reset this for the authentication */
|
||||||
|
c->waiting = false;
|
||||||
pa_rtsp_options(c->rtsp);
|
pa_rtsp_options(c->rtsp);
|
||||||
|
|
||||||
pa_xfree(sac);
|
pa_xfree(sac);
|
||||||
pa_xfree(sci);
|
pa_xfree(sci);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case STATE_OPTIONS: {
|
case STATE_OPTIONS: {
|
||||||
static bool waiting = false;
|
|
||||||
const char *current = NULL;
|
|
||||||
char space[] = " ";
|
|
||||||
char *token, *ath = NULL;
|
|
||||||
char *publ, *wath, *mth = NULL, *val;
|
|
||||||
char *realm = NULL, *nonce = NULL, *response = NULL;
|
|
||||||
char comma[] = ",";
|
|
||||||
|
|
||||||
pa_log_debug("RAOP: OPTIONS (auth cb)");
|
pa_log_debug("RAOP: OPTIONS (auth cb)");
|
||||||
/* We do not consider the Apple-Response */
|
/* We do not consider the Apple-Response */
|
||||||
pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
|
pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
|
||||||
|
|
||||||
if (STATUS_UNAUTHORIZED == status) {
|
if (STATUS_UNAUTHORIZED == status) {
|
||||||
wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
|
if (c->waiting)
|
||||||
if (true == waiting) {
|
|
||||||
pa_xfree(wath);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
c->waiting = true;
|
||||||
|
|
||||||
if (wath) {
|
|
||||||
mth = pa_split(wath, space, ¤t);
|
|
||||||
while ((token = pa_split(wath, comma, ¤t))) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_xfree(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pa_safe_streq(mth, "Basic") && realm) {
|
|
||||||
rtrim_char(realm, '\"');
|
|
||||||
|
|
||||||
pa_raop_basic_response(DEFAULT_USER_NAME, c->password, &response);
|
|
||||||
ath = pa_sprintf_malloc("Basic %s",
|
|
||||||
response);
|
|
||||||
} else if (pa_safe_streq(mth, "Digest") && realm && nonce) {
|
|
||||||
rtrim_char(realm, '\"');
|
|
||||||
rtrim_char(nonce, '\"');
|
|
||||||
|
|
||||||
pa_raop_digest_response(DEFAULT_USER_NAME, realm, c->password, nonce, "*", &response);
|
|
||||||
ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"",
|
|
||||||
DEFAULT_USER_NAME, realm, nonce,
|
|
||||||
response);
|
|
||||||
} else {
|
|
||||||
pa_log_error("unsupported authentication method: %s", mth);
|
|
||||||
pa_xfree(realm);
|
|
||||||
pa_xfree(nonce);
|
|
||||||
pa_xfree(wath);
|
|
||||||
pa_xfree(mth);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_xfree(response);
|
|
||||||
pa_xfree(realm);
|
|
||||||
pa_xfree(nonce);
|
|
||||||
pa_xfree(wath);
|
|
||||||
pa_xfree(mth);
|
|
||||||
|
|
||||||
pa_rtsp_add_header(c->rtsp, "Authorization", ath);
|
|
||||||
pa_xfree(ath);
|
|
||||||
|
|
||||||
waiting = true;
|
|
||||||
pa_rtsp_options(c->rtsp);
|
pa_rtsp_options(c->rtsp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (STATUS_OK == status) {
|
if (STATUS_OK != status)
|
||||||
publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
|
goto error;
|
||||||
|
|
||||||
|
const char *publ = pa_headerlist_gets(headers, "Public");
|
||||||
|
if (publ && strstr(publ, "POST"))
|
||||||
|
c->has_post = true;
|
||||||
c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
|
c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
|
||||||
|
|
||||||
if (c->password)
|
pa_raop_client_disconnect(c);
|
||||||
pa_xfree(c->password);
|
/* We call with authenticated, not disconnected */
|
||||||
pa_xfree(publ);
|
|
||||||
c->password = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
/* Ensure everything is cleaned before calling the callback, otherwise it may raise a crash */
|
|
||||||
if (c->state_callback)
|
if (c->state_callback)
|
||||||
c->state_callback((int) PA_RAOP_AUTHENTICATED, c->state_userdata);
|
c->state_callback(PA_RAOP_AUTHENTICATED, c->state_userdata);
|
||||||
|
|
||||||
waiting = false;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (c->state_callback)
|
|
||||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
|
|
||||||
pa_log_error("aborting authentication, wrong password");
|
pa_log_error("aborting authentication, wrong password");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
waiting = false;
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (c->state_callback)
|
|
||||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
|
|
||||||
pa_log_error("aborting authentication, unexpected failure");
|
pa_log_error("aborting authentication, unexpected failure");
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
waiting = false;
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1389,15 +1346,14 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
||||||
case STATE_TEARDOWN:
|
case STATE_TEARDOWN:
|
||||||
case STATE_DISCONNECTED:
|
case STATE_DISCONNECTED:
|
||||||
default: {
|
default: {
|
||||||
if (c->state_callback)
|
|
||||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
|
||||||
pa_rtsp_client_free(c->rtsp);
|
|
||||||
c->rtsp = NULL;
|
|
||||||
|
|
||||||
if (c->sci)
|
if (c->sci)
|
||||||
pa_xfree(c->sci);
|
pa_xfree(c->sci);
|
||||||
c->sci = NULL;
|
c->sci = NULL;
|
||||||
|
|
||||||
|
pa_raop_client_disconnect(c);
|
||||||
|
if (c->state_callback)
|
||||||
|
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1405,11 +1361,7 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
||||||
|
|
||||||
|
|
||||||
void pa_raop_client_disconnect(pa_raop_client *c) {
|
void pa_raop_client_disconnect(pa_raop_client *c) {
|
||||||
c->is_recording = false;
|
c->is_streaming = false;
|
||||||
|
|
||||||
if (c->tcp_sfd >= 0)
|
|
||||||
pa_close(c->tcp_sfd);
|
|
||||||
c->tcp_sfd = -1;
|
|
||||||
|
|
||||||
if (c->udp_sfd >= 0)
|
if (c->udp_sfd >= 0)
|
||||||
pa_close(c->udp_sfd);
|
pa_close(c->udp_sfd);
|
||||||
|
|
@ -1419,18 +1371,12 @@ void pa_raop_client_disconnect(pa_raop_client *c) {
|
||||||
c->udp_cfd = c->udp_tfd = -1;
|
c->udp_cfd = c->udp_tfd = -1;
|
||||||
c->tcp_sfd = -1;
|
c->tcp_sfd = -1;
|
||||||
|
|
||||||
pa_log_error("RTSP control channel closed (disconnected)");
|
|
||||||
|
|
||||||
if (c->rtsp)
|
if (c->rtsp)
|
||||||
pa_rtsp_client_free(c->rtsp);
|
pa_rtsp_client_free(c->rtsp);
|
||||||
if (c->sid)
|
if (c->sid)
|
||||||
pa_xfree(c->sid);
|
pa_xfree(c->sid);
|
||||||
c->rtsp = NULL;
|
c->rtsp = NULL;
|
||||||
c->sid = NULL;
|
c->sid = NULL;
|
||||||
|
|
||||||
if (c->state_callback)
|
|
||||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1460,10 +1406,9 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
||||||
c->port = a.port;
|
c->port = a.port;
|
||||||
else
|
else
|
||||||
c->port = DEFAULT_RAOP_PORT;
|
c->port = DEFAULT_RAOP_PORT;
|
||||||
c->rtsp = NULL;
|
|
||||||
c->sci = c->sid = NULL;
|
|
||||||
c->password = NULL;
|
|
||||||
c->autoreconnect = autoreconnect;
|
c->autoreconnect = autoreconnect;
|
||||||
|
c->waiting = false;
|
||||||
|
c->has_post = false;
|
||||||
|
|
||||||
c->protocol = protocol;
|
c->protocol = protocol;
|
||||||
c->encryption = encryption;
|
c->encryption = encryption;
|
||||||
|
|
@ -1475,7 +1420,6 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
||||||
c->udp_cfd = -1;
|
c->udp_cfd = -1;
|
||||||
c->udp_tfd = -1;
|
c->udp_tfd = -1;
|
||||||
|
|
||||||
c->secret = NULL;
|
|
||||||
if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
|
if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
|
||||||
c->secret = pa_raop_secret_new();
|
c->secret = pa_raop_secret_new();
|
||||||
|
|
||||||
|
|
@ -1483,7 +1427,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
||||||
if (c->protocol == PA_RAOP_PROTOCOL_UDP)
|
if (c->protocol == PA_RAOP_PROTOCOL_UDP)
|
||||||
size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET;
|
size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET;
|
||||||
|
|
||||||
c->is_recording = false;
|
c->is_streaming = false;
|
||||||
c->is_first_packet = true;
|
c->is_first_packet = true;
|
||||||
/* Packet sync interval should be around 1s (UDP only) */
|
/* Packet sync interval should be around 1s (UDP only) */
|
||||||
c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
|
c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
|
||||||
|
|
@ -1503,7 +1447,6 @@ void pa_raop_client_free(pa_raop_client *c) {
|
||||||
pa_xfree(c->sci);
|
pa_xfree(c->sci);
|
||||||
if (c->secret)
|
if (c->secret)
|
||||||
pa_raop_secret_free(c->secret);
|
pa_raop_secret_free(c->secret);
|
||||||
pa_xfree(c->password);
|
|
||||||
c->sci = c->sid = NULL;
|
c->sci = c->sid = NULL;
|
||||||
c->password = NULL;
|
c->password = NULL;
|
||||||
c->secret = NULL;
|
c->secret = NULL;
|
||||||
|
|
@ -1521,20 +1464,24 @@ int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (c->rtsp || c->password) {
|
if (c->rtsp) {
|
||||||
pa_log_debug("Authentication/Connection already in progress...");
|
pa_log_debug("Authentication/Connection already in progress...");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->password = NULL;
|
c->password = password;
|
||||||
if (password)
|
|
||||||
c->password = pa_xstrdup(password);
|
|
||||||
c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT, c->autoreconnect);
|
c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT, c->autoreconnect);
|
||||||
|
|
||||||
pa_assert(c->rtsp);
|
pa_assert(c->rtsp);
|
||||||
|
|
||||||
|
pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password);
|
||||||
pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c);
|
pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c);
|
||||||
rv = pa_rtsp_connect(c->rtsp);
|
|
||||||
|
if ((rv = pa_rtsp_connect(c->rtsp))) {
|
||||||
|
pa_rtsp_client_free(c->rtsp);
|
||||||
|
c->rtsp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1563,23 +1510,27 @@ int pa_raop_client_announce(pa_raop_client *c) {
|
||||||
pa_assert(c->rtsp);
|
pa_assert(c->rtsp);
|
||||||
|
|
||||||
c->sync_count = 0;
|
c->sync_count = 0;
|
||||||
c->is_recording = false;
|
c->is_streaming = false;
|
||||||
c->is_first_packet = true;
|
c->is_first_packet = true;
|
||||||
pa_random(&sid, sizeof(sid));
|
pa_random(&sid, sizeof(sid));
|
||||||
c->sid = pa_sprintf_malloc("%u", sid);
|
c->sid = pa_sprintf_malloc("%u", sid);
|
||||||
|
pa_rtsp_add_header(c->rtsp, "Client-Instance", c->sci);
|
||||||
|
pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password);
|
||||||
pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c);
|
pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c);
|
||||||
|
|
||||||
rv = pa_rtsp_connect(c->rtsp);
|
if ((rv = pa_rtsp_connect(c->rtsp))) {
|
||||||
|
pa_rtsp_client_free(c->rtsp);
|
||||||
|
c->rtsp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pa_raop_client_is_alive(pa_raop_client *c) {
|
bool pa_raop_client_is_alive(pa_raop_client *c) {
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp || !c->sci) {
|
if (!c->rtsp || !c->sci)
|
||||||
pa_log_debug("Not alive, connection not established yet...");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
switch (c->protocol) {
|
switch (c->protocol) {
|
||||||
case PA_RAOP_PROTOCOL_TCP:
|
case PA_RAOP_PROTOCOL_TCP:
|
||||||
|
|
@ -1597,64 +1548,28 @@ bool pa_raop_client_is_alive(pa_raop_client *c) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pa_raop_client_can_stream(pa_raop_client *c) {
|
bool pa_raop_client_is_streaming(pa_raop_client *c) {
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp || !c->sci) {
|
return pa_raop_client_is_alive(c) && c->is_streaming;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (c->protocol) {
|
|
||||||
case PA_RAOP_PROTOCOL_TCP:
|
|
||||||
if (c->tcp_sfd >= 0 && c->is_recording)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case PA_RAOP_PROTOCOL_UDP:
|
|
||||||
if (c->udp_sfd >= 0 && c->is_recording)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool pa_raop_client_is_recording(pa_raop_client *c) {
|
|
||||||
return c->is_recording;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_client_stream(pa_raop_client *c) {
|
int pa_raop_client_stream(pa_raop_client *c) {
|
||||||
int rv = 0;
|
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp || !c->sci) {
|
if (!pa_raop_client_is_alive(c)) {
|
||||||
pa_log_debug("Streaming's impossible, connection not established yet...");
|
pa_log_debug("Streaming's impossible, connection not established yet...");
|
||||||
|
return 1;
|
||||||
|
} else if (pa_raop_client_is_streaming(c)) {
|
||||||
|
pa_log_debug("Already streaming...");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (c->protocol) {
|
c->is_streaming = true;
|
||||||
case PA_RAOP_PROTOCOL_TCP:
|
|
||||||
if (c->tcp_sfd >= 0 && !c->is_recording) {
|
|
||||||
c->is_recording = true;
|
|
||||||
c->is_first_packet = true;
|
c->is_first_packet = true;
|
||||||
c->sync_count = 0;
|
c->sync_count = 0;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PA_RAOP_PROTOCOL_UDP:
|
|
||||||
if (c->udp_sfd >= 0 && !c->is_recording) {
|
|
||||||
c->is_recording = true;
|
|
||||||
c->is_first_packet = true;
|
|
||||||
c->sync_count = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
rv = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
@ -1664,11 +1579,8 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp) {
|
if (!pa_raop_client_is_alive(c)) {
|
||||||
pa_log_debug("Cannot SET_PARAMETER, connection not established yet...");
|
pa_log_debug("Cannot SET_PARAMETER, connection not established yet...");
|
||||||
return 0;
|
|
||||||
} else if (!c->sci) {
|
|
||||||
pa_log_debug("SET_PARAMETER requires a preliminary authentication");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1682,49 +1594,36 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
|
||||||
|
|
||||||
param = pa_sprintf_malloc("volume: %0.6f\r\n", db);
|
param = pa_sprintf_malloc("volume: %0.6f\r\n", db);
|
||||||
/* We just hit and hope, cannot wait for the callback. */
|
/* 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);
|
rv = pa_rtsp_setparameter(c->rtsp, param);
|
||||||
|
|
||||||
pa_xfree(param);
|
pa_xfree(param);
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_client_flush(pa_raop_client *c) {
|
int pa_raop_client_flush(pa_raop_client *c) {
|
||||||
int rv = 0;
|
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp || !pa_rtsp_exec_ready(c->rtsp)) {
|
if (!pa_raop_client_is_alive(c)) {
|
||||||
pa_log_debug("Cannot FLUSH, connection not established yet...)");
|
pa_log_debug("Cannot FLUSH, connection not established yet...");
|
||||||
return 0;
|
|
||||||
} else if (!c->sci) {
|
|
||||||
pa_log_debug("FLUSH requires a preliminary authentication");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->is_recording = false;
|
c->is_streaming = false;
|
||||||
|
|
||||||
rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
|
return pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_client_teardown(pa_raop_client *c) {
|
int pa_raop_client_teardown(pa_raop_client *c) {
|
||||||
int rv = 0;
|
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
if (!c->rtsp) {
|
if (!pa_raop_client_is_alive(c)) {
|
||||||
pa_log_debug("Cannot TEARDOWN, connection not established yet...");
|
pa_log_debug("Cannot TEARDOWN, connection not established yet...");
|
||||||
return 0;
|
|
||||||
} else if (!c->sci) {
|
|
||||||
pa_log_debug("TEARDOWN requires a preliminary authentication");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->is_recording = false;
|
c->is_streaming = false;
|
||||||
|
|
||||||
rv = pa_rtsp_teardown(c->rtsp);
|
return pa_rtsp_teardown(c->rtsp);
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) {
|
void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) {
|
||||||
|
|
@ -1803,20 +1702,44 @@ pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume)
|
||||||
return volume - volume * (minv / maxv) + minv;
|
return volume - volume * (minv / maxv) + minv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size) {
|
ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd) {
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
pa_assert(fd >= 0);
|
pa_assert(fd >= 0);
|
||||||
pa_assert(packet);
|
|
||||||
|
|
||||||
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||||
|
uint8_t packet[32];
|
||||||
|
ssize_t size;
|
||||||
|
struct sockaddr_storage sa;
|
||||||
|
socklen_t salen = sizeof(sa);
|
||||||
|
|
||||||
|
size = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr *) &sa, &salen);
|
||||||
|
|
||||||
|
if (size < 0)
|
||||||
|
return size;
|
||||||
|
|
||||||
if (fd == c->udp_cfd) {
|
if (fd == c->udp_cfd) {
|
||||||
pa_log_debug("Received UDP control packet...");
|
pa_log_debug("Received UDP control packet...");
|
||||||
handle_udp_control_packet(c, packet, size);
|
return handle_udp_control_packet(c, packet, size);
|
||||||
} else if (fd == c->udp_tfd) {
|
} else if (fd == c->udp_tfd) {
|
||||||
pa_log_debug("Received UDP timing packet...");
|
pa_log_debug("Received UDP timing packet...");
|
||||||
handle_udp_timing_packet(c, packet, size);
|
if (!c->udp_tport) {
|
||||||
|
/* Apple TV sends timing packets after the SETUP request
|
||||||
|
* which we use to get the port. It sends timing_port=0 in the
|
||||||
|
* SETUP response.
|
||||||
|
*/
|
||||||
|
if (sa.ss_family == AF_INET)
|
||||||
|
c->udp_tport = ntohs(((struct sockaddr_in *) &sa)->sin_port);
|
||||||
|
#ifdef HAVE_IPV6
|
||||||
|
else if (sa.ss_family == AF_INET6)
|
||||||
|
c->udp_tport = ntohs(((struct sockaddr_in6 *) &sa)->sin6_port);
|
||||||
|
#endif
|
||||||
|
c->udp_tfd = connect_udp_socket(c, c->udp_tfd, c->udp_tport);
|
||||||
|
}
|
||||||
|
return handle_udp_timing_packet(c, packet, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
|
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,7 @@ bool pa_raop_client_is_authenticated(pa_raop_client *c);
|
||||||
|
|
||||||
int pa_raop_client_announce(pa_raop_client *c);
|
int pa_raop_client_announce(pa_raop_client *c);
|
||||||
bool pa_raop_client_is_alive(pa_raop_client *c);
|
bool pa_raop_client_is_alive(pa_raop_client *c);
|
||||||
bool pa_raop_client_is_recording(pa_raop_client *c);
|
bool pa_raop_client_is_streaming(pa_raop_client *c);
|
||||||
bool pa_raop_client_can_stream(pa_raop_client *c);
|
|
||||||
int pa_raop_client_stream(pa_raop_client *c);
|
int pa_raop_client_stream(pa_raop_client *c);
|
||||||
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);
|
||||||
int pa_raop_client_flush(pa_raop_client *c);
|
int pa_raop_client_flush(pa_raop_client *c);
|
||||||
|
|
@ -77,7 +76,7 @@ void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *size);
|
||||||
bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item);
|
bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item);
|
||||||
bool pa_raop_client_is_timing_fd(pa_raop_client *c, const int fd);
|
bool pa_raop_client_is_timing_fd(pa_raop_client *c, const int fd);
|
||||||
pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
|
pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
|
||||||
void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size);
|
ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd);
|
||||||
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset);
|
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset);
|
||||||
|
|
||||||
typedef void (*pa_raop_client_state_cb_t)(pa_raop_state_t state, void *userdata);
|
typedef void (*pa_raop_client_state_cb_t)(pa_raop_state_t state, void *userdata);
|
||||||
|
|
|
||||||
27
src/modules/raop/raop-common.h
Normal file
27
src/modules/raop/raop-common.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef fooraopcommonfoo
|
||||||
|
#define fooraopcommonfoo
|
||||||
|
|
||||||
|
/***
|
||||||
|
This file is part of PulseAudio.
|
||||||
|
|
||||||
|
Copyright 2008 Colin Guthrie
|
||||||
|
Copyright Kungliga Tekniska högskolan
|
||||||
|
Copyright 2013 Martin Blanchard
|
||||||
|
|
||||||
|
PulseAudio is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published
|
||||||
|
by the Free Software Foundation; either version 2.1 of the License,
|
||||||
|
or (at your option) any later version.
|
||||||
|
|
||||||
|
PulseAudio is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
***/
|
||||||
|
|
||||||
|
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -37,8 +37,9 @@
|
||||||
#include <pulsecore/macro.h>
|
#include <pulsecore/macro.h>
|
||||||
#include <pulsecore/random.h>
|
#include <pulsecore/random.h>
|
||||||
|
|
||||||
|
#include <modules/rtp/rtsp-util.h>
|
||||||
|
|
||||||
#include "raop-crypto.h"
|
#include "raop-crypto.h"
|
||||||
#include "raop-util.h"
|
|
||||||
|
|
||||||
#define AES_CHUNK_SIZE 16
|
#define AES_CHUNK_SIZE 16
|
||||||
|
|
||||||
|
|
@ -92,7 +93,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pa_raop_base64_decode(rsa_modulus, modulus);
|
size = pa_rtsp_base64_decode(rsa_modulus, modulus);
|
||||||
|
|
||||||
n_bn = BN_bin2bn(modulus, size, NULL);
|
n_bn = BN_bin2bn(modulus, size, NULL);
|
||||||
if (!n_bn) {
|
if (!n_bn) {
|
||||||
|
|
@ -100,7 +101,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pa_raop_base64_decode(rsa_exponent, exponent);
|
size = pa_rtsp_base64_decode(rsa_exponent, exponent);
|
||||||
|
|
||||||
e_bn = BN_bin2bn(exponent, size, NULL);
|
e_bn = BN_bin2bn(exponent, size, NULL);
|
||||||
if (!e_bn) {
|
if (!e_bn) {
|
||||||
|
|
@ -165,7 +166,7 @@ char* pa_raop_secret_get_iv(pa_raop_secret *s) {
|
||||||
|
|
||||||
pa_assert(s);
|
pa_assert(s);
|
||||||
|
|
||||||
pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
|
pa_rtsp_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
|
||||||
|
|
||||||
return base64_iv;
|
return base64_iv;
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +185,7 @@ char* pa_raop_secret_get_key(pa_raop_secret *s) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_raop_base64_encode(rsa_key, size, &base64_key);
|
pa_rtsp_base64_encode(rsa_key, size, &base64_key);
|
||||||
|
|
||||||
return base64_key;
|
return base64_key;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
|
|
||||||
#include "raop-sink.h"
|
#include "raop-sink.h"
|
||||||
#include "raop-client.h"
|
#include "raop-client.h"
|
||||||
#include "raop-util.h"
|
#include "raop-common.h"
|
||||||
|
|
||||||
#define UDP_TIMING_PACKET_LOSS_MAX (30 * PA_USEC_PER_SEC)
|
#define UDP_TIMING_PACKET_LOSS_MAX (30 * PA_USEC_PER_SEC)
|
||||||
#define UDP_TIMING_PACKET_DISCONNECT_CYCLE 3
|
#define UDP_TIMING_PACKET_DISCONNECT_CYCLE 3
|
||||||
|
|
@ -79,6 +79,7 @@ struct userdata {
|
||||||
pa_module *module;
|
pa_module *module;
|
||||||
pa_sink *sink;
|
pa_sink *sink;
|
||||||
pa_card *card;
|
pa_card *card;
|
||||||
|
pa_subscription *subscription;
|
||||||
|
|
||||||
pa_thread *thread;
|
pa_thread *thread;
|
||||||
pa_thread_mq thread_mq;
|
pa_thread_mq thread_mq;
|
||||||
|
|
@ -91,6 +92,7 @@ struct userdata {
|
||||||
pa_raop_protocol_t protocol;
|
pa_raop_protocol_t protocol;
|
||||||
pa_raop_encryption_t encryption;
|
pa_raop_encryption_t encryption;
|
||||||
pa_raop_codec_t codec;
|
pa_raop_codec_t codec;
|
||||||
|
char *password;
|
||||||
bool autoreconnect;
|
bool autoreconnect;
|
||||||
/* if true, behaves like a null-sink when disconnected */
|
/* if true, behaves like a null-sink when disconnected */
|
||||||
bool autonull;
|
bool autonull;
|
||||||
|
|
@ -115,7 +117,9 @@ struct userdata {
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PA_SINK_MESSAGE_SET_RAOP_STATE = PA_SINK_MESSAGE_MAX,
|
PA_SINK_MESSAGE_SET_RAOP_STATE = PA_SINK_MESSAGE_MAX,
|
||||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST
|
PA_SINK_MESSAGE_CONNECTED,
|
||||||
|
PA_SINK_MESSAGE_DISCONNECTED,
|
||||||
|
PA_SINK_MESSAGE_CONNECT_REQUEST
|
||||||
};
|
};
|
||||||
|
|
||||||
static void userdata_free(struct userdata *u);
|
static void userdata_free(struct userdata *u);
|
||||||
|
|
@ -129,7 +133,8 @@ static void raop_state_cb(pa_raop_state_t state, void *userdata) {
|
||||||
|
|
||||||
pa_log_debug("State change received, informing IO thread...");
|
pa_log_debug("State change received, informing IO thread...");
|
||||||
|
|
||||||
pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL, NULL);
|
pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink),
|
||||||
|
PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t sink_get_latency(const struct userdata *u) {
|
static int64_t sink_get_latency(const struct userdata *u) {
|
||||||
|
|
@ -156,6 +161,44 @@ static int64_t sink_get_latency(const struct userdata *u) {
|
||||||
return latency;
|
return latency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void disconnect(struct userdata *u) {
|
||||||
|
unsigned int nbfds = 0;
|
||||||
|
struct pollfd *pollfd;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
if (u->rtpoll_item) {
|
||||||
|
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
|
||||||
|
if (pollfd) {
|
||||||
|
for (i = 0; i < nbfds; i++) {
|
||||||
|
if (pollfd->fd >= 0)
|
||||||
|
pa_close(pollfd->fd);
|
||||||
|
pollfd++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pa_rtpoll_item_free(u->rtpoll_item);
|
||||||
|
u->rtpoll_item = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_raop_client_disconnect(u->raop);
|
||||||
|
|
||||||
|
if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
|
||||||
|
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||||
|
|
||||||
|
if (u->sink->thread_info.state == PA_SINK_RUNNING) {
|
||||||
|
if (!u->autonull)
|
||||||
|
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||||
|
|
||||||
|
if (u->autoreconnect) {
|
||||||
|
if (pa_raop_client_is_authenticated(u->raop))
|
||||||
|
pa_raop_client_announce(u->raop);
|
||||||
|
else
|
||||||
|
pa_raop_client_authenticate(u->raop, u->password);
|
||||||
|
} else
|
||||||
|
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||||
|
PA_SINK_MESSAGE_DISCONNECTED, NULL, 0, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||||
struct userdata *u = PA_SINK(o)->userdata;
|
struct userdata *u = PA_SINK(o)->userdata;
|
||||||
|
|
||||||
|
|
@ -163,23 +206,34 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
pa_assert(u->raop);
|
pa_assert(u->raop);
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
/* Exception : for this message, we are in main thread, msg sent from the IO/thread
|
case PA_SINK_MESSAGE_CONNECTED: {
|
||||||
Done here, as alloc/free of rtsp_client is also done in this thread for other cases */
|
pa_assert_ctl_context();
|
||||||
case PA_SINK_MESSAGE_DISCONNECT_REQUEST: {
|
pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_YES);
|
||||||
if (u->sink->state == PA_SINK_RUNNING) {
|
return 0;
|
||||||
/* Disconnect raop client, and restart the whole chain since
|
|
||||||
* the authentication token might be outdated */
|
|
||||||
pa_raop_client_disconnect(u->raop);
|
|
||||||
pa_raop_client_authenticate(u->raop, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PA_SINK_MESSAGE_DISCONNECTED: {
|
||||||
|
pa_assert_ctl_context();
|
||||||
|
/* Mark the port as unavailable so a different sink can be used */
|
||||||
|
pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_NO);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PA_SINK_MESSAGE_CONNECT_REQUEST: {
|
||||||
|
pa_assert_io_context();
|
||||||
|
pa_log_debug("Received connect request");
|
||||||
|
if (pa_raop_client_is_authenticated(u->raop))
|
||||||
|
pa_raop_client_announce(u->raop);
|
||||||
|
else
|
||||||
|
pa_raop_client_authenticate(u->raop, u->password);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||||
|
pa_assert_io_context();
|
||||||
int64_t r = 0;
|
int64_t r = 0;
|
||||||
|
|
||||||
if (u->autonull || pa_raop_client_can_stream(u->raop))
|
if (u->autonull || pa_raop_client_is_streaming(u->raop))
|
||||||
r = sink_get_latency(u);
|
r = sink_get_latency(u);
|
||||||
|
|
||||||
*((int64_t*) data) = r;
|
*((int64_t*) data) = r;
|
||||||
|
|
@ -188,13 +242,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
}
|
}
|
||||||
|
|
||||||
case PA_SINK_MESSAGE_SET_RAOP_STATE: {
|
case PA_SINK_MESSAGE_SET_RAOP_STATE: {
|
||||||
|
pa_assert_io_context();
|
||||||
switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) {
|
switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) {
|
||||||
case PA_RAOP_AUTHENTICATED: {
|
case PA_RAOP_AUTHENTICATED: {
|
||||||
if (!pa_raop_client_is_authenticated(u->raop)) {
|
if (u->sink->state == PA_SINK_RUNNING) {
|
||||||
pa_module_unload_request(u->module, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u->autoreconnect && u->sink->state == PA_SINK_RUNNING) {
|
|
||||||
pa_usec_t now;
|
pa_usec_t now;
|
||||||
now = pa_rtclock_now();
|
now = pa_rtclock_now();
|
||||||
#ifdef USE_SMOOTHER_2
|
#ifdef USE_SMOOTHER_2
|
||||||
|
|
@ -202,12 +253,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
#else
|
#else
|
||||||
pa_smoother_reset(u->smoother, now, false);
|
pa_smoother_reset(u->smoother, now, false);
|
||||||
#endif
|
#endif
|
||||||
|
/* Connecting will trigger a RECORD and start streaming */
|
||||||
if (!pa_raop_client_is_alive(u->raop)) {
|
|
||||||
/* Connecting will trigger a RECORD and start steaming */
|
|
||||||
pa_raop_client_announce(u->raop);
|
pa_raop_client_announce(u->raop);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +265,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||||
|
PA_SINK_MESSAGE_CONNECTED, NULL, 0, NULL, NULL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,40 +295,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
|
|
||||||
case PA_RAOP_INVALID_STATE:
|
case PA_RAOP_INVALID_STATE:
|
||||||
case PA_RAOP_DISCONNECTED: {
|
case PA_RAOP_DISCONNECTED: {
|
||||||
unsigned int nbfds = 0;
|
disconnect(u);
|
||||||
struct pollfd *pollfd;
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (u->rtpoll_item) {
|
|
||||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
|
|
||||||
if (pollfd) {
|
|
||||||
for (i = 0; i < nbfds; i++) {
|
|
||||||
if (pollfd->fd >= 0)
|
|
||||||
pa_close(pollfd->fd);
|
|
||||||
pollfd++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pa_rtpoll_item_free(u->rtpoll_item);
|
|
||||||
u->rtpoll_item = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
|
|
||||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u->autoreconnect) {
|
|
||||||
if (u->sink->thread_info.state != PA_SINK_IDLE) {
|
|
||||||
if (!u->autonull)
|
|
||||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
|
||||||
pa_raop_client_authenticate(u->raop, NULL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (u->sink->thread_info.state != PA_SINK_IDLE)
|
|
||||||
pa_module_unload_request(u->module, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +304,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pa_assert_io_context();
|
||||||
|
|
||||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,9 +328,8 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
|
||||||
pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
|
pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
|
||||||
|
|
||||||
/* Issue a TEARDOWN if we are still connected */
|
/* Issue a TEARDOWN if we are still connected */
|
||||||
if (pa_raop_client_is_alive(u->raop)) {
|
if (pa_raop_client_is_alive(u->raop))
|
||||||
pa_raop_client_teardown(u->raop);
|
pa_raop_client_teardown(u->raop);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -348,9 +367,12 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
|
||||||
|
|
||||||
if (!pa_raop_client_is_alive(u->raop)) {
|
if (!pa_raop_client_is_alive(u->raop)) {
|
||||||
/* Connecting will trigger a RECORD and start streaming */
|
/* Connecting will trigger a RECORD and start streaming */
|
||||||
|
if (pa_raop_client_is_authenticated(u->raop))
|
||||||
pa_raop_client_announce(u->raop);
|
pa_raop_client_announce(u->raop);
|
||||||
} else if (!pa_raop_client_is_recording(u->raop)) {
|
else
|
||||||
/* RECORD alredy sent, simply start streaming */
|
pa_raop_client_authenticate(u->raop, u->password);
|
||||||
|
} else if (!pa_raop_client_is_streaming(u->raop)) {
|
||||||
|
/* RECORD already sent, simply start streaming */
|
||||||
pa_raop_client_stream(u->raop);
|
pa_raop_client_stream(u->raop);
|
||||||
pa_rtpoll_set_timer_absolute(u->rtpoll, now);
|
pa_rtpoll_set_timer_absolute(u->rtpoll, now);
|
||||||
u->write_count = 0;
|
u->write_count = 0;
|
||||||
|
|
@ -445,7 +467,7 @@ static void thread_func(void *userdata) {
|
||||||
uint64_t position;
|
uint64_t position;
|
||||||
size_t index;
|
size_t index;
|
||||||
int ret;
|
int ret;
|
||||||
bool canstream, sendstream, on_timeout;
|
bool is_streaming, on_timeout;
|
||||||
#ifndef USE_SMOOTHER_2
|
#ifndef USE_SMOOTHER_2
|
||||||
pa_usec_t estimated;
|
pa_usec_t estimated;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -478,25 +500,26 @@ static void thread_func(void *userdata) {
|
||||||
|
|
||||||
/* if oob: streaming managed by timing, pollfd for oob sockets */
|
/* if oob: streaming managed by timing, pollfd for oob sockets */
|
||||||
if (pollfd && u->oob && !on_timeout) {
|
if (pollfd && u->oob && !on_timeout) {
|
||||||
uint8_t packet[32];
|
|
||||||
ssize_t read;
|
|
||||||
|
|
||||||
for (i = 0; i < nbfds; i++) {
|
for (i = 0; i < nbfds; i++) {
|
||||||
if (pollfd->revents & POLLERR) {
|
if (pollfd->revents & POLLERR) {
|
||||||
if (u->autoreconnect && pa_raop_client_is_alive(u->raop)) {
|
disconnect(u);
|
||||||
pollfd->revents = 0;
|
|
||||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
|
||||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* one of UDP fds is in faulty state, may have been disconnected, this is fatal */
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (pollfd->revents & pollfd->events) {
|
if (pollfd->revents & pollfd->events) {
|
||||||
pollfd->revents = 0;
|
pollfd->revents = 0;
|
||||||
read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
|
if (pa_raop_client_handle_oob_packet(u->raop, pollfd->fd) < 0) {
|
||||||
pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read);
|
if (errno == EINTR) {
|
||||||
|
pa_log_debug("Failed to handle oob packet (EINTR), ignoring");
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
pa_log_debug("Failed to handle oob packet (EAGAIN), ignoring");
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
pa_log("Failed to handle oob packet: %s", pa_cstrerror(errno));
|
||||||
|
disconnect(u);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
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();
|
||||||
check_timing_count = 1;
|
check_timing_count = 1;
|
||||||
|
|
@ -510,9 +533,8 @@ static void thread_func(void *userdata) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (u->sink->thread_info.state != PA_SINK_RUNNING) {
|
if (u->sink->thread_info.state != PA_SINK_RUNNING)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (u->first) {
|
if (u->first) {
|
||||||
last_timing = 0;
|
last_timing = 0;
|
||||||
|
|
@ -521,11 +543,11 @@ static void thread_func(void *userdata) {
|
||||||
u->first = false;
|
u->first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
canstream = pa_raop_client_can_stream(u->raop);
|
is_streaming = pa_raop_client_is_streaming(u->raop);
|
||||||
now = pa_rtclock_now();
|
now = pa_rtclock_now();
|
||||||
|
|
||||||
if (u->oob && u->autoreconnect && on_timeout) {
|
if (u->oob && u->autoreconnect && on_timeout) {
|
||||||
if (!canstream) {
|
if (!is_streaming) {
|
||||||
last_timing = 0;
|
last_timing = 0;
|
||||||
} else if (last_timing != 0) {
|
} else if (last_timing != 0) {
|
||||||
pa_usec_t since = now - last_timing;
|
pa_usec_t since = now - last_timing;
|
||||||
|
|
@ -543,28 +565,23 @@ static void thread_func(void *userdata) {
|
||||||
UDP_TIMING_PACKET_DISCONNECT_CYCLE-1, since_in_sec, u->server);
|
UDP_TIMING_PACKET_DISCONNECT_CYCLE-1, since_in_sec, u->server);
|
||||||
check_timing_count++;
|
check_timing_count++;
|
||||||
} else {
|
} else {
|
||||||
/* Limit reached, then request disconnect */
|
/* Limit reached, then disconnect */
|
||||||
check_timing_count = 1;
|
check_timing_count = 1;
|
||||||
last_timing = 0;
|
last_timing = 0;
|
||||||
if (pa_raop_client_is_alive(u->raop)) {
|
pa_log_warn("UDP Timing Packets Warn limit reached - disconnecting");
|
||||||
pa_log_warn("UDP Timing Packets Warn limit reached - Requesting reconnect");
|
disconnect(u);
|
||||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
|
||||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!u->autonull) {
|
if (!u->autonull) {
|
||||||
if (!canstream) {
|
if (!is_streaming)
|
||||||
pa_log_debug("Can't stream, connection not established yet...");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
/* This assertion is meant to silence a complaint from Coverity about
|
/* This assertion is meant to silence a complaint from Coverity about
|
||||||
* pollfd being possibly NULL when we access it later. That's a false
|
* pollfd being possibly NULL when we access it later. That's a false
|
||||||
* positive, because we check pa_raop_client_can_stream() above, and if
|
* positive, because we check pa_raop_client_is_streaming() above, and if
|
||||||
* that returns true, it means that the connection is up, and when the
|
* that returns true, it means that the connection is up, and when the
|
||||||
* connection is up, pollfd will be non-NULL. */
|
* connection is up, pollfd will be non-NULL. */
|
||||||
pa_assert(pollfd);
|
pa_assert(pollfd);
|
||||||
|
|
@ -584,36 +601,30 @@ static void thread_func(void *userdata) {
|
||||||
|
|
||||||
if (u->memchunk.length > 0) {
|
if (u->memchunk.length > 0) {
|
||||||
index = u->memchunk.index;
|
index = u->memchunk.index;
|
||||||
sendstream = !u->autonull || (u->autonull && canstream);
|
if (is_streaming && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
||||||
if (sendstream && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
|
||||||
if (errno == EINTR) {
|
if (errno == EINTR) {
|
||||||
/* Just try again. */
|
/* Just try again. */
|
||||||
pa_log_debug("Failed to write data to FIFO (EINTR), retrying");
|
pa_log_debug("Failed to write audio packet (EINTR), retrying");
|
||||||
if (u->autoreconnect) {
|
|
||||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST,
|
|
||||||
0, 0, NULL, NULL);
|
|
||||||
continue;
|
continue;
|
||||||
} else
|
} else if (errno == EAGAIN) {
|
||||||
goto fail;
|
if (u->oob) {
|
||||||
} else if (errno != EAGAIN && !u->oob) {
|
/* Just try again. */
|
||||||
|
pa_log_debug("Failed to write audio packet (EAGAIN), retrying");
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
/* Buffer is full, wait for POLLOUT. */
|
/* Buffer is full, wait for POLLOUT. */
|
||||||
if (!u->oob) {
|
|
||||||
pollfd->events = POLLOUT;
|
pollfd->events = POLLOUT;
|
||||||
pollfd->revents = 0;
|
pollfd->revents = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
|
pa_log("Failed to write audio packet: %s", pa_cstrerror(errno));
|
||||||
if (u->autoreconnect) {
|
disconnect(u);
|
||||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST,
|
|
||||||
0, 0, NULL, NULL);
|
|
||||||
continue;
|
continue;
|
||||||
} else
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sendstream) {
|
if (is_streaming) {
|
||||||
u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index;
|
u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index;
|
||||||
} else {
|
} else if (u->autonull) {
|
||||||
u->write_count += u->memchunk.length;
|
u->write_count += u->memchunk.length;
|
||||||
u->memchunk.length = 0;
|
u->memchunk.length = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -627,7 +638,7 @@ static void thread_func(void *userdata) {
|
||||||
pa_smoother_put(u->smoother, now, estimated);
|
pa_smoother_put(u->smoother, now, estimated);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((u->autonull && !canstream) || (u->oob && canstream && on_timeout)) {
|
if ((u->autonull && !is_streaming) || (u->oob && is_streaming && on_timeout)) {
|
||||||
/* Sleep until next packet transmission */
|
/* Sleep until next packet transmission */
|
||||||
intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
|
intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
|
||||||
pa_rtpoll_set_timer_absolute(u->rtpoll, intvl);
|
pa_rtpoll_set_timer_absolute(u->rtpoll, intvl);
|
||||||
|
|
@ -660,6 +671,18 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
|
||||||
|
struct userdata *u = userdata;
|
||||||
|
|
||||||
|
pa_assert_ctl_context();
|
||||||
|
|
||||||
|
/* Try to reconnect on server changes */
|
||||||
|
if (u->sink->active_port->available == PA_AVAILABLE_NO) {
|
||||||
|
pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink),
|
||||||
|
PA_SINK_MESSAGE_CONNECT_REQUEST, NULL, 0, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static pa_device_port *raop_create_port(struct userdata *u, const char *server) {
|
static pa_device_port *raop_create_port(struct userdata *u, const char *server) {
|
||||||
pa_device_port_new_data data;
|
pa_device_port_new_data data;
|
||||||
pa_device_port *port;
|
pa_device_port *port;
|
||||||
|
|
@ -902,6 +925,11 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u->subscription = pa_subscription_new(
|
||||||
|
m->core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE,
|
||||||
|
subscribe_callback, u
|
||||||
|
);
|
||||||
|
|
||||||
u->sink->parent.process_msg = sink_process_msg;
|
u->sink->parent.process_msg = sink_process_msg;
|
||||||
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
|
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
|
||||||
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
|
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
|
||||||
|
|
@ -939,7 +967,8 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
|
||||||
|
|
||||||
/* username = pa_modargs_get_value(ma, "username", NULL); */
|
/* username = pa_modargs_get_value(ma, "username", NULL); */
|
||||||
password = pa_modargs_get_value(ma, "password", NULL);
|
password = pa_modargs_get_value(ma, "password", NULL);
|
||||||
pa_raop_client_authenticate(u->raop, password );
|
if (password)
|
||||||
|
u->password = pa_xstrdup(password);
|
||||||
|
|
||||||
return u->sink;
|
return u->sink;
|
||||||
|
|
||||||
|
|
@ -969,6 +998,10 @@ static void userdata_free(struct userdata *u) {
|
||||||
pa_sink_unref(u->sink);
|
pa_sink_unref(u->sink);
|
||||||
u->sink = NULL;
|
u->sink = NULL;
|
||||||
|
|
||||||
|
if (u->subscription)
|
||||||
|
pa_subscription_free(u->subscription);
|
||||||
|
u->subscription = NULL;
|
||||||
|
|
||||||
if (u->rtpoll_item)
|
if (u->rtpoll_item)
|
||||||
pa_rtpoll_item_free(u->rtpoll_item);
|
pa_rtpoll_item_free(u->rtpoll_item);
|
||||||
if (u->rtpoll)
|
if (u->rtpoll)
|
||||||
|
|
@ -995,6 +1028,8 @@ static void userdata_free(struct userdata *u) {
|
||||||
pa_card_free(u->card);
|
pa_card_free(u->card);
|
||||||
if (u->server)
|
if (u->server)
|
||||||
pa_xfree(u->server);
|
pa_xfree(u->server);
|
||||||
|
if (u->password)
|
||||||
|
pa_xfree(u->password);
|
||||||
|
|
||||||
pa_xfree(u);
|
pa_xfree(u);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@ librtp_sources = [
|
||||||
'rtp-common.c',
|
'rtp-common.c',
|
||||||
'sdp.c',
|
'sdp.c',
|
||||||
'sap.c',
|
'sap.c',
|
||||||
'rtsp_client.c',
|
'rtsp-client.c',
|
||||||
'headerlist.c',
|
'headerlist.c',
|
||||||
|
'rtsp-util.c'
|
||||||
]
|
]
|
||||||
|
|
||||||
librtp_headers = [
|
librtp_headers = [
|
||||||
'rtp.h',
|
'rtp.h',
|
||||||
'sdp.h',
|
'sdp.h',
|
||||||
'sap.h',
|
'sap.h',
|
||||||
'rtsp_client.h',
|
'rtsp-client.h',
|
||||||
'headerlist.h',
|
'headerlist.h',
|
||||||
|
'rtsp-util.h'
|
||||||
]
|
]
|
||||||
|
|
||||||
if have_gstreamer
|
if have_gstreamer
|
||||||
|
|
@ -26,7 +28,7 @@ librtp = shared_library('rtp',
|
||||||
c_args : [pa_c_args, server_c_args],
|
c_args : [pa_c_args, server_c_args],
|
||||||
link_args : [nodelete_link_args],
|
link_args : [nodelete_link_args],
|
||||||
include_directories : [configinc, topinc],
|
include_directories : [configinc, topinc],
|
||||||
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep],
|
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep, openssl_dep],
|
||||||
install : true,
|
install : true,
|
||||||
install_rpath : privlibdir,
|
install_rpath : privlibdir,
|
||||||
install_dir : modlibexecdir,
|
install_dir : modlibexecdir,
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,30 @@
|
||||||
#include <pulsecore/core-util.h>
|
#include <pulsecore/core-util.h>
|
||||||
#include <pulsecore/log.h>
|
#include <pulsecore/log.h>
|
||||||
#include <pulsecore/macro.h>
|
#include <pulsecore/macro.h>
|
||||||
|
#include <pulsecore/mutex.h>
|
||||||
#include <pulsecore/strbuf.h>
|
#include <pulsecore/strbuf.h>
|
||||||
#include <pulsecore/ioline.h>
|
#include <pulsecore/ioline.h>
|
||||||
#include <pulsecore/arpa-inet.h>
|
#include <pulsecore/arpa-inet.h>
|
||||||
#include <pulsecore/random.h>
|
#include <pulsecore/random.h>
|
||||||
#include <pulsecore/core-rtclock.h>
|
#include <pulsecore/core-rtclock.h>
|
||||||
|
|
||||||
#include "rtsp_client.h"
|
#include "rtsp-client.h"
|
||||||
|
#include "rtsp-util.h"
|
||||||
|
|
||||||
#define RECONNECT_INTERVAL (5 * PA_USEC_PER_SEC)
|
#define RECONNECT_INTERVAL (5 * PA_USEC_PER_SEC)
|
||||||
|
|
||||||
|
enum wait_state {
|
||||||
|
WAIT_NONE,
|
||||||
|
WAIT_RESPONSE,
|
||||||
|
WAIT_HEADERS
|
||||||
|
};
|
||||||
|
|
||||||
|
enum auth_method {
|
||||||
|
AUTH_NONE,
|
||||||
|
AUTH_BASIC,
|
||||||
|
AUTH_DIGEST
|
||||||
|
};
|
||||||
|
|
||||||
struct pa_rtsp_client {
|
struct pa_rtsp_client {
|
||||||
pa_mainloop_api *mainloop;
|
pa_mainloop_api *mainloop;
|
||||||
char *hostname;
|
char *hostname;
|
||||||
|
|
@ -62,10 +76,16 @@ struct pa_rtsp_client {
|
||||||
|
|
||||||
void *userdata;
|
void *userdata;
|
||||||
const char *useragent;
|
const char *useragent;
|
||||||
|
const char *username;
|
||||||
|
const char *password;
|
||||||
|
enum auth_method mth;
|
||||||
|
char *realm, *nonce;
|
||||||
|
|
||||||
pa_rtsp_state_t state;
|
pa_rtsp_state_t state;
|
||||||
pa_rtsp_status_t status;
|
pa_rtsp_status_t status;
|
||||||
uint8_t waiting;
|
enum wait_state waiting;
|
||||||
|
pa_mutex *mutex;
|
||||||
|
int length;
|
||||||
|
|
||||||
pa_headerlist* headers;
|
pa_headerlist* headers;
|
||||||
char *last_header;
|
char *last_header;
|
||||||
|
|
@ -100,7 +120,13 @@ pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostna
|
||||||
else
|
else
|
||||||
c->useragent = "PulseAudio RTSP Client";
|
c->useragent = "PulseAudio RTSP Client";
|
||||||
|
|
||||||
|
c->mth = AUTH_NONE;
|
||||||
c->autoreconnect = autoreconnect;
|
c->autoreconnect = autoreconnect;
|
||||||
|
|
||||||
|
c->waiting = WAIT_NONE;
|
||||||
|
c->mutex = pa_mutex_new(false, false);
|
||||||
|
c->length = 0;
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,6 +154,10 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
|
||||||
pa_xfree(c->session);
|
pa_xfree(c->session);
|
||||||
pa_xfree(c->transport);
|
pa_xfree(c->transport);
|
||||||
pa_xfree(c->last_header);
|
pa_xfree(c->last_header);
|
||||||
|
pa_xfree(c->realm);
|
||||||
|
pa_xfree(c->nonce);
|
||||||
|
pa_mutex_free(c->mutex);
|
||||||
|
c->mutex = NULL;
|
||||||
if (c->header_buffer)
|
if (c->header_buffer)
|
||||||
pa_strbuf_free(c->header_buffer);
|
pa_strbuf_free(c->header_buffer);
|
||||||
if (c->response_headers)
|
if (c->response_headers)
|
||||||
|
|
@ -137,14 +167,78 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
|
||||||
pa_xfree(c);
|
pa_xfree(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void authenticate(pa_rtsp_client *c) {
|
||||||
|
const char *current = NULL;
|
||||||
|
const char *wath;
|
||||||
|
char space[] = " ";
|
||||||
|
char *token = NULL;
|
||||||
|
char *val = NULL, *mth = NULL;
|
||||||
|
char comma[] = ",";
|
||||||
|
|
||||||
|
pa_xfree(c->realm);
|
||||||
|
pa_xfree(c->nonce);
|
||||||
|
|
||||||
|
c->mth = AUTH_NONE;
|
||||||
|
c->realm = c->nonce = NULL;
|
||||||
|
|
||||||
|
if (!c->username || !c->password)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wath = pa_headerlist_gets(c->response_headers, "WWW-Authenticate");
|
||||||
|
|
||||||
|
if (!wath)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mth = pa_split(wath, space, ¤t);
|
||||||
|
|
||||||
|
if (pa_safe_streq(mth, "Basic"))
|
||||||
|
c->mth = AUTH_BASIC;
|
||||||
|
else if (pa_safe_streq(mth, "Digest"))
|
||||||
|
c->mth = AUTH_DIGEST;
|
||||||
|
else
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
while ((token = pa_split(wath, comma, ¤t))) {
|
||||||
|
if ((val = strstr(token, "="))) {
|
||||||
|
if (NULL == c->realm && val > strstr(token, "realm")) {
|
||||||
|
if (!(c->realm = pa_xstrdup(val + 2)))
|
||||||
|
goto done;
|
||||||
|
pa_rtsp_rtrim_char(c->realm, '\"');
|
||||||
|
}
|
||||||
|
else if (NULL == c->nonce && val > strstr(token, "nonce")) {
|
||||||
|
if (!(c->nonce = pa_xstrdup(val + 2)))
|
||||||
|
goto done;
|
||||||
|
pa_rtsp_rtrim_char(c->nonce, '\"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_xfree(token);
|
||||||
|
token = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
pa_xfree(token);
|
||||||
|
pa_xfree(mth);
|
||||||
|
}
|
||||||
|
|
||||||
static void headers_read(pa_rtsp_client *c) {
|
static void headers_read(pa_rtsp_client *c) {
|
||||||
char delimiters[] = ";";
|
char delimiters[] = ";";
|
||||||
char* token;
|
char* token = NULL;
|
||||||
|
const char *clength;
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
pa_assert(c->response_headers);
|
pa_assert(c->response_headers);
|
||||||
pa_assert(c->callback);
|
pa_assert(c->callback);
|
||||||
|
|
||||||
|
c->length = 0;
|
||||||
|
|
||||||
|
clength = pa_headerlist_gets(c->response_headers, "Content-Length");
|
||||||
|
if (clength && pa_atoi(clength, &c->length) < 0)
|
||||||
|
pa_log_warn("Unexpected value in content-length: %s", clength);
|
||||||
|
|
||||||
|
if (c->status == STATUS_UNAUTHORIZED)
|
||||||
|
authenticate(c);
|
||||||
|
|
||||||
/* Deal with a SETUP response */
|
/* Deal with a SETUP response */
|
||||||
if (STATE_SETUP == c->state) {
|
if (STATE_SETUP == c->state) {
|
||||||
const char* token_state = NULL;
|
const char* token_state = NULL;
|
||||||
|
|
@ -154,7 +248,7 @@ static void headers_read(pa_rtsp_client *c) {
|
||||||
|
|
||||||
if (!c->session || !c->transport) {
|
if (!c->session || !c->transport) {
|
||||||
pa_log("Invalid SETUP response.");
|
pa_log("Invalid SETUP response.");
|
||||||
return;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now parse out the server port component of the response. */
|
/* Now parse out the server port component of the response. */
|
||||||
|
|
@ -165,12 +259,10 @@ static void headers_read(pa_rtsp_client *c) {
|
||||||
|
|
||||||
if (pa_atou(pc + 1, &p) < 0 || p <= 0 || p > 0xffff) {
|
if (pa_atou(pc + 1, &p) < 0 || p <= 0 || p > 0xffff) {
|
||||||
pa_log("Invalid SETUP response (invalid server_port).");
|
pa_log("Invalid SETUP response (invalid server_port).");
|
||||||
pa_xfree(token);
|
goto done;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c->rtp_port = p;
|
c->rtp_port = p;
|
||||||
pa_xfree(token);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -179,15 +271,20 @@ static void headers_read(pa_rtsp_client *c) {
|
||||||
if (0 == c->rtp_port) {
|
if (0 == c->rtp_port) {
|
||||||
/* Error no server_port in response */
|
/* Error no server_port in response */
|
||||||
pa_log("Invalid SETUP response (no port number).");
|
pa_log("Invalid SETUP response (no port number).");
|
||||||
return;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
pa_xfree(token);
|
||||||
|
|
||||||
|
c->waiting = WAIT_NONE;
|
||||||
|
|
||||||
/* Call our callback */
|
/* Call our callback */
|
||||||
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 line_callback(pa_ioline *line, const char *s, void *userdata) {
|
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||||
pa_rtsp_client *c = userdata;
|
pa_rtsp_client *c = userdata;
|
||||||
char *delimpos;
|
char *delimpos;
|
||||||
char *s2, *s2p;
|
char *s2, *s2p;
|
||||||
|
|
@ -203,6 +300,25 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Skip any body from the last response */
|
||||||
|
if (c->length) {
|
||||||
|
if (l > c->length) {
|
||||||
|
l -= c->length;
|
||||||
|
s += c->length;
|
||||||
|
c->length = 0;
|
||||||
|
} else {
|
||||||
|
c->length -= l;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_assert(l);
|
||||||
|
|
||||||
|
if (c->waiting == WAIT_NONE) {
|
||||||
|
pa_log_warn("Received more data than content length");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
s2 = pa_xstrdup(s);
|
s2 = pa_xstrdup(s);
|
||||||
/* Trim trailing carriage returns */
|
/* Trim trailing carriage returns */
|
||||||
s2p = s2 + strlen(s2) - 1;
|
s2p = s2 + strlen(s2) - 1;
|
||||||
|
|
@ -211,23 +327,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||||
s2p -= 1;
|
s2p -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) {
|
if (c->waiting == WAIT_RESPONSE && pa_streq(s2, "RTSP/1.0 200 OK")) {
|
||||||
if (c->response_headers)
|
if (c->response_headers)
|
||||||
pa_headerlist_free(c->response_headers);
|
pa_headerlist_free(c->response_headers);
|
||||||
c->response_headers = pa_headerlist_new();
|
c->response_headers = pa_headerlist_new();
|
||||||
|
|
||||||
c->status = STATUS_OK;
|
c->status = STATUS_OK;
|
||||||
c->waiting = 0;
|
c->waiting = WAIT_HEADERS;
|
||||||
goto exit;
|
goto exit;
|
||||||
} else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
|
} else if (c->waiting == WAIT_RESPONSE && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
|
||||||
if (c->response_headers)
|
if (c->response_headers)
|
||||||
pa_headerlist_free(c->response_headers);
|
pa_headerlist_free(c->response_headers);
|
||||||
c->response_headers = pa_headerlist_new();
|
c->response_headers = pa_headerlist_new();
|
||||||
|
|
||||||
c->status = STATUS_UNAUTHORIZED;
|
c->status = STATUS_UNAUTHORIZED;
|
||||||
c->waiting = 0;
|
c->waiting = WAIT_HEADERS;
|
||||||
goto exit;
|
goto exit;
|
||||||
} else if (c->waiting) {
|
} else if (c->waiting == WAIT_RESPONSE) {
|
||||||
|
if (c->response_headers)
|
||||||
|
pa_headerlist_free(c->response_headers);
|
||||||
|
c->response_headers = pa_headerlist_new();
|
||||||
|
|
||||||
pa_log_warn("Unexpected/Unhandled response: %s", s2);
|
pa_log_warn("Unexpected/Unhandled response: %s", s2);
|
||||||
|
|
||||||
if (pa_streq(s2, "RTSP/1.0 400 Bad Request"))
|
if (pa_streq(s2, "RTSP/1.0 400 Bad Request"))
|
||||||
|
|
@ -236,6 +356,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||||
c->status = STATUS_INTERNAL_ERROR;
|
c->status = STATUS_INTERNAL_ERROR;
|
||||||
else
|
else
|
||||||
c->status = STATUS_NO_RESPONSE;
|
c->status = STATUS_NO_RESPONSE;
|
||||||
|
c->waiting = WAIT_HEADERS;
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,7 +373,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||||
c->header_buffer = NULL;
|
c->header_buffer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_log_debug("Full response received. Dispatching");
|
pa_log_debug("Response received. Dispatching");
|
||||||
headers_read(c);
|
headers_read(c);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -312,10 +433,10 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reconnect_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
|
static void reconnect_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
|
||||||
if (userdata) {
|
|
||||||
pa_rtsp_client *c = userdata;
|
pa_rtsp_client *c = userdata;
|
||||||
pa_rtsp_connect(c);
|
pa_assert(c);
|
||||||
}
|
if (pa_rtsp_connect(c))
|
||||||
|
c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
|
static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
|
||||||
|
|
@ -333,6 +454,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
|
||||||
pa_assert(c->sc == sc);
|
pa_assert(c->sc == sc);
|
||||||
pa_socket_client_unref(c->sc);
|
pa_socket_client_unref(c->sc);
|
||||||
c->sc = NULL;
|
c->sc = NULL;
|
||||||
|
c->waiting = WAIT_NONE;
|
||||||
|
|
||||||
if (!io) {
|
if (!io) {
|
||||||
if (c->autoreconnect) {
|
if (c->autoreconnect) {
|
||||||
|
|
@ -346,6 +468,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
|
||||||
c->mainloop->time_restart(c->reconnect_event, pa_timeval_rtstore(&tv, pa_rtclock_now() + RECONNECT_INTERVAL, true));
|
c->mainloop->time_restart(c->reconnect_event, pa_timeval_rtstore(&tv, pa_rtclock_now() + RECONNECT_INTERVAL, true));
|
||||||
} else {
|
} else {
|
||||||
pa_log("Connection to server %s:%d failed: %s", c->hostname, c->port, pa_cstrerror(errno));
|
pa_log("Connection to server %s:%d failed: %s", c->hostname, c->port, pa_cstrerror(errno));
|
||||||
|
c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -389,7 +512,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_socket_client_set_callback(c->sc, on_connection, c);
|
pa_socket_client_set_callback(c->sc, on_connection, c);
|
||||||
c->waiting = 1;
|
c->waiting = WAIT_RESPONSE;
|
||||||
c->state = STATE_CONNECT;
|
c->state = STATE_CONNECT;
|
||||||
c->status = STATUS_NO_RESPONSE;
|
c->status = STATUS_NO_RESPONSE;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -424,18 +547,18 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) {
|
||||||
return c->rtp_port;
|
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) {
|
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) {
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
|
pa_xfree(c->url);
|
||||||
c->url = pa_xstrdup(url);
|
c->url = pa_xstrdup(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password) {
|
||||||
|
c->username = username;
|
||||||
|
c->password = password;
|
||||||
|
}
|
||||||
|
|
||||||
bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) {
|
bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) {
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
pa_assert(key);
|
pa_assert(key);
|
||||||
|
|
@ -465,22 +588,63 @@ void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) {
|
||||||
pa_headerlist_remove(c->headers, key);
|
pa_headerlist_remove(c->headers, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
static char *get_auth(pa_rtsp_client *c, const char *method, const char *url) {
|
||||||
const char *content_type, const char *content,
|
char *ath = NULL, *response = NULL;
|
||||||
int expect_response,
|
|
||||||
pa_headerlist *headers) {
|
pa_assert(method);
|
||||||
|
pa_assert(url);
|
||||||
|
|
||||||
|
if (!c->username || !c->password)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (c->mth) {
|
||||||
|
case AUTH_NONE:
|
||||||
|
break;
|
||||||
|
case AUTH_BASIC:
|
||||||
|
pa_rtsp_basic_response(c->username, c->password, &response);
|
||||||
|
ath = pa_sprintf_malloc("Basic %s", response);
|
||||||
|
break;
|
||||||
|
case AUTH_DIGEST:
|
||||||
|
pa_rtsp_digest_response(c->username, c->realm, c->password, c->nonce, method, url, &response);
|
||||||
|
ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
|
||||||
|
c->username, c->realm, c->nonce, url, response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_xfree(response);
|
||||||
|
|
||||||
|
return ath;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtsp_exec(pa_rtsp_client *c, const char *cmd, const char *url,
|
||||||
|
pa_headerlist *headers, const char *content_type, const char *content) {
|
||||||
pa_strbuf *buf;
|
pa_strbuf *buf;
|
||||||
char *hdrs;
|
char *hdrs;
|
||||||
|
|
||||||
pa_assert(c);
|
|
||||||
pa_assert(c->url);
|
|
||||||
pa_assert(cmd);
|
pa_assert(cmd);
|
||||||
|
pa_assert(url);
|
||||||
|
pa_assert(c);
|
||||||
pa_assert(c->ioline);
|
pa_assert(c->ioline);
|
||||||
|
|
||||||
|
if (!pa_mutex_try_lock(c->mutex)) {
|
||||||
|
pa_log_warn("Can't send command (locked): %s", cmd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c->waiting != WAIT_NONE) {
|
||||||
|
pa_log_warn("Can't send command (busy): %s", cmd);
|
||||||
|
pa_mutex_unlock(c->mutex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
pa_log_debug("Sending command: %s", cmd);
|
pa_log_debug("Sending command: %s", cmd);
|
||||||
|
|
||||||
|
c->waiting = WAIT_RESPONSE;
|
||||||
|
|
||||||
|
pa_mutex_unlock(c->mutex);
|
||||||
|
|
||||||
buf = pa_strbuf_new();
|
buf = pa_strbuf_new();
|
||||||
pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq);
|
pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, url, ++c->cseq);
|
||||||
if (c->session)
|
if (c->session)
|
||||||
pa_strbuf_printf(buf, "Session: %s\r\n", c->session);
|
pa_strbuf_printf(buf, "Session: %s\r\n", c->session);
|
||||||
|
|
||||||
|
|
@ -496,6 +660,13 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
||||||
content_type, (int)strlen(content));
|
content_type, (int)strlen(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *auth = get_auth(c, cmd, url);
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
pa_strbuf_printf(buf, "Authorization: %s\r\n", auth);
|
||||||
|
pa_xfree(auth);
|
||||||
|
}
|
||||||
|
|
||||||
pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent);
|
pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent);
|
||||||
|
|
||||||
if (c->headers) {
|
if (c->headers) {
|
||||||
|
|
@ -516,24 +687,18 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
||||||
pa_log_debug(hdrs);*/
|
pa_log_debug(hdrs);*/
|
||||||
pa_ioline_puts(c->ioline, hdrs);
|
pa_ioline_puts(c->ioline, hdrs);
|
||||||
pa_xfree(hdrs);
|
pa_xfree(hdrs);
|
||||||
/* The command is sent we can configure the rtsp client structure to handle a new answer */
|
|
||||||
c->waiting = 1;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_rtsp_options(pa_rtsp_client *c) {
|
int pa_rtsp_options(pa_rtsp_client *c) {
|
||||||
char *url;
|
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
url = c->url;
|
if (!(rv = rtsp_exec(c, "OPTIONS", "*", NULL, NULL, NULL)))
|
||||||
c->state = STATE_OPTIONS;
|
c->state = STATE_OPTIONS;
|
||||||
|
|
||||||
c->url = (char *)"*";
|
|
||||||
rv = rtsp_exec(c, "OPTIONS", NULL, NULL, 0, NULL);
|
|
||||||
|
|
||||||
c->url = url;
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -545,8 +710,8 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) {
|
||||||
if (!sdp)
|
if (!sdp)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "ANNOUNCE", c->url, NULL, "application/sdp", sdp)))
|
||||||
c->state = STATE_ANNOUNCE;
|
c->state = STATE_ANNOUNCE;
|
||||||
rv = rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL);
|
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
@ -563,8 +728,8 @@ int pa_rtsp_setup(pa_rtsp_client *c, const char *transport) {
|
||||||
else
|
else
|
||||||
pa_headerlist_puts(headers, "Transport", transport);
|
pa_headerlist_puts(headers, "Transport", transport);
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "SETUP", c->url, headers, NULL, NULL)))
|
||||||
c->state = STATE_SETUP;
|
c->state = STATE_SETUP;
|
||||||
rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers);
|
|
||||||
|
|
||||||
pa_headerlist_free(headers);
|
pa_headerlist_free(headers);
|
||||||
return rv;
|
return rv;
|
||||||
|
|
@ -591,8 +756,8 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) {
|
||||||
pa_headerlist_puts(headers, "RTP-Info", info);
|
pa_headerlist_puts(headers, "RTP-Info", info);
|
||||||
pa_xfree(info);
|
pa_xfree(info);
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "RECORD", c->url, headers, NULL, NULL)))
|
||||||
c->state = STATE_RECORD;
|
c->state = STATE_RECORD;
|
||||||
rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers);
|
|
||||||
|
|
||||||
pa_headerlist_free(headers);
|
pa_headerlist_free(headers);
|
||||||
return rv;
|
return rv;
|
||||||
|
|
@ -606,8 +771,19 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) {
|
||||||
if (!param)
|
if (!param)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "SET_PARAMETER", c->url, NULL, "text/parameters", param)))
|
||||||
c->state = STATE_SET_PARAMETER;
|
c->state = STATE_SET_PARAMETER;
|
||||||
rv = rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL);
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pa_rtsp_post(pa_rtsp_client *c, const char *url) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
pa_assert(c);
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "POST", url, NULL, NULL, NULL)))
|
||||||
|
c->state = STATE_POST;
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
@ -624,8 +800,8 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) {
|
||||||
pa_headerlist_puts(headers, "RTP-Info", info);
|
pa_headerlist_puts(headers, "RTP-Info", info);
|
||||||
pa_xfree(info);
|
pa_xfree(info);
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "FLUSH", c->url, headers, NULL, NULL)))
|
||||||
c->state = STATE_FLUSH;
|
c->state = STATE_FLUSH;
|
||||||
rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers);
|
|
||||||
|
|
||||||
pa_headerlist_free(headers);
|
pa_headerlist_free(headers);
|
||||||
return rv;
|
return rv;
|
||||||
|
|
@ -636,8 +812,8 @@ int pa_rtsp_teardown(pa_rtsp_client *c) {
|
||||||
|
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
||||||
|
if (!(rv = rtsp_exec(c, "TEARDOWN", c->url, NULL, NULL, NULL)))
|
||||||
c->state = STATE_TEARDOWN;
|
c->state = STATE_TEARDOWN;
|
||||||
rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
|
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
@ -39,6 +39,7 @@ typedef enum pa_rtsp_state {
|
||||||
STATE_SETUP,
|
STATE_SETUP,
|
||||||
STATE_RECORD,
|
STATE_RECORD,
|
||||||
STATE_SET_PARAMETER,
|
STATE_SET_PARAMETER,
|
||||||
|
STATE_POST,
|
||||||
STATE_FLUSH,
|
STATE_FLUSH,
|
||||||
STATE_TEARDOWN,
|
STATE_TEARDOWN,
|
||||||
STATE_DISCONNECTED
|
STATE_DISCONNECTED
|
||||||
|
|
@ -63,9 +64,9 @@ void pa_rtsp_disconnect(pa_rtsp_client *c);
|
||||||
|
|
||||||
const char* pa_rtsp_localip(pa_rtsp_client *c);
|
const char* pa_rtsp_localip(pa_rtsp_client *c);
|
||||||
uint32_t pa_rtsp_serverport(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);
|
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url);
|
||||||
|
void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password);
|
||||||
|
|
||||||
bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key);
|
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);
|
void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value);
|
||||||
|
|
@ -77,6 +78,7 @@ 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_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_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_post(pa_rtsp_client *c, const char *url);
|
||||||
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);
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
#include <pulsecore/core-util.h>
|
#include <pulsecore/core-util.h>
|
||||||
#include <pulsecore/macro.h>
|
#include <pulsecore/macro.h>
|
||||||
|
|
||||||
#include "raop-util.h"
|
#include "rtsp-util.h"
|
||||||
|
|
||||||
#ifndef MD5_DIGEST_LENGTH
|
#ifndef MD5_DIGEST_LENGTH
|
||||||
#define MD5_DIGEST_LENGTH 16
|
#define MD5_DIGEST_LENGTH 16
|
||||||
|
|
@ -94,7 +94,7 @@ static unsigned int token_decode(const char *token) {
|
||||||
return (marker << 24) | val;
|
return (marker << 24) | val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_base64_encode(const void *data, int len, char **str) {
|
int pa_rtsp_base64_encode(const void *data, int len, char **str) {
|
||||||
const unsigned char *q;
|
const unsigned char *q;
|
||||||
char *p, *s = NULL;
|
char *p, *s = NULL;
|
||||||
int i, c;
|
int i, c;
|
||||||
|
|
@ -130,7 +130,7 @@ int pa_raop_base64_encode(const void *data, int len, char **str) {
|
||||||
return strlen(s);
|
return strlen(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_base64_decode(const char *str, void *data) {
|
int pa_rtsp_base64_decode(const char *str, void *data) {
|
||||||
const char *p;
|
const char *p;
|
||||||
unsigned char *q;
|
unsigned char *q;
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ int pa_raop_base64_decode(const char *str, void *data) {
|
||||||
return q - (unsigned char *) data;
|
return q - (unsigned char *) data;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_md5_hash(const char *data, int len, char **str) {
|
int pa_rtsp_md5_hash(const char *data, int len, char **str) {
|
||||||
unsigned char d[MD5_DIGEST_LENGTH];
|
unsigned char d[MD5_DIGEST_LENGTH];
|
||||||
char *s = NULL;
|
char *s = NULL;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -162,7 +162,7 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
|
||||||
pa_assert(str);
|
pa_assert(str);
|
||||||
|
|
||||||
MD5((unsigned char*) data, len, d);
|
MD5((unsigned char*) data, len, d);
|
||||||
s = pa_xnew(char, MD5_HASH_LENGTH);
|
s = pa_xnew(char, MD5_HASH_LENGTH+1);
|
||||||
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
|
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
|
||||||
sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
|
sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
|
||||||
|
|
||||||
|
|
@ -171,36 +171,36 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
|
||||||
return strlen(s);
|
return strlen(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_basic_response(const char *user, const char *pwd, char **str) {
|
int pa_rtsp_basic_response(const char *user, const char *pwd, char **str) {
|
||||||
char *tmp, *B = NULL;
|
char *tmp, *B = NULL;
|
||||||
|
|
||||||
pa_assert(str);
|
pa_assert(str);
|
||||||
|
|
||||||
tmp = pa_sprintf_malloc("%s:%s", user, pwd);
|
tmp = pa_sprintf_malloc("%s:%s", user, pwd);
|
||||||
pa_raop_base64_encode(tmp, strlen(tmp), &B);
|
pa_rtsp_base64_encode(tmp, strlen(tmp), &B);
|
||||||
pa_xfree(tmp);
|
pa_xfree(tmp);
|
||||||
|
|
||||||
*str = B;
|
*str = B;
|
||||||
return strlen(B);
|
return strlen(B);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
int pa_rtsp_digest_response(const char *user, const char *realm, const char *password,
|
||||||
const char *nonce, const char *uri, char **str) {
|
const char *nonce, const char *method, const char *uri, char **str) {
|
||||||
char *A1, *HA1, *A2, *HA2;
|
char *A1, *HA1, *A2, *HA2;
|
||||||
char *tmp, *KD = NULL;
|
char *tmp, *KD = NULL;
|
||||||
|
|
||||||
pa_assert(str);
|
pa_assert(str);
|
||||||
|
|
||||||
A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
|
A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
|
||||||
pa_raop_md5_hash(A1, strlen(A1), &HA1);
|
pa_rtsp_md5_hash(A1, strlen(A1), &HA1);
|
||||||
pa_xfree(A1);
|
pa_xfree(A1);
|
||||||
|
|
||||||
A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
|
A2 = pa_sprintf_malloc("%s:%s", method, uri);
|
||||||
pa_raop_md5_hash(A2, strlen(A2), &HA2);
|
pa_rtsp_md5_hash(A2, strlen(A2), &HA2);
|
||||||
pa_xfree(A2);
|
pa_xfree(A2);
|
||||||
|
|
||||||
tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
|
tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
|
||||||
pa_raop_md5_hash(tmp, strlen(tmp), &KD);
|
pa_rtsp_md5_hash(tmp, strlen(tmp), &KD);
|
||||||
pa_xfree(tmp);
|
pa_xfree(tmp);
|
||||||
|
|
||||||
pa_xfree(HA1);
|
pa_xfree(HA1);
|
||||||
|
|
@ -209,3 +209,16 @@ int pa_raop_digest_response(const char *user, const char *realm, const char *pas
|
||||||
*str = KD;
|
*str = KD;
|
||||||
return strlen(KD);
|
return strlen(KD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to trim a given character at the end of a string (no realloc).
|
||||||
|
* @param str Pointer to string
|
||||||
|
* @param rc Character to trim
|
||||||
|
*/
|
||||||
|
void pa_rtsp_rtrim_char(char *str, char rc) {
|
||||||
|
char *sp = str + strlen(str) - 1;
|
||||||
|
while (sp >= str && *sp == rc) {
|
||||||
|
*sp = '\0';
|
||||||
|
sp -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef fooraoputilfoo
|
#ifndef foortsputilfoo
|
||||||
#define fooraoputilfoo
|
#define foortsputilfoo
|
||||||
|
|
||||||
/***
|
/***
|
||||||
This file is part of PulseAudio.
|
This file is part of PulseAudio.
|
||||||
|
|
@ -27,15 +27,15 @@
|
||||||
Kungliga Tekniska högskolan.
|
Kungliga Tekniska högskolan.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
|
int pa_rtsp_base64_encode(const void *data, int len, char **str);
|
||||||
|
int pa_rtsp_base64_decode(const char *str, void *data);
|
||||||
|
|
||||||
int pa_raop_base64_encode(const void *data, int len, char **str);
|
int pa_rtsp_md5_hash(const char *data, int len, char **str);
|
||||||
int pa_raop_base64_decode(const char *str, void *data);
|
|
||||||
|
|
||||||
int pa_raop_md5_hash(const char *data, int len, char **str);
|
int pa_rtsp_basic_response(const char *user, const char *pwd, char **str);
|
||||||
|
int pa_rtsp_digest_response(const char *user, const char *realm, const char *password,
|
||||||
|
const char *nonce, const char *method, const char *uri, char **str);
|
||||||
|
|
||||||
int pa_raop_basic_response(const char *user, const char *pwd, char **str);
|
void pa_rtsp_rtrim_char(char *str, char rc);
|
||||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
|
||||||
const char *nonce, const char *uri, char **str);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -57,7 +57,7 @@ struct pa_cli {
|
||||||
char *last_line;
|
char *last_line;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void line_callback(pa_ioline *line, const char *s, void *userdata);
|
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata);
|
||||||
static void client_kill(pa_client *c);
|
static void client_kill(pa_client *c);
|
||||||
|
|
||||||
pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {
|
pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {
|
||||||
|
|
@ -117,7 +117,7 @@ static void client_kill(pa_client *client) {
|
||||||
c->eof_callback(c, c->userdata);
|
c->eof_callback(c, c->userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||||
pa_strbuf *buf;
|
pa_strbuf *buf;
|
||||||
pa_cli *c = userdata;
|
pa_cli *c = userdata;
|
||||||
char *p;
|
char *p;
|
||||||
|
|
|
||||||
|
|
@ -227,14 +227,14 @@ static void failure(pa_ioline *l, bool process_leftover) {
|
||||||
/* Pass the last missing bit to the client */
|
/* Pass the last missing bit to the client */
|
||||||
|
|
||||||
if (l->callback) {
|
if (l->callback) {
|
||||||
char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
char *p = pa_xmemdup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
||||||
l->callback(l, p, l->userdata);
|
l->callback(l, p, l->rbuf_valid_length, l->userdata);
|
||||||
pa_xfree(p);
|
pa_xfree(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l->callback) {
|
if (l->callback) {
|
||||||
l->callback(l, NULL, l->userdata);
|
l->callback(l, NULL, 0, l->userdata);
|
||||||
l->callback = NULL;
|
l->callback = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,7 +256,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
|
||||||
*e = 0;
|
*e = 0;
|
||||||
|
|
||||||
p = l->rbuf + l->rbuf_index;
|
p = l->rbuf + l->rbuf_index;
|
||||||
m = strlen(p);
|
m = e - p;
|
||||||
|
|
||||||
l->rbuf_index += m+1;
|
l->rbuf_index += m+1;
|
||||||
l->rbuf_valid_length -= m+1;
|
l->rbuf_valid_length -= m+1;
|
||||||
|
|
@ -266,7 +266,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
|
||||||
l->rbuf_index = 0;
|
l->rbuf_index = 0;
|
||||||
|
|
||||||
if (l->callback)
|
if (l->callback)
|
||||||
l->callback(l, pa_strip_nl(p), l->userdata);
|
l->callback(l, pa_strip_nl(p), m, l->userdata);
|
||||||
|
|
||||||
skip = 0;
|
skip = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,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, size_t l, 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);
|
||||||
|
|
|
||||||
|
|
@ -629,7 +629,7 @@ static void handle_url(struct connection *c) {
|
||||||
html_response(c, 404, "Not Found", NULL);
|
html_response(c, 404, "Not Found", NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||||
struct connection *c = userdata;
|
struct connection *c = userdata;
|
||||||
pa_assert(line);
|
pa_assert(line);
|
||||||
pa_assert(c);
|
pa_assert(c);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue