Merge branch 'dev'

This commit is contained in:
Romain Vimont 2019-03-07 19:02:40 +01:00
commit 7fad611dfb
54 changed files with 1934 additions and 1205 deletions

View file

@ -165,6 +165,14 @@ scrcpy --record file.mp4
scrcpy -r file.mkv scrcpy -r file.mkv
``` ```
To disable mirroring while recording:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
```
"Skipped frames" are recorded, even if they are not displayed in real time (for "Skipped frames" are recorded, even if they are not displayed in real time (for
performance reasons). Frames are _timestamped_ on the device, so [packet delay performance reasons). Frames are _timestamped_ on the device, so [packet delay
variation] does not impact the recorded file. variation] does not impact the recorded file.
@ -239,6 +247,17 @@ _scrcpy_ window.
There is no visual feedback, a log is printed to the console. There is no visual feedback, a log is printed to the console.
### Read-only
To disable controls (everything which can interact with the device: input keys,
mouse events, drag&drop files):
```bash
scrcpy --no-control
scrcpy -n
```
### Forward audio ### Forward audio
Audio is not forwarded by _scrcpy_. Audio is not forwarded by _scrcpy_.
@ -267,6 +286,8 @@ you are interested, see [issue 14].
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) | | click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `POWER` | `Ctrl`+`p` | | click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ | | turn screen on | _Right-click²_ |
| expand notification panel | `Ctrl`+`n` |
| collapse notification panel | `Ctrl`+`Shift`+`n` |
| paste computer clipboard to device | `Ctrl`+`v` | | paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` |

View file

@ -8,7 +8,6 @@ src = [
'src/device.c', 'src/device.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frames.c',
'src/input_manager.c', 'src/input_manager.c',
'src/lock_util.c', 'src/lock_util.c',
'src/net.c', 'src/net.c',
@ -18,6 +17,8 @@ src = [
'src/server.c', 'src/server.c',
'src/str_util.c', 'src/str_util.c',
'src/tiny_xpm.c', 'src/tiny_xpm.c',
'src/stream.c',
'src/video_buffer.c',
] ]
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')

View file

@ -1,28 +1,33 @@
#ifndef BUFFER_UTIL_H #ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H #define BUFFER_UTIL_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include <stdint.h>
static inline void buffer_write16be(Uint8 *buf, Uint16 value) { static inline void
buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
buf[1] = value; buf[1] = value;
} }
static inline void buffer_write32be(Uint8 *buf, Uint32 value) { static inline void
buffer_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24; buf[0] = value >> 24;
buf[1] = value >> 16; buf[1] = value >> 16;
buf[2] = value >> 8; buf[2] = value >> 8;
buf[3] = value; buf[3] = value;
} }
static inline Uint32 buffer_read32be(Uint8 *buf) { static inline uint32_t
buffer_read32be(uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline Uint64 buffer_read64be(Uint8 *buf) { static inline
Uint32 msb = buffer_read32be(buf); uint64_t buffer_read64be(uint8_t *buf) {
Uint32 lsb = buffer_read32be(&buf[4]); uint32_t msb = buffer_read32be(buf);
return ((Uint64) msb << 32) | lsb; uint32_t lsb = buffer_read32be(&buf[4]);
return ((uint64_t) msb << 32) | lsb;
} }
#endif #endif

View file

@ -10,7 +10,8 @@
static const char *adb_command; static const char *adb_command;
static inline const char *get_adb_command(void) { static inline const char *
get_adb_command(void) {
if (!adb_command) { if (!adb_command) {
adb_command = getenv("ADB"); adb_command = getenv("ADB");
if (!adb_command) if (!adb_command)
@ -19,7 +20,8 @@ static inline const char *get_adb_command(void) {
return adb_command; return adb_command;
} }
static void show_adb_err_msg(enum process_result err) { static void
show_adb_err_msg(enum process_result err) {
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb"); LOGE("Failed to execute adb");
@ -34,7 +36,8 @@ static void show_adb_err_msg(enum process_result err) {
} }
} }
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) { process_t
adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4]; const char *cmd[len + 4];
int i; int i;
process_t process; process_t process;
@ -57,7 +60,9 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
return process; return process;
} }
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@ -66,14 +71,17 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t adb_forward_remove(const char *serial, uint16_t local_port) { process_t
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local}; const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@ -82,14 +90,16 @@ process_t adb_reverse(const char *serial, const char *device_socket_name, uint16
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t adb_reverse_remove(const char *serial, const char *device_socket_name) { process_t
adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote}; const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t adb_push(const char *serial, const char *local, const char *remote) { process_t
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@ -115,7 +125,8 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
return proc; return proc;
} }
process_t adb_install(const char *serial, const char *local) { process_t
adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@ -135,10 +146,11 @@ process_t adb_install(const char *serial, const char *local) {
return proc; return proc;
} }
SDL_bool process_check_success(process_t proc, const char *name) { bool
process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return SDL_FALSE; return false;
} }
exit_code_t exit_code; exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) { if (!cmd_simple_wait(proc, &exit_code)) {
@ -147,7 +159,7 @@ SDL_bool process_check_success(process_t proc, const char *name) {
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }

View file

@ -1,12 +1,13 @@
#ifndef COMMAND_H #ifndef COMMAND_H
#define COMMAND_H #define COMMAND_H
#include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include <SDL2/SDL_stdinc.h>
#ifdef _WIN32 #ifdef _WIN32
# include <winsock2.h> // not needed here, but must never be included AFTER windows.h // not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h> # include <windows.h>
# define PRIexitcode "lu" # define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
@ -38,20 +39,41 @@ enum process_result {
PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process); enum process_result
SDL_bool cmd_terminate(process_t pid); cmd_execute(const char *path, const char *const argv[], process_t *process);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len); bool
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name); cmd_terminate(process_t pid);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port); bool
process_t adb_reverse_remove(const char *serial, const char *device_socket_name); cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t adb_push(const char *serial, const char *local, const char *remote);
process_t adb_install(const char *serial, const char *local); process_t
adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
SDL_bool process_check_success(process_t process, const char *name); bool
process_check_success(process_t process, const char *name);
#endif #endif

View file

@ -1,25 +1,26 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include <SDL2/SDL_stdinc.h> #include <stdint.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
struct size { struct size {
Uint16 width; uint16_t width;
Uint16 height; uint16_t height;
}; };
struct point { struct point {
Sint32 x; int32_t x;
Sint32 y; int32_t y;
}; };
struct position { struct position {
// The video screen size may be different from the real device screen size, // The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it accordingly. // so store to which size the absolute position apply, to scale it
// accordingly.
struct size screen_size; struct size screen_size;
struct point point; struct point point;
}; };

View file

@ -1,20 +1,21 @@
#include "control_event.h" #include "control_event.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h> #include <string.h>
#include "buffer_util.h" #include "buffer_util.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
static void write_position(Uint8 *buf, const struct position *position) { static void
write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y); buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height); buffer_write16be(&buf[10], position->screen_size.height);
} }
int control_event_serialize(const struct control_event *event, unsigned char *buf) { int
control_event_serialize(const struct control_event *event, unsigned char *buf) {
buf[0] = event->type; buf[0] = event->type;
switch (event->type) { switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE: case CONTROL_EVENT_TYPE_KEYCODE:
@ -29,7 +30,7 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
// injecting a text takes time, so limit the text length // injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH; len = TEXT_MAX_LENGTH;
} }
buffer_write16be(&buf[1], (Uint16) len); buffer_write16be(&buf[1], (uint16_t) len);
memcpy(&buf[3], event->text_event.text, len); memcpy(&buf[3], event->text_event.text, len);
return 3 + len; return 3 + len;
} }
@ -40,8 +41,8 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
return 18; return 18;
case CONTROL_EVENT_TYPE_SCROLL: case CONTROL_EVENT_TYPE_SCROLL:
write_position(&buf[1], &event->scroll_event.position); write_position(&buf[1], &event->scroll_event.position);
buffer_write32be(&buf[13], (Uint32) event->scroll_event.hscroll); buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll);
buffer_write32be(&buf[17], (Uint32) event->scroll_event.vscroll); buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll);
return 21; return 21;
case CONTROL_EVENT_TYPE_COMMAND: case CONTROL_EVENT_TYPE_COMMAND:
buf[1] = event->command_event.action; buf[1] = event->command_event.action;
@ -52,28 +53,33 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
} }
} }
void control_event_destroy(struct control_event *event) { void
control_event_destroy(struct control_event *event) {
if (event->type == CONTROL_EVENT_TYPE_TEXT) { if (event->type == CONTROL_EVENT_TYPE_TEXT) {
SDL_free(event->text_event.text); SDL_free(event->text_event.text);
} }
} }
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) { bool
control_event_queue_is_empty(const struct control_event_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
SDL_bool control_event_queue_is_full(const struct control_event_queue *queue) { bool
control_event_queue_is_full(const struct control_event_queue *queue) {
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail; return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
} }
SDL_bool control_event_queue_init(struct control_event_queue *queue) { bool
control_event_queue_init(struct control_event_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
// the current implementation may not fail // the current implementation may not fail
return SDL_TRUE; return true;
} }
void control_event_queue_destroy(struct control_event_queue *queue) { void
control_event_queue_destroy(struct control_event_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
control_event_destroy(&queue->data[i]); control_event_destroy(&queue->data[i]);
@ -81,20 +87,24 @@ void control_event_queue_destroy(struct control_event_queue *queue) {
} }
} }
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) { bool
control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event) {
if (control_event_queue_is_full(queue)) { if (control_event_queue_is_full(queue)) {
return SDL_FALSE; return false;
} }
queue->data[queue->head] = *event; queue->data[queue->head] = *event;
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
return SDL_TRUE; return true;
} }
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) { bool
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event) {
if (control_event_queue_is_empty(queue)) { if (control_event_queue_is_empty(queue)) {
return SDL_FALSE; return false;
} }
*event = queue->data[queue->tail]; *event = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
return SDL_TRUE; return true;
} }

View file

@ -1,8 +1,9 @@
#ifndef CONTROLEVENT_H #ifndef CONTROLEVENT_H
#define CONTROLEVENT_H #define CONTROLEVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
@ -20,7 +21,11 @@ enum control_event_type {
CONTROL_EVENT_TYPE_COMMAND, CONTROL_EVENT_TYPE_COMMAND,
}; };
#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0 enum control_event_command {
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON,
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL,
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL,
};
struct control_event { struct control_event {
enum control_event_type type; enum control_event_type type;
@ -40,11 +45,11 @@ struct control_event {
} mouse_event; } mouse_event;
struct { struct {
struct position position; struct position position;
Sint32 hscroll; int32_t hscroll;
Sint32 vscroll; int32_t vscroll;
} scroll_event; } scroll_event;
struct { struct {
int action; enum control_event_command action;
} command_event; } command_event;
}; };
}; };
@ -56,18 +61,31 @@ struct control_event_queue {
}; };
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE // buf size must be at least SERIALIZED_EVENT_MAX_SIZE
int control_event_serialize(const struct control_event *event, unsigned char *buf); int
control_event_serialize(const struct control_event *event, unsigned char *buf);
SDL_bool control_event_queue_init(struct control_event_queue *queue); bool
void control_event_queue_destroy(struct control_event_queue *queue); control_event_queue_init(struct control_event_queue *queue);
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue); void
SDL_bool control_event_queue_is_full(const struct control_event_queue *queue); control_event_queue_destroy(struct control_event_queue *queue);
bool
control_event_queue_is_empty(const struct control_event_queue *queue);
bool
control_event_queue_is_full(const struct control_event_queue *queue);
// event is copied, the queue does not use the event after the function returns // event is copied, the queue does not use the event after the function returns
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event); bool
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event); control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event);
void control_event_destroy(struct control_event *event); bool
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event);
void
control_event_destroy(struct control_event *event);
#endif #endif

View file

