diff --git a/app/.project b/app/.project new file mode 100644 index 00000000..3256dae8 --- /dev/null +++ b/app/.project @@ -0,0 +1,11 @@ + + + app + + + + + + + + diff --git a/app/meson.build b/app/meson.build index 0163dd7f..815e27c5 100644 --- a/app/meson.build +++ b/app/meson.build @@ -17,6 +17,7 @@ src = [ 'src/scrcpy.c', 'src/screen.c', 'src/server.c', + 'src/serve.c', 'src/stream.c', 'src/tiny_xpm.c', 'src/video_buffer.c', diff --git a/app/src/cli.c b/app/src/cli.c index c960727e..a5569ffe 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -8,6 +8,7 @@ #include "config.h" #include "scrcpy.h" +#include "serve.h" #include "util/log.h" #include "util/str_util.h" @@ -119,6 +120,11 @@ scrcpy_print_usage(const char *arg0) { " --record-format format\n" " Force recording format (either mp4 or mkv).\n" "\n" + " --serve tcp:localhost:1234\n" + " Open a socket to redirect video stream.\n" + " It will wait for a client to connect before starting the mirroring,\n" + " then it would forward the video stream.\n" + "\n" " --render-driver name\n" " Request SDL to use the given render driver (this is just a\n" " hint).\n" @@ -626,6 +632,169 @@ guess_record_format(const char *filename) { return 0; } +char** +str_split(const char *a_str, const char a_delim) { + char** result = 0; + size_t count = 0; + char* tmp = (char*)a_str; + char str[50]; + strncpy(str, a_str, sizeof(str)); + char* last_comma = 0; + char delim[2]; + delim[0] = a_delim; + delim[1] = 0; + + /* Count how many elements will be extracted. */ + while (*tmp) { + if (a_delim == *tmp) { + count++; + last_comma = tmp; + } + tmp++; + } + + /* Add space for trailing token. */ + count += last_comma < (str + strlen(str) - 1); + + /* Add space for terminating null string so caller + knows where the list of returned strings ends. */ + count++; + + result = malloc(sizeof(char*) * count); + + if (result) { + size_t idx = 0; + char* token = strtok(str, delim); + + while (token) { + assert(idx < count); + *(result + idx++) = strdup(token); + token = strtok(0, delim); + } + + assert(idx == count - 1); + *(result + idx) = 0; + } + + return result; +} + +bool +check_if_ip_valid(char *ip) { + int num, dots = 0; + char* ptr; + + if (ip == NULL) + return 0; + + ptr = strtok(ip, "."); //Cut the string using dot as delimiter + + if (ptr == NULL) + return false; + + while (ptr) { + long value; + if (!parse_integer(ptr, &value)) //Check whether the substring is holding only number or not + return false; + num = atoi(ptr); //Convert substring to number + if (num >= 0 && num <= 255) { + ptr = strtok(NULL, "."); //Cut the next part of the string + if (ptr != NULL) + dots++; //Increase the dot count + } else { + return false; + } + } + + if (dots != 3) + return false; + + return true; +} + +uint32_t +convert_ip_to_int(char* ip_string) { + int num, dots = 0; + char* ptr; + + char* ip = "0x"; + + ptr = strtok(ip_string, "."); //Cut the string using dot as delimiter + + while (ptr) { + num = atoi(ptr); //Convert substring to number + if (num >= 0 && num <= 255) { + char hex[3]; + sprintf(hex, "%X", num); + strcat(ip, hex); + ptr = strtok(NULL, "."); //Cut the next part of the string + if (ptr != NULL) + dots++; //Increase the dot count + } + } + + return atoi(ip); +} + +static bool +parse_serve_args(const char *optarg, const char **s_protocol, uint32_t *s_ip, uint16_t *s_port) { + bool protocol_valid = false; + bool ip_valid = false; + bool port_valid = false; + + char* protocol = NULL; + char* ip = NULL; + uint32_t ip_value; + char* port = NULL; + + char** values; + values = str_split(optarg, ':'); + + if (values) { + protocol = *values; + ip = *(values + 1); + port = *(values + 2); + } + + free(values); + + //Check if the choosen protocol is allowed + if (!strcmp(protocol, "tcp")) { + protocol_valid = true; + } else { + LOGE("Unexpected protocol (expected tcp)"); + return false; + } + + //Check if the choosen ip is valid + if (!strcmp(ip, "localhost")) { + ip_value = 0x7F000001; + ip_valid = true; + } else if (check_if_ip_valid(ip)) { + ip_value = convert_ip_to_int(ip); + ip_valid = true; + } else { + LOGE("Unexpected ip address (expected \"localhost\" or 255.255.255.255 format)"); + return false; + } + + //Check if the choosen port is valid + long port_value = 0; + port_valid = parse_integer_arg(port, &port_value, false, 0, 0xFFFF, "port"); + + //Check if everything is valid + if (!protocol_valid || !ip_valid || !port_valid) { + LOGE("Unexpected argument format (expected [tcp]:[ip or \"localhost\"]:[port])"); + return false; + } + + *s_protocol = protocol; + *s_ip = (uint32_t)ip_value; + *s_port = (uint16_t)port_value; + + return true; +} + #define OPT_RENDER_EXPIRED_FRAMES 1000 #define OPT_WINDOW_TITLE 1001 #define OPT_PUSH_TARGET 1002 @@ -649,6 +818,7 @@ guess_record_format(const char *filename) { #define OPT_DISABLE_SCREENSAVER 1020 #define OPT_SHORTCUT_MOD 1021 #define OPT_NO_KEY_REPEAT 1022 +#define OPT_SERVE 1023 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -683,6 +853,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"rotation", required_argument, NULL, OPT_ROTATION}, {"serial", required_argument, NULL, 's'}, {"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD}, + {"serve", required_argument, NULL, OPT_SERVE}, {"show-touches", no_argument, NULL, 't'}, {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, @@ -771,6 +942,14 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 'S': opts->turn_screen_off = true; break; + case OPT_SERVE: + if (!parse_serve_args(optarg, &opts->serve_protocol, &opts->serve_ip, &opts->serve_port)) { + return false; + } + else { + opts->serve = true; + } + break; case 't': opts->show_touches = true; break; @@ -860,8 +1039,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { } } - if (!opts->display && !opts->record_filename) { - LOGE("-N/--no-display requires screen recording (-r/--record)"); + if (!opts->display && !opts->record_filename && !opts->serve) { + LOGE("-N/--no-display requires screen recording (-r/--record) or to serve to another client (--serve)"); return false; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 45068cbb..963cca19 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -27,6 +27,7 @@ #include "recorder.h" #include "screen.h" #include "server.h" +#include "serve.h" #include "stream.h" #include "tiny_xpm.h" #include "video_buffer.h" @@ -41,6 +42,7 @@ static struct video_buffer video_buffer; static struct stream stream; static struct decoder decoder; static struct recorder recorder; +static struct serve serve; static struct controller controller; static struct file_handler file_handler; @@ -390,9 +392,19 @@ scrcpy(const struct scrcpy_options *options) { recorder_initialized = true; } + struct serve* serv = NULL; + if (options->serve) { + serve_init(&serve, options->serve_protocol, options->serve_ip, options->serve_port); + + if (!serve_start(&serve)) { + goto end; + } + serv = &serve; + } + av_log_set_callback(av_log_callback); - stream_init(&stream, server.video_socket, dec, rec); + stream_init(&stream, server.video_socket, dec, rec, serv); // now we consumed the header values, the socket receives the video stream // start the stream diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 86a2b57b..1e2b22e2 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -50,6 +50,7 @@ struct scrcpy_options { const char *window_title; const char *push_target; const char *render_driver; + const char *serve_protocol; const char *codec_options; enum sc_log_level log_level; enum sc_record_format record_format; @@ -65,6 +66,8 @@ struct scrcpy_options { uint16_t window_width; uint16_t window_height; uint16_t display_id; + uint32_t serve_ip; + uint16_t serve_port; bool show_touches; bool fullscreen; bool always_on_top; @@ -75,6 +78,7 @@ struct scrcpy_options { bool prefer_text; bool window_borderless; bool mipmaps; + bool serve; bool stay_awake; bool force_adb_forward; bool disable_screensaver; @@ -88,6 +92,7 @@ struct scrcpy_options { .window_title = NULL, \ .push_target = NULL, \ .render_driver = NULL, \ + .serve_protocol = NULL, \ .codec_options = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ .record_format = SC_RECORD_FORMAT_AUTO, \ @@ -109,6 +114,8 @@ struct scrcpy_options { .window_width = 0, \ .window_height = 0, \ .display_id = 0, \ + .serve_ip = 0, \ + .serve_port = 0, \ .show_touches = false, \ .fullscreen = false, \ .always_on_top = false, \ @@ -119,6 +126,7 @@ struct scrcpy_options { .prefer_text = false, \ .window_borderless = false, \ .mipmaps = true, \ + .serve = false, \ .stay_awake = false, \ .force_adb_forward = false, \ .disable_screensaver = false, \ diff --git a/app/src/serve.c b/app/src/serve.c new file mode 100644 index 00000000..b969dc8d --- /dev/null +++ b/app/src/serve.c @@ -0,0 +1,61 @@ +#include "serve.h" + +#include +#include +#include + +#include "config.h" +#include "events.h" +#include "util/log.h" +#include "util/net.h" + +# define SOCKET_ERROR (-1) + +void +serve_init(struct serve* serve, char *protocol, uint32_t ip, uint16_t port) { + serve->protocol = protocol; + serve->ip = ip; + serve->port = port; +} + +bool +serve_start(struct serve* serve) { + LOGD("Starting serve thread"); + + socket_t Listensocket; + socket_t ClientSocket; + + Listensocket = net_listen(serve->ip, serve->port, 1); + if (Listensocket == INVALID_SOCKET) { + LOGI("Listen error"); + net_close(Listensocket); + return 0; + } + + LOGI("Waiting for a client to connect"); + + ClientSocket = net_accept(Listensocket); + if (ClientSocket == INVALID_SOCKET) { + LOGI("Client error"); + net_close(Listensocket); + return 0; + } + + LOGI("Client found"); + + net_close(Listensocket); + + serve->socket = ClientSocket; + + return true; +} + +bool +serve_push(struct serve* serve, const AVPacket *packet) { + if (net_send(serve->socket, packet->data, packet->size) == SOCKET_ERROR) { + LOGI("Client lost"); + net_close(serve->socket); + return false; + } + return true; +} \ No newline at end of file diff --git a/app/src/serve.h b/app/src/serve.h new file mode 100644 index 00000000..885fcef3 --- /dev/null +++ b/app/src/serve.h @@ -0,0 +1,28 @@ +#ifndef SERVE_H +#define SERVE_H + +#include +#include +#include +#include +#include + +#include "config.h" +#include "util/net.h" + +struct serve { + socket_t socket; + char *protocol; + uint32_t ip; + uint16_t port; +}; + +void +serve_init(struct serve* serve, char* protocol, uint32_t ip, uint16_t port); + +bool +serve_start(struct serve* serve); + +bool +serve_push(struct serve* serve, const AVPacket *packet); +#endif \ No newline at end of file diff --git a/app/src/stream.c b/app/src/stream.c index dd2dbd76..557cff06 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -13,6 +13,7 @@ #include "decoder.h" #include "events.h" #include "recorder.h" +#include "serve.h" #include "util/buffer_util.h" #include "util/log.h" @@ -93,6 +94,15 @@ process_frame(struct stream *stream, AVPacket *packet) { } } + if (stream->serve) { + packet->dts = packet->pts; + + if (!serve_push(stream->serve, packet)) { + LOGE("Could not serve packet"); + return false; + } + } + return true; } @@ -270,10 +280,11 @@ end: void stream_init(struct stream *stream, socket_t socket, - struct decoder *decoder, struct recorder *recorder) { + struct decoder *decoder, struct recorder *recorder, struct serve *serve) { stream->socket = socket; stream->decoder = decoder, stream->recorder = recorder; + stream->serve = serve; stream->has_pending = false; } diff --git a/app/src/stream.h b/app/src/stream.h index f7c5e475..fb82f802 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -18,6 +18,7 @@ struct stream { SDL_Thread *thread; struct decoder *decoder; struct recorder *recorder; + struct serve *serve; AVCodecContext *codec_ctx; AVCodecParserContext *parser; // successive packets may need to be concatenated, until a non-config @@ -28,7 +29,7 @@ struct stream { void stream_init(struct stream *stream, socket_t socket, - struct decoder *decoder, struct recorder *recorder); + struct decoder *decoder, struct recorder *recorder, struct serve *serve); bool stream_start(struct stream *stream); diff --git a/app/src/sys/unix/net.c b/app/src/sys/unix/net.c new file mode 100644 index 00000000..d67a660f --- /dev/null +++ b/app/src/sys/unix/net.c @@ -0,0 +1,21 @@ +#include "util/net.h" + +#include + +#include "config.h" + +bool +net_init(void) { + // do nothing + return true; +} + +void +net_cleanup(void) { + // do nothing +} + +bool +net_close(socket_t socket) { + return !close(socket); +} diff --git a/app/src/sys/win/net.c b/app/src/sys/win/net.c new file mode 100644 index 00000000..aebce7fc --- /dev/null +++ b/app/src/sys/win/net.c @@ -0,0 +1,25 @@ +#include "util/net.h" + +#include "config.h" +#include "util/log.h" + +bool +net_init(void) { + WSADATA wsa; + int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; + if (res < 0) { + LOGC("WSAStartup failed with error %d", res); + return false; + } + return true; +} + +void +net_cleanup(void) { + WSACleanup(); +} + +bool +net_close(socket_t socket) { + return !closesocket(socket); +}