aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/VideoQueue.java
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-01 00:00:00 +0000
committerLena <lena@omega>2026-08-01 00:00:00 +0000
commit852a8a00273c9128efeed0217a8e5d3fcd8bf780 (patch)
treec72f959731fa3c7c809cf1a0222363b6c9c9b03b /app/src/main/java/invalid/lena/scrcpy/VideoQueue.java
parentf379b93d52bf0dbd3816ec3f64d165314771d2a6 (diff)
downloadscrcpy-android-852a8a00273c9128efeed0217a8e5d3fcd8bf780.tar.gz
app: harden mirroring lifecycle and state
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/VideoQueue.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/VideoQueue.java94
1 files changed, 94 insertions, 0 deletions
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<Frame> 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<Frame> it = frames.iterator(); it.hasNext(); ) {
+ Frame frame = it.next();
+ if (!frame.config) {
+ it.remove();
+ bytes -= frame.data.length;
+ }
+ }
+ }
+}