├── img ├── camera_api.png └── screencast_opencamera.png ├── README.md └── ffscrcpy.sh /img/camera_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Procsiab/ffscrcpy/HEAD/img/camera_api.png -------------------------------------------------------------------------------- /img/screencast_opencamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Procsiab/ffscrcpy/HEAD/img/screencast_opencamera.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ffscrcpy 2 | 3 | scrcpy + v4l2loopback = 📷 4 | 5 | ## Changelog 6 | 7 | > [!IMPORTANT] 8 | > Starting from version 2.2, scrcpy is capable of streaming directly from the 9 | camera of your smartphone instead of relying on the screencast workaround I 10 | implemented with this script; however, to achieve the same level of details you 11 | need to stream at much higher resolution and you also loose all the auto-focus 12 | and white balancing features that a camera app offers. 13 | 14 | That being said, I do not plan on implementing support for direct camera capture 15 | into my script, but I would like the users to be aware of that option, if it 16 | suits their use case better; here it is a comparison between a picture taken from 17 | the Camera2 API direct streaming, and from the OpenCamera app screencast, from a 18 | Xiaomi Mi A1: 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
Camera2 API - H265 4000x3000OpenCamera - screencast
1920x1080 (default settings)
scrcpy --v4l2-sink='/dev/video2' \
--no-audio --video-source=camera \
--video-codec=h265 --camera-id 0 \
--camera-ar 4:3
./ffscrcpy
34 | 35 | --- 36 | 37 | **CHANGE**: Since version 2.5, it's possible again to avoid opening the scrcpy window while keeping the Android device's display off. 38 | 39 | **NEWS**: Since version 2.2, scrcpy can stream directly the video feedback from the smartphone's camera, without relying on the screen capture workaround I was using. 40 | 41 | **CHANGE**: Since version 2.1, scrcpy does not allow to both turn the screen off and hide displaying the screen capture window, so at the moment I am using a check on the version number to programmatically apply a workaround to still turn off the smartphone's screen. 42 | 43 | **CHANGE**: Since version 1.18, scrcpy supports streaming the device display directly to a V4L2 loopback device, so ffmpeg is not needed anymore in this equation - this means also lower latency and better stability. 44 | 45 | ## Features 46 | 47 | > [!NOTE] 48 | > This script is an automation that combines the two aforementioned tools into a 49 | not-totally-stable-but-still-works webcam: you will be able to stream the screen 50 | of your Android smartphone to any application that uses a `/dev/video` device as 51 | a webcam. 52 | 53 | - granular logging level setting 54 | - streaming over WiFi, using ADB in `tcpip` mode 55 | - check the device battery on startup, and lock the screen when video terminates 56 | - stream specific Android device to specific loopback video device 57 | - some tweaking to the image, like cropping and flipping 58 | 59 | ## Use multiple Android devices 60 | 61 | **DISCLAIMER**: before attempting to follow the instructions below, you must 62 | install all the dependencies and know how to use this script: you must have 63 | read through this guide at least once! 64 | 65 | #### Load multiple video devices 66 | 67 | This is a proof of concept that I managed to get it to work: first of all, ensure 68 | that there is no other loopback device already created by unloading the kernel 69 | module; you should run the following command: 70 | 71 | ```bash 72 | sudo rmmod v4l2loopback 73 | ``` 74 | 75 | If you get the error "*rmmod: ERROR: Module v4l2loopback is in use*", then you 76 | can try to log out and log back in to your account, or at worse try to reboot 77 | the system. 78 | 79 | Now, you **must** manually load the kernel module `v4l2loopback` with the 80 | following command (bare in mind that it is an example, and you cannot copy-paste 81 | it directly, go on reading to learn more): 82 | 83 | ```bash 84 | sudo modprobe v4l2loopback video_nr=2,3 card_label="Android device 123","Android device 456" exclusive_caps=1 85 | ``` 86 | 87 | - the list of numbers following `video_nr` will determine how many devices will be 88 | created and which numbers will they be given (in this case, */dev/video2* and */dev/video3*); 89 | - the argument `card_label` and the list of strings that follows are optional, 90 | and represent the labels that will be assigned to the video devices. 91 | 92 | #### Run multiple instances of the script 93 | 94 | For each Android device you want to stream the screen to a loopback video device, 95 | run this script with the `-n` argument, specifying the device's serial number and 96 | the video device number you would like to be streamed to (following the previous 97 | example): 98 | 99 | ```bash 100 | ./ffscrcpy -n 000000000123:2 101 | ./ffscrcpy -n 000000000456:3 102 | ``` 103 | 104 | You can obtain the serial number though the `adb devices` command; its output 105 | would look like (when all devices are attached and authorized): 106 | 107 | ``` 108 | List of devices attached 109 | 000000000123 device 110 | 000000000456 device 111 | ``` 112 | 113 | ## Build the dependencies 114 | 115 | The following instructions are meant to be run on a recent RHEL/Fedora based OS, 116 | however they might also apply to any other Unix system, with the correct assumptions. 117 | 118 | ### v4l2loopback 119 | 120 | [This](https://github.com/umlaeute/v4l2loopback) is a kernel module that you must compile yourself; however, it's pretty straightforward: 121 | 122 | 1. clone the repository and enter it 123 | 124 | ```bash 125 | git clone https://github.com/umlaeute/v4l2loopback.git && cd v4l2loopback 126 | ``` 127 | 128 | 2. compile the kernel module 129 | 130 | ```bash 131 | make && sudo make install 132 | sudo depmod -a 133 | ``` 134 | 135 | #### [optional] sign your kernel module 136 | 137 | 1. Create a compatible certificate 138 | 139 | ```bash 140 | CERT_NAME="v4l2lb-mok" 141 | MODULE_NAME="v4l2loopback" 142 | openssl req -new -x509 -newkey rsa:2048 -keyout $CERT_NAME.priv -outform DER -out $CERT_NAME.der -nodes -days 36500 -subj "/CN=V4L2 LoopBack @ $(hostname)/" 143 | ``` 144 | 145 | 2. Sign the module with the certificate 146 | 147 | ```bash 148 | sudo /usr/src/kernels/$(uname -r)/scripts/sign-file sha256 ./$CERT_NAME.priv ./$CERT_NAME.der $(modinfo -n $MODULE_NAME) 149 | ``` 150 | 151 | 3. Import the certificate and choose a password to apply it 152 | 153 | ```bash 154 | sudo mokutil --import $CERT\_NAME.der 155 | ``` 156 | 157 | 4. Reboot and follow the on-screen instructions, to enrol the certificate key 158 | 159 | ```bash 160 | sudo systemctl reboot --now 161 | ``` 162 | 163 | **NOTE**: to sign the same module with the same certificate, for a new version of the kernel, after compiling it just repeat the step 2. 164 | 165 | #### [bonus] show timeout image 166 | 167 | To show a specific image when the loopback device has no stream fed into it, you need two additional tools: 168 | 169 | - `v4l-utils`, a package that will install the v4l2-ctl CLI; 170 | - `v4l2loopback-ctl`, the CLI to interact with the loopback devices, which can be installed by running the following command inside the repository of `v4l2loopback`: 171 | ```bash 172 | sudo make install-utils 173 | ``` 174 | 175 | At this point, **AFTER** piping a video stream into your loopback device (e.g. `/dev/video1`), you can set a custom timeout picture running the command: 176 | ```bash 177 | v4l2loopback-ctl set-timeout-image /path/to/image.png /dev/video1 178 | ``` 179 | 180 | ### scrcpy 181 | 182 | Since version 1.18, the [original](https://github.com/Genymobile/scrcpy) project enables streaming the viewport to a v4l2 loopback device. 183 | 184 | To compile it, just follow the [instructions](https://github.com/Genymobile/scrcpy/blob/master/BUILD.md#system-specific-steps) on the official documentation, if not available through your distro's package repos. 185 | 186 | ## Create a UDev rule for an USB connected Android smartphone 187 | 188 | 1. Check that the group `plugdev` exists, or else create it 189 | 190 | ```bash 191 | sudo groupadd plugdev 192 | ``` 193 | 194 | 2. Add your user to that group 195 | 196 | ```bash 197 | sudo usermod -aG plugdev $(whoami) 198 | ``` 199 | 3. Find your phone from `lsusb`, and take note of its name 200 | 201 | ```bash 202 | lsusb 203 | ``` 204 | 205 | For example, let's assume the phone has the entry: 206 | ``` 207 | Bus 001 Device 011: ID 0000:0000 Google Inc. USB2.0 Hub 208 | ``` 209 | In this case, *'Google Inc.'* is the device name. 210 | 211 | **PRO TIP**: use the command `watch -n1 lsusb`, then unplug and plug in your phone to observe the entry that changes. 212 | 213 | 4. Create a UDev rule using the vendor and product ID from `lsusb` (we're still assuming the device name from the previous example): 214 | 215 | ```bash 216 | ID_INFO=$(lsusb | grep 'Google Inc.' | cut -d ' ' -f 6) 217 | VENDOR_ID=$(echo $ID_INFO | cut -d ':' -f 1) 218 | PRODUCT_ID=$(echo $ID_INFO | cut -d ':' -f 2) 219 | echo "SUBSYSTEM==\"usb\", ATTR{idVendor}==\"$VENDOR_ID\", ATTR{idProduct}==\"$PRODUCT_ID\", MODE=\"0666\", GROUP=\"plugdev\"" | sudo tee /etc/udev/rules.d/51-android.rules 220 | ``` 221 | 222 | 5. Reboot the system to make the changes take effect 223 | 224 | ```bash 225 | systemctl reboot now 226 | ``` 227 | 228 | ## Run the script 229 | 230 | Finally! Plug your android phone into your PC and run this script: 231 | ```bash 232 | ./ffscrcpy.sh 233 | ``` 234 | 235 | To get more information about the different working modes, use the `-h` argument to print the integrated help. 236 | -------------------------------------------------------------------------------- /ffscrcpy.sh: -------------------------------------------------------------------------------- 1 | ## FFSCRCPY 2 | # 3 | # Use your android smartphone as a webcam 4 | # 5 | ## Requirements: 6 | # - scrcpy 1.18 or newer (Genymobile's original project) 7 | # - v4l2loopback kernel module 8 | # - ffmpeg binary 9 | # 10 | ## Assumptions: 11 | # - the user who runs the script has access without root to its Android device ADB session 12 | # - the smartphone may run the OpenCamera app 13 | # - the sockets on localhost from port 10080 to 10089 are bindable 14 | 15 | 16 | # Print message based on verbosity level (default 1) 17 | # 18 | # Usage: _console_log LEVEL STRING 19 | _VERBOSITY=1 20 | function _console_log() { 21 | if [[ $_VERBOSITY -lt $1 ]] 22 | then 23 | return 0 24 | else 25 | case $1 in 26 | 0) 27 | echo -e "[ERR] $2" 28 | ;; 29 | 1) 30 | echo -e "[WARN] $2" 31 | ;; 32 | 2) 33 | echo -e "[INFO] $2" 34 | ;; 35 | *) 36 | echo -e "[DBG] $2" 37 | esac 38 | fi 39 | } 40 | 41 | 42 | # Turn phone screen off, after scrcpy was called with --turn-screen-off 43 | # 44 | # Usage: _turn_screen_off 45 | function _turn_screen_off() { 46 | # When terminating the program, send 3 POWER button presses to the phone to 47 | # unlock and lock again the screen: this prevents the battery from draining 48 | # because of the opened camera app in the background 49 | adb -s $DEVICE_SERIAL shell input keyevent POWER 50 | sleep 0.8 51 | adb -s $DEVICE_SERIAL shell input keyevent POWER 52 | sleep 0.8 53 | adb -s $DEVICE_SERIAL shell input keyevent POWER 54 | sleep 0.8 55 | # Moreover, disconnect the ADB server from all devices 56 | _disconnect_all 57 | } 58 | 59 | # Disconnect all ADB devices from the ADB server when the streaming terminates 60 | # 61 | # Usage: _disconnect_all 62 | function _disconnect_all() { 63 | adb disconnect > /dev/null 2>&1 64 | } 65 | 66 | # Kill all background instances of scrcpy (they might be left over from inconsistent 67 | # teardowns of the wireless streaming mode) 68 | # 69 | # Usage: _killall_scrcpy 70 | function _killall_scrcpy() { 71 | if [[ $(ps -u | grep "scrcpy --serial" | wc -l) -gt 1 ]] 72 | then 73 | _console_log 1 "killed all background instances of scrcpy" 74 | killall scrcpy 75 | fi 76 | } 77 | 78 | 79 | # Obtain the battery percentage from the phone 80 | # 81 | # Usage: _get_phone_battery 82 | function _get_phone_battery() { 83 | return $(adb -s $DEVICE_SERIAL shell dumpsys battery | grep 'level' | grep -oE '[0-9]+') 84 | } 85 | 86 | 87 | # Boolean check on the scrcpy version, to account for a renamed parameter 88 | # 89 | # Usage: _build_nodisplay_from_scrcpy_version 90 | function _build_nodisplay_from_scrcpy_version() { 91 | local _SCRCPY_VERSION=$(scrcpy -v | head -n1 | cut -f 2 -d ' ') 92 | local _NODISPLAY_PARAM="--no-display" 93 | if (( $(echo "${_SCRCPY_VERSION::3} > 2.4" | bc -l) )) 94 | then 95 | local _NODISPLAY_PARAM="--no-window --no-audio-playback" 96 | elif (( $(echo "${_SCRCPY_VERSION::3} > 2.0" | bc -l) )) 97 | then 98 | local _NODISPLAY_PARAM="--no-audio-playback" 99 | fi 100 | echo "$_NODISPLAY_PARAM" 101 | } 102 | 103 | 104 | # Parse command line arguments 105 | _IS_SCREEN_ON=0 106 | _IS_SKIP_CHECKS=0 107 | _IS_WIRELESS_ADB=0 108 | _IS_CROPPED=1 109 | _IS_FLIPPED=0 110 | _IS_AUTO_LAUNCH=1 111 | CUSTOM_CROP="" 112 | MAX_DIMENSION="" 113 | MAX_FPS=30 114 | BITRATE="" 115 | DEVICE_NUMBER=0 116 | DEVICE_SERIAL=0000000 117 | DEVICE_STREAM="" 118 | LB_CUSTOM_SIZE="" 119 | while getopts ":hoswvfCAcmbnp" OPT 120 | do 121 | case $OPT in 122 | h) 123 | echo "[HELP] Usage: ffscrcpy [-s] [-m] [-l ] [-C] [-c X:Y:OFS_X:OFS_Y] [-A] [-m PIXELS] [-b MBPS] [-p FPS] [-n SERIAL:NUMBER] [-w] [-v LEVEL]" 124 | echo "[HELP] -o: Keep phone screen on" 125 | echo "[HELP] -s: Skip startup checks" 126 | echo "[HELP] -f: Flip horizontally the video source" 127 | echo "[HELP] -C: Do not crop the canvas to hide the OpenCamera UI" 128 | echo "[HELP] -A: Do not try to automatically launch the OpenCamera app" 129 | echo "[HELP] -c: Crop the canvas using custom X:Y:OFS_X:OFS_Y dimensions" 130 | echo "[HELP] -m: Maximum dimension for the viewport (will be scaled accordingly)" 131 | echo "[HELP] -b: Bitrate in Megabits/s; an integer followed by a capital 'M' 132 | [HELP] (defaults to 8M)" 133 | echo "[HELP] -p: Maximum FPS for video; provide an integer number" 134 | echo "[HELP] -n: Choose the number to assign to the /dev/video device 135 | [HELP] (defaults to 2) and the serial of the Android device 136 | [HELP] (defaults to the first recognized device from ADB)" 137 | echo "[HELP] -w: Enable wireless streaming (the device must be connected first)" 138 | echo "[HELP] -v: Choose verbosity level between 0 and 3" 139 | exit 0 140 | ;; 141 | o) 142 | _IS_SCREEN_ON=1 143 | ;; 144 | s) 145 | _IS_SKIP_CHECKS=1 146 | ;; 147 | f) 148 | _IS_FLIPPED=1 149 | ;; 150 | C) 151 | _IS_CROPPED=0 152 | ;; 153 | A) 154 | _IS_AUTO_LAUNCH=0 155 | ;; 156 | c) 157 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 158 | if [[ $_ARGUMENT =~ ^[0-9]+:[0-9]+:[0-9]+:[0-9]+$ ]] 159 | then 160 | CUSTOM_CROP=$_ARGUMENT 161 | shift 162 | else 163 | _console_log 0 "invalid custom crop size: you must provide four integers separated by ':' (colons)" 164 | exit 1 165 | fi 166 | ;; 167 | m) 168 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 169 | if [[ $_ARGUMENT =~ ^[0-9]+$ ]] 170 | then 171 | MAX_DIMENSION=$_ARGUMENT 172 | shift 173 | else 174 | _console_log 0 "invalid maximum dimension: provide an integer" 175 | exit 1 176 | fi 177 | ;; 178 | b) 179 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 180 | if [[ $_ARGUMENT =~ ^[0-9]+M$ ]] 181 | then 182 | BITRATE=$_ARGUMENT 183 | shift 184 | else 185 | _console_log 0 "invalid bitrate value: provide an integer followed by capital 'M'" 186 | exit 1 187 | fi 188 | ;; 189 | p) 190 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 191 | if [[ $_ARGUMENT =~ ^[0-9]+$ ]] 192 | then 193 | MAX_FPS=$_ARGUMENT 194 | shift 195 | else 196 | _console_log 0 "invalid FPS value: provide an integer" 197 | exit 1 198 | fi 199 | ;; 200 | n) 201 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 202 | if [[ $_ARGUMENT =~ ^[0-9a-zA-Z]+:[0-9]$ ]] 203 | then 204 | DEVICE_STREAM=$_ARGUMENT 205 | shift 206 | elif [[ $_ARGUMENT =~ ^[0-9]+.[0-9]+.[0-9]+.[0-9]+:[0-9]+:[0-9]$ ]] 207 | then 208 | DEVICE_STREAM=$_ARGUMENT 209 | shift 210 | else 211 | _console_log 0 "invalid device name: provide the serial number and the video device number, separated by a ':' (colon)" 212 | _console_log 0 "if you wish to connect through ADB over TCP, use IP:PORT:VIDEO_NUMBER syntax for this argument" 213 | exit 1 214 | fi 215 | ;; 216 | w) 217 | _IS_WIRELESS_ADB=1 218 | ;; 219 | v) 220 | read _ARGUMENT _DISCARD <<<"${@:$OPTIND}" 221 | if [[ $_ARGUMENT =~ ^[0-3]$ ]] 222 | then 223 | _VERBOSITY=$_ARGUMENT 224 | shift 225 | else 226 | _console_log 0 "invalid verbosity level: use an integer between 0 and 3" 227 | exit 1 228 | fi 229 | ;; 230 | \?) 231 | _console_log 0 "Invalid option: -$OPTARG" >&2 232 | exit 1 233 | ;; 234 | esac 235 | done 236 | 237 | # Ensure that no other scrcpy processes are already running 238 | if [[ $_IS_SKIP_CHECKS -eq 0 ]] 239 | then 240 | _killall_scrcpy 241 | fi 242 | 243 | # Start ADB server 244 | if [[ $(adb devices | grep -w "device" | wc -l) -gt 0 ]] 245 | then 246 | _console_log 2 "ADB server connected" 247 | else 248 | _console_log 0 "no authorized Android device was detected: ensure you have connected it and you run this program with the correct permissions" 249 | exit 1 250 | fi 251 | 252 | # Check if a custom device streaming association was provided 253 | if [[ -n $DEVICE_STREAM ]] 254 | then 255 | if [[ $(echo $DEVICE_STREAM | cut -d ':' -f2 | wc -m) -eq 2 ]] 256 | then 257 | DEVICE_SERIAL=$(echo $DEVICE_STREAM | cut -d ':' -f 1) 258 | DEVICE_NUMBER=$(echo $DEVICE_STREAM | cut -d ':' -f 2) 259 | else 260 | DEVICE_SERIAL="$(echo $DEVICE_STREAM | cut -d ':' -f 1):$(echo $DEVICE_STREAM | cut -d ':' -f 2)" 261 | DEVICE_NUMBER=$(echo $DEVICE_STREAM | cut -d ':' -f 3) 262 | fi 263 | else 264 | DEVICE_SERIAL=$(adb devices | tail -n+2 | head -n1 | grep -oE "^[0-9a-zA-Z]+") 265 | _LAST_DEV=$(ls /dev/video* | sed -rn 's/\/dev\/video([0-9]+)/\1/p' | tail -n1) 266 | DEVICE_NUMBER=$_LAST_DEV 267 | fi 268 | _console_log 2 "this script will stream the screen from Android device $DEVICE_SERIAL to the loopback device /dev/video$DEVICE_NUMBER" 269 | 270 | # Startup checks, perform if not skipped 271 | if [[ $_IS_SKIP_CHECKS -eq 0 ]] 272 | then 273 | # Load video loopback kernel module, under /dev/video2 274 | if [[ $(lsmod | grep v4l2loopback | wc -l) -gt 0 ]] 275 | then 276 | _console_log 2 "module already loaded, no new video device will be created" 277 | else 278 | DEVICE_NUMBER=$((DEVICE_NUMBER + 1)) 279 | _console_log 1 "requesting module loading: grant root permissions" 280 | sudo modprobe v4l2loopback video_nr="$DEVICE_NUMBER" card_label="Android Camera" exclusive_caps=1 281 | _console_log 2 "loaded /dev/video$DEVICE_NUMBER device with name \"Android Camera\"" 282 | fi 283 | 284 | # Check battery level above 30% 285 | _get_phone_battery 286 | _BAT_LEVEL=$? 287 | _console_log 2 "phone battery level at $_BAT_LEVEL%" 288 | if [[ $_BAT_LEVEL -lt 30 ]] 289 | then 290 | read -p "[WARN] the phone battery is below 30%: do you still wish to continue? [y/N]" -n 1 -r 291 | echo 292 | if [[ ! $REPLY =~ ^[Yy]$ ]] 293 | then 294 | exit 0 295 | fi 296 | fi 297 | fi 298 | 299 | # If wireless streaming is enabled, the ADB server is restarted into TCP mode 300 | SCRCPY_DEVICE_ID="" 301 | if [[ $_IS_WIRELESS_ADB -eq 1 ]] 302 | then 303 | _console_log 2 "starting ADB server in TCP mode on port 5555" 304 | adb tcpip 5555 > /dev/null 2>&1 305 | sleep 2 306 | _console_log 2 "obtaining Android device's IP address (wlan0)" 307 | DEVICE_IP=$(adb -s $DEVICE_SERIAL shell ip addr show wlan0 2> /dev/null | grep -w "inet" | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -n1) 308 | if [[ -z $DEVICE_IP ]] 309 | then 310 | _console_log 0 "unable to establish a connection to the Android device: ensure its WiFi is turned on" 311 | adb kill-server 312 | exit 1 313 | else 314 | adb connect $DEVICE_IP:5555 > /dev/null 2>&1 315 | _ADB_DEVICES=$(adb devices | grep -w "device" | wc -l) 316 | _console_log 1 "connected to $DEVICE_IP: now you MUST disconnect the device cable" 317 | _console_log 1 " ^^^^" 318 | # Wait until the number of adb devices is one less than before 319 | while [[ $(adb devices | grep -w "device" | wc -l) -ge $_ADB_DEVICES ]] 320 | do 321 | sleep 0.33 322 | done 323 | # Wait to let the ADB daemon establish the connection 324 | sleep 10 325 | # Prepare the argument with the device serial for scrcpy to recognize it 326 | SCRCPY_DEVICE_ID="$DEVICE_IP:5555" 327 | # Use the IP instead of the serial, because of ADB wireless mode 328 | DEVICE_SERIAL="$DEVICE_IP" 329 | fi 330 | else 331 | SCRCPY_DEVICE_ID="$DEVICE_SERIAL" 332 | fi 333 | 334 | if [[ $_IS_SCREEN_ON -eq 0 ]] 335 | then 336 | # Check if the screen is locked 337 | if [[ $(adb -s $DEVICE_SERIAL shell dumpsys power | grep -o -e 'mHoldingWakeLockSuspendBlocker=false' -e 'mHoldingDisplaySuspendBlocker=false' | wc -l) -eq 2 || $_IS_AUTO_LAUNCH -eq 0 ]] 338 | then 339 | # In case the screen is locked, open a scrcpy window and ask the user to unlock and launch the camera 340 | _console_log 1 "unlock your device and open the camera app; then close the scrcpy window" 341 | scrcpy --serial $SCRCPY_DEVICE_ID \ 342 | --turn-screen-off \ 343 | > /dev/null 2>&1 344 | else 345 | # If the screen is unlocked, run directly scrcpy with turn off display flag and stop it 346 | _console_log 2 "turning screen off during the streaming" 347 | scrcpy --serial $SCRCPY_DEVICE_ID \ 348 | --turn-screen-off \ 349 | > /dev/null 2>&1 & 350 | _PID_SCRCPY_OFF_SCREEN=$! 351 | sleep 2 352 | kill $_PID_SCRCPY_OFF_SCREEN 353 | fi 354 | if [[ $_IS_AUTO_LAUNCH -eq 1 ]] 355 | then 356 | # If the screen is unlocked, try to launch the OpenCamera app if it exists 357 | if [[ $(adb -s $DEVICE_SERIAL shell pm list packages -3 | grep -o net.sourceforge.opencamera) ]] 358 | then 359 | adb -s $DEVICE_SERIAL shell am start -n net.sourceforge.opencamera/net.sourceforge.opencamera.MainActivity > /dev/null 2>&1 360 | _console_log 2 "launched the OpenCamera app automatically" 361 | else 362 | # If OpenCamera is not installed, inform the user 363 | _console_log 1 "OpenCamera app was not found: the current screen will be captured" 364 | fi 365 | fi 366 | fi 367 | 368 | # Manage custom cropping 369 | if [[ -z $CUSTOM_CROP ]] 370 | then 371 | # Obtain screen size from ADB shell 372 | SCR_SIZE=$(adb -s $DEVICE_SERIAL shell wm size | cut -d ' ' -f 3) 373 | SCR_WIDTH=$(echo $SCR_SIZE | cut -d 'x' -f 1) 374 | SCR_HEIGHT=$(echo $SCR_SIZE | cut -d 'x' -f 2) 375 | # Crop the OpenCamera UI 376 | OC_UI_WIDTH=240 377 | # Set the cropping pattern inside the related variable 378 | CUSTOM_CROP="$SCR_WIDTH:$(($SCR_HEIGHT - $(($OC_UI_WIDTH * 2)))):0:$OC_UI_WIDTH" 379 | fi 380 | 381 | # Check custom bitrate and viewport scaling parameters 382 | if [[ ! -z $MAX_DIMENSION ]] 383 | then 384 | MAX_DIMENSION="-m $MAX_DIMENSION " 385 | fi 386 | if [[ ! -z $BITRATE ]] 387 | then 388 | BITRATE="-b $BITRATE " 389 | fi 390 | _CUSTOM_OPTS="$MAX_DIMENSION$BITRATE" 391 | 392 | # Prepare the no display parameter name based on the scrcpy version 393 | NODISPLAY_PARAM="$(_build_nodisplay_from_scrcpy_version)" 394 | 395 | _console_log 2 "scrcpy is capturing the screen, streaming it to /dev/video$DEVICE_NUMBER device" 396 | _console_log 2 "send keyboard interrupt (CTRL + C) to terminate the streaming" 397 | # Capture the Android smartphone screen, crop it and send it to the V4L2 loopback device 398 | if [[ $_IS_SCREEN_ON -eq 0 ]] 399 | then 400 | if [[ $_IS_CROPPED -eq 1 ]] 401 | then 402 | # Screen off and cropping enabled 403 | scrcpy --serial $SCRCPY_DEVICE_ID $_CUSTOM_OPTS\ 404 | --max-fps $MAX_FPS \ 405 | --turn-screen-off \ 406 | --crop $CUSTOM_CROP \ 407 | $NODISPLAY_PARAM \ 408 | --v4l2-sink="/dev/video${DEVICE_NUMBER}" \ 409 | > /dev/null 2>&1 410 | else 411 | # Screen off and cropping disabled 412 | scrcpy --serial $SCRCPY_DEVICE_ID $_CUSTOM_OPTS\ 413 | --max-fps $MAX_FPS \ 414 | --turn-screen-off \ 415 | $NODISPLAY_PARAM \ 416 | --v4l2-sink="/dev/video${DEVICE_NUMBER}" \ 417 | > /dev/null 2>&1 418 | fi 419 | else 420 | if [[ $_IS_CROPPED -eq 1 ]] 421 | then 422 | # Screen on and cropping enabled 423 | scrcpy --serial $SCRCPY_DEVICE_ID $_CUSTOM_OPTS\ 424 | --max-fps $MAX_FPS \ 425 | --crop $CUSTOM_CROP \ 426 | $NODISPLAY_PARAM \ 427 | --v4l2-sink="/dev/video${DEVICE_NUMBER}" \ 428 | > /dev/null 2>&1 429 | else 430 | # Screen on and cropping disabled 431 | scrcpy --serial $SCRCPY_DEVICE_ID $_CUSTOM_OPTS\ 432 | --max-fps $MAX_FPS \ 433 | $NODISPLAY_PARAM \ 434 | --v4l2-sink="/dev/video${DEVICE_NUMBER}" \ 435 | > /dev/null 2>&1 436 | fi 437 | fi 438 | 439 | # Turn screen off again and disconnect all ADB devices 440 | if [[ $_IS_SCREEN_ON -eq 0 ]] 441 | then 442 | _turn_screen_off & 443 | else 444 | _disconnect_all & 445 | fi 446 | --------------------------------------------------------------------------------