Skip to content

Commit 66def38

Browse files
committed
Avoid additional buffer copy in userspace
Directly send the data from MediaCodec buffers to the LocalSocket, without an intermediate copy in userspace.
1 parent a60aef5 commit 66def38

File tree

4 files changed

+44
-22
lines changed

4 files changed

+44
-22
lines changed

server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import android.net.LocalSocketAddress;
66

77
import java.io.Closeable;
8+
import java.io.FileDescriptor;
89
import java.io.IOException;
910
import java.io.InputStream;
10-
import java.io.OutputStream;
1111
import java.nio.charset.StandardCharsets;
1212

1313
public final class DesktopConnection implements Closeable {
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
1818

1919
private final LocalSocket socket;
2020
private final InputStream inputStream;
21-
private final OutputStream outputStream;
21+
private final FileDescriptor fd;
2222

2323
private final ControlEventReader reader = new ControlEventReader();
2424

2525
private DesktopConnection(LocalSocket socket) throws IOException {
2626
this.socket = socket;
2727
inputStream = socket.getInputStream();
28-
outputStream = socket.getOutputStream();
28+
fd = socket.getFileDescriptor();
2929
}
3030

3131
private static LocalSocket connect(String abstractName) throws IOException {
@@ -78,11 +78,11 @@ private void send(String deviceName, int width, int height) throws IOException {
7878
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
7979
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
8080
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
81-
outputStream.write(buffer, 0, buffer.length);
81+
IO.writeFully(fd, buffer, 0, buffer.length);
8282
}
8383

84-
public OutputStream getOutputStream() {
85-
return outputStream;
84+
public FileDescriptor getFd() {
85+
return fd;
8686
}
8787

8888
public ControlEvent receiveControlEvent() throws IOException {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.genymobile.scrcpy;
2+
3+
import android.system.ErrnoException;
4+
import android.system.Os;
5+
import android.system.OsConstants;
6+
7+
import java.io.FileDescriptor;
8+
import java.io.IOException;
9+
import java.nio.ByteBuffer;
10+
11+
public class IO {
12+
private IO() {
13+
// not instantiable
14+
}
15+
16+
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
17+
while (from.hasRemaining()) {
18+
try {
19+
Os.write(fd, from);
20+
} catch (ErrnoException e) {
21+
if (e.errno != OsConstants.EINTR) {
22+
throw new IOException(e);
23+
}
24+
}
25+
}
26+
}
27+
28+
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
29+
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
30+
}
31+
}

server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import android.os.IBinder;
1010
import android.view.Surface;
1111

12+
import java.io.FileDescriptor;
1213
import java.io.IOException;
13-
import java.io.OutputStream;
1414
import java.nio.ByteBuffer;
1515
import java.util.concurrent.atomic.AtomicBoolean;
1616

@@ -48,7 +48,7 @@ public boolean consumeRotationChange() {
4848
return rotationChanged.getAndSet(false);
4949
}
5050

51-
public void streamScreen(Device device, OutputStream outputStream) throws IOException {
51+
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
5252
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
5353
device.setRotationListener(this);
5454
boolean alive;
@@ -64,7 +64,7 @@ public void streamScreen(Device device, OutputStream outputStream) throws IOExce
6464
setDisplaySurface(display, surface, contentRect, videoRect);
6565
codec.start();
6666
try {
67-
alive = encode(codec, outputStream);
67+
alive = encode(codec, fd);
6868
} finally {
6969
codec.stop();
7070
destroyDisplay(display);
@@ -77,9 +77,7 @@ public void streamScreen(Device device, OutputStream outputStream) throws IOExce
7777
}
7878
}
7979

80-
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
81-
@SuppressWarnings("checkstyle:MagicNumber")
82-
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
80+
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
8381
boolean eof = false;
8482
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
8583
while (!consumeRotationChange() && !eof) {
@@ -91,15 +89,8 @@ private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOExc
9189
break;
9290
}
9391
if (outputBufferId >= 0) {
94-
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
95-
while (outputBuffer.hasRemaining()) {
96-
int remaining = outputBuffer.remaining();
97-
int len = Math.min(buf.length, remaining);
98-
// the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels,
99-
// so we must copy the data locally to write them manually to the output stream
100-
outputBuffer.get(buf, 0, len);
101-
outputStream.write(buf, 0, len);
102-
}
92+
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
93+
IO.writeFully(fd, codecBuffer);
10394
}
10495
} finally {
10596
if (outputBufferId >= 0) {

server/src/main/java/com/genymobile/scrcpy/Server.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private static void scrcpy(Options options) throws IOException {
2121

2222
try {
2323
// synchronous
24-
screenEncoder.streamScreen(device, connection.getOutputStream());
24+
screenEncoder.streamScreen(device, connection.getFd());
2525
} catch (IOException e) {
2626
// this is expected on close
2727
Ln.d("Screen streaming stopped");

0 commit comments

Comments
 (0)