@ -1,40 +1,45 @@
#include "controller.h" #include "controller.h"
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h" #include "config.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
SDL_bool controller_init(struct controller *controller, socket_t video_socket) { bool
controller_init(struct controller *controller, socket_t video_socket) {
if (!control_event_queue_init(&controller->queue)) { if (!control_event_queue_init(&controller->queue)) {
return SDL_FALSE; return false;
} }
if (!(controller->mutex = SDL_CreateMutex())) { if (!(controller->mutex = SDL_CreateMutex())) {
return SDL_FALSE; return false;
} }
if (!(controller->event_cond = SDL_CreateCond())) { if (!(controller->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
return SDL_FALSE; return false;
} }
controller->video_socket = video_socket; controller->video_socket = video_socket;
controller->stopped = SDL_FALSE; controller->stopped = false;
return SDL_TRUE; return true;
} }
void controller_destroy(struct controller *controller) { void
controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->event_cond); SDL_DestroyCond(controller->event_cond);
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
control_event_queue_destroy(&controller->queue); control_event_queue_destroy(&controller->queue);
} }
SDL_bool controller_push_event(struct controller *controller, const struct control_event *event) { bool
SDL_bool res; controller_push_event(struct controller *controller,
const struct control_event *event) {
bool res;
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
SDL_bool was_empty = control_event_queue_is_empty(&controller->queue); bool was_empty = control_event_queue_is_empty(&controller->queue);
res = control_event_queue_push(&controller->queue, event); res = control_event_queue_push(&controller->queue, event);
if (was_empty) { if (was_empty) {
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
@ -43,22 +48,26 @@ SDL_bool controller_push_event(struct controller *controller, const struct contr
return res; return res;
} }
static SDL_bool process_event(struct controller *controller, const struct control_event *event) { static bool
process_event(struct controller *controller,
const struct control_event *event) {
unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE]; unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE];
int length = control_event_serialize(event, serialized_event); int length = control_event_serialize(event, serialized_event);
if (!length) { if (!length) {
return SDL_FALSE; return false;
} }
int w = net_send_all(controller->video_socket, serialized_event, length); int w = net_send_all(controller->video_socket, serialized_event, length);
return w == length; return w == length;
} }
static int run_controller(void *data) { static int
run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) { while (!controller->stopped
&& control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex); cond_wait(controller->event_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
@ -67,11 +76,12 @@ static int run_controller(void *data) {
break; break;
} }
struct control_event event; struct control_event event;
SDL_bool non_empty = control_event_queue_take(&controller->queue, &event); bool non_empty = control_event_queue_take(&controller->queue,
&event);
SDL_assert(non_empty); SDL_assert(non_empty);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
SDL_bool ok = process_event(controller, &event); bool ok = process_event(controller, &event);
control_event_destroy(&event); control_event_destroy(&event);
if (!ok) { if (!ok) {
LOGD("Cannot write event to socket"); LOGD("Cannot write event to socket");
@ -81,25 +91,29 @@ static int run_controller(void *data) {
return 0; return 0;
} }
SDL_bool controller_start(struct controller *controller) { bool
controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", controller); controller->thread = SDL_CreateThread(run_controller, "controller",
controller);
if (!controller->thread) { if (!controller->thread) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }
void controller_stop(struct controller *controller) { void
controller_stop(struct controller *controller) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
controller->stopped = SDL_TRUE; controller->stopped = true;
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
} }
void controller_join(struct controller *controller) { void
controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
} }

View file

@ -3,8 +3,8 @@
#include "control_event.h" #include "control_event.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "net.h" #include "net.h"
@ -14,18 +14,28 @@ struct controller {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
SDL_bool stopped; bool stopped;
struct control_event_queue queue; struct control_event_queue queue;
}; };
SDL_bool controller_init(struct controller *controller, socket_t video_socket); bool
void controller_destroy(struct controller *controller); controller_init(struct controller *controller, socket_t video_socket);
SDL_bool controller_start(struct controller *controller); void
void controller_stop(struct controller *controller); controller_destroy(struct controller *controller);
void controller_join(struct controller *controller);
bool
controller_start(struct controller *controller);
void
controller_stop(struct controller *controller);
void
controller_join(struct controller *controller);
// expose simple API to hide control_event_queue // expose simple API to hide control_event_queue
SDL_bool controller_push_event(struct controller *controller, const struct control_event *event); bool
controller_push_event(struct controller *controller,
const struct control_event *event);
#endif #endif

View file

@ -1,9 +1,10 @@
#include "convert.h" #include "convert.h"
#define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE #define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return SDL_FALSE #define FAIL default: return false
static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { static bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
@ -11,7 +12,8 @@ static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent
} }
} }
static enum android_metastate autocomplete_metastate(enum android_metastate metastate) { static enum android_metastate
autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags // fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON; metastate |= AMETA_SHIFT_ON;
@ -30,7 +32,8 @@ static enum android_metastate autocomplete_metastate(enum android_metastate meta
} }
static enum android_metastate convert_meta_state(SDL_Keymod mod) { static enum android_metastate
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON; metastate |= AMETA_SHIFT_LEFT_ON;
@ -70,7 +73,8 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) { static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
switch (from) { switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@ -88,7 +92,7 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint
MAP(SDLK_UP, AKEYCODE_DPAD_UP); MAP(SDLK_UP, AKEYCODE_DPAD_UP);
} }
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return SDL_FALSE; return false;
} }
// if ALT and META are not pressed, also handle letters and space // if ALT and META are not pressed, also handle letters and space
switch (from) { switch (from) {
@ -123,7 +127,8 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint
} }
} }
static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
@ -131,7 +136,8 @@ static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motioneven
} }
} }
static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) { static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) { if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY; buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
@ -151,31 +157,33 @@ static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
return buttons; return buttons;
} }
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, bool
struct control_event *to) { input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_KEYCODE; to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) { if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
return SDL_FALSE; return false;
} }
Uint16 mod = from->keysym.mod; uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) { if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
return SDL_FALSE; return false;
} }
to->keycode_event.metastate = convert_meta_state(mod); to->keycode_event.metastate = convert_meta_state(mod);
return SDL_TRUE; return true;
} }
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, bool
struct size screen_size, mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct control_event *to) { struct size screen_size,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
if (!convert_mouse_action(from->type, &to->mouse_event.action)) { if (!convert_mouse_action(from->type, &to->mouse_event.action)) {
return SDL_FALSE; return false;
} }
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
@ -183,12 +191,13 @@ SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return SDL_TRUE; return true;
} }
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, bool
struct size screen_size, mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct control_event *to) { struct size screen_size,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->mouse_event.buttons = convert_mouse_buttons(from->state); to->mouse_event.buttons = convert_mouse_buttons(from->state);
@ -196,12 +205,13 @@ SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return SDL_TRUE; return true;
} }
SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, bool
struct position position, mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct control_event *to) { struct position position,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_SCROLL; to->type = CONTROL_EVENT_TYPE_SCROLL;
to->scroll_event.position = position; to->scroll_event.position = position;
@ -213,5 +223,5 @@ SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.hscroll = -mul * from->x; to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * from->y; to->scroll_event.vscroll = mul * from->y;
return SDL_TRUE; return true;
} }

View file

@ -1,8 +1,9 @@
#ifndef CONVERT_H #ifndef CONVERT_H
#define CONVERT_H #define CONVERT_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "control_event.h" #include "control_event.h"
struct complete_mouse_motion_event { struct complete_mouse_motion_event {
@ -15,21 +16,26 @@ struct complete_mouse_wheel_event {
struct point position; struct point position;
}; };
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, bool
struct control_event *to); input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, struct control_event *to);
struct size screen_size,
struct control_event *to);
// the video size may be different from the real device size, so we need the size bool
// to which the absolute position apply, to scale it accordingly mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, struct size screen_size,
struct size screen_size, struct control_event *to);
struct control_event *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to);
// on Android, a scroll event requires the current mouse position // on Android, a scroll event requires the current mouse position
SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, bool
struct position position, mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct control_event *to); struct position position,
struct control_event *to);
#endif #endif

View file

@ -12,130 +12,18 @@
#include "config.h" #include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "events.h" #include "events.h"
#include "frames.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
#include "video_buffer.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static SDL_bool receiver_state_push_meta(struct receiver_state *state,
uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return SDL_FALSE;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return SDL_TRUE;
}
static uint64_t receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
struct receiver_state *state = &decoder->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining)
buf_size = state->remaining;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
// set the decoded frame as ready for rendering, and notify // set the decoded frame as ready for rendering, and notify
static void push_frame(struct decoder *decoder) { static void
SDL_bool previous_frame_consumed = frames_offer_decoded_frame(decoder->frames); push_frame(struct decoder *decoder) {
if (!previous_frame_consumed) { bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame // the previous EVENT_NEW_FRAME will consume this frame
return; return;
} }
@ -145,181 +33,71 @@ static void push_frame(struct decoder *decoder) {
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
static void notify_stopped(void) { void
SDL_Event stop_event; decoder_init(struct decoder *decoder, struct video_buffer *vb) {
stop_event.type = EVENT_DECODER_STOPPED; decoder->video_buffer = vb;
SDL_PushEvent(&stop_event);
} }
static int run_decoder(void *data) { bool
struct decoder *decoder = data; decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec);
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!decoder->codec_ctx) {
if (!codec) {
LOGE("H.264 decoder not found");
goto run_end;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGC("Could not allocate decoder context"); LOGC("Could not allocate decoder context");
goto run_end; return false;
} }
if (avcodec_open2(codec_ctx, codec, NULL) < 0) { if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open H.264 codec"); LOGE("Could not open codec");
goto run_finally_free_codec_ctx; avcodec_free_context(&decoder->codec_ctx);
return false;
} }
AVFormatContext *format_ctx = avformat_alloc_context(); return true;
if (!format_ctx) { }
LOGC("Could not allocate format context");
goto run_finally_close_codec;
}
unsigned char *buffer = av_malloc(BUFSIZE); void
if (!buffer) { decoder_close(struct decoder *decoder) {
LOGC("Could not allocate buffer"); avcodec_close(decoder->codec_ctx);
goto run_finally_free_format_ctx; avcodec_free_context(&decoder->codec_ctx);
} }
// initialize the receiver state bool
decoder->receiver_state.frame_meta_queue = NULL; decoder_push(struct decoder *decoder, const AVPacket *packet) {
decoder->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
decoder->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto run_finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto run_finally_free_avio_ctx;
}
if (decoder->recorder &&
!recorder_open(decoder->recorder, codec)) {
LOGE("Could not open recorder");
goto run_finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
// the new decoding/encoding API has been introduced by: // the new decoding/encoding API has been introduced by:
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726> // <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API #ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
int ret; int ret;
if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) { if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
goto run_quit; return false;
} }
ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame); ret = avcodec_receive_frame(decoder->codec_ctx,
if (!ret) { decoder->video_buffer->decoding_frame);
// a frame was received if (!ret) {
push_frame(decoder); // a frame was received
} else if (ret != AVERROR(EAGAIN)) { push_frame(decoder);
LOGE("Could not receive video frame: %d", ret); } else if (ret != AVERROR(EAGAIN)) {
av_packet_unref(&packet); LOGE("Could not receive video frame: %d", ret);
goto run_quit; return false;
} }
#else #else
while (packet.size > 0) { int got_picture;
int got_picture; int len = avcodec_decode_video2(decoder->codec_ctx,
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet); decoder->video_buffer->decoding_frame,
if (len < 0) { &got_picture,
LOGE("Could not decode video packet: %d", len); packet);
av_packet_unref(&packet); if (len < 0) {
goto run_quit; LOGE("Could not decode video packet: %d", len);
} return false;
if (got_picture) { }
push_frame(decoder); if (got_picture) {
} push_frame(decoder);
packet.size -= len; }
packet.data += len;
}
#endif #endif
return true;
if (decoder->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&decoder->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(decoder->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto run_quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
run_quit:
if (decoder->recorder) {
recorder_close(decoder->recorder);
}
run_finally_close_input:
avformat_close_input(&format_ctx);
run_finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
run_finally_free_format_ctx:
avformat_free_context(format_ctx);
run_finally_close_codec:
avcodec_close(codec_ctx);
run_finally_free_codec_ctx:
avcodec_free_context(&codec_ctx);
notify_stopped();
run_end:
return 0;
} }
void decoder_init(struct decoder *decoder, struct frames *frames, void
socket_t video_socket, struct recorder *recorder) { decoder_interrupt(struct decoder *decoder) {
decoder->frames = frames; video_buffer_interrupt(decoder->video_buffer);
decoder->video_socket = video_socket;
decoder->recorder = recorder;
}
SDL_bool decoder_start(struct decoder *decoder) {
LOGD("Starting decoder thread");
decoder->thread = SDL_CreateThread(run_decoder, "video_decoder", decoder);
if (!decoder->thread) {
LOGC("Could not start decoder thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void decoder_stop(struct decoder *decoder) {
frames_stop(decoder->frames);
}
void decoder_join(struct decoder *decoder) {
SDL_WaitThread(decoder->thread, NULL);
} }

View file

@ -1,36 +1,29 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include <SDL2/SDL_thread.h> #include <libavformat/avformat.h>
#include "common.h" struct video_buffer;
#include "net.h"
struct frames;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
struct decoder { struct decoder {
struct frames *frames; struct video_buffer *video_buffer;
socket_t video_socket; AVCodecContext *codec_ctx;
SDL_Thread *thread;
SDL_mutex *mutex;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
}; };
void decoder_init(struct decoder *decoder, struct frames *frames, void
socket_t video_socket, struct recorder *recoder); decoder_init(struct decoder *decoder, struct video_buffer *vb);
SDL_bool decoder_start(struct decoder *decoder);
void decoder_stop(struct decoder *decoder); bool
void decoder_join(struct decoder *decoder); decoder_open(struct decoder *decoder, const AVCodec *codec);
void
decoder_close(struct decoder *decoder);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View file

@ -1,18 +1,22 @@
#include "device.h" #include "device.h"
#include "log.h" #include "log.h"
SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) { bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf)); int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) { if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information"); LOGE("Could not retrieve device information");
return SDL_FALSE; return false;
} }
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage // in case the client sends garbage
// strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
// and strlen(buf) < DEVICE_NAME_FIELD_LENGTH // strcpy is safe here, since name contains at least
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf); strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1]; size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3]; | buf[DEVICE_NAME_FIELD_LENGTH + 1];
return SDL_TRUE; size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
} }

