* [PATCH BlueZ vRFC 2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 3/7] monitor/analyze: Fix the size of SCO packets Luiz Augusto von Dentz
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The lookup of a CIS, and of a SCO, allocates a connection when the handle
belongs to another type, so the fallback to BIS, and to eSCO, was never
reached and each packet ended up on a connection of its own.
Fixes: 8157e2fde5d3 ("monitor: Print connection type in Disconnect Complete")
Assisted-by: OpenCode:claude-opus-5.5
---
monitor/analyze.c | 37 +++++++++++++++++++++----------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/monitor/analyze.c b/monitor/analyze.c
index 13d470ff566d..168efdb4b882 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -463,6 +463,23 @@ static struct hci_conn *conn_lookup_type(struct hci_dev *dev, uint16_t handle,
return conn;
}
+/*
+ * Look up a connection of either type, only allocating one of the first type
+ * when there is none, so the packets of the second type do not end up on a
+ * new connection each.
+ */
+static struct hci_conn *conn_lookup_types(struct hci_dev *dev,
+ uint16_t handle, uint8_t type1,
+ uint8_t type2)
+{
+ struct hci_conn *conn = conn_lookup(dev, handle);
+
+ if (conn && (conn->type == type1 || conn->type == type2))
+ return conn;
+
+ return conn_lookup_type(dev, handle, type1);
+}
+
static void dev_destroy(void *data)
{
struct hci_dev *dev = data;
@@ -1386,14 +1403,8 @@ static void sco_pkt(struct timeval *tv, uint16_t index, bool out,
dev->num_hci++;
dev->num_sco++;
- conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
- BTMON_CONN_SCO);
- if (!conn) {
- conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
- BTMON_CONN_ESCO);
- if (!conn)
- return;
- }
+ conn = conn_lookup_types(dev, le16_to_cpu(hdr->handle) & 0x0fff,
+ BTMON_CONN_SCO, BTMON_CONN_ESCO);
if (out) {
conn_pkt_tx(conn, tv, size - sizeof(*hdr), NULL);
@@ -1481,14 +1492,8 @@ static void iso_pkt(struct timeval *tv, uint16_t index, bool out,
dev->num_hci++;
dev->num_iso++;
- conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
- BTMON_CONN_CIS);
- if (!conn) {
- conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
- BTMON_CONN_BIS);
- if (!conn)
- return;
- }
+ conn = conn_lookup_types(dev, le16_to_cpu(hdr->handle) & 0x0fff,
+ BTMON_CONN_CIS, BTMON_CONN_BIS);
handle = le16_to_cpu(hdr->handle);
flags = ISO_FLAGS(handle);
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH BlueZ vRFC 3/7] monitor/analyze: Fix the size of SCO packets
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 4/7] monitor/analyze: Fix the type of synchronous connections Luiz Augusto von Dentz
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The SCO header is one octet shorter than the ACL one used to strip it, so
the packets were accounted one octet short.
Fixes: dce3971d227d ("monitor/analyze: Fix not calculating latency for ISO/SCO packets")
Assisted-by: OpenCode:claude-opus-5.5
---
monitor/analyze.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/monitor/analyze.c b/monitor/analyze.c
index 168efdb4b882..b132643154f2 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -1392,7 +1392,7 @@ static void acl_pkt(struct timeval *tv, uint16_t index, bool out,
static void sco_pkt(struct timeval *tv, uint16_t index, bool out,
const void *data, uint16_t size)
{
- const struct bt_hci_acl_hdr *hdr = data;
+ const struct bt_hci_sco_hdr *hdr = data;
struct hci_dev *dev;
struct hci_conn *conn;
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH BlueZ vRFC 4/7] monitor/analyze: Fix the type of synchronous connections
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 3/7] monitor/analyze: Fix the size of SCO packets Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 5/7] monitor: Add audio quality statistics for SCO data Luiz Augusto von Dentz
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The Link_Type of the Synchronous Connection Complete event was used as
the connection type, so an eSCO connection was tracked as SCO and a SCO
one as of any type.
Fixes: 1f6e606c8504 ("monitor: Add connection tracking for SCO/ISO with -a/--analyze")
Assisted-by: OpenCode:claude-opus-5.5
---
monitor/analyze.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/monitor/analyze.c b/monitor/analyze.c
index b132643154f2..c5bf88216408 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -1063,7 +1063,9 @@ static void evt_sync_conn_complete(struct hci_dev *dev, struct timeval *tv,
if (evt->status)
return;
- conn = conn_lookup_type(dev, le16_to_cpu(evt->handle), evt->link_type);
+ conn = conn_lookup_type(dev, le16_to_cpu(evt->handle),
+ evt->link_type ? BTMON_CONN_ESCO :
+ BTMON_CONN_SCO);
if (!conn)
return;
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH BlueZ vRFC 5/7] monitor: Add audio quality statistics for SCO data
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
` (2 preceding siblings ...)
2026-10-02 19:27 ` [PATCH BlueZ vRFC 4/7] monitor/analyze: Fix the type of synchronous connections Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 6/7] monitor: Add ISO link details to the analyze mode Luiz Augusto von Dentz
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Account the Packet_Status_Flag of received SCO data, reported with
Erroneous Data Reporting, as erasures, and the jitter against the time the
data of a packet takes to play, derived from the air mode:
> BR-ESCO: Handle 257 [90:09.. flags 0x01 dlen 120 #1455 [hci0] 19979.296105
Status: Possibly invalid (0x01)
Lost: 0/1 (0.00%) missing 0 dropped 0 invalid 1 partial 0 burst 0
Found BR-ESCO connection with handle 257
...
RX loss: 0/8 (0.00%) missing 0 dropped 0 invalid 1 partial 0
RX jitter: 0.11 msec (max 0.48 msec, late 0)
RX interval: 7.50 msec (7-7 msec, ~7 msec +/- 0 msec)
The loss now reports all the erasures, with the missing samples apart.
Assisted-by: OpenCode:claude-opus-5.5
---
monitor/analyze.c | 15 +++
monitor/packet.c | 287 ++++++++++++++++++++++++++++++++--------------
monitor/packet.h | 6 +
3 files changed, 220 insertions(+), 88 deletions(-)
diff --git a/monitor/analyze.c b/monitor/analyze.c
index c5bf88216408..3a11590a0061 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -108,6 +108,7 @@ struct hci_conn {
struct hci_stats tx;
struct packet_loss rx_loss;
struct packet_jitter rx_jitter;
+ uint8_t sco_rate;
};
struct hci_conn_tx {
@@ -1072,6 +1073,7 @@ static void evt_sync_conn_complete(struct hci_dev *dev, struct timeval *tv,
memcpy(conn->bdaddr, evt->bdaddr, 6);
conn->frame_connected = frame;
conn->setup_seen = true;
+ conn->sco_rate = packet_sco_rate(evt->air_mode);
}
/* Nominal SDU interval of an unframed stream: ISO Interval over BN */
@@ -1408,6 +1410,19 @@ static void sco_pkt(struct timeval *tv, uint16_t index, bool out,
conn = conn_lookup_types(dev, le16_to_cpu(hdr->handle) & 0x0fff,
BTMON_CONN_SCO, BTMON_CONN_ESCO);
+ if (!out && size > sizeof(*hdr)) {
+ uint8_t status = (le16_to_cpu(hdr->handle) >> 12) & 0x03;
+
+ packet_loss_add_status(&conn->rx_loss, status);
+
+ if (conn->sco_rate) {
+ conn->rx_jitter.interval = hdr->dlen * 1000 /
+ conn->sco_rate;
+ packet_jitter_add(&conn->rx_jitter, tv,
+ conn->rx_jitter.interval);
+ }
+ }
+
if (out) {
conn_pkt_tx(conn, tv, size - sizeof(*hdr), NULL);
} else {
diff --git a/monitor/packet.c b/monitor/packet.c
index 09603c19e05e..1e177d0abd3d 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -12076,6 +12076,26 @@ void packet_loss_add(struct packet_loss *loss, uint16_t sn, uint8_t sflags)
loss->have_sn = true;
}
+/* Packet_Status_Flag of SCO data, reported with Erroneous Data Reporting */
+void packet_loss_add_status(struct packet_loss *loss, uint8_t status)
+{
+ loss->total++;
+
+ switch (status) {
+ case 0x01:
+ loss->invalid++;
+ break;
+ case 0x02:
+ loss->dropped++;
+ break;
+ case 0x03:
+ loss->partial++;
+ break;
+ }
+
+ loss_sample(loss, status == 0x02 || status == 0x03, 1);
+}
+
size_t packet_loss_burst_max(const struct packet_loss *loss)
{
return loss->burst_max;
@@ -12113,11 +12133,12 @@ void packet_loss_print(const struct packet_loss *loss, const char *label)
if (!loss->total)
return;
- print_field("%s loss: %zu/%zu (%zu.%02zu%%) dropped %zu invalid %zu",
- label, loss->lost, loss->total,
- loss->lost * 100 / loss->total,
- (loss->lost * 10000 / loss->total) % 100,
- loss->dropped, loss->invalid);
+ print_field("%s loss: %zu/%zu (%zu.%02zu%%) missing %zu dropped %zu "
+ "invalid %zu partial %zu", label, loss->erased,
+ loss->total, loss->erased * 100 / loss->total,
+ (loss->erased * 10000 / loss->total) % 100,
+ loss->lost, loss->dropped, loss->invalid,
+ loss->partial);
/* Account for the burst still in progress */
loss_burst_end(&tmp);
@@ -12168,6 +12189,18 @@ static void jitter_add(struct packet_jitter *jitter, struct timeval *tv,
jitter->jitter16 += (uint64_t)d - ((jitter->jitter16 + 8) >> 4);
}
+void packet_jitter_add(struct packet_jitter *jitter, struct timeval *tv,
+ uint32_t expected)
+{
+ if (!jitter->have_prev) {
+ jitter->prev_tv = *tv;
+ jitter->have_prev = true;
+ return;
+ }
+
+ jitter_add(jitter, tv, expected);
+}
+
void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
uint16_t sn)
{
@@ -12615,11 +12648,18 @@ static void sync_conn_complete_evt(struct timeval *tv, uint16_t index,
print_field("TX packet length: %d", le16_to_cpu(evt->tx_pkt_len));
print_air_mode(evt->air_mode);
- if (evt->status == 0x00)
+ if (evt->status == 0x00) {
+ struct packet_conn_data *conn;
+
assign_handle(index, le16_to_cpu(evt->handle),
evt->link_type ? BTMON_CONN_ESCO :
BTMON_CONN_SCO,
(void *)evt->bdaddr, BDADDR_BREDR);
+
+ conn = packet_get_conn_data(le16_to_cpu(evt->handle));
+ if (conn)
+ conn->sco_rate = packet_sco_rate(evt->air_mode);
+ }
}
static void sync_conn_changed_evt(struct timeval *tv, uint16_t index,
@@ -15337,71 +15377,6 @@ void packet_hci_acldata(struct timeval *tv, struct ucred *cred, uint16_t index,
packet_set_context(NULL, 0);
}
-void packet_hci_scodata(struct timeval *tv, struct ucred *cred, uint16_t index,
- bool in, const void *data, uint16_t size)
-{
- const hci_sco_hdr *hdr = data;
- uint16_t handle = le16_to_cpu(hdr->handle);
- uint8_t flags = acl_flags(handle);
- char label[8];
- char handle_str[42], extra_str[32];
- struct packet_conn_data *conn;
-
- if (index >= MAX_INDEX) {
- print_field("Invalid index (%d).", index);
- return;
- }
-
- index_list[index].frame++;
-
- if (size < HCI_SCO_HDR_SIZE) {
- if (in)
- print_packet(tv, cred, '*', index, NULL, COLOR_ERROR,
- "Malformed SCO Data RX packet", NULL, NULL);
- else
- print_packet(tv, cred, '*', index, NULL, COLOR_ERROR,
- "Malformed SCO Data TX packet", NULL, NULL);
- packet_hexdump(data, size);
- return;
- }
-
- data += HCI_SCO_HDR_SIZE;
- size -= HCI_SCO_HDR_SIZE;
- conn = packet_get_conn_data(handle);
-
- if (index_list[index].sco.total && !in)
- sprintf(handle_str, "Handle %d [%u/%u]", acl_handle(handle),
- index_list[index].sco.total, index_list[index].sco.tx);
- else
- sprintf(handle_str, "Handle %d", acl_handle(handle));
-
- handle_str_append_addr(handle_str, conn);
-
- sprintf(extra_str, "flags 0x%2.2x dlen %d", flags, hdr->dlen);
-
- if (conn)
- sprintf(label, "%s", conn_type_str(conn->type));
- else
- sprintf(label, "SCO");
-
- print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_SCODATA,
- label, handle_str, extra_str);
-
- if (!in)
- packet_enqueue_tx(tv, acl_handle(handle),
- index_list[index].frame, hdr->dlen);
-
- if (size != hdr->dlen) {
- print_text(COLOR_ERROR, "invalid packet size (%d != %d)",
- size, hdr->dlen);
- packet_hexdump(data, size);
- return;
- }
-
- if (filter_mask & PACKET_FILTER_SHOW_SCO_DATA)
- packet_hexdump(data, size);
-}
-
static void print_rx_quality(struct packet_conn_data *conn, size_t erased,
size_t late, uint8_t sflags, size_t burst)
{
@@ -15410,12 +15385,13 @@ static void print_rx_quality(struct packet_conn_data *conn, size_t erased,
/* Only report the samples that revealed a loss or arrived late */
if (loss->erased != erased || sflags)
- print_field("Lost: %zu/%zu (%zu.%02zu%%) dropped %zu "
- "invalid %zu burst %zu",
- loss->lost, loss->total,
- loss->lost * 100 / loss->total,
- (loss->lost * 10000 / loss->total) % 100,
- loss->dropped, loss->invalid, burst);
+ print_field("Lost: %zu/%zu (%zu.%02zu%%) missing %zu "
+ "dropped %zu invalid %zu partial %zu burst %zu",
+ loss->erased, loss->total,
+ loss->erased * 100 / loss->total,
+ (loss->erased * 10000 / loss->total) % 100,
+ loss->lost, loss->dropped, loss->invalid,
+ loss->partial, burst);
if (jitter->late != late)
print_field("Late: +%" PRId64 ".%02" PRId64 " msec "
@@ -15491,6 +15467,150 @@ static void quality_summary(struct timeval *tv, struct ucred *cred,
q->burst_max = loss->burst;
}
+static void rx_quality(struct timeval *tv, struct ucred *cred,
+ uint16_t index, struct packet_conn_data *conn,
+ size_t erased, size_t late, uint8_t status)
+{
+ struct packet_loss *loss = &conn->rx_loss;
+ size_t burst = loss->burst;
+
+ /* A burst that this sample has just ended */
+ if (!burst && loss->erased != erased)
+ burst = loss->burst_last;
+
+ print_rx_quality(conn, erased, late, status, burst);
+ quality_summary(tv, cred, index, conn, burst);
+}
+
+static void sco_rx_quality(struct timeval *tv, struct ucred *cred,
+ uint16_t index, struct packet_conn_data *conn,
+ uint8_t status, uint8_t dlen);
+
+void packet_hci_scodata(struct timeval *tv, struct ucred *cred, uint16_t index,
+ bool in, const void *data, uint16_t size)
+{
+ const hci_sco_hdr *hdr = data;
+ uint16_t handle = le16_to_cpu(hdr->handle);
+ uint8_t flags = acl_flags(handle);
+ char label[8];
+ char handle_str[42], extra_str[32];
+ struct packet_conn_data *conn;
+
+ if (index >= MAX_INDEX) {
+ print_field("Invalid index (%d).", index);
+ return;
+ }
+
+ index_list[index].frame++;
+
+ if (size < HCI_SCO_HDR_SIZE) {
+ if (in)
+ print_packet(tv, cred, '*', index, NULL, COLOR_ERROR,
+ "Malformed SCO Data RX packet", NULL, NULL);
+ else
+ print_packet(tv, cred, '*', index, NULL, COLOR_ERROR,
+ "Malformed SCO Data TX packet", NULL, NULL);
+ packet_hexdump(data, size);
+ return;
+ }
+
+ data += HCI_SCO_HDR_SIZE;
+ size -= HCI_SCO_HDR_SIZE;
+ conn = packet_get_conn_data(handle);
+
+ if (index_list[index].sco.total && !in)
+ sprintf(handle_str, "Handle %d [%u/%u]", acl_handle(handle),
+ index_list[index].sco.total, index_list[index].sco.tx);
+ else
+ sprintf(handle_str, "Handle %d", acl_handle(handle));
+
+ handle_str_append_addr(handle_str, conn);
+
+ sprintf(extra_str, "flags 0x%2.2x dlen %d", flags, hdr->dlen);
+
+ if (conn)
+ sprintf(label, "%s", conn_type_str(conn->type));
+ else
+ sprintf(label, "SCO");
+
+ print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_SCODATA,
+ label, handle_str, extra_str);
+
+ if (!in)
+ packet_enqueue_tx(tv, acl_handle(handle),
+ index_list[index].frame, hdr->dlen);
+
+ if (size != hdr->dlen) {
+ print_text(COLOR_ERROR, "invalid packet size (%d != %d)",
+ size, hdr->dlen);
+ packet_hexdump(data, size);
+ return;
+ }
+
+ if (in && conn)
+ sco_rx_quality(tv, cred, index, conn, flags & 0x03,
+ hdr->dlen);
+
+ if (filter_mask & PACKET_FILTER_SHOW_SCO_DATA)
+ packet_hexdump(data, size);
+}
+
+static const char *sco_status_str(uint8_t status)
+{
+ switch (status) {
+ case 0x01:
+ return "Possibly invalid";
+ case 0x02:
+ return "No data";
+ case 0x03:
+ return "Partially lost";
+ }
+
+ return "Correct";
+}
+
+/* Octets per msec of the host data, as configured by the air mode */
+uint8_t packet_sco_rate(uint8_t air_mode)
+{
+ switch (air_mode) {
+ case 0x00:
+ case 0x01:
+ /* 8 kHz 8-bit logarithmic */
+ return 8;
+ case 0x02:
+ /* 8 kHz 16-bit linear */
+ return 16;
+ case 0x03:
+ /* 64 kb/s transparent: mSBC, LC3-SWB */
+ return 8;
+ }
+
+ return 0;
+}
+
+static void sco_rx_quality(struct timeval *tv, struct ucred *cred,
+ uint16_t index, struct packet_conn_data *conn,
+ uint8_t status, uint8_t dlen)
+{
+ size_t erased = conn->rx_loss.erased;
+ size_t late = conn->rx_jitter.late;
+
+ if (status)
+ print_field("Status: %s (0x%2.2x)", sco_status_str(status),
+ status);
+
+ packet_loss_add_status(&conn->rx_loss, status);
+
+ if (tv && conn->sco_rate) {
+ /* The data of a packet takes that long to play */
+ conn->rx_jitter.interval = dlen * 1000 / conn->sco_rate;
+ packet_jitter_add(&conn->rx_jitter, tv,
+ conn->rx_jitter.interval);
+ }
+
+ rx_quality(tv, cred, index, conn, erased, late, status);
+}
+
void packet_hci_isodata(struct timeval *tv, struct ucred *cred, uint16_t index,
bool in, const void *data, uint16_t size)
{
@@ -15600,17 +15720,8 @@ void packet_hci_isodata(struct timeval *tv, struct ucred *cred, uint16_t index,
print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_ISODATA,
label, handle_str, extra_str);
- if (in && have_hdr && conn) {
- struct packet_loss *loss = &conn->rx_loss;
- size_t burst = loss->burst;
-
- /* A burst that this sample has just ended */
- if (!burst && loss->erased != erased)
- burst = loss->burst_last;
-
- print_rx_quality(conn, erased, late, sflags, burst);
- quality_summary(tv, cred, index, conn, burst);
- }
+ if (in && have_hdr && conn)
+ rx_quality(tv, cred, index, conn, erased, late, sflags);
if (!in)
packet_enqueue_tx(tv, acl_handle(handle),
diff --git a/monitor/packet.h b/monitor/packet.h
index c3e3608bdede..85b7f2ff762c 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -42,6 +42,7 @@ struct packet_loss {
size_t lost; /* Samples missing from the SN sequence */
size_t invalid; /* Samples flagged possibly invalid */
size_t dropped; /* Samples flagged as lost data */
+ size_t partial; /* Samples flagged as partially lost */
size_t total; /* Samples seen, including the lost ones */
/* Bursts of erased samples, either missing or flagged as lost */
size_t erased; /* Erased samples */
@@ -118,6 +119,7 @@ struct packet_conn_data {
struct packet_loss rx_loss;
struct packet_jitter rx_jitter;
struct packet_quality rx_quality;
+ uint8_t sco_rate; /* SCO octets per msec, 0 if unknown */
struct queue *req_q;
void *data;
void (*destroy)(struct packet_conn_data *conn, void *data);
@@ -131,6 +133,10 @@ size_t packet_loss_burst_max(const struct packet_loss *loss);
bool packet_loss_burst_ratio(const struct packet_loss *loss, double *p,
double *q, double *ratio);
void packet_loss_print(const struct packet_loss *loss, const char *label);
+void packet_loss_add_status(struct packet_loss *loss, uint8_t status);
+void packet_jitter_add(struct packet_jitter *jitter, struct timeval *tv,
+ uint32_t expected);
+uint8_t packet_sco_rate(uint8_t air_mode);
void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
uint16_t sn);
void packet_jitter_add_ts(struct packet_jitter *jitter, struct timeval *tv,
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH BlueZ vRFC 6/7] monitor: Add ISO link details to the analyze mode
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
` (3 preceding siblings ...)
2026-10-02 19:27 ` [PATCH BlueZ vRFC 5/7] monitor: Add audio quality statistics for SCO data Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 7/7] monitor: Add audio quality statistics for A2DP streams Luiz Augusto von Dentz
2026-10-02 22:07 ` [BlueZ,vRFC,1/7] monitor: Add audio quality statistics for ISO streams bluez.test.bot
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Print the link parameters of CIS and BIS connections along with the last
LE Read ISO Link Quality counters:
Found LE-BIS connection with handle 2560
...
Link: ISO interval 30.00 msec NSE 12 BN 3 PTO 0 IRC 4 latency 28880 usec
...
Link quality: 1 reads
TX unacked 140 flushed 9 last subevent 31 retransmitted 210
RX CRC errors 402 unreceived 12 duplicated 3
With a burst number above one the SDUs of an ISO interval are delivered
together, so pace them by the interval they belong to rather than by the
SDU interval, which reported most of them as jitter. On the BIS above:
- RX jitter: 13.05 msec (max 45.73 msec, late 2556)
+ RX jitter: 0.68 msec (max 30.00 msec, late 0)
Assisted-by: OpenCode:claude-opus-5.5
---
monitor/analyze.c | 73 +++++++++++++++++++++++++++++++++++++++++++++--
monitor/packet.c | 20 +++++++++++--
monitor/packet.h | 2 ++
3 files changed, 89 insertions(+), 6 deletions(-)
diff --git a/monitor/analyze.c b/monitor/analyze.c
index 3a11590a0061..2b5e3087197c 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -109,6 +109,9 @@ struct hci_conn {
struct packet_loss rx_loss;
struct packet_jitter rx_jitter;
uint8_t sco_rate;
+ size_t lq_num;
+ struct bt_hci_rsp_le_read_iso_link_quality lq;
+ char iso_link[96];
};
struct hci_conn_tx {
@@ -382,9 +385,26 @@ static void conn_destroy(void *data)
print_stats(&conn->rx, "RX");
print_stats(&conn->tx, "TX");
+ if (conn->iso_link[0])
+ print_field("Link: %s", conn->iso_link);
+
packet_loss_print(&conn->rx_loss, "RX");
packet_jitter_print(&conn->rx_jitter, "RX");
+ if (conn->lq_num) {
+ print_field("Link quality: %zu reads", conn->lq_num);
+ print_field(" TX unacked %u flushed %u last subevent %u "
+ "retransmitted %u",
+ le32_to_cpu(conn->lq.tx_unacked_packets),
+ le32_to_cpu(conn->lq.tx_flushed_packets),
+ le32_to_cpu(conn->lq.tx_last_subevent_packets),
+ le32_to_cpu(conn->lq.retransmitted_packets));
+ print_field(" RX CRC errors %u unreceived %u duplicated %u",
+ le32_to_cpu(conn->lq.crc_error_packets),
+ le32_to_cpu(conn->lq.rx_unreceived_packets),
+ le32_to_cpu(conn->lq.duplicated_packets));
+ }
+
if (conn->setup_seen) {
print_field("Connected: #%lu", conn->frame_connected);
if (conn->terminated) {
@@ -902,6 +922,24 @@ static void rsp_read_bd_addr(struct hci_dev *dev, struct timeval *tv,
memcpy(dev->bdaddr, rsp->bdaddr, 6);
}
+static void rsp_le_read_iso_link_quality(struct hci_dev *dev,
+ const void *data, uint16_t size)
+{
+ const struct bt_hci_rsp_le_read_iso_link_quality *rsp = data;
+ struct hci_conn *conn;
+
+ if (size < sizeof(*rsp) || rsp->status)
+ return;
+
+ conn = conn_lookup(dev, le16_to_cpu(rsp->handle));
+ if (!conn)
+ return;
+
+ /* The counters are cumulative so the last read has them all */
+ conn->lq = *rsp;
+ conn->lq_num++;
+}
+
static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv,
const void *data, uint16_t size)
{
@@ -919,6 +957,9 @@ static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv,
case BT_HCI_CMD_READ_BD_ADDR:
rsp_read_bd_addr(dev, tv, data, size);
break;
+ case BT_HCI_CMD_LE_READ_ISO_LINK_QUALITY:
+ rsp_le_read_iso_link_quality(dev, data, size);
+ break;
}
}
@@ -1080,9 +1121,22 @@ static void evt_sync_conn_complete(struct hci_dev *dev, struct timeval *tv,
static void set_iso_interval(struct hci_conn *conn, uint16_t interval,
uint8_t bn)
{
- if (bn)
- conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) *
- 1250 / bn;
+ if (!bn)
+ return;
+
+ conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) * 1250 / bn;
+ conn->rx_jitter.bn = bn;
+}
+
+static void set_bis_link(struct hci_conn *conn, uint16_t interval,
+ uint8_t nse, uint8_t bn, uint8_t pto,
+ uint8_t irc, const uint8_t *latency)
+{
+ snprintf(conn->iso_link, sizeof(conn->iso_link), "ISO interval "
+ "%u.%02u msec NSE %u BN %u PTO %u IRC %u latency %u "
+ "usec", le16_to_cpu(interval) * 125 / 100,
+ le16_to_cpu(interval) * 125 % 100, nse, bn, pto, irc,
+ get_le24(latency));
}
static void evt_le_cis_established(struct hci_dev *dev, struct timeval *tv,
@@ -1110,6 +1164,15 @@ static void evt_le_cis_established(struct hci_dev *dev, struct timeval *tv,
/* Only the Peripheral gets a CIS Request for the handle */
set_iso_interval(conn, evt->interval, link ? evt->c_bn : evt->p_bn);
+
+ snprintf(conn->iso_link, sizeof(conn->iso_link), "ISO interval "
+ "%u.%02u msec NSE %u BN %u/%u FT %u/%u latency %u/%u "
+ "usec (C/P)",
+ le16_to_cpu(evt->interval) * 125 / 100,
+ le16_to_cpu(evt->interval) * 125 % 100, evt->nse,
+ evt->c_bn, evt->p_bn, evt->c_ft, evt->p_ft,
+ get_le24(evt->c_latency),
+ get_le24(evt->p_latency));
}
static void evt_le_cis_req(struct hci_dev *dev, struct timeval *tv,
@@ -1152,6 +1215,8 @@ static void evt_le_big_complete(struct hci_dev *dev, struct timeval *tv,
conn->setup_seen = true;
conn->frame_connected = frame;
set_iso_interval(conn, evt->interval, evt->bn);
+ set_bis_link(conn, evt->interval, evt->nse, evt->bn,
+ evt->pto, evt->irc, evt->latency);
}
}
}
@@ -1179,6 +1244,8 @@ static void evt_le_big_sync_established(struct hci_dev *dev, struct timeval *tv,
conn->setup_seen = true;
conn->frame_connected = frame;
set_iso_interval(conn, evt->interval, evt->bn);
+ set_bis_link(conn, evt->interval, evt->nse, evt->bn,
+ evt->pto, evt->irc, evt->latency);
}
}
}
diff --git a/monitor/packet.c b/monitor/packet.c
index 1e177d0abd3d..c21c79f75971 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -12161,7 +12161,7 @@ static void jitter_add(struct packet_jitter *jitter, struct timeval *tv,
int64_t expected)
{
struct timeval delta;
- int64_t arrival, d;
+ int64_t arrival, d, period;
timersub(tv, &jitter->prev_tv, &delta);
jitter->prev_tv = *tv;
@@ -12177,7 +12177,8 @@ static void jitter_add(struct packet_jitter *jitter, struct timeval *tv,
jitter->last_dev = d;
/* A sample arriving an interval or more later than expected */
- if (jitter->interval && d >= jitter->interval)
+ period = (int64_t)jitter->interval * (jitter->bn > 1 ? jitter->bn : 1);
+ if (jitter->interval && d >= period)
jitter->late++;
if (d < 0)
@@ -12212,6 +12213,7 @@ void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
if (!jitter->have_prev) {
jitter->prev_tv = *tv;
jitter->prev_sn = sn;
+ jitter->ext_sn = sn;
jitter->have_prev = true;
return;
}
@@ -12221,7 +12223,18 @@ void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
return;
jitter->prev_sn = sn;
- jitter_add(jitter, tv, (int64_t)diff * jitter->interval);
+ jitter->ext_sn += diff;
+
+ /*
+ * With a burst number above one the samples of an ISO interval are
+ * delivered together, so pace them by the interval they belong to.
+ */
+ if (jitter->bn > 1)
+ jitter_add(jitter, tv, (int64_t)(jitter->ext_sn / jitter->bn -
+ (jitter->ext_sn - diff) / jitter->bn) *
+ jitter->interval * jitter->bn);
+ else
+ jitter_add(jitter, tv, (int64_t)diff * jitter->interval);
}
void packet_jitter_add_ts(struct packet_jitter *jitter, struct timeval *tv,
@@ -13617,6 +13630,7 @@ static void set_iso_interval(uint16_t handle, uint16_t interval, uint8_t bn)
return;
conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) * 1250 / bn;
+ conn->rx_jitter.bn = bn;
}
static void le_cis_established_evt(struct timeval *tv, uint16_t index,
diff --git a/monitor/packet.h b/monitor/packet.h
index 85b7f2ff762c..77921af39263 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -59,6 +59,8 @@ struct packet_loss {
struct packet_jitter {
uint32_t interval; /* Nominal interval in usec, 0 if unknown */
+ uint8_t bn; /* Samples delivered together, 0 if 1 */
+ uint64_t ext_sn; /* Extended sequence number */
bool have_prev;
struct timeval prev_tv;
uint32_t prev_ts;
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH BlueZ vRFC 7/7] monitor: Add audio quality statistics for A2DP streams
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
` (4 preceding siblings ...)
2026-10-02 19:27 ` [PATCH BlueZ vRFC 6/7] monitor: Add ISO link details to the analyze mode Luiz Augusto von Dentz
@ 2026-10-02 19:27 ` Luiz Augusto von Dentz
2026-10-02 22:07 ` [BlueZ,vRFC,1/7] monitor: Add audio quality statistics for ISO streams bluez.test.bot
6 siblings, 0 replies; 8+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-02 19:27 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Track the RTP sequence numbers and timestamps of AVDTP media channels, in
both directions, with the clock rate derived from the arrival times. A
pause in the arrivals of a second or more, such as a suspended stream,
restarts the pacing instead of reporting a late packet:
< BR-ACL: Handle 256 [E8:EE:C.. flags 0x02 dlen 602 #15682 [hci0] 141.214796
Channel: 41024 len 598 [PSM 25 mode Basic (0x00)] {chan 3}
Late: +374.81 msec (interval 14.51 msec)
= Quality: A2DP 256 CID 0xa040 [hci0] 36.379012
TX loss: 0/345 (0.00%) burst max 0
TX jitter: 0.12 msec late 0
Found TX L2CAP channel with CID 41024
...
TX RTP: payload type 96 clock 44100 Hz gaps 1
TX loss: 0/14786 (0.00%) missing 0 dropped 0 invalid 0 partial 0
TX jitter: 0.06 msec (max 915.34 msec, late 2)
TX interval: 14.51 msec (0-929 msec, ~14 msec +/- 8 msec)
Assisted-by: OpenCode:claude-opus-5.5
---
doc/btmon.rst | 5 +-
monitor/analyze.c | 13 +++
monitor/avdtp.c | 59 ++++++++++++
monitor/packet.c | 229 +++++++++++++++++++++++++++++++++++++---------
monitor/packet.h | 20 ++++
5 files changed, 283 insertions(+), 43 deletions(-)
diff --git a/doc/btmon.rst b/doc/btmon.rst
index fd19b73b22b3..9383fe74941b 100644
--- a/doc/btmon.rst
+++ b/doc/btmon.rst
@@ -96,8 +96,9 @@ OPTIONS
-Q MSEC, --quality MSEC Print a summary of the received audio stream
quality every MSEC milliseconds: erasures, the
longest burst of erasures, jitter and late
- samples. Only LE Audio isochronous streams are
- currently supported.
+ samples. Covers LE Audio isochronous streams,
+ SCO/eSCO data over HCI and the RTP of A2DP media
+ channels.
-E IP, --ellisys IP Send Ellisys HCI Injection.
diff --git a/monitor/analyze.c b/monitor/analyze.c
index 2b5e3087197c..7e19b61978f9 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -132,6 +132,7 @@ struct l2cap_chan {
uint8_t mode;
bool out;
struct timeval last_rx;
+ struct packet_rtp rtp;
struct hci_stats rx;
struct hci_stats tx;
};
@@ -301,6 +302,7 @@ static void chan_destroy(void *data)
print_stats(&chan->rx, "RX");
print_stats(&chan->tx, "TX");
+ packet_rtp_print(&chan->rtp, chan->out ? "TX" : "RX");
done:
queue_destroy(chan->rx.plot, free);
@@ -1444,12 +1446,23 @@ static void acl_pkt(struct timeval *tv, uint16_t index, bool out,
switch (le16_to_cpu(hdr->handle) >> 12) {
case 0x00:
case 0x02:
+ /* The start of a frame carries the L2CAP header */
+ if (size < 4)
+ break;
+
cid = get_le16(data + 2);
chan = chan_lookup(conn, cid, out);
if (cid == 1)
l2cap_sig(conn, out, data + 4, size - 4);
else if (cid == 5)
l2cap_le_sig(conn, out, data + 4, size - 4);
+ else if (chan && cid >= 0x0040 &&
+ (chan->psm == 0x0019 || !chan->psm))
+ /*
+ * AVDTP media transport, also attempted on channels
+ * whose setup was not captured.
+ */
+ packet_rtp_add(&chan->rtp, tv, data + 4, size - 4);
break;
}
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 9d0016d6bc56..4f08edf5ca95 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -768,6 +768,64 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
return true;
}
+#define MAX_STREAM 8
+
+/* Media transport channels, tracked for their RTP quality */
+static struct avdtp_stream {
+ bool used;
+ uint16_t index;
+ uint16_t handle;
+ uint16_t cid;
+ bool in;
+ struct packet_rtp rtp;
+ struct packet_quality q;
+} stream_list[MAX_STREAM];
+
+static struct avdtp_stream *stream_lookup(const struct l2cap_frame *frame)
+{
+ struct avdtp_stream *stream;
+ static unsigned int next;
+ int i;
+
+ for (i = 0; i < MAX_STREAM; i++) {
+ stream = &stream_list[i];
+
+ if (stream->used && stream->index == frame->index &&
+ stream->handle == frame->handle &&
+ stream->cid == frame->cid &&
+ stream->in == frame->in)
+ return stream;
+ }
+
+ /* Take over the oldest stream when all are used */
+ stream = &stream_list[next++ % MAX_STREAM];
+ memset(stream, 0, sizeof(*stream));
+ stream->used = true;
+ stream->index = frame->index;
+ stream->handle = frame->handle;
+ stream->cid = frame->cid;
+ stream->in = frame->in;
+
+ return stream;
+}
+
+static void avdtp_media_packet(const struct l2cap_frame *frame)
+{
+ struct avdtp_stream *stream = stream_lookup(frame);
+ struct timeval tv = frame->tv;
+ size_t erased = stream->rtp.loss.erased;
+ size_t late = stream->rtp.jitter.late;
+
+ if (!packet_rtp_add(&stream->rtp, timerisset(&tv) ? &tv : NULL,
+ frame->data, frame->size))
+ return;
+
+ packet_rtp_quality(timerisset(&tv) ? &tv : NULL, frame->index,
+ frame->handle, frame->cid,
+ frame->in, &stream->rtp, &stream->q, erased,
+ late);
+}
+
void avdtp_packet(const struct l2cap_frame *frame)
{
struct avdtp_frame avdtp_frame;
@@ -780,6 +838,7 @@ void avdtp_packet(const struct l2cap_frame *frame)
ret = avdtp_signalling_packet(&avdtp_frame);
break;
default:
+ avdtp_media_packet(frame);
if (packet_has_filter(PACKET_FILTER_SHOW_A2DP_STREAM))
packet_hexdump(frame->data, frame->size);
return;
diff --git a/monitor/packet.c b/monitor/packet.c
index c21c79f75971..def24c1c47d3 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -12256,6 +12256,125 @@ void packet_jitter_add_ts(struct packet_jitter *jitter, struct timeval *tv,
jitter_add(jitter, tv, diff);
}
+static const uint32_t rtp_rates[] = {
+ 8000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 90000, 96000,
+};
+
+/* Number of samples to derive the RTP clock rate from */
+#define RTP_PROBE 100
+
+/* Pause in the arrivals that restarts the pacing, in seconds */
+#define RTP_GAP_SEC 1
+
+static uint32_t rtp_rate(uint64_t ts, uint64_t usec)
+{
+ uint32_t best = 0;
+ uint64_t rate, best_diff = UINT64_MAX;
+ size_t i;
+
+ if (!usec)
+ return 0;
+
+ rate = ts * 1000000 / usec;
+
+ for (i = 0; i < ARRAY_SIZE(rtp_rates); i++) {
+ uint64_t diff = rate > rtp_rates[i] ? rate - rtp_rates[i] :
+ rtp_rates[i] - rate;
+
+ if (diff < best_diff) {
+ best_diff = diff;
+ best = rtp_rates[i];
+ }
+ }
+
+ return best;
+}
+
+/*
+ * Account a RTP packet, which is recognized by its version and a dynamic
+ * payload type, the latter never matching an AVDTP signal identifier.
+ */
+bool packet_rtp_add(struct packet_rtp *rtp, struct timeval *tv,
+ const void *data, uint16_t size)
+{
+ const uint8_t *hdr = data;
+ uint16_t seq;
+ uint32_t ts, diff;
+ struct timeval delta;
+
+ if (size < 12 || (hdr[0] & 0xc0) != 0x80 || (hdr[1] & 0x7f) < 96)
+ return false;
+
+ seq = get_be16(hdr + 2);
+ ts = get_be32(hdr + 4);
+
+ packet_loss_add(&rtp->loss, seq, 0);
+ rtp->pt = hdr[1] & 0x7f;
+
+ if (!tv)
+ return true;
+
+ if (!rtp->probe++) {
+ rtp->first_tv = *tv;
+ rtp->first_ts = ts;
+ rtp->prev_ts = ts;
+ return true;
+ }
+
+ if (!rtp->rate) {
+ if (rtp->probe < RTP_PROBE)
+ return true;
+
+ timersub(tv, &rtp->first_tv, &delta);
+ rtp->rate = rtp_rate(ts - rtp->first_ts,
+ (uint64_t)delta.tv_sec * 1000000 +
+ delta.tv_usec);
+ if (!rtp->rate)
+ return true;
+
+ rtp->jitter.prev_tv = *tv;
+ rtp->jitter.have_prev = true;
+ rtp->prev_ts = ts;
+ return true;
+ }
+
+ diff = ts - rtp->prev_ts;
+
+ /* Leave the duplicates and the reorders out */
+ if (!diff || diff >= 0x80000000)
+ return true;
+
+ rtp->prev_ts = ts;
+
+ /*
+ * A pause, such as the stream being suspended and started again,
+ * restarts the pacing instead of accounting the packet as late.
+ */
+ timersub(tv, &rtp->jitter.prev_tv, &delta);
+ if (delta.tv_sec >= RTP_GAP_SEC) {
+ rtp->jitter.prev_tv = *tv;
+ rtp->gaps++;
+ return true;
+ }
+
+ /* A packet is late once it misses the time its data takes to play */
+ rtp->jitter.interval = (uint64_t)diff * 1000000 / rtp->rate;
+ jitter_add(&rtp->jitter, tv, rtp->jitter.interval);
+
+ return true;
+}
+
+void packet_rtp_print(const struct packet_rtp *rtp, const char *label)
+{
+ if (!rtp->loss.total)
+ return;
+
+ print_field("%s RTP: payload type %u clock %u Hz gaps %zu", label,
+ rtp->pt, rtp->rate, rtp->gaps);
+ packet_loss_print(&rtp->loss, label);
+ packet_jitter_print(&rtp->jitter, label);
+}
+
void packet_jitter_print(const struct packet_jitter *jitter,
const char *label)
{
@@ -15391,14 +15510,12 @@ void packet_hci_acldata(struct timeval *tv, struct ucred *cred, uint16_t index,
packet_set_context(NULL, 0);
}
-static void print_rx_quality(struct packet_conn_data *conn, size_t erased,
- size_t late, uint8_t sflags, size_t burst)
+static void print_quality(struct packet_loss *loss,
+ struct packet_jitter *jitter, size_t erased,
+ size_t late, uint8_t status, size_t burst)
{
- struct packet_loss *loss = &conn->rx_loss;
- struct packet_jitter *jitter = &conn->rx_jitter;
-
/* Only report the samples that revealed a loss or arrived late */
- if (loss->erased != erased || sflags)
+ if (loss->erased != erased || status)
print_field("Lost: %zu/%zu (%zu.%02zu%%) missing %zu "
"dropped %zu invalid %zu partial %zu burst %zu",
loss->erased, loss->total,
@@ -15421,14 +15538,24 @@ void packet_set_quality_period(unsigned int msec)
quality_period = msec;
}
-static void quality_summary(struct timeval *tv, struct ucred *cred,
- uint16_t index, struct packet_conn_data *conn,
- size_t burst)
+static void quality_window(struct packet_quality *q, struct timeval *tv,
+ struct packet_loss *loss,
+ struct packet_jitter *jitter)
+{
+ q->start = *tv;
+ q->erased = loss->erased;
+ q->total = loss->total;
+ q->late = jitter->late;
+ q->burst_max = loss->burst;
+}
+
+static void quality_summary(struct timeval *tv, uint16_t index,
+ const char *name, const char *dir,
+ struct packet_quality *q,
+ struct packet_loss *loss,
+ struct packet_jitter *jitter,
+ struct packet_latency *tx_l, size_t burst)
{
- struct packet_quality *q = &conn->rx_quality;
- struct packet_loss *loss = &conn->rx_loss;
- struct packet_jitter *jitter = &conn->rx_jitter;
- char str[32];
struct timeval delta;
size_t erased, total;
uint64_t j;
@@ -15440,10 +15567,7 @@ static void quality_summary(struct timeval *tv, struct ucred *cred,
q->burst_max = burst;
if (!timerisset(&q->start)) {
- q->start = *tv;
- q->erased = loss->erased;
- q->total = loss->total;
- q->late = jitter->late;
+ quality_window(q, tv, loss, jitter);
return;
}
@@ -15455,45 +15579,68 @@ static void quality_summary(struct timeval *tv, struct ucred *cred,
total = loss->total - q->total;
j = jitter->jitter16 >> 4;
- snprintf(str, sizeof(str), "%s %u", conn_type_str(conn->type),
- conn->handle);
- print_packet(tv, cred, '=', index, NULL, COLOR_SYSTEM_NOTE,
- "Quality", str, NULL);
+ print_packet(tv, NULL, '=', index, NULL, COLOR_SYSTEM_NOTE,
+ "Quality", name, NULL);
- print_field("RX loss: %zu/%zu (%zu.%02zu%%) burst max %zu", erased,
- total, total ? erased * 100 / total : 0,
+ print_field("%s loss: %zu/%zu (%zu.%02zu%%) burst max %zu", dir,
+ erased, total, total ? erased * 100 / total : 0,
total ? (erased * 10000 / total) % 100 : 0,
q->burst_max);
if (jitter->delta.count)
- print_field("RX jitter: %" PRIu64 ".%02" PRIu64 " msec "
- "late %zu", j / 1000, (j % 1000) / 10,
+ print_field("%s jitter: %" PRIu64 ".%02" PRIu64 " msec "
+ "late %zu", dir, j / 1000, (j % 1000) / 10,
jitter->late - q->late);
- if (conn->tx_l.count)
- print_field("TX latency: ~%lld msec",
- TV_MSEC(conn->tx_l.med));
+ if (tx_l && tx_l->count)
+ print_field("TX latency: ~%lld msec", TV_MSEC(tx_l->med));
- q->start = *tv;
- q->erased = loss->erased;
- q->total = loss->total;
- q->late = jitter->late;
- q->burst_max = loss->burst;
+ quality_window(q, tv, loss, jitter);
}
-static void rx_quality(struct timeval *tv, struct ucred *cred,
- uint16_t index, struct packet_conn_data *conn,
- size_t erased, size_t late, uint8_t status)
+static void stream_quality(struct timeval *tv, uint16_t index,
+ const char *name, const char *dir,
+ struct packet_quality *q,
+ struct packet_loss *loss,
+ struct packet_jitter *jitter,
+ struct packet_latency *tx_l, size_t erased,
+ size_t late, uint8_t status)
{
- struct packet_loss *loss = &conn->rx_loss;
size_t burst = loss->burst;
/* A burst that this sample has just ended */
if (!burst && loss->erased != erased)
burst = loss->burst_last;
- print_rx_quality(conn, erased, late, status, burst);
- quality_summary(tv, cred, index, conn, burst);
+ print_quality(loss, jitter, erased, late, status, burst);
+ quality_summary(tv, index, name, dir, q, loss, jitter, tx_l, burst);
+}
+
+static void rx_quality(struct timeval *tv, uint16_t index,
+ struct packet_conn_data *conn, size_t erased,
+ size_t late, uint8_t status)
+{
+ char name[32];
+
+ snprintf(name, sizeof(name), "%s %u", conn_type_str(conn->type),
+ conn->handle);
+
+ stream_quality(tv, index, name, "RX", &conn->rx_quality,
+ &conn->rx_loss, &conn->rx_jitter, &conn->tx_l,
+ erased, late, status);
+}
+
+void packet_rtp_quality(struct timeval *tv, uint16_t index, uint16_t handle,
+ uint16_t cid, bool in, struct packet_rtp *rtp,
+ struct packet_quality *q, size_t erased,
+ size_t late)
+{
+ char name[32];
+
+ snprintf(name, sizeof(name), "A2DP %u CID 0x%4.4x", handle, cid);
+
+ stream_quality(tv, index, name, in ? "RX" : "TX", q, &rtp->loss,
+ &rtp->jitter, NULL, erased, late, 0);
}
static void sco_rx_quality(struct timeval *tv, struct ucred *cred,
@@ -15622,7 +15769,7 @@ static void sco_rx_quality(struct timeval *tv, struct ucred *cred,
conn->rx_jitter.interval);
}
- rx_quality(tv, cred, index, conn, erased, late, status);
+ rx_quality(tv, index, conn, erased, late, status);
}
void packet_hci_isodata(struct timeval *tv, struct ucred *cred, uint16_t index,
@@ -15735,7 +15882,7 @@ void packet_hci_isodata(struct timeval *tv, struct ucred *cred, uint16_t index,
label, handle_str, extra_str);
if (in && have_hdr && conn)
- rx_quality(tv, cred, index, conn, erased, late, sflags);
+ rx_quality(tv, index, conn, erased, late, sflags);
if (!in)
packet_enqueue_tx(tv, acl_handle(handle),
diff --git a/monitor/packet.h b/monitor/packet.h
index 77921af39263..3c57fa3f8e34 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -72,6 +72,19 @@ struct packet_jitter {
struct packet_latency delta; /* Arrival intervals */
};
+/* RTP media stream, the clock rate is derived from the arrival times */
+struct packet_rtp {
+ struct packet_loss loss;
+ struct packet_jitter jitter;
+ uint32_t rate; /* Clock rate in Hz, 0 until known */
+ size_t probe; /* Samples taken to derive the rate */
+ struct timeval first_tv;
+ uint32_t first_ts;
+ uint32_t prev_ts;
+ uint8_t pt; /* Payload type */
+ size_t gaps; /* Pauses, such as a suspended stream */
+};
+
/* Window of the periodic quality summary */
struct packet_quality {
struct timeval start;
@@ -145,6 +158,13 @@ void packet_jitter_add_ts(struct packet_jitter *jitter, struct timeval *tv,
uint32_t ts);
void packet_jitter_print(const struct packet_jitter *jitter,
const char *label);
+bool packet_rtp_add(struct packet_rtp *rtp, struct timeval *tv,
+ const void *data, uint16_t size);
+void packet_rtp_print(const struct packet_rtp *rtp, const char *label);
+void packet_rtp_quality(struct timeval *tv, uint16_t index, uint16_t handle,
+ uint16_t cid, bool in, struct packet_rtp *rtp,
+ struct packet_quality *q, size_t erased,
+ size_t late);
void packet_set_quality_period(unsigned int msec);
void packet_get_context(struct timeval *tv, size_t *num);
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* RE: [BlueZ,vRFC,1/7] monitor: Add audio quality statistics for ISO streams
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
` (5 preceding siblings ...)
2026-10-02 19:27 ` [PATCH BlueZ vRFC 7/7] monitor: Add audio quality statistics for A2DP streams Luiz Augusto von Dentz
@ 2026-10-02 22:07 ` bluez.test.bot
6 siblings, 0 replies; 8+ messages in thread
From: bluez.test.bot @ 2026-10-02 22:07 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 5963 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1178559/
---Test result---
Test Summary:
CheckPatch FAIL 4.07 seconds
GitLint FAIL 1.59 seconds
BuildEll PASS 19.30 seconds
BluezMake PASS 372.71 seconds
MakeCheck PASS 14.45 seconds
MakeDistcheck PASS 139.51 seconds
CheckValgrind PASS 228.69 seconds
CheckSmatch WARNING 288.24 seconds
bluezmakeextell PASS 92.37 seconds
TestFunctional PASS 1112.13 seconds
IncrementalBuild PASS 424.40 seconds
ScanBuild PASS 1110.12 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,vRFC,1/7] monitor: Add audio quality statistics for ISO streams
WARNING:COMMIT_LOG_LONG_LINE: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#105:
> LE-CIS: Handle 2304 SN... flags 0x02 dlen 4 sflags 2 #302 [hci0] 83.728300
/home/runner/work/bluez/bluez/src/patch/14864145.patch total: 0 errors, 1 warnings, 682 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14864145.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,vRFC,5/7] monitor: Add audio quality statistics for SCO data
WARNING:COMMIT_LOG_LONG_LINE: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#106:
> BR-ESCO: Handle 257 [90:09.. flags 0x01 dlen 120 #1455 [hci0] 19979.296105
/home/runner/work/bluez/bluez/src/patch/14864148.patch total: 0 errors, 1 warnings, 396 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14864148.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,vRFC,6/7] monitor: Add ISO link details to the analyze mode
WARNING:COMMIT_LOG_WRONG_LINK: 'Link:' should be followed by a public http(s) link
#107:
Link: ISO interval 30.00 msec NSE 12 BN 3 PTO 0 IRC 4 latency 28880 usec
/home/runner/work/bluez/bluez/src/patch/14864149.patch total: 0 errors, 1 warnings, 182 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14864149.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,vRFC,7/7] monitor: Add audio quality statistics for A2DP streams
WARNING:COMMIT_LOG_LONG_LINE: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#107:
< BR-ACL: Handle 256 [E8:EE:C.. flags 0x02 dlen 602 #15682 [hci0] 141.214796
/home/runner/work/bluez/bluez/src/patch/14864150.patch total: 0 errors, 1 warnings, 442 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14864150.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,vRFC,2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet
1: T1 Title exceeds max length (82>80): "[BlueZ,vRFC,2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet"
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
monitor/packet.c:2290:26: warning: Variable length array is used.monitor/packet.c: note: in included file:monitor/bt.h:3924:52: warning: array of flexible structuresmonitor/bt.h:3912:40: warning: array of flexible structuresmonitor/packet.c:2290:26: warning: Variable length array is used.monitor/packet.c: note: in included file:monitor/bt.h:3924:52: warning: array of flexible structuresmonitor/bt.h:3912:40: warning: array of flexible structuresmonitor/packet.c:2290:26: warning: Variable length array is used.monitor/packet.c: note: in included file:monitor/bt.h:3924:52: warning: array of flexible structuresmonitor/bt.h:3912:40: warning: array of flexible structuresmonitor/packet.c:2290:26: warning: Variable length array is used.monitor/packet.c: note: in included file:monitor/bt.h:3924:52: warning: array of flexible structuresmonitor/bt.h:3912:40: warning: array of flexible structures
https://github.com/bluez/bluez/pull/2623
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 8+ messages in thread