/***
This file is part of PulseAudio.
Copyright 2008 Colin Guthrie
Copyright 2013 Hajime Fujita
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 .
***/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SYS_FILIO_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "raop-client.h"
#include "raop-packet-buffer.h"
#include "raop-crypto.h"
#include "raop-util.h"
#define DEFAULT_RAOP_PORT 5000
#define FRAMES_PER_TCP_PACKET 4096
#define FRAMES_PER_UDP_PACKET 352
#define RTX_BUFFERING_SECONDS 4
#define DEFAULT_TCP_AUDIO_PORT 6000
#define DEFAULT_UDP_AUDIO_PORT 6000
#define DEFAULT_UDP_CONTROL_PORT 6001
#define DEFAULT_UDP_TIMING_PORT 6002
#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
#define DEFAULT_USER_NAME "iTunes"
#define JACK_STATUS_DISCONNECTED 0
#define JACK_STATUS_CONNECTED 1
#define JACK_TYPE_ANALOG 0
#define JACK_TYPE_DIGITAL 1
#define VOLUME_MAX 0.0
#define VOLUME_DEF -30.0
#define VOLUME_MIN -144.0
#define UDP_DEFAULT_PKT_BUF_SIZE 1000
#define APPLE_CHALLENGE_LENGTH 16
struct pa_raop_client {
pa_core *core;
char *host;
uint16_t port;
pa_rtsp_client *rtsp;
char *sci, *sid;
char *password;
bool autoreconnect;
pa_raop_protocol_t protocol;
pa_raop_encryption_t encryption;
pa_raop_codec_t codec;
pa_raop_secret *secret;
int tcp_sfd;
int udp_sfd;
int udp_cfd;
int udp_tfd;
pa_raop_packet_buffer *pbuf;
uint16_t seq;
uint32_t rtptime;
bool is_recording;
uint32_t ssrc;
bool is_first_packet;
uint32_t sync_interval;
uint32_t sync_count;
uint8_t jack_type;
uint8_t jack_status;
pa_raop_client_state_cb_t state_callback;
void *state_userdata;
};
/* Audio TCP packet header [16x8] (cf. rfc4571):
* [0,1] Frame marker; seems always 0x2400
* [2,3] RTP packet size (following): 0x0000 (to be set)
* [4,5] RTP v2: 0x80
* [5] Payload type: 0x60 | Marker bit: 0x80 (always set)
* [6,7] Sequence number: 0x0000 (to be set)
* [8,11] Timestamp: 0x00000000 (to be set)
* [12,15] SSRC: 0x00000000 (to be set) */
#define PAYLOAD_TCP_AUDIO_DATA 0x60
static const uint8_t tcp_audio_header[16] = {
0x24, 0x00, 0x00, 0x00,
0x80, 0xe0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
/* Audio UDP packet header [12x8] (cf. rfc3550):
* [0] RTP v2: 0x80
* [1] Payload type: 0x60
* [2,3] Sequence number: 0x0000 (to be set)
* [4,7] Timestamp: 0x00000000 (to be set)
* [8,12] SSRC: 0x00000000 (to be set) */
#define PAYLOAD_UDP_AUDIO_DATA 0x60
static const uint8_t udp_audio_header[12] = {
0x80, 0x60, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
/* Audio retransmission UDP packet header [4x8]:
* [0] RTP v2: 0x80
* [1] Payload type: 0x56 | Marker bit: 0x80 (always set)
* [2] Unknown; seems always 0x01
* [3] Unknown; seems some random number around 0x20~0x40 */
#define PAYLOAD_RETRANSMIT_REQUEST 0x55
#define PAYLOAD_RETRANSMIT_REPLY 0x56
static const uint8_t udp_audio_retrans_header[4] = {
0x80, 0xd6, 0x00, 0x00
};
/* Sync packet header [8x8] (cf. rfc3550):
* [0] RTP v2: 0x80
* [1] Payload type: 0x54 | Marker bit: 0x80 (always set)
* [2,3] Sequence number: 0x0007
* [4,7] Timestamp: 0x00000000 (to be set) */
static const uint8_t udp_sync_header[8] = {
0x80, 0xd4, 0x00, 0x07,
0x00, 0x00, 0x00, 0x00
};
/* Timing packet header [8x8] (cf. rfc3550):
* [0] RTP v2: 0x80
* [1] Payload type: 0x53 | Marker bit: 0x80 (always set)
* [2,3] Sequence number: 0x0007
* [4,7] Timestamp: 0x00000000 (unused) */
#define PAYLOAD_TIMING_REQUEST 0x52
#define PAYLOAD_TIMING_REPLY 0x53
static const uint8_t udp_timing_header[8] = {
0x80, 0xd3, 0x00, 0x07,
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.
* @param tv Pointer to the timeval structure
* @return The NTP timestamp
*/
static inline uint64_t timeval_to_ntp(struct timeval *tv) {
uint64_t ntp = 0;
/* Converting micro seconds to a fraction. */
ntp = (uint64_t) tv->tv_usec * UINT32_MAX / PA_USEC_PER_SEC;
/* Moving reference from 1 Jan 1970 to 1 Jan 1900 (seconds). */
ntp |= (uint64_t) (tv->tv_sec + 0x83aa7e80) << 32;
return ntp;
}
/**
* Function to write bits into a buffer.
* @param buffer Handle to the buffer. It will be incremented if new data requires it.
* @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB)
* @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks
* @param data The data to write
* @param data_bit_len The number of bits from data to write
*/
static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, size_t *size, uint8_t data, uint8_t data_bit_len) {
int bits_left, bit_overflow;
uint8_t bit_data;
if (!data_bit_len)
return;
/* If bit pos is zero, we will definitely use at least one bit from the current byte so size increments. */
if (!*bit_pos)
*size += 1;
/* Calc the number of bits left in the current byte of buffer. */
bits_left = 7 - *bit_pos + 1;
/* Calc the overflow of bits in relation to how much space we have left... */
bit_overflow = bits_left - data_bit_len;
if (bit_overflow >= 0) {
/* We can fit the new data in our current byte.
* As we write from MSB->LSB we need to left shift by the overflow amount. */
bit_data = data << bit_overflow;
if (*bit_pos)
**buffer |= bit_data;
else
**buffer = bit_data;
/* If our data fits exactly into the current byte, we need to increment our pointer. */
if (0 == bit_overflow) {
/* Do not increment size as it will be incremented on next call as bit_pos is zero. */
*buffer += 1;
*bit_pos = 0;
} else {
*bit_pos += data_bit_len;
}
} else {
/* bit_overflow is negative, there for we will need a new byte from our buffer
* Firstly fill up what's left in the current byte. */
bit_data = data >> -bit_overflow;
**buffer |= bit_data;
/* Increment our buffer pointer and size counter. */
*buffer += 1;
*size += 1;
**buffer = data << (8 + bit_overflow);
*bit_pos = -bit_overflow;
}
}
static size_t write_ALAC_data(uint8_t *packet, const size_t max, uint8_t *raw, size_t *length, bool compress) {
uint32_t nbs = (*length / 2) / 2;
uint8_t *ibp, *maxibp;
uint8_t *bp, bpos;
size_t size = 0;
bp = packet;
pa_memzero(packet, max);
size = bpos = 0;
bit_writer(&bp, &bpos, &size, 1, 3); /* channel=1, stereo */
bit_writer(&bp, &bpos, &size, 0, 4); /* Unknown */
bit_writer(&bp, &bpos, &size, 0, 8); /* Unknown */
bit_writer(&bp, &bpos, &size, 0, 4); /* Unknown */
bit_writer(&bp, &bpos, &size, 1, 1); /* Hassize */
bit_writer(&bp, &bpos, &size, 0, 2); /* Unused */
bit_writer(&bp, &bpos, &size, 1, 1); /* Is-not-compressed */
/* Size of data, integer, big endian. */
bit_writer(&bp, &bpos, &size, (nbs >> 24) & 0xff, 8);
bit_writer(&bp, &bpos, &size, (nbs >> 16) & 0xff, 8);
bit_writer(&bp, &bpos, &size, (nbs >> 8) & 0xff, 8);
bit_writer(&bp, &bpos, &size, (nbs) & 0xff, 8);
ibp = raw;
maxibp = raw + (4 * nbs) - 4;
while (ibp <= maxibp) {
/* Byte swap stereo data. */
bit_writer(&bp, &bpos, &size, *(ibp + 1), 8);
bit_writer(&bp, &bpos, &size, *(ibp + 0), 8);
bit_writer(&bp, &bpos, &size, *(ibp + 3), 8);
bit_writer(&bp, &bpos, &size, *(ibp + 2), 8);
ibp += 4;
}
*length = (ibp - raw);
return size;
}
static size_t build_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_memchunk *packet) {
const size_t head = sizeof(tcp_audio_header);
uint32_t *buffer = NULL;
uint8_t *raw = NULL;
size_t length, size;
raw = pa_memblock_acquire(block->memblock);
buffer = pa_memblock_acquire(packet->memblock);
buffer += packet->index / sizeof(uint32_t);
raw += block->index;
/* Wrap sequence number to 0 then UINT16_MAX is reached */
if (c->seq == UINT16_MAX)
c->seq = 0;
else
c->seq++;
memcpy(buffer, tcp_audio_header, sizeof(tcp_audio_header));
buffer[1] |= htonl((uint32_t) c->seq);
buffer[2] = htonl(c->rtptime);
buffer[3] = htonl(c->ssrc);
length = block->length;
size = sizeof(tcp_audio_header);
if (c->codec == PA_RAOP_CODEC_ALAC)
size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
else {
pa_log_debug("Only ALAC encoding is supported, sending zeros...");
pa_memzero(((uint8_t *) buffer + head), packet->length - head);
size += length;
}
c->rtptime += length / 4;
pa_memblock_release(block->memblock);
buffer[0] |= htonl((uint32_t) size - 4);
if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
pa_memblock_release(packet->memblock);
packet->length = size;
return size;
}
static ssize_t send_tcp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
static int write_type = 0;
const size_t max = sizeof(tcp_audio_header) + 8 + 16384;
pa_memchunk *packet = NULL;
uint8_t *buffer = NULL;
double progress = 0.0;
ssize_t written = -1;
size_t done = 0;
packet = pa_raop_packet_buffer_retrieve(c->pbuf, c->seq);
if (!packet || (packet && packet->length <= 0)) {
pa_assert(block->index == offset);
if (!(packet = pa_raop_packet_buffer_prepare(c->pbuf, c->seq, max)))
return -1;
packet->index = 0;
packet->length = max;
if (!build_tcp_audio_packet(c, block, packet))
return -1;
}
buffer = pa_memblock_acquire(packet->memblock);
pa_assert(buffer);
buffer += packet->index;
if (buffer && packet->length > 0)
written = pa_write(c->tcp_sfd, buffer, packet->length, &write_type);
if (written > 0) {
progress = (double) written / (double) packet->length;
packet->length -= written;
packet->index += written;
done = block->length * progress;
block->length -= done;
block->index += done;
}
pa_memblock_release(packet->memblock);
return written;
}
static size_t build_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, pa_memchunk *packet) {
const size_t head = sizeof(udp_audio_header);
uint32_t *buffer = NULL;
uint8_t *raw = NULL;
size_t length, size;
raw = pa_memblock_acquire(block->memblock);
buffer = pa_memblock_acquire(packet->memblock);
buffer += packet->index / sizeof(uint32_t);
raw += block->index;
memcpy(buffer, udp_audio_header, sizeof(udp_audio_header));
if (c->is_first_packet)
buffer[0] |= htonl((uint32_t) 0x80 << 16);
buffer[0] |= htonl((uint32_t) c->seq);
buffer[1] = htonl(c->rtptime);
buffer[2] = htonl(c->ssrc);
length = block->length;
size = sizeof(udp_audio_header);
if (c->codec == PA_RAOP_CODEC_ALAC)
size += write_ALAC_data(((uint8_t *) buffer + head), packet->length - head, raw, &length, false);
else {
pa_log_debug("Only ALAC encoding is supported, sending zeros...");
pa_memzero(((uint8_t *) buffer + head), packet->length - head);
size += length;
}
c->rtptime += length / 4;
/* Wrap sequence number to 0 then UINT16_MAX is reached */
if (c->seq == UINT16_MAX)
c->seq = 0;
else
c->seq++;
pa_memblock_release(block->memblock);
if (c->encryption == PA_RAOP_ENCRYPTION_RSA)
pa_raop_aes_encrypt(c->secret, (uint8_t *) buffer + head, size - head);
pa_memblock_release(packet->memblock);
packet->length = size;
return size;
}
static ssize_t send_udp_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
const size_t max = sizeof(udp_audio_retrans_header) + sizeof(udp_audio_header) + 8 + 1408;
pa_memchunk *packet = NULL;
uint8_t *buffer = NULL;
ssize_t written = -1;
/* UDP packet has to be sent at once ! */
pa_assert(block->index == offset);
if (!(packet = pa_raop_packet_buffer_prepare(c->pbuf, c->seq, max)))
return -1;
packet->index = sizeof(udp_audio_retrans_header);
packet->length = max - sizeof(udp_audio_retrans_header);
if (!build_udp_audio_packet(c, block, packet))
return -1;
buffer = pa_memblock_acquire(packet->memblock);
pa_assert(buffer);
buffer += packet->index;
if (buffer && packet->length > 0)
written = pa_write(c->udp_sfd, buffer, packet->length, NULL);
if (written < 0 && errno == EAGAIN) {
pa_log_debug("Discarding UDP (audio, seq=%d) packet due to EAGAIN (%s)", c->seq, pa_cstrerror(errno));
written = packet->length;
}
pa_memblock_release(packet->memblock);
/* It is meaningless to preseve the partial data */
block->index += block->length;
block->length = 0;
return written;
}
static size_t rebuild_udp_audio_packet(pa_raop_client *c, uint16_t seq, pa_memchunk *packet) {
size_t size = sizeof(udp_audio_retrans_header);
uint32_t *buffer = NULL;
buffer = pa_memblock_acquire(packet->memblock);
memcpy(buffer, udp_audio_retrans_header, sizeof(udp_audio_retrans_header));
buffer[0] |= htonl((uint32_t) seq);
size += packet->length;
pa_memblock_release(packet->memblock);
packet->length += sizeof(udp_audio_retrans_header);
packet->index -= sizeof(udp_audio_retrans_header);
return size;
}
static ssize_t resend_udp_audio_packets(pa_raop_client *c, uint16_t seq, uint16_t nbp) {
ssize_t total = 0;
int i = 0;
for (i = 0; i < nbp; i++) {
pa_memchunk *packet = NULL;
uint8_t *buffer = NULL;
ssize_t written = -1;
if (!(packet = pa_raop_packet_buffer_retrieve(c->pbuf, seq + i)))
continue;
if (packet->index > 0) {
if (!rebuild_udp_audio_packet(c, seq + i, packet))
continue;
}
pa_assert(packet->index == 0);
buffer = pa_memblock_acquire(packet->memblock);
pa_assert(buffer);
if (buffer && packet->length > 0)
written = pa_write(c->udp_cfd, buffer, packet->length, NULL);
if (written < 0 && errno == EAGAIN) {
pa_log_debug("Discarding UDP (audio-retransmitted, seq=%d) packet due to EAGAIN", seq + i);
pa_memblock_release(packet->memblock);
continue;
}
pa_memblock_release(packet->memblock);
total += written;
}
return total;
}
/* Caller has to free the allocated memory region for packet */
static size_t build_udp_sync_packet(pa_raop_client *c, uint32_t stamp, uint32_t **packet) {
const size_t size = sizeof(udp_sync_header) + 12;
const uint32_t delay = 88200;
uint32_t *buffer = NULL;
uint64_t transmitted = 0;
struct timeval tv;
*packet = NULL;
if (!(buffer = pa_xmalloc0(size)))
return 0;
memcpy(buffer, udp_sync_header, sizeof(udp_sync_header));
if (c->is_first_packet)
buffer[0] |= 0x10;
stamp -= delay;
buffer[1] = htonl(stamp);
/* Set the transmitted timestamp to current time. */
transmitted = timeval_to_ntp(pa_rtclock_get(&tv));
buffer[2] = htonl(transmitted >> 32);
buffer[3] = htonl(transmitted & 0xffffffff);
stamp += delay;
buffer[4] = htonl(stamp);
*packet = buffer;
return size;
}
static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) {
uint32_t * packet = NULL;
ssize_t written = 0;
size_t size = 0;
size = build_udp_sync_packet(c, stamp, &packet);
if (packet != NULL && size > 0) {
written = pa_loop_write(c->udp_cfd, packet, size, NULL);
pa_xfree(packet);
}
return written;
}
static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
uint8_t payload = 0;
uint16_t seq, nbp = 0;
ssize_t written = 0;
/* Control packets are 8 bytes long: */
if (size != 8 || packet[0] != 0x80)
return 1;
seq = ntohs((uint16_t) (packet[4] | packet[5] << 8));
nbp = ntohs((uint16_t) (packet[6] | packet[7] << 8));
if (nbp <= 0)
return 1;
/* The marker bit is always set (see rfc3550 for packet structure) ! */
payload = packet[1] ^ 0x80;
switch (payload) {
case PAYLOAD_RETRANSMIT_REQUEST:
pa_log_debug("Resending %u packets starting at %u", nbp, seq);
written = resend_udp_audio_packets(c, seq, nbp);
break;
case PAYLOAD_RETRANSMIT_REPLY:
default:
pa_log_debug("Got an unexpected payload type on control channel (%u) !", payload);
break;
}
return written;
}
/* Caller has to free the allocated memory region for packet */
static size_t build_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received, uint32_t **packet) {
const size_t size = sizeof(udp_timing_header) + 24;
uint32_t *buffer = NULL;
uint64_t transmitted = 0;
struct timeval tv;
*packet = NULL;
if (!(buffer = pa_xmalloc0(size)))
return 0;
memcpy(buffer, udp_timing_header, sizeof(udp_timing_header));
/* Copying originate timestamp from the incoming request packet. */
buffer[2] = data[4];
buffer[3] = data[5];
/* Set the receive timestamp to reception time. */
buffer[4] = htonl(received >> 32);
buffer[5] = htonl(received & 0xffffffff);
/* Set the transmit timestamp to current time. */
transmitted = timeval_to_ntp(pa_rtclock_get(&tv));
buffer[6] = htonl(transmitted >> 32);
buffer[7] = htonl(transmitted & 0xffffffff);
*packet = buffer;
return size;
}
static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received) {
uint32_t * packet = NULL;
ssize_t written = 0;
size_t size = 0;
size = build_udp_timing_packet(c, data, received, &packet);
if (packet != NULL && size > 0) {
written = pa_loop_write(c->udp_tfd, packet, size, NULL);
pa_xfree(packet);
}
return written;
}
static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
const uint32_t * data = NULL;
uint8_t payload = 0;
struct timeval tv;
size_t written = 0;
uint64_t rci = 0;
/* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
if (size != 32 || packet[0] != 0x80)
return 0;
rci = timeval_to_ntp(pa_rtclock_get(&tv));
data = (uint32_t *) (packet + sizeof(udp_timing_header));
/* The marker bit is always set (see rfc3550 for packet structure) ! */
payload = packet[1] ^ 0x80;
switch (payload) {
case PAYLOAD_TIMING_REQUEST:
pa_log_debug("Sending timing packet at %" PRIu64 , rci);
written = send_udp_timing_packet(c, data, rci);
break;
case PAYLOAD_TIMING_REPLY:
default:
pa_log_debug("Got an unexpected payload type on timing channel (%u) !", payload);
break;
}
return written;
}
static void send_initial_udp_timing_packet(pa_raop_client *c) {
uint32_t data[6] = { 0 };
struct timeval tv;
uint64_t initial_time = 0;
initial_time = timeval_to_ntp(pa_rtclock_get(&tv));
data[4] = htonl(initial_time >> 32);
data[5] = htonl(initial_time & 0xffffffff);
send_udp_timing_packet(c, data, initial_time);
}
static int connect_udp_socket(pa_raop_client *c, int fd, uint16_t port) {
struct sockaddr_in sa4;
#ifdef HAVE_IPV6
struct sockaddr_in6 sa6;
#endif
struct sockaddr *sa;
socklen_t salen;
sa_family_t af;
pa_zero(sa4);
#ifdef HAVE_IPV6
pa_zero(sa6);
#endif
if (inet_pton(AF_INET, c->host, &sa4.sin_addr) > 0) {
sa4.sin_family = af = AF_INET;
sa4.sin_port = htons(port);
sa = (struct sockaddr *) &sa4;
salen = sizeof(sa4);
#ifdef HAVE_IPV6
} else if (inet_pton(AF_INET6, c->host, &sa6.sin6_addr) > 0) {
sa6.sin6_family = af = AF_INET6;
sa6.sin6_port = htons(port);
sa = (struct sockaddr *) &sa6;
salen = sizeof(sa6);
#endif
} else {
pa_log("Invalid destination '%s'", c->host);
goto fail;
}
if (fd < 0 && (fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
pa_log("socket() failed: %s", pa_cstrerror(errno));
goto fail;
}
/* If the socket queue is full, let's drop packets */
pa_make_udp_socket_low_delay(fd);
pa_make_fd_nonblock(fd);
if (connect(fd, sa, salen) < 0) {
pa_log("connect() failed: %s", pa_cstrerror(errno));
goto fail;
}
pa_log_debug("Connected to %s on port %d (SOCK_DGRAM)", c->host, port);
return fd;
fail:
if (fd >= 0)
pa_close(fd);
return -1;
}
static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
int fd = -1;
uint16_t port;
struct sockaddr_in sa4;
#ifdef HAVE_IPV6
struct sockaddr_in6 sa6;
#endif
struct sockaddr *sa;
uint16_t *sa_port;
socklen_t salen;
sa_family_t af;
int one = 1;
pa_assert(actual_port);
port = *actual_port;
pa_zero(sa4);
#ifdef HAVE_IPV6
pa_zero(sa6);
#endif
if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) {
sa4.sin_family = af = AF_INET;
sa4.sin_port = htons(port);
sa4.sin_addr.s_addr = INADDR_ANY;
sa = (struct sockaddr *) &sa4;
salen = sizeof(sa4);
sa_port = &sa4.sin_port;
#ifdef HAVE_IPV6
} else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) {
sa6.sin6_family = af = AF_INET6;
sa6.sin6_port = htons(port);
sa6.sin6_addr = in6addr_any;
sa = (struct sockaddr *) &sa6;
salen = sizeof(sa6);
sa_port = &sa6.sin6_port;
#endif
} else {
pa_log("Could not determine which address family to use");
goto fail;
}
if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
pa_log("socket() failed: %s", pa_cstrerror(errno));
goto fail;
}
#ifdef SO_TIMESTAMP
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
goto fail;
}
#else
pa_log("SO_TIMESTAMP unsupported on this platform");
goto fail;
#endif
one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
pa_log("setsockopt(SO_REUSEADDR) failed: %s", pa_cstrerror(errno));
goto fail;
}
do {
int ret;
*sa_port = htons(port);
ret = bind(fd, sa, salen);
if (!ret)
break;
if (ret < 0 && errno != EADDRINUSE) {
pa_log("bind() failed: %s", pa_cstrerror(errno));
goto fail;
}
} while (++port > 0);
if (!port) {
pa_log("Could not bind port");
goto fail;
}
pa_log_debug("Socket bound to port %d (SOCK_DGRAM)", port);
*actual_port = port;
return fd;
fail:
if (fd >= 0)
pa_close(fd);
return -1;
}
static void tcp_connection_cb(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
pa_raop_client *c = userdata;
pa_assert(sc);
pa_assert(c);
pa_socket_client_unref(sc);
if (!io) {
pa_log("Connection failed: %s", pa_cstrerror(errno));
return;
}
c->tcp_sfd = pa_iochannel_get_send_fd(io);
pa_iochannel_set_noclose(io, true);
pa_make_tcp_socket_low_delay(c->tcp_sfd);
pa_iochannel_free(io);
pa_log_debug("Connection established (TCP)");
if (c->state_callback)
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) {
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;
int frames = 0;
const char *ip;
char *url;
int ipv;
pa_log_debug("RAOP: CONNECTED");
ip = pa_rtsp_localip(c->rtsp);
if (pa_is_ip6_address(ip)) {
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)
frames = FRAMES_PER_TCP_PACKET;
else if (c->protocol == PA_RAOP_PROTOCOL_UDP)
frames = FRAMES_PER_UDP_PACKET;
switch(c->encryption) {
case PA_RAOP_ENCRYPTION_NONE: {
sdp = pa_sprintf_malloc(
"v=0\r\n"
"o=iTunes %s 0 IN IP%d %s\r\n"
"s=iTunes\r\n"
"c=IN IP%d %s\r\n"
"t=0 0\r\n"
"m=audio 0 RTP/AVP 96\r\n"
"a=rtpmap:96 AppleLossless\r\n"
"a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
c->sid, ipv, ip, ipv, c->host, frames);
break;
}
case PA_RAOP_ENCRYPTION_RSA:
case PA_RAOP_ENCRYPTION_FAIRPLAY:
case PA_RAOP_ENCRYPTION_MFISAP:
case PA_RAOP_ENCRYPTION_FAIRPLAY_SAP25: {
key = pa_raop_secret_get_key(c->secret);
if (!key) {
pa_log("pa_raop_secret_get_key() failed.");
pa_rtsp_disconnect(rtsp);
/* 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);
sdp = pa_sprintf_malloc(
"v=0\r\n"
"o=iTunes %s 0 IN IP%d %s\r\n"
"s=iTunes\r\n"
"c=IN IP%d %s\r\n"
"t=0 0\r\n"
"m=audio 0 RTP/AVP 96\r\n"
"a=rtpmap:96 AppleLossless\r\n"
"a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
"a=rsaaeskey:%s\r\n"
"a=aesiv:%s\r\n",
c->sid, ipv, ip, ipv, c->host, frames, key, iv);
pa_xfree(key);
pa_xfree(iv);
break;
}
}
pa_rtsp_announce(c->rtsp, sdp);
connect_finish:
pa_xfree(sdp);
pa_xfree(url);
break;
}
case STATE_OPTIONS: {
pa_log_debug("RAOP: OPTIONS (stream cb)");
break;
}
case STATE_ANNOUNCE: {
uint16_t cport = DEFAULT_UDP_CONTROL_PORT;
uint16_t tport = DEFAULT_UDP_TIMING_PORT;
char *trs = NULL;
pa_log_debug("RAOP: ANNOUNCE");
if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
trs = pa_sprintf_malloc(
"RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
c->udp_cfd = open_bind_udp_socket(c, &cport);
c->udp_tfd = open_bind_udp_socket(c, &tport);
if (c->udp_cfd < 0 || c->udp_tfd < 0)
goto annonce_error;
trs = pa_sprintf_malloc(
"RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;"
"control_port=%d;timing_port=%d",
cport, tport);
}
pa_rtsp_setup(c->rtsp, trs);
pa_xfree(trs);
break;
annonce_error:
if (c->udp_cfd >= 0)
pa_close(c->udp_cfd);
c->udp_cfd = -1;
if (c->udp_tfd >= 0)
pa_close(c->udp_tfd);
c->udp_tfd = -1;
pa_rtsp_client_free(c->rtsp);
pa_log_error("Aborting RTSP announce, failed creating required sockets");
c->rtsp = NULL;
pa_xfree(trs);
break;
}
case STATE_SETUP: {
pa_socket_client *sc = NULL;
uint32_t sport = DEFAULT_UDP_AUDIO_PORT;
uint32_t cport =0, tport = 0;
char *ajs, *token, *pc, *trs;
const char *token_state = NULL;
char delimiters[] = ";";
pa_log_debug("RAOP: SETUP");
ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
if (ajs) {
c->jack_type = JACK_TYPE_ANALOG;
c->jack_status = JACK_STATUS_DISCONNECTED;
while ((token = pa_split(ajs, delimiters, &token_state))) {
if ((pc = strstr(token, "="))) {
*pc = 0;
if (pa_streq(token, "type") && pa_streq(pc + 1, "digital"))
c->jack_type = JACK_TYPE_DIGITAL;
} else {
if (pa_streq(token, "connected"))
c->jack_status = JACK_STATUS_CONNECTED;
}
pa_xfree(token);
}
} else {
pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
}
sport = pa_rtsp_serverport(c->rtsp);
if (sport <= 0)
goto setup_error;
token_state = NULL;
if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
if (!(sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, sport)))
goto setup_error;
pa_socket_client_ref(sc);
pa_socket_client_set_callback(sc, tcp_connection_cb, c);
pa_socket_client_unref(sc);
sc = NULL;
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
if (trs) {
/* Now parse out the server port component of the response. */
while ((token = pa_split(trs, delimiters, &token_state))) {
if ((pc = strstr(token, "="))) {
*pc = 0;
if (pa_streq(token, "control_port")) {
if (pa_atou(pc + 1, &cport) < 0)
goto setup_error_parse;
}
if (pa_streq(token, "timing_port")) {
if (pa_atou(pc + 1, &tport) < 0)
goto setup_error_parse;
}
*pc = '=';
}
pa_xfree(token);
}
pa_xfree(trs);
} else {
pa_log_warn("\"Transport\" missing in RTSP setup response");
}
if (cport <= 0 || tport <= 0)
goto setup_error;
if ((c->udp_sfd = connect_udp_socket(c, -1, sport)) <= 0)
goto setup_error;
if ((c->udp_cfd = connect_udp_socket(c, c->udp_cfd, cport)) <= 0)
goto setup_error;
if ((c->udp_tfd = connect_udp_socket(c, c->udp_tfd, tport)) <= 0)
goto setup_error;
pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport);
/* Send an initial UDP packet so a connection tracking firewall
* knows the src_ip:src_port <-> dest_ip:dest_port relation
* and accepts the incoming timing packets.
*/
send_initial_udp_timing_packet(c);
pa_log_debug("Sent initial timing packet to UDP port %d", tport);
if (c->state_callback)
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
}
pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
pa_xfree(ajs);
break;
setup_error_parse:
pa_log("Failed parsing server port components");
pa_xfree(token);
pa_xfree(trs);
/* fall-thru */
setup_error:
if (c->tcp_sfd >= 0)
pa_close(c->tcp_sfd);
c->tcp_sfd = -1;
if (c->udp_sfd >= 0)
pa_close(c->udp_sfd);
c->udp_sfd = -1;
c->udp_cfd = c->udp_tfd = -1;
pa_rtsp_client_free(c->rtsp);
pa_log_error("aborting RTSP setup, failed creating required sockets");
if (c->state_callback)
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
c->rtsp = NULL;
break;
}
case STATE_RECORD: {
int32_t latency = 0;
uint32_t ssrc;
char *alt;
pa_log_debug("RAOP: RECORD");
alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
if (alt) {
if (pa_atoi(alt, &latency) < 0)
pa_log("Failed to parse audio latency");
}
pa_raop_packet_buffer_reset(c->pbuf, c->seq);
pa_random(&ssrc, sizeof(ssrc));
c->is_first_packet = true;
c->is_recording = true;
c->sync_count = 0;
c->ssrc = ssrc;
if (c->state_callback)
c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata);
pa_xfree(alt);
break;
}
case STATE_SET_PARAMETER: {
pa_log_debug("RAOP: SET_PARAMETER");
break;
}
case STATE_FLUSH: {
pa_log_debug("RAOP: FLUSHED");
break;
}
case STATE_TEARDOWN: {
pa_log_debug("RAOP: TEARDOWN");
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_rtsp_client_free(c->rtsp);
pa_xfree(c->sid);
c->rtsp = NULL;
c->sid = NULL;
if (c->state_callback)
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
break;
}
case STATE_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_rtsp_client_free(c->rtsp);
pa_xfree(c->sid);
c->rtsp = NULL;
c->sid = NULL;
if (c->state_callback)
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
break;
}
}
}
static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
pa_raop_client *c = userdata;
pa_assert(c);
pa_assert(rtsp);
pa_assert(rtsp == c->rtsp);
switch (state) {
case STATE_CONNECT: {
char *sci = NULL, *sac = NULL;
uint8_t rac[APPLE_CHALLENGE_LENGTH];
struct {
uint32_t ci1;
uint32_t ci2;
} rci;
pa_random(&rci, sizeof(rci));
/* Generate a random Client-Instance number */
sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2);
pa_rtsp_add_header(c->rtsp, "Client-Instance", sci);
pa_random(rac, APPLE_CHALLENGE_LENGTH);
/* Generate a random Apple-Challenge key */
pa_raop_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
rtrim_char(sac, '=');
pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
pa_rtsp_options(c->rtsp);
pa_xfree(sac);
pa_xfree(sci);
break;
}
case STATE_OPTIONS: {
static bool waiting = false;
const char *current = NULL;
char space[] = " ";
char *token, *ath = NULL;
char *publ, *wath, *mth = NULL, *val;
char *realm = NULL, *nonce = NULL, *response = NULL;
char comma[] = ",";
pa_log_debug("RAOP: OPTIONS (auth cb)");
/* We do not consider the Apple-Response */
pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
if (STATUS_UNAUTHORIZED == status) {
wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
if (true == waiting) {
pa_xfree(wath);
goto fail;
}
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);
break;
}
if (STATUS_OK == status) {
publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
if (c->password)
pa_xfree(c->password);
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)
c->state_callback((int) PA_RAOP_AUTHENTICATED, c->state_userdata);
waiting = false;
break;
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");
waiting = false;
break;
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");
waiting = false;
break;
}
case STATE_ANNOUNCE:
case STATE_SETUP:
case STATE_RECORD:
case STATE_SET_PARAMETER:
case STATE_FLUSH:
case STATE_TEARDOWN:
case STATE_DISCONNECTED:
default: {
if (c->state_callback)
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
pa_rtsp_client_free(c->rtsp);
c->rtsp = NULL;
if (c->sci)
pa_xfree(c->sci);
c->sci = NULL;
break;
}
}
}
void pa_raop_client_disconnect(pa_raop_client *c) {
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)");
if (c->rtsp)
pa_rtsp_client_free(c->rtsp);
if (c->sid)
pa_xfree(c->sid);
c->rtsp = NULL;
c->sid = NULL;
if (c->state_callback)
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
}
pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol,
pa_raop_encryption_t encryption, pa_raop_codec_t codec, bool autoreconnect) {
pa_raop_client *c;
pa_parsed_address a;
pa_sample_spec ss;
size_t size = 2;
pa_assert(core);
pa_assert(host);
if (pa_parse_address(host, &a) < 0)
return NULL;
if (a.type == PA_PARSED_ADDRESS_UNIX) {
pa_xfree(a.path_or_host);
return NULL;
}
c = pa_xnew0(pa_raop_client, 1);
c->core = core;
c->host = a.path_or_host; /* Will eventually be freed on destruction of c */
if (a.port > 0)
c->port = a.port;
else
c->port = DEFAULT_RAOP_PORT;
c->rtsp = NULL;
c->sci = c->sid = NULL;
c->password = NULL;
c->autoreconnect = autoreconnect;
c->protocol = protocol;
c->encryption = encryption;
c->codec = codec;
c->tcp_sfd = -1;
c->udp_sfd = -1;
c->udp_cfd = -1;
c->udp_tfd = -1;
c->secret = NULL;
if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
c->secret = pa_raop_secret_new();
ss = core->default_sample_spec;
if (c->protocol == PA_RAOP_PROTOCOL_UDP)
size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET;
c->is_recording = false;
c->is_first_packet = true;
/* Packet sync interval should be around 1s (UDP only) */
c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
c->sync_count = 0;
c->pbuf = pa_raop_packet_buffer_new(c->core->mempool, size);
return c;
}
void pa_raop_client_free(pa_raop_client *c) {
pa_assert(c);
pa_raop_packet_buffer_free(c->pbuf);
pa_xfree(c->sid);
pa_xfree(c->sci);
if (c->secret)
pa_raop_secret_free(c->secret);
pa_xfree(c->password);
c->sci = c->sid = NULL;
c->password = NULL;
c->secret = NULL;
if (c->rtsp)
pa_rtsp_client_free(c->rtsp);
c->rtsp = NULL;
pa_xfree(c->host);
pa_xfree(c);
}
int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
int rv = 0;
pa_assert(c);
if (c->rtsp || c->password) {
pa_log_debug("Authentication/Connection already in progress...");
return 0;
}
c->password = NULL;
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);
pa_assert(c->rtsp);
pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c);
rv = pa_rtsp_connect(c->rtsp);
return rv;
}
bool pa_raop_client_is_authenticated(pa_raop_client *c) {
pa_assert(c);
return (c->sci != NULL);
}
int pa_raop_client_announce(pa_raop_client *c) {
uint32_t sid;
int rv = 0;
pa_assert(c);
if (c->rtsp) {
pa_log_debug("Connection already in progress...");
return 0;
} else if (!c->sci) {
pa_log_debug("ANNOUNCE requires a preliminary authentication");
return 1;
}
c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT, c->autoreconnect);
pa_assert(c->rtsp);
c->sync_count = 0;
c->is_recording = false;
c->is_first_packet = true;
pa_random(&sid, sizeof(sid));
c->sid = pa_sprintf_malloc("%u", sid);
pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c);
rv = pa_rtsp_connect(c->rtsp);
return rv;
}
bool pa_raop_client_is_alive(pa_raop_client *c) {
pa_assert(c);
if (!c->rtsp || !c->sci) {
pa_log_debug("Not alive, connection not established yet...");
return false;
}
switch (c->protocol) {
case PA_RAOP_PROTOCOL_TCP:
if (c->tcp_sfd >= 0)
return true;
break;
case PA_RAOP_PROTOCOL_UDP:
if (c->udp_sfd >= 0)
return true;
break;
default:
break;
}
return false;
}
bool pa_raop_client_can_stream(pa_raop_client *c) {
pa_assert(c);
if (!c->rtsp || !c->sci) {
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 rv = 0;
pa_assert(c);
if (!c->rtsp || !c->sci) {
pa_log_debug("Streaming's impossible, connection not established yet...");
return 0;
}
switch (c->protocol) {
case PA_RAOP_PROTOCOL_TCP:
if (c->tcp_sfd >= 0 && !c->is_recording) {
c->is_recording = true;
c->is_first_packet = true;
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;
}
int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
char *param;
int rv = 0;
double db;
pa_assert(c);
if (!c->rtsp) {
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;
}
db = pa_sw_volume_to_dB(volume);
if (db < VOLUME_MIN)
db = VOLUME_MIN;
else if (db > VOLUME_MAX)
db = VOLUME_MAX;
pa_log_debug("volume=%u db=%.6f", volume, db);
param = pa_sprintf_malloc("volume: %0.6f\r\n", db);
/* We just hit and hope, cannot wait for the callback. */
if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp))
rv = pa_rtsp_setparameter(c->rtsp, param);
pa_xfree(param);
return rv;
}
int pa_raop_client_flush(pa_raop_client *c) {
int rv = 0;
pa_assert(c);
if (!c->rtsp || !pa_rtsp_exec_ready(c->rtsp)) {
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;
}
c->is_recording = false;
rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
return rv;
}
int pa_raop_client_teardown(pa_raop_client *c) {
int rv = 0;
pa_assert(c);
if (!c->rtsp) {
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;
}
c->is_recording = false;
rv = pa_rtsp_teardown(c->rtsp);
return rv;
}
void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) {
pa_assert(c);
pa_assert(frames);
switch (c->protocol) {
case PA_RAOP_PROTOCOL_TCP:
*frames = FRAMES_PER_TCP_PACKET;
break;
case PA_RAOP_PROTOCOL_UDP:
*frames = FRAMES_PER_UDP_PACKET;
break;
default:
*frames = 0;
break;
}
}
bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item) {
struct pollfd *pollfd = NULL;
pa_rtpoll_item *item = NULL;
bool oob = true;
pa_assert(c);
pa_assert(poll);
pa_assert(poll_item);
switch (c->protocol) {
case PA_RAOP_PROTOCOL_TCP:
item = pa_rtpoll_item_new(poll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
pollfd->fd = c->tcp_sfd;
pollfd->events = POLLOUT;
pollfd->revents = 0;
*poll_item = item;
oob = false;
break;
case PA_RAOP_PROTOCOL_UDP:
item = pa_rtpoll_item_new(poll, PA_RTPOLL_NEVER, 2);
pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
pollfd->fd = c->udp_cfd;
pollfd->events = POLLIN | POLLPRI;
pollfd->revents = 0;
pollfd++;
pollfd->fd = c->udp_tfd;
pollfd->events = POLLIN | POLLPRI;
pollfd->revents = 0;
*poll_item = item;
oob = true;
break;
default:
*poll_item = NULL;
break;
}
return oob;
}
bool pa_raop_client_is_timing_fd(pa_raop_client *c, const int fd) {
return fd == c->udp_tfd;
}
pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) {
double minv, maxv;
pa_assert(c);
if (c->protocol != PA_RAOP_PROTOCOL_UDP)
return volume;
maxv = pa_sw_volume_from_dB(0.0);
minv = maxv * pow(10.0, VOLUME_DEF / 60.0);
/* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */
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) {
pa_assert(c);
pa_assert(fd >= 0);
pa_assert(packet);
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
if (fd == c->udp_cfd) {
pa_log_debug("Received UDP control packet...");
handle_udp_control_packet(c, packet, size);
} else if (fd == c->udp_tfd) {
pa_log_debug("Received UDP timing packet...");
handle_udp_timing_packet(c, packet, size);
}
}
}
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
ssize_t written = 0;
pa_assert(c);
pa_assert(block);
/* Sync RTP & NTP timestamp if required (UDP). */
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
c->sync_count++;
if (c->is_first_packet || c->sync_count >= c->sync_interval) {
send_udp_sync_packet(c, c->rtptime);
c->sync_count = 0;
}
}
switch (c->protocol) {
case PA_RAOP_PROTOCOL_TCP:
written = send_tcp_audio_packet(c, block, offset);
break;
case PA_RAOP_PROTOCOL_UDP:
written = send_udp_audio_packet(c, block, offset);
break;
default:
written = -1;
break;
}
c->is_first_packet = false;
return written;
}
void pa_raop_client_set_state_callback(pa_raop_client *c, pa_raop_client_state_cb_t callback, void *userdata) {
pa_assert(c);
c->state_callback = callback;
c->state_userdata = userdata;
}