View file

@ -1,7 +1,7 @@
#ifndef DEVICE_H #ifndef DEVICE_H
#define DEVICE_H #define DEVICE_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include "common.h" #include "common.h"
#include "net.h" #include "net.h"
@ -10,6 +10,7 @@
#define DEVICE_SDCARD_PATH "/sdcard/" #define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes // name must be at least DEVICE_NAME_FIELD_LENGTH bytes
SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size); bool
device_read_info(socket_t device_socket, char *name, struct size *frame_size);
#endif #endif

View file

@ -1,3 +1,3 @@
#define EVENT_NEW_SESSION SDL_USEREVENT #define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1) #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2) #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)

View file

@ -13,7 +13,8 @@ struct request {
const char *file; const char *file;
}; };
static struct request *request_new(file_handler_action_t action, const char *file) { static struct request *
request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req)); struct request *req = SDL_malloc(sizeof(*req));
if (!req) { if (!req) {
return NULL; return NULL;
@ -23,7 +24,8 @@ static struct request *request_new(file_handler_action_t action, const char *fil
return req; return req;
} }
static void request_free(struct request *req) { static void
request_free(struct request *req) {
if (!req) { if (!req) {
return; return;
} }
@ -31,21 +33,25 @@ static void request_free(struct request *req) {
SDL_free((void *) req); SDL_free((void *) req);
} }
static SDL_bool request_queue_is_empty(const struct request_queue *queue) { static bool
request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
static SDL_bool request_queue_is_full(const struct request_queue *queue) { static bool
request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail; return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
} }
static SDL_bool request_queue_init(struct request_queue *queue) { static bool
request_queue_init(struct request_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
return SDL_TRUE; return true;
} }
static void request_queue_destroy(struct request_queue *queue) { static void
request_queue_destroy(struct request_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
request_free(queue->reqs[i]); request_free(queue->reqs[i]);
@ -53,38 +59,41 @@ static void request_queue_destroy(struct request_queue *queue) {
} }
} }
static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) { static bool
request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) { if (request_queue_is_full(queue)) {
return SDL_FALSE; return false;
} }
queue->reqs[queue->head] = req; queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE; queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE; return true;
} }
static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) { static bool
request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) { if (request_queue_is_empty(queue)) {
return SDL_FALSE; return false;
} }
// transfer ownership // transfer ownership
*req = queue->reqs[queue->tail]; *req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE; queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE; return true;
} }
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) { bool
file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) { if (!request_queue_init(&file_handler->queue)) {
return SDL_FALSE; return false;
} }
if (!(file_handler->mutex = SDL_CreateMutex())) { if (!(file_handler->mutex = SDL_CreateMutex())) {
return SDL_FALSE; return false;
} }
if (!(file_handler->event_cond = SDL_CreateCond())) { if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE; return false;
} }
if (serial) { if (serial) {
@ -92,58 +101,63 @@ SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Cannot strdup serial"); LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE; return false;
} }
} else { } else {
file_handler->serial = NULL; file_handler->serial = NULL;
} }
// lazy initialization // lazy initialization
file_handler->initialized = SDL_FALSE; file_handler->initialized = false;
file_handler->stopped = SDL_FALSE; file_handler->stopped = false;
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
return SDL_TRUE; return true;
} }
void file_handler_destroy(struct file_handler *file_handler) { void
file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue); request_queue_destroy(&file_handler->queue);
SDL_free((void *) file_handler->serial); SDL_free((void *) file_handler->serial);
} }
static process_t install_apk(const char *serial, const char *file) { static process_t
install_apk(const char *serial, const char *file) {
return adb_install(serial, file); return adb_install(serial, file);
} }
static process_t push_file(const char *serial, const char *file) { static process_t
push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH); return adb_push(serial, file, DEVICE_SDCARD_PATH);
} }
SDL_bool file_handler_request(struct file_handler *file_handler, bool
file_handler_action_t action, file_handler_request(struct file_handler *file_handler,
const char *file) { file_handler_action_t action,
SDL_bool res; const char *file) {
bool res;
// start file_handler if it's used for the first time // start file_handler if it's used for the first time
if (!file_handler->initialized) { if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) { if (!file_handler_start(file_handler)) {
return SDL_FALSE; return false;
} }
file_handler->initialized = SDL_TRUE; file_handler->initialized = true;
} }
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file); LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push",
file);
struct request *req = request_new(action, file); struct request *req = request_new(action, file);
if (!req) { if (!req) {
LOGE("Could not create request"); LOGE("Could not create request");
return SDL_FALSE; return false;
} }
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
SDL_bool was_empty = request_queue_is_empty(&file_handler->queue); bool was_empty = request_queue_is_empty(&file_handler->queue);
res = request_queue_push(&file_handler->queue, req); res = request_queue_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
@ -152,13 +166,15 @@ SDL_bool file_handler_request(struct file_handler *file_handler,
return res; return res;
} }
static int run_file_handler(void *data) { static int
run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) { while (!file_handler->stopped
&& request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex); cond_wait(file_handler->event_cond, file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
@ -167,7 +183,7 @@ static int run_file_handler(void *data) {
break; break;
} }
struct request *req; struct request *req;
SDL_bool non_empty = request_queue_take(&file_handler->queue, &req); bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty); SDL_assert(non_empty);
process_t process; process_t process;
@ -200,21 +216,24 @@ static int run_file_handler(void *data) {
return 0; return 0;
} }
SDL_bool file_handler_start(struct file_handler *file_handler) { bool
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler); file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
file_handler);
if (!file_handler->thread) { if (!file_handler->thread) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }
void file_handler_stop(struct file_handler *file_handler) { void
file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->stopped = SDL_TRUE; file_handler->stopped = true;
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) { if (!cmd_terminate(file_handler->current_process)) {
@ -226,6 +245,7 @@ void file_handler_stop(struct file_handler *file_handler) {
mutex_unlock(file_handler->mutex); mutex_unlock(file_handler->mutex);
} }
void file_handler_join(struct file_handler *file_handler) { void
file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL); SDL_WaitThread(file_handler->thread, NULL);
} }

View file

@ -1,9 +1,10 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HANDLER_H #define FILE_HANDLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "command.h" #include "command.h"
#define REQUEST_QUEUE_SIZE 16 #define REQUEST_QUEUE_SIZE 16
@ -24,21 +25,30 @@ struct file_handler {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
SDL_bool stopped; bool stopped;
SDL_bool initialized; bool initialized;
process_t current_process; process_t current_process;
struct request_queue queue; struct request_queue queue;
}; };
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial); bool
void file_handler_destroy(struct file_handler *file_handler); file_handler_init(struct file_handler *file_handler, const char *serial);
SDL_bool file_handler_start(struct file_handler *file_handler); void
void file_handler_stop(struct file_handler *file_handler); file_handler_destroy(struct file_handler *file_handler);
void file_handler_join(struct file_handler *file_handler);
SDL_bool file_handler_request(struct file_handler *file_handler, bool
file_handler_action_t action, file_handler_start(struct file_handler *file_handler);
const char *file);
void
file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif #endif

View file

@ -4,14 +4,16 @@
#include "log.h" #include "log.h"
void fps_counter_init(struct fps_counter *counter) { void
counter->started = SDL_FALSE; fps_counter_init(struct fps_counter *counter) {
counter->started = false;
// no need to initialize the other fields, they are meaningful only when // no need to initialize the other fields, they are meaningful only when
// started is true // started is true
} }
void fps_counter_start(struct fps_counter *counter) { void
counter->started = SDL_TRUE; fps_counter_start(struct fps_counter *counter) {
counter->started = true;
counter->slice_start = SDL_GetTicks(); counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@ -19,14 +21,17 @@ void fps_counter_start(struct fps_counter *counter) {
#endif #endif
} }
void fps_counter_stop(struct fps_counter *counter) { void
counter->started = SDL_FALSE; fps_counter_stop(struct fps_counter *counter) {
counter->started = false;
} }
static void display_fps(struct fps_counter *counter) { static void
display_fps(struct fps_counter *counter) {
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped); LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
counter->nr_skipped);
} else { } else {
#endif #endif
LOGI("%d fps", counter->nr_rendered); LOGI("%d fps", counter->nr_rendered);
@ -35,12 +40,13 @@ static void display_fps(struct fps_counter *counter) {
#endif #endif
} }
static void check_expired(struct fps_counter *counter) { static void
Uint32 now = SDL_GetTicks(); check_expired(struct fps_counter *counter) {
uint32_t now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) { if (now - counter->slice_start >= 1000) {
display_fps(counter); display_fps(counter);
// add a multiple of one second // add a multiple of one second
Uint32 elapsed_slices = (now - counter->slice_start) / 1000; uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices; counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@ -49,13 +55,15 @@ static void check_expired(struct fps_counter *counter) {
} }
} }
void fps_counter_add_rendered_frame(struct fps_counter *counter) { void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_rendered; ++counter->nr_rendered;
} }
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void fps_counter_add_skipped_frame(struct fps_counter *counter) { void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_skipped; ++counter->nr_skipped;
} }

View file

