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:
@@ -441,6 +441,56 @@ test('clear and clearForSeek release the back-pressure latch (C2 latency parity)
|
||||
assertEqual(s.evaluateProductionPause(), false, 'clearForSeek resets the latch');
|
||||
});
|
||||
|
||||
// Production defaults (no setForwardWindow): the widened 60s/30s cushion. The byte cap is the
|
||||
// UNCHANGED hard OOM bound; these defaults only govern the time window. buf(1) carries no byte
|
||||
// fields, so getDecodedByteEstimate is NaN and the byte guard never fires — the time window alone
|
||||
// governs, which is exactly what we want to pin here.
|
||||
test('default forward window throttles at 60s and resumes at 30s (no setForwardWindow)', () => {
|
||||
const cm = new FakeContextManager();
|
||||
const s = makeScheduler(cm);
|
||||
// Deliberately no setForwardWindow() — exercise the PRODUCTION defaults (high 60s / low 30s).
|
||||
for (let i = 0; i < 70; i++) s.addBuffer(buf(1)); // 70s decoded, track [0,70)
|
||||
cm.now = 0;
|
||||
s.playFromPosition(0);
|
||||
advanceCursorToEnd(s, cm);
|
||||
|
||||
cm.now = 0; // forward lookahead = 70s ≥ 60s high-water
|
||||
assertEqual(s.evaluateProductionPause(), true, 'pauses at the 60s default high-water');
|
||||
cm.now = 35; // lookahead 35s: inside the 30..60 band → stays paused (hysteresis)
|
||||
assertEqual(s.evaluateProductionPause(), true, 'holds through the widened band');
|
||||
cm.now = 45; // lookahead 25s ≤ 30s low-water → resume
|
||||
assertEqual(s.evaluateProductionPause(), false, 'resumes at the 30s default low-water');
|
||||
});
|
||||
|
||||
// Lookahead correctness in the underrun state + the prime block hypothesis directly refuted: when the
|
||||
// playhead has drained the queue mid-stream, forward lookahead must read ~0 (not a stale-high value)
|
||||
// so production is NOT throttled while decoded audio is genuinely low.
|
||||
test('forward lookahead is exact during an underrun park and never trips a false pause', () => {
|
||||
const cm = new FakeContextManager();
|
||||
const s = makeScheduler(cm);
|
||||
// 5s decoded, playback started, stream NOT complete.
|
||||
for (let i = 0; i < 5; i++) s.addBuffer(buf(1));
|
||||
cm.now = 0;
|
||||
s.playFromPosition(0);
|
||||
|
||||
// Playhead advances past the decoded tail and the queue drains → mid-stream underrun park.
|
||||
cm.now = 6;
|
||||
drainAllSources(s, cm);
|
||||
assertEqual(s.isActive(), false, 'parked in underrun');
|
||||
|
||||
// At the park the playhead sits at the decoded tail: forward lookahead is 0, so production must
|
||||
// NOT be throttled (the "paused while decoded audio is low" hypothesis must not hold here).
|
||||
assertClose(s.getForwardLookaheadSeconds(), 0, 'lookahead is 0 at the underrun tail');
|
||||
assertEqual(s.evaluateProductionPause(), false, 'low decoded audio does not pause production');
|
||||
|
||||
// Refill arriving during the park grows the lead monotonically; lookahead reflects exactly it,
|
||||
// measured against the FROZEN playhead — not a stale pre-underrun position.
|
||||
s.addBuffer(buf(1));
|
||||
s.addBuffer(buf(1));
|
||||
assertClose(s.getForwardLookaheadSeconds(), 2, 'lookahead equals the freshly-accumulated lead');
|
||||
assertEqual(s.evaluateProductionPause(), false, 'still unthrottled well below the 60s high-water');
|
||||
});
|
||||
|
||||
// === False end-of-playback guard (Opus-startup misfire) ======================================
|
||||
//
|
||||
// The scheduler must distinguish a GENUINE end-of-track (stream complete AND queue drained) from a
|
||||
|
||||
Reference in New Issue
Block a user