6f7f7ddd6c
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
115 lines
4.6 KiB
Python
115 lines
4.6 KiB
Python
#!/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)
|