module-raop-sink: Port to rtp-module/stream

This commit is contained in:
Christian Glombek 2023-10-09 07:24:30 +02:00
parent cbac8c9040
commit 60d0943c19
2 changed files with 194 additions and 369 deletions

View file

@ -607,12 +607,13 @@ build_module_raop = openssl_lib.found()
if build_module_raop
pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink',
[ 'module-raop-sink.c',
'module-raop/rtsp-client.c' ],
'module-raop/rtsp-client.c',
'module-rtp/stream.c' ],
include_directories : [configinc],
install : true,
install_dir : modules_install_dir,
install_rpath: modules_install_dir,
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, openssl_lib],
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep, openssl_lib],
)
endif
summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')

View file

@ -45,6 +45,7 @@
#include "module-raop/rtsp-client.h"
#include "module-rtp/rtp.h"
#include "module-rtp/stream.h"
/** \page page_module_raop_sink PipeWire Module: AirPlay Sink
*
@ -121,16 +122,17 @@
PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define PW_LOG_TOPIC_DEFAULT mod_topic
#define BUFFER_SIZE (1u<<22)
#define BUFFER_MASK (BUFFER_SIZE-1)
#define BUFFER_SIZE2 (BUFFER_SIZE>>1)
#define BUFFER_MASK2 (BUFFER_SIZE2-1)
#define FRAMES_PER_TCP_PACKET 4096
#define FRAMES_PER_UDP_PACKET 352
#define RAOP_LATENCY_MIN 11025u
#define DEFAULT_LATENCY_MS "1500"
#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 RAOP_AUDIO_PORT 6000
#define RAOP_UDP_CONTROL_PORT 6001
#define RAOP_UDP_TIMING_PORT 6002
#define AES_CHUNK_SIZE 16
#ifndef MD5_DIGEST_LENGTH
@ -143,10 +145,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define MAX_PORT_RETRY 128
#define DEFAULT_FORMAT "S16"
#define DEFAULT_RATE 44100
#define DEFAULT_CHANNELS 2
#define DEFAULT_POSITION "[ FL FR ]"
#define RAOP_FORMAT "S16LE"
#define RAOP_STRIDE (2*DEFAULT_CHANNELS)
#define RAOP_RATE 44100
#define RAOP_LATENCY_MS 250
#define VOLUME_MAX 0.0
#define VOLUME_MIN -30.0
@ -163,8 +165,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
"( node.latency=<latency as fraction> ) " \
"( node.name=<name of the nodes> ) " \
"( node.description=<description of the nodes> ) " \
"( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \
"( audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ) " \
"( audio.format=<format, default:"RAOP_FORMAT"> ) " \
"( audio.rate=<sample rate, default: "SPA_STRINGIFY(RAOP_RATE)"> ) " \
"( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \
"( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \
"( stream.props=<properties> ) "
@ -212,9 +214,7 @@ struct impl {
struct spa_hook core_listener;
struct pw_properties *stream_props;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_audio_info_raw info;
struct rtp_stream *stream;
struct pw_rtsp_client *rtsp;
struct spa_hook rtsp_listener;
@ -245,16 +245,15 @@ struct impl {
int server_fd;
struct spa_source *server_source;
uint32_t psamples;
uint64_t rate;
uint32_t mtu;
uint32_t stride;
uint32_t latency;
uint16_t seq, cseq;
uint32_t rtptime;
uint32_t ssrc;
uint32_t sync;
uint32_t sync_period;
unsigned int first:1;
unsigned int connected:1;
unsigned int ready:1;
unsigned int recording:1;
@ -262,17 +261,14 @@ struct impl {
bool mute;
float volume;
uint8_t buffer[FRAMES_PER_TCP_PACKET * 4];
struct spa_ringbuffer ring;
uint8_t buffer[BUFFER_SIZE];
struct spa_io_position *io_position;
uint32_t filled;
};
static void stream_destroy(void *d)
{
struct impl *impl = d;
spa_hook_remove(&impl->stream_listener);
impl->stream = NULL;
}
static inline void bit_writer(uint8_t **p, int *pos, uint8_t data, int len)
{
int rb = 8 - *pos - len;
@ -307,7 +303,7 @@ static inline uint64_t ntp_now(void)
return timespec_to_ntp(&now);
}
static int send_udp_sync_packet(struct impl *impl, uint32_t rtptime)
static int send_udp_sync_packet(struct impl *impl, uint32_t rtptime, unsigned int first)
{
uint32_t out[3];
uint32_t latency = impl->latency;
@ -319,11 +315,11 @@ static int send_udp_sync_packet(struct impl *impl, uint32_t rtptime)
spa_zero(header);
header.v = 2;
if (impl->first)
if (first)
header.x = 1;
header.m = 1;
header.pt = 84;
header.sequence_number = htons(impl->cseq);
header.sequence_number = 7;
header.timestamp = htonl(rtptime - latency);
iov[0].iov_base = &header;
@ -351,10 +347,8 @@ static int send_udp_sync_packet(struct impl *impl, uint32_t rtptime)
pw_log_warn("error sending control packet: %d", res);
}
impl->cseq = (impl->cseq + 1) & 0xffff;
pw_log_debug("raop control sync: cseq:%d first:%d latency:%u now:%"PRIx64" rtptime:%u",
impl->cseq, impl->first, latency, transmitted, rtptime);
pw_log_debug("raop control sync: first:%d latency:%u now:%"PRIx64" rtptime:%u",
first, latency, transmitted, rtptime);
return res;
}
@ -452,7 +446,7 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen)
struct impl *impl = data;
const size_t max = 8 + impl->mtu;
uint32_t tcp_pkt[1], out[max], len, n_frames, rtptime;
struct iovec tcp_iov[3], udp_iov[2];
struct iovec out_vec[3];
struct rtp_header *header;
struct msghdr msg;
uint8_t *dst;
@ -466,8 +460,8 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen)
rtptime = htonl(header->timestamp);
if (header->m || impl->first || ++impl->sync == impl->sync_period) {
send_udp_sync_packet(impl, rtptime);
if (header->m || ++impl->sync == impl->sync_period) {
send_udp_sync_packet(impl, rtptime, header->m);
impl->sync = 0;
}
@ -475,6 +469,8 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen)
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = out_vec;
msg.msg_iovlen = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
@ -494,99 +490,28 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen)
if (impl->encryption == CRYPTO_RSA)
aes_encrypt(impl, dst, len);
switch (impl->protocol) {
case PROTO_UDP:
udp_iov[0].iov_base = header;
udp_iov[0].iov_len = 12;
udp_iov[1].iov_base = out;
udp_iov[1].iov_len = len;
msg.msg_iov = udp_iov;
msg.msg_iovlen = 2;
break;
case PROTO_TCP:
if (impl->protocol == PROTO_TCP) {
out[0] |= htonl((uint32_t) len + 12);
tcp_pkt[0] = htonl(0x24000000);
tcp_iov[0].iov_base = &tcp_pkt;
tcp_iov[0].iov_len = 4;
tcp_iov[1].iov_base = header;
tcp_iov[1].iov_len = 12;
tcp_iov[2].iov_base = out;
tcp_iov[2].iov_len = len;
msg.msg_iov = tcp_iov;
msg.msg_iovlen = 3;
break;
out_vec[msg.msg_iovlen++] = (struct iovec) { tcp_pkt, 4 };
}
pw_log_debug("raop sending header:%ld data:%ld", udp_iov[0].iov_len, udp_iov[1].iov_len);
out_vec[msg.msg_iovlen++] = (struct iovec) { header, 12 };
out_vec[msg.msg_iovlen++] = (struct iovec) { out, len };
pw_log_debug("raop sending %ld", out_vec[0].iov_len + out_vec[1].iov_len + out_vec[2].iov_len);
send_packet(impl->server_fd, &msg);
}
static void playback_stream_process(void *d)
static inline void
set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size,
uint32_t offset, struct iovec *iov, uint32_t len)
{
struct impl *impl = d;
struct pw_buffer *buf;
struct spa_data *bd;
uint8_t *data;
uint32_t offs, size;
struct iovec iov[2];
struct rtp_header header;
if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
pw_log_debug("out of buffers: %m");
return;
}
bd = &buf->buffer->datas[0];
offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
data = SPA_PTROFF(bd->data, offs, uint8_t);
spa_zero(header);
header.v = 2;
header.pt = 96;
if (impl->first)
header.m = 1;
header.ssrc = htonl(impl->ssrc);
while (size > 0 && impl->mtu > 0) {
uint32_t avail, to_fill;
header.sequence_number = htons(impl->seq);
header.timestamp = htonl(impl->rtptime);
avail = impl->mtu - impl->filled;
to_fill = SPA_MIN(avail, size);
memcpy(&impl->buffer[impl->filled], data, to_fill);
impl->filled += to_fill;
avail -= to_fill;
size -= to_fill;
data += to_fill;
if (avail == 0) {
iov[0].iov_base = &header;
iov[0].iov_len = 12;
iov[1].iov_base = impl->buffer;
iov[1].iov_len = impl->filled;
stream_send_packet(impl, iov, 2);
impl->rtptime += (impl->filled / impl->stride);
impl->seq = (impl->seq + 1) & 0xffff;
impl->filled = 0;
impl->first = false;
}
}
pw_stream_queue_buffer(impl->stream, buf);
iov[0].iov_len = SPA_MIN(len, size - offset);
iov[0].iov_base = SPA_PTROFF(buffer, offset, void);
iov[1].iov_len = len - iov[0].iov_len;
iov[1].iov_base = buffer;
}
static int create_udp_socket(struct impl *impl, uint16_t *port)
@ -931,6 +856,11 @@ static void rtsp_do_post_feedback(void *data, uint64_t expirations)
NULL, NULL, 0, rtsp_log_reply_status, impl);
}
static uint32_t msec_to_samples(struct impl *impl, uint32_t msec)
{
return msec * impl->rate / 1000;
}
static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
{
struct impl *impl = data;
@ -962,17 +892,18 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head
spa_zero(latency);
latency.direction = PW_DIRECTION_INPUT;
latency.min_rate = latency.max_rate = impl->latency + RAOP_LATENCY_MIN;
latency.min_rate = latency.max_rate = impl->latency + msec_to_samples(impl, RAOP_LATENCY_MS);
n_params = 0;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
pw_stream_update_params(impl->stream, params, n_params);
rtp_stream_update_params(impl->stream, params, n_params);
rtp_stream_set_first(impl->stream);
impl->first = true;
impl->sync = 0;
impl->sync_period = impl->info.rate / (impl->mtu / impl->stride);
impl->sync_period = impl->rate / (impl->mtu / impl->stride);
impl->recording = true;
rtsp_send_volume(impl);
@ -984,13 +915,18 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head
static int rtsp_do_record(struct impl *impl)
{
int res;
uint16_t seq;
uint32_t rtptime;
if (!impl->ready || impl->recording)
return 0;
seq = rtp_stream_get_seq(impl->stream);
rtptime = rtp_stream_get_time(impl->stream, &impl->rate);
pw_properties_set(impl->headers, "Range", "npt=0-");
pw_properties_setf(impl->headers, "RTP-Info",
"seq=%u;rtptime=%u", impl->seq, impl->rtptime);
"seq=%u;rtptime=%u", seq, rtptime);
res = rtsp_send(impl, "RECORD", NULL, NULL, rtsp_record_reply);
@ -1023,7 +959,7 @@ on_server_source_io(void *data, int fd, uint32_t mask)
goto error;
impl->ready = true;
if (pw_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
if (rtp_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
rtsp_do_record(impl);
}
return;
@ -1038,7 +974,6 @@ static int rtsp_setup_reply(void *data, int status, const struct spa_dict *heade
size_t len;
uint64_t ntp;
uint16_t control_port, timing_port;
int res;
pw_log_info("setup status: %d", status);
@ -1070,12 +1005,6 @@ static int rtsp_setup_reply(void *data, int status, const struct spa_dict *heade
return 0;
}
if ((res = pw_getrandom(&impl->seq, sizeof(impl->seq), 0)) < 0 ||
(res = pw_getrandom(&impl->rtptime, sizeof(impl->rtptime), 0)) < 0) {
pw_log_error("error generating random seq and rtptime: %s", spa_strerror(res));
return 0;
}
pw_log_info("server port:%u", impl->server_port);
switch (impl->protocol) {
@ -1114,7 +1043,7 @@ static int rtsp_setup_reply(void *data, int status, const struct spa_dict *heade
SPA_IO_IN, false, on_control_source_io, impl);
impl->ready = true;
if (pw_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
if (rtp_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
rtsp_do_record(impl);
break;
default:
@ -1134,8 +1063,8 @@ static int rtsp_do_setup(struct impl *impl)
break;
case PROTO_UDP:
impl->control_port = DEFAULT_UDP_CONTROL_PORT;
impl->timing_port = DEFAULT_UDP_TIMING_PORT;
impl->control_port = RAOP_UDP_CONTROL_PORT;
impl->timing_port = RAOP_UDP_TIMING_PORT;
impl->control_fd = create_udp_socket(impl, &impl->control_port);
impl->timing_fd = create_udp_socket(impl, &impl->timing_port);
@ -1274,19 +1203,14 @@ static int rtsp_do_announce(struct impl *impl)
{
const char *host;
uint8_t rsakey[512];
uint32_t rtp_latency;
char key[512*2];
char iv[16*2];
int res, frames, rsa_len, ip_version;
int res, rsa_len, ip_version;
spa_autofree char *sdp = NULL;
char local_ip[256];
host = pw_properties_get(impl->props, "raop.ip");
if (impl->protocol == PROTO_TCP)
frames = FRAMES_PER_TCP_PACKET;
else
frames = FRAMES_PER_UDP_PACKET;
impl->mtu = frames * impl->stride;
rtp_latency = msec_to_samples(impl, RAOP_LATENCY_MS);
pw_rtsp_client_get_local_ip(impl->rtsp, &ip_version,
local_ip, sizeof(local_ip));
@ -1302,7 +1226,7 @@ static int rtsp_do_announce(struct impl *impl)
"a=rtpmap:96 AppleLossless\r\n"
"a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n",
impl->session_id, ip_version, local_ip,
ip_version, host, frames, impl->info.rate);
ip_version, host, impl->psamples, (uint32_t)impl->rate);
if (!sdp)
return -errno;
break;
@ -1317,8 +1241,8 @@ static int rtsp_do_announce(struct impl *impl)
"a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n"
"a=min-latency:%d",
impl->session_id, ip_version, local_ip,
ip_version, host, frames, impl->info.rate,
RAOP_LATENCY_MIN);
ip_version, host, impl->psamples, (uint32_t)impl->rate,
rtp_latency);
if (!sdp)
return -errno;
break;
@ -1354,7 +1278,7 @@ static int rtsp_do_announce(struct impl *impl)
"a=rsaaeskey:%s\r\n"
"a=aesiv:%s\r\n",
impl->session_id, ip_version, local_ip,
ip_version, host, frames, impl->info.rate,
ip_version, host, impl->psamples, (uint32_t)impl->rate,
key, iv);
if (!sdp)
return -errno;
@ -1576,21 +1500,23 @@ static const struct pw_rtsp_client_events rtsp_events = {
.message = rtsp_message,
};
static void stream_state_changed(void *d, enum pw_stream_state old,
enum pw_stream_state state, const char *error)
static void stream_destroy(void *d)
{
struct impl *impl = d;
switch (state) {
case PW_STREAM_STATE_ERROR:
case PW_STREAM_STATE_UNCONNECTED:
pw_impl_module_schedule_destroy(impl->module);
break;
case PW_STREAM_STATE_STREAMING:
rtsp_do_record(impl);
break;
default:
break;
impl->stream = NULL;
}
static void stream_state_changed(void *data, bool started, const char *error)
{
struct impl *impl = data;
if (error) {
pw_log_error("stream error: %s", error);
pw_impl_module_schedule_destroy(impl->module);
return;
}
if (started)
rtsp_do_record(impl);
}
static int rtsp_do_connect(struct impl *impl)
@ -1705,7 +1631,7 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp
}
param = spa_pod_builder_pop(&b, &f[0]);
pw_stream_set_param(impl->stream, id, param);
rtp_stream_set_param(impl->stream, id, param);
}
static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
@ -1728,57 +1654,14 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *
}
}
static const struct pw_stream_events playback_stream_events = {
PW_VERSION_STREAM_EVENTS,
static const struct rtp_stream_events stream_events = {
RTP_VERSION_STREAM_EVENTS,
.destroy = stream_destroy,
.state_changed = stream_state_changed,
.param_changed = stream_param_changed,
.process = playback_stream_process
.send_packet = stream_send_packet
};
static int create_stream(struct impl *impl)
{
int res;
uint32_t n_params;
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b;
impl->stream = pw_stream_new(impl->core, "RAOP sink", impl->stream_props);
impl->stream_props = NULL;
if (impl->stream == NULL)
return -errno;
pw_stream_add_listener(impl->stream,
&impl->stream_listener,
&playback_stream_events, impl);
n_params = 0;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
params[n_params++] = spa_format_audio_raw_build(&b,
SPA_PARAM_EnumFormat, &impl->info);
if ((res = pw_stream_connect(impl->stream,
PW_DIRECTION_INPUT,
PW_ID_ANY,
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, n_params)) < 0)
return res;
impl->headers = pw_properties_new(NULL, NULL);
impl->rtsp = pw_rtsp_client_new(impl->loop, NULL, 0);
if (impl->rtsp == NULL)
return -errno;
pw_rtsp_client_add_listener(impl->rtsp, &impl->rtsp_listener,
&rtsp_events, impl);
return 0;
}
static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
{
struct impl *impl = data;
@ -1810,7 +1693,7 @@ static const struct pw_proxy_events core_proxy_events = {
static void impl_destroy(struct impl *impl)
{
if (impl->stream)
pw_stream_destroy(impl->stream);
rtp_stream_destroy(impl->stream);
if (impl->core && impl->do_disconnect)
pw_core_disconnect(impl->core);
@ -1839,97 +1722,6 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy,
};
static inline uint32_t format_from_name(const char *name, size_t len)
{
int i;
for (i = 0; spa_type_audio_format[i].name; i++) {
if (strncmp(name, spa_debug_type_short_name(spa_type_audio_format[i].name), len) == 0)
return spa_type_audio_format[i].type;
}
return SPA_AUDIO_FORMAT_UNKNOWN;
}
static uint32_t channel_from_name(const char *name)
{
int i;
for (i = 0; spa_type_audio_channel[i].name; i++) {
if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name)))
return spa_type_audio_channel[i].type;
}
return SPA_AUDIO_CHANNEL_UNKNOWN;
}
static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len)
{
struct spa_json it[2];
char v[256];
spa_json_init(&it[0], val, len);
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
spa_json_init(&it[1], val, len);
info->channels = 0;
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
info->channels < SPA_AUDIO_MAX_CHANNELS) {
info->position[info->channels++] = channel_from_name(v);
}
}
static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info)
{
const char *str;
spa_zero(*info);
if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL)
str = DEFAULT_FORMAT;
info->format = format_from_name(str, strlen(str));
info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate);
if (info->rate == 0)
info->rate = DEFAULT_RATE;
info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels);
info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS);
if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL)
parse_position(info, str, strlen(str));
if (info->channels == 0)
parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION));
}
static int calc_stride(struct spa_audio_info_raw *info)
{
int res = info->channels;
switch (info->format) {
case SPA_AUDIO_FORMAT_U8:
case SPA_AUDIO_FORMAT_S8:
case SPA_AUDIO_FORMAT_ALAW:
case SPA_AUDIO_FORMAT_ULAW:
return res;
case SPA_AUDIO_FORMAT_S16:
case SPA_AUDIO_FORMAT_S16_OE:
case SPA_AUDIO_FORMAT_U16:
return res * 2;
case SPA_AUDIO_FORMAT_S24:
case SPA_AUDIO_FORMAT_S24_OE:
case SPA_AUDIO_FORMAT_U24:
return res * 3;
case SPA_AUDIO_FORMAT_S24_32:
case SPA_AUDIO_FORMAT_S24_32_OE:
case SPA_AUDIO_FORMAT_S32:
case SPA_AUDIO_FORMAT_S32_OE:
case SPA_AUDIO_FORMAT_U32:
case SPA_AUDIO_FORMAT_U32_OE:
case SPA_AUDIO_FORMAT_F32:
case SPA_AUDIO_FORMAT_F32_OE:
return res * 4;
case SPA_AUDIO_FORMAT_F64:
case SPA_AUDIO_FORMAT_F64_OE:
return res * 8;
default:
return 0;
}
}
static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
{
const char *str;
@ -1946,7 +1738,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
struct pw_properties *props = NULL;
struct impl *impl;
const char *str, *name, *hostname, *ip, *port;
int res;
int res = 0;
PW_LOG_TOPIC_INIT(mod_topic);
@ -1994,67 +1786,15 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
goto error;
}
if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
if (pw_properties_get(props, PW_KEY_DEVICE_ICON_NAME) == NULL)
pw_properties_set(props, PW_KEY_DEVICE_ICON_NAME, "audio-speakers");
if ((name = pw_properties_get(props, "raop.name")) == NULL)
name = "RAOP";
if ((str = strstr(name, "@"))) {
str++;
if (strlen(str) > 0)
name = str;
}
if ((hostname = pw_properties_get(props, "raop.hostname")) == NULL)
hostname = name;
if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.%s.%s",
hostname, ip, port);
if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
"%s", name);
if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL)
pw_properties_set(props, PW_KEY_NODE_LATENCY, "352/44100");
if ((str = pw_properties_get(props, "stream.props")) != NULL)
pw_properties_update_string(impl->stream_props, str, strlen(str));
copy_props(impl, props, PW_KEY_AUDIO_FORMAT);
copy_props(impl, props, PW_KEY_AUDIO_RATE);
copy_props(impl, props, PW_KEY_AUDIO_CHANNELS);
copy_props(impl, props, SPA_KEY_AUDIO_POSITION);
copy_props(impl, props, PW_KEY_DEVICE_ICON_NAME);
copy_props(impl, props, PW_KEY_NODE_NAME);
copy_props(impl, props, PW_KEY_NODE_DESCRIPTION);
copy_props(impl, props, PW_KEY_NODE_GROUP);
copy_props(impl, props, PW_KEY_NODE_LATENCY);
copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
copy_props(impl, props, PW_KEY_MEDIA_CLASS);
parse_audio_info(impl->stream_props, &impl->info);
impl->stride = calc_stride(&impl->info);
if (impl->stride == 0) {
pw_log_error("unsupported audio format:%d channels:%d",
impl->info.format, impl->info.channels);
res = -EINVAL;
goto error;
}
if ((str = pw_properties_get(props, "raop.transport")) == NULL)
str = "udp";
if (spa_streq(str, "udp"))
if (spa_streq(str, "udp")) {
impl->protocol = PROTO_UDP;
else if (spa_streq(str, "tcp"))
impl->psamples = FRAMES_PER_UDP_PACKET;
} else if (spa_streq(str, "tcp")) {
impl->protocol = PROTO_TCP;
else {
impl->psamples = FRAMES_PER_TCP_PACKET;
} else {
pw_log_error( "can't handle transport %s", str);
res = -EINVAL;
goto error;
@ -2088,9 +1828,78 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
str = pw_properties_get(props, "raop.password");
impl->password = str ? strdup(str) : NULL;
if ((str = pw_properties_get(props, "raop.latency.ms")) == NULL)
str = DEFAULT_LATENCY_MS;
impl->latency = SPA_MAX(atoi(str) * impl->info.rate / 1000u, RAOP_LATENCY_MIN);
if ((name = pw_properties_get(props, "raop.name")) == NULL)
name = "RAOP";
if ((str = strchr(name, '@')) != NULL) {
str++;
if (strlen(str) > 0)
name = str;
}
if ((hostname = pw_properties_get(props, "raop.hostname")) == NULL)
hostname = name;
impl->rate = RAOP_RATE;
impl->latency = msec_to_samples(impl, RAOP_LATENCY_MS);
impl->stride = RAOP_STRIDE;
impl->mtu = impl->stride * impl->psamples;
impl->sync_period = impl->rate / impl->psamples;
if (pw_properties_get(props, PW_KEY_AUDIO_FORMAT) == NULL)
pw_properties_setf(props, PW_KEY_AUDIO_FORMAT, "%s", RAOP_FORMAT);
if (pw_properties_get(props, PW_KEY_AUDIO_RATE) == NULL)
pw_properties_setf(props, PW_KEY_AUDIO_RATE, "%ld", impl->rate);
if (pw_properties_get(props, PW_KEY_DEVICE_ICON_NAME) == NULL)
pw_properties_set(props, PW_KEY_DEVICE_ICON_NAME, "audio-speakers");
if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.%s.%s",
hostname, ip, port);
if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", name);
if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL)
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%ld",
impl->psamples, impl->rate);
if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
if (pw_properties_get(props, PW_KEY_MEDIA_FORMAT) == NULL)
pw_properties_setf(props, PW_KEY_MEDIA_FORMAT, "%d", SPA_AUDIO_FORMAT_S16_LE);
if (pw_properties_get(props, "net.mtu") == NULL)
pw_properties_setf(props, "net.mtu", "%d", impl->mtu);
if (pw_properties_get(props, "rtp.sender-ts-offset") == NULL)
pw_properties_setf(props, "rtp.sender-ts-offset", "%d", 0);
if (pw_properties_get(props, "sess.ts-direct") == NULL)
pw_properties_set(props, "sess.ts-direct", 0);
if (pw_properties_get(props, "sess.media") == NULL)
pw_properties_set(props, "sess.media", "raop");
if (pw_properties_get(props, "sess.latency.msec") == NULL)
pw_properties_setf(props, "sess.latency.msec", "%d", RAOP_LATENCY_MS);
if ((str = pw_properties_get(props, "stream.props")) != NULL)
pw_properties_update_string(impl->stream_props, str, strlen(str));
copy_props(impl, props, PW_KEY_AUDIO_FORMAT);
copy_props(impl, props, PW_KEY_AUDIO_RATE);
copy_props(impl, props, PW_KEY_AUDIO_CHANNELS);
copy_props(impl, props, SPA_KEY_AUDIO_POSITION);
copy_props(impl, props, PW_KEY_DEVICE_ICON_NAME);
copy_props(impl, props, PW_KEY_NODE_NAME);
copy_props(impl, props, PW_KEY_NODE_DESCRIPTION);
copy_props(impl, props, PW_KEY_NODE_GROUP);
copy_props(impl, props, PW_KEY_NODE_LATENCY);
copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
copy_props(impl, props, PW_KEY_MEDIA_CLASS);
copy_props(impl, props, PW_KEY_MEDIA_FORMAT);
copy_props(impl, props, "net.mtu");
copy_props(impl, props, "rtp.sender-ts-offset");
copy_props(impl, props, "sess.media");
copy_props(impl, props, "sess.name");
copy_props(impl, props, "sess.min-ptime");
copy_props(impl, props, "sess.max-ptime");
copy_props(impl, props, "sess.latency.msec");
copy_props(impl, props, "sess.ts-refclk");
copy_props(impl, props, "sess.ts-direct");
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
if (impl->core == NULL) {
@ -2115,8 +1924,23 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
&impl->core_listener,
&core_events, impl);
if ((res = create_stream(impl)) < 0)
impl->stream = rtp_stream_new(impl->core,
PW_DIRECTION_INPUT, pw_properties_copy(impl->stream_props),
&stream_events, impl);
if (impl->stream == NULL) {
res = -errno;
pw_log_error("can't create raop stream: %m");
goto error;
}
impl->headers = pw_properties_new(NULL, NULL);
impl->rtsp = pw_rtsp_client_new(impl->loop, NULL, 0);
if (impl->rtsp == NULL)
goto error;
pw_rtsp_client_add_listener(impl->rtsp, &impl->rtsp_listener,
&rtsp_events, impl);
pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);