[raop] Reorganise code a little

This commit is contained in:
ejurgensen 2019-02-08 21:52:44 +01:00
parent fcc91ecd86
commit a924a8dd66

View File

@ -352,6 +352,16 @@ static struct raop_session *raop_sessions;
/* Struct with default quality levels */
static struct media_quality raop_quality_default = { RAOP_QUALITY_SAMPLE_RATE_DEFAULT, RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT, RAOP_QUALITY_CHANNELS_DEFAULT };
// Forwards
static int
raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime);
static void
raop_device_stop(struct output_session *session);
/* ------------------------------- MISC HELPERS ----------------------------- */
/* ALAC bits writer - big endian
* p outgoing buffer pointer
* val bitfield value
@ -469,10 +479,10 @@ raop_v2_timing_get_clock_ntp(struct ntp_stamp *ns)
}
/* RAOP crypto stuff - from VLC */
/* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
* specification.
*/
/* ----------------------- RAOP crypto stuff - from VLC --------------------- */
// MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
// specification.
static int
raop_crypt_mgf1(uint8_t *mask, size_t l, const uint8_t *z, const size_t zlen, const int hash)
{
@ -811,146 +821,8 @@ raop_crypt_encrypt_aes_key_base64(void)
}
/* RAOP metadata */
static void
raop_metadata_free(struct raop_metadata *rmd)
{
evbuffer_free(rmd->metadata);
if (rmd->artwork)
evbuffer_free(rmd->artwork);
free(rmd);
}
/* ------------------ Helpers for sending RAOP/RTSP requests ---------------- */
static void
raop_metadata_purge(void)
{
struct raop_metadata *rmd;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
metadata_tail = NULL;
}
static void
raop_metadata_prune(uint64_t rtptime)
{
struct raop_metadata *rmd;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
if (rmd->end >= rtptime)
break;
if (metadata_tail == metadata_head)
metadata_tail = rmd->next;
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
}
/* Thread: worker */
static void *
raop_metadata_prepare(int id)
{
struct db_queue_item *queue_item;
struct raop_metadata *rmd;
struct evbuffer *tmp;
int ret;
rmd = (struct raop_metadata *)malloc(sizeof(struct raop_metadata));
if (!rmd)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP metadata\n");
return NULL;
}
memset(rmd, 0, sizeof(struct raop_metadata));
queue_item = db_queue_fetch_byitemid(id);
if (!queue_item)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for queue item\n");
goto out_rmd;
}
/* Get artwork */
rmd->artwork = evbuffer_new();
if (!rmd->artwork)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for artwork evbuffer; no artwork will be sent\n");
goto skip_artwork;
}
ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT);
if (ret < 0)
{
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file id %d; no artwork will be sent\n", id);
evbuffer_free(rmd->artwork);
rmd->artwork = NULL;
}
rmd->artwork_fmt = ret;
skip_artwork:
/* Turn it into DAAP metadata */
tmp = evbuffer_new();
if (!tmp)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for temporary metadata evbuffer; metadata will not be sent\n");
goto out_qi;
}
rmd->metadata = evbuffer_new();
if (!rmd->metadata)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for metadata evbuffer; metadata will not be sent\n");
evbuffer_free(tmp);
goto out_qi;
}
ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item);
evbuffer_free(tmp);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not encode file metadata; metadata will not be sent\n");
goto out_metadata;
}
/* Progress - raop_metadata_send() will add rtptime to these */
rmd->start = 0;
rmd->end = ((uint64_t)queue_item->song_length * 44100UL) / 1000UL;
free_queue_item(queue_item, 0);
return rmd;
out_metadata:
evbuffer_free(rmd->metadata);
out_qi:
free_queue_item(queue_item, 0);
out_rmd:
free(rmd);
return NULL;
}
/* Helpers */
static int
raop_add_auth(struct raop_session *rs, struct evrtsp_request *req, const char *method, const char *uri)
{
@ -1304,7 +1176,8 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address
}
/* RAOP/RTSP requests */
/* ----------------- Handlers for sending RAOP/RTSP requests ---------------- */
/*
* Request queueing HOWTO
*
@ -2273,6 +2146,143 @@ session_make(struct output_device *rd, int family, output_status_cb cb, bool onl
/* ----------------------------- Metadata handling -------------------------- */
static void
raop_metadata_free(struct raop_metadata *rmd)
{
evbuffer_free(rmd->metadata);
if (rmd->artwork)
evbuffer_free(rmd->artwork);
free(rmd);
}
static void
raop_metadata_purge(void)
{
struct raop_metadata *rmd;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
metadata_tail = NULL;
}
static void
raop_metadata_prune(uint64_t rtptime)
{
struct raop_metadata *rmd;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
if (rmd->end >= rtptime)
break;
if (metadata_tail == metadata_head)
metadata_tail = rmd->next;
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
}
/* Thread: worker */
static void *
raop_metadata_prepare(int id)
{
struct db_queue_item *queue_item;
struct raop_metadata *rmd;
struct evbuffer *tmp;
int ret;
rmd = (struct raop_metadata *)malloc(sizeof(struct raop_metadata));
if (!rmd)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP metadata\n");
return NULL;
}
memset(rmd, 0, sizeof(struct raop_metadata));
queue_item = db_queue_fetch_byitemid(id);
if (!queue_item)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for queue item\n");
goto out_rmd;
}
/* Get artwork */
rmd->artwork = evbuffer_new();
if (!rmd->artwork)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for artwork evbuffer; no artwork will be sent\n");
goto skip_artwork;
}
ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT);
if (ret < 0)
{
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file id %d; no artwork will be sent\n", id);
evbuffer_free(rmd->artwork);
rmd->artwork = NULL;
}
rmd->artwork_fmt = ret;
skip_artwork:
/* Turn it into DAAP metadata */
tmp = evbuffer_new();
if (!tmp)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for temporary metadata evbuffer; metadata will not be sent\n");
goto out_qi;
}
rmd->metadata = evbuffer_new();
if (!rmd->metadata)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for metadata evbuffer; metadata will not be sent\n");
evbuffer_free(tmp);
goto out_qi;
}
ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item);
evbuffer_free(tmp);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not encode file metadata; metadata will not be sent\n");
goto out_metadata;
}
/* Progress - raop_metadata_send() will add rtptime to these */
rmd->start = 0;
rmd->end = ((uint64_t)queue_item->song_length * 44100UL) / 1000UL;
free_queue_item(queue_item, 0);
return rmd;
out_metadata:
evbuffer_free(rmd->metadata);
out_qi:
free_queue_item(queue_item, 0);
out_rmd:
free(rmd);
return NULL;
}
static void
raop_cb_metadata(struct evrtsp_request *req, void *arg)
{
@ -2553,7 +2563,8 @@ raop_metadata_send(void *metadata, uint64_t rtptime, uint64_t offset, int startu
}
}
/* Volume handling */
/* ------------------------------ Volume handling --------------------------- */
static float
raop_volume_from_pct(int volume, char *name)
@ -2787,48 +2798,6 @@ raop_cb_keep_alive(struct evrtsp_request *req, void *arg)
return;
}
static void
raop_cb_pin_start(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
int ret;
rs->reqs_in_flight--;
if (!req)
goto error;
if (req->response_code != RTSP_OK)
{
DPRINTF(E_LOG, L_RAOP, "Request for starting PIN verification failed: %d %s\n", req->response_code, req->response_code_line);
goto error;
}
ret = raop_check_cseq(rs, req);
if (ret < 0)
goto error;
rs->state = RAOP_STATE_UNVERIFIED;
raop_status(rs);
// TODO If the user never verifies the session will remain stale
return;
error:
session_failure(rs);
}
// Forward
static int
raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime);
static void
raop_device_stop(struct output_session *session);
static void
raop_flush_timer_cb(int fd, short what, void *arg)
{
@ -2859,47 +2828,176 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
}
}
/* -------------------- Creation and sending of RTP packets ---------------- */
static int
raop_flush(output_status_cb cb, uint64_t rtptime)
packet_prepare(struct rtp_packet *pkt, uint8_t *rawbuf, size_t rawbuf_size, bool encrypt)
{
char ebuf[64];
gpg_error_t gc_err;
alac_encode(pkt->payload, rawbuf, rawbuf_size);
if (!encrypt)
return 0;
// Reset cipher
gc_err = gcry_cipher_reset(raop_aes_ctx);
if (gc_err != GPG_ERR_NO_ERROR)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not reset AES cipher: %s\n", ebuf);
return -1;
}
// Set IV
gc_err = gcry_cipher_setiv(raop_aes_ctx, raop_aes_iv, sizeof(raop_aes_iv));
if (gc_err != GPG_ERR_NO_ERROR)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not set AES IV: %s\n", ebuf);
return -1;
}
// Encrypt in blocks of 16 bytes
gc_err = gcry_cipher_encrypt(raop_aes_ctx, pkt->payload, (pkt->payload_len / 16) * 16, NULL, 0);
if (gc_err != GPG_ERR_NO_ERROR)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not encrypt payload: %s\n", ebuf);
return -1;
}
return 0;
}
static int
packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
struct timeval tv;
struct raop_session *rs;
struct raop_session *next;
int pending;
int ret;
pending = 0;
for (rs = raop_sessions; rs; rs = next)
if (!rs)
return -1;
ret = send(rs->server_fd, pkt->data, pkt->data_len, 0);
if (ret < 0)
{
next = rs->next;
DPRINTF(E_LOG, L_RAOP, "Send error for '%s': %s\n", rs->devname, strerror(errno));
if (rs->state != RAOP_STATE_STREAMING)
continue;
session_failure(rs);
return -1;
}
else if (ret != pkt->data_len)
{
DPRINTF(E_WARN, L_RAOP, "Partial send (%d) for '%s'\n", ret, rs->devname);
return -1;
}
ret = raop_send_req_flush(rs, rtptime, raop_cb_flush, "flush");
return 0;
}
static void
control_packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
int len;
int ret;
switch (rs->sa.ss.ss_family)
{
case AF_INET:
rs->sa.sin.sin_port = htons(rs->control_port);
len = sizeof(rs->sa.sin);
break;
case AF_INET6:
rs->sa.sin6.sin6_port = htons(rs->control_port);
len = sizeof(rs->sa.sin6);
break;
default:
DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family);
return;
}
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len);
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
}
static void
packets_resend(struct raop_session *rs, uint16_t seqnum, uint16_t len)
{
struct rtp_packet *pkt;
uint16_t s;
bool pkt_missing = false;
for (s = seqnum; s < seqnum + len; s++)
{
pkt = rtp_packet_get(rs->master_session->rtp_session, s);
if (pkt)
packet_send(rs, pkt);
else
pkt_missing = true;
}
if (pkt_missing)
DPRINTF(E_WARN, L_RAOP, "Device '%s' asking for seqnum %" PRIu16 " (len %" PRIu16 "), but not in buffer\n", rs->devname, seqnum, len);
}
static int
frame_send(struct raop_master_session *rms)
{
struct rtp_packet *pkt;
struct rtp_packet *sync_pkt;
struct raop_session *rs;
struct raop_session *next;
bool sync_send;
int ret;
while (evbuffer_get_length(rms->evbuf) >= rms->rawbuf_size)
{
evbuffer_remove(rms->evbuf, rms->rawbuf, rms->rawbuf_size);
pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size, rms->samples_per_packet);
ret = packet_prepare(pkt, rms->rawbuf, rms->rawbuf_size, rms->encrypt);
if (ret < 0)
{
session_failure(rs);
return -1;
continue;
sync_send = rtp_sync_check(rms->rtp_session, pkt);
if (sync_send)
sync_pkt = rtp_sync_packet_next(rms->rtp_session);
for (rs = raop_sessions; rs; rs = next)
{
// packet_send() may free rs on failure, so save rs->next now
next = rs->next;
// A device could have joined after playback_start() was called, so we
// also update state here
if (rs->state == RAOP_STATE_CONNECTED)
rs->state = RAOP_STATE_STREAMING;
if (rs->master_session != rms || rs->state != RAOP_STATE_STREAMING)
continue;
if (sync_send)
control_packet_send(rs, sync_pkt);
packet_send(rs, pkt);
}
rs->status_cb = cb;
pending++;
// Commits packet to retransmit buffer, and prepares the session for the next packet
rtp_packet_commit(rms->rtp_session, pkt);
}
if (pending > 0)
{
evutil_timerclear(&tv);
tv.tv_sec = 10;
evtimer_add(flush_timer, &tv);
}
return pending;
return 0;
}
/* AirTunes v2 time synchronization */
/* ------------------------------ Time service ------------------------------ */
static void
raop_v2_timing_cb(int fd, short what, void *arg)
{
@ -3144,38 +3242,7 @@ raop_v2_timing_start(int v6enabled)
}
/* AirTunes v2 playback synchronization */
static void
sync_packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
int len;
int ret;
switch (rs->sa.ss.ss_family)
{
case AF_INET:
rs->sa.sin.sin_port = htons(rs->control_port);
len = sizeof(rs->sa.sin);
break;
case AF_INET6:
rs->sa.sin6.sin6_port = htons(rs->control_port);
len = sizeof(rs->sa.sin6);
break;
default:
DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family);
return;
}
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len);
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
}
/* Forward */
static void
packets_resend(struct raop_session *rs, uint16_t seqnum, uint16_t len);
/* ----------------- Control service (retransmission and sync) ---------------*/
static void
raop_v2_control_cb(int fd, short what, void *arg)
@ -3429,177 +3496,72 @@ raop_v2_control_start(int v6enabled)
}
/* AirTunes v2 streaming */
static int
packet_prepare(struct rtp_packet *pkt, uint8_t *rawbuf, size_t rawbuf_size, bool encrypt)
/* ------------------------------ Session startup --------------------------- */
static void
raop_startup_cancel(struct raop_session *rs)
{
char ebuf[64];
gpg_error_t gc_err;
alac_encode(pkt->payload, rawbuf, rawbuf_size);
if (!encrypt)
return 0;
// Reset cipher
gc_err = gcry_cipher_reset(raop_aes_ctx);
if (gc_err != GPG_ERR_NO_ERROR)
if (!rs->session)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not reset AES cipher: %s\n", ebuf);
return -1;
}
// Set IV
gc_err = gcry_cipher_setiv(raop_aes_ctx, raop_aes_iv, sizeof(raop_aes_iv));
if (gc_err != GPG_ERR_NO_ERROR)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not set AES IV: %s\n", ebuf);
return -1;
}
// Encrypt in blocks of 16 bytes
gc_err = gcry_cipher_encrypt(raop_aes_ctx, pkt->payload, (pkt->payload_len / 16) * 16, NULL, 0);
if (gc_err != GPG_ERR_NO_ERROR)
{
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
DPRINTF(E_LOG, L_RAOP, "Could not encrypt payload: %s\n", ebuf);
return -1;
}
return 0;
}
static int
packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
int ret;
if (!rs)
return -1;
ret = send(rs->server_fd, pkt->data, pkt->data_len, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Send error for '%s': %s\n", rs->devname, strerror(errno));
session_failure(rs);
return -1;
}
else if (ret != pkt->data_len)
{
DPRINTF(E_WARN, L_RAOP, "Partial send (%d) for '%s'\n", ret, rs->devname);
return -1;
return;
}
return 0;
// Some devices don't seem to work with ipv6, so if the error wasn't a hard
// failure (bad password) we fall back to ipv4 and flag device as bad for ipv6
if (rs->family == AF_INET6 && !(rs->state & RAOP_STATE_F_FAILED))
{
// This flag is permanent and will not be overwritten by mdns advertisements
rs->device->v6_disabled = 1;
// Be nice to our peer + session_failure_cb() cleans up old session
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
// Try to start a new session
raop_device_start(rs->device, rs->status_cb, rs->start_rtptime);
// Don't let the failed session make a negative status callback
rs->status_cb = NULL;
return;
}
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
}
static void
packets_resend(struct raop_session *rs, uint16_t seqnum, uint16_t len)
raop_cb_pin_start(struct evrtsp_request *req, void *arg)
{
struct rtp_packet *pkt;
uint16_t s;
bool pkt_missing = false;
for (s = seqnum; s < seqnum + len; s++)
{
pkt = rtp_packet_get(rs->master_session->rtp_session, s);
if (pkt)
packet_send(rs, pkt);
else
pkt_missing = true;
}
if (pkt_missing)
DPRINTF(E_WARN, L_RAOP, "Device '%s' asking for seqnum %" PRIu16 " (len %" PRIu16 "), but not in buffer\n", rs->devname, seqnum, len);
}
// Forward
static void
raop_playback_stop(void);
static int
frame_send(struct raop_master_session *rms)
{
struct rtp_packet *pkt;
struct rtp_packet *sync_pkt;
struct raop_session *rs;
struct raop_session *next;
bool sync_send;
struct raop_session *rs = arg;
int ret;
while (evbuffer_get_length(rms->evbuf) >= rms->rawbuf_size)
rs->reqs_in_flight--;
if (!req)
goto error;
if (req->response_code != RTSP_OK)
{
evbuffer_remove(rms->evbuf, rms->rawbuf, rms->rawbuf_size);
DPRINTF(E_LOG, L_RAOP, "Request for starting PIN verification failed: %d %s\n", req->response_code, req->response_code_line);
pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size, rms->samples_per_packet);
ret = packet_prepare(pkt, rms->rawbuf, rms->rawbuf_size, rms->encrypt);
if (ret < 0)
{
raop_playback_stop();
return -1;
}
sync_send = rtp_sync_check(rms->rtp_session, pkt);
if (sync_send)
sync_pkt = rtp_sync_packet_next(rms->rtp_session);
for (rs = raop_sessions; rs; rs = next)
{
// packet_send() may free rs on failure, so save rs->next now
next = rs->next;
// A device could have joined after playback_start() was called, so we
// also update state here
if (rs->state == RAOP_STATE_CONNECTED)
rs->state = RAOP_STATE_STREAMING;
if (rs->master_session != rms || rs->state != RAOP_STATE_STREAMING)
continue;
if (sync_send)
sync_packet_send(rs, sync_pkt);
packet_send(rs, pkt);
}
// Commits packet to retransmit buffer, and prepares the session for the next packet
rtp_packet_commit(rms->rtp_session, pkt);
goto error;
}
return 0;
ret = raop_check_cseq(rs, req);
if (ret < 0)
goto error;
rs->state = RAOP_STATE_UNVERIFIED;
raop_status(rs);
// TODO If the user never verifies the session will remain stale
return;
error:
session_failure(rs);
}
static void
raop_write(struct output_buffer *obuf)
{
struct raop_master_session *rms;
int i;
for (rms = raop_master_sessions; rms; rms = rms->next)
{
for (i = 0; obuf->frames[i].buffer; i++)
{
if (quality_is_equal(&obuf->frames[i].quality, &rms->rtp_session->quality))
{
/* DPRINTF(E_LOG, L_RAOP, "raop_write: stream %d, bufsize %zu, samples %d (%d/%d/%d)\n", 0, obuf->frames[0].bufsize, obuf->frames[0].samples,
obuf->frames[0].quality.sample_rate, obuf->frames[0].quality.bits_per_sample, obuf->frames[0].quality.channels);
DPRINTF(E_LOG, L_RAOP, "raop_write: stream %d, bufsize %zu, samples %d (%d/%d/%d)\n", i, obuf->frames[i].bufsize, obuf->frames[i].samples,
obuf->frames[i].quality.sample_rate, obuf->frames[i].quality.bits_per_sample, obuf->frames[i].quality.channels);
*/
evbuffer_add_buffer_reference(rms->evbuf, obuf->frames[i].evbuf);
frame_send(rms);
}
}
}
}
static int
raop_v2_stream_open(struct raop_session *rs)
{
@ -3654,38 +3616,6 @@ raop_v2_stream_open(struct raop_session *rs)
return -1;
}
/* Session startup */
static void
raop_startup_cancel(struct raop_session *rs)
{
if (!rs->session)
{
session_failure(rs);
return;
}
// Some devices don't seem to work with ipv6, so if the error wasn't a hard
// failure (bad password) we fall back to ipv4 and flag device as bad for ipv6
if (rs->family == AF_INET6 && !(rs->state & RAOP_STATE_F_FAILED))
{
// This flag is permanent and will not be overwritten by mdns advertisements
rs->device->v6_disabled = 1;
// Be nice to our peer + session_failure_cb() cleans up old session
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
// Try to start a new session
raop_device_start(rs->device, rs->status_cb, rs->start_rtptime);
// Don't let the failed session make a negative status callback
rs->status_cb = NULL;
return;
}
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
}
static void
raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
{
@ -4120,7 +4050,8 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
}
/* tvOS device verification - e.g. for the ATV4 (read it from the bottom and up) */
/* ------------------------- tvOS device verification ----------------------- */
/* e.g. for the ATV4 (read it from the bottom and up) */
#ifdef RAOP_VERIFICATION
static int
@ -4481,8 +4412,10 @@ raop_verification_verify(struct raop_session *rs)
}
#endif /* RAOP_VERIFICATION */
/* RAOP devices discovery - mDNS callback */
/* Thread: main (mdns) */
/* ------------------ RAOP devices discovery - mDNS callback ---------------- */
/* Thread: main (mdns) */
/* Examples of txt content:
* Apple TV 2:
["sf=0x4" "am=AppleTV2,1" "vs=130.14" "vn=65537" "tp=UDP" "ss=16" "sr=4 4100" "sv=false" "pw=false" "md=0,1,2" "et=0,3,5" "da=true" "cn=0,1,2,3" "ch=2"]
@ -4749,6 +4682,10 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
outputs_device_free(rd);
}
/* ---------------------------- Module definitions -------------------------- */
/* Thread: player */
static int
raop_device_start_generic(struct output_device *rd, output_status_cb cb, bool only_probe)
{
@ -4856,7 +4793,7 @@ raop_playback_start(struct timespec *start_time)
rs->state = RAOP_STATE_STREAMING;
if (sync_pkt && rs->state == RAOP_STATE_STREAMING)
sync_packet_send(rs, sync_pkt);
control_packet_send(rs, sync_pkt);
}
}
}
@ -4877,6 +4814,64 @@ raop_playback_stop(void)
}
}
static void
raop_write(struct output_buffer *obuf)
{
struct raop_master_session *rms;
int i;
for (rms = raop_master_sessions; rms; rms = rms->next)
{
for (i = 0; obuf->frames[i].buffer; i++)
{
if (!quality_is_equal(&obuf->frames[i].quality, &rms->rtp_session->quality))
continue;
evbuffer_add_buffer_reference(rms->evbuf, obuf->frames[i].evbuf);
frame_send(rms);
}
}
}
static int
raop_flush(output_status_cb cb, uint64_t rtptime)
{
struct timeval tv;
struct raop_session *rs;
struct raop_session *next;
int pending;
int ret;
pending = 0;
for (rs = raop_sessions; rs; rs = next)
{
next = rs->next;
if (rs->state != RAOP_STATE_STREAMING)
continue;
ret = raop_send_req_flush(rs, rtptime, raop_cb_flush, "flush");
if (ret < 0)
{
session_failure(rs);
continue;
}
rs->status_cb = cb;
pending++;
}
if (pending > 0)
{
evutil_timerclear(&tv);
tv.tv_sec = 10;
evtimer_add(flush_timer, &tv);
}
return pending;
}
static void
raop_set_status_cb(struct output_session *session, output_status_cb cb)
{