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:
parent
a60aef5aaf
commit
66def38b73
4 changed files with 44 additions and 22 deletions
|
@ -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 {
|
||||
|
|
31
server/src/main/java/com/genymobile/scrcpy/IO.java
Normal file
31
server/src/main/java/com/genymobile/scrcpy/IO.java
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue