pipewire/spa/plugins/bluez5/bt-latency.h
Pauli Virtanen a6dcdfae0c bluez5: iso-io: track and apply corrections to tx latency
Use TX timestamps to get accurate reading of queue length and latency on
kernel + controller side.

This is new kernel BT feature, so requires kernel with the necessary
patches, available currently only in bluetooth-next/master branch.
Enabling Poll Errqueue kernel experimental Bluetooth feature is also
required for this.

Use the latency information to mitigate controller issues where ISO
streams are desynchronized due to tx problems or spontaneously when some
packets that should have been sent are left sitting in the queue, and
transmission is off by a multiple of the ISO interval.  This state is
visible in the latency information, so if we see streams in a group have
persistently different latencies, drop packets to resynchronize them.

Also make corrections if the kernel/controller queues get too long, so
that we don't have too big latency there.

Since BlueZ watches the same socket for errors, and TX timestamps arrive
via the socket error queue, we need to set BT_POLL_ERRQUEUE in addition
to SO_TIMESTAMPING so that BlueZ doesn't think TX timestamps are errors.

Link: https://github.com/bluez/bluez/issues/515
Link: https://lore.kernel.org/linux-bluetooth/cover.1710440392.git.pav@iki.fi/
Link: https://lore.kernel.org/linux-bluetooth/f57e065bb571d633f811610d273711c7047af335.1712499936.git.pav@iki.fi/
2024-04-12 18:50:15 +03:00

175 lines
4.3 KiB
C

/* Spa Bluez5 ISO I/O */
/* SPDX-FileCopyrightText: Copyright © 2024 Pauli Virtanen */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_BLUEZ5_BT_LATENCY_H
#define SPA_BLUEZ5_BT_LATENCY_H
#include <sys/socket.h>
#include <linux/net_tstamp.h>
#include <linux/errqueue.h>
#include <linux/sockios.h>
#include <spa/utils/defs.h>
#include <spa/support/log.h>
#include "rate-control.h"
/* New kernel API */
#ifndef BT_SCM_ERROR
#define BT_SCM_ERROR 0x04
#endif
#ifndef BT_POLL_ERRQUEUE
#define BT_POLL_ERRQUEUE 21
#endif
/**
* Bluetooth latency tracking.
*/
struct spa_bt_latency
{
uint64_t value;
struct spa_bt_ptp ptp;
bool valid;
bool disabled;
struct {
int64_t send[64];
uint32_t pos;
int64_t prev_tx;
} impl;
};
static inline void spa_bt_latency_init(struct spa_bt_latency *lat, int fd,
uint32_t period, struct spa_log *log)
{
int so_timestamping = (SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY);
uint32_t flag;
int res;
spa_zero(*lat);
flag = 0;
res = setsockopt(fd, SOL_BLUETOOTH, BT_POLL_ERRQUEUE, &flag, sizeof(flag));
if (res < 0) {
spa_log_warn(log, "setsockopt(BT_POLL_ERRQUEUE) failed (kernel feature not enabled?): %d (%m)", errno);
lat->disabled = true;
return;
}
res = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping));
if (res < 0) {
spa_log_warn(log, "setsockopt(SO_TIMESTAMPING) failed (kernel feature not enabled?): %d (%m)", errno);
lat->disabled = true;
return;
}
/* Flush errqueue on start */
do {
res = recv(fd, NULL, 0, MSG_ERRQUEUE | MSG_DONTWAIT | MSG_TRUNC);
} while (res == 0);
spa_bt_ptp_init(&lat->ptp, period, period / 2);
}
static inline void spa_bt_latency_reset(struct spa_bt_latency *lat)
{
lat->value = 0;
lat->valid = false;
spa_bt_ptp_init(&lat->ptp, lat->ptp.period, lat->ptp.period / 2);
}
static inline void spa_bt_latency_sent(struct spa_bt_latency *lat, uint64_t now)
{
const unsigned int n = SPA_N_ELEMENTS(lat->impl.send);
if (lat->disabled)
return;
lat->impl.send[lat->impl.pos++] = now;
if (lat->impl.pos >= n)
lat->impl.pos = 0;
}
static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int fd, struct spa_log *log)
{
struct {
struct cmsghdr cm;
char control[512];
} control;
if (lat->disabled)
return -EOPNOTSUPP;
do {
struct iovec data = {
.iov_base = NULL,
.iov_len = 0
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
struct scm_timestamping *tss = NULL;
struct sock_extended_err *serr = NULL;
int res;
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
return -errno;
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
tss = (void *)CMSG_DATA(cmsg);
else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR)
serr = (void *)CMSG_DATA(cmsg);
else
continue;
}
if (!tss || !serr || serr->ee_errno != ENOMSG || serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING)
return -EINVAL;
if (serr->ee_info != SCM_TSTAMP_SND)
continue;
struct timespec *ts = &tss->ts[0];
int64_t tx_time = SPA_TIMESPEC_TO_NSEC(ts);
uint32_t tx_pos = serr->ee_data % SPA_N_ELEMENTS(lat->impl.send);
lat->value = tx_time - lat->impl.send[tx_pos];
if (lat->impl.prev_tx && tx_time > lat->impl.prev_tx)
spa_bt_ptp_update(&lat->ptp, lat->value, tx_time - lat->impl.prev_tx);
lat->impl.prev_tx = tx_time;
spa_log_trace(log, "fd:%d latency[%d] nsec:%"PRIu64" range:%d..%d ms",
fd, tx_pos, lat->value,
(int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.min / SPA_NSEC_PER_MSEC : -1),
(int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.max / SPA_NSEC_PER_MSEC : -1));
} while (true);
lat->valid = spa_bt_ptp_valid(&lat->ptp);
return 0;
}
static inline void spa_bt_latency_flush(struct spa_bt_latency *lat, int fd, struct spa_log *log)
{
int so_timestamping = 0;
/* Disable timestamping and flush errqueue */
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping));
spa_bt_latency_recv_errqueue(lat, fd, log);
lat->disabled = true;
}
#endif