@ -1,26 +1,35 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include <stdint.h>
#include "config.h" #include "config.h"
struct fps_counter { struct fps_counter {
SDL_bool started; bool started;
Uint32 slice_start; // initialized by SDL_GetTicks() uint32_t slice_start; // initialized by SDL_GetTicks()
int nr_rendered; int nr_rendered;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
int nr_skipped; int nr_skipped;
#endif #endif
}; };
void fps_counter_init(struct fps_counter *counter); void
void fps_counter_start(struct fps_counter *counter); fps_counter_init(struct fps_counter *counter);
void fps_counter_stop(struct fps_counter *counter);
void
fps_counter_start(struct fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void fps_counter_add_rendered_frame(struct fps_counter *counter);
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void fps_counter_add_skipped_frame(struct fps_counter *counter); void
fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif #endif
#endif #endif

View file

@ -1,110 +0,0 @@
#include "frames.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {
if (!(frames->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(frames->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(frames->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(frames->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(frames->mutex);
goto error_2;
}
frames->stopped = SDL_FALSE;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
frames->rendering_frame_consumed = SDL_TRUE;
fps_counter_init(&frames->fps_counter);
return SDL_TRUE;
error_2:
av_frame_free(&frames->rendering_frame);
error_1:
av_frame_free(&frames->decoding_frame);
error_0:
return SDL_FALSE;
}
void frames_destroy(struct frames *frames) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(frames->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(frames->mutex);
av_frame_free(&frames->rendering_frame);
av_frame_free(&frames->decoding_frame);
}
static void frames_swap(struct frames *frames) {
AVFrame *tmp = frames->decoding_frame;
frames->decoding_frame = frames->rendering_frame;
frames->rendering_frame = tmp;
}
SDL_bool frames_offer_decoded_frame(struct frames *frames) {
mutex_lock(frames->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!frames->rendering_frame_consumed && !frames->stopped) {
cond_wait(frames->rendering_frame_consumed_cond, frames->mutex);
}
#else
if (frames->fps_counter.started && !frames->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&frames->fps_counter);
}
#endif
frames_swap(frames);
SDL_bool previous_frame_consumed = frames->rendering_frame_consumed;
frames->rendering_frame_consumed = SDL_FALSE;
mutex_unlock(frames->mutex);
return previous_frame_consumed;
}
const AVFrame *frames_consume_rendered_frame(struct frames *frames) {
SDL_assert(!frames->rendering_frame_consumed);
frames->rendering_frame_consumed = SDL_TRUE;
if (frames->fps_counter.started) {
fps_counter_add_rendered_frame(&frames->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(frames->rendering_frame_consumed_cond);
#endif
return frames->rendering_frame;
}
void frames_stop(struct frames *frames) {
#ifdef SKIP_FRAMES
(void) frames; // unused
#else
mutex_lock(frames->mutex);
frames->stopped = SDL_TRUE;
mutex_unlock(frames->mutex);
// wake up blocking wait
cond_signal(frames->rendering_frame_consumed_cond);
#endif
}

View file

@ -5,11 +5,13 @@
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events) // Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
// //
// See my question: // See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event> // <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) { static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport; SDL_Rect viewport;
float scale_x, scale_y; float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport); SDL_RenderGetViewport(renderer, &viewport);
@ -18,7 +20,8 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int
*y = (int) (*y / scale_y) - viewport.y; *y = (int) (*y / scale_y) - viewport.y;
} }
static struct point get_mouse_point(struct screen *screen) { static struct point
get_mouse_point(struct screen *screen) {
int x; int x;
int y; int y;
SDL_GetMouseState(&x, &y); SDL_GetMouseState(&x, &y);
@ -32,7 +35,9 @@ static struct point get_mouse_point(struct screen *screen) {
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) { static void
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
// send DOWN event // send DOWN event
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE; control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
@ -55,58 +60,93 @@ static void send_keycode(struct controller *controller, enum android_keycode key
} }
} }
static inline void action_home(struct controller *controller, int actions) { static inline void
action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
} }
static inline void action_back(struct controller *controller, int actions) { static inline void
action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
} }
static inline void action_app_switch(struct controller *controller, int actions) { static inline void
action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
} }
static inline void action_power(struct controller *controller, int actions) { static inline void
action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
} }
static inline void action_volume_up(struct controller *controller, int actions) { static inline void
action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
} }
static inline void action_volume_down(struct controller *controller, int actions) { static inline void
action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
} }
static inline void action_menu(struct controller *controller, int actions) { static inline void
action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
static void press_back_or_turn_screen_on(struct controller *controller) { static void
press_back_or_turn_screen_on(struct controller *controller) {
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND; control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON; control_event.command_event.action =
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
if (!controller_push_event(controller, &control_event)) { if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot turn screen on"); LOGW("Cannot turn screen on");
} }
} }
static void switch_fps_counter_state(struct frames *frames) { static void
mutex_lock(frames->mutex); expand_notification_panel(struct controller *controller) {
if (frames->fps_counter.started) { struct control_event control_event;
LOGI("FPS counter stopped"); control_event.type = CONTROL_EVENT_TYPE_COMMAND;
fps_counter_stop(&frames->fps_counter); control_event.command_event.action =
} else { CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL;
LOGI("FPS counter started");
fps_counter_start(&frames->fps_counter); if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot expand notification panel");
} }
mutex_unlock(frames->mutex);
} }
static void clipboard_paste(struct controller *controller) { static void
collapse_notification_panel(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot collapse notification panel");
}
}
static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter);
} else {
LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter);
}
mutex_unlock(vb->mutex);
}
static void
clipboard_paste(struct controller *controller) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
LOGW("Cannot get clipboard text: %s", SDL_GetError()); LOGW("Cannot get clipboard text: %s", SDL_GetError());
@ -127,8 +167,9 @@ static void clipboard_paste(struct controller *controller) {
} }
} }
void input_manager_process_text_input(struct input_manager *input_manager, void
const SDL_TextInputEvent *event) { input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0'); SDL_assert(event->text[1] == '\0');
@ -148,11 +189,13 @@ void input_manager_process_text_input(struct input_manager *input_manager,
} }
} }
void input_manager_process_key(struct input_manager *input_manager, void
const SDL_KeyboardEvent *event) { input_manager_process_key(struct input_manager *input_manager,
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); const SDL_KeyboardEvent *event,
SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); bool control) {
SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) { if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded // no shortcut involves Alt or Meta, and they should not be forwarded
@ -162,47 +205,42 @@ void input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events // capture all Ctrl events
if (ctrl | meta) { if (ctrl | meta) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP; int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
SDL_bool repeat = event->repeat; bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (ctrl && !meta && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_home(input_manager->controller, action); action_home(input_manager->controller, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (ctrl && !meta && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_back(input_manager->controller, action); action_back(input_manager->controller, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (ctrl && !meta && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_app_switch(input_manager->controller, action); action_app_switch(input_manager->controller, action);
} }
return; return;
case SDLK_m: case SDLK_m:
if (ctrl && !meta && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_menu(input_manager->controller, action); action_menu(input_manager->controller, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (ctrl && !meta && !repeat) { if (control && ctrl && !meta && !shift && !repeat) {
action_power(input_manager->controller, action); action_power(input_manager->controller, action);
} }
return; return;
case SDLK_DOWN: case SDLK_DOWN:
#ifdef __APPLE__ #ifdef __APPLE__
if (!ctrl && meta) { if (control && !ctrl && meta && !shift) {
#else #else
if (ctrl && !meta) { if (control && ctrl && !meta && !shift) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_down(input_manager->controller, action); action_volume_down(input_manager->controller, action);
@ -210,37 +248,52 @@ void input_manager_process_key(struct input_manager *input_manager,
return; return;
case SDLK_UP: case SDLK_UP:
#ifdef __APPLE__ #ifdef __APPLE__
if (!ctrl && meta) { if (control && !ctrl && meta && !shift) {
#else #else
if (ctrl && !meta) { if (control && ctrl && !meta && !shift) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_up(input_manager->controller, action); action_volume_up(input_manager->controller, action);
} }
return; return;
case SDLK_v: case SDLK_v:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { if (control && ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller); clipboard_paste(input_manager->controller);
} }
return; return;
case SDLK_f: case SDLK_f:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(input_manager->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { if (ctrl && !meta && !shift && !repeat
switch_fps_counter_state(input_manager->frames); && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->video_buffer);
}
return;
case SDLK_n:
if (control && ctrl && !meta
&& !repeat && event->type == SDL_KEYDOWN) {
if (shift) {
collapse_notification_panel(input_manager->controller);
} else {
expand_notification_panel(input_manager->controller);
}
} }
return; return;
} }
@ -248,6 +301,10 @@ void input_manager_process_key(struct input_manager *input_manager,
return; return;
} }
if (!control) {
return;
}
struct control_event control_event; struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) { if (input_key_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
@ -256,43 +313,48 @@ void input_manager_process_key(struct input_manager *input_manager,
} }
} }
void input_manager_process_mouse_motion(struct input_manager *input_manager, void
const SDL_MouseMotionEvent *event) { input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
// do not send motion events when no button is pressed // do not send motion events when no button is pressed
return; return;
} }
struct control_event control_event; struct control_event control_event;
if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { if (mouse_motion_from_sdl_to_android(event,
input_manager->screen->frame_size,
&control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send mouse motion event"); LOGW("Cannot send mouse motion event");
} }
} }
} }
static SDL_bool is_outside_device_screen(struct input_manager *input_manager, static bool
int x, int y) is_outside_device_screen(struct input_manager *input_manager, int x, int y)
{ {
return x < 0 || x >= input_manager->screen->frame_size.width || return x < 0 || x >= input_manager->screen->frame_size.width ||
y < 0 || y >= input_manager->screen->frame_size.height; y < 0 || y >= input_manager->screen->frame_size.height;
} }
void input_manager_process_mouse_button(struct input_manager *input_manager, void
const SDL_MouseButtonEvent *event) { input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control) {
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(input_manager->controller);
return; return;
} }
if (event->button == SDL_BUTTON_MIDDLE) { if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
SDL_bool outside= is_outside_device_screen(input_manager, bool outside = is_outside_device_screen(input_manager,
event->x, event->x,
event->y); event->y);
if (outside) { if (outside) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
return; return;
@ -301,16 +363,23 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
// otherwise, send the click event to the device // otherwise, send the click event to the device
} }
if (!control) {
return;
}
struct control_event control_event; struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { if (mouse_button_from_sdl_to_android(event,
input_manager->screen->frame_size,
&control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send mouse button event"); LOGW("Cannot send mouse button event");
} }
} }
} }
void input_manager_process_mouse_wheel(struct input_manager *input_manager, void
const SDL_MouseWheelEvent *event) { input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event) {
struct position position = { struct position position = {
.screen_size = input_manager->screen->frame_size, .screen_size = input_manager->screen->frame_size,
.point = get_mouse_point(input_manager->screen), .point = get_mouse_point(input_manager->screen),

View file

@ -1,27 +1,40 @@
#ifndef INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define INPUTMANAGER_H #define INPUTMANAGER_H
#include <stdbool.h>
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "frames.h" #include "video_buffer.h"
#include "screen.h" #include "screen.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct frames *frames; struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
}; };
void input_manager_process_text_input(struct input_manager *input_manager, void
const SDL_TextInputEvent *event); input_manager_process_text_input(struct input_manager *input_manager,
void input_manager_process_key(struct input_manager *input_manager, const SDL_TextInputEvent *event);
const SDL_KeyboardEvent *event);
void input_manager_process_mouse_motion(struct input_manager *input_manager, void
const SDL_MouseMotionEvent *event); input_manager_process_key(struct input_manager *input_manager,
void input_manager_process_mouse_button(struct input_manager *input_manager, const SDL_KeyboardEvent *event,
const SDL_MouseButtonEvent *event); bool control);
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event); void
input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event);
void
input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control);
void
input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event);
#endif #endif

View file

