├── 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 | | Camera2 API - H265 4000x3000 |
23 | OpenCamera - screencast 1920x1080 (default settings) |
24 |
25 |
26 |  |
27 |  |
28 |
29 |
30 | scrcpy --v4l2-sink='/dev/video2' \ --no-audio --video-source=camera \ --video-codec=h265 --camera-id 0 \ --camera-ar 4:3 |
31 | ./ffscrcpy |
32 |
33 |
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 |
--------------------------------------------------------------------------------