Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3c2955fb9 | ||
|
|
5042f8de93 | ||
|
|
7536f95d1c | ||
|
|
6832e8d629 | ||
|
|
28313631e5 | ||
|
|
fdbc9397a7 | ||
|
|
a3cdf1a6b8 | ||
|
|
b16d4d1835 | ||
|
|
b8d43866d2 | ||
|
|
2d79aeb117 | ||
|
|
888a5aae7d | ||
|
|
323ea2f1d9 | ||
|
|
9ca554ca41 | ||
|
|
9d3c656414 | ||
|
|
379caf8551 | ||
|
|
2aec7b4c9d | ||
|
|
fc52b24503 |
@@ -16,26 +16,27 @@ _scrcpy() {
|
|||||||
--display=
|
--display=
|
||||||
--display-buffer=
|
--display-buffer=
|
||||||
-e --select-tcpip
|
-e --select-tcpip
|
||||||
|
-f --fullscreen
|
||||||
--force-adb-forward
|
--force-adb-forward
|
||||||
--forward-all-clicks
|
--forward-all-clicks
|
||||||
-f --fullscreen
|
|
||||||
-K --hid-keyboard
|
|
||||||
-h --help
|
-h --help
|
||||||
|
--kill-adb-on-close
|
||||||
|
-K --hid-keyboard
|
||||||
--legacy-paste
|
--legacy-paste
|
||||||
--list-displays
|
--list-displays
|
||||||
--list-encoders
|
--list-encoders
|
||||||
--lock-video-orientation
|
--lock-video-orientation
|
||||||
--lock-video-orientation=
|
--lock-video-orientation=
|
||||||
--max-fps=
|
|
||||||
-M --hid-mouse
|
|
||||||
-m --max-size=
|
-m --max-size=
|
||||||
|
-M --hid-mouse
|
||||||
|
--max-fps=
|
||||||
|
-n --no-control
|
||||||
|
-N --no-playback
|
||||||
--no-audio
|
--no-audio
|
||||||
--no-audio-playback
|
--no-audio-playback
|
||||||
--no-cleanup
|
--no-cleanup
|
||||||
--no-clipboard-autosync
|
--no-clipboard-autosync
|
||||||
--no-downsize-on-error
|
--no-downsize-on-error
|
||||||
-n --no-control
|
|
||||||
-N --no-playback
|
|
||||||
--no-key-repeat
|
--no-key-repeat
|
||||||
--no-mipmaps
|
--no-mipmaps
|
||||||
--no-power-on
|
--no-power-on
|
||||||
@@ -47,24 +48,25 @@ _scrcpy() {
|
|||||||
--prefer-text
|
--prefer-text
|
||||||
--print-fps
|
--print-fps
|
||||||
--push-target=
|
--push-target=
|
||||||
--raw-key-events
|
|
||||||
-r --record=
|
-r --record=
|
||||||
|
--raw-key-events
|
||||||
--record-format=
|
--record-format=
|
||||||
--render-driver=
|
--render-driver=
|
||||||
--require-audio
|
--require-audio
|
||||||
--rotation=
|
--rotation=
|
||||||
-s --serial=
|
-s --serial=
|
||||||
--shortcut-mod=
|
|
||||||
-S --turn-screen-off
|
-S --turn-screen-off
|
||||||
|
--shortcut-mod=
|
||||||
-t --show-touches
|
-t --show-touches
|
||||||
--tcpip
|
--tcpip
|
||||||
--tcpip=
|
--tcpip=
|
||||||
|
--time-limit=
|
||||||
--tunnel-host=
|
--tunnel-host=
|
||||||
--tunnel-port=
|
--tunnel-port=
|
||||||
--v4l2-buffer=
|
--v4l2-buffer=
|
||||||
--v4l2-sink=
|
--v4l2-sink=
|
||||||
-V --verbosity=
|
|
||||||
-v --version
|
-v --version
|
||||||
|
-V --verbosity=
|
||||||
--video-codec=
|
--video-codec=
|
||||||
--video-codec-options=
|
--video-codec-options=
|
||||||
--video-encoder=
|
--video-encoder=
|
||||||
|
|||||||
@@ -23,25 +23,26 @@ arguments=(
|
|||||||
'--display=[Specify the display id to mirror]'
|
'--display=[Specify the display id to mirror]'
|
||||||
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
|
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
|
||||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||||
|
{-f,--fullscreen}'[Start in fullscreen]'
|
||||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
||||||
'--forward-all-clicks[Forward clicks to device]'
|
'--forward-all-clicks[Forward clicks to device]'
|
||||||
{-f,--fullscreen}'[Start in fullscreen]'
|
|
||||||
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
|
|
||||||
{-h,--help}'[Print the help]'
|
{-h,--help}'[Print the help]'
|
||||||
|
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||||
|
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
|
||||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||||
'--list-displays[List displays available on the device]'
|
'--list-displays[List displays available on the device]'
|
||||||
'--list-encoders[List video and audio encoders available on the device]'
|
'--list-encoders[List video and audio encoders available on the device]'
|
||||||
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
|
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
|
||||||
'--max-fps=[Limit the frame rate of screen capture]'
|
|
||||||
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
|
|
||||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||||
|
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
|
||||||
|
'--max-fps=[Limit the frame rate of screen capture]'
|
||||||
|
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
|
||||||
|
{-N,--no-playback}'[Disable video and audio playback]'
|
||||||
'--no-audio[Disable audio forwarding]'
|
'--no-audio[Disable audio forwarding]'
|
||||||
'--no-audio-playback[Disable audio playback]'
|
'--no-audio-playback[Disable audio playback]'
|
||||||
'--no-cleanup[Disable device cleanup actions on exit]'
|
'--no-cleanup[Disable device cleanup actions on exit]'
|
||||||
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
|
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
|
||||||
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
||||||
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
|
|
||||||
{-N,--no-playback}'[Disable video and audio playback]'
|
|
||||||
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
||||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||||
'--no-power-on[Do not power on the device on start]'
|
'--no-power-on[Do not power on the device on start]'
|
||||||
@@ -53,23 +54,24 @@ arguments=(
|
|||||||
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
|
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
|
||||||
'--print-fps[Start FPS counter, to print frame logs to the console]'
|
'--print-fps[Start FPS counter, to print frame logs to the console]'
|
||||||
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
|
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
|
||||||
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
|
|
||||||
{-r,--record=}'[Record screen to file]:record file:_files'
|
{-r,--record=}'[Record screen to file]:record file:_files'
|
||||||
|
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
|
||||||
'--record-format=[Force recording format]:format:(mp4 mkv)'
|
'--record-format=[Force recording format]:format:(mp4 mkv)'
|
||||||
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
|
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
|
||||||
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
||||||
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
|
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
|
||||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
|
||||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||||
|
'--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]'
|
{-t,--show-touches}'[Show physical touches]'
|
||||||
'--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]'
|
'--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-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]'
|
'--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]'
|
'--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]'
|
||||||
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
|
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
|
||||||
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
|
|
||||||
{-v,--version}'[Print the version of scrcpy]'
|
{-v,--version}'[Print the version of scrcpy]'
|
||||||
|
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
|
||||||
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
|
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
|
||||||
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
|
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
|
||||||
'--video-encoder=[Use a specific MediaCodec video encoder]'
|
'--video-encoder=[Use a specific MediaCodec video encoder]'
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ src = [
|
|||||||
'src/util/term.c',
|
'src/util/term.c',
|
||||||
'src/util/thread.c',
|
'src/util/thread.c',
|
||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
|
'src/util/timeout.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
|
|||||||
84
app/scrcpy.1
84
app/scrcpy.1
@@ -113,6 +113,10 @@ Use TCP/IP device (if there is exactly one, like adb -e).
|
|||||||
|
|
||||||
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-f, \-\-fullscreen
|
||||||
|
Start in fullscreen.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-force\-adb\-forward
|
.B \-\-force\-adb\-forward
|
||||||
Do not attempt to use "adb reverse" to connect to the device.
|
Do not attempt to use "adb reverse" to connect to the device.
|
||||||
@@ -121,14 +125,14 @@ Do not attempt to use "adb reverse" to connect to the device.
|
|||||||
.B \-\-forward\-all\-clicks
|
.B \-\-forward\-all\-clicks
|
||||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-f, \-\-fullscreen
|
|
||||||
Start in fullscreen.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-kill\-adb\-on\-close
|
||||||
|
Kill adb when scrcpy terminates.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-K, \-\-hid\-keyboard
|
.B \-K, \-\-hid\-keyboard
|
||||||
Simulate a physical keyboard by using HID over AOAv2.
|
Simulate a physical keyboard by using HID over AOAv2.
|
||||||
@@ -167,10 +171,6 @@ Default is "unlocked".
|
|||||||
|
|
||||||
Passing the option without argument is equivalent to passing "initial".
|
Passing the option without argument is equivalent to passing "initial".
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-max\-fps " value
|
|
||||||
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-m, \-\-max\-size " value
|
.BI "\-m, \-\-max\-size " value
|
||||||
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
||||||
@@ -189,6 +189,18 @@ It may only work over USB.
|
|||||||
|
|
||||||
Also see \fB\-\-hid\-keyboard\fR.
|
Also see \fB\-\-hid\-keyboard\fR.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-max\-fps " value
|
||||||
|
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-n, \-\-no\-control
|
||||||
|
Disable device control (mirror the device in read\-only).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-N, \-\-no\-playback
|
||||||
|
Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-audio
|
.B \-\-no\-audio
|
||||||
Disable audio forwarding.
|
Disable audio forwarding.
|
||||||
@@ -215,14 +227,6 @@ By default, on MediaCodec error, scrcpy automatically tries again with a lower d
|
|||||||
|
|
||||||
This option disables this behavior.
|
This option disables this behavior.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-n, \-\-no\-control
|
|
||||||
Disable device control (mirror the device in read\-only).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-N, \-\-no\-playback
|
|
||||||
Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-key\-repeat
|
.B \-\-no\-key\-repeat
|
||||||
Do not forward repeated key events when a key is held down.
|
Do not forward repeated key events when a key is held down.
|
||||||
@@ -284,10 +288,6 @@ Set the target directory for pushing files to the device by drag & drop. It is p
|
|||||||
|
|
||||||
Default is "/sdcard/Download/".
|
Default is "/sdcard/Download/".
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-raw\-key\-events
|
|
||||||
Inject key events for all input keys, and ignore text events.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-r, \-\-record " file
|
.BI "\-r, \-\-record " file
|
||||||
Record screen to
|
Record screen to
|
||||||
@@ -297,6 +297,10 @@ The format is determined by the
|
|||||||
.B \-\-record\-format
|
.B \-\-record\-format
|
||||||
option if set, or by the file extension (.mp4 or .mkv).
|
option if set, or by the file extension (.mp4 or .mkv).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-raw\-key\-events
|
||||||
|
Inject key events for all input keys, and ignore text events.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-record\-format " format
|
.BI "\-\-record\-format " format
|
||||||
Force recording format (either mp4 or mkv).
|
Force recording format (either mp4 or mkv).
|
||||||
@@ -322,6 +326,10 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
|
|||||||
.BI "\-s, \-\-serial " number
|
.BI "\-s, \-\-serial " number
|
||||||
The device serial number. Mandatory only if several devices are connected to adb.
|
The device serial number. Mandatory only if several devices are connected to adb.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-S, \-\-turn\-screen\-off
|
||||||
|
Turn the device screen off immediately.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
||||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||||
@@ -332,6 +340,12 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
|
|||||||
|
|
||||||
Default is "lalt,lsuper" (left-Alt or left-Super).
|
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-t, \-\-show\-touches
|
||||||
|
Enable "show touches" on start, restore the initial value on exit.
|
||||||
|
|
||||||
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
|
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
|
||||||
Configure and reconnect the device over TCP/IP.
|
Configure and reconnect the device over TCP/IP.
|
||||||
@@ -341,14 +355,8 @@ 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.
|
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
|
.TP
|
||||||
.B \-S, \-\-turn\-screen\-off
|
.BI "\-\-time\-limit " seconds
|
||||||
Turn the device screen off immediately.
|
Set the maximum mirroring time, in seconds.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-t, \-\-show\-touches
|
|
||||||
Enable "show touches" on start, restore the initial value on exit.
|
|
||||||
|
|
||||||
It only shows physical touches (not clicks from scrcpy).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-tunnel\-host " ip
|
.BI "\-\-tunnel\-host " ip
|
||||||
@@ -362,6 +370,16 @@ Set the TCP port of the adb tunnel to reach the scrcpy server. This option autom
|
|||||||
|
|
||||||
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-v, \-\-version
|
||||||
|
Print the version of scrcpy.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-V, \-\-verbosity " value
|
||||||
|
Set the log level ("verbose", "debug", "info", "warn" or "error").
|
||||||
|
|
||||||
|
Default is "info" for release builds, "debug" for debug builds.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-v4l2-sink " /dev/videoN
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
Output to v4l2loopback device.
|
Output to v4l2loopback device.
|
||||||
@@ -376,16 +394,6 @@ This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
|
|||||||
|
|
||||||
Default is 0 (no buffering).
|
Default is 0 (no buffering).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-V, \-\-verbosity " value
|
|
||||||
Set the log level ("verbose", "debug", "info", "warn" or "error").
|
|
||||||
|
|
||||||
Default is "info" for release builds, "debug" for debug builds.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-v, \-\-version
|
|
||||||
Print the version of scrcpy.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-video\-codec " name
|
.BI "\-\-video\-codec " name
|
||||||
Select a video codec (h264, h265 or av1).
|
Select a video codec (h264, h265 or av1).
|
||||||
|
|||||||
143
app/src/cli.c
143
app/src/cli.c
@@ -77,6 +77,8 @@ enum {
|
|||||||
OPT_NO_AUDIO_PLAYBACK,
|
OPT_NO_AUDIO_PLAYBACK,
|
||||||
OPT_NO_VIDEO_PLAYBACK,
|
OPT_NO_VIDEO_PLAYBACK,
|
||||||
OPT_AUDIO_SOURCE,
|
OPT_AUDIO_SOURCE,
|
||||||
|
OPT_KILL_ADB_ON_CLOSE,
|
||||||
|
OPT_TIME_LIMIT,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@@ -257,6 +259,11 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "encoder",
|
.longopt = "encoder",
|
||||||
.argdesc = "name",
|
.argdesc = "name",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'f',
|
||||||
|
.longopt = "fullscreen",
|
||||||
|
.text = "Start in fullscreen.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
||||||
.longopt = "force-adb-forward",
|
.longopt = "force-adb-forward",
|
||||||
@@ -271,9 +278,14 @@ static const struct sc_option options[] = {
|
|||||||
"shortcuts and forwards the clicks to the device instead.",
|
"shortcuts and forwards the clicks to the device instead.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'f',
|
.shortopt = 'h',
|
||||||
.longopt = "fullscreen",
|
.longopt = "help",
|
||||||
.text = "Start in fullscreen.",
|
.text = "Print this help.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
||||||
|
.longopt = "kill-adb-on-close",
|
||||||
|
.text = "Kill adb when scrcpy terminates.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'K',
|
.shortopt = 'K',
|
||||||
@@ -292,11 +304,6 @@ static const struct sc_option options[] = {
|
|||||||
"is enabled (or a physical keyboard is connected).\n"
|
"is enabled (or a physical keyboard is connected).\n"
|
||||||
"Also see --hid-mouse.",
|
"Also see --hid-mouse.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortopt = 'h',
|
|
||||||
.longopt = "help",
|
|
||||||
.text = "Print this help.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_LEGACY_PASTE,
|
.longopt_id = OPT_LEGACY_PASTE,
|
||||||
.longopt = "legacy-paste",
|
.longopt = "legacy-paste",
|
||||||
@@ -330,11 +337,13 @@ static const struct sc_option options[] = {
|
|||||||
"\"initial\".",
|
"\"initial\".",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_MAX_FPS,
|
.shortopt = 'm',
|
||||||
.longopt = "max-fps",
|
.longopt = "max-size",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.text = "Limit the frame rate of screen capture (officially supported "
|
.text = "Limit both the width and height of the video to value. The "
|
||||||
"since Android 10, but may work on earlier versions).",
|
"other dimension is computed so that the device aspect-ratio "
|
||||||
|
"is preserved.\n"
|
||||||
|
"Default is 0 (unlimited).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'M',
|
.shortopt = 'M',
|
||||||
@@ -348,13 +357,22 @@ static const struct sc_option options[] = {
|
|||||||
"Also see --hid-keyboard.",
|
"Also see --hid-keyboard.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'm',
|
.longopt_id = OPT_MAX_FPS,
|
||||||
.longopt = "max-size",
|
.longopt = "max-fps",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.text = "Limit both the width and height of the video to value. The "
|
.text = "Limit the frame rate of screen capture (officially supported "
|
||||||
"other dimension is computed so that the device aspect-ratio "
|
"since Android 10, but may work on earlier versions).",
|
||||||
"is preserved.\n"
|
},
|
||||||
"Default is 0 (unlimited).",
|
{
|
||||||
|
.shortopt = 'n',
|
||||||
|
.longopt = "no-control",
|
||||||
|
.text = "Disable device control (mirror the device in read-only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'N',
|
||||||
|
.longopt = "no-playback",
|
||||||
|
.text = "Disable video and audio playback on the computer (equivalent "
|
||||||
|
"to --no-video-playback --no-audio-playback).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_AUDIO,
|
.longopt_id = OPT_NO_AUDIO,
|
||||||
@@ -390,17 +408,6 @@ static const struct sc_option options[] = {
|
|||||||
"again with a lower definition.\n"
|
"again with a lower definition.\n"
|
||||||
"This option disables this behavior.",
|
"This option disables this behavior.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortopt = 'n',
|
|
||||||
.longopt = "no-control",
|
|
||||||
.text = "Disable device control (mirror the device in read-only).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'N',
|
|
||||||
.longopt = "no-playback",
|
|
||||||
.text = "Disable video and audio playback on the computer (equivalent "
|
|
||||||
"to --no-video-playback --no-audio-playback).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// deprecated
|
// deprecated
|
||||||
.longopt_id = OPT_NO_DISPLAY,
|
.longopt_id = OPT_NO_DISPLAY,
|
||||||
@@ -484,11 +491,6 @@ static const struct sc_option options[] = {
|
|||||||
"drag & drop. It is passed as is to \"adb push\".\n"
|
"drag & drop. It is passed as is to \"adb push\".\n"
|
||||||
"Default is \"/sdcard/Download/\".",
|
"Default is \"/sdcard/Download/\".",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_RAW_KEY_EVENTS,
|
|
||||||
.longopt = "raw-key-events",
|
|
||||||
.text = "Inject key events for all input keys, and ignore text events."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortopt = 'r',
|
.shortopt = 'r',
|
||||||
.longopt = "record",
|
.longopt = "record",
|
||||||
@@ -497,6 +499,11 @@ static const struct sc_option options[] = {
|
|||||||
"The format is determined by the --record-format option if "
|
"The format is determined by the --record-format option if "
|
||||||
"set, or by the file extension (.mp4 or .mkv).",
|
"set, or by the file extension (.mp4 or .mkv).",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_RAW_KEY_EVENTS,
|
||||||
|
.longopt = "raw-key-events",
|
||||||
|
.text = "Inject key events for all input keys, and ignore text events."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_RECORD_FORMAT,
|
.longopt_id = OPT_RECORD_FORMAT,
|
||||||
.longopt = "record-format",
|
.longopt = "record-format",
|
||||||
@@ -535,6 +542,11 @@ static const struct sc_option options[] = {
|
|||||||
.text = "The device serial number. Mandatory only if several devices "
|
.text = "The device serial number. Mandatory only if several devices "
|
||||||
"are connected to adb.",
|
"are connected to adb.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'S',
|
||||||
|
.longopt = "turn-screen-off",
|
||||||
|
.text = "Turn the device screen off immediately.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_SHORTCUT_MOD,
|
.longopt_id = OPT_SHORTCUT_MOD,
|
||||||
.longopt = "shortcut-mod",
|
.longopt = "shortcut-mod",
|
||||||
@@ -548,11 +560,6 @@ static const struct sc_option options[] = {
|
|||||||
"shortcuts, pass \"lctrl+lalt,lsuper\".\n"
|
"shortcuts, pass \"lctrl+lalt,lsuper\".\n"
|
||||||
"Default is \"lalt,lsuper\" (left-Alt or left-Super).",
|
"Default is \"lalt,lsuper\" (left-Alt or left-Super).",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortopt = 'S',
|
|
||||||
.longopt = "turn-screen-off",
|
|
||||||
.text = "Turn the device screen off immediately.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortopt = 't',
|
.shortopt = 't',
|
||||||
.longopt = "show-touches",
|
.longopt = "show-touches",
|
||||||
@@ -574,6 +581,12 @@ static const struct sc_option options[] = {
|
|||||||
"connected over USB), enables TCP/IP mode, then connects to "
|
"connected over USB), enables TCP/IP mode, then connects to "
|
||||||
"this address before starting.",
|
"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_id = OPT_TUNNEL_HOST,
|
||||||
.longopt = "tunnel-host",
|
.longopt = "tunnel-host",
|
||||||
@@ -593,6 +606,22 @@ static const struct sc_option options[] = {
|
|||||||
"Default is 0 (not forced): the local port used for "
|
"Default is 0 (not forced): the local port used for "
|
||||||
"establishing the tunnel will be used.",
|
"establishing the tunnel will be used.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'v',
|
||||||
|
.longopt = "version",
|
||||||
|
.text = "Print the version of scrcpy.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'V',
|
||||||
|
.longopt = "verbosity",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Set the log level (verbose, debug, info, warn or error).\n"
|
||||||
|
#ifndef NDEBUG
|
||||||
|
"Default is debug.",
|
||||||
|
#else
|
||||||
|
"Default is info.",
|
||||||
|
#endif
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_V4L2_SINK,
|
.longopt_id = OPT_V4L2_SINK,
|
||||||
.longopt = "v4l2-sink",
|
.longopt = "v4l2-sink",
|
||||||
@@ -613,22 +642,6 @@ static const struct sc_option options[] = {
|
|||||||
"Default is 0 (no buffering).\n"
|
"Default is 0 (no buffering).\n"
|
||||||
"This option is only available on Linux.",
|
"This option is only available on Linux.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortopt = 'V',
|
|
||||||
.longopt = "verbosity",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Set the log level (verbose, debug, info, warn or error).\n"
|
|
||||||
#ifndef NDEBUG
|
|
||||||
"Default is debug.",
|
|
||||||
#else
|
|
||||||
"Default is info.",
|
|
||||||
#endif
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'v',
|
|
||||||
.longopt = "version",
|
|
||||||
.text = "Print the version of scrcpy.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_VIDEO_CODEC,
|
.longopt_id = OPT_VIDEO_CODEC,
|
||||||
.longopt = "video-codec",
|
.longopt = "video-codec",
|
||||||
@@ -1612,6 +1625,18 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
|||||||
return false;
|
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
|
static bool
|
||||||
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||||
const char *optstring, const struct option *longopts) {
|
const char *optstring, const struct option *longopts) {
|
||||||
@@ -1944,6 +1969,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
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:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,3 +6,4 @@
|
|||||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
#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,
|
.display_buffer = 0,
|
||||||
.audio_buffer = SC_TICK_FROM_MS(50),
|
.audio_buffer = SC_TICK_FROM_MS(50),
|
||||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||||
|
.time_limit = 0,
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
.v4l2_device = NULL,
|
.v4l2_device = NULL,
|
||||||
.v4l2_buffer = 0,
|
.v4l2_buffer = 0,
|
||||||
@@ -80,4 +81,5 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.require_audio = false,
|
.require_audio = false,
|
||||||
.list_encoders = false,
|
.list_encoders = false,
|
||||||
.list_displays = false,
|
.list_displays = false,
|
||||||
|
.kill_adb_on_close = false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ struct scrcpy_options {
|
|||||||
sc_tick display_buffer;
|
sc_tick display_buffer;
|
||||||
sc_tick audio_buffer;
|
sc_tick audio_buffer;
|
||||||
sc_tick audio_output_buffer;
|
sc_tick audio_output_buffer;
|
||||||
|
sc_tick time_limit;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
const char *v4l2_device;
|
const char *v4l2_device;
|
||||||
sc_tick v4l2_buffer;
|
sc_tick v4l2_buffer;
|
||||||
@@ -180,6 +181,7 @@ struct scrcpy_options {
|
|||||||
bool require_audio;
|
bool require_audio;
|
||||||
bool list_encoders;
|
bool list_encoders;
|
||||||
bool list_displays;
|
bool list_displays;
|
||||||
|
bool kill_adb_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
|||||||
@@ -96,23 +96,30 @@ sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index,
|
sc_recorder_write_stream(struct sc_recorder *recorder,
|
||||||
AVPacket *packet) {
|
struct sc_recorder_stream *st, AVPacket *packet) {
|
||||||
AVStream *stream = recorder->ctx->streams[stream_index];
|
AVStream *stream = recorder->ctx->streams[st->index];
|
||||||
sc_recorder_rescale_packet(stream, packet);
|
sc_recorder_rescale_packet(stream, packet);
|
||||||
|
if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) {
|
||||||
|
LOGW("Fixing PTS non monotonically increasing in stream %d "
|
||||||
|
"(%" PRIi64 " >= %" PRIi64 ")",
|
||||||
|
st->index, st->last_pts, packet->pts);
|
||||||
|
packet->pts = ++st->last_pts;
|
||||||
|
packet->dts = packet->pts;
|
||||||
|
} else {
|
||||||
|
st->last_pts = packet->pts;
|
||||||
|
}
|
||||||
return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
|
return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
|
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
return sc_recorder_write_stream(recorder, recorder->video_stream_index,
|
return sc_recorder_write_stream(recorder, &recorder->video_stream, packet);
|
||||||
packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
|
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
return sc_recorder_write_stream(recorder, recorder->audio_stream_index,
|
return sc_recorder_write_stream(recorder, &recorder->audio_stream, packet);
|
||||||
packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -178,10 +185,11 @@ static bool
|
|||||||
sc_recorder_process_header(struct sc_recorder *recorder) {
|
sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
while (!recorder->stopped && (!recorder->video_init
|
while (!recorder->stopped &&
|
||||||
|| !recorder->audio_init
|
((recorder->video && !recorder->video_init)
|
||||||
|| sc_recorder_has_empty_queues(recorder))) {
|
|| (recorder->audio && !recorder->audio_init)
|
||||||
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
|
|| sc_recorder_has_empty_queues(recorder))) {
|
||||||
|
sc_cond_wait(&recorder->cond, &recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
|
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
|
||||||
@@ -214,9 +222,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(recorder->video_stream_index >= 0);
|
assert(recorder->video_stream.index >= 0);
|
||||||
AVStream *video_stream =
|
AVStream *video_stream =
|
||||||
recorder->ctx->streams[recorder->video_stream_index];
|
recorder->ctx->streams[recorder->video_stream.index];
|
||||||
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
|
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@@ -229,9 +237,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(recorder->audio_stream_index >= 0);
|
assert(recorder->audio_stream.index >= 0);
|
||||||
AVStream *audio_stream =
|
AVStream *audio_stream =
|
||||||
recorder->ctx->streams[recorder->audio_stream_index];
|
recorder->ctx->streams[recorder->audio_stream.index];
|
||||||
bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
|
bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@@ -289,7 +297,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
// A new packet may be assigned to audio_pkt and be processed
|
// A new packet may be assigned to audio_pkt and be processed
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
sc_cond_wait(&recorder->cond, &recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If stopped is set, continue to process the remaining events (to
|
// If stopped is set, continue to process the remaining events (to
|
||||||
@@ -504,10 +512,10 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->video_stream_index = stream->index;
|
recorder->video_stream.index = stream->index;
|
||||||
|
|
||||||
recorder->video_init = true;
|
recorder->video_init = true;
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -522,7 +530,7 @@ sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) {
|
|||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
// EOS also stops the recorder
|
// EOS also stops the recorder
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,7 +556,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rec->stream_index = recorder->video_stream_index;
|
rec->stream_index = recorder->video_stream.index;
|
||||||
|
|
||||||
bool ok = sc_vecdeque_push(&recorder->video_queue, rec);
|
bool ok = sc_vecdeque_push(&recorder->video_queue, rec);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -557,7 +565,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return true;
|
return true;
|
||||||
@@ -585,10 +593,10 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->audio_stream_index = stream->index;
|
recorder->audio_stream.index = stream->index;
|
||||||
|
|
||||||
recorder->audio_init = true;
|
recorder->audio_init = true;
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -604,7 +612,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
|
|||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
// EOS also stops the recorder
|
// EOS also stops the recorder
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,7 +639,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rec->stream_index = recorder->audio_stream_index;
|
rec->stream_index = recorder->audio_stream.index;
|
||||||
|
|
||||||
bool ok = sc_vecdeque_push(&recorder->audio_queue, rec);
|
bool ok = sc_vecdeque_push(&recorder->audio_queue, rec);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -640,7 +648,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return true;
|
return true;
|
||||||
@@ -658,10 +666,16 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
|
|||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->audio = false;
|
recorder->audio = false;
|
||||||
recorder->audio_init = true;
|
recorder->audio_init = true;
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_recorder_stream_init(struct sc_recorder_stream *stream) {
|
||||||
|
stream->index = -1;
|
||||||
|
stream->last_pts = AV_NOPTS_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format, bool video, bool audio,
|
enum sc_record_format format, bool video, bool audio,
|
||||||
@@ -677,16 +691,11 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
|||||||
goto error_free_filename;
|
goto error_free_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->queue_cond);
|
ok = sc_cond_init(&recorder->cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_mutex_destroy;
|
goto error_mutex_destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->stream_cond);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_queue_cond_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(video || audio);
|
assert(video || audio);
|
||||||
recorder->video = video;
|
recorder->video = video;
|
||||||
recorder->audio = audio;
|
recorder->audio = audio;
|
||||||
@@ -698,8 +707,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
|||||||
recorder->video_init = false;
|
recorder->video_init = false;
|
||||||
recorder->audio_init = false;
|
recorder->audio_init = false;
|
||||||
|
|
||||||
recorder->video_stream_index = -1;
|
sc_recorder_stream_init(&recorder->video_stream);
|
||||||
recorder->audio_stream_index = -1;
|
sc_recorder_stream_init(&recorder->audio_stream);
|
||||||
|
|
||||||
recorder->format = format;
|
recorder->format = format;
|
||||||
|
|
||||||
@@ -730,8 +739,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_queue_cond_destroy:
|
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
error_mutex_destroy:
|
error_mutex_destroy:
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
error_free_filename:
|
error_free_filename:
|
||||||
@@ -756,8 +763,7 @@ void
|
|||||||
sc_recorder_stop(struct sc_recorder *recorder) {
|
sc_recorder_stop(struct sc_recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->cond);
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,8 +774,7 @@ sc_recorder_join(struct sc_recorder *recorder) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
sc_recorder_destroy(struct sc_recorder *recorder) {
|
||||||
sc_cond_destroy(&recorder->stream_cond);
|
sc_cond_destroy(&recorder->cond);
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
struct sc_recorder_queue SC_VECDEQUE(AVPacket *);
|
struct sc_recorder_queue SC_VECDEQUE(AVPacket *);
|
||||||
|
|
||||||
|
struct sc_recorder_stream {
|
||||||
|
int index;
|
||||||
|
int64_t last_pts;
|
||||||
|
};
|
||||||
|
|
||||||
struct sc_recorder {
|
struct sc_recorder {
|
||||||
struct sc_packet_sink video_packet_sink;
|
struct sc_packet_sink video_packet_sink;
|
||||||
struct sc_packet_sink audio_packet_sink;
|
struct sc_packet_sink audio_packet_sink;
|
||||||
@@ -35,19 +40,18 @@ struct sc_recorder {
|
|||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond queue_cond;
|
sc_cond cond;
|
||||||
// set on sc_recorder_stop(), packet_sink close or recording failure
|
// set on sc_recorder_stop(), packet_sink close or recording failure
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_recorder_queue video_queue;
|
struct sc_recorder_queue video_queue;
|
||||||
struct sc_recorder_queue audio_queue;
|
struct sc_recorder_queue audio_queue;
|
||||||
|
|
||||||
// wake up the recorder thread once the video or audio codec is known
|
// wake up the recorder thread once the video or audio codec is known
|
||||||
sc_cond stream_cond;
|
|
||||||
bool video_init;
|
bool video_init;
|
||||||
bool audio_init;
|
bool audio_init;
|
||||||
|
|
||||||
int video_stream_index;
|
struct sc_recorder_stream video_stream;
|
||||||
int audio_stream_index;
|
struct sc_recorder_stream audio_stream;
|
||||||
|
|
||||||
const struct sc_recorder_callbacks *cbs;
|
const struct sc_recorder_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/rand.h"
|
#include "util/rand.h"
|
||||||
|
#include "util/timeout.h"
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
# include "v4l2_sink.h"
|
# include "v4l2_sink.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -73,6 +74,7 @@ struct scrcpy {
|
|||||||
struct sc_hid_mouse mouse_hid;
|
struct sc_hid_mouse mouse_hid;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
struct sc_timeout timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -171,6 +173,9 @@ event_loop(struct scrcpy *s) {
|
|||||||
case SC_EVENT_RECORDER_ERROR:
|
case SC_EVENT_RECORDER_ERROR:
|
||||||
LOGE("Recorder error");
|
LOGE("Recorder error");
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||||
|
LOGI("Time limit reached");
|
||||||
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
@@ -280,6 +285,14 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
|||||||
// event
|
// 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
|
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||||
static uint32_t
|
static uint32_t
|
||||||
scrcpy_generate_scid() {
|
scrcpy_generate_scid() {
|
||||||
@@ -321,6 +334,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
bool screen_initialized = false;
|
bool screen_initialized = false;
|
||||||
|
bool timeout_initialized = false;
|
||||||
|
bool timeout_started = false;
|
||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
|
|
||||||
@@ -364,6 +379,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.power_on = options->power_on,
|
.power_on = options->power_on,
|
||||||
.list_encoders = options->list_encoders,
|
.list_encoders = options->list_encoders,
|
||||||
.list_displays = options->list_displays,
|
.list_displays = options->list_displays,
|
||||||
|
.kill_adb_on_close = options->kill_adb_on_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sc_server_callbacks cbs = {
|
static const struct sc_server_callbacks cbs = {
|
||||||
@@ -742,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);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
@@ -750,6 +787,10 @@ aoa_hid_end:
|
|||||||
sc_screen_hide_window(&s->screen);
|
sc_screen_hide_window(&s->screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (timeout_started) {
|
||||||
|
sc_timeout_stop(&s->timeout);
|
||||||
|
}
|
||||||
|
|
||||||
// The demuxer is not stopped explicitly, because it will stop by itself on
|
// The demuxer is not stopped explicitly, because it will stop by itself on
|
||||||
// end-of-stream
|
// end-of-stream
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
@@ -785,6 +826,13 @@ end:
|
|||||||
sc_server_stop(&s->server);
|
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
|
// now that the sockets are shutdown, the demuxer and controller are
|
||||||
// interrupted, we can join them
|
// interrupted, we can join them
|
||||||
if (video_demuxer_started) {
|
if (video_demuxer_started) {
|
||||||
|
|||||||
@@ -794,6 +794,15 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
|
|||||||
return sc_server_connect_to_tcpip(server, ip_port);
|
return sc_server_connect_to_tcpip(server, ip_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_server_kill_adb_if_requested(struct sc_server *server) {
|
||||||
|
if (server->params.kill_adb_on_close) {
|
||||||
|
LOGI("Killing adb server...");
|
||||||
|
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
|
||||||
|
sc_adb_kill_server(&server->intr, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_server(void *data) {
|
run_server(void *data) {
|
||||||
struct sc_server *server = data;
|
struct sc_server *server = data;
|
||||||
@@ -805,7 +814,7 @@ run_server(void *data) {
|
|||||||
// is parsed, so it is not output)
|
// is parsed, so it is not output)
|
||||||
bool ok = sc_adb_start_server(&server->intr, 0);
|
bool ok = sc_adb_start_server(&server->intr, 0);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start adb daemon");
|
LOGE("Could not start adb server");
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -993,9 +1002,12 @@ run_server(void *data) {
|
|||||||
|
|
||||||
sc_process_close(pid);
|
sc_process_close(pid);
|
||||||
|
|
||||||
|
sc_server_kill_adb_if_requested(server);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error_connection_failed:
|
error_connection_failed:
|
||||||
|
sc_server_kill_adb_if_requested(server);
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ struct sc_server_params {
|
|||||||
bool power_on;
|
bool power_on;
|
||||||
bool list_encoders;
|
bool list_encoders;
|
||||||
bool list_displays;
|
bool list_displays;
|
||||||
|
bool kill_adb_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server {
|
struct sc_server {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// On Windows, only one process could open a USB device
|
// On Windows, only one process could open a USB device
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/2773>
|
// <https://github.com/Genymobile/scrcpy/issues/2773>
|
||||||
LOGI("Killing adb daemon (if any)...");
|
LOGI("Killing adb server (if any)...");
|
||||||
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
|
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
|
||||||
// uninterruptible (intr == NULL), but in practice it's very quick
|
// uninterruptible (intr == NULL), but in practice it's very quick
|
||||||
sc_adb_kill_server(NULL, flags);
|
sc_adb_kill_server(NULL, flags);
|
||||||
|
|||||||
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
|
||||||
@@ -30,8 +30,9 @@ To disable only the audio playback, see [no playback](video.md#no-playback).
|
|||||||
|
|
||||||
To play audio only, disable the video:
|
To play audio only, disable the video:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
scrcpy --no-video
|
scrcpy --no-video
|
||||||
|
# interrupt with Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
Without video, the audio latency is typically not criticial, so it might be
|
Without video, the audio latency is typically not criticial, so it might be
|
||||||
@@ -55,7 +56,7 @@ For example, to use the device as a dictaphone and record a capture directly on
|
|||||||
the computer:
|
the computer:
|
||||||
|
|
||||||
```
|
```
|
||||||
scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus
|
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,24 +17,19 @@ To record only the audio:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-video --record=file.opus
|
scrcpy --no-video --record=file.opus
|
||||||
scrcpy --no-video --audio-codec=aac --record-file=file.aac
|
scrcpy --no-video --audio-codec=aac --record=file.aac
|
||||||
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
|
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
|
||||||
```
|
```
|
||||||
|
|
||||||
To disable playback while recording:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --no-playback --record=file.mp4
|
|
||||||
scrcpy -Nr file.mkv
|
|
||||||
# interrupt recording with Ctrl+C
|
|
||||||
```
|
|
||||||
|
|
||||||
Timestamps are captured on the device, so [packet delay variation] does not
|
Timestamps are captured on the device, so [packet delay variation] does not
|
||||||
impact the recorded file, which is always clean (only if you use `--record` of
|
impact the recorded file, which is always clean (only if you use `--record` of
|
||||||
course, not if you capture your scrcpy window and audio output on the computer).
|
course, not if you capture your scrcpy window and audio output on the computer).
|
||||||
|
|
||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
The video and audio streams are encoded on the device, but are muxed on the
|
The video and audio streams are encoded on the device, but are muxed on the
|
||||||
client side. Two formats (containers) are supported:
|
client side. Two formats (containers) are supported:
|
||||||
- Matroska (`.mkv`)
|
- Matroska (`.mkv`)
|
||||||
@@ -48,3 +43,36 @@ needs not end with `.mkv` or `.mp4`):
|
|||||||
```
|
```
|
||||||
scrcpy --record=file --record-format=mkv
|
scrcpy --record=file --record-format=mkv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## No playback
|
||||||
|
|
||||||
|
To disable playback while recording:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-playback --record=file.mp4
|
||||||
|
scrcpy -Nr file.mkv
|
||||||
|
# interrupt recording with Ctrl+C
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to disable video and audio playback separately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ the computer. This option is useful when [recording](recording.md) or when
|
|||||||
```bash
|
```bash
|
||||||
scrcpy --v4l2-sink=/dev/video2 --no-playback
|
scrcpy --v4l2-sink=/dev/video2 --no-playback
|
||||||
scrcpy --record=file.mkv --no-playback
|
scrcpy --record=file.mkv --no-playback
|
||||||
|
# interrupt with Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to disable video and audio playback separately:
|
It is also possible to disable video and audio playback separately:
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
Ln.d("Audio encoder stopped");
|
Ln.d("Audio encoder stopped");
|
||||||
listener.onTerminated(fatalError);
|
listener.onTerminated(fatalError);
|
||||||
}
|
}
|
||||||
});
|
}, "audio-encoder");
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
Codec codec = streamer.getCodec();
|
Codec codec = streamer.getCodec();
|
||||||
mediaCodec = createMediaCodec(codec, encoderName);
|
mediaCodec = createMediaCodec(codec, encoderName);
|
||||||
|
|
||||||
mediaCodecThread = new HandlerThread("AudioEncoder");
|
mediaCodecThread = new HandlerThread("media-codec");
|
||||||
mediaCodecThread.start();
|
mediaCodecThread.start();
|
||||||
|
|
||||||
MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions);
|
MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions);
|
||||||
@@ -201,7 +201,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
} finally {
|
} finally {
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
});
|
}, "audio-in");
|
||||||
|
|
||||||
outputThread = new Thread(() -> {
|
outputThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -216,7 +216,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
} finally {
|
} finally {
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
});
|
}, "audio-out");
|
||||||
|
|
||||||
mediaCodec.start();
|
mediaCodec.start();
|
||||||
mediaCodecStarted = true;
|
mediaCodecStarted = true;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
|||||||
Ln.d("Audio recorder stopped");
|
Ln.d("Audio recorder stopped");
|
||||||
listener.onTerminated(fatalError);
|
listener.onTerminated(fatalError);
|
||||||
}
|
}
|
||||||
});
|
}, "audio-raw");
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class Controller implements AsyncProcessor {
|
|||||||
Ln.d("Controller stopped");
|
Ln.d("Controller stopped");
|
||||||
listener.onTerminated(true);
|
listener.onTerminated(true);
|
||||||
}
|
}
|
||||||
});
|
}, "control-recv");
|
||||||
thread.start();
|
thread.start();
|
||||||
sender.start();
|
sender.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public final class DeviceMessageSender {
|
|||||||
} finally {
|
} finally {
|
||||||
Ln.d("Device message sender stopped");
|
Ln.d("Device message sender stopped");
|
||||||
}
|
}
|
||||||
});
|
}, "control-send");
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -318,9 +318,9 @@ public class Options {
|
|||||||
case "send_codec_meta":
|
case "send_codec_meta":
|
||||||
options.sendCodecMeta = Boolean.parseBoolean(value);
|
options.sendCodecMeta = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
case "raw_video_stream":
|
case "raw_stream":
|
||||||
boolean rawVideoStream = Boolean.parseBoolean(value);
|
boolean rawStream = Boolean.parseBoolean(value);
|
||||||
if (rawVideoStream) {
|
if (rawStream) {
|
||||||
options.sendDeviceMeta = false;
|
options.sendDeviceMeta = false;
|
||||||
options.sendFrameMeta = false;
|
options.sendFrameMeta = false;
|
||||||
options.sendDummyByte = false;
|
options.sendDummyByte = false;
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
|
|||||||
Ln.d("Screen streaming stopped");
|
Ln.d("Screen streaming stopped");
|
||||||
listener.onTerminated(true);
|
listener.onTerminated(true);
|
||||||
}
|
}
|
||||||
});
|
}, "video");
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,8 +137,7 @@ public final class Server {
|
|||||||
if (audio) {
|
if (audio) {
|
||||||
AudioCodec audioCodec = options.getAudioCodec();
|
AudioCodec audioCodec = options.getAudioCodec();
|
||||||
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
||||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(),
|
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||||
options.getSendFrameMeta());
|
|
||||||
AsyncProcessor audioRecorder;
|
AsyncProcessor audioRecorder;
|
||||||
if (audioCodec == AudioCodec.RAW) {
|
if (audioCodec == AudioCodec.RAW) {
|
||||||
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
||||||
@@ -185,7 +184,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Thread startInitThread(final Options options) {
|
private static Thread startInitThread(final Options options) {
|
||||||
Thread thread = new Thread(() -> initAndCleanUp(options));
|
Thread thread = new Thread(() -> initAndCleanUp(options), "init-cleanup");
|
||||||
thread.start();
|
thread.start();
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
public class ControlMessageReaderTest {
|
public class ControlMessageReaderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user