Bluetooth mic startup: honest Listening indicator, startup-time investigation
The bar turned green after 2 s on a timer even when the microphone delivered nothing. It now turns green only on real audio: RMS above 0.0005, or three consecutive chunks that are not digital silence (the virtual mic emits one stray nonzero chunk right after start, and a Bluetooth headset's floor ramps in from a few LSB). Measured 1.2-1.4 s after the key, within 0.14 s of the first real samples. diagnostics/bluetooth/ records how startup went from ~1.6 s (or never) to ~1.1-1.4 s: a btusb driver bug, two PipeWire bluez5 bugs, a 500 ms WirePlumber switch timeout and an over-broad auto-connect rule. Those fixes are machine level and live outside this repo; the patches, tools and measurements are here. STARTUP-TIME.md is the summary and explains the ~0.9 s hardware floor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xoc9DJCViR7dg9eKzzAcfQ
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the actual acquire functions with mocked I/O; no Bluetooth access.
|
||||
|
||||
This is a focused reproduction harness, not PipeWire's full integration suite.
|
||||
Usage: test-sco-acquire.py <backend-native.c> <bluez5-dbus.c>
|
||||
"""
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def function(path, signature):
|
||||
source = pathlib.Path(path).read_text()
|
||||
start = source.index(signature + "\n{")
|
||||
end = source.index("\n}", start) + 2
|
||||
return source[start:end]
|
||||
|
||||
|
||||
stub = r'''
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
enum { SPA_BT_TRANSPORT_STATE_ERROR = -1, SPA_BT_TRANSPORT_STATE_IDLE,
|
||||
SPA_BT_TRANSPORT_STATE_PENDING, SPA_BT_TRANSPORT_STATE_ACTIVE };
|
||||
struct impl { void *log; } backend;
|
||||
struct spa_bt_monitor { void *log; } monitor;
|
||||
struct transport_data { int err; bool requesting; } td;
|
||||
struct spa_bt_transport {
|
||||
void *backend, *user_data;
|
||||
struct spa_bt_monitor *monitor;
|
||||
int fd, state, acquire_refcount, error_count;
|
||||
bool acquired;
|
||||
uint64_t last_error_time;
|
||||
};
|
||||
#define SPA_CONTAINER_OF(p, type, member) ((type *)(p))
|
||||
#define spa_log_debug(...) ((void)0)
|
||||
#define spa_assert assert
|
||||
#define TRANSPORT_ERROR_TIMEOUT 6000000000ULL
|
||||
#define TRANSPORT_ERROR_MAX_RETRY 3
|
||||
#define spa_bt_transport_impl(t, op, version, optional) sco_acquire_cb(t, optional)
|
||||
static int error_events, connect_result, connect_error;
|
||||
static uint64_t get_time_now(struct spa_bt_monitor *m) { return 10000000000ULL; }
|
||||
static void spa_bt_transport_emit_state_changed(struct spa_bt_transport *t, int old, int state) {
|
||||
if (state == SPA_BT_TRANSPORT_STATE_ERROR) ++error_events;
|
||||
}
|
||||
static void spa_bt_transport_set_state(struct spa_bt_transport *t, int state) {
|
||||
int old = t->state;
|
||||
if (old != state) {
|
||||
t->state = state;
|
||||
spa_bt_transport_emit_state_changed(t, old, state);
|
||||
}
|
||||
}
|
||||
static int sco_do_connect(struct spa_bt_transport *t) {
|
||||
td.err = connect_error;
|
||||
return connect_result;
|
||||
}
|
||||
static void sco_start_source(struct spa_bt_transport *t) {}
|
||||
static void sco_ready(struct spa_bt_transport *t) {
|
||||
td.requesting = false;
|
||||
spa_bt_transport_set_state(t, td.err ? SPA_BT_TRANSPORT_STATE_ERROR : SPA_BT_TRANSPORT_STATE_ACTIVE);
|
||||
}
|
||||
'''
|
||||
tests = r'''
|
||||
static struct spa_bt_transport fresh(int state) {
|
||||
td = (struct transport_data){0};
|
||||
error_events = 0;
|
||||
connect_result = 42;
|
||||
connect_error = -EINPROGRESS;
|
||||
return (struct spa_bt_transport){ .backend=&backend, .user_data=&td,
|
||||
.monitor=&monitor, .fd=-1, .state=state };
|
||||
}
|
||||
int main(void) {
|
||||
for (int initial = SPA_BT_TRANSPORT_STATE_ERROR; initial <= SPA_BT_TRANSPORT_STATE_IDLE; ++initial) {
|
||||
struct spa_bt_transport t = fresh(initial);
|
||||
assert(spa_bt_transport_acquire(&t, false) == 0);
|
||||
if (t.state != SPA_BT_TRANSPORT_STATE_PENDING) {
|
||||
fprintf(stderr, "FAIL: asynchronous acquire retained state %d (initial %d)\n", t.state, initial);
|
||||
return 1;
|
||||
}
|
||||
assert(td.requesting && t.acquire_refcount == 1 && t.acquired);
|
||||
assert(spa_bt_transport_acquire(&t, false) == 0);
|
||||
assert(t.acquire_refcount == 2 && !error_events);
|
||||
assert(t.state != SPA_BT_TRANSPORT_STATE_ACTIVE);
|
||||
td.err = 0;
|
||||
sco_ready(&t);
|
||||
assert(t.state == SPA_BT_TRANSPORT_STATE_ACTIVE);
|
||||
}
|
||||
struct spa_bt_transport t = fresh(SPA_BT_TRANSPORT_STATE_IDLE);
|
||||
connect_result = -1;
|
||||
assert(spa_bt_transport_acquire(&t, false) < 0);
|
||||
assert(t.state == SPA_BT_TRANSPORT_STATE_ERROR && !t.acquired && error_events == 1);
|
||||
t = fresh(SPA_BT_TRANSPORT_STATE_IDLE);
|
||||
t.fd = 42;
|
||||
assert(spa_bt_transport_acquire(&t, true) == 0);
|
||||
assert(t.state == SPA_BT_TRANSPORT_STATE_ACTIVE && !error_events);
|
||||
t = fresh(SPA_BT_TRANSPORT_STATE_ERROR);
|
||||
assert(spa_bt_transport_acquire(&t, false) == 0);
|
||||
td.err = -ECONNREFUSED;
|
||||
sco_ready(&t);
|
||||
assert(t.state == SPA_BT_TRANSPORT_STATE_ERROR && error_events == 1);
|
||||
puts("PASS: cold/retry concurrent acquire, delayed readiness, synchronous success, real failures");
|
||||
}
|
||||
'''
|
||||
source = stub + function(sys.argv[1], 'static int sco_acquire_cb(void *data, bool optional)')
|
||||
source += function(sys.argv[2], 'int spa_bt_transport_acquire(struct spa_bt_transport *transport, bool optional)')
|
||||
source += tests
|
||||
with tempfile.TemporaryDirectory(prefix='stt-sco-unit-') as tmp:
|
||||
binary = str(pathlib.Path(tmp) / 'test')
|
||||
subprocess.run(['cc', '-std=c11', '-Wall', '-Wno-unused-variable', '-Wno-unused-parameter',
|
||||
'-x', 'c', '-', '-o', binary], input=source, text=True, check=True)
|
||||
sys.exit(subprocess.run([binary]).returncode)
|
||||
Reference in New Issue
Block a user