diff options
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/VideoSink.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/VideoSink.java | 55 |
1 files changed, 34 insertions, 21 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/VideoSink.java b/app/src/main/java/invalid/lena/scrcpy/VideoSink.java index 2d69339..ff8f449 100644 --- a/app/src/main/java/invalid/lena/scrcpy/VideoSink.java +++ b/app/src/main/java/invalid/lena/scrcpy/VideoSink.java @@ -20,8 +20,8 @@ import java.util.Iterator; // plus a corresponding pool of free input buffer indices. // // Back-pressure policy: when no input buffer is free and the pending -// queue is full, the oldest pending non-config frame is dropped. Config -// frames (CSD) always survive - the decoder cannot start without them. +// queue is full, replace the oldest frame of the same kind, or the oldest +// frame overall. The queue remains bounded even if a peer floods CSD. // Keyframes are not distinguishable here (the flag stays in VideoStream) // so they drop like any delta frame; the picture heals at the next one. // @@ -36,8 +36,9 @@ public final class VideoSink implements VideoFrames { private static final int MAX_PENDING = 8; private volatile Surface surface; + private final Runnable onFatalError; public volatile long frames; // public read for the status overlay - private MediaCodec codec; + private volatile MediaCodec codec; private HandlerThread handlerThread; private Handler handler; @@ -56,8 +57,9 @@ public final class VideoSink implements VideoFrames { Frame(byte[] d, long pts, boolean cfg) { data = d; ptsUs = pts; isConfig = cfg; } } - public VideoSink(Surface surface) { + public VideoSink(Surface surface, Runnable onFatalError) { this.surface = surface; + this.onFatalError = onFatalError; } // Swap the output Surface without rebuilding MediaCodec. Passing null @@ -97,7 +99,7 @@ public final class VideoSink implements VideoFrames { codec = MediaCodec.createDecoderByType(mime); codec.setCallback(new MediaCodec.Callback() { @Override public void onInputBufferAvailable(MediaCodec mc, int idx) { - onFreeInput(idx); + onFreeInput(mc, idx); } @Override public void onOutputBufferAvailable(MediaCodec mc, int idx, MediaCodec.BufferInfo info) { try { @@ -118,6 +120,7 @@ public final class VideoSink implements VideoFrames { } @Override public void onError(MediaCodec mc, MediaCodec.CodecException e) { Log.e(e, "video sink: codec error"); + if (mc == codec && onFatalError != null) onFatalError.run(); } @Override public void onOutputFormatChanged(MediaCodec mc, MediaFormat fmt) { Log.i("video sink: output format %s", fmt); @@ -137,20 +140,26 @@ public final class VideoSink implements VideoFrames { if (released) return; // Try to drain immediately if there's a free input. while (!pending.isEmpty() && !freeInputs.isEmpty()) { - submit(pending.pollFirst(), freeInputs.pollFirst()); + submit(codec, pending.pollFirst(), freeInputs.pollFirst()); } if (!freeInputs.isEmpty()) { - submit(new Frame(data, ptsUs, isConfig), freeInputs.pollFirst()); + submit(codec, new Frame(data, ptsUs, isConfig), freeInputs.pollFirst()); return; } - // Queue, with bounded drop policy on non-config frames. - if (pending.size() >= MAX_PENDING && !isConfig) { - // Drop the oldest non-config frame to avoid stalling - // forever. Config frames must survive: the decoder - // cannot start without its CSD. + // Keep the queue strictly bounded. A newer config frame replaces + // an older one; retaining every config packet lets a hostile peer + // turn the queue into an unbounded allocation sink. + if (pending.size() >= MAX_PENDING) { + boolean removed = false; for (Iterator<Frame> it = pending.iterator(); it.hasNext(); ) { - if (!it.next().isConfig) { it.remove(); break; } + Frame f = it.next(); + if (f.isConfig == isConfig) { + it.remove(); + removed = true; + break; + } } + if (!removed) pending.pollFirst(); } pending.offerLast(new Frame(data, ptsUs, isConfig)); } @@ -196,10 +205,10 @@ public final class VideoSink implements VideoFrames { } // Internal - runs on the MediaCodec callback thread. - private void onFreeInput(int idx) { + private void onFreeInput(MediaCodec mc, int idx) { synchronized (lock) { - if (released) return; - if (!pending.isEmpty()) submit(pending.pollFirst(), idx); + if (released || mc != codec) return; + if (!pending.isEmpty()) submit(mc, pending.pollFirst(), idx); else freeInputs.offerLast(idx); } } @@ -207,15 +216,19 @@ public final class VideoSink implements VideoFrames { // Must be called with `lock` held. codec can be null mid-reconfigure // (teardownCodec runs unlocked); the frame is dropped like any other // back-pressure casualty. - private void submit(Frame f, int idx) { - if (codec == null) return; + private void submit(MediaCodec mc, Frame f, int idx) { + if (mc == null || mc != codec) return; try { - ByteBuffer buf = codec.getInputBuffer(idx); - if (buf == null) return; + ByteBuffer buf = mc.getInputBuffer(idx); + if (buf == null || f.data.length > buf.capacity()) { + Log.e("video sink: frame exceeds codec input (%d bytes)", f.data.length); + if (onFatalError != null) onFatalError.run(); + return; + } buf.clear(); buf.put(f.data); int flags = f.isConfig ? MediaCodec.BUFFER_FLAG_CODEC_CONFIG : 0; - codec.queueInputBuffer(idx, 0, f.data.length, f.ptsUs, flags); + mc.queueInputBuffer(idx, 0, f.data.length, f.ptsUs, flags); } catch (IllegalStateException e) { Log.w("video sink: queueInputBuffer: %s", e); } |