@ -4,28 +4,32 @@
#include "log.h" #include "log.h"
void mutex_lock(SDL_mutex *mutex) { void
mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) { if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex"); LOGC("Could not lock mutex");
abort(); abort();
} }
} }
void mutex_unlock(SDL_mutex *mutex) { void
mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) { if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex"); LOGC("Could not unlock mutex");
abort(); abort();
} }
} }
void cond_wait(SDL_cond *cond, SDL_mutex *mutex) { void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) { if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition"); LOGC("Could not wait on condition");
abort(); abort();
} }
} }
void cond_signal(SDL_cond *cond) { void
cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) { if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition"); LOGC("Could not signal a condition");
abort(); abort();

View file

@ -5,9 +5,16 @@
typedef struct SDL_mutex SDL_mutex; typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond; typedef struct SDL_cond SDL_cond;
void mutex_lock(SDL_mutex *mutex); void
void mutex_unlock(SDL_mutex *mutex); mutex_lock(SDL_mutex *mutex);
void cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void cond_signal(SDL_cond *cond); void
mutex_unlock(SDL_mutex *mutex);
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void
cond_signal(SDL_cond *cond);
#endif #endif

View file

@ -1,6 +1,8 @@
#include "scrcpy.h" #include "scrcpy.h"
#include <getopt.h> #include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -15,14 +17,16 @@ struct args {
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; enum recorder_format record_format;
SDL_bool fullscreen; bool fullscreen;
SDL_bool help; bool no_control;
SDL_bool version; bool no_display;
SDL_bool show_touches; bool help;
Uint16 port; bool version;
Uint16 max_size; bool show_touches;
Uint32 bit_rate; uint16_t port;
SDL_bool always_on_top; uint16_t max_size;
uint32_t bit_rate;
bool always_on_top;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@ -57,6 +61,13 @@ static void usage(const char *arg0) {
" is preserved.\n" " is preserved.\n"
" Default is %d%s.\n" " Default is %d%s.\n"
"\n" "\n"
" -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n"
"\n"
" -N, --no-display\n"
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" -p, --port port\n" " -p, --port port\n"
" Set the TCP port the client listens on.\n" " Set the TCP port the client listens on.\n"
" Default is %d.\n" " Default is %d.\n"
@ -120,6 +131,12 @@ static void usage(const char *arg0) {
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" turn screen on\n" " turn screen on\n"
"\n" "\n"
" Ctrl+n\n"
" expand notification panel\n"
"\n"
" Ctrl+Shift+n\n"
" collapse notification panel\n"
"\n"
" Ctrl+v\n" " Ctrl+v\n"
" paste computer clipboard to device\n" " paste computer clipboard to device\n"
"\n" "\n"
@ -135,28 +152,37 @@ static void usage(const char *arg0) {
DEFAULT_LOCAL_PORT); DEFAULT_LOCAL_PORT);
} }
static void print_version(void) { static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION); fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n"); fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO); SDL_PATCHLEVEL);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO); fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO); LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
} }
static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) { static bool
parse_bit_rate(char *optarg, uint32_t *bit_rate) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Bit-rate parameter is empty"); LOGE("Bit-rate parameter is empty");
return SDL_FALSE; return false;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
int mul = 1; int mul = 1;
if (*endptr != '\0') { if (*endptr != '\0') {
if (optarg == endptr) { if (optarg == endptr) {
LOGE("Invalid bit-rate: %s", optarg); LOGE("Invalid bit-rate: %s", optarg);
return SDL_FALSE; return false;
} }
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') { if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
mul = 1000000; mul = 1000000;
@ -164,70 +190,72 @@ static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) {
mul = 1000; mul = 1000;
} else { } else {
LOGE("Invalid bit-rate unit: %s", optarg); LOGE("Invalid bit-rate unit: %s", optarg);
return SDL_FALSE; return false;
} }
} }
if (value < 0 || ((Uint32) -1) / mul < value) { if (value < 0 || ((uint32_t) -1) / mul < value) {
LOGE("Bitrate must be positive and less than 2^32: %s", optarg); LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
return SDL_FALSE; return false;
} }
*bit_rate = (Uint32) value * mul; *bit_rate = (uint32_t) value * mul;
return SDL_TRUE; return true;
} }
static SDL_bool parse_max_size(char *optarg, Uint16 *max_size) { static bool
parse_max_size(char *optarg, uint16_t *max_size) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Max size parameter is empty"); LOGE("Max size parameter is empty");
return SDL_FALSE; return false;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid max size: %s", optarg); LOGE("Invalid max size: %s", optarg);
return SDL_FALSE; return false;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Max size must be between 0 and 65535: %ld", value); LOGE("Max size must be between 0 and 65535: %ld", value);
return SDL_FALSE; return false;
} }
*max_size = (Uint16) value; *max_size = (uint16_t) value;
return SDL_TRUE; return true;
} }
static SDL_bool parse_port(char *optarg, Uint16 *port) { static bool
parse_port(char *optarg, uint16_t *port) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Invalid port parameter is empty"); LOGE("Invalid port parameter is empty");
return SDL_FALSE; return false;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid port: %s", optarg); LOGE("Invalid port: %s", optarg);
return SDL_FALSE; return false;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Port out of range: %ld", value); LOGE("Port out of range: %ld", value);
return SDL_FALSE; return false;
} }
*port = (Uint16) value; *port = (uint16_t) value;
return SDL_TRUE; return true;
} }
static SDL_bool static bool
parse_record_format(const char *optarg, enum recorder_format *format) { parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) { if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4; *format = RECORDER_FORMAT_MP4;
return SDL_TRUE; return true;
} }
if (!strcmp(optarg, "mkv")) { if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV; *format = RECORDER_FORMAT_MKV;
return SDL_TRUE; return true;
} }
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return SDL_FALSE; return false;
} }
static enum recorder_format static enum recorder_format
@ -246,7 +274,8 @@ guess_record_format(const char *filename) {
return 0; return 0;
} }
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static bool
parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, 'T'}, {"always-on-top", no_argument, NULL, 'T'},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
@ -254,6 +283,8 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, 'f'}, {"record-format", required_argument, NULL, 'f'},
@ -263,35 +294,42 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:p:r:s:tTv", long_options, NULL)) != -1) { while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:tTv", long_options,
NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE; return false;
} }
break; break;
case 'c': case 'c':
args->crop = optarg; args->crop = optarg;
break; break;
case 'f': case 'f':
args->fullscreen = SDL_TRUE; args->fullscreen = true;
break; break;
case 'F': case 'F':
if (!parse_record_format(optarg, &args->record_format)) { if (!parse_record_format(optarg, &args->record_format)) {
return SDL_FALSE; return false;
} }
break; break;
case 'h': case 'h':
args->help = SDL_TRUE; args->help = true;
break; break;
case 'm': case 'm':
if (!parse_max_size(optarg, &args->max_size)) { if (!parse_max_size(optarg, &args->max_size)) {
return SDL_FALSE; return false;
} }
break; break;
case 'n':
args->no_control = true;
break;
case 'N':
args->no_display = true;
break;
case 'p': case 'p':
if (!parse_port(optarg, &args->port)) { if (!parse_port(optarg, &args->port)) {
return SDL_FALSE; return false;
} }
break; break;
case 'r': case 'r':
@ -301,29 +339,39 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
args->serial = optarg; args->serial = optarg;
break; break;
case 't': case 't':
args->show_touches = SDL_TRUE; args->show_touches = true;
break; break;
case 'T': case 'T':
args->always_on_top = SDL_TRUE; args->always_on_top = true;
break; break;
case 'v': case 'v':
args->version = SDL_TRUE; args->version = true;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return SDL_FALSE; return false;
} }
} }
if (args->no_display && !args->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
if (args->no_display && args->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind; int index = optind;
if (index < argc) { if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]); LOGE("Unexpected additional argument: %s", argv[index]);
return SDL_FALSE; return false;
} }
if (args->record_format && !args->record_filename) { if (args->record_format && !args->record_filename) {
LOGE("Record format specified without recording"); LOGE("Record format specified without recording");
return SDL_FALSE; return false;
} }
if (args->record_filename && !args->record_format) { if (args->record_filename && !args->record_format) {
@ -331,14 +379,15 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
if (!args->record_format) { if (!args->record_format) {
LOGE("No format specified for \"%s\" (try with -F mkv)", LOGE("No format specified for \"%s\" (try with -F mkv)",
args->record_filename); args->record_filename);
return SDL_FALSE; return false;
} }
} }
return SDL_TRUE; return true;
} }
int main(int argc, char *argv[]) { int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// disable buffering, we want logs immediately // disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient // even line buffering (setvbuf() with mode _IOLBF) is not sufficient
@ -350,13 +399,15 @@ int main(int argc, char *argv[]) {
.crop = NULL, .crop = NULL,
.record_filename = NULL, .record_filename = NULL,
.record_format = 0, .record_format = 0,
.help = SDL_FALSE, .help = false,
.version = SDL_FALSE, .version = false,
.show_touches = SDL_FALSE, .show_touches = false,
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
.always_on_top = SDL_FALSE, .always_on_top = false,
.no_control = false,
.no_display = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@ -395,6 +446,8 @@ int main(int argc, char *argv[]) {
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen, .fullscreen = args.fullscreen,
.always_on_top = args.always_on_top, .always_on_top = args.always_on_top,
.no_control = args.no_control,
.no_display = args.no_display,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View file

@ -18,7 +18,8 @@
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
#endif #endif
socket_t net_connect(Uint32 addr, Uint16 port) { socket_t
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@ -38,7 +39,8 @@ socket_t net_connect(Uint32 addr, Uint16 port) {
return sock; return sock;
} }
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { socket_t
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@ -46,7 +48,8 @@ socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
} }
int reuse = 1; int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) { if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
perror("setsockopt(SO_REUSEADDR)"); perror("setsockopt(SO_REUSEADDR)");
} }
@ -68,25 +71,30 @@ socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
return sock; return sock;
} }
socket_t net_accept(socket_t server_socket) { socket_t
net_accept(socket_t server_socket) {
SOCKADDR_IN csin; SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin); socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize); return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
} }
ssize_t net_recv(socket_t socket, void *buf, size_t len) { ssize_t
net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0); return recv(socket, buf, len, 0);
} }
ssize_t net_recv_all(socket_t socket, void *buf, size_t len) { ssize_t
net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL); return recv(socket, buf, len, MSG_WAITALL);
} }
ssize_t net_send(socket_t socket, const void *buf, size_t len) { ssize_t
net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0); return send(socket, buf, len, 0);
} }
ssize_t net_send_all(socket_t socket, const void *buf, size_t len) { ssize_t
net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0; ssize_t w = 0;
while (len > 0) { while (len > 0) {
w = send(socket, buf, len, 0); w = send(socket, buf, len, 0);
@ -99,6 +107,7 @@ ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
return w; return w;
} }
SDL_bool net_shutdown(socket_t socket, int how) { bool
net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how); return !shutdown(socket, how);
} }

View file

@ -1,8 +1,9 @@
#ifndef NET_H #ifndef NET_H
#define NET_H #define NET_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include <SDL2/SDL_stdinc.h>
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <winsock2.h> # include <winsock2.h>
@ -16,20 +17,39 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
SDL_bool net_init(void); bool
void net_cleanup(void); net_init(void);
socket_t net_connect(Uint32 addr, Uint16 port); void
socket_t net_listen(Uint32 addr, Uint16 port, int backlog); net_cleanup(void);
socket_t net_accept(socket_t server_socket);
socket_t
net_connect(uint32_t addr, uint16_t port);
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog);
socket_t
net_accept(socket_t server_socket);
// the _all versions wait/retry until len bytes have been written/read // the _all versions wait/retry until len bytes have been written/read
ssize_t net_recv(socket_t socket, void *buf, size_t len); ssize_t
ssize_t net_recv_all(socket_t socket, void *buf, size_t len); net_recv(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, const void *buf, size_t len);
ssize_t net_send_all(socket_t socket, const void *buf, size_t len); ssize_t
net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t
net_send(socket_t socket, const void *buf, size_t len);
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
SDL_bool net_shutdown(socket_t socket, int how); bool
SDL_bool net_close(socket_t socket); net_shutdown(socket_t socket, int how);
bool
net_close(socket_t socket);
#endif #endif

View file

@ -9,7 +9,8 @@
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
static const AVOutputFormat *find_muxer(const char *name) { static const AVOutputFormat *
find_muxer(const char *name) {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API #ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
void *opaque = NULL; void *opaque = NULL;
#endif #endif
@ -25,24 +26,26 @@ static const AVOutputFormat *find_muxer(const char *name) {
return oformat; return oformat;
} }
SDL_bool recorder_init(struct recorder *recorder, bool
const char *filename, recorder_init(struct recorder *recorder,
enum recorder_format format, const char *filename,
struct size declared_frame_size) { enum recorder_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename); recorder->filename = SDL_strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Cannot strdup filename"); LOGE("Cannot strdup filename");
return SDL_FALSE; return false;
} }
recorder->format = format; recorder->format = format;
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
recorder->header_written = SDL_FALSE; recorder->header_written = false;
return SDL_TRUE; return true;
} }
void recorder_destroy(struct recorder *recorder) { void
recorder_destroy(struct recorder *recorder) {
SDL_free(recorder->filename); SDL_free(recorder->filename);
} }
@ -55,19 +58,20 @@ recorder_get_format_name(enum recorder_format format) {
} }
} }
SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format); const char *format_name = recorder_get_format_name(recorder->format);
SDL_assert(format_name); SDL_assert(format_name);
const AVOutputFormat *format = find_muxer(format_name); const AVOutputFormat *format = find_muxer(format_name);
if (!format) { if (!format) {
LOGE("Could not find muxer"); LOGE("Could not find muxer");
return SDL_FALSE; return false;
} }
recorder->ctx = avformat_alloc_context(); recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) { if (!recorder->ctx) {
LOGE("Could not allocate output context"); LOGE("Could not allocate output context");
return SDL_FALSE; return false;
} }
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
@ -79,7 +83,7 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) { if (!ostream) {
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return SDL_FALSE; return false;
} }
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API #ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
@ -102,15 +106,16 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
LOGE("Failed to open output file: %s", recorder->filename); LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning // ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return SDL_FALSE; return false;
} }
LOGI("Recording started to %s file: %s", format_name, recorder->filename); LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return SDL_TRUE; return true;
} }
void recorder_close(struct recorder *recorder) { void
recorder_close(struct recorder *recorder) {
int ret = av_write_trailer(recorder->ctx); int ret = av_write_trailer(recorder->ctx);
if (ret < 0) { if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename); LOGE("Failed to write trailer to %s", recorder->filename);
@ -122,14 +127,14 @@ void recorder_close(struct recorder *recorder) {
LOGI("Recording complete to %s file: %s", format_name, recorder->filename); LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
} }
static SDL_bool static bool
recorder_write_header(struct recorder *recorder, AVPacket *packet) { recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) { if (!extradata) {
LOGC("Cannot allocate extradata"); LOGC("Cannot allocate extradata");
return SDL_FALSE; return false;
} }
// copy the first packet to the extra data // copy the first packet to the extra data
@ -149,10 +154,10 @@ recorder_write_header(struct recorder *recorder, AVPacket *packet) {
SDL_free(extradata); SDL_free(extradata);
avio_closep(&recorder->ctx->pb); avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }
static void static void
@ -161,13 +166,14 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
} }
SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) { bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
SDL_bool ok = recorder_write_header(recorder, packet); bool ok = recorder_write_header(recorder, packet);
if (!ok) { if (!ok) {
return SDL_FALSE; return false;
} }
recorder->header_written = SDL_TRUE; recorder->header_written = true;
} }
recorder_rescale_packet(recorder, packet); recorder_rescale_packet(recorder, packet);

View file

@ -1,8 +1,8 @@
#ifndef RECORDER_H #ifndef RECORDER_H
#define RECORDER_H #define RECORDER_H
#include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_stdinc.h>
#include "common.h" #include "common.h"
@ -16,19 +16,23 @@ struct recorder {
enum recorder_format format; enum recorder_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
SDL_bool header_written; bool header_written;
}; };
SDL_bool recorder_init(struct recorder *recoder, bool
const char *filename, recorder_init(struct recorder *recoder, const char *filename,
enum recorder_format format, enum recorder_format format, struct size declared_frame_size);
struct size declared_frame_size);
void recorder_destroy(struct recorder *recorder); void
recorder_destroy(struct recorder *recorder);
SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec); bool
void recorder_close(struct recorder *recorder); recorder_open(struct recorder *recorder, const AVCodec *input_codec);
SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet); void
recorder_close(struct recorder *recorder);
bool
recorder_write(struct recorder *recorder, AVPacket *packet);
#endif #endif

View file

