mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04: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;
|
||||
};
|
||||
|
||||
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;
|
||||
pa_module *m = NULL;
|
||||
unsigned devnum;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ libraop_sources = [
|
|||
'raop-crypto.c',
|
||||
'raop-packet-buffer.c',
|
||||
'raop-sink.c',
|
||||
'raop-util.c',
|
||||
]
|
||||
|
||||
libraop_headers = [
|
||||
|
|
@ -11,7 +10,7 @@ libraop_headers = [
|
|||
'raop-crypto.h',
|
||||
'raop-packet-buffer.h',
|
||||
'raop-sink.h',
|
||||
'raop-util.h',
|
||||
'raop-common.h',
|
||||
]
|
||||
|
||||
# FIXME: meson doesn't support multiple RPATH arguments currently
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/avahi-wrap.h>
|
||||
|
||||
#include "raop-util.h"
|
||||
#include "raop-common.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Colin Guthrie");
|
||||
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -65,8 +65,7 @@ bool pa_raop_client_is_authenticated(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_recording(pa_raop_client *c);
|
||||
bool pa_raop_client_can_stream(pa_raop_client *c);
|
||||
bool pa_raop_client_is_streaming(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_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_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);
|
||||
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);
|
||||
|
||||
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/random.h>
|
||||
|
||||
#include <modules/rtp/rtsp-util.h>
|
||||
|
||||
#include "raop-crypto.h"
|
||||
#include "raop-util.h"
|
||||
|
||||
#define AES_CHUNK_SIZE 16
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
|||
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);
|
||||
if (!n_bn) {
|
||||
|
|
@ -100,7 +101,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
|||
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);
|
||||
if (!e_bn) {
|
||||
|
|
@ -165,7 +166,7 @@ char* pa_raop_secret_get_iv(pa_raop_secret *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;
|
||||
}
|
||||
|
|
@ -184,7 +185,7 @@ char* pa_raop_secret_get_key(pa_raop_secret *s) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
pa_raop_base64_encode(rsa_key, size, &base64_key);
|
||||
pa_rtsp_base64_encode(rsa_key, size, &base64_key);
|
||||
|
||||
return base64_key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
#include "raop-sink.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_DISCONNECT_CYCLE 3
|
||||
|
|
@ -79,6 +79,7 @@ struct userdata {
|
|||
pa_module *module;
|
||||
pa_sink *sink;
|
||||
pa_card *card;
|
||||
pa_subscription *subscription;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
|
|
@ -91,6 +92,7 @@ struct userdata {
|
|||
pa_raop_protocol_t protocol;
|
||||
pa_raop_encryption_t encryption;
|
||||
pa_raop_codec_t codec;
|
||||
char *password;
|
||||
bool autoreconnect;
|
||||
/* if true, behaves like a null-sink when disconnected */
|
||||
bool autonull;
|
||||
|
|
@ -115,7 +117,9 @@ struct userdata {
|
|||
|
||||
enum {
|
||||
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);
|
||||
|
|
@ -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_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) {
|
||||
|
|
@ -156,6 +161,44 @@ static int64_t sink_get_latency(const struct userdata *u) {
|
|||
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) {
|
||||
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);
|
||||
|
||||
switch (code) {
|
||||
/* Exception : for this message, we are in main thread, msg sent from the IO/thread
|
||||
Done here, as alloc/free of rtsp_client is also done in this thread for other cases */
|
||||
case PA_SINK_MESSAGE_DISCONNECT_REQUEST: {
|
||||
if (u->sink->state == PA_SINK_RUNNING) {
|
||||
/* 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_CONNECTED: {
|
||||
pa_assert_ctl_context();
|
||||
pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_YES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_assert_io_context();
|
||||
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);
|
||||
|
||||
*((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: {
|
||||
pa_assert_io_context();
|
||||
switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) {
|
||||
case PA_RAOP_AUTHENTICATED: {
|
||||
if (!pa_raop_client_is_authenticated(u->raop)) {
|
||||
pa_module_unload_request(u->module, true);
|
||||
}
|
||||
|
||||
if (u->autoreconnect && u->sink->state == PA_SINK_RUNNING) {
|
||||
if (u->sink->state == PA_SINK_RUNNING) {
|
||||
pa_usec_t now;
|
||||
now = pa_rtclock_now();
|
||||
#ifdef USE_SMOOTHER_2
|
||||
|
|
@ -202,11 +253,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
#else
|
||||
pa_smoother_reset(u->smoother, now, false);
|
||||
#endif
|
||||
|
||||
if (!pa_raop_client_is_alive(u->raop)) {
|
||||
/* Connecting will trigger a RECORD and start steaming */
|
||||
pa_raop_client_announce(u->raop);
|
||||
}
|
||||
/* Connecting will trigger a RECORD and start streaming */
|
||||
pa_raop_client_announce(u->raop);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_CONNECTED, NULL, 0, NULL, NULL);
|
||||
|
||||
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_DISCONNECTED: {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
disconnect(u);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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)) {
|
||||
/* Connecting will trigger a RECORD and start streaming */
|
||||
pa_raop_client_announce(u->raop);
|
||||
} else if (!pa_raop_client_is_recording(u->raop)) {
|
||||
/* RECORD alredy sent, simply start streaming */
|
||||
if (pa_raop_client_is_authenticated(u->raop))
|
||||
pa_raop_client_announce(u->raop);
|
||||
else
|
||||
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_rtpoll_set_timer_absolute(u->rtpoll, now);
|
||||
u->write_count = 0;
|
||||
|
|
@ -445,7 +467,7 @@ static void thread_func(void *userdata) {
|
|||
uint64_t position;
|
||||
size_t index;
|
||||
int ret;
|
||||
bool canstream, sendstream, on_timeout;
|
||||
bool is_streaming, on_timeout;
|
||||
#ifndef USE_SMOOTHER_2
|
||||
pa_usec_t estimated;
|
||||
#endif
|
||||
|
|
@ -478,25 +500,26 @@ static void thread_func(void *userdata) {
|
|||
|
||||
/* if oob: streaming managed by timing, pollfd for oob sockets */
|
||||
if (pollfd && u->oob && !on_timeout) {
|
||||
uint8_t packet[32];
|
||||
ssize_t read;
|
||||
|
||||
for (i = 0; i < nbfds; i++) {
|
||||
if (pollfd->revents & POLLERR) {
|
||||
if (u->autoreconnect && pa_raop_client_is_alive(u->raop)) {
|
||||
pollfd->revents = 0;
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* one of UDP fds is in faulty state, may have been disconnected, this is fatal */
|
||||
goto fail;
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
if (pollfd->revents & pollfd->events) {
|
||||
pollfd->revents = 0;
|
||||
read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
|
||||
pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read);
|
||||
if (pa_raop_client_handle_oob_packet(u->raop, pollfd->fd) < 0) {
|
||||
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)) {
|
||||
last_timing = pa_rtclock_now();
|
||||
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;
|
||||
}
|
||||
|
||||
if (u->first) {
|
||||
last_timing = 0;
|
||||
|
|
@ -521,11 +543,11 @@ static void thread_func(void *userdata) {
|
|||
u->first = false;
|
||||
}
|
||||
|
||||
canstream = pa_raop_client_can_stream(u->raop);
|
||||
is_streaming = pa_raop_client_is_streaming(u->raop);
|
||||
now = pa_rtclock_now();
|
||||
|
||||
if (u->oob && u->autoreconnect && on_timeout) {
|
||||
if (!canstream) {
|
||||
if (!is_streaming) {
|
||||
last_timing = 0;
|
||||
} else if (last_timing != 0) {
|
||||
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);
|
||||
check_timing_count++;
|
||||
} else {
|
||||
/* Limit reached, then request disconnect */
|
||||
/* Limit reached, then disconnect */
|
||||
check_timing_count = 1;
|
||||
last_timing = 0;
|
||||
if (pa_raop_client_is_alive(u->raop)) {
|
||||
pa_log_warn("UDP Timing Packets Warn limit reached - Requesting reconnect");
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
||||
continue;
|
||||
}
|
||||
pa_log_warn("UDP Timing Packets Warn limit reached - disconnecting");
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!u->autonull) {
|
||||
if (!canstream) {
|
||||
pa_log_debug("Can't stream, connection not established yet...");
|
||||
if (!is_streaming)
|
||||
continue;
|
||||
}
|
||||
/* This assertion is meant to silence a complaint from Coverity about
|
||||
* 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
|
||||
* connection is up, pollfd will be non-NULL. */
|
||||
pa_assert(pollfd);
|
||||
|
|
@ -584,36 +601,30 @@ static void thread_func(void *userdata) {
|
|||
|
||||
if (u->memchunk.length > 0) {
|
||||
index = u->memchunk.index;
|
||||
sendstream = !u->autonull || (u->autonull && canstream);
|
||||
if (sendstream && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
||||
if (is_streaming && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
||||
if (errno == EINTR) {
|
||||
/* Just try again. */
|
||||
pa_log_debug("Failed to write data to FIFO (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);
|
||||
pa_log_debug("Failed to write audio packet (EINTR), retrying");
|
||||
continue;
|
||||
} else if (errno == EAGAIN) {
|
||||
if (u->oob) {
|
||||
/* Just try again. */
|
||||
pa_log_debug("Failed to write audio packet (EAGAIN), retrying");
|
||||
continue;
|
||||
} else
|
||||
goto fail;
|
||||
} else if (errno != EAGAIN && !u->oob) {
|
||||
/* Buffer is full, wait for POLLOUT. */
|
||||
if (!u->oob) {
|
||||
} else {
|
||||
/* Buffer is full, wait for POLLOUT. */
|
||||
pollfd->events = POLLOUT;
|
||||
pollfd->revents = 0;
|
||||
}
|
||||
} else {
|
||||
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
|
||||
if (u->autoreconnect) {
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST,
|
||||
0, 0, NULL, NULL);
|
||||
continue;
|
||||
} else
|
||||
goto fail;
|
||||
pa_log("Failed to write audio packet: %s", pa_cstrerror(errno));
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (sendstream) {
|
||||
if (is_streaming) {
|
||||
u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index;
|
||||
} else {
|
||||
} else if (u->autonull) {
|
||||
u->write_count += u->memchunk.length;
|
||||
u->memchunk.length = 0;
|
||||
}
|
||||
|
|
@ -627,7 +638,7 @@ static void thread_func(void *userdata) {
|
|||
pa_smoother_put(u->smoother, now, estimated);
|
||||
#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 */
|
||||
intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
pa_device_port_new_data data;
|
||||
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;
|
||||
}
|
||||
|
||||
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->set_state_in_io_thread = sink_set_state_in_io_thread_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); */
|
||||
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;
|
||||
|
||||
|
|
@ -969,6 +998,10 @@ static void userdata_free(struct userdata *u) {
|
|||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
|
||||
if (u->subscription)
|
||||
pa_subscription_free(u->subscription);
|
||||
u->subscription = NULL;
|
||||
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
if (u->rtpoll)
|
||||
|
|
@ -995,6 +1028,8 @@ static void userdata_free(struct userdata *u) {
|
|||
pa_card_free(u->card);
|
||||
if (u->server)
|
||||
pa_xfree(u->server);
|
||||
if (u->password)
|
||||
pa_xfree(u->password);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ librtp_sources = [
|
|||
'rtp-common.c',
|
||||
'sdp.c',
|
||||
'sap.c',
|
||||
'rtsp_client.c',
|
||||
'rtsp-client.c',
|
||||
'headerlist.c',
|
||||
'rtsp-util.c'
|
||||
]
|
||||
|
||||
librtp_headers = [
|
||||
'rtp.h',
|
||||
'sdp.h',
|
||||
'sap.h',
|
||||
'rtsp_client.h',
|
||||
'rtsp-client.h',
|
||||
'headerlist.h',
|
||||
'rtsp-util.h'
|
||||
]
|
||||
|
||||
if have_gstreamer
|
||||
|
|
@ -26,7 +28,7 @@ librtp = shared_library('rtp',
|
|||
c_args : [pa_c_args, server_c_args],
|
||||
link_args : [nodelete_link_args],
|
||||
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_rpath : privlibdir,
|
||||
install_dir : modlibexecdir,
|
||||
|
|
|
|||
|
|
@ -40,16 +40,30 @@
|
|||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/mutex.h>
|
||||
#include <pulsecore/strbuf.h>
|
||||
#include <pulsecore/ioline.h>
|
||||
#include <pulsecore/arpa-inet.h>
|
||||
#include <pulsecore/random.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)
|
||||
|
||||
enum wait_state {
|
||||
WAIT_NONE,
|
||||
WAIT_RESPONSE,
|
||||
WAIT_HEADERS
|
||||
};
|
||||
|
||||
enum auth_method {
|
||||
AUTH_NONE,
|
||||
AUTH_BASIC,
|
||||
AUTH_DIGEST
|
||||
};
|
||||
|
||||
struct pa_rtsp_client {
|
||||
pa_mainloop_api *mainloop;
|
||||
char *hostname;
|
||||
|
|
@ -62,10 +76,16 @@ struct pa_rtsp_client {
|
|||
|
||||
void *userdata;
|
||||
const char *useragent;
|
||||
const char *username;
|
||||
const char *password;
|
||||
enum auth_method mth;
|
||||
char *realm, *nonce;
|
||||
|
||||
pa_rtsp_state_t state;
|
||||
pa_rtsp_status_t status;
|
||||
uint8_t waiting;
|
||||
enum wait_state waiting;
|
||||
pa_mutex *mutex;
|
||||
int length;
|
||||
|
||||
pa_headerlist* headers;
|
||||
char *last_header;
|
||||
|
|
@ -100,7 +120,13 @@ pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostna
|
|||
else
|
||||
c->useragent = "PulseAudio RTSP Client";
|
||||
|
||||
c->mth = AUTH_NONE;
|
||||
c->autoreconnect = autoreconnect;
|
||||
|
||||
c->waiting = WAIT_NONE;
|
||||
c->mutex = pa_mutex_new(false, false);
|
||||
c->length = 0;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +154,10 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
|
|||
pa_xfree(c->session);
|
||||
pa_xfree(c->transport);
|
||||
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)
|
||||
pa_strbuf_free(c->header_buffer);
|
||||
if (c->response_headers)
|
||||
|
|
@ -137,14 +167,78 @@ void pa_rtsp_client_free(pa_rtsp_client *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) {
|
||||
char delimiters[] = ";";
|
||||
char* token;
|
||||
char* token = NULL;
|
||||
const char *clength;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(c->response_headers);
|
||||
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 */
|
||||
if (STATE_SETUP == c->state) {
|
||||
const char* token_state = NULL;
|
||||
|
|
@ -154,7 +248,7 @@ static void headers_read(pa_rtsp_client *c) {
|
|||
|
||||
if (!c->session || !c->transport) {
|
||||
pa_log("Invalid SETUP response.");
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
pa_log("Invalid SETUP response (invalid server_port).");
|
||||
pa_xfree(token);
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
|
||||
c->rtp_port = p;
|
||||
pa_xfree(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -179,15 +271,20 @@ static void headers_read(pa_rtsp_client *c) {
|
|||
if (0 == c->rtp_port) {
|
||||
/* Error no server_port in response */
|
||||
pa_log("Invalid SETUP response (no port number).");
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
pa_xfree(token);
|
||||
|
||||
c->waiting = WAIT_NONE;
|
||||
|
||||
/* Call our callback */
|
||||
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;
|
||||
char *delimpos;
|
||||
char *s2, *s2p;
|
||||
|
|
@ -203,6 +300,25 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
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);
|
||||
/* Trim trailing carriage returns */
|
||||
s2p = s2 + strlen(s2) - 1;
|
||||
|
|
@ -211,23 +327,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
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)
|
||||
pa_headerlist_free(c->response_headers);
|
||||
c->response_headers = pa_headerlist_new();
|
||||
|
||||
c->status = STATUS_OK;
|
||||
c->waiting = 0;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
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)
|
||||
pa_headerlist_free(c->response_headers);
|
||||
c->response_headers = pa_headerlist_new();
|
||||
|
||||
c->status = STATUS_UNAUTHORIZED;
|
||||
c->waiting = 0;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
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);
|
||||
|
||||
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;
|
||||
else
|
||||
c->status = STATUS_NO_RESPONSE;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +373,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
c->header_buffer = NULL;
|
||||
}
|
||||
|
||||
pa_log_debug("Full response received. Dispatching");
|
||||
pa_log_debug("Response received. Dispatching");
|
||||
headers_read(c);
|
||||
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) {
|
||||
if (userdata) {
|
||||
pa_rtsp_client *c = userdata;
|
||||
pa_rtsp_connect(c);
|
||||
}
|
||||
pa_rtsp_client *c = userdata;
|
||||
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) {
|
||||
|
|
@ -333,6 +454,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
|
|||
pa_assert(c->sc == sc);
|
||||
pa_socket_client_unref(c->sc);
|
||||
c->sc = NULL;
|
||||
c->waiting = WAIT_NONE;
|
||||
|
||||
if (!io) {
|
||||
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));
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
|
@ -389,7 +512,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) {
|
|||
}
|
||||
|
||||
pa_socket_client_set_callback(c->sc, on_connection, c);
|
||||
c->waiting = 1;
|
||||
c->waiting = WAIT_RESPONSE;
|
||||
c->state = STATE_CONNECT;
|
||||
c->status = STATUS_NO_RESPONSE;
|
||||
return 0;
|
||||
|
|
@ -424,18 +547,18 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) {
|
|||
return c->rtp_port;
|
||||
}
|
||||
|
||||
bool pa_rtsp_exec_ready(const pa_rtsp_client *c) {
|
||||
pa_assert(c);
|
||||
|
||||
return c->url != NULL && c->ioline != NULL;
|
||||
}
|
||||
|
||||
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) {
|
||||
pa_assert(c);
|
||||
|
||||
pa_xfree(c->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) {
|
||||
pa_assert(c);
|
||||
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);
|
||||
}
|
||||
|
||||
static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
||||
const char *content_type, const char *content,
|
||||
int expect_response,
|
||||
pa_headerlist *headers) {
|
||||
static char *get_auth(pa_rtsp_client *c, const char *method, const char *url) {
|
||||
char *ath = NULL, *response = NULL;
|
||||
|
||||
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;
|
||||
char *hdrs;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(c->url);
|
||||
pa_assert(cmd);
|
||||
pa_assert(url);
|
||||
pa_assert(c);
|
||||
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);
|
||||
|
||||
c->waiting = WAIT_RESPONSE;
|
||||
|
||||
pa_mutex_unlock(c->mutex);
|
||||
|
||||
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)
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (c->headers) {
|
||||
|
|
@ -516,24 +687,18 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
|||
pa_log_debug(hdrs);*/
|
||||
pa_ioline_puts(c->ioline, 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;
|
||||
}
|
||||
|
||||
int pa_rtsp_options(pa_rtsp_client *c) {
|
||||
char *url;
|
||||
int rv;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
url = c->url;
|
||||
c->state = STATE_OPTIONS;
|
||||
if (!(rv = rtsp_exec(c, "OPTIONS", "*", NULL, NULL, NULL)))
|
||||
c->state = STATE_OPTIONS;
|
||||
|
||||
c->url = (char *)"*";
|
||||
rv = rtsp_exec(c, "OPTIONS", NULL, NULL, 0, NULL);
|
||||
|
||||
c->url = url;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
@ -545,8 +710,8 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) {
|
|||
if (!sdp)
|
||||
return -1;
|
||||
|
||||
c->state = STATE_ANNOUNCE;
|
||||
rv = rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL);
|
||||
if (!(rv = rtsp_exec(c, "ANNOUNCE", c->url, NULL, "application/sdp", sdp)))
|
||||
c->state = STATE_ANNOUNCE;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -563,8 +728,8 @@ int pa_rtsp_setup(pa_rtsp_client *c, const char *transport) {
|
|||
else
|
||||
pa_headerlist_puts(headers, "Transport", transport);
|
||||
|
||||
c->state = STATE_SETUP;
|
||||
rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers);
|
||||
if (!(rv = rtsp_exec(c, "SETUP", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_SETUP;
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
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_xfree(info);
|
||||
|
||||
c->state = STATE_RECORD;
|
||||
rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers);
|
||||
if (!(rv = rtsp_exec(c, "RECORD", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_RECORD;
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
return rv;
|
||||
|
|
@ -606,8 +771,19 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) {
|
|||
if (!param)
|
||||
return -1;
|
||||
|
||||
c->state = STATE_SET_PARAMETER;
|
||||
rv = rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL);
|
||||
if (!(rv = rtsp_exec(c, "SET_PARAMETER", c->url, NULL, "text/parameters", param)))
|
||||
c->state = STATE_SET_PARAMETER;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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_xfree(info);
|
||||
|
||||
c->state = STATE_FLUSH;
|
||||
rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers);
|
||||
if (!(rv = rtsp_exec(c, "FLUSH", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_FLUSH;
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
return rv;
|
||||
|
|
@ -636,8 +812,8 @@ int pa_rtsp_teardown(pa_rtsp_client *c) {
|
|||
|
||||
pa_assert(c);
|
||||
|
||||
c->state = STATE_TEARDOWN;
|
||||
rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
|
||||
if (!(rv = rtsp_exec(c, "TEARDOWN", c->url, NULL, NULL, NULL)))
|
||||
c->state = STATE_TEARDOWN;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ typedef enum pa_rtsp_state {
|
|||
STATE_SETUP,
|
||||
STATE_RECORD,
|
||||
STATE_SET_PARAMETER,
|
||||
STATE_POST,
|
||||
STATE_FLUSH,
|
||||
STATE_TEARDOWN,
|
||||
STATE_DISCONNECTED
|
||||
|
|
@ -63,9 +64,9 @@ void pa_rtsp_disconnect(pa_rtsp_client *c);
|
|||
|
||||
const char* pa_rtsp_localip(pa_rtsp_client *c);
|
||||
uint32_t pa_rtsp_serverport(pa_rtsp_client *c);
|
||||
bool pa_rtsp_exec_ready(const pa_rtsp_client *c);
|
||||
|
||||
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url);
|
||||
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);
|
||||
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_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_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_teardown(pa_rtsp_client *c);
|
||||
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "raop-util.h"
|
||||
#include "rtsp-util.h"
|
||||
|
||||
#ifndef MD5_DIGEST_LENGTH
|
||||
#define MD5_DIGEST_LENGTH 16
|
||||
|
|
@ -94,7 +94,7 @@ static unsigned int token_decode(const char *token) {
|
|||
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;
|
||||
char *p, *s = NULL;
|
||||
int i, c;
|
||||
|
|
@ -130,7 +130,7 @@ int pa_raop_base64_encode(const void *data, int len, char **str) {
|
|||
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;
|
||||
unsigned char *q;
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ int pa_raop_base64_decode(const char *str, void *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];
|
||||
char *s = NULL;
|
||||
int i;
|
||||
|
|
@ -162,7 +162,7 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
|
|||
pa_assert(str);
|
||||
|
||||
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++)
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
pa_assert(str);
|
||||
|
||||
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);
|
||||
|
||||
*str = B;
|
||||
return strlen(B);
|
||||
}
|
||||
|
||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *uri, 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) {
|
||||
char *A1, *HA1, *A2, *HA2;
|
||||
char *tmp, *KD = NULL;
|
||||
|
||||
pa_assert(str);
|
||||
|
||||
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);
|
||||
|
||||
A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
|
||||
pa_raop_md5_hash(A2, strlen(A2), &HA2);
|
||||
A2 = pa_sprintf_malloc("%s:%s", method, uri);
|
||||
pa_rtsp_md5_hash(A2, strlen(A2), &HA2);
|
||||
pa_xfree(A2);
|
||||
|
||||
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(HA1);
|
||||
|
|
@ -209,3 +209,16 @@ int pa_raop_digest_response(const char *user, const char *realm, const char *pas
|
|||
*str = 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
|
||||
#define fooraoputilfoo
|
||||
#ifndef foortsputilfoo
|
||||
#define foortsputilfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
|
@ -27,15 +27,15 @@
|
|||
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_raop_base64_decode(const char *str, void *data);
|
||||
int pa_rtsp_md5_hash(const char *data, int len, char **str);
|
||||
|
||||
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);
|
||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *uri, char **str);
|
||||
void pa_rtsp_rtrim_char(char *str, char rc);
|
||||
|
||||
#endif
|
||||
|
|
@ -57,7 +57,7 @@ struct pa_cli {
|
|||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_cli *c = userdata;
|
||||
char *p;
|
||||
|
|
|
|||
|
|
@ -227,14 +227,14 @@ static void failure(pa_ioline *l, bool process_leftover) {
|
|||
/* Pass the last missing bit to the client */
|
||||
|
||||
if (l->callback) {
|
||||
char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
||||
l->callback(l, p, l->userdata);
|
||||
char *p = pa_xmemdup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
||||
l->callback(l, p, l->rbuf_valid_length, l->userdata);
|
||||
pa_xfree(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (l->callback) {
|
||||
l->callback(l, NULL, l->userdata);
|
||||
l->callback(l, NULL, 0, l->userdata);
|
||||
l->callback = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +256,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
|
|||
*e = 0;
|
||||
|
||||
p = l->rbuf + l->rbuf_index;
|
||||
m = strlen(p);
|
||||
m = e - p;
|
||||
|
||||
l->rbuf_index += 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;
|
||||
|
||||
if (l->callback)
|
||||
l->callback(l, pa_strip_nl(p), l->userdata);
|
||||
l->callback(l, pa_strip_nl(p), m, l->userdata);
|
||||
|
||||
skip = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
pa_assert(line);
|
||||
pa_assert(c);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue