diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt index 64cbc5e0..4e6bc19b 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt @@ -94,14 +94,23 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv resetAudio(); resetVideo(); videoBase = -1; prefillUntil = 0 connectDeadline = System.nanoTime() + 10_000_000_000 } - // Отброшенный звук всё равно прогревает Opus; остающиеся пакеты будут декодированы один раз при воспроизведении. - fun warmAudio(frame: Frame) { - if (warmAudioPts >= 0 && frame.pts != warmAudioPts) { - LogManager.addLog("WARN", "stream", "preroll audio discontinuity id=$id expected=$warmAudioPts pts=${frame.pts}") + // Прогрев допускает джиттер PTS и короткий PLC; большой разрыв начинает новое состояние Opus. + fun prepareAudio(pts: Long) { + if (warmAudioPts >= 0 && (pts < warmAudioPts - 10000 || pts > warmAudioPts + 100000)) { + LogManager.addLog("WARN", "stream", "preroll audio discontinuity id=$id expected=$warmAudioPts pts=$pts") if (opus != 0L) NativeLib.streamOpusClose(opus) - opus = 0 + opus = 0; warmAudioPts = -1 } if (opus == 0L) { opus = NativeLib.streamOpusCreate(false); check(opus != 0L) } + val missing = if (warmAudioPts < 0) 0 else ((pts - warmAudioPts + 10000) / 20000).toInt() + repeat(missing) { + check(NativeLib.streamOpusDecode(opus, byteArrayOf()) != null) { "Preroll Opus PLC failed" } + prerollFrames += 960 + } + } + // Отброшенный звук всё равно прогревает Opus; остающиеся пакеты декодируются один раз при воспроизведении. + fun warmAudio(frame: Frame) { + prepareAudio(frame.pts) check(NativeLib.streamOpusDecode(opus, frame.data) != null) { "Preroll Opus decode failed" } warmAudioPts = frame.pts + 20000; prerollFrames += 960 } @@ -237,12 +246,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv val now = System.nanoTime() if (prefillUntil != 0L && now >= prefillUntil) { if (audios.isNotEmpty() && track == null) { - if (warmAudioPts >= 0 && audios.first.pts != warmAudioPts) { - LogManager.addLog("WARN", "stream", "audio startup discontinuity id=$id expected=$warmAudioPts pts=${audios.first.pts}") - if (opus != 0L) NativeLib.streamOpusClose(opus) - opus = 0 - } - if (opus == 0L) { opus = NativeLib.streamOpusCreate(false); check(opus != 0L) } + prepareAudio(audios.first.pts) track = AudioTrack.Builder().setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build()).setAudioFormat(AudioFormat.Builder() .setSampleRate(48000).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())