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);
+}