Avoid additional buffer copy in userspace

Directly send the data from MediaCodec buffers to the LocalSocket,
without an intermediate copy in userspace.
This commit is contained in:
Romain Vimont 2018-09-09 15:01:55 +02:00
parent a60aef5aaf
commit 66def38b73
4 changed files with 44 additions and 22 deletions

View file

@ -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 {

View file

@ -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));
}
}

View file

@ -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) {

View file

@ -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");