From 66def38b734829170654647086bd76f99efdec70 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Sep 2018 15:01:55 +0200 Subject: [PATCH] Avoid additional buffer copy in userspace Directly send the data from MediaCodec buffers to the LocalSocket, without an intermediate copy in userspace. --- .../genymobile/scrcpy/DesktopConnection.java | 12 +++---- .../main/java/com/genymobile/scrcpy/IO.java | 31 +++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 21 ++++--------- .../java/com/genymobile/scrcpy/Server.java | 2 +- 4 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/IO.java diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index d5740c15..d87a7fd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -5,9 +5,9 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import java.io.Closeable; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; public final class DesktopConnection implements Closeable { @@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable { private final LocalSocket socket; private final InputStream inputStream; - private final OutputStream outputStream; + private final FileDescriptor fd; private final ControlEventReader reader = new ControlEventReader(); private DesktopConnection(LocalSocket socket) throws IOException { this.socket = socket; inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); + fd = socket.getFileDescriptor(); } private static LocalSocket connect(String abstractName) throws IOException { @@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable { buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; - outputStream.write(buffer, 0, buffer.length); + IO.writeFully(fd, buffer, 0, buffer.length); } - public OutputStream getOutputStream() { - return outputStream; + public FileDescriptor getFd() { + return fd; } public ControlEvent receiveControlEvent() throws IOException { diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java new file mode 100644 index 00000000..bfd48be2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/IO.java @@ -0,0 +1,31 @@ +package com.genymobile.scrcpy; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class IO { + private IO() { + // not instantiable + } + + public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException { + while (from.hasRemaining()) { + try { + Os.write(fd, from); + } catch (ErrnoException e) { + if (e.errno != OsConstants.EINTR) { + throw new IOException(e); + } + } + } + } + + public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { + writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index e2ee8122..636bbb00 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -9,8 +9,8 @@ import android.media.MediaFormat; import android.os.IBinder; import android.view.Surface; +import java.io.FileDescriptor; import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, OutputStream outputStream) throws IOException { + public void streamScreen(Device device, FileDescriptor fd) throws IOException { MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); device.setRotationListener(this); boolean alive; @@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener { setDisplaySurface(display, surface, contentRect, videoRect); codec.start(); try { - alive = encode(codec, outputStream); + alive = encode(codec, fd); } finally { codec.stop(); destroyDisplay(display); @@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener { } } - private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException { - @SuppressWarnings("checkstyle:MagicNumber") - byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video + private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (!consumeRotationChange() && !eof) { @@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener { break; } if (outputBufferId >= 0) { - ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); - while (outputBuffer.hasRemaining()) { - int remaining = outputBuffer.remaining(); - int len = Math.min(buf.length, remaining); - // the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels, - // so we must copy the data locally to write them manually to the output stream - outputBuffer.get(buf, 0, len); - outputStream.write(buf, 0, len); - } + ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); + IO.writeFully(fd, codecBuffer); } } finally { if (outputBufferId >= 0) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9fd93863..b218e83d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -21,7 +21,7 @@ public final class Server { try { // synchronous - screenEncoder.streamScreen(device, connection.getOutputStream()); + screenEncoder.streamScreen(device, connection.getFd()); } catch (IOException e) { // this is expected on close Ln.d("Screen streaming stopped"); -- GitLab