bluez5: use BT_PKT_SEQNUM for ISO packet sequence numbers

Use kernel BT_PKT_SEQNUM (likely in Linux v6.17) to provide the ISO
packets sequence numbers. Fall back to counting packets if kernel is too
old to support the feature.
This commit is contained in:
Pauli Virtanen 2025-07-13 20:08:22 +03:00 committed by Wim Taymans
parent 79a069c886
commit e8fa7929b7
3 changed files with 42 additions and 21 deletions

View file

@ -35,6 +35,7 @@
#include <sys/socket.h>
#include <linux/net_tstamp.h>
#include <linux/errqueue.h>
#include <bluetooth/bluetooth.h>
#include <spa/utils/defs.h>
#include <spa/support/log.h>
@ -45,6 +46,12 @@
#define BUFFERING_SHORT_MSEC 1000
#define BUFFERING_RATE_DIFF_MAX 0.005
#ifndef BT_PKT_SEQNUM
#define BT_PKT_SEQNUM 22
#endif
#ifndef BT_SCM_PKT_SEQNUM
#define BT_SCM_PKT_SEQNUM 0x05
#endif
struct spa_bt_decode_buffer
{
@ -363,10 +370,11 @@ static inline void spa_bt_recvmsg_update_clock(struct spa_bt_recvmsg_data *data,
data->err += (SPA_ABS(err) - data->err) / n_avg;
}
static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time)
static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time,
int *seqnum)
{
union {
char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
char buf[CMSG_SPACE(sizeof(struct scm_timestamping)) + CMSG_SPACE(sizeof(uint16_t))];
struct cmsghdr align;
} control;
struct iovec data = {
@ -383,6 +391,8 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s
uint64_t t = 0, now;
ssize_t res;
*seqnum = -1;
res = recvmsg(r->fd, &msg, MSG_DONTWAIT);
if (res < 0 || !rx_time)
return res;
@ -392,12 +402,12 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
struct scm_timestamping *tss;
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_TIMESTAMPING)
continue;
tss = (struct scm_timestamping *)CMSG_DATA(cmsg);
t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]);
break;
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
tss = (struct scm_timestamping *)CMSG_DATA(cmsg);
t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]);
} else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_PKT_SEQNUM) {
*seqnum = *((uint16_t *)CMSG_DATA(cmsg));
}
}
if (!t) {
@ -411,8 +421,8 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s
if (*rx_time > now || *rx_time + 20 * SPA_NSEC_PER_MSEC < now)
*rx_time = now;
spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64,
r, *rx_time, now, now - *rx_time, r->offset);
spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64" sn:%d",
r, *rx_time, now, now - *rx_time, r->offset, *seqnum);
return res;
}
@ -423,6 +433,7 @@ static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd,
{
int flags = 0;
socklen_t len = sizeof(flags);
uint32_t opt;
data->log = log;
data->data_system = data_system;
@ -436,6 +447,10 @@ static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd,
flags |= SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)) < 0)
spa_log_info(log, "failed to set SO_TIMESTAMPING");
opt = 1;
if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0)
spa_log_info(log, "failed to set BT_PKT_SEQNUM");
}
#endif

View file

@ -428,14 +428,14 @@ static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer
}
}
static int32_t read_data(struct impl *this, uint64_t *rx_time)
static int32_t read_data(struct impl *this, uint64_t *rx_time, int *seqnum)
{
const ssize_t b_size = sizeof(this->buffer_read);
int32_t size_read = 0;
again:
/* read data from socket */
size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time);
size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time, seqnum);
if (size_read == 0)
return 0;
@ -481,7 +481,7 @@ static int produce_plc_data(struct impl *this)
}
static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size,
uint8_t *dst, uint32_t dst_size, uint32_t *dst_out)
uint8_t *dst, uint32_t dst_size, uint32_t *dst_out, int pkt_seqnum)
{
ssize_t processed;
size_t written, avail;
@ -494,6 +494,9 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size,
src, src_avail, &seqnum, NULL)) < 0)
return processed;
if (pkt_seqnum >= 0)
seqnum = pkt_seqnum;
src += processed;
src_avail -= processed;
@ -552,12 +555,12 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size,
return src_size - src_avail;
}
static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now)
static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now, int pkt_seqnum)
{
struct port *port = &this->port;
uint32_t decoded;
spa_log_trace(this->log, "read socket data size:%d", src_size);
spa_log_trace(this->log, "%p: read socket data size:%d", this, src_size);
do {
int32_t consumed;
@ -567,7 +570,7 @@ static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_
buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail);
consumed = decode_data(this, src, src_size, buf, avail, &decoded);
consumed = decode_data(this, src, src_size, buf, avail, &decoded, pkt_seqnum);
if (consumed < 0) {
spa_log_debug(this->log, "%p: failed to decode data: %d", this, consumed);
return;
@ -583,7 +586,8 @@ static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_
if (decoded) {
dt = now - this->now;
this->now = now;
spa_log_trace(this->log, "decoded socket data seq:%u size:%d frames:%d dt:%d dms",
spa_log_trace(this->log, "%p: decoded socket data seq:%u size:%d frames:%d dt:%d dms",
this,
(unsigned int)this->seqnum, (int)decoded, (int)decoded/port->frame_size,
(int)(dt / 100000));
} else {
@ -615,6 +619,7 @@ static void media_on_ready_read(struct spa_source *source)
struct impl *this = source->data;
int32_t size_read;
uint64_t now = 0;
int pkt_seqnum = -1;
/* make sure the source is an input */
if ((source->rmask & SPA_IO_IN) == 0) {
@ -636,7 +641,7 @@ static void media_on_ready_read(struct spa_source *source)
spa_log_trace(this->log, "socket poll");
/* read */
size_read = read_data (this, &now);
size_read = read_data (this, &now, &pkt_seqnum);
if (size_read < 0) {
spa_log_error(this->log, "failed to read data: %s", spa_strerror(size_read));
goto stop;
@ -648,7 +653,7 @@ static void media_on_ready_read(struct spa_source *source)
this->codec_props_changed = false;
}
add_data(this, this->buffer_read, size_read, now);
add_data(this, this->buffer_read, size_read, now, pkt_seqnum);
return;
stop:
@ -671,7 +676,7 @@ static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, u
if (size_read == 0)
return 0;
add_data(this, buffer_read, size_read, now);
add_data(this, buffer_read, size_read, now, -1);
return 0;
stop:

View file

@ -81,10 +81,11 @@ static void sco_io_on_ready(struct spa_source *source)
if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) {
int res;
int dummy;
uint64_t rx_time = 0;
read_again:
res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time);
res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time, &dummy);
if (res <= 0) {
if (errno == EINTR) {
/* retry if interrupted */