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
+39
-9
@@ -17,6 +17,14 @@
|
||||
|
||||
extern uint8_t interrupt_in_data[63]; // defined in main.cpp
|
||||
|
||||
// Mic diagnostic counters (defined in main.cpp).
|
||||
extern uint32_t bt_31_packet_count();
|
||||
extern uint8_t bt_31_last_byte2();
|
||||
extern uint8_t bt_31_b2_or_mask();
|
||||
extern uint16_t bt_31_len_min();
|
||||
extern uint16_t bt_31_len_max();
|
||||
extern void bt_31_mic_prefix(uint8_t out[6]);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint kPinDC = 8;
|
||||
@@ -618,28 +626,38 @@ __attribute__((noinline)) void render_screen_diag() {
|
||||
draw_text(kContentX, 9, buf);
|
||||
|
||||
// Per-second rates for the audio path counters — recompute every render.
|
||||
static uint32_t prev_us_frames = 0, prev_bt_packets = 0;
|
||||
static uint32_t prev_us_frames = 0, prev_bt_packets = 0, prev_mic_frames = 0, prev_bt31 = 0;
|
||||
static uint32_t prev_sample_us = 0;
|
||||
const uint32_t now_us = time_us_32();
|
||||
const uint32_t cur_us_frames = audio_usb_frames();
|
||||
const uint32_t cur_bt_packets = audio_bt_packets();
|
||||
uint32_t usb_rate = 0, bt_rate = 0;
|
||||
const uint32_t cur_mic_frames = audio_mic_frames();
|
||||
const uint32_t cur_bt31 = bt_31_packet_count();
|
||||
uint32_t usb_rate = 0, bt_rate = 0, mic_rate = 0, bt31_rate = 0;
|
||||
if (prev_sample_us != 0 && now_us > prev_sample_us) {
|
||||
const uint32_t dt_us = now_us - prev_sample_us;
|
||||
if (dt_us > 0) {
|
||||
usb_rate = (uint32_t)(((uint64_t)(cur_us_frames - prev_us_frames) * 1000000u) / dt_us);
|
||||
bt_rate = (uint32_t)(((uint64_t)(cur_bt_packets - prev_bt_packets) * 1000000u) / dt_us);
|
||||
usb_rate = (uint32_t)(((uint64_t)(cur_us_frames - prev_us_frames) * 1000000u) / dt_us);
|
||||
bt_rate = (uint32_t)(((uint64_t)(cur_bt_packets - prev_bt_packets) * 1000000u) / dt_us);
|
||||
mic_rate = (uint32_t)(((uint64_t)(cur_mic_frames - prev_mic_frames) * 1000000u) / dt_us);
|
||||
bt31_rate = (uint32_t)(((uint64_t)(cur_bt31 - prev_bt31) * 1000000u) / dt_us);
|
||||
}
|
||||
}
|
||||
prev_us_frames = cur_us_frames;
|
||||
prev_us_frames = cur_us_frames;
|
||||
prev_bt_packets = cur_bt_packets;
|
||||
prev_sample_us = now_us;
|
||||
prev_mic_frames = cur_mic_frames;
|
||||
prev_bt31 = cur_bt31;
|
||||
prev_sample_us = now_us;
|
||||
|
||||
snprintf(buf, sizeof(buf), "USB aud %lu/s", (unsigned long)usb_rate);
|
||||
snprintf(buf, sizeof(buf), "BT31 %lu Mic %lu/s", (unsigned long)bt31_rate, (unsigned long)mic_rate);
|
||||
draw_text(kContentX, 18, buf);
|
||||
snprintf(buf, sizeof(buf), "BT 0x32 %lu/s", (unsigned long)bt_rate);
|
||||
uint8_t pfx[6]; bt_31_mic_prefix(pfx);
|
||||
snprintf(buf, sizeof(buf), "%02X %02X %02X %02X %02X %02X",
|
||||
pfx[0], pfx[1], pfx[2], pfx[3], pfx[4], pfx[5]);
|
||||
draw_text(kContentX, 27, buf);
|
||||
snprintf(buf, sizeof(buf), "HCI errs: %lu", (unsigned long)bt_hci_err_count());
|
||||
snprintf(buf, sizeof(buf), "dec=%ld w=%u",
|
||||
(long)audio_mic_last_decoded(),
|
||||
(unsigned)audio_mic_last_wrote());
|
||||
draw_text(kContentX, 36, buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "BT: %s", bt_is_connected() ? "connected" : "waiting");
|
||||
@@ -1339,6 +1357,18 @@ void oled_loop() {
|
||||
// caches its frequency-counter measurement here).
|
||||
static int last_rendered_screen = -1;
|
||||
const bool screen_entered = (current_screen != last_rendered_screen);
|
||||
|
||||
// Leaving Trigger Test in either direction → reset the adaptive
|
||||
// trigger preset to OFF and push it to the controller. Otherwise
|
||||
// the last-cycled effect (Weapon snap, Galloping pulse, etc.)
|
||||
// stays active on the DS5 indefinitely, which surprised users
|
||||
// who'd just navigated away expecting a clean slate.
|
||||
if (last_rendered_screen == kScreenTriggers
|
||||
&& current_screen != kScreenTriggers) {
|
||||
trigger_preset = 0;
|
||||
send_trigger_effect(0);
|
||||
}
|
||||
|
||||
last_rendered_screen = current_screen;
|
||||
|
||||
switch (current_screen) {
|
||||
|
||||
Reference in New Issue
Block a user