From b6bd6497b2061943ce61de793eec2fdd3833ba00 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Sun, 4 Oct 2026 20:10:49 -0700 Subject: [PATCH] repeat the last video frame when the screen goes still Decoders that show a frame only once the next arrives held the last change until something else moved, so a closing menu looked a second late on LIVI. --- .../main/java/app/seb3thehacker/gearslip/VideoSource.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/VideoSource.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/VideoSource.kt index b85398c..6a17a01 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/VideoSource.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/VideoSource.kt @@ -60,6 +60,14 @@ class VideoSource( // Drops already ask for a fresh keyframe, so a long interval costs nothing in recovery // and spares the link a big frame every second. setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL_S) + if (mode == Mode.SURFACE) { + // A surface encoder only makes a frame when the screen changes, and many decoders + // show a frame only once the next one arrives. So the last change before the + // screen goes still (a menu closing) sat in the car's decoder until something + // else moved. Repeating the last frame after a short pause pushes it through; + // a repeat of an unchanged screen costs a few bytes. scrcpy does the same. + setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_AFTER_US) + } setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline) setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel31) if (tuned) { @@ -215,6 +223,7 @@ class VideoSource( const val MIN_BIT_RATE = 4_000_000 const val MAX_BIT_RATE = 12_000_000 const val I_FRAME_INTERVAL_S = 5 + private const val REPEAT_AFTER_US = 100_000L /** (Y, Cb, Cr) - white, yellow, cyan, green, magenta, red, blue. */ val BAR_COLOURS = listOf(