From 852a8a00273c9128efeed0217a8e5d3fcd8bf780 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: app: harden mirroring lifecycle and state --- .../main/java/invalid/lena/scrcpy/VideoQueue.java | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/src/main/java/invalid/lena/scrcpy/VideoQueue.java (limited to 'app/src/main/java/invalid/lena/scrcpy/VideoQueue.java') diff --git a/app/src/main/java/invalid/lena/scrcpy/VideoQueue.java b/app/src/main/java/invalid/lena/scrcpy/VideoQueue.java new file mode 100644 index 0000000..a3c308f --- /dev/null +++ b/app/src/main/java/invalid/lena/scrcpy/VideoQueue.java @@ -0,0 +1,94 @@ +package invalid.lena.scrcpy; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +// Bounded encoded-video queue. It never leaves delta frames queued without +// the keyframe that starts their decoder generation. +final class VideoQueue { + + static final class Frame { + final byte[] data; + final long ptsUs; + final boolean config; + final boolean keyframe; + + Frame(byte[] data, long ptsUs, boolean config, boolean keyframe) { + this.data = data; + this.ptsUs = ptsUs; + this.config = config; + this.keyframe = keyframe; + } + } + + private final int maxFrames; + private final int maxBytes; + private final Deque frames; + private int bytes; + private boolean needsKeyframe = true; + + VideoQueue(int maxFrames, int maxBytes) { + if (maxFrames < 1 || maxBytes < 1) throw new IllegalArgumentException(); + this.maxFrames = maxFrames; + this.maxBytes = maxBytes; + frames = new ArrayDeque<>(maxFrames); + } + + boolean offer(Frame frame) { + if (frame.config) { + clear(); + return append(frame); + } + if (frame.keyframe) { + removeMediaFrames(); + needsKeyframe = false; + if (append(frame)) return true; + needsKeyframe = true; + return false; + } + if (needsKeyframe) return false; + if (append(frame)) return true; + needsKeyframe = true; + return false; + } + + Frame poll() { + Frame frame = frames.pollFirst(); + if (frame != null) bytes -= frame.data.length; + return frame; + } + + boolean isEmpty() { + return frames.isEmpty(); + } + + boolean needsKeyframe() { + return needsKeyframe; + } + + void clear() { + frames.clear(); + bytes = 0; + needsKeyframe = true; + } + + private boolean append(Frame frame) { + if (frames.size() >= maxFrames || frame.data.length > maxBytes - bytes) { + return false; + } + frames.offerLast(frame); + bytes += frame.data.length; + return true; + } + + private void removeMediaFrames() { + for (Iterator it = frames.iterator(); it.hasNext(); ) { + Frame frame = it.next(); + if (!frame.config) { + it.remove(); + bytes -= frame.data.length; + } + } + } +} -- cgit v1.2.3