@ -14,7 +14,6 @@
#include "device.h" #include "device.h"
#include "events.h" #include "events.h"
#include "file_handler.h" #include "file_handler.h"
#include "frames.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "input_manager.h" #include "input_manager.h"
#include "log.h" #include "log.h"
@ -23,22 +22,59 @@
#include "recorder.h" #include "recorder.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
static struct server server = SERVER_INITIALIZER; static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames; static struct video_buffer video_buffer;
static struct stream stream;
static struct decoder decoder; static struct decoder decoder;
static struct recorder recorder;
static struct controller controller; static struct controller controller;
static struct file_handler file_handler; static struct file_handler file_handler;
static struct recorder recorder;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.frames = &frames, .video_buffer = &video_buffer,
.screen = &screen, .screen = &screen,
}; };
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!display) {
return true;
}
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return true;
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@ -49,8 +85,10 @@ static struct input_manager input_manager = {
// //
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077> // <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>
static int event_watcher(void *data, SDL_Event *event) { static int
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { event_watcher(void *data, SDL_Event *event) {
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround! // called from another thread, not very safe, but it's a workaround!
screen_render(&screen); screen_render(&screen);
} }
@ -58,75 +96,117 @@ static int event_watcher(void *data, SDL_Event *event) {
} }
#endif #endif
static SDL_bool is_apk(const char *file) { static bool
is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk"); return ext && !strcmp(ext, ".apk");
} }
static SDL_bool event_loop(void) { enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(SDL_Event *event, bool control) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return false;
}
break;
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key, control);
break;
case SDL_MOUSEMOTION:
if (!control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button,
control);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
file_handler_action_t action;
if (is_apk(event->drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event->drop.file);
break;
}
}
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(bool display, bool control) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, NULL); if (display) {
SDL_AddEventWatch(event_watcher, NULL);
}
#endif #endif
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
switch (event.type) { enum event_result result = handle_event(&event, control);
case EVENT_DECODER_STOPPED: switch (result) {
LOGD("Video decoder stopped"); case EVENT_RESULT_STOPPED_BY_USER:
return SDL_FALSE; return true;
case SDL_QUIT: case EVENT_RESULT_STOPPED_BY_EOS:
LOGD("User requested to quit"); return false;
return SDL_TRUE; case EVENT_RESULT_CONTINUE:
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
}
break; break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
input_manager_process_text_input(&input_manager, &event.text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
input_manager_process_key(&input_manager, &event.key);
break;
case SDL_MOUSEMOTION:
input_manager_process_mouse_motion(&input_manager, &event.motion);
break;
case SDL_MOUSEWHEEL:
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
case SDL_DROPFILE: {
file_handler_action_t action;
if (is_apk(event.drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event.drop.file);
break;
}
} }
} }
return SDL_FALSE; return false;
} }
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) { static process_t
set_show_touches_enabled(const char *serial, bool enabled) {
const char *value = enabled ? "1" : "0"; const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = { const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value "shell", "settings", "put", "system", "show_touches", value
@ -134,12 +214,14 @@ static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled)
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static void wait_show_touches(process_t process) { static void
wait_show_touches(process_t process) {
// reap the process, ignore the result // reap the process, ignore the result
process_check_success(process, "show_touches"); process_check_success(process, "show_touches");
} }
static SDL_LogPriority sdl_priority_from_av_level(int level) { static SDL_LogPriority
sdl_priority_from_av_level(int level) {
switch (level) { switch (level) {
case AV_LOG_PANIC: case AV_LOG_PANIC:
case AV_LOG_FATAL: case AV_LOG_FATAL:
@ -173,67 +255,77 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
SDL_free(local_fmt); SDL_free(local_fmt);
} }
SDL_bool scrcpy(const struct scrcpy_options *options) { bool
SDL_bool send_frame_meta = !!options->record_filename; scrcpy(const struct scrcpy_options *options) {
bool record = !!options->record_filename;
if (!server_start(&server, options->serial, options->port, if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop, options->max_size, options->bit_rate, options->crop,
send_frame_meta)) { record)) {
return SDL_FALSE; return false;
} }
process_t proc_show_touches = PROCESS_NONE; process_t proc_show_touches = PROCESS_NONE;
SDL_bool show_touches_waited; bool show_touches_waited;
if (options->show_touches) { if (options->show_touches) {
LOGI("Enable show_touches"); LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE); proc_show_touches = set_show_touches_enabled(options->serial, true);
show_touches_waited = SDL_FALSE; show_touches_waited = false;
} }
SDL_bool ret = SDL_TRUE; bool ret = true;
if (!sdl_init_and_configure()) { bool display = !options->no_display;
ret = SDL_FALSE; bool control = !options->no_control;
if (!sdl_init_and_configure(display)) {
ret = false;
goto finally_destroy_server; goto finally_destroy_server;
} }
socket_t device_socket = server_connect_to(&server); socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) { if (device_socket == INVALID_SOCKET) {
server_stop(&server); server_stop(&server);
ret = SDL_FALSE; ret = false;
goto finally_destroy_server; goto finally_destroy_server;
} }
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 change // screenrecord does not send frames when the screen content does not
// therefore, we transmit the screen size before the video stream, to be able // change therefore, we transmit the screen size before the video stream,
// 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(device_socket, device_name, &frame_size)) {
server_stop(&server); server_stop(&server);
ret = SDL_FALSE; ret = false;
goto finally_destroy_server; goto finally_destroy_server;
} }
if (!frames_init(&frames)) { struct decoder *dec = NULL;
server_stop(&server); if (display) {
ret = SDL_FALSE; if (!video_buffer_init(&video_buffer)) {
goto finally_destroy_server; server_stop(&server);
} ret = false;
goto finally_destroy_server;
}
if (!file_handler_init(&file_handler, server.serial)) { if (control && !file_handler_init(&file_handler, server.serial)) {
ret = SDL_FALSE; ret = false;
server_stop(&server); server_stop(&server);
goto finally_destroy_frames; goto finally_destroy_video_buffer;
}
decoder_init(&decoder, &video_buffer);
dec = &decoder;
} }
struct recorder *rec = NULL; struct recorder *rec = NULL;
if (options->record_filename) { if (record) {
if (!recorder_init(&recorder, if (!recorder_init(&recorder,
options->record_filename, options->record_filename,
options->record_format, options->record_format,
frame_size)) { frame_size)) {
ret = SDL_FALSE; ret = false;
server_stop(&server); server_stop(&server);
goto finally_destroy_file_handler; goto finally_destroy_file_handler;
} }
@ -242,65 +334,78 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
decoder_init(&decoder, &frames, device_socket, rec); stream_init(&stream, device_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 decoder // start the stream
if (!decoder_start(&decoder)) { if (!stream_start(&stream)) {
ret = SDL_FALSE; ret = false;
server_stop(&server); server_stop(&server);
goto finally_destroy_recorder; goto finally_destroy_recorder;
} }
if (!controller_init(&controller, device_socket)) { if (display) {
ret = SDL_FALSE; if (control) {
goto finally_stop_decoder; if (!controller_init(&controller, device_socket)) {
} ret = false;
goto finally_stop_stream;
}
if (!controller_start(&controller)) { if (!controller_start(&controller)) {
ret = SDL_FALSE; ret = false;
goto finally_destroy_controller; goto finally_destroy_controller;
} }
}
if (!screen_init_rendering(&screen, device_name, frame_size, options->always_on_top)) { if (!screen_init_rendering(&screen, device_name, frame_size,
ret = SDL_FALSE; options->always_on_top)) {
goto finally_stop_and_join_controller; ret = false;
goto finally_stop_and_join_controller;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
} }
if (options->show_touches) { if (options->show_touches) {
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
show_touches_waited = SDL_TRUE; show_touches_waited = true;
} }
if (options->fullscreen) { ret = event_loop(display, control);
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen); screen_destroy(&screen);
finally_stop_and_join_controller: finally_stop_and_join_controller:
controller_stop(&controller); if (display && control) {
controller_join(&controller); controller_stop(&controller);
controller_join(&controller);
}
finally_destroy_controller: finally_destroy_controller:
controller_destroy(&controller); if (display && control) {
finally_stop_decoder: controller_destroy(&controller);
decoder_stop(&decoder); }
// stop the server before decoder_join() to wake up the decoder finally_stop_stream:
stream_stop(&stream);
// stop the server before stream_join() to wake up the stream
server_stop(&server); server_stop(&server);
decoder_join(&decoder); stream_join(&stream);
finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_recorder: finally_destroy_recorder:
if (options->record_filename) { if (record) {
recorder_destroy(&recorder); recorder_destroy(&recorder);
} }
finally_destroy_frames: finally_destroy_file_handler:
frames_destroy(&frames); if (display && control) {
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
}
finally_destroy_video_buffer:
if (display) {
video_buffer_destroy(&video_buffer);
}
finally_destroy_server: finally_destroy_server:
if (options->show_touches) { if (options->show_touches) {
if (!show_touches_waited) { if (!show_touches_waited) {
@ -308,7 +413,8 @@ finally_destroy_server:
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }
LOGI("Disable show_touches"); LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE); proc_show_touches = set_show_touches_enabled(options->serial,
false);
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }

View file

@ -1,7 +1,8 @@
#ifndef SCRCPY_H #ifndef SCRCPY_H
#define SCRCPY_H #define SCRCPY_H
#include <SDL2/SDL_stdinc.h> #include <stdbool.h>
#include <stdint.h>
#include <recorder.h> #include <recorder.h>
struct scrcpy_options { struct scrcpy_options {
@ -9,14 +10,17 @@ struct scrcpy_options {
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; enum recorder_format record_format;
Uint16 port; uint16_t port;
Uint16 max_size; uint16_t max_size;
Uint32 bit_rate; uint32_t bit_rate;
SDL_bool show_touches; bool show_touches;
SDL_bool fullscreen; bool fullscreen;
SDL_bool always_on_top; bool always_on_top;
bool no_control;
bool no_display;
}; };
SDL_bool scrcpy(const struct scrcpy_options *options); bool
scrcpy(const struct scrcpy_options *options);
#endif #endif

View file

@ -1,44 +1,21 @@
#include "screen.h" #include "screen.h"
#include <SDL2/SDL.h>
#include <string.h> #include <string.h>
#include <SDL2/SDL.h>
#include "common.h"
#include "compat.h" #include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
SDL_bool sdl_init_and_configure(void) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return SDL_FALSE;
}
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return SDL_TRUE;
}
// get the window size in a struct size // get the window size in a struct size
static struct size get_native_window_size(SDL_Window *window) { static struct size
get_native_window_size(SDL_Window *window) {
int width; int width;
int height; int height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
@ -50,7 +27,8 @@ static struct size get_native_window_size(SDL_Window *window) {
} }
// get the windowed window size // get the windowed window size
static struct size get_window_size(const struct screen *screen) { static struct size
get_window_size(const struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return screen->windowed_window_size; return screen->windowed_window_size;
} }
@ -58,7 +36,8 @@ static struct size get_window_size(const struct screen *screen) {
} }
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void set_window_size(struct screen *screen, struct size new_size) { static void
set_window_size(struct screen *screen, struct size new_size) {
// setting the window size during fullscreen is implementation defined, // setting the window size during fullscreen is implementation defined,
// so apply the resize only after fullscreen is disabled // so apply the resize only after fullscreen is disabled
if (screen->fullscreen) { if (screen->fullscreen) {
@ -70,7 +49,8 @@ static void set_window_size(struct screen *screen, struct size new_size) {
} }
// get the preferred display bounds (i.e. the screen bounds with some margins) // get the preferred display bounds (i.e. the screen bounds with some margins)
static SDL_bool get_preferred_display_bounds(struct size *bounds) { static bool
get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect; SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS #ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
@ -79,19 +59,21 @@ static SDL_bool get_preferred_display_bounds(struct size *bounds) {
#endif #endif
if (GET_DISPLAY_BOUNDS(0, &rect)) { if (GET_DISPLAY_BOUNDS(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError()); LOGW("Could not get display usable bounds: %s", SDL_GetError());
return SDL_FALSE; return false;
} }
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS); bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS); bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
return SDL_TRUE; return true;
} }
// return the optimal size of the window, with the following constraints: // return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it crops the black borders) // - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size get_optimal_size(struct size current_size, struct size frame_size) { static struct size
get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) { if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
@ -99,8 +81,8 @@ static struct size get_optimal_size(struct size current_size, struct size frame_
struct size display_size; struct size display_size;
// 32 bits because we need to multiply two 16 bits values // 32 bits because we need to multiply two 16 bits values
Uint32 w; uint32_t w;
Uint32 h; uint32_t h;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// cannot get display bounds, do not constraint the size // cannot get display bounds, do not constraint the size
@ -111,12 +93,13 @@ static struct size get_optimal_size(struct size current_size, struct size frame_
h = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
SDL_bool keep_width = frame_size.width * h > frame_size.height * w; bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
h = frame_size.height * w / frame_size.width; h = frame_size.height * w / frame_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already fits) // remove black borders on left and right (or none at all if it already
// fits)
w = frame_size.width * h / frame_size.height; w = frame_size.width * h / frame_size.height;
} }
@ -126,33 +109,37 @@ static struct size get_optimal_size(struct size current_size, struct size frame_
} }
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size get_optimal_window_size(const struct screen *screen, struct size frame_size) { static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
return get_optimal_size(current_size, frame_size); return get_optimal_size(current_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
static inline struct size get_initial_optimal_size(struct size frame_size) { static inline struct size
get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size); return get_optimal_size(frame_size, frame_size);
} }
void screen_init(struct screen *screen) { void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
} }
static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) { static inline SDL_Texture *
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height); frame_size.width, frame_size.height);
} }
SDL_bool screen_init_rendering(struct screen *screen, bool
const char *device_name, screen_init_rendering(struct screen *screen, const char *device_name,
struct size frame_size, struct size frame_size, bool always_on_top) {
SDL_bool always_on_top) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size); struct size window_size = get_initial_optimal_size(frame_size);
Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif #endif
@ -165,51 +152,58 @@ SDL_bool screen_init_rendering(struct screen *screen,
#endif #endif
} }
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height, window_flags); SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height,
window_flags);
if (!screen->window) { if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError()); LOGC("Could not create window: %s", SDL_GetError());
return SDL_FALSE; return false;
} }
screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED); screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; return false;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) { if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; return false;
} }
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (!icon) { if (!icon) {
LOGE("Could not load icon: %s", SDL_GetError()); LOGE("Could not load icon: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; return false;
} }
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon); SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height); LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size); screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }
void screen_show_window(struct screen *screen) { void
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void screen_destroy(struct screen *screen) { void
screen_destroy(struct screen *screen) {
if (screen->texture) { if (screen->texture) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
} }
@ -222,11 +216,14 @@ void screen_destroy(struct screen *screen) {
} }
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) { static bool
if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) { if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return SDL_FALSE; return false;
} }
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@ -234,8 +231,10 @@ static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_s
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
struct size target_size = { struct size target_size = {
(Uint32) current_size.width * new_frame_size.width / screen->frame_size.width, (uint32_t) current_size.width * new_frame_size.width
(Uint32) current_size.height * new_frame_size.height / screen->frame_size.height, / screen->frame_size.width,
(uint32_t) current_size.height * new_frame_size.height
/ screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
@ -247,48 +246,52 @@ static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_s
screen->texture = create_texture(screen->renderer, new_frame_size); screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
return SDL_FALSE; return false;
} }
} }
return SDL_TRUE; return true;
} }
// write the frame into the texture // write the frame into the texture
static void update_texture(struct screen *screen, const AVFrame *frame) { static void
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL, SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
} }
SDL_bool screen_update_frame(struct screen *screen, struct frames *frames) { bool
mutex_lock(frames->mutex); screen_update_frame(struct screen *screen, struct video_buffer *vb) {
const AVFrame *frame = frames_consume_rendered_frame(frames); mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(frames->mutex); mutex_unlock(vb->mutex);
return SDL_FALSE; return false;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(frames->mutex); mutex_unlock(vb->mutex);
screen_render(screen); screen_render(screen);
return SDL_TRUE; return true;
} }
void screen_render(struct screen *screen) { void
screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
void screen_switch_fullscreen(struct screen *screen) { void
screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size // going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window); screen->windowed_window_size = get_native_window_size(screen->window);
} }
Uint32 new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
return; return;
@ -297,24 +300,30 @@ void screen_switch_fullscreen(struct screen *screen) {
screen->fullscreen = !screen->fullscreen; screen->fullscreen = !screen->fullscreen;
if (!screen->fullscreen) { if (!screen->fullscreen) {
// fullscreen disabled, restore expected windowed window size // fullscreen disabled, restore expected windowed window size
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, screen->windowed_window_size.height); SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
screen->windowed_window_size.height);
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen); screen_render(screen);
} }
void screen_resize_to_fit(struct screen *screen) { void
screen_resize_to_fit(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
struct size optimal_size = get_optimal_window_size(screen, screen->frame_size); struct size optimal_size = get_optimal_window_size(screen,
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); screen->frame_size);
SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
LOGD("Resized to optimal size"); LOGD("Resized to optimal size");
} }
} }
void screen_resize_to_pixel_perfect(struct screen *screen) { void
screen_resize_to_pixel_perfect(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
SDL_SetWindowSize(screen->window, screen->frame_size.width, screen->frame_size.height); SDL_SetWindowSize(screen->window, screen->frame_size.width,
screen->frame_size.height);
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }

View file

@ -1,11 +1,13 @@
#ifndef SCREEN_H #ifndef SCREEN_H
#define SCREEN_H #define SCREEN_H
#include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "common.h" #include "common.h"
#include "frames.h"
struct video_buffer;
struct screen { struct screen {
SDL_Window *window; SDL_Window *window;
@ -14,8 +16,9 @@ struct screen {
struct size frame_size; struct size frame_size;
//used only in fullscreen mode to know the windowed window size //used only in fullscreen mode to know the windowed window size
struct size windowed_window_size; struct size windowed_window_size;
SDL_bool has_frame; bool has_frame;
SDL_bool fullscreen; bool fullscreen;
bool no_window;
}; };
#define SCREEN_INITIALIZER { \ #define SCREEN_INITIALIZER { \
@ -30,41 +33,46 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.has_frame = SDL_FALSE, \ .has_frame = false, \
.fullscreen = SDL_FALSE, \ .fullscreen = false, \
.no_window = false, \
} }
// init SDL and set appropriate hints
SDL_bool sdl_init_and_configure(void);
// initialize default values // initialize default values
void screen_init(struct screen *screen); void
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
SDL_bool screen_init_rendering(struct screen *screen, bool
const char *device_name, screen_init_rendering(struct screen *screen, const char *device_name,
struct size frame_size, struct size frame_size, bool always_on_top);
SDL_bool always_on_top);
// show the window // show the window
void screen_show_window(struct screen *screen); void
screen_show_window(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void screen_destroy(struct screen *screen); void
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture // resize if necessary and write the rendered frame into the texture
SDL_bool screen_update_frame(struct screen *screen, struct frames *frames); bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
void screen_render(struct screen *screen); void
screen_render(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void screen_switch_fullscreen(struct screen *screen); void
screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders) // resize window to optimal size (remove black borders)
void screen_resize_to_fit(struct screen *screen); void
screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen); void
screen_resize_to_pixel_perfect(struct screen *screen);
#endif #endif

