mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
Merge branch 'rtp-module-fixes' into 'master'
module-rtp: Changes for better robustness, including SAP receive start retries, PTP management socket reconnects, and IGMP recovery logic See merge request pipewire/pipewire!2580
This commit is contained in:
commit
57811bf80c
8 changed files with 469 additions and 52 deletions
|
|
@ -248,6 +248,16 @@ struct node {
|
||||||
struct session *session;
|
struct session *session;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct igmp_recovery {
|
||||||
|
struct pw_timer timer;
|
||||||
|
int socket_fd;
|
||||||
|
struct sockaddr_storage mcast_addr;
|
||||||
|
socklen_t mcast_len;
|
||||||
|
uint32_t if_index;
|
||||||
|
bool is_ipv6;
|
||||||
|
uint32_t deadline;
|
||||||
|
};
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
struct pw_properties *props;
|
struct pw_properties *props;
|
||||||
|
|
||||||
|
|
@ -265,7 +275,11 @@ struct impl {
|
||||||
struct pw_registry *registry;
|
struct pw_registry *registry;
|
||||||
struct spa_hook registry_listener;
|
struct spa_hook registry_listener;
|
||||||
|
|
||||||
struct pw_timer timer;
|
struct pw_timer sap_send_timer;
|
||||||
|
|
||||||
|
/* This timer is used when the first start_sap() call fails because
|
||||||
|
* of an ENODEV error (see the start_sap() code for details) */
|
||||||
|
struct pw_timer start_sap_retry_timer;
|
||||||
|
|
||||||
char *ifname;
|
char *ifname;
|
||||||
uint32_t ttl;
|
uint32_t ttl;
|
||||||
|
|
@ -281,6 +295,10 @@ struct impl {
|
||||||
struct spa_source *sap_source;
|
struct spa_source *sap_source;
|
||||||
uint32_t cleanup_interval;
|
uint32_t cleanup_interval;
|
||||||
|
|
||||||
|
/* IGMP recovery (triggers when no SAP packets are
|
||||||
|
* received after the recovery deadline is reached) */
|
||||||
|
struct igmp_recovery igmp_recovery;
|
||||||
|
|
||||||
uint32_t max_sessions;
|
uint32_t max_sessions;
|
||||||
uint32_t n_sessions;
|
uint32_t n_sessions;
|
||||||
struct spa_list sessions;
|
struct spa_list sessions;
|
||||||
|
|
@ -288,7 +306,7 @@ struct impl {
|
||||||
char *extra_attrs_preamble;
|
char *extra_attrs_preamble;
|
||||||
char *extra_attrs_end;
|
char *extra_attrs_end;
|
||||||
|
|
||||||
char *ptp_mgmt_socket;
|
char *ptp_mgmt_socket_path;
|
||||||
int ptp_fd;
|
int ptp_fd;
|
||||||
uint32_t ptp_seq;
|
uint32_t ptp_seq;
|
||||||
uint8_t clock_id[8];
|
uint8_t clock_id[8];
|
||||||
|
|
@ -322,6 +340,7 @@ static const struct format_info *find_audio_format_info(const char *mime)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int start_sap(struct impl *impl);
|
||||||
static int send_sap(struct impl *impl, struct session *sess, bool bye);
|
static int send_sap(struct impl *impl, struct session *sess, bool bye);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -383,7 +402,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_unix_socket(const char *path) {
|
static int make_unix_ptp_mgmt_socket(const char *path) {
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
|
|
||||||
spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||||
|
|
@ -419,7 +438,7 @@ static int make_send_socket(
|
||||||
|
|
||||||
af = src->ss_family;
|
af = src->ss_family;
|
||||||
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
||||||
pw_log_error("socket failed: %m");
|
pw_log_error("socket() failed: %m");
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
if (bind(fd, (struct sockaddr*)src, src_len) < 0) {
|
if (bind(fd, (struct sockaddr*)src, src_len) < 0) {
|
||||||
|
|
@ -451,6 +470,9 @@ static int make_send_socket(
|
||||||
pw_log_warn("setsockopt(IPV6_MULTICAST_HOPS) failed: %m");
|
pw_log_warn("setsockopt(IPV6_MULTICAST_HOPS) failed: %m");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pw_log_info("sender socket up and running");
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
error:
|
error:
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
@ -458,7 +480,7 @@ error:
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
|
static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
|
||||||
char *ifname)
|
char *ifname, struct igmp_recovery *igmp_recovery)
|
||||||
{
|
{
|
||||||
int af, fd, val, res;
|
int af, fd, val, res;
|
||||||
struct ifreq req;
|
struct ifreq req;
|
||||||
|
|
@ -468,13 +490,13 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
|
||||||
|
|
||||||
af = sa->ss_family;
|
af = sa->ss_family;
|
||||||
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
||||||
pw_log_error("socket failed: %m");
|
pw_log_error("socket() failed: %m");
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
val = 1;
|
val = 1;
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error("setsockopt failed: %m");
|
pw_log_error("setsockopt() failed: %m");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
spa_zero(req);
|
spa_zero(req);
|
||||||
|
|
@ -528,6 +550,16 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store multicast info for recovery */
|
||||||
|
igmp_recovery->socket_fd = fd;
|
||||||
|
igmp_recovery->mcast_addr = ba;
|
||||||
|
igmp_recovery->mcast_len = salen;
|
||||||
|
igmp_recovery->if_index = req.ifr_ifindex;
|
||||||
|
igmp_recovery->is_ipv6 = (af == AF_INET6);
|
||||||
|
pw_log_debug("stored %s multicast info: socket_fd=%d, "
|
||||||
|
"if_index=%d", igmp_recovery->is_ipv6 ?
|
||||||
|
"IPv6" : "IPv4", fd, req.ifr_ifindex);
|
||||||
|
|
||||||
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
|
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error("bind() failed: %m");
|
pw_log_error("bind() failed: %m");
|
||||||
|
|
@ -540,6 +572,9 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pw_log_info("receiver socket up and running");
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
error:
|
error:
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
@ -548,8 +583,13 @@ error:
|
||||||
|
|
||||||
static bool update_ts_refclk(struct impl *impl)
|
static bool update_ts_refclk(struct impl *impl)
|
||||||
{
|
{
|
||||||
if (!impl->ptp_mgmt_socket || impl->ptp_fd < 0)
|
if (!impl->ptp_mgmt_socket_path)
|
||||||
return false;
|
return false;
|
||||||
|
if (impl->ptp_fd < 0) {
|
||||||
|
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
|
||||||
|
if (impl->ptp_fd < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Read if something is left in the socket
|
// Read if something is left in the socket
|
||||||
int avail;
|
int avail;
|
||||||
|
|
@ -581,6 +621,12 @@ static bool update_ts_refclk(struct impl *impl)
|
||||||
|
|
||||||
if (write(impl->ptp_fd, &req, sizeof(req)) == -1) {
|
if (write(impl->ptp_fd, &req, sizeof(req)) == -1) {
|
||||||
pw_log_warn("Failed to send PTP management request: %m");
|
pw_log_warn("Failed to send PTP management request: %m");
|
||||||
|
if (errno != ENOTCONN)
|
||||||
|
return false;
|
||||||
|
close(impl->ptp_fd);
|
||||||
|
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
|
||||||
|
if (impl->ptp_fd > -1)
|
||||||
|
pw_log_info("Reopened PTP management socket");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -922,7 +968,98 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_timer_event(void *data)
|
static void on_igmp_recovery_timer_event(void *data)
|
||||||
|
{
|
||||||
|
struct impl *impl = data;
|
||||||
|
char addr[128];
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
/* Only attempt recovery if we have a valid socket and multicast address */
|
||||||
|
if (impl->igmp_recovery.socket_fd < 0) {
|
||||||
|
pw_log_debug("no socket, skipping IGMP recovery");
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL);
|
||||||
|
pw_log_info("IGMP recovery triggered for %s", addr);
|
||||||
|
|
||||||
|
/* Force IGMP membership refresh by leaving the group first, then rejoin */
|
||||||
|
if (impl->igmp_recovery.is_ipv6) {
|
||||||
|
struct ipv6_mreq mr6;
|
||||||
|
memset(&mr6, 0, sizeof(mr6));
|
||||||
|
mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr;
|
||||||
|
mr6.ipv6mr_interface = impl->igmp_recovery.if_index;
|
||||||
|
|
||||||
|
/* Leave the group first */
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
|
||||||
|
&mr6, sizeof(mr6));
|
||||||
|
if (SPA_LIKELY(res == 0)) {
|
||||||
|
pw_log_info("left IPv6 multicast group");
|
||||||
|
} else {
|
||||||
|
if (errno == EADDRNOTAVAIL) {
|
||||||
|
pw_log_info("attempted to leave IPv6 multicast group, but "
|
||||||
|
"membership was already silently dropped");
|
||||||
|
} else {
|
||||||
|
pw_log_warn("failed to leave IPv6 multicast group: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
|
||||||
|
&mr6, sizeof(mr6));
|
||||||
|
if (res < 0) {
|
||||||
|
pw_log_warn("failed to re-join IPv6 multicast group: %m");
|
||||||
|
} else {
|
||||||
|
pw_log_info("re-joined IPv6 multicast group successfully");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct ip_mreqn mr4;
|
||||||
|
memset(&mr4, 0, sizeof(mr4));
|
||||||
|
mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr;
|
||||||
|
mr4.imr_ifindex = impl->igmp_recovery.if_index;
|
||||||
|
|
||||||
|
/* Leave the group first */
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
|
||||||
|
&mr4, sizeof(mr4));
|
||||||
|
if (SPA_LIKELY(res == 0)) {
|
||||||
|
pw_log_info("left IPv4 multicast group");
|
||||||
|
} else {
|
||||||
|
if (errno == EADDRNOTAVAIL) {
|
||||||
|
pw_log_info("attempted to leave IPv4 multicast group, but "
|
||||||
|
"membership was already silently dropped");
|
||||||
|
} else {
|
||||||
|
pw_log_warn("failed to leave IPv4 multicast group: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
||||||
|
&mr4, sizeof(mr4));
|
||||||
|
if (res < 0) {
|
||||||
|
pw_log_warn("failed to re-join IPv4 multicast group: %m");
|
||||||
|
} else {
|
||||||
|
pw_log_info("re-joined IPv4 multicast group successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
|
/* If rejoining failed, try again in 1 second. This can happen
|
||||||
|
* for example when the network interface went down, and is not
|
||||||
|
* yet up and running again, and ENODEV is returned as a result.
|
||||||
|
* Otherwise, continue with the usual deadline. */
|
||||||
|
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
|
||||||
|
&impl->igmp_recovery.timer.timeout,
|
||||||
|
((res == 0) ? impl->igmp_recovery.deadline : 1) * SPA_NSEC_PER_SEC,
|
||||||
|
on_igmp_recovery_timer_event, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rearm_igmp_recovery_timer(struct impl *impl)
|
||||||
|
{
|
||||||
|
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
|
||||||
|
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
|
||||||
|
NULL, impl->igmp_recovery.deadline * SPA_NSEC_PER_SEC,
|
||||||
|
on_igmp_recovery_timer_event, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_sap_send_timer_event(void *data)
|
||||||
{
|
{
|
||||||
struct impl *impl = data;
|
struct impl *impl = data;
|
||||||
struct session *sess, *tmp;
|
struct session *sess, *tmp;
|
||||||
|
|
@ -956,9 +1093,16 @@ static void on_timer_event(void *data)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pw_timer_queue_add(impl->timer_queue, &impl->timer,
|
pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer,
|
||||||
&impl->timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
|
&impl->sap_send_timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
|
||||||
on_timer_event, impl);
|
on_sap_send_timer_event, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_start_sap_retry_timer_event(void *data)
|
||||||
|
{
|
||||||
|
struct impl *impl = data;
|
||||||
|
pw_log_debug("trying again to start SAP send after previous attempt failed with ENODEV");
|
||||||
|
start_sap(impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct session *session_find(struct impl *impl, const struct sdp_info *info)
|
static struct session *session_find(struct impl *impl, const struct sdp_info *info)
|
||||||
|
|
@ -1646,23 +1790,70 @@ on_sap_io(void *data, int fd, uint32_t mask)
|
||||||
buffer[len] = 0;
|
buffer[len] = 0;
|
||||||
if ((res = parse_sap(impl, buffer, len)) < 0)
|
if ((res = parse_sap(impl, buffer, len)) < 0)
|
||||||
pw_log_warn("error parsing SAP: %s", spa_strerror(res));
|
pw_log_warn("error parsing SAP: %s", spa_strerror(res));
|
||||||
|
|
||||||
|
rearm_igmp_recovery_timer(impl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int start_sap(struct impl *impl)
|
static int start_sap(struct impl *impl)
|
||||||
{
|
{
|
||||||
int fd = -1, res;
|
int fd = -1, res = 0;
|
||||||
char addr[128] = "invalid";
|
char addr[128] = "invalid";
|
||||||
|
|
||||||
pw_log_info("starting SAP timer");
|
pw_log_info("starting SAP send timer");
|
||||||
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->timer,
|
/* start_sap() might be called more than once. See the make_recv_socket()
|
||||||
|
* call below for why that can happen. In such a case, the timer was
|
||||||
|
* started already. The easiest way of handling it is to just cancel it.
|
||||||
|
* Such cases are not expected to occur often, so canceling and then
|
||||||
|
* adding the timer again is acceptable. */
|
||||||
|
pw_timer_queue_cancel(&impl->sap_send_timer);
|
||||||
|
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer,
|
||||||
NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
|
NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
|
||||||
on_timer_event, impl)) < 0) {
|
on_sap_send_timer_event, impl)) < 0) {
|
||||||
pw_log_error("can't add timer: %s", spa_strerror(res));
|
pw_log_error("can't add SAP send timer: %s", spa_strerror(res));
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0)
|
if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname,
|
||||||
return fd;
|
&(impl->igmp_recovery))) < 0) {
|
||||||
|
/* If make_recv_socket() tries to create a socket and join to a multicast
|
||||||
|
* group while the network interfaces are not ready yet to do so
|
||||||
|
* (usually because a network manager component is still setting up
|
||||||
|
* those network interfaces), ENODEV will be returned. This is essentially
|
||||||
|
* a race condition. There is no discernible way to be notified when the
|
||||||
|
* network interfaces are ready for that operation, so the next best
|
||||||
|
* approach is to essentially do a form of polling by retrying the
|
||||||
|
* start_sap() call after some time. The start_sap_retry_timer exists
|
||||||
|
* precisely for that purpose. This means that ENODEV is not treated as
|
||||||
|
* an error, but instead, it triggers the creation of that timer. */
|
||||||
|
if (fd == -ENODEV) {
|
||||||
|
pw_log_warn("failed to create receiver socket because network device "
|
||||||
|
"is not ready and present yet; will try again");
|
||||||
|
|
||||||
|
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
|
||||||
|
/* Use a 1-second retry interval. The network interfaces
|
||||||
|
* are likely to be up and running then. */
|
||||||
|
pw_timer_queue_add(impl->timer_queue, &impl->start_sap_retry_timer,
|
||||||
|
NULL, 1 * SPA_NSEC_PER_SEC,
|
||||||
|
on_start_sap_retry_timer_event, impl);
|
||||||
|
|
||||||
|
/* It is important to return 0 in this case. Otherwise, the nonzero return
|
||||||
|
* value will later be propagated through the core as an error. */
|
||||||
|
res = 0;
|
||||||
|
goto finish;
|
||||||
|
} else {
|
||||||
|
pw_log_error("failed to create socket: %s", spa_strerror(-fd));
|
||||||
|
/* If ENODEV was returned earlier, and the start_sap_retry_timer
|
||||||
|
* was consequently created, but then a non-ENODEV error occurred,
|
||||||
|
* the timer must be stopped and removed. */
|
||||||
|
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
|
||||||
|
res = fd;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cleanup the timer in case ENODEV occurred earlier, and this time,
|
||||||
|
* the socket creation succeeded. */
|
||||||
|
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
|
||||||
|
|
||||||
pw_net_get_ip(&impl->sap_addr, addr, sizeof(addr), NULL, NULL);
|
pw_net_get_ip(&impl->sap_addr, addr, sizeof(addr), NULL, NULL);
|
||||||
pw_log_info("starting SAP listener on %s", addr);
|
pw_log_info("starting SAP listener on %s", addr);
|
||||||
|
|
@ -1673,11 +1864,15 @@ static int start_sap(struct impl *impl)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
rearm_igmp_recovery_timer(impl);
|
||||||
|
|
||||||
|
finish:
|
||||||
|
return res;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (fd > 0)
|
if (fd > 0)
|
||||||
close(fd);
|
close(fd);
|
||||||
return res;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void node_event_info(void *data, const struct pw_node_info *info)
|
static void node_event_info(void *data, const struct pw_node_info *info)
|
||||||
|
|
@ -1807,7 +2002,9 @@ static void impl_destroy(struct impl *impl)
|
||||||
if (impl->core && impl->do_disconnect)
|
if (impl->core && impl->do_disconnect)
|
||||||
pw_core_disconnect(impl->core);
|
pw_core_disconnect(impl->core);
|
||||||
|
|
||||||
pw_timer_queue_cancel(&impl->timer);
|
pw_timer_queue_cancel(&impl->sap_send_timer);
|
||||||
|
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
|
||||||
|
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
|
||||||
if (impl->sap_source)
|
if (impl->sap_source)
|
||||||
pw_loop_destroy_source(impl->loop, impl->sap_source);
|
pw_loop_destroy_source(impl->loop, impl->sap_source);
|
||||||
|
|
||||||
|
|
@ -1821,7 +2018,7 @@ static void impl_destroy(struct impl *impl)
|
||||||
free(impl->extra_attrs_preamble);
|
free(impl->extra_attrs_preamble);
|
||||||
free(impl->extra_attrs_end);
|
free(impl->extra_attrs_end);
|
||||||
|
|
||||||
free(impl->ptp_mgmt_socket);
|
free(impl->ptp_mgmt_socket_path);
|
||||||
free(impl->ifname);
|
free(impl->ifname);
|
||||||
free(impl);
|
free(impl);
|
||||||
}
|
}
|
||||||
|
|
@ -1874,6 +2071,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
impl->ptp_fd = -1;
|
impl->ptp_fd = -1;
|
||||||
spa_list_init(&impl->sessions);
|
spa_list_init(&impl->sessions);
|
||||||
|
|
||||||
|
impl->igmp_recovery.socket_fd = -1;
|
||||||
|
impl->igmp_recovery.if_index = -1;
|
||||||
|
|
||||||
if (args == NULL)
|
if (args == NULL)
|
||||||
args = "";
|
args = "";
|
||||||
|
|
||||||
|
|
@ -1893,11 +2093,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
impl->ifname = str ? strdup(str) : NULL;
|
impl->ifname = str ? strdup(str) : NULL;
|
||||||
|
|
||||||
str = pw_properties_get(props, "ptp.management-socket");
|
str = pw_properties_get(props, "ptp.management-socket");
|
||||||
impl->ptp_mgmt_socket = str ? strdup(str) : NULL;
|
impl->ptp_mgmt_socket_path = str ? strdup(str) : NULL;
|
||||||
|
|
||||||
// TODO: support UDP management access as well
|
// TODO: support UDP management access as well
|
||||||
if (impl->ptp_mgmt_socket)
|
if (impl->ptp_mgmt_socket_path)
|
||||||
impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket);
|
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
|
||||||
|
|
||||||
if ((str = pw_properties_get(props, "sap.ip")) == NULL)
|
if ((str = pw_properties_get(props, "sap.ip")) == NULL)
|
||||||
str = DEFAULT_SAP_IP;
|
str = DEFAULT_SAP_IP;
|
||||||
|
|
@ -1909,6 +2109,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
impl->cleanup_interval = pw_properties_get_uint32(impl->props,
|
impl->cleanup_interval = pw_properties_get_uint32(impl->props,
|
||||||
"sap.cleanup.sec", DEFAULT_CLEANUP_SEC);
|
"sap.cleanup.sec", DEFAULT_CLEANUP_SEC);
|
||||||
|
|
||||||
|
/* We will use half of the cleanup interval for IGMP deadline, minimum 1 second */
|
||||||
|
impl->igmp_recovery.deadline = SPA_MAX(impl->cleanup_interval / 2, 1u);
|
||||||
|
pw_log_info("using IGMP deadline of %" PRIu32 " second(s)",
|
||||||
|
impl->igmp_recovery.deadline);
|
||||||
|
|
||||||
impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL);
|
impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL);
|
||||||
impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP);
|
impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP);
|
||||||
impl->max_sessions = pw_properties_get_uint32(props, "sap.max-sessions", DEFAULT_MAX_SESSIONS);
|
impl->max_sessions = pw_properties_get_uint32(props, "sap.max-sessions", DEFAULT_MAX_SESSIONS);
|
||||||
|
|
|
||||||
|
|
@ -1043,8 +1043,11 @@ on_data_io(void *data, int fd, uint32_t mask)
|
||||||
if (sess == NULL)
|
if (sess == NULL)
|
||||||
goto unknown_ssrc;
|
goto unknown_ssrc;
|
||||||
|
|
||||||
if (sess->data_ready && sess->receiving)
|
if (sess->data_ready && sess->receiving) {
|
||||||
rtp_stream_receive_packet(sess->recv, buffer, len);
|
uint64_t current_time = rtp_stream_get_nsec(sess->recv);
|
||||||
|
rtp_stream_receive_packet(sess->recv, buffer, len,
|
||||||
|
current_time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include <spa/utils/atomic.h>
|
||||||
#include <spa/utils/hook.h>
|
#include <spa/utils/hook.h>
|
||||||
#include <spa/utils/result.h>
|
#include <spa/utils/result.h>
|
||||||
#include <spa/utils/ringbuffer.h>
|
#include <spa/utils/ringbuffer.h>
|
||||||
|
|
@ -156,6 +157,9 @@
|
||||||
PW_LOG_TOPIC(mod_topic, "mod." NAME);
|
PW_LOG_TOPIC(mod_topic, "mod." NAME);
|
||||||
#define PW_LOG_TOPIC_DEFAULT mod_topic
|
#define PW_LOG_TOPIC_DEFAULT mod_topic
|
||||||
|
|
||||||
|
#define DEFAULT_IGMP_CHECK_INTERVAL_SEC 5
|
||||||
|
#define DEFAULT_IGMP_DEADLINE_SEC 30
|
||||||
|
|
||||||
#define DEFAULT_CLEANUP_SEC 60
|
#define DEFAULT_CLEANUP_SEC 60
|
||||||
#define DEFAULT_SOURCE_IP "224.0.0.56"
|
#define DEFAULT_SOURCE_IP "224.0.0.56"
|
||||||
|
|
||||||
|
|
@ -180,6 +184,23 @@ static const struct spa_dict_item module_info[] = {
|
||||||
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct igmp_recovery {
|
||||||
|
struct pw_timer timer;
|
||||||
|
int socket_fd;
|
||||||
|
struct sockaddr_storage mcast_addr;
|
||||||
|
socklen_t mcast_len;
|
||||||
|
uint32_t if_index;
|
||||||
|
bool is_ipv6;
|
||||||
|
/* This is the interval the recovery timer runs at. The timer
|
||||||
|
* checks at each interval if recovery is required. This value
|
||||||
|
* is defined by the igmp.check.interval.sec property. */
|
||||||
|
uint32_t check_interval;
|
||||||
|
/* This is the deadline for packets to arrive. If the deadline
|
||||||
|
* is exceeded, an IGMP recovery is attempted. This value is
|
||||||
|
* defined by the igmp.deadline.sec property. */
|
||||||
|
uint32_t deadline;
|
||||||
|
};
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
struct pw_impl_module *module;
|
struct pw_impl_module *module;
|
||||||
struct spa_hook module_listener;
|
struct spa_hook module_listener;
|
||||||
|
|
@ -201,6 +222,15 @@ struct impl {
|
||||||
bool always_process;
|
bool always_process;
|
||||||
uint32_t cleanup_interval;
|
uint32_t cleanup_interval;
|
||||||
|
|
||||||
|
/* IGMP recovery (triggers when no RTP packets are
|
||||||
|
* received after the recovery deadline is reached) */
|
||||||
|
struct igmp_recovery igmp_recovery;
|
||||||
|
|
||||||
|
/* Monotonic timestamp of the last time a packet was
|
||||||
|
* received. This is accessed with atomic accessors
|
||||||
|
* to avoid race conditions. */
|
||||||
|
uint64_t last_packet_time;
|
||||||
|
|
||||||
struct pw_timer standby_timer;
|
struct pw_timer standby_timer;
|
||||||
/* This timer is used when the first stream_start() call fails because
|
/* This timer is used when the first stream_start() call fails because
|
||||||
* of an ENODEV error (see the stream_start() code for details) */
|
* of an ENODEV error (see the stream_start() code for details) */
|
||||||
|
|
@ -227,13 +257,6 @@ struct impl {
|
||||||
bool waiting;
|
bool waiting;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline uint64_t get_time_ns(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
||||||
return SPA_TIMESPEC_TO_NSEC(&ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
|
static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
|
||||||
size_t size, void *user_data)
|
size_t size, void *user_data)
|
||||||
{
|
{
|
||||||
|
|
@ -261,6 +284,9 @@ on_rtp_io(void *data, int fd, uint32_t mask)
|
||||||
struct impl *impl = data;
|
struct impl *impl = data;
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
int suppressed;
|
int suppressed;
|
||||||
|
uint64_t current_time;
|
||||||
|
|
||||||
|
current_time = rtp_stream_get_nsec(impl->stream);
|
||||||
|
|
||||||
if (mask & SPA_IO_IN) {
|
if (mask & SPA_IO_IN) {
|
||||||
if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0)
|
if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0)
|
||||||
|
|
@ -270,10 +296,17 @@ on_rtp_io(void *data, int fd, uint32_t mask)
|
||||||
goto short_packet;
|
goto short_packet;
|
||||||
|
|
||||||
if (SPA_LIKELY(impl->stream)) {
|
if (SPA_LIKELY(impl->stream)) {
|
||||||
if (rtp_stream_receive_packet(impl->stream, impl->buffer, len) < 0)
|
if (rtp_stream_receive_packet(impl->stream, impl->buffer, len,
|
||||||
|
current_time) < 0)
|
||||||
goto receive_error;
|
goto receive_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update last packet timestamp for IGMP recovery.
|
||||||
|
* The recovery timer will check this to see if recovery
|
||||||
|
* is necessary. Do this _before_ invoking do_start()
|
||||||
|
* in case the stream is waking up from standby. */
|
||||||
|
SPA_ATOMIC_STORE(impl->last_packet_time, current_time);
|
||||||
|
|
||||||
if (SPA_ATOMIC_LOAD(impl->state) != STATE_RECEIVING) {
|
if (SPA_ATOMIC_LOAD(impl->state) != STATE_RECEIVING) {
|
||||||
if (!SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_RECEIVING)) {
|
if (!SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_RECEIVING)) {
|
||||||
if (SPA_ATOMIC_CAS(impl->state, STATE_IDLE, STATE_RECEIVING))
|
if (SPA_ATOMIC_CAS(impl->state, STATE_IDLE, STATE_RECEIVING))
|
||||||
|
|
@ -284,17 +317,148 @@ on_rtp_io(void *data, int fd, uint32_t mask)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
receive_error:
|
receive_error:
|
||||||
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
|
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0)
|
||||||
pw_log_warn("(%d suppressed) recv() error: %m", suppressed);
|
pw_log_warn("(%d suppressed) recv() error: %m", suppressed);
|
||||||
return;
|
return;
|
||||||
short_packet:
|
short_packet:
|
||||||
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
|
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0)
|
||||||
pw_log_warn("(%d suppressed) short packet of len %zd received",
|
pw_log_warn("(%d suppressed) short packet of len %zd received",
|
||||||
suppressed, len);
|
suppressed, len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
|
static int rejoin_igmp_group(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
|
const void *data, size_t size, void *user_data)
|
||||||
|
{
|
||||||
|
/* IMPORTANT: This must be run from within the data loop. */
|
||||||
|
|
||||||
|
int res;
|
||||||
|
struct impl *impl = user_data;
|
||||||
|
uint64_t current_time;
|
||||||
|
|
||||||
|
/* Force IGMP membership refresh by leaving the group first, then rejoin */
|
||||||
|
if (impl->igmp_recovery.is_ipv6) {
|
||||||
|
struct ipv6_mreq mr6;
|
||||||
|
memset(&mr6, 0, sizeof(mr6));
|
||||||
|
mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr;
|
||||||
|
mr6.ipv6mr_interface = impl->igmp_recovery.if_index;
|
||||||
|
|
||||||
|
/* Leave the group first */
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
|
||||||
|
&mr6, sizeof(mr6));
|
||||||
|
if (SPA_LIKELY(res == 0)) {
|
||||||
|
pw_log_info("left IPv6 multicast group");
|
||||||
|
} else {
|
||||||
|
if (errno == EADDRNOTAVAIL) {
|
||||||
|
pw_log_info("attempted to leave IPv6 multicast group, but "
|
||||||
|
"membership was already silently dropped");
|
||||||
|
} else {
|
||||||
|
pw_log_warn("failed to leave IPv6 multicast group: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
|
||||||
|
&mr6, sizeof(mr6));
|
||||||
|
if (res < 0) {
|
||||||
|
pw_log_warn("failed to re-join IPv6 multicast group: %m");
|
||||||
|
} else {
|
||||||
|
pw_log_info("re-joined IPv6 multicast group successfully");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct ip_mreqn mr4;
|
||||||
|
memset(&mr4, 0, sizeof(mr4));
|
||||||
|
mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr;
|
||||||
|
mr4.imr_ifindex = impl->igmp_recovery.if_index;
|
||||||
|
|
||||||
|
/* Leave the group first */
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
|
||||||
|
&mr4, sizeof(mr4));
|
||||||
|
if (SPA_LIKELY(res == 0)) {
|
||||||
|
pw_log_info("left IPv4 multicast group");
|
||||||
|
} else {
|
||||||
|
if (errno == EADDRNOTAVAIL) {
|
||||||
|
pw_log_info("attempted to leave IPv4 multicast group, but "
|
||||||
|
"membership was already silently dropped");
|
||||||
|
} else {
|
||||||
|
pw_log_warn("failed to leave IPv4 multicast group: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
||||||
|
&mr4, sizeof(mr4));
|
||||||
|
if (res < 0) {
|
||||||
|
pw_log_warn("failed to re-join IPv4 multicast group: %m");
|
||||||
|
} else {
|
||||||
|
pw_log_info("re-joined IPv4 multicast group successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_time = rtp_stream_get_nsec(impl->stream);
|
||||||
|
SPA_ATOMIC_STORE(impl->last_packet_time, current_time);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_igmp_recovery_timer_event(void *data)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
struct impl *impl = data;
|
||||||
|
char addr[128];
|
||||||
|
uint64_t current_time, elapsed_seconds, last_packet_time;
|
||||||
|
|
||||||
|
/* Only attempt recovery if we have a valid socket and multicast address */
|
||||||
|
if (SPA_UNLIKELY(impl->igmp_recovery.socket_fd < 0)) {
|
||||||
|
pw_log_trace("no socket, skipping IGMP recovery");
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This check if performed even if standby = false or
|
||||||
|
* receiving != STATE_RECEIVING , because the very reason
|
||||||
|
* for these states may be that the receiver socket was
|
||||||
|
* silently kicked out of the IGMP group (which causes data
|
||||||
|
* to no longer arrive, thus leading to these states). */
|
||||||
|
|
||||||
|
current_time = rtp_stream_get_nsec(impl->stream);
|
||||||
|
last_packet_time = SPA_ATOMIC_LOAD(impl->last_packet_time);
|
||||||
|
elapsed_seconds = (current_time - last_packet_time) / SPA_NSEC_PER_SEC;
|
||||||
|
|
||||||
|
/* Only trigger recovery if enough time has elapsed since last packet */
|
||||||
|
if (elapsed_seconds < impl->igmp_recovery.deadline) {
|
||||||
|
pw_log_trace("IGMP recovery check: %" PRIu64 " seconds elapsed, "
|
||||||
|
"need %" PRIu32 " seconds", elapsed_seconds,
|
||||||
|
impl->igmp_recovery.deadline);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL);
|
||||||
|
pw_log_info("starting IGMP recovery for %s", addr);
|
||||||
|
|
||||||
|
/* Run the actual recovery in the data loop, since recovery involves
|
||||||
|
* rejoining the socket to the IGMP group. By running this in the
|
||||||
|
* data loop, race conditions due to stray packets causing an on_rtp_io()
|
||||||
|
* invocation at the same time when the IGMP group rejoining takes place
|
||||||
|
* is avoided, since on_rtp_io() too runs in the data loop.
|
||||||
|
* This is a blocking call to make sure the rejoin attempt was fully
|
||||||
|
* done by the time this callback ends. (rejoin_igmp_group() does not
|
||||||
|
* do work that takes a long time to finish. )*/
|
||||||
|
res = pw_loop_locked(impl->data_loop, rejoin_igmp_group, 1, NULL, 0, impl);
|
||||||
|
|
||||||
|
if (SPA_LIKELY(res == 0)) {
|
||||||
|
pw_log_info("IGMP recovery for %s finished", addr);
|
||||||
|
} else {
|
||||||
|
pw_log_error("error while finishing IGMP recovery for %s: %s",
|
||||||
|
addr, spa_strerror(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
|
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
|
||||||
|
&impl->igmp_recovery.timer.timeout,
|
||||||
|
impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
|
||||||
|
on_igmp_recovery_timer_event, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
|
||||||
|
struct igmp_recovery *igmp_recovery)
|
||||||
{
|
{
|
||||||
int af, fd, val, res;
|
int af, fd, val, res;
|
||||||
struct ifreq req;
|
struct ifreq req;
|
||||||
|
|
@ -374,6 +538,16 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store multicast info for recovery */
|
||||||
|
igmp_recovery->socket_fd = fd;
|
||||||
|
igmp_recovery->mcast_addr = ba;
|
||||||
|
igmp_recovery->mcast_len = salen;
|
||||||
|
igmp_recovery->if_index = req.ifr_ifindex;
|
||||||
|
igmp_recovery->is_ipv6 = (af == AF_INET6);
|
||||||
|
pw_log_debug("stored %s multicast info: socket_fd=%d, "
|
||||||
|
"if_index=%d", igmp_recovery->is_ipv6 ?
|
||||||
|
"IPv6" : "IPv4", fd, req.ifr_ifindex);
|
||||||
|
|
||||||
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
|
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error("bind() failed: %m");
|
pw_log_error("bind() failed: %m");
|
||||||
|
|
@ -422,7 +596,8 @@ static void stream_open_connection(void *data, int *result)
|
||||||
pw_log_info("starting RTP listener");
|
pw_log_info("starting RTP listener");
|
||||||
|
|
||||||
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
|
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
|
||||||
impl->src_len, impl->ifname)) < 0) {
|
impl->src_len, impl->ifname,
|
||||||
|
&(impl->igmp_recovery))) < 0) {
|
||||||
/* If make_socket() tries to create a socket and join to a multicast
|
/* If make_socket() tries to create a socket and join to a multicast
|
||||||
* group while the network interfaces are not ready yet to do so
|
* group while the network interfaces are not ready yet to do so
|
||||||
* (usually because a network manager component is still setting up
|
* (usually because a network manager component is still setting up
|
||||||
|
|
@ -433,7 +608,7 @@ static void stream_open_connection(void *data, int *result)
|
||||||
* stream_start() call after some time. The stream_start_retry_timer exists
|
* stream_start() call after some time. The stream_start_retry_timer exists
|
||||||
* precisely for that purpose. This means that ENODEV is not treated as
|
* precisely for that purpose. This means that ENODEV is not treated as
|
||||||
* an error, but instead, it triggers the creation of that timer. */
|
* an error, but instead, it triggers the creation of that timer. */
|
||||||
if (errno == ENODEV) {
|
if (fd == -ENODEV) {
|
||||||
pw_log_warn("failed to create socket because network device is not ready "
|
pw_log_warn("failed to create socket because network device is not ready "
|
||||||
"and present yet; will try again");
|
"and present yet; will try again");
|
||||||
|
|
||||||
|
|
@ -449,12 +624,12 @@ static void stream_open_connection(void *data, int *result)
|
||||||
res = 0;
|
res = 0;
|
||||||
goto finish;
|
goto finish;
|
||||||
} else {
|
} else {
|
||||||
pw_log_error("failed to create socket: %m");
|
pw_log_error("failed to create socket: %s", spa_strerror(fd));
|
||||||
/* If ENODEV was returned earlier, and the stream_start_retry_timer
|
/* If ENODEV was returned earlier, and the stream_start_retry_timer
|
||||||
* was consequently created, but then a non-ENODEV error occurred,
|
* was consequently created, but then a non-ENODEV error occurred,
|
||||||
* the timer must be stopped and removed. */
|
* the timer must be stopped and removed. */
|
||||||
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
||||||
res = -errno;
|
res = fd;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -472,6 +647,13 @@ static void stream_open_connection(void *data, int *result)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
|
||||||
|
NULL, impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
|
||||||
|
on_igmp_recovery_timer_event, impl)) < 0) {
|
||||||
|
pw_log_error("can't add timer: %s", spa_strerror(res));
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
pw_log_error("failed to start RTP stream: %s", spa_strerror(res));
|
pw_log_error("failed to start RTP stream: %s", spa_strerror(res));
|
||||||
|
|
@ -495,6 +677,7 @@ static void stream_close_connection(void *data, int *result)
|
||||||
pw_log_info("stopping RTP listener");
|
pw_log_info("stopping RTP listener");
|
||||||
|
|
||||||
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
||||||
|
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
|
||||||
|
|
||||||
pw_loop_destroy_source(impl->data_loop, impl->source);
|
pw_loop_destroy_source(impl->data_loop, impl->source);
|
||||||
impl->source = NULL;
|
impl->source = NULL;
|
||||||
|
|
@ -633,6 +816,7 @@ static void impl_destroy(struct impl *impl)
|
||||||
|
|
||||||
pw_timer_queue_cancel(&impl->standby_timer);
|
pw_timer_queue_cancel(&impl->standby_timer);
|
||||||
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
|
||||||
|
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
|
||||||
|
|
||||||
if (impl->data_loop)
|
if (impl->data_loop)
|
||||||
pw_context_release_loop(impl->context, impl->data_loop);
|
pw_context_release_loop(impl->context, impl->data_loop);
|
||||||
|
|
@ -797,9 +981,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
* till we make it (or get timed out) */
|
* till we make it (or get timed out) */
|
||||||
pw_properties_set(stream_props, "rtp.receiving", "true");
|
pw_properties_set(stream_props, "rtp.receiving", "true");
|
||||||
|
|
||||||
impl->cleanup_interval = pw_properties_get_uint32(props,
|
impl->cleanup_interval = pw_properties_get_uint32(stream_props,
|
||||||
"cleanup.sec", DEFAULT_CLEANUP_SEC);
|
"cleanup.sec", DEFAULT_CLEANUP_SEC);
|
||||||
|
|
||||||
|
impl->igmp_recovery.check_interval = SPA_MAX(pw_properties_get_uint32(stream_props,
|
||||||
|
"igmp.check.interval.sec",
|
||||||
|
DEFAULT_IGMP_CHECK_INTERVAL_SEC), 1u);
|
||||||
|
pw_log_info("using IGMP check interval of %" PRIu32 " second(s)",
|
||||||
|
impl->igmp_recovery.check_interval);
|
||||||
|
|
||||||
|
impl->igmp_recovery.deadline = SPA_MAX(pw_properties_get_uint32(stream_props,
|
||||||
|
"igmp.deadline.sec", DEFAULT_IGMP_DEADLINE_SEC), 5u);
|
||||||
|
pw_log_info("using IGMP deadline of %" PRIu32 " second(s)",
|
||||||
|
impl->igmp_recovery.deadline);
|
||||||
|
|
||||||
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
|
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
|
||||||
if (impl->core == NULL) {
|
if (impl->core == NULL) {
|
||||||
str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
|
str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,8 @@ static void rtp_audio_process_playback(void *data)
|
||||||
pw_stream_queue_buffer(impl->stream, buf);
|
pw_stream_queue_buffer(impl->stream, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
|
static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
|
||||||
|
uint64_t current_time)
|
||||||
{
|
{
|
||||||
struct rtp_header *hdr;
|
struct rtp_header *hdr;
|
||||||
ssize_t hlen, plen;
|
ssize_t hlen, plen;
|
||||||
|
|
@ -273,7 +274,7 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
|
||||||
timestamp = ntohl(hdr->timestamp) - impl->ts_offset;
|
timestamp = ntohl(hdr->timestamp) - impl->ts_offset;
|
||||||
|
|
||||||
impl->receiving = true;
|
impl->receiving = true;
|
||||||
impl->last_recv_timestamp = pw_stream_get_nsec(impl->stream);
|
impl->last_recv_timestamp = current_time;
|
||||||
|
|
||||||
plen = len - hlen;
|
plen = len - hlen;
|
||||||
samples = plen / stride;
|
samples = plen / stride;
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,8 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
|
static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
|
||||||
|
uint64_t current_time)
|
||||||
{
|
{
|
||||||
struct rtp_header *hdr;
|
struct rtp_header *hdr;
|
||||||
ssize_t hlen;
|
ssize_t hlen;
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,8 @@ static void rtp_opus_process_playback(void *data)
|
||||||
pw_stream_queue_buffer(impl->stream, buf);
|
pw_stream_queue_buffer(impl->stream, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
|
static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
|
||||||
|
uint64_t current_time)
|
||||||
{
|
{
|
||||||
struct rtp_header *hdr;
|
struct rtp_header *hdr;
|
||||||
ssize_t hlen, plen;
|
ssize_t hlen, plen;
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,8 @@ struct impl {
|
||||||
* access below for the reason why. */
|
* access below for the reason why. */
|
||||||
uint8_t timer_running;
|
uint8_t timer_running;
|
||||||
|
|
||||||
int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len);
|
int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len,
|
||||||
|
uint64_t current_time);
|
||||||
/* Used for resetting the ring buffer before the stream starts, to prevent
|
/* Used for resetting the ring buffer before the stream starts, to prevent
|
||||||
* reading from uninitialized memory. This can otherwise happen in direct
|
* reading from uninitialized memory. This can otherwise happen in direct
|
||||||
* timestamp mode when the read index is set to an uninitialized location.
|
* timestamp mode when the read index is set to an uninitialized location.
|
||||||
|
|
@ -1036,10 +1037,17 @@ int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *di
|
||||||
return pw_stream_update_properties(impl->stream, dict);
|
return pw_stream_update_properties(impl->stream, dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len)
|
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len,
|
||||||
|
uint64_t current_time)
|
||||||
{
|
{
|
||||||
struct impl *impl = (struct impl*)s;
|
struct impl *impl = (struct impl*)s;
|
||||||
return impl->receive_rtp(impl, buffer, len);
|
return impl->receive_rtp(impl, buffer, len, current_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t rtp_stream_get_nsec(struct rtp_stream *s)
|
||||||
|
{
|
||||||
|
struct impl *impl = (struct impl*)s;
|
||||||
|
return pw_stream_get_nsec(impl->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate)
|
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,10 @@ void rtp_stream_destroy(struct rtp_stream *s);
|
||||||
|
|
||||||
int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *dict);
|
int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *dict);
|
||||||
|
|
||||||
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len);
|
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len,
|
||||||
|
uint64_t current_time);
|
||||||
|
|
||||||
|
uint64_t rtp_stream_get_nsec(struct rtp_stream *s);
|
||||||
|
|
||||||
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate);
|
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue