From 48cdbb23a9ae6ae201e6f83c7f61d89327e2b160 Mon Sep 17 00:00:00 2001 From: Eugen Pechanec Date: Sat, 25 Jun 2022 13:31:57 +0200 Subject: [PATCH] Extract MediaCodecCompat from ScreenEncoder --- .../genymobile/scrcpy/MediaCodecCompat.java | 116 ++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 17 +-- 2 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/MediaCodecCompat.java diff --git a/server/src/main/java/com/genymobile/scrcpy/MediaCodecCompat.java b/server/src/main/java/com/genymobile/scrcpy/MediaCodecCompat.java new file mode 100644 index 00000000..5e2741c9 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/MediaCodecCompat.java @@ -0,0 +1,116 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodec; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.nio.ByteBuffer; + +import static android.media.MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED; +import static android.os.Build.VERSION.SDK_INT; + +/** + * Version of {@link MediaCodec} that backports {@link #getOutputBuffer} to Kitkat. + * The backported implementation isn't thread safe. + */ +abstract class MediaCodecCompat { + private final MediaCodec delegate; + + MediaCodecCompat(@NonNull MediaCodec delegate) { + this.delegate = delegate; + } + + @NonNull + protected MediaCodec getDelegate() { + return delegate; + } + + @NonNull + static MediaCodecCompat wrap(@NonNull MediaCodec codec) { + if (SDK_INT >= 21) { + return new Platform(codec); + } else { + return new Backport(codec); + } + } + + abstract int dequeueOutputBuffer( + @NonNull MediaCodec.BufferInfo info, long timeoutUs); + + @Nullable + abstract ByteBuffer getOutputBuffer(int index); + + abstract void releaseOutputBuffer(int index, boolean render); + + @SuppressWarnings("deprecation") + private static class Backport extends MediaCodecCompat { + private ByteBuffer[] cachedOutputBuffers = null; + + Backport(@NonNull MediaCodec delegate) { + super(delegate); + } + + @Override + int dequeueOutputBuffer( + @NonNull MediaCodec.BufferInfo info, long timeoutUs) { + final int res = getDelegate().dequeueOutputBuffer(info, timeoutUs); + if (res == INFO_OUTPUT_BUFFERS_CHANGED) { + cachedOutputBuffers = null; + } + return res; + } + + @Nullable + @Override + ByteBuffer getOutputBuffer(int index) { + if (cachedOutputBuffers == null) { + cacheOutputBuffers(); + } + if (cachedOutputBuffers == null) { + throw new IllegalStateException(); + } + return cachedOutputBuffers[index]; + } + + @Override + void releaseOutputBuffer(int index, boolean render) { + cachedOutputBuffers = null; + getDelegate().releaseOutputBuffer(index, render); + } + + private void cacheOutputBuffers() { + ByteBuffer[] buffers = null; + try { + buffers = getDelegate().getOutputBuffers(); + } catch (IllegalStateException e) { + // we don't get buffers in async mode + } + cachedOutputBuffers = buffers; + } + } + + @RequiresApi(21) + private static class Platform extends MediaCodecCompat { + Platform(@NonNull MediaCodec delegate) { + super(delegate); + } + + @Override + int dequeueOutputBuffer( + @NonNull MediaCodec.BufferInfo info, long timeoutUs) { + return getDelegate().dequeueOutputBuffer(info, timeoutUs); + } + + @Nullable + @Override + ByteBuffer getOutputBuffer(int index) { + return getDelegate().getOutputBuffer(index); + } + + @Override + void releaseOutputBuffer(int index, boolean render) { + getDelegate().releaseOutputBuffer(index, render); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f65fd4a0..ccdf0d1b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -144,11 +144,10 @@ public class ScreenEncoder implements Device.RotationListener { return 0; } - @SuppressWarnings("deprecation") // Android API 19 requires to call deprecated methods - private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { + private boolean encode(MediaCodec platformCodec, FileDescriptor fd) throws IOException { + final MediaCodecCompat codec = MediaCodecCompat.wrap(platformCodec); boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - ByteBuffer[] cachedOutputBuffers = null; while (!consumeRotationChange() && !eof) { int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); @@ -159,15 +158,7 @@ public class ScreenEncoder implements Device.RotationListener { break; } if (outputBufferId >= 0) { - ByteBuffer codecBuffer; - if (Build.VERSION.SDK_INT >= 21) { - codecBuffer = codec.getOutputBuffer(outputBufferId); - } else { - if (cachedOutputBuffers == null) { - cachedOutputBuffers = codec.getOutputBuffers(); - } - codecBuffer = cachedOutputBuffers[outputBufferId]; - } + ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); if (sendFrameMeta) { writeFrameMeta(fd, bufferInfo, codecBuffer.remaining()); @@ -178,8 +169,6 @@ public class ScreenEncoder implements Device.RotationListener { // If this is not a config packet, then it contains a frame firstFrameSent = true; } - } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { - cachedOutputBuffers = null; } } finally { if (outputBufferId >= 0) {