View file

@ -2,7 +2,6 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
@ -21,7 +20,8 @@
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static const char *get_server_path(void) { static const char *
get_server_path(void) {
const char *server_path = getenv("SCRCPY_SERVER_PATH"); const char *server_path = getenv("SCRCPY_SERVER_PATH");
if (!server_path) { if (!server_path) {
server_path = DEFAULT_SERVER_PATH; server_path = DEFAULT_SERVER_PATH;
@ -29,52 +29,60 @@ static const char *get_server_path(void) {
return server_path; return server_path;
} }
static SDL_bool push_server(const char *serial) { static bool
push_server(const char *serial) {
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH); process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
return process_check_success(process, "adb push"); return process_check_success(process, "adb push");
} }
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) { static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse"); return process_check_success(process, "adb reverse");
} }
static SDL_bool disable_tunnel_reverse(const char *serial) { static bool
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove"); return process_check_success(process, "adb reverse --remove");
} }
static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) { static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward"); return process_check_success(process, "adb forward");
} }
static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) { static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove"); return process_check_success(process, "adb forward --remove");
} }
static SDL_bool enable_tunnel(struct server *server) { static bool
enable_tunnel(struct server *server) {
if (enable_tunnel_reverse(server->serial, server->local_port)) { if (enable_tunnel_reverse(server->serial, server->local_port)) {
return SDL_TRUE; return true;
} }
LOGW("'adb reverse' failed, fallback to 'adb forward'"); LOGW("'adb reverse' failed, fallback to 'adb forward'");
server->tunnel_forward = SDL_TRUE; server->tunnel_forward = true;
return enable_tunnel_forward(server->serial, server->local_port); return enable_tunnel_forward(server->serial, server->local_port);
} }
static SDL_bool disable_tunnel(struct server *server) { static bool
disable_tunnel(struct server *server) {
if (server->tunnel_forward) { if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port); return disable_tunnel_forward(server->serial, server->local_port);
} }
return disable_tunnel_reverse(server->serial); return disable_tunnel_reverse(server->serial);
} }
static process_t execute_server(const char *serial, static process_t
Uint16 max_size, Uint32 bit_rate, execute_server(const char *serial,
SDL_bool tunnel_forward, const char *crop, uint16_t max_size, uint32_t bit_rate,
SDL_bool send_frame_meta) { bool tunnel_forward, const char *crop,
bool send_frame_meta) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size); sprintf(max_size_string, "%"PRIu16, max_size);
@ -96,11 +104,13 @@ static process_t execute_server(const char *serial,
#define IPV4_LOCALHOST 0x7F000001 #define IPV4_LOCALHOST 0x7F000001
static socket_t listen_on_port(Uint16 port) { static socket_t
listen_on_port(uint16_t port) {
return net_listen(IPV4_LOCALHOST, port, 1); return net_listen(IPV4_LOCALHOST, port, 1);
} }
static socket_t connect_and_read_byte(Uint16 port) { static socket_t
connect_and_read_byte(uint16_t port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port); socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) { if (socket == INVALID_SOCKET) {
return INVALID_SOCKET; return INVALID_SOCKET;
@ -116,7 +126,8 @@ static socket_t connect_and_read_byte(Uint16 port) {
return socket; return socket;
} }
static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) { static socket_t
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
do { do {
LOGD("Remaining connection attempts: %d", (int) attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port); socket_t socket = connect_and_read_byte(port);
@ -131,7 +142,8 @@ static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
static void close_socket(socket_t *socket) { static void
close_socket(socket_t *socket) {
SDL_assert(*socket != INVALID_SOCKET); SDL_assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR); net_shutdown(*socket, SHUT_RDWR);
if (!net_close(*socket)) { if (!net_close(*socket)) {
@ -141,30 +153,32 @@ static void close_socket(socket_t *socket) {
*socket = INVALID_SOCKET; *socket = INVALID_SOCKET;
} }
void server_init(struct server *server) { void
server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER; *server = (struct server) SERVER_INITIALIZER;
} }
SDL_bool server_start(struct server *server, const char *serial, bool
Uint16 local_port, Uint16 max_size, Uint32 bit_rate, server_start(struct server *server, const char *serial,
const char *crop, SDL_bool send_frame_meta) { uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta) {
server->local_port = local_port; server->local_port = local_port;
if (serial) { if (serial) {
server->serial = SDL_strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) { if (!server->serial) {
return SDL_FALSE; return false;
} }
} }
if (!push_server(serial)) { if (!push_server(serial)) {
SDL_free((void *) server->serial); SDL_free(server->serial);
return SDL_FALSE; return false;
} }
if (!enable_tunnel(server)) { if (!enable_tunnel(server)) {
SDL_free((void *) server->serial); SDL_free(server->serial);
return SDL_FALSE; return false;
} }
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
@ -173,15 +187,16 @@ SDL_bool server_start(struct server *server, const char *serial,
// At the application level, the device part is "the server" because it // At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the // serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the // client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no need to // client can listen before starting the server app, so there is no
// try to connect until the server socket is listening on the device. // need to try to connect until the server socket is listening on the
// device.
server->server_socket = listen_on_port(local_port); server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) { if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port); LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial); SDL_free(server->serial);
return SDL_FALSE; return false;
} }
} }
@ -196,21 +211,23 @@ SDL_bool server_start(struct server *server, const char *serial,
} }
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial); SDL_free((void *) server->serial);
return SDL_FALSE; return false;
} }
server->tunnel_enabled = SDL_TRUE; server->tunnel_enabled = true;
return SDL_TRUE; return true;
} }
socket_t server_connect_to(struct server *server) { socket_t
server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket); server->device_socket = net_accept(server->server_socket);
} else { } else {
Uint32 attempts = 100; uint32_t attempts = 100;
Uint32 delay = 100; // ms uint32_t delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay); server->device_socket = connect_to_server(server->local_port, attempts,
delay);
} }
if (server->device_socket == INVALID_SOCKET) { if (server->device_socket == INVALID_SOCKET) {
@ -224,12 +241,13 @@ socket_t server_connect_to(struct server *server) {
// we don't need the adb tunnel anymore // we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure disable_tunnel(server); // ignore failure
server->tunnel_enabled = SDL_FALSE; server->tunnel_enabled = false;
return server->device_socket; return server->device_socket;
} }
void server_stop(struct server *server) { void
server_stop(struct server *server) {
SDL_assert(server->process != PROCESS_NONE); SDL_assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) { if (!cmd_terminate(server->process)) {
@ -245,12 +263,13 @@ void server_stop(struct server *server) {
} }
} }
void server_destroy(struct server *server) { void
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->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket); close_socket(&server->device_socket);
} }
SDL_free((void *) server->serial); SDL_free(server->serial);
} }

View file

