audio: widen forward decode cushion 30/15->60/30s + add [BP-DIAG] back-pressure instrumentation

Byte cap (96MB) unchanged as the hard OOM bound; the wider time window only lets sparse Opus use existing memory headroom to ride out decode jitter. Diag logs pin whether the block is back-pressure or decode throughput.
This commit is contained in:
daniel-c-harvey
2026-06-25 21:52:20 -04:00
parent f0d1463619
commit 61e185a2f7
4 changed files with 126 additions and 12 deletions
@@ -163,6 +163,11 @@ export class OpusStreamDecoder implements IStreamingDecoder {
// order-sensitive). configure() is deferred too — no need to spin up the decoder while
// throttled. The C# loop also stops reading above high-water, so the stash stays small.
if (this.isSchedulerFull?.()) {
// [BP-DIAG] First stash since last drain — production is now throttled and decode is parked.
// Trivially removable.
if (this.pendingBytes.length === 0) {
console.log('[BP-DIAG] Opus stash START (scheduler full, decode parked)');
}
this.pendingBytes.push(chunk);
return [];
}
@@ -173,6 +178,8 @@ export class OpusStreamDecoder implements IStreamingDecoder {
// the new chunk, through the demuxer as one contiguous feed.
const out: AudioBuffer[] = [];
if (this.pendingBytes.length > 0) {
// [BP-DIAG] Scheduler drained below low-water — replaying the stash. Trivially removable.
console.log(`[BP-DIAG] Opus stash DRAIN ${this.pendingBytes.length} chunks`);
const stashed = this.pendingBytes;
this.pendingBytes = [];
for (const bytes of stashed) {
@@ -375,6 +382,13 @@ export class OpusStreamDecoder implements IStreamingDecoder {
let iters = 0;
const poll = () => {
if (!this.decoder || this.decoder.decodeQueueSize === 0 || iters >= MAX_YIELD_ITERS) {
// [BP-DIAG] If we hit the 200 ms ceiling with the decode queue still non-empty, the
// WebCodecs decoder is falling behind realtime (the throughput suspect for sustained
// underrun — worse with HW accel off). Frequent CAP lines pin decode, not back-pressure,
// as the block. Trivially removable.
if (this.decoder && iters >= MAX_YIELD_ITERS && this.decoder.decodeQueueSize > 0) {
console.log(`[BP-DIAG] Opus yield CAP hit, decodeQueueSize=${this.decoder.decodeQueueSize} (decoder behind realtime)`);
}
resolve();
return;
}