Add --time-limit
Add an option to stop scrcpy automatically after a given delay. PR #4052 <https://github.com/Genymobile/scrcpy/pull/4052> Fixes #3752 <https://github.com/Genymobile/scrcpy/issues/3752>
This commit is contained in:
parent
5042f8de93
commit
d3c2955fb9
12 changed files with 216 additions and 0 deletions
|
@ -60,6 +60,7 @@ _scrcpy() {
|
|||
-t --show-touches
|
||||
--tcpip
|
||||
--tcpip=
|
||||
--time-limit=
|
||||
--tunnel-host=
|
||||
--tunnel-port=
|
||||
--v4l2-buffer=
|
||||
|
|
|
@ -65,6 +65,7 @@ arguments=(
|
|||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||
{-t,--show-touches}'[Show physical touches]'
|
||||
'--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]'
|
||||
'--time-limit=[Set the maximum mirroring time, in seconds]'
|
||||
'--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]'
|
||||
'--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]'
|
||||
'--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]'
|
||||
|
|
|
@ -51,6 +51,7 @@ src = [
|
|||
'src/util/term.c',
|
||||
'src/util/thread.c',
|
||||
'src/util/tick.c',
|
||||
'src/util/timeout.c',
|
||||
]
|
||||
|
||||
conf = configuration_data()
|
||||
|
|
|
@ -354,6 +354,10 @@ If a destination address is provided, then scrcpy connects to this address befor
|
|||
|
||||
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
||||
|
||||
.TP
|
||||
.BI "\-\-time\-limit " seconds
|
||||
Set the maximum mirroring time, in seconds.
|
||||
|
||||
.TP
|
||||
.BI "\-\-tunnel\-host " ip
|
||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||
|
|
|
@ -78,6 +78,7 @@ enum {
|
|||
OPT_NO_VIDEO_PLAYBACK,
|
||||
OPT_AUDIO_SOURCE,
|
||||
OPT_KILL_ADB_ON_CLOSE,
|
||||
OPT_TIME_LIMIT,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
|
@ -580,6 +581,12 @@ static const struct sc_option options[] = {
|
|||
"connected over USB), enables TCP/IP mode, then connects to "
|
||||
"this address before starting.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_TIME_LIMIT,
|
||||
.longopt = "time-limit",
|
||||
.argdesc = "seconds",
|
||||
.text = "Set the maximum mirroring time, in seconds.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_TUNNEL_HOST,
|
||||
.longopt = "tunnel-host",
|
||||
|
@ -1618,6 +1625,18 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_time_limit(const char *s, sc_tick *tick) {
|
||||
long value;
|
||||
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "time limit");
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*tick = SC_TICK_FROM_SEC(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
const char *optstring, const struct option *longopts) {
|
||||
|
@ -1953,6 +1972,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||
case OPT_KILL_ADB_ON_CLOSE:
|
||||
opts->kill_adb_on_close = true;
|
||||
break;
|
||||
case OPT_TIME_LIMIT:
|
||||
if (!parse_time_limit(optarg, &opts->time_limit)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
||||
|
|
|
@ -42,6 +42,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||
.display_buffer = 0,
|
||||
.audio_buffer = SC_TICK_FROM_MS(50),
|
||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||
.time_limit = 0,
|
||||
#ifdef HAVE_V4L2
|
||||
.v4l2_device = NULL,
|
||||
.v4l2_buffer = 0,
|
||||
|
|
|
@ -142,6 +142,7 @@ struct scrcpy_options {
|
|||
sc_tick display_buffer;
|
||||
sc_tick audio_buffer;
|
||||
sc_tick audio_output_buffer;
|
||||
sc_tick time_limit;
|
||||
#ifdef HAVE_V4L2
|
||||
const char *v4l2_device;
|
||||
sc_tick v4l2_buffer;
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/rand.h"
|
||||
#include "util/timeout.h"
|
||||
#ifdef HAVE_V4L2
|
||||
# include "v4l2_sink.h"
|
||||
#endif
|
||||
|
@ -73,6 +74,7 @@ struct scrcpy {
|
|||
struct sc_hid_mouse mouse_hid;
|
||||
#endif
|
||||
};
|
||||
struct sc_timeout timeout;
|
||||
};
|
||||
|
||||
static inline void
|
||||
|
@ -171,6 +173,9 @@ event_loop(struct scrcpy *s) {
|
|||
case SC_EVENT_RECORDER_ERROR:
|
||||
LOGE("Recorder error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||
LOGI("Time limit reached");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
|
@ -280,6 +285,14 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
|||
// event
|
||||
}
|
||||
|
||||
static void
|
||||
sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
|
||||
(void) timeout;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED);
|
||||
}
|
||||
|
||||
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||
static uint32_t
|
||||
scrcpy_generate_scid() {
|
||||
|
@ -321,6 +334,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
bool screen_initialized = false;
|
||||
bool timeout_initialized = false;
|
||||
bool timeout_started = false;
|
||||
|
||||
struct sc_acksync *acksync = NULL;
|
||||
|
||||
|
@ -743,6 +758,27 @@ aoa_hid_end:
|
|||
}
|
||||
}
|
||||
|
||||
if (options->time_limit) {
|
||||
bool ok = sc_timeout_init(&s->timeout);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
timeout_initialized = true;
|
||||
|
||||
sc_tick deadline = sc_tick_now() + options->time_limit;
|
||||
static const struct sc_timeout_callbacks cbs = {
|
||||
.on_timeout = sc_timeout_on_timeout,
|
||||
};
|
||||
|
||||
ok = sc_timeout_start(&s->timeout, deadline, &cbs, NULL);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
timeout_started = true;
|
||||
}
|
||||
|
||||
ret = event_loop(s);
|
||||
LOGD("quit...");
|
||||
|
||||
|
@ -751,6 +787,10 @@ aoa_hid_end:
|
|||
sc_screen_hide_window(&s->screen);
|
||||
|
||||
end:
|
||||
if (timeout_started) {
|
||||
sc_timeout_stop(&s->timeout);
|
||||
}
|
||||
|
||||
// The demuxer is not stopped explicitly, because it will stop by itself on
|
||||
// end-of-stream
|
||||
#ifdef HAVE_USB
|
||||
|
@ -786,6 +826,13 @@ end:
|
|||
sc_server_stop(&s->server);
|
||||
}
|
||||
|
||||
if (timeout_started) {
|
||||
sc_timeout_join(&s->timeout);
|
||||
}
|
||||
if (timeout_initialized) {
|
||||
sc_timeout_destroy(&s->timeout);
|
||||
}
|
||||
|
||||
// now that the sockets are shutdown, the demuxer and controller are
|
||||
// interrupted, we can join them
|
||||
if (video_demuxer_started) {
|
||||
|
|
77
app/src/util/timeout.c
Normal file
77
app/src/util/timeout.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "timeout.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
bool
|
||||
sc_timeout_init(struct sc_timeout *timeout) {
|
||||
bool ok = sc_mutex_init(&timeout->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&timeout->cond);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timeout->stopped = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_timeout(void *data) {
|
||||
struct sc_timeout *timeout = data;
|
||||
sc_tick deadline = timeout->deadline;
|
||||
|
||||
sc_mutex_lock(&timeout->mutex);
|
||||
bool timed_out = false;
|
||||
while (!timeout->stopped && !timed_out) {
|
||||
timed_out = !sc_cond_timedwait(&timeout->cond, &timeout->mutex,
|
||||
deadline);
|
||||
}
|
||||
sc_mutex_unlock(&timeout->mutex);
|
||||
|
||||
timeout->cbs->on_timeout(timeout, timeout->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline,
|
||||
const struct sc_timeout_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = sc_thread_create(&timeout->thread, run_timeout, "scrcpy-timeout",
|
||||
timeout);
|
||||
if (!ok) {
|
||||
LOGE("Timeout: could not start thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
timeout->deadline = deadline;
|
||||
|
||||
assert(cbs && cbs->on_timeout);
|
||||
timeout->cbs = cbs;
|
||||
timeout->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_timeout_stop(struct sc_timeout *timeout) {
|
||||
sc_mutex_lock(&timeout->mutex);
|
||||
timeout->stopped = true;
|
||||
sc_mutex_unlock(&timeout->mutex);
|
||||
}
|
||||
|
||||
void
|
||||
sc_timeout_join(struct sc_timeout *timeout) {
|
||||
sc_thread_join(&timeout->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sc_timeout_destroy(struct sc_timeout *timeout) {
|
||||
sc_mutex_destroy(&timeout->mutex);
|
||||
sc_cond_destroy(&timeout->cond);
|
||||
}
|
43
app/src/util/timeout.h
Normal file
43
app/src/util/timeout.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef SC_TIMEOUT_H
|
||||
#define SC_TIMEOUT_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "thread.h"
|
||||
#include "tick.h"
|
||||
|
||||
struct sc_timeout {
|
||||
sc_thread thread;
|
||||
sc_tick deadline;
|
||||
|
||||
sc_mutex mutex;
|
||||
sc_cond cond;
|
||||
bool stopped;
|
||||
|
||||
const struct sc_timeout_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_timeout_callbacks {
|
||||
void (*on_timeout)(struct sc_timeout *timeout, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_timeout_init(struct sc_timeout *timeout);
|
||||
|
||||
void
|
||||
sc_timeout_destroy(struct sc_timeout *timeout);
|
||||
|
||||
bool
|
||||
sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline,
|
||||
const struct sc_timeout_callbacks *cbs, void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_timeout_stop(struct sc_timeout *timeout);
|
||||
|
||||
void
|
||||
sc_timeout_join(struct sc_timeout *timeout);
|
||||
|
||||
#endif
|
|
@ -61,3 +61,18 @@ It is also possible to disable video and audio playback separately:
|
|||
# Record both video and audio, but only play video
|
||||
scrcpy --record=file.mkv --no-audio-playback
|
||||
```
|
||||
|
||||
## Time limit
|
||||
|
||||
To limit the recording time:
|
||||
|
||||
```bash
|
||||
scrcpy --record=file.mkv --time-limit=20 # in seconds
|
||||
```
|
||||
|
||||
The `--time-limit` option is not limited to recording, it also impacts simple
|
||||
mirroring:
|
||||
|
||||
```
|
||||
scrcpy --time-limit=20
|
||||
```
|
||||
|
|
Loading…
Reference in a new issue