@ -1,18 +1,21 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include <stdbool.h>
#include <stdint.h>
#include "command.h" #include "command.h"
#include "net.h" #include "net.h"
struct server { struct server {
const 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 device_socket;
Uint16 local_port; uint16_t local_port;
SDL_bool tunnel_enabled; bool tunnel_enabled;
SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse" bool tunnel_forward; // use "adb forward" instead of "adb reverse"
SDL_bool send_frame_meta; // request frame PTS to be able to record properly bool send_frame_meta; // request frame PTS to be able to record properly
}; };
#define SERVER_INITIALIZER { \ #define SERVER_INITIALIZER { \
@ -21,26 +24,31 @@ struct server {
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \
.local_port = 0, \ .local_port = 0, \
.tunnel_enabled = SDL_FALSE, \ .tunnel_enabled = false, \
.tunnel_forward = SDL_FALSE, \ .tunnel_forward = false, \
.send_frame_meta = SDL_FALSE, \ .send_frame_meta = false, \
} }
// init default values // init default values
void server_init(struct server *server); void
server_init(struct server *server);
// push, enable tunnel et start the server // push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, bool
Uint16 local_port, Uint16 max_size, Uint32 bit_rate, server_start(struct server *server, const char *serial,
const char *crop, SDL_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 // block until the communication with the server is established
socket_t server_connect_to(struct server *server); socket_t
server_connect_to(struct server *server);
// disconnect and kill the server process // disconnect and kill the server process
void server_stop(struct server *server); void
server_stop(struct server *server);
// close and release sockets // close and release sockets
void server_destroy(struct server *server); void
server_destroy(struct server *server);
#endif #endif

View file

@ -8,7 +8,8 @@
# include <tchar.h> # include <tchar.h>
#endif #endif
size_t xstrncpy(char *dest, const char *src, size_t n) { size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i) for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i]; dest[i] = src[i];
@ -17,7 +18,8 @@ size_t xstrncpy(char *dest, const char *src, size_t n) {
return src[i] == '\0' ? i : n; return src[i] == '\0' ? i : n;
} }
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens; const char *const *remaining = tokens;
const char *token = *remaining++; const char *token = *remaining++;
size_t i = 0; size_t i = 0;
@ -40,7 +42,8 @@ truncated:
return n; return n;
} }
char *strquote(const char *src) { char *
strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
@ -55,7 +58,8 @@ char *strquote(const char *src) {
#ifdef _WIN32 #ifdef _WIN32
wchar_t *utf8_to_wide_char(const char *utf8) { wchar_t *
utf8_to_wide_char(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) { if (!len) {
return NULL; return NULL;

View file

@ -9,21 +9,25 @@
// - it does not write useless bytes if strlen(src) < n // - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has // - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated // been copied completely, or n if src has been truncated
size_t xstrncpy(char *dest, const char *src, size_t n); size_t
xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst // join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no trucation // returns the number of chars actually written (max n-1) if no trucation
// occurred, or n if truncated // occurred, or n if truncated
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string // quote a string
// returns the new allocated string, to be freed by the caller // returns the new allocated string, to be freed by the caller
char *strquote(const char *src); char *
strquote(const char *src);
#ifdef _WIN32 #ifdef _WIN32
// convert a UTF-8 string to a wchar_t string // convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller // returns the new allocated string, to be freed by the caller
wchar_t *utf8_to_wide_char(const char *utf8); wchar_t *
utf8_to_wide_char(const char *utf8);
#endif #endif
#endif #endif

286
app/src/stream.c Normal file
View file

@ -0,0 +1,286 @@
#include "stream.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "compat.h"
#include "config.h"
#include "buffer_util.h"
#include "decoder.h"
#include "events.h"
#include "lock_util.h"
#include "log.h"
#include "recorder.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *
frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void
frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static bool
receiver_state_push_meta(struct receiver_state *state, uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return false;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return true;
}
static uint64_t
receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int
read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
struct receiver_state *state = &stream->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining) {
buf_size = state->remaining;
}
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int
read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVFormatContext *format_ctx = avformat_alloc_context();
if (!format_ctx) {
LOGC("Could not allocate format context");
goto end;
}
unsigned char *buffer = av_malloc(BUFSIZE);
if (!buffer) {
LOGC("Could not allocate buffer");
goto finally_free_format_ctx;
}
// initialize the receiver state
stream->receiver_state.frame_meta_queue = NULL;
stream->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
stream->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, stream,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto finally_free_avio_ctx;
}
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
LOGE("Could not open decoder");
goto finally_close_input;
}
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
if (stream->decoder && !decoder_push(stream->decoder, &packet)) {
av_packet_unref(&packet);
goto quit;
}
if (stream->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&stream->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(stream->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
quit:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_input:
avformat_close_input(&format_ctx);
finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
finally_free_format_ctx:
avformat_free_context(format_ctx);
end:
notify_stopped();
return 0;
}
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
}

43
app/src/stream.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef STREAM_H
#define STREAM_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "net.h"
struct video_buffer;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
bool
stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View file

@ -11,7 +11,8 @@
#include <unistd.h> #include <unistd.h>
#include "log.h" #include "log.h"
enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) { enum process_result
cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2]; int fd[2];
if (pipe(fd) == -1) { if (pipe(fd) == -1) {
@ -72,15 +73,18 @@ end:
return ret; return ret;
} }
SDL_bool cmd_terminate(pid_t pid) { bool
cmd_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
abort(); abort();
} }
return kill(pid, SIGTERM) != -1; return kill(pid, SIGTERM) != -1;
} }
SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) { bool
cmd_simple_wait(pid_t pid, int *exit_code) {
int status; int status;
int code; int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {

View file

@ -1,16 +1,19 @@
#include "net.h" #include "net.h"
# include <unistd.h> #include <unistd.h>
SDL_bool net_init(void) { bool
net_init(void) {
// do nothing // do nothing
return SDL_TRUE; return true;
} }
void net_cleanup(void) { void
net_cleanup(void) {
// do nothing // do nothing
} }
SDL_bool net_close(socket_t socket) { bool
net_close(socket_t socket) {
return !close(socket); return !close(socket);
} }

View file

@ -4,7 +4,8 @@
#include "log.h" #include "log.h"
#include "str_util.h" #include "str_util.h"
static int build_cmd(char *cmd, size_t len, const char *const argv[]) { static int
build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF: // Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS> // <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program // only make it work for this very specific program
@ -17,7 +18,8 @@ static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
return 0; return 0;
} }
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { enum process_result
cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
STARTUPINFOW si; STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
@ -40,7 +42,8 @@ enum process_result cmd_execute(const char *path, const char *const argv[], HAND
#else #else
int flags = 0; int flags = 0;
#endif #endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si,
&pi)) {
free(wide); free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
@ -54,13 +57,16 @@ enum process_result cmd_execute(const char *path, const char *const argv[], HAND
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
SDL_bool cmd_terminate(HANDLE handle) { bool
cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle); return TerminateProcess(handle, 1) && CloseHandle(handle);
} }
SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) { bool
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) {
// cannot wait or retrieve the exit code // cannot wait or retrieve the exit code
code = -1; // max value, it's unsigned code = -1; // max value, it's unsigned
} }

View file

@ -2,20 +2,23 @@
#include "log.h" #include "log.h"
SDL_bool net_init(void) { bool
net_init(void) {
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
LOGC("WSAStartup failed with error %d", res); LOGC("WSAStartup failed with error %d", res);
return SDL_FALSE; return false;
} }
return SDL_TRUE; return true;
} }
void net_cleanup(void) { void
net_cleanup(void) {
WSACleanup(); WSACleanup();
} }
SDL_bool net_close(socket_t socket) { bool
net_close(socket_t socket) {
return !closesocket(socket); return !closesocket(socket);
} }

View file

@ -1,5 +1,7 @@
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -7,19 +9,20 @@
struct index { struct index {
char c; char c;
Uint32 color; uint32_t color;
}; };
static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) { static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array // there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
if (index[i].c == c) { if (index[i].c == c) {
*color = index[i].color; *color = index[i].color;
return SDL_TRUE; return true;
} }
} }
*color = 0; *color = 0;
return SDL_FALSE; return false;
} }
// We encounter some problems with SDL2_image on MSYS2 (Windows), // We encounter some problems with SDL2_image on MSYS2 (Windows),
@ -30,7 +33,8 @@ static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color)
// //
// Parameter is not "const char *" because XPM formats are generally stored in a // Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *" // (non-const) "char *"
SDL_Surface *read_xpm(char *xpm[]) { SDL_Surface *
read_xpm(char *xpm[]) {
#if SDL_ASSERT_LEVEL >= 2 #if SDL_ASSERT_LEVEL >= 2
// patch the XPM to change the icon color in debug mode // patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC"; xpm[2] = ". c #CC00CC";
@ -69,7 +73,7 @@ SDL_Surface *read_xpm(char *xpm[]) {
} }
// parse image // parse image
Uint32 *pixels = SDL_malloc(4 * width * height); uint32_t *pixels = SDL_malloc(4 * width * height);
if (!pixels) { if (!pixels) {
LOGE("Could not allocate icon memory"); LOGE("Could not allocate icon memory");
return NULL; return NULL;
@ -78,23 +82,23 @@ SDL_Surface *read_xpm(char *xpm[]) {
const char *line = xpm[1 + colors + y]; const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
char c = line[x]; char c = line[x];
Uint32 color; uint32_t color;
SDL_bool color_found = find_color(index, colors, c, &color); bool color_found = find_color(index, colors, c, &color);
SDL_assert(color_found); SDL_assert(color_found);
pixels[y * width + x] = color; pixels[y * width + x] = color;
} }
} }
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
Uint32 amask = 0x000000ff; uint32_t amask = 0x000000ff;
Uint32 rmask = 0x0000ff00; uint32_t rmask = 0x0000ff00;
Uint32 gmask = 0x00ff0000; uint32_t gmask = 0x00ff0000;
Uint32 bmask = 0xff000000; uint32_t bmask = 0xff000000;
#else // little endian, like x86 #else // little endian, like x86
Uint32 amask = 0xff000000; uint32_t amask = 0xff000000;
Uint32 rmask = 0x00ff0000; uint32_t rmask = 0x00ff0000;
Uint32 gmask = 0x0000ff00; uint32_t gmask = 0x0000ff00;
Uint32 bmask = 0x000000ff; uint32_t bmask = 0x000000ff;
#endif #endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels, SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,

View file

@ -3,6 +3,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
SDL_Surface *read_xpm(char *xpm[]); SDL_Surface *
read_xpm(char *xpm[]);
#endif #endif

116
app/src/video_buffer.c Normal file
View file

@ -0,0 +1,116 @@
#include "video_buffer.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
bool
video_buffer_init(struct video_buffer *vb) {
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
goto error_2;
}
vb->interrupted = false;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);
return true;
error_2:
av_frame_free(&vb->rendering_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
mutex_lock(vb->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
#else
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
#endif
video_buffer_swap_frames(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
mutex_unlock(vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(vb->rendering_frame_consumed_cond);
#endif
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
#ifdef SKIP_FRAMES
(void) vb; // unused
#else
mutex_lock(vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
#endif
}

View file

@ -1,8 +1,8 @@
#ifndef FRAMES_H #ifndef VIDEO_BUFFER_H
#define FRAMES_H #define VIDEO_BUFFER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "config.h" #include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
@ -10,33 +10,40 @@
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
struct frames { struct video_buffer {
AVFrame *decoding_frame; AVFrame *decoding_frame;
AVFrame *rendering_frame; AVFrame *rendering_frame;
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef SKIP_FRAMES #ifndef SKIP_FRAMES
SDL_bool stopped; bool interrupted;
SDL_cond *rendering_frame_consumed_cond; SDL_cond *rendering_frame_consumed_cond;
#endif #endif
SDL_bool rendering_frame_consumed; bool rendering_frame_consumed;
struct fps_counter fps_counter; struct fps_counter fps_counter;
}; };
SDL_bool frames_init(struct frames *frames); bool
void frames_destroy(struct frames *frames); video_buffer_init(struct video_buffer *vb);
// set the decoder frame as ready for rendering void
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution // this function locks frames->mutex during its execution
// returns true if the previous frame had been consumed // the output flag is set to report whether the previous frame has been skipped
SDL_bool frames_offer_decoded_frame(struct frames *frames); void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// mark the rendering frame as consumed and return it // mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!! // MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before // the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex // unlocking frames->mutex
const AVFrame *frames_consume_rendered_frame(struct frames *frames); const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
// wake up and avoid any blocking call // wake up and avoid any blocking call
void frames_stop(struct frames *frames); void
video_buffer_interrupt(struct video_buffer *vb);
#endif #endif

View file

@ -12,6 +12,8 @@ public final class ControlEvent {
public static final int TYPE_COMMAND = 4; public static final int TYPE_COMMAND = 4;
public static final int COMMAND_BACK_OR_SCREEN_ON = 0; public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1;
public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2;
private int type; private int type;
private String text; private String text;

View file

@ -132,6 +132,14 @@ public final class Device {
this.rotationListener = rotationListener; this.rotationListener = rotationListener;
} }
public void expandNotificationPanel() {
serviceManager.getStatusBarManager().expandNotificationsPanel();
}
public void collapsePanels() {
serviceManager.getStatusBarManager().collapsePanels();
}
static Rect flipRect(Rect crop) { static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right); return new Rect(crop.top, crop.left, crop.bottom, crop.right);
} }

View file

@ -1,6 +1,7 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point; import android.graphics.Point;
import android.os.SystemClock; import android.os.SystemClock;
@ -172,6 +173,12 @@ public class EventController {
switch (action) { switch (action) {
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
return pressBackOrTurnScreenOn(); return pressBackOrTurnScreenOn();
case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel();
return true;
case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
return true;
default: default:
Ln.w("Unsupported command: " + action); Ln.w("Unsupported command: " + action);
} }

View file

@ -14,6 +14,7 @@ public final class ServiceManager {
private DisplayManager displayManager; private DisplayManager displayManager;
private InputManager inputManager; private InputManager inputManager;
private PowerManager powerManager; private PowerManager powerManager;
private StatusBarManager statusBarManager;
public ServiceManager() { public ServiceManager() {
try { try {
@ -60,4 +61,11 @@ public final class ServiceManager {
} }
return powerManager; return powerManager;
} }
public StatusBarManager getStatusBarManager() {
if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
}
return statusBarManager;
}
} }

View file

@ -0,0 +1,41 @@
package com.genymobile.scrcpy.wrappers;
import android.annotation.SuppressLint;
import android.os.IInterface;
import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StatusBarManager {
private final IInterface manager;
private final Method expandNotificationsPanelMethod;
private final Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) {
this.manager = manager;
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public void expandNotificationsPanel() {
try {
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public void collapsePanels() {
try {
collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}