From aa27892c1204914e1e016dad8650bd07dce5085d Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 01:32:01 +0100 Subject: [PATCH] Reject regressed stream PTS before submitting it to the decoder --- .../src/main/java/com/utun/chat/data/StreamDecodePump.kt | 6 ++++-- .../test/java/com/utun/chat/data/StreamDecodePumpTest.kt | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamDecodePump.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamDecodePump.kt index f88e7b38..7bdd3f3a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamDecodePump.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamDecodePump.kt @@ -2,7 +2,8 @@ package com.utun.chat.data import java.util.ArrayDeque -/** Один рабочий вход и один готовый выход. Codec владеет внутренним конвейером; +/** Один рабочий вход и один готовый выход; PTS/position/now/lead/period — микросекунды. + * Codec владеет внутренним конвейером; * число поданных кадров и часы показа не ограничивают заполнение этого конвейера. * Все вызовы принадлежат одному player worker. Ошибки логирует владелец вместе с session ID. */ internal class StreamDecodePump( @@ -57,8 +58,9 @@ internal class StreamDecodePump( drain(position, now) if (output != null || ended) return val frame = input - if (frame != null && submit(frame)) { + if (frame != null) { check(lastInputPts < frame.pts) { "Decoder input PTS did not advance" } + if (!submit(frame)) return if (lastInputPts >= 0) framePeriodUs = (frame.pts - lastInputPts).coerceIn(1, 1_000_000) lastInputPts = frame.pts; lastInputAt = now; inputCount++; submitted.addLast(frame.pts); input = null drain(position, now) diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamDecodePumpTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamDecodePumpTest.kt index 643dc5cd..7de25119 100644 --- a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamDecodePumpTest.kt +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamDecodePumpTest.kt @@ -106,4 +106,11 @@ class StreamDecodePumpTest { assertTrue(pump.ended); assertEquals(0, pump.inflight); assertEquals(-1L, pump.oldestSubmittedPts) assertEquals(1, released); pump.checkProgress(30_000_000, true) } + + @Test fun regressedInputPtsIsRejectedBeforeTheCodecReceivesIt() { + val codec = Codec(1); codec.pump.offer(Frame(100), 0); codec.pump.step(100, 0) + codec.pump.offer(Frame(100), 1) + assertThrows(IllegalStateException::class.java) { codec.pump.step(100, 1) } + assertTrue(codec.queued.isEmpty()); assertEquals(1L, codec.pump.inputCount) + } }