Use different sockets for video and control

The socket used the device-to-computer direction to stream the video and
the computer-to-device direction to send control events.

Some features, like copy-paste from device to computer, require to send
non-video data from the device to the computer.

To make them possible, use two sockets:
 - one for streaming the video from the device to the client;
 - one for control/events in both direction.
This commit is contained in:
Romain Vimont 2019-05-28 21:03:54 +02:00
parent 2a1bdd0af3
commit f88345de0a
5 changed files with 76 additions and 35 deletions

View file

@ -296,15 +296,13 @@ scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server; goto finally_destroy_server;
} }
socket_t device_socket = server.device_socket;
char device_name[DEVICE_NAME_FIELD_LENGTH]; char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size; struct size frame_size;
// screenrecord does not send frames when the screen content does not // screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream, // change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately // to be able to init the window immediately
if (!device_read_info(device_socket, device_name, &frame_size)) { if (!device_read_info(server.video_socket, device_name, &frame_size)) {
server_stop(&server); server_stop(&server);
ret = false; ret = false;
goto finally_destroy_server; goto finally_destroy_server;
@ -343,7 +341,7 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, device_socket, dec, rec); stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
// start the stream // start the stream
@ -355,7 +353,7 @@ scrcpy(const struct scrcpy_options *options) {
if (display) { if (display) {
if (control) { if (control) {
if (!controller_init(&controller, device_socket)) { if (!controller_init(&controller, server.control_socket)) {
ret = false; ret = false;
goto finally_stop_stream; goto finally_stop_stream;
} }

View file

@ -222,8 +222,14 @@ server_start(struct server *server, const char *serial,
bool bool
server_connect_to(struct server *server) { server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket); server->video_socket = net_accept(server->server_socket);
if (server->device_socket == INVALID_SOCKET) { if (server->video_socket == INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be clean up on destroy
return false; return false;
} }
@ -232,9 +238,16 @@ server_connect_to(struct server *server) {
} else { } else {
uint32_t attempts = 100; uint32_t attempts = 100;
uint32_t delay = 100; // ms uint32_t delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, server->video_socket =
delay); connect_to_server(server->local_port, attempts, delay);
if (server->device_socket == INVALID_SOCKET) { if (server->video_socket == INVALID_SOCKET) {
return false;
}
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
return false; return false;
} }
} }
@ -268,8 +281,11 @@ server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) { if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
if (server->device_socket != INVALID_SOCKET) { if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->device_socket); close_socket(&server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
} }
SDL_free(server->serial); SDL_free(server->serial);
} }

View file

@ -11,7 +11,8 @@ struct server {
char *serial; char *serial;
process_t process; process_t process;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t device_socket; socket_t video_socket;
socket_t control_socket;
uint16_t local_port; uint16_t local_port;
bool tunnel_enabled; bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" bool tunnel_forward; // use "adb forward" instead of "adb reverse"
@ -22,7 +23,8 @@ struct server {
.serial = NULL, \ .serial = NULL, \
.process = PROCESS_NONE, \ .process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.local_port = 0, \ .local_port = 0, \
.tunnel_enabled = false, \ .tunnel_enabled = false, \
.tunnel_forward = false, \ .tunnel_forward = false, \

View file

@ -16,16 +16,20 @@ public final class DesktopConnection implements Closeable {
private static final String SOCKET_NAME = "scrcpy"; private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket socket; private final LocalSocket videoSocket;
private final InputStream inputStream; private final FileDescriptor videoFd;
private final FileDescriptor fd;
private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final ControlEventReader reader = new ControlEventReader(); private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException { private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
this.socket = socket; this.videoSocket = videoSocket;
inputStream = socket.getInputStream(); this.controlSocket = controlSocket;
fd = socket.getFileDescriptor(); controlInputStream = controlSocket.getInputStream();
videoFd = videoSocket.getFileDescriptor();
} }
private static LocalSocket connect(String abstractName) throws IOException { private static LocalSocket connect(String abstractName) throws IOException {
@ -44,25 +48,46 @@ public final class DesktopConnection implements Closeable {
} }
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket socket; LocalSocket videoSocket;
LocalSocket controlSocket;
if (tunnelForward) { if (tunnelForward) {
socket = listenAndAccept(SOCKET_NAME); LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
// send one byte so the client may read() to detect a connection error try {
socket.getOutputStream().write(0); videoSocket = localServerSocket.accept();
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
try {
controlSocket = localServerSocket.accept();
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} finally {
localServerSocket.close();
}
} else { } else {
socket = connect(SOCKET_NAME); videoSocket = connect(SOCKET_NAME);
try {
controlSocket = connect(SOCKET_NAME);
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} }
DesktopConnection connection = new DesktopConnection(socket); DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
Size videoSize = device.getScreenInfo().getVideoSize(); Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection; return connection;
} }
public void close() throws IOException { public void close() throws IOException {
socket.shutdownInput(); videoSocket.shutdownInput();
socket.shutdownOutput(); videoSocket.shutdownOutput();
socket.close(); videoSocket.close();
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
controlSocket.close();
} }
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
@ -78,17 +103,17 @@ public final class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
IO.writeFully(fd, buffer, 0, buffer.length); IO.writeFully(videoFd, buffer, 0, buffer.length);
} }
public FileDescriptor getFd() { public FileDescriptor getVideoFd() {
return fd; return videoFd;
} }
public ControlEvent receiveControlEvent() throws IOException { public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next(); ControlEvent event = reader.next();
while (event == null) { while (event == null) {
reader.readFrom(inputStream); reader.readFrom(controlInputStream);
event = reader.next(); event = reader.next();
} }
return event; return event;

View file

@ -24,7 +24,7 @@ public final class Server {
try { try {
// synchronous // synchronous
screenEncoder.streamScreen(device, connection.getFd()); screenEncoder.streamScreen(device, connection.getVideoFd());
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");