diff --git a/android/app/build.gradle b/android/app/build.gradle index a45a4439..f6bbe65f 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 41 - versionName "2.16" + versionCode 42 + versionName "2.17" buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" } diff --git a/android/app/src/main/java/com/openipc/decoder/Decoder.java b/android/app/src/main/java/com/openipc/decoder/Decoder.java index de6097e4..46cb3567 100755 --- a/android/app/src/main/java/com/openipc/decoder/Decoder.java +++ b/android/app/src/main/java/com/openipc/decoder/Decoder.java @@ -213,6 +213,8 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onDecoderStarted() { removeClearOverlay(); } @Override public void onDecoderFailed() {} }); + // Single view: streams above 1080p on decoders that need to know it up front (#166) + videoDecoder.allowLargeFrames(true); audioManager = new AudioManager(); rtspClient = new RtspClient(framePool, nalAssembler, nalQueue); rtspClient.setListener(new RtspClient.Listener() { 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 f75d6ca0..4c5acbec 100644 --- a/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java @@ -56,6 +56,13 @@ class MediaCodecManager { // Surface set by the owning activity private volatile Surface surface; + /** + * Single view (#166): announce the largest picture the decoder supports (up to 4K) as + * KEY_MAX_WIDTH/HEIGHT. Configured at 1280×720 without it, some hardware decoders + * (Rockchip OMX) never give a frame for streams above 1080p, e.g. 2304×1296. Off in the + * grid: nine decoders with 4K buffers would not fit in memory. + */ + private volatile boolean largeFrames; /** Frames rendered so far (#82): with the frames received this gives the real decode rate. */ private volatile int framesOut; @@ -255,6 +262,27 @@ void setSurface(Surface s) { this.surface = s; } + /** See {@link #largeFrames}; takes effect with the next decoder. */ + void allowLargeFrames(boolean on) { + largeFrames = on; + } + + /** Largest supported size up to 3840×2160 (a few steps down), or null if none above 1080p. */ + static int[] maxFrameSize(MediaCodec codec, String mime) { + try { + MediaCodecInfo.VideoCapabilities video = + codec.getCodecInfo().getCapabilitiesForType(mime).getVideoCapabilities(); + if (video == null) return null; + int[][] sizes = {{3840, 2160}, {2560, 1920}, {2560, 1440}, {2304, 1296}}; + for (int[] size : sizes) { + if (video.isSizeSupported(size[0], size[1])) return size; + } + } catch (Exception ignored) { + // Capabilities unknown: keep the decoder's defaults + } + return null; + } + /** Enable or disable decoding; disabling also releases the decoder. */ /** * Enable or disable decoding. Never blocks the caller (the UI thread): disabling only @@ -374,6 +402,13 @@ void createDecoder(boolean codecH265) { Log.i(TAG, "Start video decoder (" + type + ")"); local = MediaCodec.createDecoderByType(type); learnLimits(local, type); + int[] max = largeFrames ? maxFrameSize(local, type) : null; + if (max != null) { + format.setInteger(MediaFormat.KEY_MAX_WIDTH, max[0]); + format.setInteger(MediaFormat.KEY_MAX_HEIGHT, max[1]); + Log.i(TAG, "Decoder " + local.getName() + ": pictures up to " + + max[0] + "×" + max[1]); + } } try { local.configure(format, s, null, 0);