Files
alan b93327dc26 Ask for the headset profile at capture start; relaunch the daemon on update
The daemon now requests the Bluetooth headset profile itself (pactl
set-card-profile) the moment a take starts, instead of waiting for
WirePlumber to notice the stream and run its switch timer. The microphone
link comes up 0.15-0.18 s after the key instead of 0.21-0.27 s; WirePlumber
still restores A2DP when the stream goes away. First real audio is now at
0.82-0.86 s, which is the headset's own floor.

When the plugin is updated while the daemon runs, the daemon sees its source
change, exits with code 4 once idle, and the shell service relaunches it
without counting that as a crash.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xoc9DJCViR7dg9eKzzAcfQ
2026-09-09 12:17:06 +01:00

55 lines
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# How we cut Bluetooth microphone startup time
Measured on a Sony WH-1000XM5 over an Intel AX210, PipeWire 1.6.8, WirePlumber
0.5.17, kernel 7.2.3. Times are after the dictation key press.
| | before | after |
|---|---|---|
| mic link (eSCO) up | 0.71.0 s, sometimes never | 0.150.18 s |
| first real audio at the daemon | 1.51.7 s, or never | 0.820.86 s |
| "Listening" in the bar | 2.1 s on a timer, even with no audio | 0.860.94 s, only on real audio |
"After" is with the daemon recording straight from `bluez_input.<addr>`; via
`camera-effects-mic` add ~0.25 s (its 250 ms poll and processing).
## What changed
| Change | Where it lives | Gain |
|---|---|---|
| Daemon indicator: green on real audio only (RMS > 0.0005 or three consecutive non-silent chunks), no timer | `daemon/sttd.py` | the bar cannot claim to listen to a dead mic |
| Daemon asks for the headset profile itself at capture start (`pactl set-card-profile`), before WirePlumber notices the stream | `daemon/sttd.py` | link up ~0.1 s sooner (0.210.27 s → 0.150.18 s); WirePlumber still restores A2DP |
| Record from `bluez_input.<addr>` instead of `camera-effects-mic` | `~/.config/speech-to-text/config.json` | ~0.25 s sooner, no noise suppression on dictation |
| WirePlumber profile-switch timeout 500 ms → 50 ms | `~/.local/share/wireplumber/scripts/device/autoswitch-bluetooth-profile.lua` | link up ~0.45 s sooner |
| A2DP auto-connect rule narrowed to the XM5 | `~/.config/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf` | no 5 s pages of absent devices at login |
| btusb driver patch `0002` | `/usr/lib/modules/<kernel>/updates/btusb.ko` | mic link no longer comes up silent when a page overlaps it |
| PipeWire bluez5 patches `0001` + `0003` | `~/.local/lib/spa-0.2/bluez5/` via `SPA_PLUGIN_DIR` drop-ins | no stale-error dead takes; back-to-back takes work |
Only the first two rows are part of this repo. The rest are machine-level and are
described, with patches and measurements, in [HANDOFF.md](HANDOFF.md).
## Why ~0.85 s is the floor
```
key press
0.00 s ─┬─ daemon asks for the headset profile, starts pw-record
0.07 s ─┼─ eSCO connect() reaches the kernel
│ headset leaves sniff mode, radio sets up the link
0.16 s ─┼─ eSCO link up
│ headset sends encoded digital silence while its own
│ mic path starts and mSBC syncs: ~0.6 s
│ (measured at the decoder; Linux cannot shorten it)
0.80 s ─┼─ first non-zero samples leave the headset
│ decode + 50 ms chunking
0.85 s ─┴─ first real audio reaches the daemon
```
Nothing measurable is left on the Linux side.
The headset's silence after link-up and the eSCO setup are hardware. Words
spoken in the first ~0.9 s are lost unless the microphone is already in
hands-free mode, which drops playback to mono, so it is off by default
(`warmMic`).