Compare commits

...

7 Commits

Author SHA1 Message Date
Romain Vimont
72c83b2ac2 Format meson.build for readability 2019-05-28 21:04:03 +02:00
Romain Vimont
7dcac5b8b3 Replace SDL_bool by bool in tests
Commit dfed1b250e5aada1c0b4c75fbaf54aebc19a92d5 replaced SDL types by
standard types in sources, but tests were not updated.
2019-05-28 21:04:03 +02:00
Romain Vimont
f88345de0a 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.
2019-05-28 21:03:54 +02:00
Romain Vimont
2a1bdd0af3 Use net_recv() to read only one byte
Partial read is impossible for 1 byte, so net_recv_all() is useless.
2019-05-28 21:03:44 +02:00
Romain Vimont
2a7fee6c16 Simplify server_connect_to()
Only use 2 branches, using either forward or remote tunnel.
2019-05-28 21:02:57 +02:00
Romain Vimont
92f0266704 Make server_connect_to() return a bool
The resulting socket is accessible from the server instance, there is no
need to return it.

This paves the way to use several sockets in parallel.
2019-05-28 13:43:34 +02:00
Romain Vimont
6cf134f31f Fix indentation
Previous refactorings broke indentation.
2019-05-28 13:37:27 +02:00
7 changed files with 126 additions and 68 deletions

View File

@ -143,18 +143,34 @@ else
link_args = []
endif
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
executable('scrcpy', src,
dependencies: dependencies,
include_directories: src_dir,
install: true,
c_args: c_args,
link_args: link_args)
### TESTS
tests = [
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']],
['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']],
['test_control_event_queue', [
'tests/test_control_event_queue.c',
'src/control_event.c'
]],
['test_control_event_serialize', [
'tests/test_control_event_serialize.c',
'src/control_event.c'
]],
['test_strutil', [
'tests/test_strutil.c',
'src/str_util.c'
]],
]
foreach t : tests
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies)
test(t[0], exe)
endforeach

View File

@ -290,8 +290,7 @@ scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server;
}
socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) {
if (!server_connect_to(&server)) {
server_stop(&server);
ret = false;
goto finally_destroy_server;
@ -303,7 +302,7 @@ scrcpy(const struct scrcpy_options *options) {
// 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)) {
server_stop(&server);
ret = false;
goto finally_destroy_server;
@ -342,7 +341,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
@ -354,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)) {
ret = false;
goto finally_stop_stream;
}

View File

@ -119,7 +119,7 @@ connect_and_read_byte(uint16_t port) {
char byte;
// the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection
if (net_recv_all(socket, &byte, 1) != 1) {
if (net_recv(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
return INVALID_SOCKET;
}
@ -219,31 +219,44 @@ server_start(struct server *server, const char *serial,
return true;
}
socket_t
bool
server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_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;
}
// we don't need the server socket anymore
close_socket(&server->server_socket);
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts,
delay);
}
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
if (server->device_socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
if (!server->tunnel_forward) {
// we don't need the server socket anymore
close_socket(&server->server_socket);
// 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;
}
}
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
return server->device_socket;
return true;
}
void
@ -268,8 +281,11 @@ server_destroy(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_free(server->serial);
}

View File

@ -11,19 +11,21 @@ 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"
bool send_frame_meta; // request frame PTS to be able to record properly
};
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \
.local_port = 0, \
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
.send_frame_meta = false, \
@ -36,11 +38,11 @@ server_init(struct server *server);
// push, enable tunnel et start the server
bool
server_start(struct server *server, const char *serial,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta);
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta);
// block until the communication with the server is established
socket_t
bool
server_connect_to(struct server *server);
// disconnect and kill the server process

View File

@ -5,21 +5,21 @@
static void test_control_event_queue_empty(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(control_event_queue_is_empty(&queue));
struct control_event dummy_event;
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
bool push_ok = control_event_queue_push(&queue, &dummy_event);
assert(push_ok);
assert(!control_event_queue_is_empty(&queue));
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(control_event_queue_is_empty(&queue));
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
assert(!take_empty_ok); // the queue is empty
control_event_queue_destroy(&queue);
@ -27,7 +27,7 @@ static void test_control_event_queue_empty(void) {
static void test_control_event_queue_full(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(!control_event_queue_is_full(&queue));
@ -36,7 +36,7 @@ static void test_control_event_queue_full(void) {
// fill the queue
while (control_event_queue_push(&queue, &dummy_event));
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(!control_event_queue_is_full(&queue));
@ -45,7 +45,7 @@ static void test_control_event_queue_full(void) {
static void test_control_event_queue_push_take(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
struct control_event event = {
@ -57,7 +57,7 @@ static void test_control_event_queue_push_take(void) {
},
};
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
bool push1_ok = control_event_queue_push(&queue, &event);
assert(push1_ok);
event = (struct control_event) {
@ -67,11 +67,11 @@ static void test_control_event_queue_push_take(void) {
},
};
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
bool push2_ok = control_event_queue_push(&queue, &event);
assert(push2_ok);
// overwrite event
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
bool take1_ok = control_event_queue_take(&queue, &event);
assert(take1_ok);
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
@ -79,7 +79,7 @@ static void test_control_event_queue_push_take(void) {
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
// overwrite event
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
bool take2_ok = control_event_queue_take(&queue, &event);
assert(take2_ok);
assert(event.type == CONTROL_EVENT_TYPE_TEXT);
assert(!strcmp(event.text_event.text, "abc"));

View File

@ -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 {
@ -44,25 +48,46 @@ public final class DesktopConnection implements Closeable {
}
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")
@ -78,17 +103,17 @@ 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;
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;

View File

@ -24,7 +24,7 @@ public final class Server {
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");