Skip to content

Commit

Permalink
Use two sockets for video and control
Browse files Browse the repository at this point in the history
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 directions.
  • Loading branch information
rom1v committed May 30, 2019
1 parent 69360c7 commit ec71a3f
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 49 deletions.
6 changes: 3 additions & 3 deletions app/src/controller.c
Expand Up @@ -7,7 +7,7 @@
#include "log.h"

bool
controller_init(struct controller *controller, socket_t video_socket) {
controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue);

if (!(controller->mutex = SDL_CreateMutex())) {
Expand All @@ -19,7 +19,7 @@ controller_init(struct controller *controller, socket_t video_socket) {
return false;
}

controller->video_socket = video_socket;
controller->control_socket = control_socket;
controller->stopped = false;

return true;
Expand Down Expand Up @@ -57,7 +57,7 @@ process_event(struct controller *controller,
if (!length) {
return false;
}
int w = net_send_all(controller->video_socket, serialized_event, length);
int w = net_send_all(controller->control_socket, serialized_event, length);
return w == length;
}

Expand Down
4 changes: 2 additions & 2 deletions app/src/controller.h
Expand Up @@ -12,7 +12,7 @@
struct control_event_queue CBUF(struct control_event, 64);

struct controller {
socket_t video_socket;
socket_t control_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
Expand All @@ -21,7 +21,7 @@ struct controller {
};

bool
controller_init(struct controller *controller, socket_t video_socket);
controller_init(struct controller *controller, socket_t control_socket);

void
controller_destroy(struct controller *controller);
Expand Down
8 changes: 3 additions & 5 deletions app/src/scrcpy.c
Expand Up @@ -300,15 +300,13 @@ scrcpy(const struct scrcpy_options *options) {
goto end;
}

socket_t device_socket = server.device_socket;

char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size;

// screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream,
// 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)) {
goto end;
}

Expand Down Expand Up @@ -344,7 +342,7 @@ scrcpy(const struct scrcpy_options *options) {

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
// start the stream
Expand All @@ -355,7 +353,7 @@ scrcpy(const struct scrcpy_options *options) {

if (display) {
if (control) {
if (!controller_init(&controller, device_socket)) {
if (!controller_init(&controller, server.control_socket)) {
goto end;
}

Expand Down
30 changes: 23 additions & 7 deletions app/src/server.c
Expand Up @@ -222,8 +222,14 @@ server_start(struct server *server, const char *serial,
bool
server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
if (server->device_socket == INVALID_SOCKET) {
server->video_socket = net_accept(server->server_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;
}

Expand All @@ -232,9 +238,16 @@ server_connect_to(struct server *server) {
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts,
delay);
if (server->device_socket == INVALID_SOCKET) {
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
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;
}
}
Expand All @@ -251,8 +264,11 @@ server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket);
}
if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket);
if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
}

SDL_assert(server->process != PROCESS_NONE);
Expand Down
6 changes: 4 additions & 2 deletions app/src/server.h
Expand Up @@ -11,7 +11,8 @@ struct server {
char *serial;
process_t process;
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;
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
Expand All @@ -22,7 +23,8 @@ struct server {
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
Expand Down
74 changes: 45 additions & 29 deletions server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
Expand Up @@ -16,16 +16,20 @@ public final class DesktopConnection implements Closeable {

private static final String SOCKET_NAME = "scrcpy";

private final LocalSocket socket;
private final InputStream inputStream;
private final FileDescriptor fd;
private final LocalSocket videoSocket;
private final FileDescriptor videoFd;

private final LocalSocket controlSocket;
private final InputStream controlInputStream;


private final ControlEventReader reader = new ControlEventReader();

private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
fd = socket.getFileDescriptor();
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket;
this.controlSocket = controlSocket;
controlInputStream = controlSocket.getInputStream();
videoFd = videoSocket.getFileDescriptor();
}

private static LocalSocket connect(String abstractName) throws IOException {
Expand All @@ -34,35 +38,47 @@ private static LocalSocket connect(String abstractName) throws IOException {
return localSocket;
}

private static LocalSocket listenAndAccept(String abstractName) throws IOException {
LocalServerSocket localServerSocket = new LocalServerSocket(abstractName);
try {
return localServerSocket.accept();
} finally {
localServerSocket.close();
}
}

public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket socket;
LocalSocket videoSocket;
LocalSocket controlSocket;
if (tunnelForward) {
socket = listenAndAccept(SOCKET_NAME);
// send one byte so the client may read() to detect a connection error
socket.getOutputStream().write(0);
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
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 {
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();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection;
}

public void close() throws IOException {
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
controlSocket.close();
}

@SuppressWarnings("checkstyle:MagicNumber")
Expand All @@ -78,17 +94,17 @@ private void send(String deviceName, int width, int height) throws IOException {
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;
IO.writeFully(fd, buffer, 0, buffer.length);
IO.writeFully(videoFd, buffer, 0, buffer.length);
}

public FileDescriptor getFd() {
return fd;
public FileDescriptor getVideoFd() {
return videoFd;
}

public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next();
while (event == null) {
reader.readFrom(inputStream);
reader.readFrom(controlInputStream);
event = reader.next();
}
return event;
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/com/genymobile/scrcpy/Server.java
Expand Up @@ -24,7 +24,7 @@ private static void scrcpy(Options options) throws IOException {

try {
// synchronous
screenEncoder.streamScreen(device, connection.getFd());
screenEncoder.streamScreen(device, connection.getVideoFd());
} catch (IOException e) {
// this is expected on close
Ln.d("Screen streaming stopped");
Expand Down

0 comments on commit ec71a3f

Please sign in to comment.