wip(mic): BT-side mic capture infrastructure + host-side diag
In-progress work on DualSense microphone capture over BT. Mic-add tap itself is disabled (was decoding standard input bytes as Opus and producing INT16_MIN garbage on the USB IN endpoint) but everything around it is wired and ready to re-enable once we identify the actual mic transport. Firmware: - src/audio.cpp: Opus decoder on core0, mic_fifo queue, audio_loop mic-in path with decode + mono->stereo + tud_audio_write. Decoder init in audio_init() (creates 48kHz mono OpusDecoder). - src/audio.h: exports mic_add_queue() + per-frame diagnostic accessors (audio_mic_frames, last_decoded, last_want, last_wrote, last_toc). - src/main.cpp on_bt_data(): BT-side instrumentation — counts every INTERRUPT input report, tracks min/max length, OR mask of byte[2], most recent non-0x31 report ID, hex prefix of last 0x31/other/any frame, full content of the longest 0x31 frame seen. Mic-tap call itself stubbed behind `if (false)` pending the real detector. - src/state_mgr.cpp: state_init_data byte 6 (VolumeMic) 0xFF→0x40 (was out of range), byte 9 (MuteControl) 0x0F→0x00 (clear all PowerSave bits — AudioPowerSave was muting DSP). - src/cmd.cpp: two new vendor feature reports — 0xFD returns 32-byte diagnostic state (counters + prefixes), 0xFE returns the longest 0x31 frame in full (up to 80 bytes). Both queryable via /dev/hidraw on Linux from the host script. - src/oled.cpp: Diagnostics screen shows TOC + decode result + USB wrote/want bytes for live BT-side visibility. Host-side: - scripts/mic_diag.sh: subcommands `status`, `capture [secs]`, `watch`, `bt-trace`. The bt-trace subcommand reads the 0xFD feature report via hidraw ioctl, decodes counters + recent prefixes, computes per-second rates. Drastically cuts iteration time — no OLED relay or per-test flash cycle needed. Findings to date: - Upstream/mic's mic-flag bit ((data[2] >> 1) & 1) does NOT match this DS5 firmware; bit 1 of byte[2] is NEVER set. Bit 0 is the standard input report type indicator, not a mic tag — confirmed by stick-bytes appearing as our supposed "Opus prefix". - DS5 sends both report ID 0x01 and 0x31 over BT; the longest frame is a standard 79-byte 0x31 input report with sticks/IMU/touchpad but no audio bytes appended. - Conclusion in progress: the DS5 firmware on this controller is not currently streaming mic over BT at all, even with AllowAudioControl=1, VolumeMic=0x40, AudioPowerSave=0, MicMute=0. Next investigation step: compare against a USB-mode DS5 to see what a real mic stream looks like at the UAC1 layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2209f9b8c7
commit
72f163ca50
+68
-6
@@ -30,6 +30,32 @@ int reportSeqCounter = 0;
|
||||
uint8_t packetCounter = 0;
|
||||
bool spk_active = false;
|
||||
|
||||
// Mic-debug instrumentation: count every 0x31 BT input report regardless
|
||||
// of mic-tag bit, accumulate OR-mask of every byte-2 value seen (tells us
|
||||
// which bits ever fire) and remember the last byte-2 value. Also track
|
||||
// observed frame-length range. Surfaced on the OLED Diagnostics screen.
|
||||
volatile uint32_t g_bt_31_packets = 0;
|
||||
volatile uint32_t g_bt_other_packets = 0;
|
||||
volatile uint8_t g_last_other_id = 0;
|
||||
volatile uint8_t g_other_id_or = 0;
|
||||
volatile uint8_t g_last_31_b2 = 0;
|
||||
volatile uint8_t g_31_b2_or = 0;
|
||||
volatile uint16_t g_31_len_min = 0xFFFF;
|
||||
volatile uint16_t g_31_len_max = 0;
|
||||
volatile uint8_t g_mic_prefix[6] = {0};
|
||||
volatile uint8_t g_last_other_prefix[8] = {0};
|
||||
volatile uint8_t g_last_any_prefix[16] = {0};
|
||||
volatile uint16_t g_longest_len = 0;
|
||||
volatile uint8_t g_longest_frame[80] = {0};
|
||||
uint32_t bt_31_packet_count() { return g_bt_31_packets; }
|
||||
uint8_t bt_31_last_byte2() { return g_last_31_b2; }
|
||||
uint8_t bt_31_b2_or_mask() { return g_31_b2_or; }
|
||||
uint16_t bt_31_len_min() { return g_31_len_min == 0xFFFF ? 0 : g_31_len_min; }
|
||||
uint16_t bt_31_len_max() { return g_31_len_max; }
|
||||
void bt_31_mic_prefix(uint8_t out[6]) {
|
||||
for (int i = 0; i < 6; i++) out[i] = g_mic_prefix[i];
|
||||
}
|
||||
|
||||
uint8_t interrupt_in_data[63] = {
|
||||
0x7f, 0x7d, 0x7f, 0x7e, 0x00, 0x00, 0xa7,
|
||||
0x08, 0x00, 0x00, 0x00, 0x52, 0x43, 0x30, 0x41,
|
||||
@@ -101,6 +127,48 @@ void interrupt_loop() {
|
||||
|
||||
void on_bt_data(CHANNEL_TYPE channel, uint8_t *data, uint16_t len) {
|
||||
// printf("[Main] BT data callback: channel=%u len=%u\n", channel, len);
|
||||
// Track ALL INTERRUPT input reports, not just 0x31. The mic stream
|
||||
// may live on a different report ID — confirmed 2026-05-19 that data[2]
|
||||
// bit 0 (and bit 1) is NOT a mic flag, just the report-type indicator;
|
||||
// every "mic-tagged" frame turned out to be standard input.
|
||||
if (channel == INTERRUPT && len > 1) {
|
||||
if (data[1] == 0x31) g_bt_31_packets++;
|
||||
else {
|
||||
g_bt_other_packets++;
|
||||
g_last_other_id = data[1];
|
||||
g_other_id_or = (uint8_t)(g_other_id_or | data[1]);
|
||||
for (uint16_t i = 0; i < 8 && i < len; i++) {
|
||||
g_last_other_prefix[i] = data[i];
|
||||
}
|
||||
}
|
||||
if (len > 2) {
|
||||
g_last_31_b2 = data[2];
|
||||
g_31_b2_or = (uint8_t)(g_31_b2_or | data[2]);
|
||||
}
|
||||
if (len < g_31_len_min) g_31_len_min = len;
|
||||
if (len > g_31_len_max) g_31_len_max = len;
|
||||
for (uint16_t i = 0; i < 16 && i < len; i++) {
|
||||
g_last_any_prefix[i] = data[i];
|
||||
}
|
||||
|
||||
// Capture the entire content of the longest 0x31 frame we've
|
||||
// seen. Long frames almost certainly carry the mic audio appended
|
||||
// after the standard 63-byte input report — this lets us look
|
||||
// at the trailing bytes directly via 0xFD diagnostic.
|
||||
if (data[1] == 0x31 && len > g_longest_len) {
|
||||
g_longest_len = len;
|
||||
for (uint16_t i = 0; i < 80 && i < len; i++) {
|
||||
g_longest_frame[i] = data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mic-add tap DISABLED — was decoding standard input (button/stick
|
||||
// bytes) as Opus and producing INT16_MIN garbage on the USB IN
|
||||
// endpoint. Re-enable once we identify the actual mic transport.
|
||||
// (Standard input handling below resumes — Status screen + HID
|
||||
// reports to host need this.)
|
||||
|
||||
if (channel == INTERRUPT && data[1] == 0x31) {
|
||||
if ((data[56] & 1) != (interrupt_in_data[53] & 1)) {
|
||||
set_headset(data[56] & 1);
|
||||
@@ -114,12 +182,6 @@ void on_bt_data(CHANNEL_TYPE channel, uint8_t *data, uint16_t len) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We add the critical section here to avoid any race conditions when writing to the interrupt_in_data buffer,
|
||||
// which is shared between the main loop and this callback.
|
||||
// The critical section ensures that only one thread can access the buffer at a time,
|
||||
// preventing data corruption and ensuring thread safety.
|
||||
// We also set the report_dirty flag to true to indicate that new data is available
|
||||
// and needs to be sent in the next interrupt report.
|
||||
critical_section_enter_blocking(&report_cs);
|
||||
memcpy(interrupt_in_data, data + 3, 63);
|
||||
report_dirty = true;
|
||||
|
||||
Reference in New Issue
Block a user