Compare commits
12 Commits
noplayback
...
fix_audio_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f7869d130 | ||
|
|
0f28d39127 | ||
|
|
2aec7b4c9d | ||
|
|
fc52b24503 | ||
|
|
ff5ffc892f | ||
|
|
360f2fea1e | ||
|
|
24999d0d32 | ||
|
|
8e2c0d6407 | ||
|
|
9a2abba098 | ||
|
|
b2d860382f | ||
|
|
4c4a03ebe1 | ||
|
|
798dfd240e |
@@ -7,6 +7,7 @@ _scrcpy() {
|
|||||||
--audio-codec=
|
--audio-codec=
|
||||||
--audio-codec-options=
|
--audio-codec-options=
|
||||||
--audio-encoder=
|
--audio-encoder=
|
||||||
|
--audio-source=
|
||||||
--audio-output-buffer=
|
--audio-output-buffer=
|
||||||
-b --video-bit-rate=
|
-b --video-bit-rate=
|
||||||
--crop=
|
--crop=
|
||||||
@@ -15,9 +16,9 @@ _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
|
-K --hid-keyboard
|
||||||
-h --help
|
-h --help
|
||||||
--legacy-paste
|
--legacy-paste
|
||||||
@@ -25,16 +26,16 @@ _scrcpy() {
|
|||||||
--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
|
||||||
@@ -46,15 +47,15 @@ _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=
|
||||||
@@ -62,8 +63,8 @@ _scrcpy() {
|
|||||||
--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=
|
||||||
@@ -86,6 +87,10 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
|
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
--audio-source)
|
||||||
|
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
|
||||||
|
return
|
||||||
|
;;
|
||||||
--lock-video-orientation)
|
--lock-video-orientation)
|
||||||
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
|
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ arguments=(
|
|||||||
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
|
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
|
||||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||||
|
'--audio-source=[Select the audio source]:source:(output mic)'
|
||||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||||
@@ -22,25 +23,25 @@ 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]'
|
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
|
||||||
{-h,--help}'[Print the help]'
|
{-h,--help}'[Print the help]'
|
||||||
'--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]'
|
||||||
@@ -52,23 +53,23 @@ 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]'
|
||||||
'--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]'
|
||||||
|
|||||||
102
app/scrcpy.1
102
app/scrcpy.1
@@ -33,14 +33,6 @@ Lower values decrease the latency, but increase the likelyhood of buffer underru
|
|||||||
|
|
||||||
Default is 50.
|
Default is 50.
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-audio\-output\-buffer ms
|
|
||||||
Configure the size of the SDL audio output buffer (in milliseconds).
|
|
||||||
|
|
||||||
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
|
|
||||||
|
|
||||||
Default is 5.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-codec " name
|
.BI "\-\-audio\-codec " name
|
||||||
Select an audio codec (opus, aac or raw).
|
Select an audio codec (opus, aac or raw).
|
||||||
@@ -63,6 +55,20 @@ Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\
|
|||||||
|
|
||||||
The available encoders can be listed by \-\-list\-encoders.
|
The available encoders can be listed by \-\-list\-encoders.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-audio\-source " source
|
||||||
|
Select the audio source (output or mic).
|
||||||
|
|
||||||
|
Default is output.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-audio\-output\-buffer ms
|
||||||
|
Configure the size of the SDL audio output buffer (in milliseconds).
|
||||||
|
|
||||||
|
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
|
||||||
|
|
||||||
|
Default is 5.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-b, \-\-video\-bit\-rate " value
|
.BI "\-b, \-\-video\-bit\-rate " value
|
||||||
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
@@ -107,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.
|
||||||
@@ -115,10 +125,6 @@ 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.
|
||||||
@@ -161,10 +167,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.
|
||||||
@@ -183,6 +185,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.
|
||||||
@@ -209,14 +223,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.
|
||||||
@@ -278,10 +284,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
|
||||||
@@ -291,6 +293,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).
|
||||||
@@ -316,6 +322,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".
|
||||||
@@ -326,6 +336,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.
|
||||||
@@ -334,16 +350,6 @@ 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
|
|
||||||
.B \-S, \-\-turn\-screen\-off
|
|
||||||
Turn the device screen off immediately.
|
|
||||||
|
|
||||||
.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
|
||||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||||
@@ -356,6 +362,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.
|
||||||
@@ -370,16 +386,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).
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
// latency.
|
// latency.
|
||||||
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
||||||
silence);
|
silence);
|
||||||
memset(stream + read, 0, TO_BYTES(silence));
|
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||||
|
|
||||||
if (ap->received) {
|
if (ap->received) {
|
||||||
// Inserting additional samples immediately increases buffering
|
// Inserting additional samples immediately increases buffering
|
||||||
|
|||||||
153
app/src/cli.c
153
app/src/cli.c
@@ -76,6 +76,7 @@ enum {
|
|||||||
OPT_NO_VIDEO,
|
OPT_NO_VIDEO,
|
||||||
OPT_NO_AUDIO_PLAYBACK,
|
OPT_NO_AUDIO_PLAYBACK,
|
||||||
OPT_NO_VIDEO_PLAYBACK,
|
OPT_NO_VIDEO_PLAYBACK,
|
||||||
|
OPT_AUDIO_SOURCE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@@ -134,16 +135,6 @@ static const struct sc_option options[] = {
|
|||||||
"likelyhood of buffer underrun (causing audio glitches).\n"
|
"likelyhood of buffer underrun (causing audio glitches).\n"
|
||||||
"Default is 50.",
|
"Default is 50.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_AUDIO_OUTPUT_BUFFER,
|
|
||||||
.longopt = "audio-output-buffer",
|
|
||||||
.argdesc = "ms",
|
|
||||||
.text = "Configure the size of the SDL audio output buffer (in "
|
|
||||||
"milliseconds).\n"
|
|
||||||
"If you get \"robotic\" audio playback, you should test with "
|
|
||||||
"a higher value (10). Do not change this setting otherwise.\n"
|
|
||||||
"Default is 5.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_AUDIO_CODEC,
|
.longopt_id = OPT_AUDIO_CODEC,
|
||||||
.longopt = "audio-codec",
|
.longopt = "audio-codec",
|
||||||
@@ -171,6 +162,23 @@ static const struct sc_option options[] = {
|
|||||||
"codec provided by --audio-codec).\n"
|
"codec provided by --audio-codec).\n"
|
||||||
"The available encoders can be listed by --list-encoders.",
|
"The available encoders can be listed by --list-encoders.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_SOURCE,
|
||||||
|
.longopt = "audio-source",
|
||||||
|
.argdesc = "source",
|
||||||
|
.text = "Select the audio source (output or mic).\n"
|
||||||
|
"Default is output.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_OUTPUT_BUFFER,
|
||||||
|
.longopt = "audio-output-buffer",
|
||||||
|
.argdesc = "ms",
|
||||||
|
.text = "Configure the size of the SDL audio output buffer (in "
|
||||||
|
"milliseconds).\n"
|
||||||
|
"If you get \"robotic\" audio playback, you should test with "
|
||||||
|
"a higher value (10). Do not change this setting otherwise.\n"
|
||||||
|
"Default is 5.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'b',
|
.shortopt = 'b',
|
||||||
.longopt = "video-bit-rate",
|
.longopt = "video-bit-rate",
|
||||||
@@ -249,6 +257,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",
|
||||||
@@ -262,11 +275,6 @@ static const struct sc_option options[] = {
|
|||||||
"middle-click triggers HOME. This option disables these "
|
"middle-click triggers HOME. This option disables these "
|
||||||
"shortcuts and forwards the clicks to the device instead.",
|
"shortcuts and forwards the clicks to the device instead.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortopt = 'f',
|
|
||||||
.longopt = "fullscreen",
|
|
||||||
.text = "Start in fullscreen.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortopt = 'K',
|
.shortopt = 'K',
|
||||||
.longopt = "hid-keyboard",
|
.longopt = "hid-keyboard",
|
||||||
@@ -322,11 +330,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',
|
||||||
@@ -340,13 +350,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,
|
||||||
@@ -382,17 +401,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,
|
||||||
@@ -476,11 +484,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",
|
||||||
@@ -489,6 +492,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",
|
||||||
@@ -527,6 +535,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",
|
||||||
@@ -540,11 +553,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",
|
||||||
@@ -585,6 +593,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",
|
||||||
@@ -605,22 +629,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",
|
||||||
@@ -1588,6 +1596,22 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
||||||
|
if (!strcmp(optarg, "mic")) {
|
||||||
|
*source = SC_AUDIO_SOURCE_MIC;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "output")) {
|
||||||
|
*source = SC_AUDIO_SOURCE_OUTPUT;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("Unsupported audio source: %s (expected output or mic)", optarg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -1915,6 +1939,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OPT_AUDIO_SOURCE:
|
||||||
|
if (!parse_audio_source(optarg, &opts->audio_source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -79,9 +79,8 @@ sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
// The video stream contains raw packets, without time information. When we
|
// The video and audio streams contain a sequence of raw packets (as
|
||||||
// record, we retrieve the timestamps separately, from a "meta" header
|
// provided by MediaCodec), each prefixed with a "meta" header.
|
||||||
// added by the server before each raw packet.
|
|
||||||
//
|
//
|
||||||
// The "meta" header length is 12 bytes:
|
// The "meta" header length is 12 bytes:
|
||||||
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.log_level = SC_LOG_LEVEL_INFO,
|
.log_level = SC_LOG_LEVEL_INFO,
|
||||||
.video_codec = SC_CODEC_H264,
|
.video_codec = SC_CODEC_H264,
|
||||||
.audio_codec = SC_CODEC_OPUS,
|
.audio_codec = SC_CODEC_OPUS,
|
||||||
|
.audio_source = SC_AUDIO_SOURCE_OUTPUT,
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
|
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ enum sc_codec {
|
|||||||
SC_CODEC_RAW,
|
SC_CODEC_RAW,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_audio_source {
|
||||||
|
SC_AUDIO_SOURCE_OUTPUT,
|
||||||
|
SC_AUDIO_SOURCE_MIC,
|
||||||
|
};
|
||||||
|
|
||||||
enum sc_lock_video_orientation {
|
enum sc_lock_video_orientation {
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
// lock the current orientation when scrcpy starts
|
// lock the current orientation when scrcpy starts
|
||||||
@@ -115,6 +120,7 @@ struct scrcpy_options {
|
|||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_codec video_codec;
|
enum sc_codec video_codec;
|
||||||
enum sc_codec audio_codec;
|
enum sc_codec audio_codec;
|
||||||
|
enum sc_audio_source audio_source;
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
enum sc_mouse_input_mode mouse_input_mode;
|
enum sc_mouse_input_mode mouse_input_mode;
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.video_codec = options->video_codec,
|
.video_codec = options->video_codec,
|
||||||
.audio_codec = options->audio_codec,
|
.audio_codec = options->audio_codec,
|
||||||
|
.audio_source = options->audio_source,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
.tunnel_host = options->tunnel_host,
|
.tunnel_host = options->tunnel_host,
|
||||||
@@ -639,17 +640,6 @@ aoa_hid_end:
|
|||||||
}
|
}
|
||||||
controller_started = true;
|
controller_started = true;
|
||||||
controller = &s->controller;
|
controller = &s->controller;
|
||||||
|
|
||||||
if (options->turn_screen_off) {
|
|
||||||
struct sc_control_msg msg;
|
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
|
||||||
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
|
||||||
LOGW("Could not request 'set screen power mode'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is a controller if and only if control is enabled
|
// There is a controller if and only if control is enabled
|
||||||
@@ -740,6 +730,18 @@ aoa_hid_end:
|
|||||||
audio_demuxer_started = true;
|
audio_demuxer_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the device screen is to be turned off, send the control message after
|
||||||
|
// everything is set up
|
||||||
|
if (options->control && options->turn_screen_off) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||||
|
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
||||||
|
LOGW("Could not request 'set screen power mode'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,10 @@ execute_server(struct sc_server *server,
|
|||||||
ADD_PARAM("audio_codec=%s",
|
ADD_PARAM("audio_codec=%s",
|
||||||
sc_server_get_codec_name(params->audio_codec));
|
sc_server_get_codec_name(params->audio_codec));
|
||||||
}
|
}
|
||||||
|
if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT) {
|
||||||
|
assert(params->audio_source == SC_AUDIO_SOURCE_MIC);
|
||||||
|
ADD_PARAM("audio_source=mic");
|
||||||
|
}
|
||||||
if (params->max_size) {
|
if (params->max_size) {
|
||||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ struct sc_server_params {
|
|||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_codec video_codec;
|
enum sc_codec video_codec;
|
||||||
enum sc_codec audio_codec;
|
enum sc_codec audio_codec;
|
||||||
|
enum sc_audio_source audio_source;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *video_codec_options;
|
const char *video_codec_options;
|
||||||
const char *audio_codec_options;
|
const char *audio_codec_options;
|
||||||
|
|||||||
21
doc/audio.md
21
doc/audio.md
@@ -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
|
||||||
@@ -41,6 +42,24 @@ interesting to add [buffering](#buffering) to minimize glitches:
|
|||||||
scrcpy --no-video --audio-buffer=200
|
scrcpy --no-video --audio-buffer=200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
By default, the device audio output is forwarded.
|
||||||
|
|
||||||
|
It is possible to capture the device microphone instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --audio-source=mic
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, to use the device as a dictaphone and record a capture directly on
|
||||||
|
the computer:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Codec
|
## Codec
|
||||||
|
|
||||||
The audio codec can be selected. The possible values are `opus` (default), `aac`
|
The audio codec can be selected. The possible values are `opus` (default), `aac`
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
26
server/src/main/aidl/android/view/IDisplayFoldListener.aidl
Normal file
26
server/src/main/aidl/android/view/IDisplayFoldListener.aidl
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@hide}
|
||||||
|
*/
|
||||||
|
oneway interface IDisplayFoldListener
|
||||||
|
{
|
||||||
|
/** Called when the foldedness of a display changes */
|
||||||
|
void onDisplayFoldChanged(int displayId, boolean folded);
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import android.media.AudioFormat;
|
|||||||
import android.media.AudioRecord;
|
import android.media.AudioRecord;
|
||||||
import android.media.AudioTimestamp;
|
import android.media.AudioTimestamp;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaRecorder;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public final class AudioCapture {
|
public final class AudioCapture {
|
||||||
|
|
||||||
public static final int SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX;
|
|
||||||
public static final int SAMPLE_RATE = 48000;
|
public static final int SAMPLE_RATE = 48000;
|
||||||
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
|
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
public static final int CHANNELS = 2;
|
public static final int CHANNELS = 2;
|
||||||
@@ -26,12 +24,18 @@ public final class AudioCapture {
|
|||||||
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
public static final int BYTES_PER_SAMPLE = 2;
|
public static final int BYTES_PER_SAMPLE = 2;
|
||||||
|
|
||||||
|
private final int audioSource;
|
||||||
|
|
||||||
private AudioRecord recorder;
|
private AudioRecord recorder;
|
||||||
|
|
||||||
private final AudioTimestamp timestamp = new AudioTimestamp();
|
private final AudioTimestamp timestamp = new AudioTimestamp();
|
||||||
private long previousPts = 0;
|
private long previousPts = 0;
|
||||||
private long nextPts = 0;
|
private long nextPts = 0;
|
||||||
|
|
||||||
|
public AudioCapture(AudioSource audioSource) {
|
||||||
|
this.audioSource = audioSource.value();
|
||||||
|
}
|
||||||
|
|
||||||
public static int millisToBytes(int millis) {
|
public static int millisToBytes(int millis) {
|
||||||
return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
|
return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
|
||||||
}
|
}
|
||||||
@@ -46,13 +50,13 @@ public final class AudioCapture {
|
|||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
@SuppressLint({"WrongConstant", "MissingPermission"})
|
@SuppressLint({"WrongConstant", "MissingPermission"})
|
||||||
private static AudioRecord createAudioRecord() {
|
private static AudioRecord createAudioRecord(int audioSource) {
|
||||||
AudioRecord.Builder builder = new AudioRecord.Builder();
|
AudioRecord.Builder builder = new AudioRecord.Builder();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
|
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
|
||||||
builder.setContext(FakeContext.get());
|
builder.setContext(FakeContext.get());
|
||||||
}
|
}
|
||||||
builder.setAudioSource(SOURCE);
|
builder.setAudioSource(audioSource);
|
||||||
builder.setAudioFormat(createAudioFormat());
|
builder.setAudioFormat(createAudioFormat());
|
||||||
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
|
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
|
||||||
// This buffer size does not impact latency
|
// This buffer size does not impact latency
|
||||||
@@ -100,12 +104,12 @@ public final class AudioCapture {
|
|||||||
|
|
||||||
private void startRecording() {
|
private void startRecording() {
|
||||||
try {
|
try {
|
||||||
recorder = createAudioRecord();
|
recorder = createAudioRecord(audioSource);
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
// Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones:
|
// Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones:
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/3805>
|
// - <https://github.com/Genymobile/scrcpy/issues/3805>
|
||||||
// - <https://github.com/Genymobile/scrcpy/pull/3862>
|
// - <https://github.com/Genymobile/scrcpy/pull/3862>
|
||||||
recorder = Workarounds.createAudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
|
recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
|
||||||
}
|
}
|
||||||
recorder.startRecording();
|
recorder.startRecording();
|
||||||
}
|
}
|
||||||
@@ -159,7 +163,7 @@ public final class AudioCapture {
|
|||||||
// - an estimation from the previous PTS and the packet size as a fallback.
|
// - an estimation from the previous PTS and the packet size as a fallback.
|
||||||
//
|
//
|
||||||
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
|
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
|
||||||
pts = previousPts + 1;
|
pts = previousPts;
|
||||||
}
|
}
|
||||||
previousPts = pts;
|
previousPts = pts;
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
private static final int READ_MS = 5; // milliseconds
|
private static final int READ_MS = 5; // milliseconds
|
||||||
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
|
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
|
||||||
|
|
||||||
|
private final AudioCapture capture;
|
||||||
private final Streamer streamer;
|
private final Streamer streamer;
|
||||||
private final int bitRate;
|
private final int bitRate;
|
||||||
private final List<CodecOption> codecOptions;
|
private final List<CodecOption> codecOptions;
|
||||||
@@ -58,7 +59,8 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
|
|
||||||
private boolean ended;
|
private boolean ended;
|
||||||
|
|
||||||
public AudioEncoder(Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
|
public AudioEncoder(AudioCapture capture, Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
|
||||||
|
this.capture = capture;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
this.codecOptions = codecOptions;
|
this.codecOptions = codecOptions;
|
||||||
@@ -103,8 +105,17 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
||||||
streamer.writeAudioHeader();
|
streamer.writeAudioHeader();
|
||||||
|
|
||||||
|
long lastPts = 0;
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
OutputTask task = outputTasks.take();
|
OutputTask task = outputTasks.take();
|
||||||
|
|
||||||
|
if (task.bufferInfo.presentationTimeUs < lastPts) {
|
||||||
|
// Fix PTS if not monotonically increasing
|
||||||
|
task.bufferInfo.presentationTimeUs = lastPts;
|
||||||
|
} else {
|
||||||
|
lastPts = task.bufferInfo.presentationTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
||||||
try {
|
try {
|
||||||
streamer.writePacket(buffer, task.bufferInfo);
|
streamer.writePacket(buffer, task.bufferInfo);
|
||||||
@@ -175,7 +186,6 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaCodec mediaCodec = null;
|
MediaCodec mediaCodec = null;
|
||||||
AudioCapture capture = new AudioCapture();
|
|
||||||
|
|
||||||
boolean mediaCodecStarted = false;
|
boolean mediaCodecStarted = false;
|
||||||
try {
|
try {
|
||||||
@@ -192,10 +202,9 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
capture.start();
|
capture.start();
|
||||||
|
|
||||||
final MediaCodec mediaCodecRef = mediaCodec;
|
final MediaCodec mediaCodecRef = mediaCodec;
|
||||||
final AudioCapture captureRef = capture;
|
|
||||||
inputThread = new Thread(() -> {
|
inputThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
inputThread(mediaCodecRef, captureRef);
|
inputThread(mediaCodecRef, capture);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
Ln.e("Audio capture error", e);
|
Ln.e("Audio capture error", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public final class AudioRawRecorder implements AsyncProcessor {
|
public final class AudioRawRecorder implements AsyncProcessor {
|
||||||
|
|
||||||
|
private final AudioCapture capture;
|
||||||
private final Streamer streamer;
|
private final Streamer streamer;
|
||||||
|
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
@@ -15,7 +16,8 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
|||||||
private static final int READ_MS = 5; // milliseconds
|
private static final int READ_MS = 5; // milliseconds
|
||||||
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
|
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
|
||||||
|
|
||||||
public AudioRawRecorder(Streamer streamer) {
|
public AudioRawRecorder(AudioCapture capture, Streamer streamer) {
|
||||||
|
this.capture = capture;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +31,6 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
|||||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE);
|
final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE);
|
||||||
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
AudioCapture capture = new AudioCapture();
|
|
||||||
try {
|
try {
|
||||||
capture.start();
|
capture.start();
|
||||||
|
|
||||||
|
|||||||
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
|
||||||
|
public enum AudioSource {
|
||||||
|
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
|
||||||
|
MIC("mic", MediaRecorder.AudioSource.MIC);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
AudioSource(String name, int value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AudioSource findByName(String name) {
|
||||||
|
for (AudioSource audioSource : AudioSource.values()) {
|
||||||
|
if (name.equals(audioSource.name)) {
|
||||||
|
return audioSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import android.os.Build;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.IRotationWatcher;
|
import android.view.IRotationWatcher;
|
||||||
|
import android.view.IDisplayFoldListener;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
@@ -35,6 +36,10 @@ public final class Device {
|
|||||||
void onRotationChanged(int rotation);
|
void onRotationChanged(int rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface FoldListener {
|
||||||
|
void onFoldChanged(int displayId, boolean folded);
|
||||||
|
}
|
||||||
|
|
||||||
public interface ClipboardListener {
|
public interface ClipboardListener {
|
||||||
void onClipboardTextChanged(String text);
|
void onClipboardTextChanged(String text);
|
||||||
}
|
}
|
||||||
@@ -46,6 +51,7 @@ public final class Device {
|
|||||||
|
|
||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
|
private FoldListener foldListener;
|
||||||
private ClipboardListener clipboardListener;
|
private ClipboardListener clipboardListener;
|
||||||
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
|
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
|
||||||
|
|
||||||
@@ -93,6 +99,26 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}, displayId);
|
}, displayId);
|
||||||
|
|
||||||
|
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onDisplayFoldChanged(int displayId, boolean folded) {
|
||||||
|
synchronized (Device.this) {
|
||||||
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
|
if (displayInfo == null) {
|
||||||
|
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
|
||||||
|
options.getMaxSize(), options.getLockVideoOrientation());
|
||||||
|
// notify
|
||||||
|
if (foldListener != null) {
|
||||||
|
foldListener.onFoldChanged(displayId, folded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (options.getControl() && options.getClipboardAutosync()) {
|
if (options.getControl() && options.getClipboardAutosync()) {
|
||||||
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically
|
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically
|
||||||
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||||
@@ -224,6 +250,10 @@ public final class Device {
|
|||||||
this.rotationListener = rotationListener;
|
this.rotationListener = rotationListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void setFoldListener(FoldListener foldlistener) {
|
||||||
|
this.foldListener = foldlistener;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
|
public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
|
||||||
this.clipboardListener = clipboardListener;
|
this.clipboardListener = clipboardListener;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class Options {
|
|||||||
private int maxSize;
|
private int maxSize;
|
||||||
private VideoCodec videoCodec = VideoCodec.H264;
|
private VideoCodec videoCodec = VideoCodec.H264;
|
||||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||||
|
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||||
private int videoBitRate = 8000000;
|
private int videoBitRate = 8000000;
|
||||||
private int audioBitRate = 128000;
|
private int audioBitRate = 128000;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
@@ -72,6 +73,10 @@ public class Options {
|
|||||||
return audioCodec;
|
return audioCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AudioSource getAudioSource() {
|
||||||
|
return audioSource;
|
||||||
|
}
|
||||||
|
|
||||||
public int getVideoBitRate() {
|
public int getVideoBitRate() {
|
||||||
return videoBitRate;
|
return videoBitRate;
|
||||||
}
|
}
|
||||||
@@ -225,6 +230,13 @@ public class Options {
|
|||||||
}
|
}
|
||||||
options.audioCodec = audioCodec;
|
options.audioCodec = audioCodec;
|
||||||
break;
|
break;
|
||||||
|
case "audio_source":
|
||||||
|
AudioSource audioSource = AudioSource.findByName(value);
|
||||||
|
if (audioSource == null) {
|
||||||
|
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
||||||
|
}
|
||||||
|
options.audioSource = audioSource;
|
||||||
|
break;
|
||||||
case "max_size":
|
case "max_size":
|
||||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, AsyncProcessor {
|
||||||
|
|
||||||
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
|
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
|
||||||
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
||||||
@@ -26,7 +26,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
|||||||
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
|
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
|
||||||
private static final int MAX_CONSECUTIVE_ERRORS = 3;
|
private static final int MAX_CONSECUTIVE_ERRORS = 3;
|
||||||
|
|
||||||
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
private final AtomicBoolean resetCapture = new AtomicBoolean();
|
||||||
|
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final Streamer streamer;
|
private final Streamer streamer;
|
||||||
@@ -54,12 +54,17 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRotationChanged(int rotation) {
|
public void onFoldChanged(int displayId, boolean folded) {
|
||||||
rotationChanged.set(true);
|
resetCapture.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean consumeRotationChange() {
|
@Override
|
||||||
return rotationChanged.getAndSet(false);
|
public void onRotationChanged(int rotation) {
|
||||||
|
resetCapture.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean consumeResetCapture() {
|
||||||
|
return resetCapture.getAndSet(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamScreen() throws IOException, ConfigurationException {
|
private void streamScreen() throws IOException, ConfigurationException {
|
||||||
@@ -68,6 +73,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
|||||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||||
IBinder display = createDisplay();
|
IBinder display = createDisplay();
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
|
device.setFoldListener(this);
|
||||||
|
|
||||||
streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
|
streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
|
||||||
|
|
||||||
@@ -115,6 +121,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
|||||||
} finally {
|
} finally {
|
||||||
mediaCodec.release();
|
mediaCodec.release();
|
||||||
device.setRotationListener(null);
|
device.setRotationListener(null);
|
||||||
|
device.setFoldListener(null);
|
||||||
SurfaceControl.destroyDisplay(display);
|
SurfaceControl.destroyDisplay(display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,14 +176,14 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
|
|||||||
boolean alive = true;
|
boolean alive = true;
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
while (!consumeRotationChange() && !eof) {
|
while (!consumeResetCapture() && !eof) {
|
||||||
if (stopped.get()) {
|
if (stopped.get()) {
|
||||||
alive = false;
|
alive = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
||||||
try {
|
try {
|
||||||
if (consumeRotationChange()) {
|
if (consumeResetCapture()) {
|
||||||
// must restart encoding with new size
|
// must restart encoding with new size
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,13 +136,14 @@ public final class Server {
|
|||||||
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
AudioCodec audioCodec = options.getAudioCodec();
|
AudioCodec audioCodec = options.getAudioCodec();
|
||||||
|
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(audioStreamer);
|
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
||||||
} else {
|
} else {
|
||||||
audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
|
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
|
||||||
options.getAudioEncoder());
|
options.getAudioEncoder());
|
||||||
}
|
}
|
||||||
asyncProcessors.add(audioRecorder);
|
asyncProcessors.add(audioRecorder);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.genymobile.scrcpy.Ln;
|
|||||||
|
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
import android.view.IRotationWatcher;
|
import android.view.IRotationWatcher;
|
||||||
|
import android.view.IDisplayFoldListener;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -108,4 +109,13 @@ public final class WindowManager {
|
|||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
|
||||||
|
try {
|
||||||
|
Class<?> cls = manager.getClass();
|
||||||
|
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user