From 7fe1f7968fc27639b1dedb85ecd4ec0257faa721 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Wed, 14 Oct 2026 15:21:59 +0000 Subject: [PATCH 1/3] fix: picture size from the decoder's crop rectangle, not the padded buffer (#166) KEY_WIDTH / KEY_HEIGHT of the output format are the buffer, padded to the decoder's alignment: 1080 became 1088 everywhere, and one H.265 decoder reported a 1920x1080 stream as 2304x1088, which also skewed the aspect ratio the single view fits the picture to. The crop-* keys give the picture; the buffer size stays when a decoder sets none. --- .../openipc/decoder/MediaCodecManager.java | 38 ++++++++++++++--- .../com/openipc/decoder/VisibleSizeTest.java | 41 +++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java 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 1f2026d0..493c07ca 100644 --- a/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java @@ -490,6 +490,34 @@ boolean decodeFrame(Frame buffer, boolean codecH265, long watchdogRef) { return needCreate; } + /** + * The picture's size: the crop rectangle when the decoder gives one. KEY_WIDTH / KEY_HEIGHT + * are the buffer, padded to the decoder's alignment (1080 → 1088; one H.265 decoder made + * 1920 wide 2304), which skewed the picture's aspect ratio and the size shown (#166). + */ + static int[] visibleSize(MediaFormat format) { + return visibleSize(format.getInteger(MediaFormat.KEY_WIDTH), + format.getInteger(MediaFormat.KEY_HEIGHT), + cropKey(format, "crop-left"), cropKey(format, "crop-top"), + cropKey(format, "crop-right"), cropKey(format, "crop-bottom")); + } + + private static int cropKey(MediaFormat format, String key) { + return format.containsKey(key) ? format.getInteger(key) : -1; + } + + /** As above from plain values; a crop value < 0 is missing. Crop right / bottom are inclusive. */ + static int[] visibleSize(int width, int height, int left, int top, int right, int bottom) { + int w = width; + int h = height; + if (left >= 0 && right > left) w = right - left + 1; + if (top >= 0 && bottom > top) h = bottom - top + 1; + // A crop larger than the buffer is not believed + if (w > width && width > 0) w = width; + if (h > height && height > 0) h = height; + return new int[]{w, h}; + } + /** Renders every decoded frame that is ready; caller holds decoderLock. */ private void drainOutput(MediaCodec codec) { MediaCodec.BufferInfo info = bufferInfo; @@ -497,13 +525,11 @@ private void drainOutput(MediaCodec codec) { while ((outputBufferId = codec.dequeueOutputBuffer(info, 0)) >= 0 || outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - MediaFormat format = codec.getOutputFormat(); - int mWidth = format.getInteger(MediaFormat.KEY_WIDTH); - int mHeight = format.getInteger(MediaFormat.KEY_HEIGHT); - outWidth = mWidth; - outHeight = mHeight; + int[] size = visibleSize(codec.getOutputFormat()); + outWidth = size[0]; + outHeight = size[1]; if (listener != null) { - listener.onResolutionChanged(mWidth, mHeight); + listener.onResolutionChanged(size[0], size[1]); } } else { codec.releaseOutputBuffer(outputBufferId, true); diff --git a/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java b/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java new file mode 100644 index 00000000..40837780 --- /dev/null +++ b/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) OpenIPC https://openipc.org Prosperity Public License 3.0.0 + * + * VisibleSizeTest.java — picture size from the decoder's crop rectangle, not the padded buffer (#166) + * + */ + +package com.openipc.decoder; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +public class VisibleSizeTest { + @Test + public void cropGivesThePictureSize() { + // H.264 1080p: 16-line macroblocks pad the buffer to 1088 + assertArrayEquals(new int[]{1920, 1080}, + MediaCodecManager.visibleSize(1920, 1088, 0, 0, 1919, 1079)); + // H.265 1080p on a decoder that pads the width to 2304 (#166) + assertArrayEquals(new int[]{1920, 1080}, + MediaCodecManager.visibleSize(2304, 1088, 0, 0, 1919, 1079)); + // An offset crop + assertArrayEquals(new int[]{1280, 720}, + MediaCodecManager.visibleSize(1280, 736, 0, 8, 1279, 727)); + } + + @Test + public void withoutCropTheBufferSizeStays() { + assertArrayEquals(new int[]{1920, 1088}, + MediaCodecManager.visibleSize(1920, 1088, -1, -1, -1, -1)); + assertArrayEquals(new int[]{640, 480}, + MediaCodecManager.visibleSize(640, 480, 0, 0, 0, 0)); + } + + @Test + public void cropLargerThanTheBufferIsIgnored() { + assertArrayEquals(new int[]{1920, 1088}, + MediaCodecManager.visibleSize(1920, 1088, 0, 0, 3839, 2159)); + } +} From 09e0c7ac77f7959206148c9dcbee6e8e46fe57f3 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Wed, 14 Oct 2026 16:13:33 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20Qodo=20on=20#167=20=E2=80=94=20a=20c?= =?UTF-8?q?rop=20edge=20outside=20the=20buffer=20keeps=20that=20axis=20at?= =?UTF-8?q?=20the=20buffer=20size?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/openipc/decoder/MediaCodecManager.java | 10 +++------- .../test/java/com/openipc/decoder/VisibleSizeTest.java | 8 +++++++- 2 files changed, 10 insertions(+), 8 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 493c07ca..f75d6ca0 100644 --- a/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java @@ -508,13 +508,9 @@ private static int cropKey(MediaFormat format, String key) { /** As above from plain values; a crop value < 0 is missing. Crop right / bottom are inclusive. */ static int[] visibleSize(int width, int height, int left, int top, int right, int bottom) { - int w = width; - int h = height; - if (left >= 0 && right > left) w = right - left + 1; - if (top >= 0 && bottom > top) h = bottom - top + 1; - // A crop larger than the buffer is not believed - if (w > width && width > 0) w = width; - if (h > height && height > 0) h = height; + // A crop edge outside the buffer is not believed: that axis keeps the buffer size + int w = left >= 0 && right > left && right < width ? right - left + 1 : width; + int h = top >= 0 && bottom > top && bottom < height ? bottom - top + 1 : height; return new int[]{w, h}; } diff --git a/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java b/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java index 40837780..a7f1da7c 100644 --- a/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java +++ b/android/app/src/test/java/com/openipc/decoder/VisibleSizeTest.java @@ -34,8 +34,14 @@ public void withoutCropTheBufferSizeStays() { } @Test - public void cropLargerThanTheBufferIsIgnored() { + public void cropOutsideTheBufferIsIgnored() { assertArrayEquals(new int[]{1920, 1088}, MediaCodecManager.visibleSize(1920, 1088, 0, 0, 3839, 2159)); + // Wholly outside, narrower than the buffer (Qodo on #167) + assertArrayEquals(new int[]{1920, 1088}, + MediaCodecManager.visibleSize(1920, 1088, 3000, 2000, 3100, 2100)); + // One axis bad, the other kept + assertArrayEquals(new int[]{1920, 1080}, + MediaCodecManager.visibleSize(1920, 1088, 0, 0, 1920, 1079)); } } From 03966ee9e7f0bc09eddf46a10932b6068e2d3aaf Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Wed, 14 Oct 2026 16:13:33 +0000 Subject: [PATCH 3/3] chore: version 2.04 --- android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 751e19f5..e3fc69f7 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,8 +23,8 @@ android { applicationId "com.openipc.decoder" minSdk 21 targetSdk 36 - versionCode 28 - versionName "2.03" + versionCode 29 + versionName "2.04" buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" }