feat(audio): mic packet-loss concealment (jitter buffer + Opus PLC)

The BT mic decode path gained a decoded-frame jitter buffer (8 frames) drained
at a steady 10 ms playout cadence. Bursty BT delivery is smoothed; a dropped
mic frame during an active session is concealed with an Opus PLC frame
(opus_decode(decoder, NULL, 0, ...)) instead of leaving a hole the host hears
as a click/dropout. Playout pre-buffers 3 frames and stops after 300 ms of no
real frames so it never emits comfort noise when the mic is idle. New "Mic PLC:"
Diagnostics counter climbs only when concealment fires (a live link-quality
gauge). Verified on hardware: forced BT loss kept captured audio gap-free
(longest zero-run ~0 ms) while the counter climbed; clean link leaves it idle.
Adds ~30 ms mic latency (the pre-buffer). Design ported from
SundayMoments/DS5_Bridge (credit). Unreleased — batching with the next feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MarcelineVPQ
2026-05-24 09:27:11 -06:00
co-authored by Claude Opus 4.7
parent 9920516f52
commit da63e2bb72
5 changed files with 83 additions and 17 deletions
+5 -2
View File
@@ -827,7 +827,7 @@ void sample_diag_rates() {
// Row list ordered by relevance: always-useful at top, parked-mic-investigation
// data at bottom. To add a row, bump kNumDiagRows and add a case.
constexpr int kNumDiagRows = 11;
constexpr int kNumDiagRows = 12;
__attribute__((noinline))
void format_diag_row(int idx, char* line, size_t n) {
switch (idx) {
@@ -872,7 +872,10 @@ void format_diag_row(int idx, char* line, size_t n) {
(long)audio_mic_last_decoded(),
(unsigned)audio_mic_last_wrote());
break;
case 10: {
case 10:
snprintf(line, n, "Mic PLC: %lu", (unsigned long)audio_mic_plc_frames());
break;
case 11: {
uint8_t pfx[6]; bt_31_mic_prefix(pfx);
snprintf(line, n, "%02X %02X %02X %02X %02X %02X",
pfx[0], pfx[1], pfx[2], pfx[3], pfx[4], pfx[5]);