Files
DS5Dongle-OLED-Edition-stea…/BLUETOOTH_AUDIO_NOTES.md
MarcelineVPQandClaude Opus 4.7 20b41d80a1 feat: DualSense BT microphone + USB 3.0 connection watchdog
BT microphone over Bluetooth: the DS5 mic now works over the dongle's BT
pairing — decoded from the controller's Opus stream to the USB capture
endpoint. Hinges on pkt[4] bit 0 (mic-enable) in the outbound 0x36 audio
report; credit to awalol (upstream) for identifying it. Mic-tagged 0x31
frames ((data[2]>>1)&1) are ALWAYS diverted out of the input path (decoded
when on, dropped when off) so Opus payload can never corrupt sticks/buttons.
Always-on via a sticky-latch keep-alive that only runs post-enumeration
(tud_mounted) so it never floods the fresh-pair handshake (which otherwise
delayed controller detection past the watchdog and tore the link down).
Toggle: bt_mic_enable config field (default on) — OLED Settings + web config.
README gains a "DualSense Microphone over Bluetooth" section;
BLUETOOTH_AUDIO_NOTES.md rewritten from "dead end" to the working mechanism.

USB 3.0 connection watchdog: auto-recovers a stalled connection (re-inquiry)
instead of hanging on the amber lightbar, for USB 3.0 ~2.4 GHz RF interference
that desensitizes the CYW43 BT radio. Re-enabled the ACL-fail / auth-fail /
create-connection-reject recovery paths. README "USB 3.0 ports & Bluetooth
interference" section with mitigations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 01:30:37 -06:00

4.8 KiB
Raw Permalink Blame History

Bluetooth microphone — SOLVED

TL;DR: The DualSense's built-in microphone works over this dongle's Bluetooth pairing as of v0.6.8. It is decoded and presented to the host as the standard DualSense USB capture device. Earlier versions of this document concluded the opposite — that it was a Sony-side limitation, probably encrypted, a dead end. That conclusion was wrong. The whole thing hinged on a single enable bit. Full credit to awalol (upstream mic branch) for identifying it.

How it works

  1. Enable bit (dongle → DS5). In the outbound 0x36 BT audio report, the audio-control sub-report's first flag byte (pkt[4]) has bit 0 = mic-enable. Setting it (0b111111100b11111111; bits 17 were already the speaker/haptic enables) tells the DS5 to start streaming its mic. See src/audio.cpp.
  2. Mic frames (DS5 → dongle). Once enabled, the DS5 tags certain 0x31 BT input reports as mic frames by setting bit 1 of byte 2 ((data[2] >> 1) & 1); the payload is a 71-byte Opus packet at offset +4. src/main.cpp:on_bt_data() routes those to mic_add_queue().
  3. Decode + present. src/audio.cpp Opus-decodes each frame to mono 48 kHz (480-sample / 10 ms frames), duplicates mono → stereo, and tud_audio_writes it to the UAC1 capture endpoint. The host sees a normal DualSense mic.
  4. Sticky. Once the DS5 starts streaming it keeps going for the rest of the session even after the enable bit / audio output stops (verified: mic stays live with no further 0x36 frames).
  5. Always-on. Because the enable normally only rides the audio-gated 0x36 frames, the dongle sends a control-only 0x36 keep-alive (enable bit + SetStateData + silent haptic, no speaker payload) at ~4 Hz only until mic frames start arriving, then stops (sticky takes over). So the mic works with no game audio playing, at minimal extra BT traffic. See mic_enable_keepalive() in src/audio.cpp.
  6. Toggle. Gated by Config_body.bt_mic_enable (default on) — OLED Settings → BT Mic and the web config tool. Off = no enable sent, no keep-alive, and inbound mic frames are not routed (so it's off host-side even if a previously-enabled DS5 is still streaming). Off by toggle saves DS5 battery (always-on keeps its audio subsystem awake).

Why the original conclusion was wrong (the lesson)

The earlier investigation ported awalol's receive side (the (data[2]>>1)&1 trigger + Opus decode) and then watched for that bit — but never sent the enable bit, because this fork's audio.cpp had diverged (the pre-3a31bd7 SetStateData revert) and sat at pkt[4] = 0b11111110. With nothing telling the DS5 to start, it never streamed, so bit 1 of byte 2 never set — which got misread as "the trigger never fires → the channel must be encrypted / it's a dead end."

It was never encrypted. It was one un-set bit on the transmit side. Lesson: when porting a two-sided protocol, confirm both halves (enable and receive) before concluding the device "can't" do something.

What we use (host-side)

  • scripts/mic_diag.shstatus / capture [secs] / watch / bt-trace. capture arecords the DualSense mic card and reports peak/RMS/non-zero, the fastest way to confirm real audio.
  • The DualSense capture card's Headset capture control defaults low — raise it (amixer -c <card> sset 'Headset' 90%) or captures look silent.
  • OLED Diagnostics Mic in: (~100/s when streaming) + Mic dec= (480 = good Opus decode) are the on-device confirmation.
  • Vendor HID feature reports 0xFD / 0xFE and the BT counters remain useful general audio-debug infra.

Open follow-ups

  • No documented "stop" command. Disabling mid-session relies on gating the receive side; the DS5 keeps streaming until reconnect. If a real stop/disable bit is found, wire it into the toggle to stop the DS5-side battery drain immediately.
  • Mono only. Decoded mono is duplicated to the stereo endpoint; the DS5 mic is mono so this is fine, but the descriptor could be made truly mono (as awalol's branch does) to halve endpoint bandwidth.
  • Name the pkt[4] bits precisely. daidr/dualsense-tester's outputStruct.ts documents the standard output report flags but not the 0x36 BT-audio sub-report; the bit meanings beyond bit 0 are inferred from the working speaker/haptic path.

References

  • Upstream awalol/DS5Dongle branch mic (commits 9c197fc, 3829163) — the source of the enable bit (pkt[4] bit 0) and the receive-side decode. awalol confirmed bit 0 is the key.
  • Linux kernel drivers/hid/hid-playstation.c (~line 1509, "Bluetooth audio is currently not supported") — still true for the kernel driver; not for this dongle.
  • daidr/dualsense-testersrc/router/DualSense/views/_OutputPanel/outputStruct.ts for the standard output-report flag layout.