From 48e54498b6e88253f6c23b9e575e7368192804b0 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 13 Oct 2026 11:57:08 +0000 Subject: [PATCH 1/3] fix: a stopped grid cell closes its decoder in surfaceDestroyed, before its surface goes away (#76) --- .../app/src/main/java/com/openipc/decoder/QuadCell.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/openipc/decoder/QuadCell.java b/android/app/src/main/java/com/openipc/decoder/QuadCell.java index 7dda393c..6cec152f 100644 --- a/android/app/src/main/java/com/openipc/decoder/QuadCell.java +++ b/android/app/src/main/java/com/openipc/decoder/QuadCell.java @@ -156,6 +156,8 @@ void start() { private final SurfaceHolder.Callback holderCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { + // A stopped cell keeps this callback only to see its surface go (#76) + if (!started) return; // The surface belongs to the SurfaceView: it is used, never released, here surface = holder.getSurface(); if (!threadsStarted) startThreads(); @@ -169,6 +171,7 @@ public void surfaceDestroyed(SurfaceHolder holder) { // a new one when the next surface is there surface = null; decoder.closeDecoder(); + if (!started) holder.removeCallback(this); } }; @@ -218,7 +221,10 @@ void stop() { executor = null; } nalQueue.clear(); - if (view != null) view.getHolder().removeCallback(holderCallback); + // The holder callback stays until surfaceDestroyed(): there the decoder is closed + // before the surface goes away. Removing it here let the grid's surfaces be destroyed + // while up to nine decoders were still being released on their own threads, and + // SurfaceFlinger then stalled the window's buffers for tens of seconds (#76) surface = null; } From 4414500c2a957b13729764d4e1ef3d066d4d9bdc Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 13 Oct 2026 12:12:21 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20set=20up=20one=20decoder=20at=20a=20?= =?UTF-8?q?time,=20250=20ms=20apart=20=E2=80=94=20nine=20at=20once=20stall?= =?UTF-8?q?ed=20SurfaceFlinger=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openipc/decoder/MediaCodecManager.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java b/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java index cab2808b..a56db513 100644 --- a/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java @@ -257,6 +257,11 @@ private static long nowMs() { return System.nanoTime() / 1_000_000; } + /** Decoders are set up one after another, at least CREATE_GAP_MS apart (#76). */ + private static final Object CREATE_LOCK = new Object(); + static final long CREATE_GAP_MS = 250; + private static long lastCreateMs; + void createDecoder(boolean codecH265) { final int gen; synchronized (decoderLock) { @@ -290,17 +295,30 @@ void createDecoder(boolean codecH265) { MediaCodec local; try { - Log.i(TAG, "Start video decoder (" + type + ")"); - local = MediaCodec.createDecoderByType(type); - learnLimits(local, type); - try { - local.configure(format, s, null, 0); - local.start(); - if (listener != null) listener.onDecoderStarted(); - } catch (Exception e) { - local.release(); - throw e; + // One decoder set up at a time, with a gap: the 3x3 grid used to start up to nine + // at once, and on the AG-216 that stalled SurfaceFlinger for tens of seconds (#76) + synchronized (CREATE_LOCK) { + long wait = lastCreateMs + CREATE_GAP_MS - nowMs(); + if (wait > 0) Thread.sleep(wait); + Log.i(TAG, "Start video decoder (" + type + ")"); + local = MediaCodec.createDecoderByType(type); + learnLimits(local, type); + try { + local.configure(format, s, null, 0); + local.start(); + } catch (Exception e) { + local.release(); + throw e; + } finally { + lastCreateMs = nowMs(); + } } + if (listener != null) listener.onDecoderStarted(); + } catch (InterruptedException e) { + // The cell stopped while waiting for its turn: nothing was created + Thread.currentThread().interrupt(); + InstanceBudget.release(type); + return; } catch (Exception e) { InstanceBudget.release(type); synchronized (decoderLock) { From 0b87b46fc330b793b9beed39ac556d85c4bc6233 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 13 Oct 2026 12:19:51 +0000 Subject: [PATCH 3/3] Revert "fix: a stopped grid cell closes its decoder in surfaceDestroyed, before its surface goes away (#76)" This reverts commit 48e54498b6e88253f6c23b9e575e7368192804b0. --- .../app/src/main/java/com/openipc/decoder/QuadCell.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/android/app/src/main/java/com/openipc/decoder/QuadCell.java b/android/app/src/main/java/com/openipc/decoder/QuadCell.java index 6cec152f..7dda393c 100644 --- a/android/app/src/main/java/com/openipc/decoder/QuadCell.java +++ b/android/app/src/main/java/com/openipc/decoder/QuadCell.java @@ -156,8 +156,6 @@ void start() { private final SurfaceHolder.Callback holderCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { - // A stopped cell keeps this callback only to see its surface go (#76) - if (!started) return; // The surface belongs to the SurfaceView: it is used, never released, here surface = holder.getSurface(); if (!threadsStarted) startThreads(); @@ -171,7 +169,6 @@ public void surfaceDestroyed(SurfaceHolder holder) { // a new one when the next surface is there surface = null; decoder.closeDecoder(); - if (!started) holder.removeCallback(this); } }; @@ -221,10 +218,7 @@ void stop() { executor = null; } nalQueue.clear(); - // The holder callback stays until surfaceDestroyed(): there the decoder is closed - // before the surface goes away. Removing it here let the grid's surfaces be destroyed - // while up to nine decoders were still being released on their own threads, and - // SurfaceFlinger then stalled the window's buffers for tens of seconds (#76) + if (view != null) view.getHolder().removeCallback(holderCallback); surface = null; }