Compare commits
202
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa6c5b5149 | ||
|
|
884997e854 | ||
|
|
a0fa9967b8 | ||
|
|
260edc318f | ||
|
|
e48098ec8d | ||
|
|
8fad02aafa | ||
|
|
bb935764ae | ||
|
|
fe127af72e | ||
|
|
8e32d15e6c | ||
|
|
7f2989e1d5 | ||
|
|
da57902dd5 | ||
|
|
28701090f6 | ||
|
|
c50dc53bc2 | ||
|
|
30b8429752 | ||
|
|
38e5dafba6 | ||
|
|
d60a502485 | ||
|
|
55f4c42f19 | ||
|
|
7a9eefb04a | ||
|
|
e39ad0f695 | ||
|
|
377b6f57c5 | ||
|
|
aa8ed923f0 | ||
|
|
13211f82a1 | ||
|
|
7f50ed2458 | ||
|
|
9187472014 | ||
|
|
a331c2c653 | ||
|
|
f75dc2e477 | ||
|
|
8a5be9e2a6 | ||
|
|
705d69aaea | ||
|
|
ad51a2b411 | ||
|
|
7581dc10d3 | ||
|
|
68cd396e1f | ||
|
|
8dc1fd172a | ||
|
|
9c34c34e5d | ||
|
|
0abb268432 | ||
|
|
f2c65808fa | ||
|
|
93e86a5661 | ||
|
|
e614e19df4 | ||
|
|
e7c931139b | ||
|
|
98ece15421 | ||
|
|
8050de011c | ||
|
|
714b01204a | ||
|
|
9e831005c4 | ||
|
|
55eb874ed6 | ||
|
|
667882e9c4 | ||
|
|
0e62580570 | ||
|
|
ded19ca0f0 | ||
|
|
ad683461d6 | ||
|
|
504793b5c9 | ||
|
|
00e88acfaa | ||
|
|
7c0ee70261 | ||
|
|
2a4eec702d | ||
|
|
1e113feb59 | ||
|
|
08da34835c | ||
|
|
7b3a39bdc7 | ||
|
|
5586335276 | ||
|
|
8bb63cf14c | ||
|
|
5b01457364 | ||
|
|
010da4df59 | ||
|
|
de332e3e96 | ||
|
|
e679e3a966 | ||
|
|
97ae0a2d13 | ||
|
|
f1a4349834 | ||
|
|
a95bfe4f01 | ||
|
|
84f1792c6f | ||
|
|
317a5e93bb | ||
|
|
6b669d2dba | ||
|
|
21dd946edc | ||
|
|
669cbc7457 | ||
|
|
d3adda176b | ||
|
|
e7fa099be4 | ||
|
|
7ec2c7e232 | ||
|
|
932e698cdd | ||
|
|
ca5b962377 | ||
|
|
9233f1990e | ||
|
|
17a486d763 | ||
|
|
65e9206b3f | ||
|
|
bf7c0f2c33 | ||
|
|
dc585f033e | ||
|
|
b67ea5173c | ||
|
|
cf9718bee4 | ||
|
|
3c3c07db05 | ||
|
|
6b422e21bf | ||
|
|
8e8b039a63 | ||
|
|
0702be86d8 | ||
|
|
0cea7fb24c | ||
|
|
3d10fbd9b4 | ||
|
|
3e3756a323 | ||
|
|
5d6bcc5966 | ||
|
|
5973d4cdd7 | ||
|
|
0a151b96fe | ||
|
|
ebecbe6bc6 | ||
|
|
d5dff239c8 | ||
|
|
5cf86ef7ff | ||
|
|
e02f30f895 | ||
|
|
25e2eb7d7c | ||
|
|
280a9afda8 | ||
|
|
e91618586c | ||
|
|
680ddf64be | ||
|
|
f4e7085c34 | ||
|
|
439a1fd4ed | ||
|
|
49eb326ce9 | ||
|
|
f03f32267e | ||
|
|
45b2e6db5c | ||
|
|
400a1c69b1 | ||
|
|
730eb1086a | ||
|
|
4f9e9c6619 | ||
|
|
953edfd1df | ||
|
|
230b8274b9 | ||
|
|
40866ddc10 | ||
|
|
bd56c0abf7 | ||
|
|
6524e90c68 | ||
|
|
f2dee20a20 | ||
|
|
d2dce51038 | ||
|
|
4342c5637d | ||
|
|
3e517cd40e | ||
|
|
f70f6cdd3e | ||
|
|
87972e2022 | ||
|
|
3aac74e9e9 | ||
|
|
1c82c3923d | ||
|
|
36d656e91f | ||
|
|
bdbf1f4eb7 | ||
|
|
4177de5880 | ||
|
|
6a07e3d470 | ||
|
|
9b286ec8a7 | ||
|
|
8c5c55f9e1 | ||
|
|
0afef0c634 | ||
|
|
07806ba915 | ||
|
|
a52053421a | ||
|
|
a9b2697f3e | ||
|
|
b53d2c66e0 | ||
|
|
6cccf3ab2a | ||
|
|
52f85fd6f1 | ||
|
|
91c69ad95c | ||
|
|
75d7c01a0c | ||
|
|
74d32e612d | ||
|
|
bdba554118 | ||
|
|
234ad7ee78 | ||
|
|
8cbbcc939f | ||
|
|
b22810b17c | ||
|
|
4315be1648 | ||
|
|
74e3f8b253 | ||
|
|
059ec45f82 | ||
|
|
e6cd42355b | ||
|
|
bf8696d02e | ||
|
|
d8c2fe6ef2 | ||
|
|
54c7baceac | ||
|
|
4c43784fd1 | ||
|
|
fe21158c20 | ||
|
|
8e0c899218 | ||
|
|
725a922271 | ||
|
|
b5773a6fe8 | ||
|
|
67fb457dcb | ||
|
|
c7b1d0ea9a | ||
|
|
18082f6069 | ||
|
|
8b38b11875 | ||
|
|
64821466a1 | ||
|
|
82cb8ab870 | ||
|
|
b51841e85d | ||
|
|
bd1deffa70 | ||
|
|
6469b55861 | ||
|
|
c00a9ead5e | ||
|
|
597703b62e | ||
|
|
48bb6f2ea8 | ||
|
|
d71587e39b | ||
|
|
b62424a98a | ||
|
|
ffc7b91693 | ||
|
|
cb46e4a64a | ||
|
|
16e2c1ce26 | ||
|
|
1bfbadef96 | ||
|
|
40644994e8 | ||
|
|
7505f7117e | ||
|
|
949b64dff2 | ||
|
|
00e9e69c2a | ||
|
|
4a5cdcd390 | ||
|
|
e5e210506f | ||
|
|
a2a22f497f | ||
|
|
51a1762cbd | ||
|
|
c1ec1d1023 | ||
|
|
0a0a446ea6 | ||
|
|
fccfc43b9e | ||
|
|
121bb71dfe | ||
|
|
57056d078d | ||
|
|
1f138aef41 | ||
|
|
1ab6c19486 | ||
|
|
fd3483c837 | ||
|
|
041cdf6cf5 | ||
|
|
136ab8c199 | ||
|
|
3848ce86f1 | ||
|
|
5b8e9aa0e9 | ||
|
|
3a66b5fd01 | ||
|
|
9c1722f428 | ||
|
|
d19606eb0c | ||
|
|
d23b3e88a4 | ||
|
|
a47848f304 | ||
|
|
db8c1ce8e1 | ||
|
|
4aeb78ece2 | ||
|
|
396e4bd925 | ||
|
|
7f2f5950f2 | ||
|
|
af4b7855e1 | ||
|
|
b1d8c72780 | ||
|
|
55e65fa270 | ||
|
|
69fb5f6ee1 |
@@ -260,7 +260,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
|
|||||||
Then, build:
|
Then, build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype=release --strip -Db_lto=true
|
meson setup x --buildtype=release --strip -Db_lto=true
|
||||||
ninja -Cx # DO NOT RUN AS ROOT
|
ninja -Cx # DO NOT RUN AS ROOT
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -272,16 +272,16 @@ install` must be run as root)._
|
|||||||
|
|
||||||
#### Option 2: Use prebuilt server
|
#### Option 2: Use prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.24`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.25`][direct-scrcpy-server]
|
||||||
<sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub>
|
<sub>SHA-256: `ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb`</sub>
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype=release --strip -Db_lto=true \
|
meson setup x --buildtype=release --strip -Db_lto=true \
|
||||||
-Dprebuilt_server=/path/to/scrcpy-server
|
-Dprebuilt_server=/path/to/scrcpy-server
|
||||||
ninja -Cx # DO NOT RUN AS ROOT
|
ninja -Cx # DO NOT RUN AS ROOT
|
||||||
```
|
```
|
||||||
|
|||||||
+2
-2
@@ -277,7 +277,7 @@ The server is pushed to the device by the client on startup.
|
|||||||
To debug it, enable the server debugger during configuration:
|
To debug it, enable the server debugger during configuration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x -Dserver_debugger=true
|
meson setup x -Dserver_debugger=true
|
||||||
# or, if x is already configured
|
# or, if x is already configured
|
||||||
meson configure x -Dserver_debugger=true
|
meson configure x -Dserver_debugger=true
|
||||||
```
|
```
|
||||||
@@ -286,7 +286,7 @@ If your device runs Android 8 or below, set the `server_debugger_method` to
|
|||||||
`old` in addition:
|
`old` in addition:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x -Dserver_debugger=true -Dserver_debugger_method=old
|
meson setup x -Dserver_debugger=true -Dserver_debugger_method=old
|
||||||
# or, if x is already configured
|
# or, if x is already configured
|
||||||
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
|
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.24)
|
# scrcpy (v1.25)
|
||||||
|
|
||||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||||
|
|
||||||
@@ -106,10 +106,10 @@ process][BUILD_simple]).
|
|||||||
For Windows, a prebuilt archive with all the dependencies (including `adb`) is
|
For Windows, a prebuilt archive with all the dependencies (including `adb`) is
|
||||||
available:
|
available:
|
||||||
|
|
||||||
- [`scrcpy-win64-v1.24.zip`][direct-win64]
|
- [`scrcpy-win64-v1.25.zip`][direct-win64]
|
||||||
<sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub>
|
<sub>SHA-256: `db65125e9c65acd00359efb7cea9c05f63cc7ccd5833000cd243cc92f5053028`</sub>
|
||||||
|
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-win64-v1.25.zip
|
||||||
|
|
||||||
It is also available in [Chocolatey]:
|
It is also available in [Chocolatey]:
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ increase performance.
|
|||||||
To limit both the width and height to some value (e.g. 1024):
|
To limit both the width and height to some value (e.g. 1024):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --max-size 1024
|
scrcpy --max-size=1024
|
||||||
scrcpy -m 1024 # short version
|
scrcpy -m 1024 # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
|
|||||||
The default bit-rate is 8 Mbps. To change the video bitrate (e.g. to 2 Mbps):
|
The default bit-rate is 8 Mbps. To change the video bitrate (e.g. to 2 Mbps):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M
|
scrcpy --video-bit-rate=2M
|
||||||
scrcpy -b 2M # short version
|
scrcpy -b 2M # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ scrcpy -b 2M # short version
|
|||||||
The capture frame rate can be limited:
|
The capture frame rate can be limited:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --max-fps 15
|
scrcpy --max-fps=15
|
||||||
```
|
```
|
||||||
|
|
||||||
This is officially supported since Android 10, but may work on earlier versions.
|
This is officially supported since Android 10, but may work on earlier versions.
|
||||||
@@ -229,7 +229,7 @@ The device screen may be cropped to mirror only part of the screen.
|
|||||||
This is useful, for example, to mirror only one eye of the Oculus Go:
|
This is useful, for example, to mirror only one eye of the Oculus Go:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||||
```
|
```
|
||||||
|
|
||||||
If `--max-size` is also specified, resizing is applied after cropping.
|
If `--max-size` is also specified, resizing is applied after cropping.
|
||||||
@@ -252,20 +252,31 @@ This affects recording orientation.
|
|||||||
The [window may also be rotated](#rotation) independently.
|
The [window may also be rotated](#rotation) independently.
|
||||||
|
|
||||||
|
|
||||||
#### Encoder
|
#### Codec
|
||||||
|
|
||||||
Some devices have more than one encoder, and some of them may cause issues or
|
The video codec can be selected. The possible values are `h264` (default),
|
||||||
crash. It is possible to select a different encoder:
|
`h265` and `av1`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --encoder OMX.qcom.video.encoder.avc
|
scrcpy --video-codec=h264 # default
|
||||||
|
scrcpy --video-codec=h265
|
||||||
|
scrcpy --video-codec=av1
|
||||||
```
|
```
|
||||||
|
|
||||||
To list the available encoders, you can pass an invalid encoder name; the
|
|
||||||
error will give the available encoders:
|
##### Encoder
|
||||||
|
|
||||||
|
Some devices have more than one encoder for a specific codec, and some of them
|
||||||
|
may cause issues or crash. It is possible to select a different encoder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --encoder _
|
scrcpy --video-encoder=OMX.qcom.video.encoder.avc
|
||||||
|
```
|
||||||
|
|
||||||
|
To list the available encoders:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --list-encoders
|
||||||
```
|
```
|
||||||
|
|
||||||
### Capture
|
### Capture
|
||||||
@@ -275,14 +286,14 @@ scrcpy --encoder _
|
|||||||
It is possible to record the screen while mirroring:
|
It is possible to record the screen while mirroring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --record file.mp4
|
scrcpy --record=file.mp4
|
||||||
scrcpy -r file.mkv
|
scrcpy -r file.mkv
|
||||||
```
|
```
|
||||||
|
|
||||||
To disable mirroring while recording:
|
To disable mirroring while recording:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-display --record file.mp4
|
scrcpy --no-display --record=file.mp4
|
||||||
scrcpy -Nr file.mkv
|
scrcpy -Nr file.mkv
|
||||||
# interrupt recording with Ctrl+C
|
# interrupt recording with Ctrl+C
|
||||||
```
|
```
|
||||||
@@ -395,8 +406,8 @@ address), connect the device over USB, then run:
|
|||||||
scrcpy --tcpip # without arguments
|
scrcpy --tcpip # without arguments
|
||||||
```
|
```
|
||||||
|
|
||||||
It will automatically find the device IP address, enable TCP/IP mode, then
|
It will automatically find the device IP address and adb port, enable TCP/IP
|
||||||
connect to the device before starting.
|
mode if necessary, then connect to the device before starting.
|
||||||
|
|
||||||
##### Manual
|
##### Manual
|
||||||
|
|
||||||
@@ -431,7 +442,7 @@ none found, try running `adb disconnect`, and then run those two commands again.
|
|||||||
It may be useful to decrease the bit-rate and the resolution:
|
It may be useful to decrease the bit-rate and the resolution:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M --max-size 800
|
scrcpy --video-bit-rate=2M --max-size=800
|
||||||
scrcpy -b2M -m800 # short version
|
scrcpy -b2M -m800 # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -443,7 +454,7 @@ scrcpy -b2M -m800 # short version
|
|||||||
If several devices are listed in `adb devices`, you can specify the _serial_:
|
If several devices are listed in `adb devices`, you can specify the _serial_:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --serial 0123456789abcdef
|
scrcpy --serial=0123456789abcdef
|
||||||
scrcpy -s 0123456789abcdef # short version
|
scrcpy -s 0123456789abcdef # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -453,7 +464,7 @@ The serial may also be provided via the environment variable `ANDROID_SERIAL`
|
|||||||
If the device is connected over TCP/IP:
|
If the device is connected over TCP/IP:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --serial 192.168.0.1:5555
|
scrcpy --serial=192.168.0.1:5555
|
||||||
scrcpy -s 192.168.0.1:5555 # short version
|
scrcpy -s 192.168.0.1:5555 # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -606,7 +617,7 @@ scrcpy --force-adb-forward
|
|||||||
Like for wireless connections, it may be useful to reduce quality:
|
Like for wireless connections, it may be useful to reduce quality:
|
||||||
|
|
||||||
```
|
```
|
||||||
scrcpy -b2M -m800 --max-fps 15
|
scrcpy -b2M -m800 --max-fps=15
|
||||||
```
|
```
|
||||||
|
|
||||||
### Window configuration
|
### Window configuration
|
||||||
@@ -616,7 +627,7 @@ scrcpy -b2M -m800 --max-fps 15
|
|||||||
By default, the window title is the device model. It can be changed:
|
By default, the window title is the device model. It can be changed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --window-title 'My device'
|
scrcpy --window-title='My device'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Position and size
|
#### Position and size
|
||||||
@@ -624,7 +635,7 @@ scrcpy --window-title 'My device'
|
|||||||
The initial window position and size may be specified:
|
The initial window position and size may be specified:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
scrcpy --window-x=100 --window-y=100 --window-width=800 --window-height=600
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Borderless
|
#### Borderless
|
||||||
@@ -659,7 +670,7 @@ Fullscreen can then be toggled dynamically with <kbd>MOD</kbd>+<kbd>f</kbd>.
|
|||||||
The window may be rotated:
|
The window may be rotated:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --rotation 1
|
scrcpy --rotation=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Possible values:
|
Possible values:
|
||||||
@@ -701,7 +712,7 @@ If several displays are available, it is possible to select the display to
|
|||||||
mirror:
|
mirror:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --display 1
|
scrcpy --display=1
|
||||||
```
|
```
|
||||||
|
|
||||||
The list of display ids can be retrieved by:
|
The list of display ids can be retrieved by:
|
||||||
|
|||||||
@@ -2,28 +2,33 @@ _scrcpy() {
|
|||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
local opts="
|
local opts="
|
||||||
--always-on-top
|
--always-on-top
|
||||||
-b --bit-rate=
|
--audio-bit-rate=
|
||||||
--codec-options=
|
--audio-codec=
|
||||||
|
--audio-codec-options=
|
||||||
|
--audio-encoder=
|
||||||
|
-b --video-bit-rate=
|
||||||
--crop=
|
--crop=
|
||||||
-d --select-usb
|
-d --select-usb
|
||||||
--disable-screensaver
|
--disable-screensaver
|
||||||
--display=
|
--display=
|
||||||
--display-buffer=
|
--display-buffer=
|
||||||
-e --select-tcpip
|
-e --select-tcpip
|
||||||
--encoder=
|
|
||||||
--force-adb-forward
|
--force-adb-forward
|
||||||
--forward-all-clicks
|
--forward-all-clicks
|
||||||
-f --fullscreen
|
-f --fullscreen
|
||||||
-K --hid-keyboard
|
-K --hid-keyboard
|
||||||
-h --help
|
-h --help
|
||||||
--legacy-paste
|
--legacy-paste
|
||||||
|
--list-displays
|
||||||
|
--list-encoders
|
||||||
--lock-video-orientation
|
--lock-video-orientation
|
||||||
--lock-video-orientation=
|
--lock-video-orientation=
|
||||||
--max-fps=
|
--max-fps=
|
||||||
-M --hid-mouse
|
-M --hid-mouse
|
||||||
-m --max-size=
|
-m --max-size=
|
||||||
|
--no-audio
|
||||||
--no-cleanup
|
--no-cleanup
|
||||||
--no-clipboard-on-error
|
--no-clipboard-autosync
|
||||||
--no-downsize-on-error
|
--no-downsize-on-error
|
||||||
-n --no-control
|
-n --no-control
|
||||||
-N --no-display
|
-N --no-display
|
||||||
@@ -53,6 +58,9 @@ _scrcpy() {
|
|||||||
--v4l2-sink=
|
--v4l2-sink=
|
||||||
-V --verbosity=
|
-V --verbosity=
|
||||||
-v --version
|
-v --version
|
||||||
|
--video-codec=
|
||||||
|
--video-codec-options=
|
||||||
|
--video-encoder=
|
||||||
-w --stay-awake
|
-w --stay-awake
|
||||||
--window-borderless
|
--window-borderless
|
||||||
--window-title=
|
--window-title=
|
||||||
@@ -64,6 +72,14 @@ _scrcpy() {
|
|||||||
_init_completion -s || return
|
_init_completion -s || return
|
||||||
|
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
|
--video-codec)
|
||||||
|
COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur"))
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--audio-codec)
|
||||||
|
COMPREPLY=($(compgen -W 'opus aac' -- "$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
|
||||||
@@ -93,7 +109,12 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
|
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
-b|--bitrate \
|
-s|--serial)
|
||||||
|
# Use 'adb devices' to list serial numbers
|
||||||
|
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
-b|--video-bit-rate \
|
||||||
|--codec-options \
|
|--codec-options \
|
||||||
|--crop \
|
|--crop \
|
||||||
|--display \
|
|--display \
|
||||||
@@ -103,7 +124,6 @@ _scrcpy() {
|
|||||||
|-m|--max-size \
|
|-m|--max-size \
|
||||||
|-p|--port \
|
|-p|--port \
|
||||||
|--push-target \
|
|--push-target \
|
||||||
|-s|--serial \
|
|
||||||
|--tunnel-host \
|
|--tunnel-host \
|
||||||
|--tunnel-port \
|
|--tunnel-port \
|
||||||
|--v4l2-buffer \
|
|--v4l2-buffer \
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=scrcpy (console)
|
||||||
|
GenericName=Android Remote Control
|
||||||
|
Comment=Display and control your Android device
|
||||||
|
# For some users, the PATH or ADB environment variables are set from the shell
|
||||||
|
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
||||||
|
# environment correctly initialized.
|
||||||
|
Exec=/bin/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."'
|
||||||
|
Icon=scrcpy
|
||||||
|
Terminal=true
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;RemoteAccess;
|
||||||
|
StartupNotify=false
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=scrcpy
|
||||||
|
GenericName=Android Remote Control
|
||||||
|
Comment=Display and control your Android device
|
||||||
|
# For some users, the PATH or ADB environment variables are set from the shell
|
||||||
|
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
||||||
|
# environment correctly initialized.
|
||||||
|
Exec=/bin/sh -c '"$SHELL" -i -c scrcpy'
|
||||||
|
Icon=scrcpy
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;RemoteAccess;
|
||||||
|
StartupNotify=false
|
||||||
@@ -9,25 +9,30 @@ local arguments
|
|||||||
|
|
||||||
arguments=(
|
arguments=(
|
||||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||||
{-b,--bit-rate=}'[Encode the video at the given bit-rate]'
|
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||||
'--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]'
|
'--audio-codec=[Select the audio codec]:codec:(opus aac)'
|
||||||
|
'--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]'
|
||||||
|
{-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]'
|
||||||
{-d,--select-usb}'[Use USB device]'
|
{-d,--select-usb}'[Use USB device]'
|
||||||
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
||||||
'--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]'
|
||||||
'--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]'
|
|
||||||
'--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]'
|
{-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-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]'
|
'--max-fps=[Limit the frame rate of screen capture]'
|
||||||
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
|
{-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]'
|
||||||
|
'--no-audio[Disable audio forwarding]'
|
||||||
'--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]'
|
||||||
@@ -47,7 +52,7 @@ arguments=(
|
|||||||
'--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)'
|
||||||
'--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\)]'
|
{-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)'
|
'--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]'
|
||||||
{-t,--show-touches}'[Show physical touches]'
|
{-t,--show-touches}'[Show physical touches]'
|
||||||
@@ -58,6 +63,9 @@ arguments=(
|
|||||||
'--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,--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]'
|
||||||
|
'--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-encoder=[Use a specific MediaCodec video encoder]'
|
||||||
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
|
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
|
||||||
'--window-borderless[Disable window decorations \(display borderless window\)]'
|
'--window-borderless[Disable window decorations \(display borderless window\)]'
|
||||||
'--window-title=[Set a custom window title]'
|
'--window-title=[Set a custom window title]'
|
||||||
|
|||||||
+36
-20
@@ -4,6 +4,7 @@ src = [
|
|||||||
'src/adb/adb_device.c',
|
'src/adb/adb_device.c',
|
||||||
'src/adb/adb_parser.c',
|
'src/adb/adb_parser.c',
|
||||||
'src/adb/adb_tunnel.c',
|
'src/adb/adb_tunnel.c',
|
||||||
|
'src/audio_player.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/clock.c',
|
'src/clock.c',
|
||||||
'src/compat.c',
|
'src/compat.c',
|
||||||
@@ -21,6 +22,7 @@ src = [
|
|||||||
'src/mouse_inject.c',
|
'src/mouse_inject.c',
|
||||||
'src/opengl.c',
|
'src/opengl.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
|
'src/packet_merger.c',
|
||||||
'src/receiver.c',
|
'src/receiver.c',
|
||||||
'src/recorder.c',
|
'src/recorder.c',
|
||||||
'src/scrcpy.c',
|
'src/scrcpy.c',
|
||||||
@@ -29,6 +31,8 @@ src = [
|
|||||||
'src/version.c',
|
'src/version.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/acksync.c',
|
'src/util/acksync.c',
|
||||||
|
'src/util/average.c',
|
||||||
|
'src/util/bytebuf.c',
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
'src/util/intmap.c',
|
'src/util/intmap.c',
|
||||||
'src/util/intr.c',
|
'src/util/intr.c',
|
||||||
@@ -37,6 +41,7 @@ src = [
|
|||||||
'src/util/net_intr.c',
|
'src/util/net_intr.c',
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/process_intr.c',
|
'src/util/process_intr.c',
|
||||||
|
'src/util/rand.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/term.c',
|
'src/util/term.c',
|
||||||
@@ -97,6 +102,7 @@ if not crossbuild_windows
|
|||||||
dependency('libavformat', version: '>= 57.33'),
|
dependency('libavformat', version: '>= 57.33'),
|
||||||
dependency('libavcodec', version: '>= 57.37'),
|
dependency('libavcodec', version: '>= 57.37'),
|
||||||
dependency('libavutil'),
|
dependency('libavutil'),
|
||||||
|
dependency('libswresample'),
|
||||||
dependency('sdl2', version: '>= 2.0.5'),
|
dependency('sdl2', version: '>= 2.0.5'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -127,24 +133,19 @@ else
|
|||||||
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
|
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
|
||||||
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
|
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
|
||||||
|
|
||||||
# ffmpeg versions are different for win32 and win64 builds
|
|
||||||
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
|
|
||||||
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
|
|
||||||
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
|
|
||||||
|
|
||||||
ffmpeg = declare_dependency(
|
ffmpeg = declare_dependency(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
|
cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
|
cc.find_library('avformat-60', dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
|
cc.find_library('avutil-58', dirs: ffmpeg_bin_dir),
|
||||||
|
cc.find_library('swresample-4', dirs: ffmpeg_bin_dir),
|
||||||
],
|
],
|
||||||
include_directories: include_directories(ffmpeg_include_dir)
|
include_directories: include_directories(ffmpeg_include_dir)
|
||||||
)
|
)
|
||||||
|
|
||||||
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
|
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
|
||||||
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
|
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/bin'
|
||||||
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb
|
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb + '/include'
|
||||||
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
|
|
||||||
|
|
||||||
libusb = declare_dependency(
|
libusb = declare_dependency(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -170,6 +171,8 @@ check_functions = [
|
|||||||
'strdup',
|
'strdup',
|
||||||
'asprintf',
|
'asprintf',
|
||||||
'vasprintf',
|
'vasprintf',
|
||||||
|
'nrand48',
|
||||||
|
'jrand48',
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach f : check_functions
|
foreach f : check_functions
|
||||||
@@ -197,10 +200,6 @@ conf.set('PORTABLE', get_option('portable'))
|
|||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||||
|
|
||||||
# the default video bitrate, in bits/second
|
|
||||||
# overridden by option --bit-rate
|
|
||||||
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
|
||||||
|
|
||||||
# run a server debugger and wait for a client to be attached
|
# run a server debugger and wait for a client to be attached
|
||||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||||
|
|
||||||
@@ -223,14 +222,26 @@ executable('scrcpy', src,
|
|||||||
install: true,
|
install: true,
|
||||||
c_args: [])
|
c_args: [])
|
||||||
|
|
||||||
|
# <https://mesonbuild.com/Builtin-options.html#directories>
|
||||||
|
datadir = get_option('datadir') # by default 'share'
|
||||||
|
|
||||||
install_man('scrcpy.1')
|
install_man('scrcpy.1')
|
||||||
install_data('data/icon.png',
|
install_data('data/icon.png',
|
||||||
rename: 'scrcpy.png',
|
rename: 'scrcpy.png',
|
||||||
install_dir: 'share/icons/hicolor/256x256/apps')
|
install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps'))
|
||||||
install_data('data/zsh-completion/_scrcpy',
|
install_data('data/zsh-completion/_scrcpy',
|
||||||
install_dir: 'share/zsh/site-functions')
|
install_dir: join_paths(datadir, 'zsh/site-functions'))
|
||||||
install_data('data/bash-completion/scrcpy',
|
install_data('data/bash-completion/scrcpy',
|
||||||
install_dir: 'share/bash-completion/completions')
|
install_dir: join_paths(datadir, 'bash-completion/completions'))
|
||||||
|
|
||||||
|
# Desktop entry file for application launchers
|
||||||
|
if host_machine.system() == 'linux'
|
||||||
|
# Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop)
|
||||||
|
install_data('data/scrcpy.desktop',
|
||||||
|
install_dir: join_paths(datadir, 'applications'))
|
||||||
|
install_data('data/scrcpy-console.desktop',
|
||||||
|
install_dir: join_paths(datadir, 'applications'))
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
@@ -245,8 +256,12 @@ if get_option('buildtype') == 'debug'
|
|||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
]],
|
]],
|
||||||
['test_buffer_util', [
|
['test_binary', [
|
||||||
'tests/test_buffer_util.c',
|
'tests/test_binary.c',
|
||||||
|
]],
|
||||||
|
['test_bytebuf', [
|
||||||
|
'tests/test_bytebuf.c',
|
||||||
|
'src/util/bytebuf.c',
|
||||||
]],
|
]],
|
||||||
['test_cbuf', [
|
['test_cbuf', [
|
||||||
'tests/test_cbuf.c',
|
'tests/test_cbuf.c',
|
||||||
@@ -255,6 +270,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ cd "$DIR"
|
|||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
mkdir -p "$PREBUILT_DATA_DIR"
|
||||||
cd "$PREBUILT_DATA_DIR"
|
cd "$PREBUILT_DATA_DIR"
|
||||||
|
|
||||||
DEP_DIR=platform-tools-33.0.1
|
DEP_DIR=platform-tools-33.0.3
|
||||||
|
|
||||||
FILENAME=platform-tools_r33.0.1-windows.zip
|
FILENAME=platform-tools_r33.0.3-windows.zip
|
||||||
SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42
|
SHA256SUM=1e59afd40a74c5c0eab0a9fad3f0faf8a674267106e0b19921be9f67081808c2
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
if [[ -d "$DEP_DIR" ]]
|
||||||
then
|
then
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
DEP_DIR=ffmpeg-win32-4.3.1
|
|
||||||
|
|
||||||
FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip
|
|
||||||
SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2
|
|
||||||
|
|
||||||
FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip
|
|
||||||
SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \
|
|
||||||
"$FILENAME_SHARED" "$SHA256SUM_SHARED"
|
|
||||||
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \
|
|
||||||
"$FILENAME_DEV" "$SHA256SUM_DEV"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared
|
|
||||||
unzip "../$FILENAME_SHARED" \
|
|
||||||
"$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \
|
|
||||||
"$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \
|
|
||||||
"$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \
|
|
||||||
"$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \
|
|
||||||
"$ZIP_PREFIX_SHARED"/bin/swscale-5.dll
|
|
||||||
|
|
||||||
ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev
|
|
||||||
unzip "../$FILENAME_DEV" \
|
|
||||||
"$ZIP_PREFIX_DEV/include/*"
|
|
||||||
|
|
||||||
mv "$ZIP_PREFIX_SHARED"/* .
|
|
||||||
mv "$ZIP_PREFIX_DEV"/* .
|
|
||||||
rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV"
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
VERSION=5.0.1
|
|
||||||
DEP_DIR=ffmpeg-win64-$VERSION
|
|
||||||
|
|
||||||
FILENAME=ffmpeg-$VERSION-full_build-shared.7z
|
|
||||||
SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://github.com/GyanD/codexffmpeg/releases/download/$VERSION/$FILENAME" \
|
|
||||||
"$FILENAME" "$SHA256SUM"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared
|
|
||||||
7z x "../$FILENAME" \
|
|
||||||
"$ZIP_PREFIX"/bin/avutil-57.dll \
|
|
||||||
"$ZIP_PREFIX"/bin/avcodec-59.dll \
|
|
||||||
"$ZIP_PREFIX"/bin/avformat-59.dll \
|
|
||||||
"$ZIP_PREFIX"/bin/swresample-4.dll \
|
|
||||||
"$ZIP_PREFIX"/bin/swscale-6.dll \
|
|
||||||
"$ZIP_PREFIX"/include
|
|
||||||
mv "$ZIP_PREFIX"/* .
|
|
||||||
rmdir "$ZIP_PREFIX"
|
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DIR"
|
||||||
|
. common
|
||||||
|
mkdir -p "$PREBUILT_DATA_DIR"
|
||||||
|
cd "$PREBUILT_DATA_DIR"
|
||||||
|
|
||||||
|
VERSION=6.0-scrcpy
|
||||||
|
DEP_DIR="ffmpeg-$VERSION"
|
||||||
|
|
||||||
|
FILENAME="$DEP_DIR".7z
|
||||||
|
SHA256SUM=f3956295b4325a84aada05447ba3f314fbed96697811666d495de4de40d59f98
|
||||||
|
|
||||||
|
if [[ -d "$DEP_DIR" ]]
|
||||||
|
then
|
||||||
|
echo "$DEP_DIR" found
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \
|
||||||
|
"$FILENAME" "$SHA256SUM"
|
||||||
|
|
||||||
|
mkdir "$DEP_DIR"
|
||||||
|
cd "$DEP_DIR"
|
||||||
|
|
||||||
|
ZIP_PREFIX=ffmpeg
|
||||||
|
7z x "../$FILENAME"
|
||||||
|
mv "$ZIP_PREFIX"/* .
|
||||||
|
rmdir "$ZIP_PREFIX"
|
||||||
@@ -22,13 +22,12 @@ get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME"
|
|||||||
mkdir "$DEP_DIR"
|
mkdir "$DEP_DIR"
|
||||||
cd "$DEP_DIR"
|
cd "$DEP_DIR"
|
||||||
|
|
||||||
# include/ is the same in all folders of the archive
|
|
||||||
7z x "../$FILENAME" \
|
7z x "../$FILENAME" \
|
||||||
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
|
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
|
||||||
|
libusb-1.0.26-binaries/libusb-MinGW-Win32/include/ \
|
||||||
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
|
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
|
||||||
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
|
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
|
||||||
|
|
||||||
mv libusb-1.0.26-binaries/libusb-MinGW-Win32/bin MinGW-Win32
|
mv libusb-1.0.26-binaries/libusb-MinGW-Win32 .
|
||||||
mv libusb-1.0.26-binaries/libusb-MinGW-x64/bin MinGW-x64
|
mv libusb-1.0.26-binaries/libusb-MinGW-x64 .
|
||||||
mv libusb-1.0.26-binaries/libusb-MinGW-x64/include .
|
|
||||||
rm -rf libusb-1.0.26-binaries
|
rm -rf libusb-1.0.26-binaries
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ cd "$DIR"
|
|||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
mkdir -p "$PREBUILT_DATA_DIR"
|
||||||
cd "$PREBUILT_DATA_DIR"
|
cd "$PREBUILT_DATA_DIR"
|
||||||
|
|
||||||
DEP_DIR=SDL2-2.0.22
|
DEP_DIR=SDL2-2.26.1
|
||||||
|
|
||||||
FILENAME=SDL2-devel-2.0.22-mingw.tar.gz
|
FILENAME=SDL2-devel-2.26.1-mingw.tar.gz
|
||||||
SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1
|
SHA256SUM=aa43e1531a89551f9f9e14b27953a81d4ac946a9e574b5813cd0f2b36e83cc1c
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
if [[ -d "$DEP_DIR" ]]
|
||||||
then
|
then
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ BEGIN
|
|||||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||||
VALUE "OriginalFilename", "scrcpy.exe"
|
VALUE "OriginalFilename", "scrcpy.exe"
|
||||||
VALUE "ProductName", "scrcpy"
|
VALUE "ProductName", "scrcpy"
|
||||||
VALUE "ProductVersion", "1.24"
|
VALUE "ProductVersion", "1.25"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
+54
-21
@@ -20,20 +20,28 @@ provides display and control of Android devices connected on USB (or over TCP/IP
|
|||||||
Make scrcpy window always on top (above other windows).
|
Make scrcpy window always on top (above other windows).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-b, \-\-bit\-rate " value
|
.BI "\-\-audio\-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 audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
|
|
||||||
Default is 8000000.
|
Default is 196K (196000).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-codec\-options " key[:type]=value[,...]
|
.BI "\-\-audio\-codec " name
|
||||||
Set a list of comma-separated key:type=value options for the device encoder.
|
Select an audio codec (opus or aac).
|
||||||
|
|
||||||
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
|
Default is opus.
|
||||||
|
|
||||||
The list of possible codec options is available in the Android documentation
|
.TP
|
||||||
.UR https://d.android.com/reference/android/media/MediaFormat
|
.BI "\-\-audio\-encoder " name
|
||||||
.UE .
|
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||||
|
|
||||||
|
The available encoders can be listed by \-\-list\-encoders.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.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).
|
||||||
|
|
||||||
|
Default is 8M (8000000).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
@@ -55,10 +63,9 @@ Disable screensaver while scrcpy is running.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-display " id
|
.BI "\-\-display " id
|
||||||
Specify the display id to mirror.
|
Specify the device display id to mirror.
|
||||||
|
|
||||||
The list of possible display ids can be listed by "adb shell dumpsys display"
|
The available display ids can be listed by \-\-list\-displays.
|
||||||
(search "mDisplayId=" in the output).
|
|
||||||
|
|
||||||
Default is 0.
|
Default is 0.
|
||||||
|
|
||||||
@@ -74,10 +81,6 @@ 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
|
|
||||||
.BI "\-\-encoder " name
|
|
||||||
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
|
||||||
|
|
||||||
.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.
|
||||||
@@ -117,7 +120,15 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
|
|||||||
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-lock\-video\-orientation[=value]
|
.B \-\-list\-encoders
|
||||||
|
List video and audio encoders available on the device.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-list\-displays
|
||||||
|
List displays available on the device.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
|
||||||
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
|
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
|
||||||
|
|
||||||
Default is "unlocked".
|
Default is "unlocked".
|
||||||
@@ -199,7 +210,7 @@ It may only work over USB.
|
|||||||
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port[:port]
|
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||||
Set the TCP port (range) used by the client to listen.
|
Set the TCP port (range) used by the client to listen.
|
||||||
|
|
||||||
Default is 27183:27199.
|
Default is 27183:27199.
|
||||||
@@ -260,7 +271,7 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
|
|||||||
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
|
.TP
|
||||||
.BI "\-\-shortcut\-mod " key[+...]][,...]
|
.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".
|
||||||
|
|
||||||
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||||
@@ -270,12 +281,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
|
.TP
|
||||||
.BI "\-\-tcpip[=ip[:port]]
|
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
|
||||||
Configure and reconnect the device over TCP/IP.
|
Configure and reconnect the device over TCP/IP.
|
||||||
|
|
||||||
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
||||||
|
|
||||||
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
|
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-S, \-\-turn\-screen\-off
|
.B \-S, \-\-turn\-screen\-off
|
||||||
@@ -323,6 +334,28 @@ Default is "info" for release builds, "debug" for debug builds.
|
|||||||
.B \-v, \-\-version
|
.B \-v, \-\-version
|
||||||
Print the version of scrcpy.
|
Print the version of scrcpy.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-video\-codec " name
|
||||||
|
Select a video codec (h264, h265 or av1).
|
||||||
|
|
||||||
|
Default is h264.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-video\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
|
||||||
|
Set a list of comma-separated key:type=value options for the device video encoder.
|
||||||
|
|
||||||
|
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
|
||||||
|
|
||||||
|
The list of possible codec options is available in the Android documentation
|
||||||
|
.UR https://d.android.com/reference/android/media/MediaFormat
|
||||||
|
.UE .
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-video\-encoder " name
|
||||||
|
Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR).
|
||||||
|
|
||||||
|
The available encoders can be listed by \-\-list\-encoders.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-w, \-\-stay-awake
|
.B \-w, \-\-stay-awake
|
||||||
Keep the device on while scrcpy is running, when the device is plugged in.
|
Keep the device on while scrcpy is running, when the device is plugged in.
|
||||||
|
|||||||
+2
-1
@@ -401,6 +401,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
|
|||||||
#define BUFSIZE 65536
|
#define BUFSIZE 65536
|
||||||
char *buf = malloc(BUFSIZE);
|
char *buf = malloc(BUFSIZE);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
|
LOG_OOM();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,5 +711,5 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
|||||||
// It is parsed as a NUL-terminated string
|
// It is parsed as a NUL-terminated string
|
||||||
buf[r] = '\0';
|
buf[r] = '\0';
|
||||||
|
|
||||||
return sc_adb_parse_device_ip_from_output(buf);
|
return sc_adb_parse_device_ip(buf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ sc_adb_parse_device_ip_from_line(char *line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
sc_adb_parse_device_ip_from_output(char *str) {
|
sc_adb_parse_device_ip(char *str) {
|
||||||
size_t idx_line = 0;
|
size_t idx_line = 0;
|
||||||
while (str[idx_line] != '\0') {
|
while (str[idx_line] != '\0') {
|
||||||
char *line = &str[idx_line];
|
char *line = &str[idx_line];
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
|
|||||||
* Warning: this function modifies the buffer for optimization purposes.
|
* Warning: this function modifies the buffer for optimization purposes.
|
||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
sc_adb_parse_device_ip_from_output(char *str);
|
sc_adb_parse_device_ip(char *str);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+13
-11
@@ -7,8 +7,6 @@
|
|||||||
#include "util/net_intr.h"
|
#include "util/net_intr.h"
|
||||||
#include "util/process_intr.h"
|
#include "util/process_intr.h"
|
||||||
|
|
||||||
#define SC_SOCKET_NAME "scrcpy"
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||||
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
|
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
|
||||||
@@ -17,10 +15,11 @@ listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
|||||||
static bool
|
static bool
|
||||||
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
||||||
struct sc_intr *intr, const char *serial,
|
struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
|
if (!sc_adb_reverse(intr, serial, device_socket_name, port,
|
||||||
SC_ADB_NO_STDOUT)) {
|
SC_ADB_NO_STDOUT)) {
|
||||||
// the command itself failed, it will fail on any port
|
// the command itself failed, it will fail on any port
|
||||||
return false;
|
return false;
|
||||||
@@ -52,7 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
// failure, disable tunnel and try another port
|
||||||
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
if (!sc_adb_reverse_remove(intr, serial, device_socket_name,
|
||||||
SC_ADB_NO_STDOUT)) {
|
SC_ADB_NO_STDOUT)) {
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
@@ -78,12 +77,13 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
|||||||
static bool
|
static bool
|
||||||
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
||||||
struct sc_intr *intr, const char *serial,
|
struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
tunnel->forward = true;
|
tunnel->forward = true;
|
||||||
|
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
|
if (sc_adb_forward(intr, serial, port, device_socket_name,
|
||||||
SC_ADB_NO_STDOUT)) {
|
SC_ADB_NO_STDOUT)) {
|
||||||
// success
|
// success
|
||||||
tunnel->local_port = port;
|
tunnel->local_port = port;
|
||||||
@@ -123,13 +123,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||||
const char *serial, struct sc_port_range port_range,
|
const char *serial, const char *device_socket_name,
|
||||||
bool force_adb_forward) {
|
struct sc_port_range port_range, bool force_adb_forward) {
|
||||||
assert(!tunnel->enabled);
|
assert(!tunnel->enabled);
|
||||||
|
|
||||||
if (!force_adb_forward) {
|
if (!force_adb_forward) {
|
||||||
// Attempt to use "adb reverse"
|
// Attempt to use "adb reverse"
|
||||||
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
|
if (enable_tunnel_reverse_any_port(tunnel, intr, serial,
|
||||||
|
device_socket_name, port_range)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,12 +140,13 @@ sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|||||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
|
return enable_tunnel_forward_any_port(tunnel, intr, serial,
|
||||||
|
device_socket_name, port_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||||
const char *serial) {
|
const char *serial, const char *device_socket_name) {
|
||||||
assert(tunnel->enabled);
|
assert(tunnel->enabled);
|
||||||
|
|
||||||
bool ret;
|
bool ret;
|
||||||
@@ -152,7 +154,7 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|||||||
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
|
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
|
||||||
SC_ADB_NO_STDOUT);
|
SC_ADB_NO_STDOUT);
|
||||||
} else {
|
} else {
|
||||||
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
ret = sc_adb_reverse_remove(intr, serial, device_socket_name,
|
||||||
SC_ADB_NO_STDOUT);
|
SC_ADB_NO_STDOUT);
|
||||||
|
|
||||||
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
|
|||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||||
const char *serial, struct sc_port_range port_range,
|
const char *serial, const char *device_socket_name,
|
||||||
bool force_adb_forward);
|
struct sc_port_range port_range, bool force_adb_forward);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the tunnel
|
* Close the tunnel
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||||
const char *serial);
|
const char *serial, const char *device_socket_name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
#include "audio_player.h"
|
||||||
|
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
||||||
|
|
||||||
|
/** Downcast frame_sink to sc_audio_player */
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
||||||
|
|
||||||
|
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
|
||||||
|
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
||||||
|
|
||||||
|
#define SC_AUDIO_OUTPUT_BUFFER_SAMPLES 480 // 10ms at 48000Hz
|
||||||
|
|
||||||
|
// The target number of buffered samples between the producer and the consumer.
|
||||||
|
// This value is directly use for compensation.
|
||||||
|
#define SC_TARGET_BUFFERED_SAMPLES (3 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES)
|
||||||
|
|
||||||
|
// If the consumer is too late, skip samples to keep at most this value
|
||||||
|
#define SC_BUFFERED_SAMPLES_THRESHOLD 2400 // 50ms at 48000Hz
|
||||||
|
|
||||||
|
// Use a ring-buffer of 1 second (at 48000Hz) between the producer and the
|
||||||
|
// consumer. It too big, but it guarantees that the producer and the consumer
|
||||||
|
// will be able to access it in parallel without locking.
|
||||||
|
#define SC_BYTEBUF_SIZE_IN_SAMPLES 48000
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||||
|
struct sc_audio_player *ap = userdata;
|
||||||
|
|
||||||
|
// This callback is called with the lock used by SDL_AudioDeviceLock(), so
|
||||||
|
// the bytebuf is protected
|
||||||
|
|
||||||
|
assert(len_int > 0);
|
||||||
|
size_t len = len_int;
|
||||||
|
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
LOGD("[Audio] SDL callback requests %" SC_PRIsizet " samples",
|
||||||
|
len / (ap->nb_channels * ap->out_bytes_per_sample));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t read = sc_bytebuf_read_remaining(&ap->buf);
|
||||||
|
size_t max_buffered_bytes = SC_BUFFERED_SAMPLES_THRESHOLD
|
||||||
|
* ap->nb_channels * ap->out_bytes_per_sample;
|
||||||
|
if (read > max_buffered_bytes + len) {
|
||||||
|
size_t skip = read - (max_buffered_bytes + len);
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
LOGD("[Audio] Buffered samples threshold exceeded: %" SC_PRIsizet
|
||||||
|
" bytes, skipping %" SC_PRIsizet " bytes", read, skip);
|
||||||
|
#endif
|
||||||
|
// After this callback, exactly max_buffered_bytes will remain
|
||||||
|
sc_bytebuf_skip(&ap->buf, skip);
|
||||||
|
read = max_buffered_bytes + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of buffered samples (may be negative on underflow)
|
||||||
|
float buffered_samples = ((float) read - len_int)
|
||||||
|
/ (ap->nb_channels * ap->out_bytes_per_sample);
|
||||||
|
sc_average_push(&ap->avg_buffered_samples, buffered_samples);
|
||||||
|
|
||||||
|
if (read) {
|
||||||
|
if (read > len) {
|
||||||
|
read = len;
|
||||||
|
}
|
||||||
|
sc_bytebuf_read(&ap->buf, stream, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read < len) {
|
||||||
|
// Insert silence
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
LOGD("[Audio] Buffer underflow, inserting silence: %" SC_PRIsizet
|
||||||
|
" bytes", len - read);
|
||||||
|
#endif
|
||||||
|
memset(stream + read, 0, len - read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
sc_audio_player_get_buf_size(struct sc_audio_player *ap, size_t samples) {
|
||||||
|
assert(ap->nb_channels);
|
||||||
|
assert(ap->out_bytes_per_sample);
|
||||||
|
return samples * ap->nb_channels * ap->out_bytes_per_sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *
|
||||||
|
sc_audio_player_get_swr_buf(struct sc_audio_player *ap, size_t min_samples) {
|
||||||
|
size_t min_buf_size = sc_audio_player_get_buf_size(ap, min_samples);
|
||||||
|
if (min_buf_size < ap->swr_buf_alloc_size) {
|
||||||
|
size_t new_size = min_buf_size + 4096;
|
||||||
|
uint8_t *buf = realloc(ap->swr_buf, new_size);
|
||||||
|
if (!buf) {
|
||||||
|
LOG_OOM();
|
||||||
|
// Could not realloc to the requested size
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ap->swr_buf = buf;
|
||||||
|
ap->swr_buf_alloc_size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ap->swr_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||||
|
const AVCodecContext *ctx) {
|
||||||
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||||
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
|
assert(ctx->ch_layout.nb_channels > 0);
|
||||||
|
unsigned nb_channels = ctx->ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
|
int tmp = av_get_channel_layout_nb_channels(ctx->channel_layout);
|
||||||
|
assert(tmp > 0);
|
||||||
|
unsigned nb_channels = tmp;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SDL_AudioSpec desired = {
|
||||||
|
.freq = ctx->sample_rate,
|
||||||
|
.format = SC_SDL_SAMPLE_FMT,
|
||||||
|
.channels = nb_channels,
|
||||||
|
.samples = SC_AUDIO_OUTPUT_BUFFER_SAMPLES,
|
||||||
|
.callback = sc_audio_player_sdl_callback,
|
||||||
|
.userdata = ap,
|
||||||
|
};
|
||||||
|
SDL_AudioSpec obtained;
|
||||||
|
|
||||||
|
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||||
|
if (!ap->device) {
|
||||||
|
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwrContext *swr_ctx = swr_alloc();
|
||||||
|
if (!swr_ctx) {
|
||||||
|
LOG_OOM();
|
||||||
|
goto error_close_audio_device;
|
||||||
|
}
|
||||||
|
ap->swr_ctx = swr_ctx;
|
||||||
|
|
||||||
|
assert(ctx->sample_rate > 0);
|
||||||
|
assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT));
|
||||||
|
|
||||||
|
int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT);
|
||||||
|
assert(out_bytes_per_sample > 0);
|
||||||
|
|
||||||
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
|
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
|
||||||
|
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
|
||||||
|
#else
|
||||||
|
av_opt_set_channel_layout(swr_ctx, "in_channel_layout",
|
||||||
|
ctx->channel_layout, 0);
|
||||||
|
av_opt_set_channel_layout(swr_ctx, "out_channel_layout",
|
||||||
|
ctx->channel_layout, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0);
|
||||||
|
av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0);
|
||||||
|
|
||||||
|
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", ctx->sample_fmt, 0);
|
||||||
|
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", SC_AV_SAMPLE_FMT, 0);
|
||||||
|
|
||||||
|
int ret = swr_init(swr_ctx);
|
||||||
|
if (ret) {
|
||||||
|
LOGE("Failed to initialize the resampling context");
|
||||||
|
goto error_free_swr_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
ap->sample_rate = ctx->sample_rate;
|
||||||
|
ap->nb_channels = nb_channels;
|
||||||
|
ap->out_bytes_per_sample = out_bytes_per_sample;
|
||||||
|
|
||||||
|
size_t bytebuf_size =
|
||||||
|
sc_audio_player_get_buf_size(ap, SC_BYTEBUF_SIZE_IN_SAMPLES);
|
||||||
|
|
||||||
|
bool ok = sc_bytebuf_init(&ap->buf, bytebuf_size);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_free_swr_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||||
|
|
||||||
|
size_t initial_swr_buf_size = sc_audio_player_get_buf_size(ap, 4096);
|
||||||
|
ap->swr_buf = malloc(initial_swr_buf_size);
|
||||||
|
if (!ap->swr_buf) {
|
||||||
|
LOG_OOM();
|
||||||
|
goto error_destroy_bytebuf;
|
||||||
|
}
|
||||||
|
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
||||||
|
|
||||||
|
sc_average_init(&ap->avg_buffered_samples, 32);
|
||||||
|
ap->samples_since_resync = 0;
|
||||||
|
|
||||||
|
SDL_PauseAudioDevice(ap->device, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_destroy_bytebuf:
|
||||||
|
sc_bytebuf_destroy(&ap->buf);
|
||||||
|
error_free_swr_ctx:
|
||||||
|
swr_free(&ap->swr_ctx);
|
||||||
|
error_close_audio_device:
|
||||||
|
SDL_CloseAudioDevice(ap->device);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
||||||
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||||
|
|
||||||
|
assert(ap->device);
|
||||||
|
SDL_PauseAudioDevice(ap->device, 1);
|
||||||
|
SDL_CloseAudioDevice(ap->device);
|
||||||
|
|
||||||
|
free(ap->swr_buf);
|
||||||
|
sc_bytebuf_destroy(&ap->buf);
|
||||||
|
swr_free(&ap->swr_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
|
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||||
|
|
||||||
|
SwrContext *swr_ctx = ap->swr_ctx;
|
||||||
|
|
||||||
|
int64_t delay = swr_get_delay(swr_ctx, ap->sample_rate);
|
||||||
|
// No need to av_rescale_rnd(), input and output sample rates are the same
|
||||||
|
int dst_nb_samples = delay + frame->nb_samples;
|
||||||
|
|
||||||
|
uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, frame->nb_samples);
|
||||||
|
if (!swr_buf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = swr_convert(swr_ctx, &swr_buf, dst_nb_samples,
|
||||||
|
(const uint8_t **) frame->data, frame->nb_samples);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Resampling failed: %d", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t samples_written = ret;
|
||||||
|
size_t swr_buf_size = sc_audio_player_get_buf_size(ap, samples_written);
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
LOGI("[Audio] %" SC_PRIsizet " samples written to buffer", samples_written);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// It should almost always be possible to write without lock
|
||||||
|
bool can_write_without_lock = swr_buf_size <= ap->safe_empty_buffer;
|
||||||
|
if (can_write_without_lock) {
|
||||||
|
sc_bytebuf_prepare_write(&ap->buf, swr_buf, swr_buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LockAudioDevice(ap->device);
|
||||||
|
if (can_write_without_lock) {
|
||||||
|
sc_bytebuf_commit_write(&ap->buf, swr_buf_size);
|
||||||
|
} else {
|
||||||
|
sc_bytebuf_write(&ap->buf, swr_buf, swr_buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next time, it will remain at least the current empty space
|
||||||
|
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||||
|
|
||||||
|
// Read the value written by the SDL thread under lock
|
||||||
|
float avg;
|
||||||
|
bool has_avg = sc_average_get(&ap->avg_buffered_samples, &avg);
|
||||||
|
|
||||||
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
|
|
||||||
|
if (has_avg) {
|
||||||
|
ap->samples_since_resync += samples_written;
|
||||||
|
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||||
|
// Resync every second
|
||||||
|
ap->samples_since_resync = 0;
|
||||||
|
|
||||||
|
int diff = SC_TARGET_BUFFERED_SAMPLES - avg;
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
LOGI("[Audio] Average buffered samples = %f, compensation %d",
|
||||||
|
avg, diff);
|
||||||
|
#endif
|
||||||
|
// Compensate the diff over 3 seconds (but will be recomputed after
|
||||||
|
// 1 second)
|
||||||
|
int ret = swr_set_compensation(swr_ctx, diff, 3 * ap->sample_rate);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGW("Resampling compensation failed: %d", ret);
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audio_player_init(struct sc_audio_player *ap) {
|
||||||
|
static const struct sc_frame_sink_ops ops = {
|
||||||
|
.open = sc_audio_player_frame_sink_open,
|
||||||
|
.close = sc_audio_player_frame_sink_close,
|
||||||
|
.push = sc_audio_player_frame_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
ap->frame_sink.ops = &ops;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef SC_AUDIO_PLAYER_H
|
||||||
|
#define SC_AUDIO_PLAYER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "trait/frame_sink.h"
|
||||||
|
#include <util/average.h>
|
||||||
|
#include <util/bytebuf.h>
|
||||||
|
#include <util/thread.h>
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
struct sc_audio_player {
|
||||||
|
struct sc_frame_sink frame_sink;
|
||||||
|
|
||||||
|
SDL_AudioDeviceID device;
|
||||||
|
|
||||||
|
// protected by SDL_AudioDeviceLock()
|
||||||
|
struct sc_bytebuf buf;
|
||||||
|
// Number of bytes which could be written without locking
|
||||||
|
size_t safe_empty_buffer;
|
||||||
|
|
||||||
|
struct SwrContext *swr_ctx;
|
||||||
|
|
||||||
|
// The sample rate is the same for input and output
|
||||||
|
unsigned sample_rate;
|
||||||
|
// The number of channels is the same for input and output
|
||||||
|
unsigned nb_channels;
|
||||||
|
|
||||||
|
unsigned out_bytes_per_sample;
|
||||||
|
|
||||||
|
// Target buffer for resampling
|
||||||
|
uint8_t *swr_buf;
|
||||||
|
size_t swr_buf_alloc_size;
|
||||||
|
|
||||||
|
// Number of buffered samples (may be negative on underflow)
|
||||||
|
struct sc_average avg_buffered_samples;
|
||||||
|
unsigned samples_since_resync;
|
||||||
|
|
||||||
|
const struct sc_audio_player_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_audio_player_callbacks {
|
||||||
|
void (*on_ended)(struct sc_audio_player *ap, bool success, void *userdata);
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audio_player_init(struct sc_audio_player *ap);
|
||||||
|
|
||||||
|
#endif
|
||||||
+221
-57
@@ -17,46 +17,59 @@
|
|||||||
#define STR_IMPL_(x) #x
|
#define STR_IMPL_(x) #x
|
||||||
#define STR(x) STR_IMPL_(x)
|
#define STR(x) STR_IMPL_(x)
|
||||||
|
|
||||||
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
enum {
|
||||||
#define OPT_WINDOW_TITLE 1001
|
OPT_RENDER_EXPIRED_FRAMES = 1000,
|
||||||
#define OPT_PUSH_TARGET 1002
|
OPT_WINDOW_TITLE,
|
||||||
#define OPT_ALWAYS_ON_TOP 1003
|
OPT_PUSH_TARGET,
|
||||||
#define OPT_CROP 1004
|
OPT_ALWAYS_ON_TOP,
|
||||||
#define OPT_RECORD_FORMAT 1005
|
OPT_CROP,
|
||||||
#define OPT_PREFER_TEXT 1006
|
OPT_RECORD_FORMAT,
|
||||||
#define OPT_WINDOW_X 1007
|
OPT_PREFER_TEXT,
|
||||||
#define OPT_WINDOW_Y 1008
|
OPT_WINDOW_X,
|
||||||
#define OPT_WINDOW_WIDTH 1009
|
OPT_WINDOW_Y,
|
||||||
#define OPT_WINDOW_HEIGHT 1010
|
OPT_WINDOW_WIDTH,
|
||||||
#define OPT_WINDOW_BORDERLESS 1011
|
OPT_WINDOW_HEIGHT,
|
||||||
#define OPT_MAX_FPS 1012
|
OPT_WINDOW_BORDERLESS,
|
||||||
#define OPT_LOCK_VIDEO_ORIENTATION 1013
|
OPT_MAX_FPS,
|
||||||
#define OPT_DISPLAY_ID 1014
|
OPT_LOCK_VIDEO_ORIENTATION,
|
||||||
#define OPT_ROTATION 1015
|
OPT_DISPLAY_ID,
|
||||||
#define OPT_RENDER_DRIVER 1016
|
OPT_ROTATION,
|
||||||
#define OPT_NO_MIPMAPS 1017
|
OPT_RENDER_DRIVER,
|
||||||
#define OPT_CODEC_OPTIONS 1018
|
OPT_NO_MIPMAPS,
|
||||||
#define OPT_FORCE_ADB_FORWARD 1019
|
OPT_CODEC_OPTIONS,
|
||||||
#define OPT_DISABLE_SCREENSAVER 1020
|
OPT_VIDEO_CODEC_OPTIONS,
|
||||||
#define OPT_SHORTCUT_MOD 1021
|
OPT_FORCE_ADB_FORWARD,
|
||||||
#define OPT_NO_KEY_REPEAT 1022
|
OPT_DISABLE_SCREENSAVER,
|
||||||
#define OPT_FORWARD_ALL_CLICKS 1023
|
OPT_SHORTCUT_MOD,
|
||||||
#define OPT_LEGACY_PASTE 1024
|
OPT_NO_KEY_REPEAT,
|
||||||
#define OPT_ENCODER_NAME 1025
|
OPT_FORWARD_ALL_CLICKS,
|
||||||
#define OPT_POWER_OFF_ON_CLOSE 1026
|
OPT_LEGACY_PASTE,
|
||||||
#define OPT_V4L2_SINK 1027
|
OPT_ENCODER,
|
||||||
#define OPT_DISPLAY_BUFFER 1028
|
OPT_VIDEO_ENCODER,
|
||||||
#define OPT_V4L2_BUFFER 1029
|
OPT_POWER_OFF_ON_CLOSE,
|
||||||
#define OPT_TUNNEL_HOST 1030
|
OPT_V4L2_SINK,
|
||||||
#define OPT_TUNNEL_PORT 1031
|
OPT_DISPLAY_BUFFER,
|
||||||
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
OPT_V4L2_BUFFER,
|
||||||
#define OPT_TCPIP 1033
|
OPT_TUNNEL_HOST,
|
||||||
#define OPT_RAW_KEY_EVENTS 1034
|
OPT_TUNNEL_PORT,
|
||||||
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
|
OPT_NO_CLIPBOARD_AUTOSYNC,
|
||||||
#define OPT_OTG 1036
|
OPT_TCPIP,
|
||||||
#define OPT_NO_CLEANUP 1037
|
OPT_RAW_KEY_EVENTS,
|
||||||
#define OPT_PRINT_FPS 1038
|
OPT_NO_DOWNSIZE_ON_ERROR,
|
||||||
#define OPT_NO_POWER_ON 1039
|
OPT_OTG,
|
||||||
|
OPT_NO_CLEANUP,
|
||||||
|
OPT_PRINT_FPS,
|
||||||
|
OPT_NO_POWER_ON,
|
||||||
|
OPT_CODEC,
|
||||||
|
OPT_VIDEO_CODEC,
|
||||||
|
OPT_NO_AUDIO,
|
||||||
|
OPT_AUDIO_BIT_RATE,
|
||||||
|
OPT_AUDIO_CODEC,
|
||||||
|
OPT_AUDIO_CODEC_OPTIONS,
|
||||||
|
OPT_AUDIO_ENCODER,
|
||||||
|
OPT_LIST_ENCODERS,
|
||||||
|
OPT_LIST_DISPLAYS,
|
||||||
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
char shortopt;
|
char shortopt;
|
||||||
@@ -98,25 +111,63 @@ static const struct sc_option options[] = {
|
|||||||
.text = "Make scrcpy window always on top (above other windows).",
|
.text = "Make scrcpy window always on top (above other windows).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'b',
|
.longopt_id = OPT_AUDIO_BIT_RATE,
|
||||||
.longopt = "bit-rate",
|
.longopt = "audio-bit-rate",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.text = "Encode the video at the gitven bit-rate, expressed in bits/s. "
|
.text = "Encode the audio at the given bit-rate, expressed in bits/s. "
|
||||||
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
"Default is " STR(DEFAULT_BIT_RATE) ".",
|
"Default is 196K (196000).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CODEC_OPTIONS,
|
.longopt_id = OPT_AUDIO_CODEC,
|
||||||
.longopt = "codec-options",
|
.longopt = "audio-codec",
|
||||||
|
.argdesc = "name",
|
||||||
|
.text = "Select an audio codec (opus or aac).\n"
|
||||||
|
"Default is opus.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_CODEC_OPTIONS,
|
||||||
|
.longopt = "audio-codec-options",
|
||||||
.argdesc = "key[:type]=value[,...]",
|
.argdesc = "key[:type]=value[,...]",
|
||||||
.text = "Set a list of comma-separated key:type=value options for the "
|
.text = "Set a list of comma-separated key:type=value options for the "
|
||||||
"device encoder.\n"
|
"device audio encoder.\n"
|
||||||
"The possible values for 'type' are 'int' (default), 'long', "
|
"The possible values for 'type' are 'int' (default), 'long', "
|
||||||
"'float' and 'string'.\n"
|
"'float' and 'string'.\n"
|
||||||
"The list of possible codec options is available in the "
|
"The list of possible codec options is available in the "
|
||||||
"Android documentation: "
|
"Android documentation: "
|
||||||
"<https://d.android.com/reference/android/media/MediaFormat>",
|
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_ENCODER,
|
||||||
|
.longopt = "audio-encoder",
|
||||||
|
.argdesc = "name",
|
||||||
|
.text = "Use a specific MediaCodec audio encoder (depending on the "
|
||||||
|
"codec provided by --audio-codec).\n"
|
||||||
|
"The available encoders can be listed by --list-encoders.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'b',
|
||||||
|
.longopt = "video-bit-rate",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Encode the video at the given bit-rate, expressed in bits/s. "
|
||||||
|
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
|
"Default is 8M (8000000).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Not really deprecated (--codec has never been released), but without
|
||||||
|
// declaring an explicit --codec option, getopt_long() partial matching
|
||||||
|
// behavior would consider --codec to be equivalent to --codec-options,
|
||||||
|
// which would be confusing.
|
||||||
|
.longopt_id = OPT_CODEC,
|
||||||
|
.longopt = "codec",
|
||||||
|
.argdesc = "value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// deprecated
|
||||||
|
.longopt_id = OPT_CODEC_OPTIONS,
|
||||||
|
.longopt = "codec-options",
|
||||||
|
.argdesc = "key[:type]=value[,...]",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CROP,
|
.longopt_id = OPT_CROP,
|
||||||
.longopt = "crop",
|
.longopt = "crop",
|
||||||
@@ -141,10 +192,9 @@ static const struct sc_option options[] = {
|
|||||||
.longopt_id = OPT_DISPLAY_ID,
|
.longopt_id = OPT_DISPLAY_ID,
|
||||||
.longopt = "display",
|
.longopt = "display",
|
||||||
.argdesc = "id",
|
.argdesc = "id",
|
||||||
.text = "Specify the display id to mirror.\n"
|
.text = "Specify the device display id to mirror.\n"
|
||||||
"The list of possible display ids can be listed by:\n"
|
"The available display ids can be listed by:\n"
|
||||||
" adb shell dumpsys display\n"
|
" scrcpy --list-displays\n"
|
||||||
"(search \"mDisplayId=\" in the output)\n"
|
|
||||||
"Default is 0.",
|
"Default is 0.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -162,10 +212,10 @@ static const struct sc_option options[] = {
|
|||||||
"Also see -d (--select-usb).",
|
"Also see -d (--select-usb).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_ENCODER_NAME,
|
// deprecated
|
||||||
|
.longopt_id = OPT_ENCODER,
|
||||||
.longopt = "encoder",
|
.longopt = "encoder",
|
||||||
.argdesc = "name",
|
.argdesc = "name",
|
||||||
.text = "Use a specific MediaCodec encoder (must be a H.264 encoder).",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
||||||
@@ -215,6 +265,16 @@ static const struct sc_option options[] = {
|
|||||||
"This is a workaround for some devices not behaving as "
|
"This is a workaround for some devices not behaving as "
|
||||||
"expected when setting the device clipboard programmatically.",
|
"expected when setting the device clipboard programmatically.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_LIST_DISPLAYS,
|
||||||
|
.longopt = "list-displays",
|
||||||
|
.text = "List device displays.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_LIST_ENCODERS,
|
||||||
|
.longopt = "list-encoders",
|
||||||
|
.text = "List video and audio encoders available on the device.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
||||||
.longopt = "lock-video-orientation",
|
.longopt = "lock-video-orientation",
|
||||||
@@ -256,6 +316,11 @@ static const struct sc_option options[] = {
|
|||||||
"is preserved.\n"
|
"is preserved.\n"
|
||||||
"Default is 0 (unlimited).",
|
"Default is 0 (unlimited).",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_NO_AUDIO,
|
||||||
|
.longopt = "no-audio",
|
||||||
|
.text = "Disable audio forwarding.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_CLEANUP,
|
.longopt_id = OPT_NO_CLEANUP,
|
||||||
.longopt = "no-cleanup",
|
.longopt = "no-cleanup",
|
||||||
@@ -502,6 +567,33 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "version",
|
.longopt = "version",
|
||||||
.text = "Print the version of scrcpy.",
|
.text = "Print the version of scrcpy.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_VIDEO_CODEC,
|
||||||
|
.longopt = "video-codec",
|
||||||
|
.argdesc = "name",
|
||||||
|
.text = "Select a video codec (h264, h265 or av1).\n"
|
||||||
|
"Default is h264.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_VIDEO_CODEC_OPTIONS,
|
||||||
|
.longopt = "video-codec-options",
|
||||||
|
.argdesc = "key[:type]=value[,...]",
|
||||||
|
.text = "Set a list of comma-separated key:type=value options for the "
|
||||||
|
"device video encoder.\n"
|
||||||
|
"The possible values for 'type' are 'int' (default), 'long', "
|
||||||
|
"'float' and 'string'.\n"
|
||||||
|
"The list of possible codec options is available in the "
|
||||||
|
"Android documentation: "
|
||||||
|
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_VIDEO_ENCODER,
|
||||||
|
.longopt = "video-encoder",
|
||||||
|
.argdesc = "name",
|
||||||
|
.text = "Use a specific MediaCodec video encoder (depending on the "
|
||||||
|
"codec provided by --video-codec).\n"
|
||||||
|
"The available encoders can be listed by --list-encoders.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'w',
|
.shortopt = 'w',
|
||||||
.longopt = "stay-awake",
|
.longopt = "stay-awake",
|
||||||
@@ -1377,6 +1469,38 @@ guess_record_format(const char *filename) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_video_codec(const char *optarg, enum sc_codec *codec) {
|
||||||
|
if (!strcmp(optarg, "h264")) {
|
||||||
|
*codec = SC_CODEC_H264;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!strcmp(optarg, "h265")) {
|
||||||
|
*codec = SC_CODEC_H265;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!strcmp(optarg, "av1")) {
|
||||||
|
*codec = SC_CODEC_AV1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOGE("Unsupported video codec: %s (expected h264, h265 or av1)", optarg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_audio_codec(const char *optarg, enum sc_codec *codec) {
|
||||||
|
if (!strcmp(optarg, "opus")) {
|
||||||
|
*codec = SC_CODEC_OPUS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!strcmp(optarg, "aac")) {
|
||||||
|
*codec = SC_CODEC_AAC;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOGE("Unsupported audio codec: %s (expected opus)", 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) {
|
||||||
@@ -1388,7 +1512,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
if (!parse_bit_rate(optarg, &opts->video_bit_rate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_AUDIO_BIT_RATE:
|
||||||
|
if (!parse_bit_rate(optarg, &opts->audio_bit_rate)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1561,10 +1690,23 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->forward_key_repeat = false;
|
opts->forward_key_repeat = false;
|
||||||
break;
|
break;
|
||||||
case OPT_CODEC_OPTIONS:
|
case OPT_CODEC_OPTIONS:
|
||||||
opts->codec_options = optarg;
|
LOGW("--codec-options is deprecated, use --video-codec-options "
|
||||||
|
"instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_VIDEO_CODEC_OPTIONS:
|
||||||
|
opts->video_codec_options = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_ENCODER_NAME:
|
case OPT_AUDIO_CODEC_OPTIONS:
|
||||||
opts->encoder_name = optarg;
|
opts->audio_codec_options = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_ENCODER:
|
||||||
|
LOGW("--encoder is deprecated, use --video-encoder instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_VIDEO_ENCODER:
|
||||||
|
opts->video_encoder = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_AUDIO_ENCODER:
|
||||||
|
opts->audio_encoder = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_FORCE_ADB_FORWARD:
|
case OPT_FORCE_ADB_FORWARD:
|
||||||
opts->force_adb_forward = true;
|
opts->force_adb_forward = true;
|
||||||
@@ -1601,6 +1743,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_NO_DOWNSIZE_ON_ERROR:
|
case OPT_NO_DOWNSIZE_ON_ERROR:
|
||||||
opts->downsize_on_error = false;
|
opts->downsize_on_error = false;
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_AUDIO:
|
||||||
|
opts->audio = false;
|
||||||
|
break;
|
||||||
case OPT_NO_CLEANUP:
|
case OPT_NO_CLEANUP:
|
||||||
opts->cleanup = false;
|
opts->cleanup = false;
|
||||||
break;
|
break;
|
||||||
@@ -1610,6 +1755,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_PRINT_FPS:
|
case OPT_PRINT_FPS:
|
||||||
opts->start_fps_counter = true;
|
opts->start_fps_counter = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_CODEC:
|
||||||
|
LOGW("--codec is deprecated, use --video-codec instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_VIDEO_CODEC:
|
||||||
|
if (!parse_video_codec(optarg, &opts->video_codec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_AUDIO_CODEC:
|
||||||
|
if (!parse_audio_codec(optarg, &opts->audio_codec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OPT_OTG:
|
case OPT_OTG:
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
opts->otg = true;
|
opts->otg = true;
|
||||||
@@ -1637,6 +1795,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
case OPT_LIST_ENCODERS:
|
||||||
|
opts->list_encoders = true;
|
||||||
|
break;
|
||||||
|
case OPT_LIST_DISPLAYS:
|
||||||
|
opts->list_displays = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+1
-1
@@ -98,7 +98,7 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
|||||||
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
||||||
|
|
||||||
#ifndef SC_CLOCK_NDEBUG
|
#ifndef SC_CLOCK_NDEBUG
|
||||||
LOGD("Clock estimation: %g * pts + %" PRItick,
|
LOGD("Clock estimation: %f * pts + %" PRItick,
|
||||||
clock->slope, clock->offset);
|
clock->slope, clock->offset);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,47 @@ int vasprintf(char **strp, const char *fmt, va_list ap) {
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(HAVE_NRAND48) || !defined(HAVE_JRAND48)
|
||||||
|
#define SC_RAND48_MASK UINT64_C(0xFFFFFFFFFFFF) // 48 bits
|
||||||
|
#define SC_RAND48_A UINT64_C(0x5DEECE66D)
|
||||||
|
#define SC_RAND48_C 0xB
|
||||||
|
static inline uint64_t rand_iter48(uint64_t x) {
|
||||||
|
assert((x & ~SC_RAND48_MASK) == 0);
|
||||||
|
return (x * SC_RAND48_A + SC_RAND48_C) & SC_RAND48_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t rand_iter48_xsubi(unsigned short xsubi[3]) {
|
||||||
|
uint64_t x = ((uint64_t) xsubi[0] << 32)
|
||||||
|
| ((uint64_t) xsubi[1] << 16)
|
||||||
|
| xsubi[2];
|
||||||
|
|
||||||
|
x = rand_iter48(x);
|
||||||
|
|
||||||
|
xsubi[0] = (x >> 32) & 0XFFFF;
|
||||||
|
xsubi[1] = (x >> 16) & 0XFFFF;
|
||||||
|
xsubi[2] = x & 0XFFFF;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_NRAND48
|
||||||
|
long nrand48(unsigned short xsubi[3]) {
|
||||||
|
// range [0, 2^31)
|
||||||
|
return rand_iter48_xsubi(xsubi) >> 17;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_JRAND48
|
||||||
|
long jrand48(unsigned short xsubi[3]) {
|
||||||
|
// range [-2^31, 2^31)
|
||||||
|
union {
|
||||||
|
uint32_t u;
|
||||||
|
int32_t i;
|
||||||
|
} v;
|
||||||
|
v.u = rand_iter48_xsubi(xsubi) >> 16;
|
||||||
|
return v.i;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -37,6 +37,13 @@
|
|||||||
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Not documented in ffmpeg/doc/APIchanges, but the channel_layout API
|
||||||
|
// has been replaced by chlayout in FFmpeg commit
|
||||||
|
// f423497b455da06c1337846902c770028760e094.
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 23, 100)
|
||||||
|
# define SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
|
#endif
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
||||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||||
@@ -59,4 +66,12 @@ int asprintf(char **strp, const char *fmt, ...);
|
|||||||
int vasprintf(char **strp, const char *fmt, va_list ap);
|
int vasprintf(char **strp, const char *fmt, va_list ap);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_NRAND48
|
||||||
|
long nrand48(unsigned short xsubi[3]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_JRAND48
|
||||||
|
long jrand48(unsigned short xsubi[3]);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+40
-27
@@ -5,7 +5,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
|
|||||||
"move",
|
"move",
|
||||||
"cancel",
|
"cancel",
|
||||||
"outside",
|
"outside",
|
||||||
"ponter-down",
|
"pointer-down",
|
||||||
"pointer-up",
|
"pointer-up",
|
||||||
"hover-move",
|
"hover-move",
|
||||||
"scroll",
|
"scroll",
|
||||||
@@ -61,6 +61,22 @@ static const char *const copy_key_labels[] = {
|
|||||||
"cut",
|
"cut",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline const char *
|
||||||
|
get_well_known_pointer_id_name(uint64_t pointer_id) {
|
||||||
|
switch (pointer_id) {
|
||||||
|
case POINTER_ID_MOUSE:
|
||||||
|
return "mouse";
|
||||||
|
case POINTER_ID_GENERIC_FINGER:
|
||||||
|
return "finger";
|
||||||
|
case POINTER_ID_VIRTUAL_MOUSE:
|
||||||
|
return "vmouse";
|
||||||
|
case POINTER_ID_VIRTUAL_FINGER:
|
||||||
|
return "vfinger";
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
write_position(uint8_t *buf, const struct sc_position *position) {
|
||||||
sc_write32be(&buf[0], position->point.x);
|
sc_write32be(&buf[0], position->point.x);
|
||||||
@@ -78,16 +94,6 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
|||||||
return 4 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
|
||||||
to_fixed_point_16(float f) {
|
|
||||||
assert(f >= 0.0f && f <= 1.0f);
|
|
||||||
uint32_t u = f * 0x1p16f; // 2^16
|
|
||||||
if (u >= 0xffff) {
|
|
||||||
u = 0xffff;
|
|
||||||
}
|
|
||||||
return (uint16_t) u;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
@@ -109,18 +115,21 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
|||||||
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
||||||
write_position(&buf[10], &msg->inject_touch_event.position);
|
write_position(&buf[10], &msg->inject_touch_event.position);
|
||||||
uint16_t pressure =
|
uint16_t pressure =
|
||||||
to_fixed_point_16(msg->inject_touch_event.pressure);
|
sc_float_to_u16fp(msg->inject_touch_event.pressure);
|
||||||
sc_write16be(&buf[22], pressure);
|
sc_write16be(&buf[22], pressure);
|
||||||
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
|
sc_write32be(&buf[24], msg->inject_touch_event.action_button);
|
||||||
return 28;
|
sc_write32be(&buf[28], msg->inject_touch_event.buttons);
|
||||||
|
return 32;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||||
sc_write32be(&buf[13],
|
int16_t hscroll =
|
||||||
(uint32_t) msg->inject_scroll_event.hscroll);
|
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
|
||||||
sc_write32be(&buf[17],
|
int16_t vscroll =
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
|
||||||
sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
|
sc_write16be(&buf[13], (uint16_t) hscroll);
|
||||||
return 25;
|
sc_write16be(&buf[15], (uint16_t) vscroll);
|
||||||
|
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
|
||||||
|
return 21;
|
||||||
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
@@ -167,32 +176,36 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
int action = msg->inject_touch_event.action
|
int action = msg->inject_touch_event.action
|
||||||
& AMOTION_EVENT_ACTION_MASK;
|
& AMOTION_EVENT_ACTION_MASK;
|
||||||
uint64_t id = msg->inject_touch_event.pointer_id;
|
uint64_t id = msg->inject_touch_event.pointer_id;
|
||||||
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
|
const char *pointer_name = get_well_known_pointer_id_name(id);
|
||||||
|
if (pointer_name) {
|
||||||
// string pointer id
|
// string pointer id
|
||||||
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
||||||
" pressure=%g buttons=%06lx",
|
" pressure=%f action_button=%06lx buttons=%06lx",
|
||||||
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
|
pointer_name,
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
msg->inject_touch_event.position.point.x,
|
msg->inject_touch_event.position.point.x,
|
||||||
msg->inject_touch_event.position.point.y,
|
msg->inject_touch_event.position.point.y,
|
||||||
msg->inject_touch_event.pressure,
|
msg->inject_touch_event.pressure,
|
||||||
|
(long) msg->inject_touch_event.action_button,
|
||||||
(long) msg->inject_touch_event.buttons);
|
(long) msg->inject_touch_event.buttons);
|
||||||
} else {
|
} else {
|
||||||
// numeric pointer id
|
// numeric pointer id
|
||||||
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
||||||
PRIi32 " pressure=%g buttons=%06lx",
|
PRIi32 " pressure=%f action_button=%06lx"
|
||||||
|
" buttons=%06lx",
|
||||||
id,
|
id,
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
msg->inject_touch_event.position.point.x,
|
msg->inject_touch_event.position.point.x,
|
||||||
msg->inject_touch_event.position.point.y,
|
msg->inject_touch_event.position.point.y,
|
||||||
msg->inject_touch_event.pressure,
|
msg->inject_touch_event.pressure,
|
||||||
|
(long) msg->inject_touch_event.action_button,
|
||||||
(long) msg->inject_touch_event.buttons);
|
(long) msg->inject_touch_event.buttons);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
|
||||||
" vscroll=%" PRIi32 " buttons=%06lx",
|
" vscroll=%f buttons=%06lx",
|
||||||
msg->inject_scroll_event.position.point.x,
|
msg->inject_scroll_event.position.point.x,
|
||||||
msg->inject_scroll_event.position.point.y,
|
msg->inject_scroll_event.position.point.y,
|
||||||
msg->inject_scroll_event.hscroll,
|
msg->inject_scroll_event.hscroll,
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||||
|
|
||||||
|
// Used for injecting an additional virtual pointer for pinch-to-zoom
|
||||||
|
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
|
||||||
|
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
|
||||||
|
|
||||||
enum sc_control_msg_type {
|
enum sc_control_msg_type {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
@@ -61,6 +65,7 @@ struct sc_control_msg {
|
|||||||
} inject_text;
|
} inject_text;
|
||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
|
enum android_motionevent_buttons action_button;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
uint64_t pointer_id;
|
uint64_t pointer_id;
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
@@ -68,8 +73,8 @@ struct sc_control_msg {
|
|||||||
} inject_touch_event;
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
float hscroll;
|
||||||
int32_t vscroll;
|
float vscroll;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
|||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
|
bool ok = sc_receiver_init(&controller->receiver, control_socket, acksync);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_mutex_init(&controller->mutex);
|
ok = sc_mutex_init(&controller->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
sc_receiver_destroy(&controller->receiver);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&controller->msg_cond);
|
ok = sc_cond_init(&controller->msg_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
sc_receiver_destroy(&controller->receiver);
|
||||||
sc_mutex_destroy(&controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ sc_controller_destroy(struct sc_controller *controller) {
|
|||||||
sc_control_msg_destroy(&msg);
|
sc_control_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver_destroy(&controller->receiver);
|
sc_receiver_destroy(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -117,7 +117,7 @@ sc_controller_start(struct sc_controller *controller) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiver_start(&controller->receiver)) {
|
if (!sc_receiver_start(&controller->receiver)) {
|
||||||
sc_controller_stop(controller);
|
sc_controller_stop(controller);
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
return false;
|
return false;
|
||||||
@@ -137,5 +137,5 @@ sc_controller_stop(struct sc_controller *controller) {
|
|||||||
void
|
void
|
||||||
sc_controller_join(struct sc_controller *controller) {
|
sc_controller_join(struct sc_controller *controller) {
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
receiver_join(&controller->receiver);
|
sc_receiver_join(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct sc_controller {
|
|||||||
sc_cond msg_cond;
|
sc_cond msg_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_control_msg_queue queue;
|
struct sc_control_msg_queue queue;
|
||||||
struct receiver receiver;
|
struct sc_receiver receiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
+26
-10
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
@@ -25,11 +26,10 @@ sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
sc_decoder_open_sinks(struct sc_decoder *decoder, const AVCodecContext *ctx) {
|
||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->open(sink)) {
|
if (!sink->ops->open(sink, ctx)) {
|
||||||
LOGE("Could not open frame sink %d", i);
|
|
||||||
sc_decoder_close_first_sinks(decoder, i);
|
sc_decoder_close_first_sinks(decoder, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -48,8 +48,23 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
|
|
||||||
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||||
|
|
||||||
|
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||||
|
// Hardcoded video properties
|
||||||
|
decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
|
} else {
|
||||||
|
// Hardcoded audio properties
|
||||||
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
|
decoder->codec_ctx->ch_layout =
|
||||||
|
(AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
|
||||||
|
#else
|
||||||
|
decoder->codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
decoder->codec_ctx->channels = 2;
|
||||||
|
#endif
|
||||||
|
decoder->codec_ctx->sample_rate = 48000;
|
||||||
|
}
|
||||||
|
|
||||||
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
||||||
LOGE("Could not open codec");
|
LOGE("Decoder '%s': could not open codec", decoder->name);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -62,8 +77,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_decoder_open_sinks(decoder)) {
|
if (!sc_decoder_open_sinks(decoder, decoder->codec_ctx)) {
|
||||||
LOGE("Could not open decoder sinks");
|
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
@@ -86,7 +100,6 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
|||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->push(sink, frame)) {
|
if (!sink->ops->push(sink, frame)) {
|
||||||
LOGE("Could not send frame to sink %d", i);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +117,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
|||||||
|
|
||||||
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
|
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
|
||||||
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not send video packet: %d", ret);
|
LOGE("Decoder '%s': could not send video packet: %d",
|
||||||
|
decoder->name, ret);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
|
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
|
||||||
@@ -117,7 +131,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
|||||||
|
|
||||||
av_frame_unref(decoder->frame);
|
av_frame_unref(decoder->frame);
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not receive video frame: %d", ret);
|
LOGE("Decoder '%s', could not receive video frame: %d",
|
||||||
|
decoder->name, ret);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -143,7 +158,8 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder) {
|
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||||
|
decoder->name = name; // statically allocated
|
||||||
decoder->sink_count = 0;
|
decoder->sink_count = 0;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
|
|||||||
+4
-1
@@ -14,6 +14,8 @@
|
|||||||
struct sc_decoder {
|
struct sc_decoder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
|
const char *name; // must be statically allocated (e.g. a string literal)
|
||||||
|
|
||||||
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
||||||
unsigned sink_count;
|
unsigned sink_count;
|
||||||
|
|
||||||
@@ -21,8 +23,9 @@ struct sc_decoder {
|
|||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The name must be statically allocated (e.g. a string literal)
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder);
|
sc_decoder_init(struct sc_decoder *decoder, const char *name);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
|
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
|
||||||
|
|||||||
+118
-79
@@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
|
#include "packet_merger.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_PACKET_HEADER_SIZE 12
|
#define SC_PACKET_HEADER_SIZE 12
|
||||||
@@ -17,6 +18,42 @@
|
|||||||
|
|
||||||
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
||||||
|
|
||||||
|
static enum AVCodecID
|
||||||
|
sc_demuxer_to_avcodec_id(uint32_t codec_id) {
|
||||||
|
#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII
|
||||||
|
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
|
||||||
|
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
|
||||||
|
#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII
|
||||||
|
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII"
|
||||||
|
switch (codec_id) {
|
||||||
|
case SC_CODEC_ID_H264:
|
||||||
|
return AV_CODEC_ID_H264;
|
||||||
|
case SC_CODEC_ID_H265:
|
||||||
|
return AV_CODEC_ID_HEVC;
|
||||||
|
case SC_CODEC_ID_AV1:
|
||||||
|
return AV_CODEC_ID_AV1;
|
||||||
|
case SC_CODEC_ID_OPUS:
|
||||||
|
return AV_CODEC_ID_OPUS;
|
||||||
|
case SC_CODEC_ID_AAC:
|
||||||
|
return AV_CODEC_ID_AAC;
|
||||||
|
default:
|
||||||
|
LOGE("Unknown codec id 0x%08" PRIx32, codec_id);
|
||||||
|
return AV_CODEC_ID_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||||
|
uint8_t data[4];
|
||||||
|
ssize_t r = net_recv_all(demuxer->socket, data, 4);
|
||||||
|
if (r < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*codec_id = sc_read32be(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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 stream contains raw packets, without time information. When we
|
||||||
@@ -37,8 +74,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
// CK...... ........ ........ ........ ........ ........ ........ ........
|
// CK...... ........ ........ ........ ........ ........ ........ ........
|
||||||
// ^^<------------------------------------------------------------------->
|
// ^^<------------------------------------------------------------------->
|
||||||
// || PTS
|
// || PTS
|
||||||
// | `- config packet
|
// | `- key frame
|
||||||
// `-- key frame
|
// `-- config packet
|
||||||
|
|
||||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||||
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
|
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
|
||||||
@@ -80,7 +117,6 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->push(sink, packet)) {
|
if (!sink->ops->push(sink, packet)) {
|
||||||
LOGE("Could not send config packet to sink %d", i);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,52 +126,9 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
|
||||||
|
|
||||||
// A config packet must not be decoded immediately (it contains no
|
|
||||||
// frame); instead, it must be concatenated with the future data packet.
|
|
||||||
if (demuxer->pending || is_config) {
|
|
||||||
size_t offset;
|
|
||||||
if (demuxer->pending) {
|
|
||||||
offset = demuxer->pending->size;
|
|
||||||
if (av_grow_packet(demuxer->pending, packet->size)) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offset = 0;
|
|
||||||
demuxer->pending = av_packet_alloc();
|
|
||||||
if (!demuxer->pending) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (av_new_packet(demuxer->pending, packet->size)) {
|
|
||||||
LOG_OOM();
|
|
||||||
av_packet_free(&demuxer->pending);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(demuxer->pending->data + offset, packet->data, packet->size);
|
|
||||||
|
|
||||||
if (!is_config) {
|
|
||||||
// prepare the concat packet to send to the decoder
|
|
||||||
demuxer->pending->pts = packet->pts;
|
|
||||||
demuxer->pending->dts = packet->dts;
|
|
||||||
demuxer->pending->flags = packet->flags;
|
|
||||||
packet = demuxer->pending;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = push_packet_to_sinks(demuxer, packet);
|
bool ok = push_packet_to_sinks(demuxer, packet);
|
||||||
|
|
||||||
if (!is_config && demuxer->pending) {
|
|
||||||
// the pending packet must be discarded (consumed or error)
|
|
||||||
av_packet_free(&demuxer->pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not process packet");
|
LOGE("Demuxer '%s': could not process packet", demuxer->name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +153,6 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->open(sink, codec)) {
|
if (!sink->ops->open(sink, codec)) {
|
||||||
LOGE("Could not open packet sink %d", i);
|
|
||||||
sc_demuxer_close_first_sinks(demuxer, i);
|
sc_demuxer_close_first_sinks(demuxer, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -169,50 +161,99 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) {
|
||||||
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
|
if (sink->ops->disable) {
|
||||||
|
sink->ops->disable(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_demuxer(void *data) {
|
run_demuxer(void *data) {
|
||||||
struct sc_demuxer *demuxer = data;
|
struct sc_demuxer *demuxer = data;
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
// Flag to report end-of-stream (i.e. device disconnected)
|
||||||
if (!codec) {
|
bool eos = false;
|
||||||
LOGE("H.264 decoder not found");
|
|
||||||
|
uint32_t raw_codec_id;
|
||||||
|
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Demuxer '%s': stream disabled due to connection error",
|
||||||
|
demuxer->name);
|
||||||
|
eos = true;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
demuxer->codec_ctx = avcodec_alloc_context3(codec);
|
if (raw_codec_id == 0) {
|
||||||
if (!demuxer->codec_ctx) {
|
LOGW("Demuxer '%s': stream explicitly disabled by the device",
|
||||||
LOG_OOM();
|
demuxer->name);
|
||||||
|
sc_demuxer_disable_sinks(demuxer);
|
||||||
|
eos = true;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw_codec_id == 1) {
|
||||||
|
LOGE("Demuxer '%s': stream configuration error on the device",
|
||||||
|
demuxer->name);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
|
||||||
|
if (codec_id == AV_CODEC_ID_NONE) {
|
||||||
|
LOGE("Demuxer '%s': stream disabled due to unsupported codec",
|
||||||
|
demuxer->name);
|
||||||
|
sc_demuxer_disable_sinks(demuxer);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
||||||
|
if (!codec) {
|
||||||
|
LOGE("Demuxer '%s': stream disabled due to missing decoder",
|
||||||
|
demuxer->name);
|
||||||
|
sc_demuxer_disable_sinks(demuxer);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
||||||
LOGE("Could not open demuxer sinks");
|
goto end;
|
||||||
goto finally_free_codec_ctx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
demuxer->parser = av_parser_init(AV_CODEC_ID_H264);
|
// Config packets must be merged with the next non-config packet only for
|
||||||
if (!demuxer->parser) {
|
// video streams
|
||||||
LOGE("Could not initialize parser");
|
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
|
||||||
goto finally_close_sinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must only pass complete frames to av_parser_parse2()!
|
struct sc_packet_merger merger;
|
||||||
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
|
||||||
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
if (must_merge_config_packet) {
|
||||||
|
sc_packet_merger_init(&merger);
|
||||||
|
}
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
AVPacket *packet = av_packet_alloc();
|
||||||
if (!packet) {
|
if (!packet) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto finally_close_parser;
|
goto finally_close_sinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// end of stream
|
// end of stream
|
||||||
|
eos = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (must_merge_config_packet) {
|
||||||
|
// Prepend any config packet to the next media packet
|
||||||
|
ok = sc_packet_merger_merge(&merger, packet);
|
||||||
|
if (!ok) {
|
||||||
|
av_packet_unref(packet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ok = sc_demuxer_push_packet(demuxer, packet);
|
ok = sc_demuxer_push_packet(demuxer, packet);
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -221,33 +262,31 @@ run_demuxer(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("End of frames");
|
LOGD("Demuxer '%s': end of frames", demuxer->name);
|
||||||
|
|
||||||
if (demuxer->pending) {
|
if (must_merge_config_packet) {
|
||||||
av_packet_free(&demuxer->pending);
|
sc_packet_merger_destroy(&merger);
|
||||||
}
|
}
|
||||||
|
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
finally_close_parser:
|
|
||||||
av_parser_close(demuxer->parser);
|
|
||||||
finally_close_sinks:
|
finally_close_sinks:
|
||||||
sc_demuxer_close_sinks(demuxer);
|
sc_demuxer_close_sinks(demuxer);
|
||||||
finally_free_codec_ctx:
|
|
||||||
avcodec_free_context(&demuxer->codec_ctx);
|
|
||||||
end:
|
end:
|
||||||
demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata);
|
demuxer->cbs->on_ended(demuxer, eos, demuxer->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
||||||
|
assert(socket != SC_SOCKET_NONE);
|
||||||
|
|
||||||
|
demuxer->name = name; // statically allocated
|
||||||
demuxer->socket = socket;
|
demuxer->socket = socket;
|
||||||
demuxer->pending = NULL;
|
|
||||||
demuxer->sink_count = 0;
|
demuxer->sink_count = 0;
|
||||||
|
|
||||||
assert(cbs && cbs->on_eos);
|
assert(cbs && cbs->on_ended);
|
||||||
|
|
||||||
demuxer->cbs = cbs;
|
demuxer->cbs = cbs;
|
||||||
demuxer->cbs_userdata = cbs_userdata;
|
demuxer->cbs_userdata = cbs_userdata;
|
||||||
@@ -263,12 +302,12 @@ sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
||||||
LOGD("Starting demuxer thread");
|
LOGD("Demuxer '%s': starting thread", demuxer->name);
|
||||||
|
|
||||||
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
||||||
demuxer);
|
demuxer);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start demuxer thread");
|
LOGE("Demuxer '%s': could not start thread", demuxer->name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+5
-8
@@ -15,28 +15,25 @@
|
|||||||
#define SC_DEMUXER_MAX_SINKS 2
|
#define SC_DEMUXER_MAX_SINKS 2
|
||||||
|
|
||||||
struct sc_demuxer {
|
struct sc_demuxer {
|
||||||
|
const char *name; // must be statically allocated (e.g. a string literal)
|
||||||
|
|
||||||
sc_socket socket;
|
sc_socket socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
|
|
||||||
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
||||||
unsigned sink_count;
|
unsigned sink_count;
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
|
||||||
AVCodecParserContext *parser;
|
|
||||||
// successive packets may need to be concatenated, until a non-config
|
|
||||||
// packet is available
|
|
||||||
AVPacket *pending;
|
|
||||||
|
|
||||||
const struct sc_demuxer_callbacks *cbs;
|
const struct sc_demuxer_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_demuxer_callbacks {
|
struct sc_demuxer_callbacks {
|
||||||
void (*on_eos)(struct sc_demuxer *demuxer, void *userdata);
|
void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The name must be statically allocated (e.g. a string literal)
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
|
|||||||
+7
-5
@@ -1,5 +1,7 @@
|
|||||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
||||||
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||||
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||||
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||||
|
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||||
|
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ decode_image(const char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
|
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
|
||||||
LOGE("Could not open image codec: %s", path);
|
LOGE("Could not open icon image: %s", path);
|
||||||
goto free_ctx;
|
goto free_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,18 +353,20 @@ struct sc_mouse_click_event {
|
|||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
enum sc_action action;
|
enum sc_action action;
|
||||||
enum sc_mouse_button button;
|
enum sc_mouse_button button;
|
||||||
|
uint64_t pointer_id;
|
||||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_mouse_scroll_event {
|
struct sc_mouse_scroll_event {
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
float hscroll;
|
||||||
int32_t vscroll;
|
float vscroll;
|
||||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_mouse_motion_event {
|
struct sc_mouse_motion_event {
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
|
uint64_t pointer_id;
|
||||||
int32_t xrel;
|
int32_t xrel;
|
||||||
int32_t yrel;
|
int32_t yrel;
|
||||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||||
|
|||||||
+15
-3
@@ -335,8 +335,11 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
|||||||
msg.inject_touch_event.action = action;
|
msg.inject_touch_event.action = action;
|
||||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||||
msg.inject_touch_event.position.point = point;
|
msg.inject_touch_event.position.point = point;
|
||||||
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER;
|
msg.inject_touch_event.pointer_id =
|
||||||
|
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
|
||||||
|
: POINTER_ID_VIRTUAL_FINGER;
|
||||||
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
||||||
|
msg.inject_touch_event.action_button = 0;
|
||||||
msg.inject_touch_event.buttons = 0;
|
msg.inject_touch_event.buttons = 0;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||||
@@ -564,6 +567,8 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
|||||||
event->x,
|
event->x,
|
||||||
event->y),
|
event->y),
|
||||||
},
|
},
|
||||||
|
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||||
|
: POINTER_ID_GENERIC_FINGER,
|
||||||
.xrel = event->xrel,
|
.xrel = event->xrel,
|
||||||
.yrel = event->yrel,
|
.yrel = event->yrel,
|
||||||
.buttons_state =
|
.buttons_state =
|
||||||
@@ -687,6 +692,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
},
|
},
|
||||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||||
.button = sc_mouse_button_from_sdl(event->button),
|
.button = sc_mouse_button_from_sdl(event->button),
|
||||||
|
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||||
|
: POINTER_ID_GENERIC_FINGER,
|
||||||
.buttons_state =
|
.buttons_state =
|
||||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||||
im->forward_all_clicks),
|
im->forward_all_clicks),
|
||||||
@@ -747,8 +754,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
||||||
mouse_x, mouse_y),
|
mouse_x, mouse_y),
|
||||||
},
|
},
|
||||||
.hscroll = event->x,
|
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||||
.vscroll = event->y,
|
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
||||||
|
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
||||||
|
#else
|
||||||
|
.hscroll = CLAMP(event->x, -1, 1),
|
||||||
|
.vscroll = CLAMP(event->y, -1, 1),
|
||||||
|
#endif
|
||||||
.buttons_state =
|
.buttons_state =
|
||||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||||
};
|
};
|
||||||
|
|||||||
+60
-5
@@ -15,11 +15,17 @@
|
|||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "usb/scrcpy_otg.h"
|
#include "usb/scrcpy_otg.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
#include "util/net.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include "util/str.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[]) {
|
main_scrcpy(int argc, char *argv[]) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
// disable buffering, we want logs immediately
|
// disable buffering, we want logs immediately
|
||||||
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
|
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
|
||||||
setbuf(stdout, NULL);
|
setbuf(stdout, NULL);
|
||||||
@@ -65,10 +71,12 @@ main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (avformat_network_init()) {
|
if (!net_init()) {
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sc_log_configure();
|
||||||
|
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
|
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
|
||||||
: scrcpy(&args.opts);
|
: scrcpy(&args.opts);
|
||||||
@@ -76,7 +84,54 @@ main(int argc, char *argv[]) {
|
|||||||
enum scrcpy_exit_code ret = scrcpy(&args.opts);
|
enum scrcpy_exit_code ret = scrcpy(&args.opts);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
avformat_network_deinit(); // ignore failure
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[]) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
return main_scrcpy(argc, argv);
|
||||||
|
#else
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
int wargc;
|
||||||
|
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
||||||
|
if (!wargv) {
|
||||||
|
LOG_OOM();
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
|
||||||
|
if (!argv_utf8) {
|
||||||
|
LOG_OOM();
|
||||||
|
LocalFree(wargv);
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
argv_utf8[wargc] = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < wargc; ++i) {
|
||||||
|
argv_utf8[i] = sc_str_from_wchars(wargv[i]);
|
||||||
|
if (!argv_utf8[i]) {
|
||||||
|
LOG_OOM();
|
||||||
|
for (int j = 0; j < i; ++j) {
|
||||||
|
free(argv_utf8[j]);
|
||||||
|
}
|
||||||
|
LocalFree(wargv);
|
||||||
|
free(argv_utf8);
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFree(wargv);
|
||||||
|
|
||||||
|
int ret = main_scrcpy(wargc, argv_utf8);
|
||||||
|
|
||||||
|
for (int i = 0; i < wargc; ++i) {
|
||||||
|
free(argv_utf8[i]);
|
||||||
|
}
|
||||||
|
free(argv_utf8);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
|||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
.inject_touch_event = {
|
.inject_touch_event = {
|
||||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
.action = AMOTION_EVENT_ACTION_MOVE,
|
||||||
.pointer_id = POINTER_ID_MOUSE,
|
.pointer_id = event->pointer_id,
|
||||||
.position = event->position,
|
.position = event->position,
|
||||||
.pressure = 1.f,
|
.pressure = 1.f,
|
||||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
.buttons = convert_mouse_buttons(event->buttons_state),
|
||||||
@@ -90,9 +90,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
.inject_touch_event = {
|
.inject_touch_event = {
|
||||||
.action = convert_mouse_action(event->action),
|
.action = convert_mouse_action(event->action),
|
||||||
.pointer_id = POINTER_ID_MOUSE,
|
.pointer_id = event->pointer_id,
|
||||||
.position = event->position,
|
.position = event->position,
|
||||||
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
|
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
|
||||||
|
.action_button = convert_mouse_buttons(event->button),
|
||||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
.buttons = convert_mouse_buttons(event->buttons_state),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-3
@@ -7,14 +7,19 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.window_title = NULL,
|
.window_title = NULL,
|
||||||
.push_target = NULL,
|
.push_target = NULL,
|
||||||
.render_driver = NULL,
|
.render_driver = NULL,
|
||||||
.codec_options = NULL,
|
.video_codec_options = NULL,
|
||||||
.encoder_name = NULL,
|
.audio_codec_options = NULL,
|
||||||
|
.video_encoder = NULL,
|
||||||
|
.audio_encoder = NULL,
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
.v4l2_device = NULL,
|
.v4l2_device = NULL,
|
||||||
#endif
|
#endif
|
||||||
.log_level = SC_LOG_LEVEL_INFO,
|
.log_level = SC_LOG_LEVEL_INFO,
|
||||||
|
.video_codec = SC_CODEC_H264,
|
||||||
|
.audio_codec = SC_CODEC_OPUS,
|
||||||
.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,
|
||||||
.port_range = {
|
.port_range = {
|
||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
||||||
@@ -26,7 +31,8 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.count = 2,
|
.count = 2,
|
||||||
},
|
},
|
||||||
.max_size = 0,
|
.max_size = 0,
|
||||||
.bit_rate = DEFAULT_BIT_RATE,
|
.video_bit_rate = 0,
|
||||||
|
.audio_bit_rate = 0,
|
||||||
.max_fps = 0,
|
.max_fps = 0,
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
||||||
.rotation = 0,
|
.rotation = 0,
|
||||||
@@ -65,4 +71,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.cleanup = true,
|
.cleanup = true,
|
||||||
.start_fps_counter = false,
|
.start_fps_counter = false,
|
||||||
.power_on = true,
|
.power_on = true,
|
||||||
|
.audio = true,
|
||||||
|
.list_encoders = false,
|
||||||
|
.list_displays = false,
|
||||||
};
|
};
|
||||||
|
|||||||
+19
-3
@@ -23,6 +23,14 @@ enum sc_record_format {
|
|||||||
SC_RECORD_FORMAT_MKV,
|
SC_RECORD_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_codec {
|
||||||
|
SC_CODEC_H264,
|
||||||
|
SC_CODEC_H265,
|
||||||
|
SC_CODEC_AV1,
|
||||||
|
SC_CODEC_OPUS,
|
||||||
|
SC_CODEC_AAC,
|
||||||
|
};
|
||||||
|
|
||||||
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
|
||||||
@@ -87,12 +95,16 @@ struct scrcpy_options {
|
|||||||
const char *window_title;
|
const char *window_title;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
const char *video_codec_options;
|
||||||
const char *encoder_name;
|
const char *audio_codec_options;
|
||||||
|
const char *video_encoder;
|
||||||
|
const char *audio_encoder;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
const char *v4l2_device;
|
const char *v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
|
enum sc_codec video_codec;
|
||||||
|
enum sc_codec audio_codec;
|
||||||
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;
|
||||||
@@ -101,7 +113,8 @@ struct scrcpy_options {
|
|||||||
uint16_t tunnel_port;
|
uint16_t tunnel_port;
|
||||||
struct sc_shortcut_mods shortcut_mods;
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t video_bit_rate;
|
||||||
|
uint32_t audio_bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
@@ -140,6 +153,9 @@ struct scrcpy_options {
|
|||||||
bool cleanup;
|
bool cleanup;
|
||||||
bool start_fps_counter;
|
bool start_fps_counter;
|
||||||
bool power_on;
|
bool power_on;
|
||||||
|
bool audio;
|
||||||
|
bool list_encoders;
|
||||||
|
bool list_displays;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "packet_merger.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_packet_merger_init(struct sc_packet_merger *merger) {
|
||||||
|
merger->config = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_packet_merger_destroy(struct sc_packet_merger *merger) {
|
||||||
|
free(merger->config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet) {
|
||||||
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
|
|
||||||
|
if (is_config) {
|
||||||
|
free(merger->config);
|
||||||
|
|
||||||
|
merger->config = malloc(packet->size);
|
||||||
|
if (!merger->config) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(merger->config, packet->data, packet->size);
|
||||||
|
merger->config_size = packet->size;
|
||||||
|
} else if (merger->config) {
|
||||||
|
size_t config_size = merger->config_size;
|
||||||
|
size_t media_size = packet->size;
|
||||||
|
|
||||||
|
if (av_grow_packet(packet, config_size)) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memmove(packet->data + config_size, packet->data, media_size);
|
||||||
|
memcpy(packet->data, merger->config, config_size);
|
||||||
|
|
||||||
|
free(merger->config);
|
||||||
|
merger->config = NULL;
|
||||||
|
// merger->size is meaningless when merger->config is NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef SC_PACKET_MERGER_H
|
||||||
|
#define SC_PACKET_MERGER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config packets (containing the SPS/PPS) are sent in-band. A new config
|
||||||
|
* packet is sent whenever a new encoding session is started (on start and on
|
||||||
|
* device orientation change).
|
||||||
|
*
|
||||||
|
* Every time a config packet is received, it must be sent alone (for recorder
|
||||||
|
* extradata), then concatenated to the next media packet (for correct decoding
|
||||||
|
* and recording).
|
||||||
|
*
|
||||||
|
* This helper reads every input packet and modifies each media packet which
|
||||||
|
* immediately follows a config packet to prepend the config packet payload.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct sc_packet_merger {
|
||||||
|
uint8_t *config;
|
||||||
|
size_t config_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_packet_merger_init(struct sc_packet_merger *merger);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_packet_merger_destroy(struct sc_packet_merger *merger);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the packet is a config packet, then keep its data for later.
|
||||||
|
* Otherwise (if the packet is a media packet), then if a config packet is
|
||||||
|
* pending, prepend the config packet to this packet (so the packet is
|
||||||
|
* modified!).
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet);
|
||||||
|
|
||||||
|
#endif
|
||||||
+7
-7
@@ -7,7 +7,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -21,12 +21,12 @@ receiver_init(struct receiver *receiver, sc_socket control_socket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver) {
|
sc_receiver_destroy(struct sc_receiver *receiver) {
|
||||||
sc_mutex_destroy(&receiver->mutex);
|
sc_mutex_destroy(&receiver->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
process_msg(struct sc_receiver *receiver, struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
char *current = SDL_GetClipboardText();
|
char *current = SDL_GetClipboardText();
|
||||||
@@ -51,7 +51,7 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) {
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
@@ -76,7 +76,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
run_receiver(void *data) {
|
run_receiver(void *data) {
|
||||||
struct receiver *receiver = data;
|
struct sc_receiver *receiver = data;
|
||||||
|
|
||||||
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
@@ -108,7 +108,7 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_start(struct receiver *receiver) {
|
sc_receiver_start(struct sc_receiver *receiver) {
|
||||||
LOGD("Starting receiver thread");
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
||||||
@@ -122,6 +122,6 @@ receiver_start(struct receiver *receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_join(struct receiver *receiver) {
|
sc_receiver_join(struct sc_receiver *receiver) {
|
||||||
sc_thread_join(&receiver->thread, NULL);
|
sc_thread_join(&receiver->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct receiver {
|
struct sc_receiver {
|
||||||
sc_socket control_socket;
|
sc_socket control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
@@ -20,18 +20,18 @@ struct receiver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
struct sc_acksync *acksync);
|
struct sc_acksync *acksync);
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver);
|
sc_receiver_destroy(struct sc_receiver *receiver);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_start(struct receiver *receiver);
|
sc_receiver_start(struct sc_receiver *receiver);
|
||||||
|
|
||||||
// no receiver_stop(), it will automatically stop on control_socket shutdown
|
// no sc_receiver_stop(), it will automatically stop on control_socket shutdown
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_join(struct receiver *receiver);
|
sc_receiver_join(struct sc_receiver *receiver);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+620
-221
@@ -8,8 +8,11 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to recorder */
|
/** Downcast packet sinks to recorder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink)
|
#define DOWNCAST_VIDEO(SINK) \
|
||||||
|
container_of(SINK, struct sc_recorder, video_packet_sink)
|
||||||
|
#define DOWNCAST_AUDIO(SINK) \
|
||||||
|
container_of(SINK, struct sc_recorder, audio_packet_sink)
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
@@ -78,9 +81,7 @@ sc_recorder_get_format_name(enum sc_record_format format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
sc_recorder_set_extradata(AVStream *ostream, const AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
|
||||||
|
|
||||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
if (!extradata) {
|
if (!extradata) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
@@ -92,170 +93,56 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
|
|
||||||
ostream->codecpar->extradata = extradata;
|
ostream->codecpar->extradata = extradata;
|
||||||
ostream->codecpar->extradata_size = packet->size;
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
|
|
||||||
int ret = avformat_write_header(recorder->ctx, NULL);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write header to %s", recorder->filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static inline void
|
||||||
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) {
|
sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, stream->time_base);
|
||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index,
|
||||||
if (!recorder->header_written) {
|
AVPacket *packet) {
|
||||||
if (packet->pts != AV_NOPTS_VALUE) {
|
AVStream *stream = recorder->ctx->streams[stream_index];
|
||||||
LOGE("The first packet is not a config packet");
|
sc_recorder_rescale_packet(stream, packet);
|
||||||
return false;
|
return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
|
||||||
}
|
|
||||||
bool ok = sc_recorder_write_header(recorder, packet);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
recorder->header_written = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet->pts == AV_NOPTS_VALUE) {
|
|
||||||
// ignore config packets
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_recorder_rescale_packet(recorder, packet);
|
|
||||||
return av_write_frame(recorder->ctx, packet) >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static inline bool
|
||||||
run_recorder(void *data) {
|
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
struct sc_recorder *recorder = data;
|
return sc_recorder_write_stream(recorder, recorder->video_stream_index,
|
||||||
|
packet);
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
static inline bool
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
|
return sc_recorder_write_stream(recorder, recorder->audio_stream_index,
|
||||||
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
packet);
|
||||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if stopped is set, continue to process the remaining events (to
|
|
||||||
// finish the recording) before actually stopping
|
|
||||||
|
|
||||||
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
struct sc_record_packet *last = recorder->previous;
|
|
||||||
if (last) {
|
|
||||||
// assign an arbitrary duration to the last packet
|
|
||||||
last->packet->duration = 100000;
|
|
||||||
bool ok = sc_recorder_write(recorder, last->packet);
|
|
||||||
if (!ok) {
|
|
||||||
// failing to write the last frame is not very serious, no
|
|
||||||
// future frame may depend on it, so the resulting file
|
|
||||||
// will still be valid
|
|
||||||
LOGW("Could not record last packet");
|
|
||||||
}
|
|
||||||
sc_record_packet_delete(last);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_record_packet *rec;
|
|
||||||
sc_queue_take(&recorder->queue, next, &rec);
|
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
// recorder->previous is only written from this thread, no need to lock
|
|
||||||
struct sc_record_packet *previous = recorder->previous;
|
|
||||||
recorder->previous = rec;
|
|
||||||
|
|
||||||
if (!previous) {
|
|
||||||
// we just received the first packet
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// config packets have no PTS, we must ignore them
|
|
||||||
if (rec->packet->pts != AV_NOPTS_VALUE
|
|
||||||
&& previous->packet->pts != AV_NOPTS_VALUE) {
|
|
||||||
// we now know the duration of the previous packet
|
|
||||||
previous->packet->duration =
|
|
||||||
rec->packet->pts - previous->packet->pts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = sc_recorder_write(recorder, previous->packet);
|
|
||||||
sc_record_packet_delete(previous);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not record packet");
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
recorder->failed = true;
|
|
||||||
// discard pending packets
|
|
||||||
sc_recorder_queue_clear(&recorder->queue);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recorder->failed) {
|
|
||||||
if (recorder->header_written) {
|
|
||||||
int ret = av_write_trailer(recorder->ctx);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
|
||||||
recorder->failed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the recorded file is empty
|
|
||||||
recorder->failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->failed) {
|
|
||||||
LOGE("Recording failed to %s", recorder->filename);
|
|
||||||
} else {
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
|
||||||
LOGI("Recording complete to %s file: %s", format_name,
|
|
||||||
recorder->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Recorder thread ended");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
||||||
bool ok = sc_mutex_init(&recorder->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->queue_cond);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_mutex_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_queue_init(&recorder->queue);
|
|
||||||
recorder->stopped = false;
|
|
||||||
recorder->failed = false;
|
|
||||||
recorder->header_written = false;
|
|
||||||
recorder->previous = NULL;
|
|
||||||
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
||||||
assert(format_name);
|
assert(format_name);
|
||||||
const AVOutputFormat *format = find_muxer(format_name);
|
const AVOutputFormat *format = find_muxer(format_name);
|
||||||
if (!format) {
|
if (!format) {
|
||||||
LOGE("Could not find muxer");
|
LOGE("Could not find muxer");
|
||||||
goto error_cond_destroy;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->ctx = avformat_alloc_context();
|
recorder->ctx = avformat_alloc_context();
|
||||||
if (!recorder->ctx) {
|
if (!recorder->ctx) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto error_cond_destroy;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
||||||
|
AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to open output file: %s", recorder->filename);
|
||||||
|
avformat_free_context(recorder->ctx);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
@@ -267,71 +154,441 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
|||||||
av_dict_set(&recorder->ctx->metadata, "comment",
|
av_dict_set(&recorder->ctx->metadata, "comment",
|
||||||
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
|
||||||
if (!ostream) {
|
|
||||||
goto error_avformat_free_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
ostream->codecpar->codec_id = input_codec->id;
|
|
||||||
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
|
||||||
ostream->codecpar->width = recorder->declared_frame_size.width;
|
|
||||||
ostream->codecpar->height = recorder->declared_frame_size.height;
|
|
||||||
|
|
||||||
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
|
||||||
AVIO_FLAG_WRITE);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to open output file: %s", recorder->filename);
|
|
||||||
// ostream will be cleaned up during context cleaning
|
|
||||||
goto error_avformat_free_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Starting recorder thread");
|
|
||||||
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
|
|
||||||
recorder);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not start recorder thread");
|
|
||||||
goto error_avio_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_avio_close:
|
|
||||||
avio_close(recorder->ctx->pb);
|
|
||||||
error_avformat_free_context:
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
error_cond_destroy:
|
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
error_mutex_destroy:
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_close(struct sc_recorder *recorder) {
|
sc_recorder_close_output_file(struct sc_recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
recorder->stopped = true;
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
sc_thread_join(&recorder->thread, NULL);
|
|
||||||
|
|
||||||
avio_close(recorder->ctx->pb);
|
avio_close(recorder->ctx->pb);
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
sc_recorder_wait_video_stream(struct sc_recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
assert(!recorder->stopped);
|
while (!recorder->video_codec && !recorder->stopped) {
|
||||||
|
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
|
||||||
|
}
|
||||||
|
const AVCodec *codec = recorder->video_codec;
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
if (recorder->failed) {
|
if (codec) {
|
||||||
// reject any new packet (this will stop the stream)
|
AVStream *stream = avformat_new_stream(recorder->ctx, codec);
|
||||||
|
if (!stream) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
stream->codecpar->codec_id = codec->id;
|
||||||
|
stream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
stream->codecpar->width = recorder->declared_frame_size.width;
|
||||||
|
stream->codecpar->height = recorder->declared_frame_size.height;
|
||||||
|
|
||||||
|
recorder->video_stream_index = stream->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_wait_audio_stream(struct sc_recorder *recorder) {
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
while (!recorder->audio_codec && !recorder->audio_disabled
|
||||||
|
&& !recorder->stopped) {
|
||||||
|
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->audio_disabled) {
|
||||||
|
// Reset audio flag. From there, the recorder thread may access this
|
||||||
|
// flag without any mutex.
|
||||||
|
recorder->audio = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodec *codec = recorder->audio_codec;
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
if (codec) {
|
||||||
|
AVStream *stream = avformat_new_stream(recorder->ctx, codec);
|
||||||
|
if (!stream) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||||
|
stream->codecpar->codec_id = codec->id;
|
||||||
|
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||||
|
stream->codecpar->ch_layout.nb_channels = 2;
|
||||||
|
#else
|
||||||
|
stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
stream->codecpar->channels = 2;
|
||||||
|
#endif
|
||||||
|
stream->codecpar->sample_rate = 48000;
|
||||||
|
|
||||||
|
recorder->audio_stream_index = stream->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
sc_recorder_has_empty_queues(struct sc_recorder *recorder) {
|
||||||
|
if (sc_queue_is_empty(&recorder->video_queue)) {
|
||||||
|
// The video queue is empty
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->audio && sc_queue_is_empty(&recorder->audio_queue)) {
|
||||||
|
// The audio queue is empty (when audio is enabled)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No queue is empty
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
|
while (!recorder->stopped && sc_recorder_has_empty_queues(recorder)) {
|
||||||
|
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) {
|
||||||
|
// If the recorder is stopped, don't process anything if there are not
|
||||||
|
// at least video packets
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_record_packet *video_pkt;
|
||||||
|
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||||
|
|
||||||
|
struct sc_record_packet *audio_pkt = NULL;
|
||||||
|
if (!sc_queue_is_empty(&recorder->audio_queue)) {
|
||||||
|
assert(recorder->audio);
|
||||||
|
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
int ret = false;
|
||||||
|
|
||||||
|
if (video_pkt->packet->pts != AV_NOPTS_VALUE) {
|
||||||
|
LOGE("The first video packet is not a config packet");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(recorder->video_stream_index >= 0);
|
||||||
|
AVStream *video_stream =
|
||||||
|
recorder->ctx->streams[recorder->video_stream_index];
|
||||||
|
bool ok = sc_recorder_set_extradata(video_stream, video_pkt->packet);
|
||||||
|
if (!ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pkt) {
|
||||||
|
if (audio_pkt->packet->pts != AV_NOPTS_VALUE) {
|
||||||
|
LOGE("The first audio packet is not a config packet");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(recorder->audio_stream_index >= 0);
|
||||||
|
AVStream *audio_stream =
|
||||||
|
recorder->ctx->streams[recorder->audio_stream_index];
|
||||||
|
ok = sc_recorder_set_extradata(audio_stream, audio_pkt->packet);
|
||||||
|
if (!ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = avformat_write_header(recorder->ctx, NULL) >= 0;
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Failed to write header to %s", recorder->filename);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
sc_record_packet_delete(video_pkt);
|
||||||
|
if (audio_pkt) {
|
||||||
|
sc_record_packet_delete(audio_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||||
|
int64_t pts_origin = AV_NOPTS_VALUE;
|
||||||
|
|
||||||
|
bool header_written = sc_recorder_process_header(recorder);
|
||||||
|
if (!header_written) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_record_packet *video_pkt = NULL;
|
||||||
|
struct sc_record_packet *audio_pkt = NULL;
|
||||||
|
|
||||||
|
// We can write a video packet only once we received the next one so that
|
||||||
|
// we can set its duration (next_pts - current_pts)
|
||||||
|
struct sc_record_packet *video_pkt_previous = NULL;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
|
while (!recorder->stopped) {
|
||||||
|
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
||||||
|
// A new packet may be assigned to video_pkt and be processed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (recorder->audio && !audio_pkt
|
||||||
|
&& !sc_queue_is_empty(&recorder->audio_queue)) {
|
||||||
|
// A new packet may be assigned to audio_pkt and be processed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If stopped is set, continue to process the remaining events (to
|
||||||
|
// finish the recording) before actually stopping.
|
||||||
|
|
||||||
|
// If there is no audio, then the audio_queue will remain empty forever
|
||||||
|
// and audio_pkt will always be NULL.
|
||||||
|
assert(recorder->audio
|
||||||
|
|| (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue)));
|
||||||
|
|
||||||
|
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
||||||
|
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!audio_pkt && !sc_queue_is_empty(&recorder->audio_queue)) {
|
||||||
|
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->stopped && !video_pkt && !audio_pkt) {
|
||||||
|
assert(sc_queue_is_empty(&recorder->video_queue));
|
||||||
|
assert(sc_queue_is_empty(&recorder->audio_queue));
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(video_pkt || audio_pkt); // at least one
|
||||||
|
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
// Ignore further config packets (e.g. on device orientation
|
||||||
|
// change). The next non-config packet will have the config packet
|
||||||
|
// data prepended.
|
||||||
|
if (video_pkt && video_pkt->packet->pts == AV_NOPTS_VALUE) {
|
||||||
|
sc_record_packet_delete(video_pkt);
|
||||||
|
video_pkt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pkt && audio_pkt->packet->pts == AV_NOPTS_VALUE) {
|
||||||
|
sc_record_packet_delete(audio_pkt);
|
||||||
|
audio_pkt= NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pts_origin == AV_NOPTS_VALUE) {
|
||||||
|
if (!recorder->audio) {
|
||||||
|
assert(video_pkt);
|
||||||
|
pts_origin = video_pkt->packet->pts;
|
||||||
|
} else if (video_pkt && audio_pkt) {
|
||||||
|
pts_origin =
|
||||||
|
MIN(video_pkt->packet->pts, audio_pkt->packet->pts);
|
||||||
|
} else if (recorder->stopped) {
|
||||||
|
if (video_pkt) {
|
||||||
|
// The recorder is stopped without audio, record the video
|
||||||
|
// packets
|
||||||
|
pts_origin = video_pkt->packet->pts;
|
||||||
|
} else {
|
||||||
|
// Fail if there is no video
|
||||||
|
error = true;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
// If the recorder is stopped while one of the streams has no
|
||||||
|
// packets, then we must avoid a live-loop and correctly record
|
||||||
|
// the stream having packets.
|
||||||
|
pts_origin = video_pkt ? video_pkt->packet->pts
|
||||||
|
: audio_pkt->packet->pts;
|
||||||
|
} else {
|
||||||
|
// We need both video and audio packets to initialize pts_origin
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(pts_origin != AV_NOPTS_VALUE);
|
||||||
|
|
||||||
|
if (video_pkt) {
|
||||||
|
video_pkt->packet->pts -= pts_origin;
|
||||||
|
video_pkt->packet->dts = video_pkt->packet->pts;
|
||||||
|
|
||||||
|
if (video_pkt_previous) {
|
||||||
|
// we now know the duration of the previous packet
|
||||||
|
video_pkt_previous->packet->duration =
|
||||||
|
video_pkt->packet->pts - video_pkt_previous->packet->pts;
|
||||||
|
|
||||||
|
bool ok = sc_recorder_write_video(recorder,
|
||||||
|
video_pkt_previous->packet);
|
||||||
|
sc_record_packet_delete(video_pkt_previous);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not record video packet");
|
||||||
|
error = true;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video_pkt_previous = video_pkt;
|
||||||
|
video_pkt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pkt) {
|
||||||
|
audio_pkt->packet->pts -= pts_origin;
|
||||||
|
audio_pkt->packet->dts = audio_pkt->packet->pts;
|
||||||
|
|
||||||
|
bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not record audio packet");
|
||||||
|
error = true;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_record_packet_delete(audio_pkt);
|
||||||
|
audio_pkt = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the last video packet
|
||||||
|
struct sc_record_packet *last = video_pkt_previous;
|
||||||
|
if (last) {
|
||||||
|
// assign an arbitrary duration to the last packet
|
||||||
|
last->packet->duration = 100000;
|
||||||
|
bool ok = sc_recorder_write_video(recorder, last->packet);
|
||||||
|
if (!ok) {
|
||||||
|
// failing to write the last frame is not very serious, no
|
||||||
|
// future frame may depend on it, so the resulting file
|
||||||
|
// will still be valid
|
||||||
|
LOGW("Could not record last packet");
|
||||||
|
}
|
||||||
|
sc_record_packet_delete(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = av_write_trailer(recorder->ctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||||
|
error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (video_pkt) {
|
||||||
|
sc_record_packet_delete(video_pkt);
|
||||||
|
}
|
||||||
|
if (audio_pkt) {
|
||||||
|
sc_record_packet_delete(audio_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_record(struct sc_recorder *recorder) {
|
||||||
|
bool ok = sc_recorder_open_output_file(recorder);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_recorder_wait_video_stream(recorder);
|
||||||
|
if (!ok) {
|
||||||
|
sc_recorder_close_output_file(recorder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->audio) {
|
||||||
|
ok = sc_recorder_wait_audio_stream(recorder);
|
||||||
|
if (!ok) {
|
||||||
|
sc_recorder_close_output_file(recorder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If recorder->stopped, process any queued packet anyway
|
||||||
|
|
||||||
|
ok = sc_recorder_process_packets(recorder);
|
||||||
|
sc_recorder_close_output_file(recorder);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_recorder(void *data) {
|
||||||
|
struct sc_recorder *recorder = data;
|
||||||
|
|
||||||
|
bool success = sc_recorder_record(recorder);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
// Prevent the producer to push any new packet
|
||||||
|
recorder->stopped = true;
|
||||||
|
// Discard pending packets
|
||||||
|
sc_recorder_queue_clear(&recorder->video_queue);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
||||||
|
LOGI("Recording complete to %s file: %s", format_name,
|
||||||
|
recorder->filename);
|
||||||
|
} else {
|
||||||
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Recorder thread ended");
|
||||||
|
|
||||||
|
recorder->cbs->on_ended(recorder, success, recorder->cbs_userdata);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
||||||
|
const AVCodec *codec) {
|
||||||
|
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
||||||
|
assert(codec);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
if (recorder->stopped) {
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder->video_codec = codec;
|
||||||
|
sc_cond_signal(&recorder->stream_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
|
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
// EOS also stops the recorder
|
||||||
|
recorder->stopped = true;
|
||||||
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
||||||
|
const AVPacket *packet) {
|
||||||
|
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
|
if (recorder->stopped) {
|
||||||
|
// reject any new packet
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -343,7 +600,9 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_queue_push(&recorder->queue, next, rec);
|
rec->packet->stream_index = 0;
|
||||||
|
|
||||||
|
sc_queue_push(&recorder->video_queue, next, rec);
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
@@ -351,51 +610,191 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
|
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
|
||||||
const AVCodec *codec) {
|
const AVCodec *codec) {
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||||
return sc_recorder_open(recorder, codec);
|
assert(recorder->audio);
|
||||||
|
// only written from this thread, no need to lock
|
||||||
|
assert(!recorder->audio_disabled);
|
||||||
|
assert(codec);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
recorder->audio_codec = codec;
|
||||||
|
sc_cond_signal(&recorder->stream_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||||
sc_recorder_close(recorder);
|
assert(recorder->audio);
|
||||||
|
// only written from this thread, no need to lock
|
||||||
|
assert(!recorder->audio_disabled);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
// EOS also stops the recorder
|
||||||
|
recorder->stopped = true;
|
||||||
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
|
sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
|
||||||
const AVPacket *packet) {
|
const AVPacket *packet) {
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||||
return sc_recorder_push(recorder, packet);
|
assert(recorder->audio);
|
||||||
|
// only written from this thread, no need to lock
|
||||||
|
assert(!recorder->audio_disabled);
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
|
if (recorder->stopped) {
|
||||||
|
// reject any new packet
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
||||||
|
if (!rec) {
|
||||||
|
LOG_OOM();
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rec->packet->stream_index = 1;
|
||||||
|
|
||||||
|
sc_queue_push(&recorder->audio_queue, next, rec);
|
||||||
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
|
||||||
|
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||||
|
assert(recorder->audio);
|
||||||
|
// only written from this thread, no need to lock
|
||||||
|
assert(!recorder->audio_disabled);
|
||||||
|
assert(!recorder->audio_codec);
|
||||||
|
|
||||||
|
LOGW("Audio stream recording disabled");
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
recorder->audio_disabled = true;
|
||||||
|
sc_cond_signal(&recorder->stream_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder,
|
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||||
const char *filename,
|
enum sc_record_format format, bool audio,
|
||||||
enum sc_record_format format,
|
struct sc_size declared_frame_size,
|
||||||
struct sc_size declared_frame_size) {
|
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_free_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&recorder->stream_cond);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_queue_cond_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder->audio = audio;
|
||||||
|
|
||||||
|
sc_queue_init(&recorder->video_queue);
|
||||||
|
sc_queue_init(&recorder->audio_queue);
|
||||||
|
recorder->stopped = false;
|
||||||
|
|
||||||
|
recorder->video_codec = NULL;
|
||||||
|
recorder->audio_codec = NULL;
|
||||||
|
recorder->audio_disabled = false;
|
||||||
|
|
||||||
|
recorder->video_stream_index = -1;
|
||||||
|
recorder->audio_stream_index = -1;
|
||||||
|
|
||||||
recorder->format = format;
|
recorder->format = format;
|
||||||
recorder->declared_frame_size = declared_frame_size;
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
assert(cbs && cbs->on_ended);
|
||||||
.open = sc_recorder_packet_sink_open,
|
recorder->cbs = cbs;
|
||||||
.close = sc_recorder_packet_sink_close,
|
recorder->cbs_userdata = cbs_userdata;
|
||||||
.push = sc_recorder_packet_sink_push,
|
|
||||||
|
static const struct sc_packet_sink_ops video_ops = {
|
||||||
|
.open = sc_recorder_video_packet_sink_open,
|
||||||
|
.close = sc_recorder_video_packet_sink_close,
|
||||||
|
.push = sc_recorder_video_packet_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
recorder->packet_sink.ops = &ops;
|
recorder->video_packet_sink.ops = &video_ops;
|
||||||
|
|
||||||
|
if (audio) {
|
||||||
|
static const struct sc_packet_sink_ops audio_ops = {
|
||||||
|
.open = sc_recorder_audio_packet_sink_open,
|
||||||
|
.close = sc_recorder_audio_packet_sink_close,
|
||||||
|
.push = sc_recorder_audio_packet_sink_push,
|
||||||
|
.disable = sc_recorder_audio_packet_sink_disable,
|
||||||
|
};
|
||||||
|
|
||||||
|
recorder->audio_packet_sink.ops = &audio_ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_queue_cond_destroy:
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
|
error_free_filename:
|
||||||
|
free(recorder->filename);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_recorder_start(struct sc_recorder *recorder) {
|
||||||
|
bool ok = sc_thread_create(&recorder->thread, run_recorder,
|
||||||
|
"scrcpy-recorder", recorder);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not start recorder thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_recorder_stop(struct sc_recorder *recorder) {
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
recorder->stopped = true;
|
||||||
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
sc_cond_signal(&recorder->stream_cond);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_recorder_join(struct sc_recorder *recorder) {
|
||||||
|
sc_thread_join(&recorder->thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
sc_recorder_destroy(struct sc_recorder *recorder) {
|
||||||
|
sc_cond_destroy(&recorder->stream_cond);
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-12
@@ -20,32 +20,66 @@ struct sc_record_packet {
|
|||||||
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
||||||
|
|
||||||
struct sc_recorder {
|
struct sc_recorder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink video_packet_sink;
|
||||||
|
struct sc_packet_sink audio_packet_sink;
|
||||||
|
|
||||||
|
/* The audio flag is unprotected:
|
||||||
|
* - it is initialized from sc_recorder_init() from the main thread;
|
||||||
|
* - it may be reset once from the recorder thread if the audio is
|
||||||
|
* disabled dynamically.
|
||||||
|
*
|
||||||
|
* Therefore, once the recorder thread is started, only the recorder thread
|
||||||
|
* may access it without data races.
|
||||||
|
*/
|
||||||
|
bool audio;
|
||||||
|
|
||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
struct sc_size declared_frame_size;
|
||||||
bool header_written;
|
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond queue_cond;
|
sc_cond queue_cond;
|
||||||
bool stopped; // set on recorder_close()
|
// set on sc_recorder_stop(), packet_sink close or recording failure
|
||||||
bool failed; // set on packet write failure
|
bool stopped;
|
||||||
struct sc_recorder_queue queue;
|
struct sc_recorder_queue video_queue;
|
||||||
|
struct sc_recorder_queue audio_queue;
|
||||||
|
|
||||||
// we can write a packet only once we received the next one so that we can
|
// wake up the recorder thread once the video or audio codec is known
|
||||||
// set its duration (next_pts - current_pts)
|
sc_cond stream_cond;
|
||||||
// "previous" is only accessed from the recorder thread, so it does not
|
const AVCodec *video_codec;
|
||||||
// need to be protected by the mutex
|
const AVCodec *audio_codec;
|
||||||
struct sc_record_packet *previous;
|
// Instead of providing an audio_codec, the demuxer may notify that the
|
||||||
|
// stream is disabled if the device could not capture audio
|
||||||
|
bool audio_disabled;
|
||||||
|
|
||||||
|
int video_stream_index;
|
||||||
|
int audio_stream_index;
|
||||||
|
|
||||||
|
const struct sc_recorder_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_recorder_callbacks {
|
||||||
|
void (*on_ended)(struct sc_recorder *recorder, bool success,
|
||||||
|
void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format, bool audio,
|
||||||
struct sc_size declared_frame_size);
|
struct sc_size declared_frame_size,
|
||||||
|
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_recorder_start(struct sc_recorder *recorder);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_recorder_stop(struct sc_recorder *recorder);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_recorder_join(struct sc_recorder *recorder);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder);
|
sc_recorder_destroy(struct sc_recorder *recorder);
|
||||||
|
|||||||
+180
-90
@@ -13,6 +13,7 @@
|
|||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "audio_player.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "demuxer.h"
|
#include "demuxer.h"
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
#include "util/acksync.h"
|
#include "util/acksync.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/rand.h"
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
# include "v4l2_sink.h"
|
# include "v4l2_sink.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -39,8 +41,11 @@
|
|||||||
struct scrcpy {
|
struct scrcpy {
|
||||||
struct sc_server server;
|
struct sc_server server;
|
||||||
struct sc_screen screen;
|
struct sc_screen screen;
|
||||||
struct sc_demuxer demuxer;
|
struct sc_audio_player audio_player;
|
||||||
struct sc_decoder decoder;
|
struct sc_demuxer video_demuxer;
|
||||||
|
struct sc_demuxer audio_demuxer;
|
||||||
|
struct sc_decoder video_decoder;
|
||||||
|
struct sc_decoder audio_decoder;
|
||||||
struct sc_recorder recorder;
|
struct sc_recorder recorder;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
struct sc_v4l2_sink v4l2_sink;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
@@ -154,9 +159,15 @@ event_loop(struct scrcpy *s) {
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case EVENT_STREAM_STOPPED:
|
case SC_EVENT_DEVICE_DISCONNECTED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
|
case SC_EVENT_DEMUXER_ERROR:
|
||||||
|
LOGE("Demuxer error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
case SC_EVENT_RECORDER_ERROR:
|
||||||
|
LOGE("Recorder error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
@@ -175,15 +186,16 @@ await_for_server(bool *connected) {
|
|||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
if (connected) {
|
||||||
*connected = false;
|
*connected = false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
case EVENT_SERVER_CONNECTION_FAILED:
|
case SC_EVENT_SERVER_CONNECTION_FAILED:
|
||||||
LOGE("Server connection failed");
|
|
||||||
return false;
|
return false;
|
||||||
case EVENT_SERVER_CONNECTED:
|
case SC_EVENT_SERVER_CONNECTED:
|
||||||
LOGD("Server connected");
|
if (connected) {
|
||||||
*connected = true;
|
*connected = true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -194,49 +206,47 @@ await_for_server(bool *connected) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_LogPriority
|
static void
|
||||||
sdl_priority_from_av_level(int level) {
|
sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
||||||
switch (level) {
|
void *userdata) {
|
||||||
case AV_LOG_PANIC:
|
(void) recorder;
|
||||||
case AV_LOG_FATAL:
|
(void) userdata;
|
||||||
return SDL_LOG_PRIORITY_CRITICAL;
|
|
||||||
case AV_LOG_ERROR:
|
if (!success) {
|
||||||
return SDL_LOG_PRIORITY_ERROR;
|
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
|
||||||
case AV_LOG_WARNING:
|
|
||||||
return SDL_LOG_PRIORITY_WARN;
|
|
||||||
case AV_LOG_INFO:
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
}
|
}
|
||||||
// do not forward others, which are too verbose
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||||
(void) avcl;
|
void *userdata) {
|
||||||
SDL_LogPriority priority = sdl_priority_from_av_level(level);
|
|
||||||
if (priority == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t fmt_len = strlen(fmt);
|
|
||||||
char *local_fmt = malloc(fmt_len + 10);
|
|
||||||
if (!local_fmt) {
|
|
||||||
LOG_OOM();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
|
||||||
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
|
|
||||||
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
|
||||||
free(local_fmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
|
||||||
(void) demuxer;
|
(void) demuxer;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
if (eos) {
|
||||||
|
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
|
} else {
|
||||||
|
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||||
|
void *userdata) {
|
||||||
|
(void) demuxer;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
// Contrary to the video demuxer, keep mirroring if only the audio fails.
|
||||||
|
// 'eos' is true on end-of-stream, including when audio capture is not
|
||||||
|
// possible on the device (so that scrcpy continue to mirror video without
|
||||||
|
// failing).
|
||||||
|
// However, if an audio configuration failure occurs (for example the user
|
||||||
|
// explicitly selected an unknown audio encoder), 'eos' is false and scrcpy
|
||||||
|
// must exit.
|
||||||
|
|
||||||
|
if (!eos) {
|
||||||
|
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -244,7 +254,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -252,7 +262,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -265,6 +275,15 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
|||||||
// event
|
// event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||||
|
static uint32_t
|
||||||
|
scrcpy_generate_scid() {
|
||||||
|
struct sc_rand rand;
|
||||||
|
sc_rand_init(&rand);
|
||||||
|
// Only use 31 bits to avoid issues with signed values on the Java-side
|
||||||
|
return sc_rand_u32(&rand) & 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
enum scrcpy_exit_code
|
enum scrcpy_exit_code
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
@@ -283,10 +302,12 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
bool file_pusher_initialized = false;
|
bool file_pusher_initialized = false;
|
||||||
bool recorder_initialized = false;
|
bool recorder_initialized = false;
|
||||||
|
bool recorder_started = false;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
bool v4l2_sink_initialized = false;
|
bool v4l2_sink_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool demuxer_started = false;
|
bool video_demuxer_started = false;
|
||||||
|
bool audio_demuxer_started = false;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
bool hid_keyboard_initialized = false;
|
bool hid_keyboard_initialized = false;
|
||||||
@@ -298,25 +319,34 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
|
|
||||||
|
uint32_t scid = scrcpy_generate_scid();
|
||||||
|
|
||||||
struct sc_server_params params = {
|
struct sc_server_params params = {
|
||||||
|
.scid = scid,
|
||||||
.req_serial = options->serial,
|
.req_serial = options->serial,
|
||||||
.select_usb = options->select_usb,
|
.select_usb = options->select_usb,
|
||||||
.select_tcpip = options->select_tcpip,
|
.select_tcpip = options->select_tcpip,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
|
.video_codec = options->video_codec,
|
||||||
|
.audio_codec = options->audio_codec,
|
||||||
.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,
|
||||||
.tunnel_port = options->tunnel_port,
|
.tunnel_port = options->tunnel_port,
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
.bit_rate = options->bit_rate,
|
.video_bit_rate = options->video_bit_rate,
|
||||||
|
.audio_bit_rate = options->audio_bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.lock_video_orientation = options->lock_video_orientation,
|
.lock_video_orientation = options->lock_video_orientation,
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
|
.audio = options->audio,
|
||||||
.show_touches = options->show_touches,
|
.show_touches = options->show_touches,
|
||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.codec_options = options->codec_options,
|
.video_codec_options = options->video_codec_options,
|
||||||
.encoder_name = options->encoder_name,
|
.audio_codec_options = options->audio_codec_options,
|
||||||
|
.video_encoder = options->video_encoder,
|
||||||
|
.audio_encoder = options->audio_encoder,
|
||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
@@ -325,6 +355,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.tcpip_dst = options->tcpip_dst,
|
.tcpip_dst = options->tcpip_dst,
|
||||||
.cleanup = options->cleanup,
|
.cleanup = options->cleanup,
|
||||||
.power_on = options->power_on,
|
.power_on = options->power_on,
|
||||||
|
.list_encoders = options->list_encoders,
|
||||||
|
.list_displays = options->list_displays,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sc_server_callbacks cbs = {
|
static const struct sc_server_callbacks cbs = {
|
||||||
@@ -342,14 +374,27 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
server_started = true;
|
server_started = true;
|
||||||
|
|
||||||
|
if (options->list_encoders || options->list_displays) {
|
||||||
|
bool ok = await_for_server(NULL);
|
||||||
|
ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
sdl_set_hints(options->render_driver);
|
sdl_set_hints(options->render_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize SDL video in addition if display is enabled
|
// Initialize SDL video in addition if display is enabled
|
||||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
if (options->display) {
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
if (SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
goto end;
|
LOGE("Could not initialize SDL video: %s", SDL_GetError());
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->audio && SDL_Init(SDL_INIT_AUDIO)) {
|
||||||
|
LOGE("Could not initialize SDL audio: %s", SDL_GetError());
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
sdl_configure(options->display, options->disable_screensaver);
|
||||||
@@ -357,15 +402,19 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
// Await for server without blocking Ctrl+C handling
|
// Await for server without blocking Ctrl+C handling
|
||||||
bool connected;
|
bool connected;
|
||||||
if (!await_for_server(&connected)) {
|
if (!await_for_server(&connected)) {
|
||||||
|
LOGE("Server connection failed");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
// This is not an error, user requested to quit
|
// This is not an error, user requested to quit
|
||||||
|
LOGD("User requested to quit");
|
||||||
ret = SCRCPY_EXIT_SUCCESS;
|
ret = SCRCPY_EXIT_SUCCESS;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGD("Server connected");
|
||||||
|
|
||||||
// It is necessarily initialized here, since the device is connected
|
// It is necessarily initialized here, since the device is connected
|
||||||
struct sc_server_info *info = &s->server.info;
|
struct sc_server_info *info = &s->server.info;
|
||||||
|
|
||||||
@@ -383,41 +432,55 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
file_pusher_initialized = true;
|
file_pusher_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_decoder *dec = NULL;
|
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
|
||||||
bool needs_decoder = options->display;
|
.on_ended = sc_video_demuxer_on_ended,
|
||||||
|
};
|
||||||
|
sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
|
||||||
|
&video_demuxer_cbs, NULL);
|
||||||
|
|
||||||
|
if (options->audio) {
|
||||||
|
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
|
||||||
|
.on_ended = sc_audio_demuxer_on_ended,
|
||||||
|
};
|
||||||
|
sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket,
|
||||||
|
&audio_demuxer_cbs, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_video_decoder = options->display;
|
||||||
|
bool needs_audio_decoder = options->audio && options->display;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
needs_decoder |= !!options->v4l2_device;
|
needs_video_decoder |= !!options->v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
if (needs_decoder) {
|
if (needs_video_decoder) {
|
||||||
sc_decoder_init(&s->decoder);
|
sc_decoder_init(&s->video_decoder, "video");
|
||||||
dec = &s->decoder;
|
sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink);
|
||||||
|
}
|
||||||
|
if (needs_audio_decoder) {
|
||||||
|
sc_decoder_init(&s->audio_decoder, "audio");
|
||||||
|
sc_demuxer_add_sink(&s->audio_demuxer, &s->audio_decoder.packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_recorder *rec = NULL;
|
|
||||||
if (options->record_filename) {
|
if (options->record_filename) {
|
||||||
if (!sc_recorder_init(&s->recorder,
|
static const struct sc_recorder_callbacks recorder_cbs = {
|
||||||
options->record_filename,
|
.on_ended = sc_recorder_on_ended,
|
||||||
options->record_format,
|
};
|
||||||
info->frame_size)) {
|
if (!sc_recorder_init(&s->recorder, options->record_filename,
|
||||||
|
options->record_format, options->audio,
|
||||||
|
info->frame_size, &recorder_cbs, NULL)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
|
||||||
recorder_initialized = true;
|
recorder_initialized = true;
|
||||||
}
|
|
||||||
|
|
||||||
av_log_set_callback(av_log_callback);
|
if (!sc_recorder_start(&s->recorder)) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
recorder_started = true;
|
||||||
|
|
||||||
static const struct sc_demuxer_callbacks demuxer_cbs = {
|
sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.video_packet_sink);
|
||||||
.on_eos = sc_demuxer_on_eos,
|
if (options->audio) {
|
||||||
};
|
sc_demuxer_add_sink(&s->audio_demuxer,
|
||||||
sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL);
|
&s->recorder.audio_packet_sink);
|
||||||
|
}
|
||||||
if (dec) {
|
|
||||||
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rec) {
|
|
||||||
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_controller *controller = NULL;
|
struct sc_controller *controller = NULL;
|
||||||
@@ -608,7 +671,12 @@ aoa_hid_end:
|
|||||||
}
|
}
|
||||||
screen_initialized = true;
|
screen_initialized = true;
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
sc_decoder_add_sink(&s->video_decoder, &s->screen.frame_sink);
|
||||||
|
|
||||||
|
if (options->audio) {
|
||||||
|
sc_audio_player_init(&s->audio_player);
|
||||||
|
sc_decoder_add_sink(&s->audio_decoder, &s->audio_player.frame_sink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -618,24 +686,31 @@ aoa_hid_end:
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
sc_decoder_add_sink(&s->video_decoder, &s->v4l2_sink.frame_sink);
|
||||||
|
|
||||||
v4l2_sink_initialized = true;
|
v4l2_sink_initialized = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// now we consumed the header values, the socket receives the video stream
|
// now we consumed the header values, the socket receives the video stream
|
||||||
// start the demuxer
|
// start the video demuxer
|
||||||
if (!sc_demuxer_start(&s->demuxer)) {
|
if (!sc_demuxer_start(&s->video_demuxer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
demuxer_started = true;
|
video_demuxer_started = true;
|
||||||
|
|
||||||
|
if (options->audio) {
|
||||||
|
if (!sc_demuxer_start(&s->audio_demuxer)) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
audio_demuxer_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
// Close the window immediately on closing, because screen_destroy() may
|
// Close the window immediately on closing, because screen_destroy() may
|
||||||
// only be called once the demuxer thread is joined (it may take time)
|
// only be called once the video demuxer thread is joined (it may take time)
|
||||||
sc_screen_hide_window(&s->screen);
|
sc_screen_hide_window(&s->screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
@@ -662,6 +737,9 @@ end:
|
|||||||
if (file_pusher_initialized) {
|
if (file_pusher_initialized) {
|
||||||
sc_file_pusher_stop(&s->file_pusher);
|
sc_file_pusher_stop(&s->file_pusher);
|
||||||
}
|
}
|
||||||
|
if (recorder_initialized) {
|
||||||
|
sc_recorder_stop(&s->recorder);
|
||||||
|
}
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_interrupt(&s->screen);
|
sc_screen_interrupt(&s->screen);
|
||||||
}
|
}
|
||||||
@@ -673,8 +751,12 @@ end:
|
|||||||
|
|
||||||
// now that the sockets are shutdown, the demuxer and controller are
|
// now that the sockets are shutdown, the demuxer and controller are
|
||||||
// interrupted, we can join them
|
// interrupted, we can join them
|
||||||
if (demuxer_started) {
|
if (video_demuxer_started) {
|
||||||
sc_demuxer_join(&s->demuxer);
|
sc_demuxer_join(&s->video_demuxer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_demuxer_started) {
|
||||||
|
sc_demuxer_join(&s->audio_demuxer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -693,8 +775,9 @@ end:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Destroy the screen only after the demuxer is guaranteed to be finished,
|
// Destroy the screen only after the video demuxer is guaranteed to be
|
||||||
// because otherwise the screen could receive new frames after destruction
|
// finished, because otherwise the screen could receive new frames after
|
||||||
|
// destruction
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_join(&s->screen);
|
sc_screen_join(&s->screen);
|
||||||
sc_screen_destroy(&s->screen);
|
sc_screen_destroy(&s->screen);
|
||||||
@@ -707,6 +790,9 @@ end:
|
|||||||
sc_controller_destroy(&s->controller);
|
sc_controller_destroy(&s->controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (recorder_started) {
|
||||||
|
sc_recorder_join(&s->recorder);
|
||||||
|
}
|
||||||
if (recorder_initialized) {
|
if (recorder_initialized) {
|
||||||
sc_recorder_destroy(&s->recorder);
|
sc_recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
@@ -716,6 +802,10 @@ end:
|
|||||||
sc_file_pusher_destroy(&s->file_pusher);
|
sc_file_pusher_destroy(&s->file_pusher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server_started) {
|
||||||
|
sc_server_join(&s->server);
|
||||||
|
}
|
||||||
|
|
||||||
sc_server_destroy(&s->server);
|
sc_server_destroy(&s->server);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
+8
-4
@@ -330,7 +330,11 @@ event_watcher(void *data, SDL_Event *event) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||||
|
const AVCodecContext *ctx) {
|
||||||
|
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||||
|
(void) ctx;
|
||||||
|
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct sc_screen *screen = DOWNCAST(sink);
|
||||||
(void) screen;
|
(void) screen;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@@ -371,7 +375,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
bool need_new_event;
|
bool need_new_event;
|
||||||
if (previous_skipped) {
|
if (previous_skipped) {
|
||||||
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead, unless the previous event failed
|
// this new frame instead, unless the previous event failed
|
||||||
need_new_event = screen->event_failed;
|
need_new_event = screen->event_failed;
|
||||||
} else {
|
} else {
|
||||||
@@ -380,7 +384,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
|
|
||||||
if (need_new_event) {
|
if (need_new_event) {
|
||||||
static SDL_Event new_frame_event = {
|
static SDL_Event new_frame_event = {
|
||||||
.type = EVENT_NEW_FRAME,
|
.type = SC_EVENT_NEW_FRAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
@@ -820,7 +824,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
bool relative_mode = sc_screen_is_relative_mode(screen);
|
bool relative_mode = sc_screen_is_relative_mode(screen);
|
||||||
|
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_NEW_FRAME: {
|
case SC_EVENT_NEW_FRAME: {
|
||||||
bool ok = sc_screen_update_frame(screen);
|
bool ok = sc_screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Frame update failed\n");
|
LOGW("Frame update failed\n");
|
||||||
|
|||||||
+193
-56
@@ -8,6 +8,7 @@
|
|||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "adb/adb.h"
|
#include "adb/adb.h"
|
||||||
|
#include "util/binary.h"
|
||||||
#include "util/file.h"
|
#include "util/file.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net_intr.h"
|
#include "util/net_intr.h"
|
||||||
@@ -19,6 +20,9 @@
|
|||||||
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
|
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
|
||||||
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||||
|
|
||||||
|
#define SC_ADB_PORT_DEFAULT 5555
|
||||||
|
#define SC_SOCKET_NAME_PREFIX "scrcpy_"
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@@ -67,8 +71,10 @@ sc_server_params_destroy(struct sc_server_params *params) {
|
|||||||
// The server stores a copy of the params provided by the user
|
// The server stores a copy of the params provided by the user
|
||||||
free((char *) params->req_serial);
|
free((char *) params->req_serial);
|
||||||
free((char *) params->crop);
|
free((char *) params->crop);
|
||||||
free((char *) params->codec_options);
|
free((char *) params->video_codec_options);
|
||||||
free((char *) params->encoder_name);
|
free((char *) params->audio_codec_options);
|
||||||
|
free((char *) params->video_encoder);
|
||||||
|
free((char *) params->audio_encoder);
|
||||||
free((char *) params->tcpip_dst);
|
free((char *) params->tcpip_dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +97,10 @@ sc_server_params_copy(struct sc_server_params *dst,
|
|||||||
|
|
||||||
COPY(req_serial);
|
COPY(req_serial);
|
||||||
COPY(crop);
|
COPY(crop);
|
||||||
COPY(codec_options);
|
COPY(video_codec_options);
|
||||||
COPY(encoder_name);
|
COPY(audio_codec_options);
|
||||||
|
COPY(video_encoder);
|
||||||
|
COPY(audio_encoder);
|
||||||
COPY(tcpip_dst);
|
COPY(tcpip_dst);
|
||||||
#undef COPY
|
#undef COPY
|
||||||
|
|
||||||
@@ -152,6 +160,24 @@ sc_server_sleep(struct sc_server *server, sc_tick deadline) {
|
|||||||
return !stopped;
|
return !stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
sc_server_get_codec_name(enum sc_codec codec) {
|
||||||
|
switch (codec) {
|
||||||
|
case SC_CODEC_H264:
|
||||||
|
return "h264";
|
||||||
|
case SC_CODEC_H265:
|
||||||
|
return "h265";
|
||||||
|
case SC_CODEC_AV1:
|
||||||
|
return "av1";
|
||||||
|
case SC_CODEC_OPUS:
|
||||||
|
return "opus";
|
||||||
|
case SC_CODEC_AAC:
|
||||||
|
return "aac";
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static sc_pid
|
static sc_pid
|
||||||
execute_server(struct sc_server *server,
|
execute_server(struct sc_server *server,
|
||||||
const struct sc_server_params *params) {
|
const struct sc_server_params *params) {
|
||||||
@@ -195,9 +221,25 @@ execute_server(struct sc_server *server,
|
|||||||
cmd[count++] = p; \
|
cmd[count++] = p; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ADD_PARAM("scid=%08x", params->scid);
|
||||||
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
||||||
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
|
|
||||||
|
|
||||||
|
if (params->video_bit_rate) {
|
||||||
|
ADD_PARAM("video_bit_rate=%" PRIu32, params->video_bit_rate);
|
||||||
|
}
|
||||||
|
if (!params->audio) {
|
||||||
|
ADD_PARAM("audio=false");
|
||||||
|
} else if (params->audio_bit_rate) {
|
||||||
|
ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate);
|
||||||
|
}
|
||||||
|
if (params->video_codec != SC_CODEC_H264) {
|
||||||
|
ADD_PARAM("video_codec=%s",
|
||||||
|
sc_server_get_codec_name(params->video_codec));
|
||||||
|
}
|
||||||
|
if (params->audio_codec != SC_CODEC_OPUS) {
|
||||||
|
ADD_PARAM("audio_codec=%s",
|
||||||
|
sc_server_get_codec_name(params->audio_codec));
|
||||||
|
}
|
||||||
if (params->max_size) {
|
if (params->max_size) {
|
||||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||||
}
|
}
|
||||||
@@ -227,11 +269,17 @@ execute_server(struct sc_server *server,
|
|||||||
if (params->stay_awake) {
|
if (params->stay_awake) {
|
||||||
ADD_PARAM("stay_awake=true");
|
ADD_PARAM("stay_awake=true");
|
||||||
}
|
}
|
||||||
if (params->codec_options) {
|
if (params->video_codec_options) {
|
||||||
ADD_PARAM("codec_options=%s", params->codec_options);
|
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||||
}
|
}
|
||||||
if (params->encoder_name) {
|
if (params->audio_codec_options) {
|
||||||
ADD_PARAM("encoder_name=%s", params->encoder_name);
|
ADD_PARAM("audio_codec_options=%s", params->audio_codec_options);
|
||||||
|
}
|
||||||
|
if (params->video_encoder) {
|
||||||
|
ADD_PARAM("video_encoder=%s", params->video_encoder);
|
||||||
|
}
|
||||||
|
if (params->audio_encoder) {
|
||||||
|
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
|
||||||
}
|
}
|
||||||
if (params->power_off_on_close) {
|
if (params->power_off_on_close) {
|
||||||
ADD_PARAM("power_off_on_close=true");
|
ADD_PARAM("power_off_on_close=true");
|
||||||
@@ -252,6 +300,12 @@ execute_server(struct sc_server *server,
|
|||||||
// By default, power_on is true
|
// By default, power_on is true
|
||||||
ADD_PARAM("power_on=false");
|
ADD_PARAM("power_on=false");
|
||||||
}
|
}
|
||||||
|
if (params->list_encoders) {
|
||||||
|
ADD_PARAM("list_encoders=true");
|
||||||
|
}
|
||||||
|
if (params->list_displays) {
|
||||||
|
ADD_PARAM("list_displays=true");
|
||||||
|
}
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
|
|
||||||
@@ -362,9 +416,11 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
|||||||
}
|
}
|
||||||
|
|
||||||
server->serial = NULL;
|
server->serial = NULL;
|
||||||
|
server->device_socket_name = NULL;
|
||||||
server->stopped = false;
|
server->stopped = false;
|
||||||
|
|
||||||
server->video_socket = SC_SOCKET_NONE;
|
server->video_socket = SC_SOCKET_NONE;
|
||||||
|
server->audio_socket = SC_SOCKET_NONE;
|
||||||
server->control_socket = SC_SOCKET_NONE;
|
server->control_socket = SC_SOCKET_NONE;
|
||||||
|
|
||||||
sc_adb_tunnel_init(&server->tunnel);
|
sc_adb_tunnel_init(&server->tunnel);
|
||||||
@@ -393,10 +449,9 @@ device_read_info(struct sc_intr *intr, sc_socket device_socket,
|
|||||||
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
||||||
|
|
||||||
info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8)
|
unsigned char *fields = &buf[SC_DEVICE_NAME_FIELD_LENGTH];
|
||||||
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 1];
|
info->frame_size.width = sc_read16be(fields);
|
||||||
info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
info->frame_size.height = sc_read16be(&fields[2]);
|
||||||
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 3];
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,9 +464,11 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
const char *serial = server->serial;
|
const char *serial = server->serial;
|
||||||
assert(serial);
|
assert(serial);
|
||||||
|
|
||||||
|
bool audio = server->params.audio;
|
||||||
bool control = server->params.control;
|
bool control = server->params.control;
|
||||||
|
|
||||||
sc_socket video_socket = SC_SOCKET_NONE;
|
sc_socket video_socket = SC_SOCKET_NONE;
|
||||||
|
sc_socket audio_socket = SC_SOCKET_NONE;
|
||||||
sc_socket control_socket = SC_SOCKET_NONE;
|
sc_socket control_socket = SC_SOCKET_NONE;
|
||||||
if (!tunnel->forward) {
|
if (!tunnel->forward) {
|
||||||
video_socket = net_accept_intr(&server->intr, tunnel->server_socket);
|
video_socket = net_accept_intr(&server->intr, tunnel->server_socket);
|
||||||
@@ -419,6 +476,14 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audio) {
|
||||||
|
audio_socket =
|
||||||
|
net_accept_intr(&server->intr, tunnel->server_socket);
|
||||||
|
if (audio_socket == SC_SOCKET_NONE) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
control_socket =
|
control_socket =
|
||||||
net_accept_intr(&server->intr, tunnel->server_socket);
|
net_accept_intr(&server->intr, tunnel->server_socket);
|
||||||
@@ -445,6 +510,18 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audio) {
|
||||||
|
audio_socket = net_socket();
|
||||||
|
if (audio_socket == SC_SOCKET_NONE) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host,
|
||||||
|
tunnel_port);
|
||||||
|
if (!ok) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
// we know that the device is listening, we don't need several
|
// we know that the device is listening, we don't need several
|
||||||
// attempts
|
// attempts
|
||||||
@@ -461,7 +538,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we don't need the adb tunnel anymore
|
// we don't need the adb tunnel anymore
|
||||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(tunnel, &server->intr, serial,
|
||||||
|
server->device_socket_name);
|
||||||
|
|
||||||
// The sockets will be closed on stop if device_read_info() fails
|
// The sockets will be closed on stop if device_read_info() fails
|
||||||
bool ok = device_read_info(&server->intr, video_socket, info);
|
bool ok = device_read_info(&server->intr, video_socket, info);
|
||||||
@@ -470,9 +548,11 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(video_socket != SC_SOCKET_NONE);
|
assert(video_socket != SC_SOCKET_NONE);
|
||||||
|
assert(!audio || audio_socket != SC_SOCKET_NONE);
|
||||||
assert(!control || control_socket != SC_SOCKET_NONE);
|
assert(!control || control_socket != SC_SOCKET_NONE);
|
||||||
|
|
||||||
server->video_socket = video_socket;
|
server->video_socket = video_socket;
|
||||||
|
server->audio_socket = audio_socket;
|
||||||
server->control_socket = control_socket;
|
server->control_socket = control_socket;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -484,6 +564,12 @@ fail:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audio_socket != SC_SOCKET_NONE) {
|
||||||
|
if (!net_close(audio_socket)) {
|
||||||
|
LOGW("Could not close audio socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (control_socket != SC_SOCKET_NONE) {
|
if (control_socket != SC_SOCKET_NONE) {
|
||||||
if (!net_close(control_socket)) {
|
if (!net_close(control_socket)) {
|
||||||
LOGW("Could not close control socket");
|
LOGW("Could not close control socket");
|
||||||
@@ -492,7 +578,8 @@ fail:
|
|||||||
|
|
||||||
if (tunnel->enabled) {
|
if (tunnel->enabled) {
|
||||||
// Always leave this function with tunnel disabled
|
// Always leave this function with tunnel disabled
|
||||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(tunnel, &server->intr, serial,
|
||||||
|
server->device_socket_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -513,27 +600,36 @@ sc_server_on_terminated(void *userdata) {
|
|||||||
LOGD("Server terminated");
|
LOGD("Server terminated");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static uint16_t
|
||||||
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
|
get_adb_tcp_port(struct sc_server *server, const char *serial) {
|
||||||
struct sc_intr *intr = &server->intr;
|
struct sc_intr *intr = &server->intr;
|
||||||
|
|
||||||
char *current_port =
|
char *current_port =
|
||||||
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
|
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
|
||||||
if (!current_port) {
|
if (!current_port) {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the device is listening on TCP on port 5555?
|
long value;
|
||||||
bool enabled = !strcmp("5555", current_port);
|
bool ok = sc_str_parse_integer(current_port, &value);
|
||||||
free(current_port);
|
free(current_port);
|
||||||
return enabled;
|
if (!ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 0 || value > 0xFFFF) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
|
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
|
||||||
unsigned attempts, sc_tick delay) {
|
uint16_t expected_port, unsigned attempts,
|
||||||
if (is_tcpip_mode_enabled(server, serial)) {
|
sc_tick delay) {
|
||||||
LOGI("TCP/IP mode enabled");
|
uint16_t adb_port = get_adb_tcp_port(server, serial);
|
||||||
|
if (adb_port == expected_port) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,28 +643,23 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_tcpip_mode_enabled(server, serial)) {
|
adb_port = get_adb_tcp_port(server, serial);
|
||||||
LOGI("TCP/IP mode enabled");
|
if (adb_port == expected_port) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} while (--attempts);
|
} while (--attempts);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
static char *
|
||||||
append_port_5555(const char *ip) {
|
append_port(const char *ip, uint16_t port) {
|
||||||
size_t len = strlen(ip);
|
char *ip_port;
|
||||||
|
int ret = asprintf(&ip_port, "%s:%" PRIu16, ip, port);
|
||||||
// sizeof counts the final '\0'
|
if (ret == -1) {
|
||||||
char *ip_port = malloc(len + sizeof(":5555"));
|
|
||||||
if (!ip_port) {
|
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(ip_port, ip, len);
|
|
||||||
memcpy(ip_port + len, ":5555", sizeof(":5555"));
|
|
||||||
|
|
||||||
return ip_port;
|
return ip_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,34 +677,36 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *ip_port = append_port_5555(ip);
|
uint16_t adb_port = get_adb_tcp_port(server, serial);
|
||||||
free(ip);
|
if (adb_port) {
|
||||||
if (!ip_port) {
|
LOGI("TCP/IP mode already enabled on port %" PRIu16, adb_port);
|
||||||
return NULL;
|
} else {
|
||||||
}
|
LOGI("Enabling TCP/IP mode on port " SC_STR(SC_ADB_PORT_DEFAULT) "...");
|
||||||
|
|
||||||
bool tcp_mode = is_tcpip_mode_enabled(server, serial);
|
bool ok = sc_adb_tcpip(intr, serial, SC_ADB_PORT_DEFAULT,
|
||||||
|
SC_ADB_NO_STDOUT);
|
||||||
if (!tcp_mode) {
|
|
||||||
bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not restart adbd in TCP/IP mode");
|
LOGE("Could not restart adbd in TCP/IP mode");
|
||||||
goto error;
|
free(ip);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned attempts = 40;
|
unsigned attempts = 40;
|
||||||
sc_tick delay = SC_TICK_FROM_MS(250);
|
sc_tick delay = SC_TICK_FROM_MS(250);
|
||||||
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay);
|
ok = wait_tcpip_mode_enabled(server, serial, SC_ADB_PORT_DEFAULT,
|
||||||
|
attempts, delay);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error;
|
free(ip);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adb_port = SC_ADB_PORT_DEFAULT;
|
||||||
|
LOGI("TCP/IP mode enabled on port " SC_STR(SC_ADB_PORT_DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *ip_port = append_port(ip, adb_port);
|
||||||
|
free(ip);
|
||||||
return ip_port;
|
return ip_port;
|
||||||
|
|
||||||
error:
|
|
||||||
free(ip_port);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -640,7 +733,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
|
|||||||
const char *addr) {
|
const char *addr) {
|
||||||
// Append ":5555" if no port is present
|
// Append ":5555" if no port is present
|
||||||
bool contains_port = strchr(addr, ':');
|
bool contains_port = strchr(addr, ':');
|
||||||
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
|
char *ip_port = contains_port ? strdup(addr)
|
||||||
|
: append_port(addr, SC_ADB_PORT_DEFAULT);
|
||||||
if (!ip_port) {
|
if (!ip_port) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return false;
|
return false;
|
||||||
@@ -658,6 +752,11 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
|
|||||||
if (is_already_tcpip) {
|
if (is_already_tcpip) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
LOGI("Device already connected via TCP/IP: %s", serial);
|
LOGI("Device already connected via TCP/IP: %s", serial);
|
||||||
|
server->serial = strdup(serial);
|
||||||
|
if (!server->serial) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,8 +859,32 @@ run_server(void *data) {
|
|||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If --list-* is passed, then the server just prints the requested data
|
||||||
|
// then exits.
|
||||||
|
if (params->list_encoders || params->list_displays) {
|
||||||
|
sc_pid pid = execute_server(server, params);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
goto error_connection_failed;
|
||||||
|
}
|
||||||
|
sc_process_wait(pid, NULL); // ignore exit code
|
||||||
|
sc_process_close(pid);
|
||||||
|
// Wake up await_for_server()
|
||||||
|
server->cbs->on_connected(server, server->cbs_userdata);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x",
|
||||||
|
params->scid);
|
||||||
|
if (r == -1) {
|
||||||
|
LOG_OOM();
|
||||||
|
goto error_connection_failed;
|
||||||
|
}
|
||||||
|
assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8);
|
||||||
|
assert(server->device_socket_name);
|
||||||
|
|
||||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
|
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
|
||||||
params->port_range, params->force_adb_forward);
|
server->device_socket_name, params->port_range,
|
||||||
|
params->force_adb_forward);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
@@ -769,7 +892,8 @@ run_server(void *data) {
|
|||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
sc_pid pid = execute_server(server, params);
|
sc_pid pid = execute_server(server, params);
|
||||||
if (pid == SC_PROCESS_NONE) {
|
if (pid == SC_PROCESS_NONE) {
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial,
|
||||||
|
server->device_socket_name);
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,7 +905,8 @@ run_server(void *data) {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_process_terminate(pid);
|
sc_process_terminate(pid);
|
||||||
sc_process_wait(pid, true); // ignore exit code
|
sc_process_wait(pid, true); // ignore exit code
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial,
|
||||||
|
server->device_socket_name);
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -809,6 +934,11 @@ run_server(void *data) {
|
|||||||
assert(server->video_socket != SC_SOCKET_NONE);
|
assert(server->video_socket != SC_SOCKET_NONE);
|
||||||
net_interrupt(server->video_socket);
|
net_interrupt(server->video_socket);
|
||||||
|
|
||||||
|
if (server->audio_socket != SC_SOCKET_NONE) {
|
||||||
|
// There is no audio_socket if --no-audio is set
|
||||||
|
net_interrupt(server->audio_socket);
|
||||||
|
}
|
||||||
|
|
||||||
if (server->control_socket != SC_SOCKET_NONE) {
|
if (server->control_socket != SC_SOCKET_NONE) {
|
||||||
// There is no control_socket if --no-control is set
|
// There is no control_socket if --no-control is set
|
||||||
net_interrupt(server->control_socket);
|
net_interrupt(server->control_socket);
|
||||||
@@ -861,7 +991,10 @@ sc_server_stop(struct sc_server *server) {
|
|||||||
sc_cond_signal(&server->cond_stopped);
|
sc_cond_signal(&server->cond_stopped);
|
||||||
sc_intr_interrupt(&server->intr);
|
sc_intr_interrupt(&server->intr);
|
||||||
sc_mutex_unlock(&server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_server_join(struct sc_server *server) {
|
||||||
sc_thread_join(&server->thread, NULL);
|
sc_thread_join(&server->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,11 +1003,15 @@ sc_server_destroy(struct sc_server *server) {
|
|||||||
if (server->video_socket != SC_SOCKET_NONE) {
|
if (server->video_socket != SC_SOCKET_NONE) {
|
||||||
net_close(server->video_socket);
|
net_close(server->video_socket);
|
||||||
}
|
}
|
||||||
|
if (server->audio_socket != SC_SOCKET_NONE) {
|
||||||
|
net_close(server->audio_socket);
|
||||||
|
}
|
||||||
if (server->control_socket != SC_SOCKET_NONE) {
|
if (server->control_socket != SC_SOCKET_NONE) {
|
||||||
net_close(server->control_socket);
|
net_close(server->control_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(server->serial);
|
free(server->serial);
|
||||||
|
free(server->device_socket_name);
|
||||||
sc_server_params_destroy(&server->params);
|
sc_server_params_destroy(&server->params);
|
||||||
sc_intr_destroy(&server->intr);
|
sc_intr_destroy(&server->intr);
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
sc_cond_destroy(&server->cond_stopped);
|
||||||
|
|||||||
+18
-3
@@ -22,20 +22,27 @@ struct sc_server_info {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server_params {
|
struct sc_server_params {
|
||||||
|
uint32_t scid;
|
||||||
const char *req_serial;
|
const char *req_serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
|
enum sc_codec video_codec;
|
||||||
|
enum sc_codec audio_codec;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *video_codec_options;
|
||||||
const char *encoder_name;
|
const char *audio_codec_options;
|
||||||
|
const char *video_encoder;
|
||||||
|
const char *audio_encoder;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint32_t tunnel_host;
|
uint32_t tunnel_host;
|
||||||
uint16_t tunnel_port;
|
uint16_t tunnel_port;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t video_bit_rate;
|
||||||
|
uint32_t audio_bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
bool control;
|
bool control;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
|
bool audio;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
@@ -48,12 +55,15 @@ struct sc_server_params {
|
|||||||
bool select_tcpip;
|
bool select_tcpip;
|
||||||
bool cleanup;
|
bool cleanup;
|
||||||
bool power_on;
|
bool power_on;
|
||||||
|
bool list_encoders;
|
||||||
|
bool list_displays;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server {
|
struct sc_server {
|
||||||
// The internal allocated strings are copies owned by the server
|
// The internal allocated strings are copies owned by the server
|
||||||
struct sc_server_params params;
|
struct sc_server_params params;
|
||||||
char *serial;
|
char *serial;
|
||||||
|
char *device_socket_name;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
struct sc_server_info info; // initialized once connected
|
struct sc_server_info info; // initialized once connected
|
||||||
@@ -66,6 +76,7 @@ struct sc_server {
|
|||||||
struct sc_adb_tunnel tunnel;
|
struct sc_adb_tunnel tunnel;
|
||||||
|
|
||||||
sc_socket video_socket;
|
sc_socket video_socket;
|
||||||
|
sc_socket audio_socket;
|
||||||
sc_socket control_socket;
|
sc_socket control_socket;
|
||||||
|
|
||||||
const struct sc_server_callbacks *cbs;
|
const struct sc_server_callbacks *cbs;
|
||||||
@@ -105,6 +116,10 @@ sc_server_start(struct sc_server *server);
|
|||||||
void
|
void
|
||||||
sc_server_stop(struct sc_server *server);
|
sc_server_stop(struct sc_server *server);
|
||||||
|
|
||||||
|
// join the server thread
|
||||||
|
void
|
||||||
|
sc_server_join(struct sc_server *server);
|
||||||
|
|
||||||
// close and release sockets
|
// close and release sockets
|
||||||
void
|
void
|
||||||
sc_server_destroy(struct sc_server *server);
|
sc_server_destroy(struct sc_server *server);
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#ifndef STREAM_H
|
|
||||||
#define STREAM_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
#include "trait/packet_sink.h"
|
|
||||||
#include "util/net.h"
|
|
||||||
#include "util/thread.h"
|
|
||||||
|
|
||||||
#define STREAM_MAX_SINKS 2
|
|
||||||
|
|
||||||
struct stream {
|
|
||||||
sc_socket socket;
|
|
||||||
sc_thread thread;
|
|
||||||
|
|
||||||
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
|
||||||
unsigned sink_count;
|
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
|
||||||
AVCodecParserContext *parser;
|
|
||||||
// successive packets may need to be concatenated, until a non-config
|
|
||||||
// packet is available
|
|
||||||
AVPacket *pending;
|
|
||||||
|
|
||||||
const struct stream_callbacks *cbs;
|
|
||||||
void *cbs_userdata;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct stream_callbacks {
|
|
||||||
void (*on_eos)(struct stream *stream, void *userdata);
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
stream_init(struct stream *stream, sc_socket socket,
|
|
||||||
const struct stream_callbacks *cbs, void *cbs_userdata);
|
|
||||||
|
|
||||||
void
|
|
||||||
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
|
|
||||||
|
|
||||||
bool
|
|
||||||
stream_start(struct stream *stream);
|
|
||||||
|
|
||||||
void
|
|
||||||
stream_join(struct stream *stream);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
|||||||
close(in[0]);
|
close(in[0]);
|
||||||
}
|
}
|
||||||
close(in[1]);
|
close(in[1]);
|
||||||
|
} else {
|
||||||
|
int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666);
|
||||||
|
if (devnull != -1) {
|
||||||
|
dup2(devnull, STDIN_FILENO);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not open /dev/null for stdin");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Do not close stdin in the child process, this makes adb fail on Linux
|
|
||||||
|
|
||||||
if (pout) {
|
if (pout) {
|
||||||
if (out[1] != STDOUT_FILENO) {
|
if (out[1] != STDOUT_FILENO) {
|
||||||
@@ -102,8 +108,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
|||||||
}
|
}
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
} else if (!inherit_stdout) {
|
} else if (!inherit_stdout) {
|
||||||
// Close stdout in the child process
|
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||||
close(STDOUT_FILENO);
|
if (devnull != -1) {
|
||||||
|
dup2(devnull, STDOUT_FILENO);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not open /dev/null for stdout");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perr) {
|
if (perr) {
|
||||||
@@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
|||||||
}
|
}
|
||||||
close(err[0]);
|
close(err[0]);
|
||||||
} else if (!inherit_stderr) {
|
} else if (!inherit_stderr) {
|
||||||
// Close stderr in the child process
|
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||||
close(STDERR_FILENO);
|
if (devnull != -1) {
|
||||||
|
dup2(devnull, STDERR_FILENO);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not open /dev/null for stderr");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ struct sc_frame_sink {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_frame_sink_ops {
|
struct sc_frame_sink_ops {
|
||||||
bool (*open)(struct sc_frame_sink *sink);
|
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
|
||||||
void (*close)(struct sc_frame_sink *sink);
|
void (*close)(struct sc_frame_sink *sink);
|
||||||
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,9 +19,20 @@ struct sc_packet_sink {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_packet_sink_ops {
|
struct sc_packet_sink_ops {
|
||||||
|
/* The codec instance is static, it is valid until the end of the program */
|
||||||
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
|
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
|
||||||
void (*close)(struct sc_packet_sink *sink);
|
void (*close)(struct sc_packet_sink *sink);
|
||||||
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
||||||
|
|
||||||
|
/*/
|
||||||
|
* Called when the input stream has been disabled at runtime.
|
||||||
|
*
|
||||||
|
* If it is called, then open(), close() and push() will never be called.
|
||||||
|
*
|
||||||
|
* It is useful to notify the recorder that the requested audio stream has
|
||||||
|
* finally been disabled because the device could not capture it.
|
||||||
|
*/
|
||||||
|
void (*disable)(struct sc_packet_sink *sink);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
|||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
event.type = EVENT_USB_DEVICE_DISCONNECTED;
|
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED;
|
||||||
int ret = SDL_PushEvent(&event);
|
int ret = SDL_PushEvent(&event);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
||||||
@@ -34,7 +34,7 @@ event_loop(struct scrcpy_otg *s) {
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case EVENT_USB_DEVICE_DISCONNECTED:
|
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
|
|||||||
|
|
||||||
// When non-negative, 'result' contains the number of bytes written
|
// When non-negative, 'result' contains the number of bytes written
|
||||||
char *s = malloc(result + 1);
|
char *s = malloc(result + 1);
|
||||||
|
if (!s) {
|
||||||
|
LOG_OOM();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(s, buffer, result);
|
memcpy(s, buffer, result);
|
||||||
s[result] = '\0';
|
s[result] = '\0';
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include "average.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_average_init(struct sc_average *avg, unsigned range) {
|
||||||
|
avg->range = range;
|
||||||
|
avg->avg = 0;
|
||||||
|
avg->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_average_push(struct sc_average *avg, float value) {
|
||||||
|
if (avg->count < avg->range) {
|
||||||
|
++avg->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(avg->count);
|
||||||
|
avg->avg = ((avg->count - 1) * avg->avg + value) / avg->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_average_get(struct sc_average *avg, float *value) {
|
||||||
|
*value = avg->avg;
|
||||||
|
return avg->count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef SC_AVERAGE
|
||||||
|
#define SC_AVERAGE
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct sc_average {
|
||||||
|
// Current average value
|
||||||
|
float avg;
|
||||||
|
|
||||||
|
// Target range, to update the average as follow:
|
||||||
|
// avg = ((range - 1) * avg + new_value) / range
|
||||||
|
unsigned range;
|
||||||
|
|
||||||
|
// Number of values pushed when less than range (count <= range).
|
||||||
|
// The purpose is to handle the first (range - 1) values properly.
|
||||||
|
unsigned count;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_average_init(struct sc_average *avg, unsigned range);
|
||||||
|
|
||||||
|
/* Push a new value to update the "rolling" average */
|
||||||
|
void
|
||||||
|
sc_average_push(struct sc_average *avg, float value);
|
||||||
|
|
||||||
|
/* Get the current average value (if available)
|
||||||
|
*
|
||||||
|
* An average is available if sc_average_push() has been called at least once.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
sc_average_get(struct sc_average *avg, float *value);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
#ifndef SC_BUFFER_UTIL_H
|
#ifndef SC_BINARY_H
|
||||||
#define SC_BUFFER_UTIL_H
|
#define SC_BINARY_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -43,4 +44,33 @@ sc_read64be(const uint8_t *buf) {
|
|||||||
return ((uint64_t) msb << 32) | lsb;
|
return ((uint64_t) msb << 32) | lsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a float between 0 and 1 to an unsigned 16-bit fixed-point value
|
||||||
|
*/
|
||||||
|
static inline uint16_t
|
||||||
|
sc_float_to_u16fp(float f) {
|
||||||
|
assert(f >= 0.0f && f <= 1.0f);
|
||||||
|
uint32_t u = f * 0x1p16f; // 2^16
|
||||||
|
if (u >= 0xffff) {
|
||||||
|
assert(u == 0x10000); // for f == 1.0f
|
||||||
|
u = 0xffff;
|
||||||
|
}
|
||||||
|
return (uint16_t) u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a float between -1 and 1 to a signed 16-bit fixed-point value
|
||||||
|
*/
|
||||||
|
static inline int16_t
|
||||||
|
sc_float_to_i16fp(float f) {
|
||||||
|
assert(f >= -1.0f && f <= 1.0f);
|
||||||
|
int32_t i = f * 0x1p15f; // 2^15
|
||||||
|
assert(i >= -0x8000);
|
||||||
|
if (i >= 0x7fff) {
|
||||||
|
assert(i == 0x8000); // for f == 1.0f
|
||||||
|
i = 0x7fff;
|
||||||
|
}
|
||||||
|
return (int16_t) i;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#include "bytebuf.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
|
||||||
|
assert(alloc_size);
|
||||||
|
// sufficient, but use more for alignment.
|
||||||
|
buf->data = malloc(alloc_size);
|
||||||
|
if (!buf->data) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->alloc_size = alloc_size;
|
||||||
|
buf->head = 0;
|
||||||
|
buf->tail = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
||||||
|
free(buf->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
||||||
|
assert(len);
|
||||||
|
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||||
|
assert(buf->tail != buf->head); // the buffer could not be empty
|
||||||
|
|
||||||
|
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
|
||||||
|
size_t right_len = right_limit - buf->tail;
|
||||||
|
if (len < right_len) {
|
||||||
|
right_len = len;
|
||||||
|
}
|
||||||
|
memcpy(to, buf->data + buf->tail, right_len);
|
||||||
|
|
||||||
|
if (len > right_len) {
|
||||||
|
memcpy(to + right_len, buf->data, len - right_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->tail = (buf->tail + len) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
||||||
|
assert(len);
|
||||||
|
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||||
|
assert(buf->tail != buf->head); // the buffer could not be empty
|
||||||
|
|
||||||
|
buf->tail = (buf->tail + len) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
||||||
|
assert(len);
|
||||||
|
|
||||||
|
size_t max_len = buf->alloc_size - 1;
|
||||||
|
if (len >= max_len) {
|
||||||
|
// Copy only the right-most bytes
|
||||||
|
memcpy(buf->data, from + len - max_len, max_len);
|
||||||
|
buf->tail = 0;
|
||||||
|
buf->head = max_len;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t right_limit = buf->head < buf->tail ? buf->tail : buf->alloc_size;
|
||||||
|
size_t right_len = right_limit - buf->head;
|
||||||
|
if (len < right_len) {
|
||||||
|
right_len = len;
|
||||||
|
}
|
||||||
|
memcpy(buf->data + buf->head, from, right_len);
|
||||||
|
if (len > right_len) {
|
||||||
|
memcpy(buf->data, from + right_len, len - right_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t empty_space = sc_bytebuf_write_remaining(buf);
|
||||||
|
if (len > empty_space) {
|
||||||
|
buf->tail = (buf->tail + len - empty_space) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
buf->head = (buf->head + len) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
||||||
|
size_t len) {
|
||||||
|
// *This function MUST NOT access buf->tail (even in assert()).*
|
||||||
|
// The purpose of this function is to allow a reader and a writer to access
|
||||||
|
// different parts of the buffer in parallel simultaneously. It is intended
|
||||||
|
// to be called without lock (only sc_bytebuf_commit_write() is intended to
|
||||||
|
// be called with lock held).
|
||||||
|
|
||||||
|
assert(len < buf->alloc_size - 1);
|
||||||
|
|
||||||
|
size_t right_len = buf->alloc_size - buf->head;
|
||||||
|
if (len < right_len) {
|
||||||
|
right_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf->data + buf->head, from, right_len);
|
||||||
|
if (len > right_len) {
|
||||||
|
memcpy(buf->data, from + right_len, len - right_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
||||||
|
assert(len <= sc_bytebuf_write_remaining(buf));
|
||||||
|
buf->head = (buf->head + len) % buf->alloc_size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef SC_BYTEBUF_H
|
||||||
|
#define SC_BYTEBUF_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct sc_bytebuf {
|
||||||
|
uint8_t *data;
|
||||||
|
// The actual capacity is (allocated - 1) so that head == tail is
|
||||||
|
// non-ambiguous
|
||||||
|
size_t alloc_size;
|
||||||
|
size_t head;
|
||||||
|
size_t tail;
|
||||||
|
// empty: tail == head
|
||||||
|
// full: (tail + 1) % allocated == head
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy from the bytebuf to a user-provided array
|
||||||
|
*
|
||||||
|
* The caller must check that len <= buf->len (it is an error to attempt to read
|
||||||
|
* more bytes than available).
|
||||||
|
*
|
||||||
|
* This function is guaranteed to not change the head.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop len bytes from the buffer
|
||||||
|
*
|
||||||
|
* The caller must check that len <= buf->len (it is an error to attempt to skip
|
||||||
|
* more bytes than available).
|
||||||
|
*
|
||||||
|
* This function is guaranteed to not change the head.
|
||||||
|
*
|
||||||
|
* It is equivalent to call sc_bytebuf_read() to some array and discard the
|
||||||
|
* array (but more efficient since there is no copy).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the user-provided array to the bytebuf
|
||||||
|
*
|
||||||
|
* The length of the input array is not restricted:
|
||||||
|
* if len >= sc_bytebuf_write_remaining(buf), then the excessive input bytes
|
||||||
|
* will overwrite the oldest bytes in the buffer.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the user-provided array to the bytebuf, but do not advance the cursor
|
||||||
|
*
|
||||||
|
* The caller must check that len <= buf->len (it is an error to attempt to
|
||||||
|
* write more bytes than available).
|
||||||
|
*
|
||||||
|
* After this function is called, the write must be committed with
|
||||||
|
* sc_bytebuf_commit_write().
|
||||||
|
*
|
||||||
|
* The purpose of this mechanism is to acquire a lock only to commit the write,
|
||||||
|
* but not to perform the actual copy.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit a prepared write
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of bytes which can be read
|
||||||
|
*
|
||||||
|
* It is an error to read more bytes than available.
|
||||||
|
*/
|
||||||
|
static inline size_t
|
||||||
|
sc_bytebuf_read_remaining(struct sc_bytebuf *buf) {
|
||||||
|
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of bytes which can be written without overwriting
|
||||||
|
*
|
||||||
|
* It is not an error to write more bytes than the available space, but this
|
||||||
|
* would overwrite the oldest bytes in the buffer.
|
||||||
|
*/
|
||||||
|
static inline size_t
|
||||||
|
sc_bytebuf_write_remaining(struct sc_bytebuf *buf) {
|
||||||
|
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
static SDL_LogPriority
|
static SDL_LogPriority
|
||||||
log_level_sc_to_sdl(enum sc_log_level level) {
|
log_level_sc_to_sdl(enum sc_log_level level) {
|
||||||
@@ -47,6 +48,7 @@ void
|
|||||||
sc_set_log_level(enum sc_log_level level) {
|
sc_set_log_level(enum sc_log_level level) {
|
||||||
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
|
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||||
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum sc_log_level
|
enum sc_log_level
|
||||||
@@ -85,3 +87,46 @@ sc_log_windows_error(const char *prefix, int error) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static SDL_LogPriority
|
||||||
|
sdl_priority_from_av_level(int level) {
|
||||||
|
switch (level) {
|
||||||
|
case AV_LOG_PANIC:
|
||||||
|
case AV_LOG_FATAL:
|
||||||
|
return SDL_LOG_PRIORITY_CRITICAL;
|
||||||
|
case AV_LOG_ERROR:
|
||||||
|
return SDL_LOG_PRIORITY_ERROR;
|
||||||
|
case AV_LOG_WARNING:
|
||||||
|
return SDL_LOG_PRIORITY_WARN;
|
||||||
|
case AV_LOG_INFO:
|
||||||
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
|
}
|
||||||
|
// do not forward others, which are too verbose
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
||||||
|
(void) avcl;
|
||||||
|
SDL_LogPriority priority = sdl_priority_from_av_level(level);
|
||||||
|
if (priority == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fmt_len = strlen(fmt);
|
||||||
|
char *local_fmt = malloc(fmt_len + 10);
|
||||||
|
if (!local_fmt) {
|
||||||
|
LOG_OOM();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
||||||
|
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
|
||||||
|
SDL_LogMessageV(SDL_LOG_CATEGORY_CUSTOM, priority, local_fmt, vl);
|
||||||
|
free(local_fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_log_configure() {
|
||||||
|
// Redirect FFmpeg logs to SDL logs
|
||||||
|
av_log_set_callback(sc_av_log_callback);
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,4 +35,7 @@ bool
|
|||||||
sc_log_windows_error(const char *prefix, int error);
|
sc_log_windows_error(const char *prefix, int error);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_log_configure();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+11
-12
@@ -3,11 +3,10 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
# include <ws2tcpip.h>
|
# include <ws2tcpip.h>
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
typedef SOCKET sc_raw_socket;
|
typedef SOCKET sc_raw_socket;
|
||||||
@@ -29,10 +28,10 @@
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
net_init(void) {
|
net_init(void) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
WSADATA wsa;
|
WSADATA wsa;
|
||||||
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
|
int res = WSAStartup(MAKEWORD(1, 1), &wsa);
|
||||||
if (res < 0) {
|
if (res) {
|
||||||
LOGE("WSAStartup failed with error %d", res);
|
LOGE("WSAStartup failed with error %d", res);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,14 +41,14 @@ net_init(void) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
net_cleanup(void) {
|
net_cleanup(void) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline sc_socket
|
static inline sc_socket
|
||||||
wrap(sc_raw_socket sock) {
|
wrap(sc_raw_socket sock) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
return SC_SOCKET_NONE;
|
return SC_SOCKET_NONE;
|
||||||
}
|
}
|
||||||
@@ -72,7 +71,7 @@ wrap(sc_raw_socket sock) {
|
|||||||
|
|
||||||
static inline sc_raw_socket
|
static inline sc_raw_socket
|
||||||
unwrap(sc_socket socket) {
|
unwrap(sc_socket socket) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
if (socket == SC_SOCKET_NONE) {
|
if (socket == SC_SOCKET_NONE) {
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
@@ -160,8 +159,8 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
|
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) {
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
sc_raw_socket raw_sock = unwrap(server_socket);
|
||||||
|
|
||||||
int reuse = 1;
|
int reuse = 1;
|
||||||
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
||||||
@@ -248,7 +247,7 @@ net_interrupt(sc_socket socket) {
|
|||||||
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
sc_raw_socket raw_sock = unwrap(socket);
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
if (!atomic_flag_test_and_set(&socket->closed)) {
|
||||||
return !closesocket(raw_sock);
|
return !closesocket(raw_sock);
|
||||||
}
|
}
|
||||||
@@ -262,7 +261,7 @@ bool
|
|||||||
net_close(sc_socket socket) {
|
net_close(sc_socket socket) {
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
sc_raw_socket raw_sock = unwrap(socket);
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
if (!atomic_flag_test_and_set(&socket->closed)) {
|
||||||
ret = !closesocket(raw_sock);
|
ret = !closesocket(raw_sock);
|
||||||
|
|||||||
+3
-4
@@ -5,9 +5,8 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef _WIN32
|
||||||
|
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
# include <stdatomic.h>
|
# include <stdatomic.h>
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
atomic_flag closed;
|
atomic_flag closed;
|
||||||
} *sc_socket;
|
} *sc_socket;
|
||||||
|
|
||||||
#else // not __WINDOWS__
|
#else // not _WIN32
|
||||||
|
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
# define SC_SOCKET_NONE -1
|
# define SC_SOCKET_NONE -1
|
||||||
@@ -40,7 +39,7 @@ bool
|
|||||||
net_connect(sc_socket socket, uint32_t addr, uint16_t port);
|
net_connect(sc_socket socket, uint32_t addr, uint16_t port);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
|
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog);
|
||||||
|
|
||||||
sc_socket
|
sc_socket
|
||||||
net_accept(sc_socket server_socket);
|
net_accept(sc_socket server_socket);
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
|
||||||
uint16_t port, int backlog) {
|
uint16_t port, int backlog) {
|
||||||
if (!sc_intr_set_socket(intr, socket)) {
|
if (!sc_intr_set_socket(intr, server_socket)) {
|
||||||
// Already interrupted
|
// Already interrupted
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = net_listen(socket, addr, port, backlog);
|
bool ret = net_listen(server_socket, addr, port, backlog);
|
||||||
|
|
||||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
|||||||
uint16_t port);
|
uint16_t port);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
|
||||||
uint16_t port, int backlog);
|
uint16_t port, int backlog);
|
||||||
|
|
||||||
sc_socket
|
sc_socket
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include "rand.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "tick.h"
|
||||||
|
|
||||||
|
void sc_rand_init(struct sc_rand *rand) {
|
||||||
|
sc_tick seed = sc_tick_now(); // microsecond precision
|
||||||
|
rand->xsubi[0] = (seed >> 32) & 0xFFFF;
|
||||||
|
rand->xsubi[1] = (seed >> 16) & 0xFFFF;
|
||||||
|
rand->xsubi[2] = seed & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t sc_rand_u32(struct sc_rand *rand) {
|
||||||
|
// jrand returns a value in range [-2^31, 2^31]
|
||||||
|
// conversion from signed to unsigned is well-defined to wrap-around
|
||||||
|
return jrand48(rand->xsubi);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_rand_u64(struct sc_rand *rand) {
|
||||||
|
uint32_t msb = sc_rand_u32(rand);
|
||||||
|
uint32_t lsb = sc_rand_u32(rand);
|
||||||
|
return ((uint64_t) msb << 32) | lsb;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef SC_RAND_H
|
||||||
|
#define SC_RAND_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
struct sc_rand {
|
||||||
|
unsigned short xsubi[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
void sc_rand_init(struct sc_rand *rand);
|
||||||
|
uint32_t sc_rand_u32(struct sc_rand *rand);
|
||||||
|
uint64_t sc_rand_u64(struct sc_rand *rand);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* Stringify a numeric value */
|
||||||
|
#define SC_STR(s) SC_XSTR(s)
|
||||||
|
#define SC_XSTR(s) #s
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like strncpy(), except:
|
* Like strncpy(), except:
|
||||||
* - it copies at most n-1 chars
|
* - it copies at most n-1 chars
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
// Adapted from vlc_vector:
|
// Adapted from vlc_vector:
|
||||||
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>
|
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>
|
||||||
|
|||||||
+6
-3
@@ -156,7 +156,10 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||||
|
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||||
|
(void) ctx;
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
};
|
};
|
||||||
@@ -336,9 +339,9 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink) {
|
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) {
|
||||||
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
return sc_v4l2_sink_open(vs);
|
return sc_v4l2_sink_open(vs, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
+26
-24
@@ -5,7 +5,7 @@
|
|||||||
#include "adb/adb_device.h"
|
#include "adb/adb_device.h"
|
||||||
#include "adb/adb_parser.h"
|
#include "adb/adb_parser.h"
|
||||||
|
|
||||||
static void test_adb_devices() {
|
static void test_adb_devices(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\n"
|
"List of devices attached\n"
|
||||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||||
@@ -31,7 +31,7 @@ static void test_adb_devices() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_cr() {
|
static void test_adb_devices_cr(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\r\n"
|
"List of devices attached\r\n"
|
||||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||||
@@ -57,7 +57,7 @@ static void test_adb_devices_cr() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_daemon_start() {
|
static void test_adb_devices_daemon_start(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"* daemon not running; starting now at tcp:5037\n"
|
"* daemon not running; starting now at tcp:5037\n"
|
||||||
"* daemon started successfully\n"
|
"* daemon started successfully\n"
|
||||||
@@ -78,7 +78,7 @@ static void test_adb_devices_daemon_start() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_daemon_start_mixed() {
|
static void test_adb_devices_daemon_start_mixed(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\n"
|
"List of devices attached\n"
|
||||||
"adb server version (41) doesn't match this client (39); killing...\n"
|
"adb server version (41) doesn't match this client (39); killing...\n"
|
||||||
@@ -105,7 +105,7 @@ static void test_adb_devices_daemon_start_mixed() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_without_eol() {
|
static void test_adb_devices_without_eol(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\n"
|
"List of devices attached\n"
|
||||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||||
@@ -124,7 +124,7 @@ static void test_adb_devices_without_eol() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_without_header() {
|
static void test_adb_devices_without_header(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||||
"device:MyDevice transport_id:1\n";
|
"device:MyDevice transport_id:1\n";
|
||||||
@@ -134,7 +134,7 @@ static void test_adb_devices_without_header() {
|
|||||||
assert(!ok);
|
assert(!ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_corrupted() {
|
static void test_adb_devices_corrupted(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\n"
|
"List of devices attached\n"
|
||||||
"corrupted_garbage\n";
|
"corrupted_garbage\n";
|
||||||
@@ -145,7 +145,7 @@ static void test_adb_devices_corrupted() {
|
|||||||
assert(vec.size == 0);
|
assert(vec.size == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_adb_devices_spaces() {
|
static void test_adb_devices_spaces(void) {
|
||||||
char output[] =
|
char output[] =
|
||||||
"List of devices attached\n"
|
"List of devices attached\n"
|
||||||
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
|
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
|
||||||
@@ -163,81 +163,81 @@ static void test_adb_devices_spaces() {
|
|||||||
sc_adb_devices_destroy(&vec);
|
sc_adb_devices_destroy(&vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_single_line() {
|
static void test_get_ip_single_line(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.12.34\r\r\n";
|
"192.168.12.34\r\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_single_line_without_eol() {
|
static void test_get_ip_single_line_without_eol(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.12.34";
|
"192.168.12.34";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_single_line_with_trailing_space() {
|
static void test_get_ip_single_line_with_trailing_space(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.12.34 \n";
|
"192.168.12.34 \n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_multiline_first_ok() {
|
static void test_get_ip_multiline_first_ok(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.1.2\r\n"
|
"192.168.1.2\r\n"
|
||||||
"10.0.0.0/24 dev rmnet proto kernel scope link src "
|
"10.0.0.0/24 dev rmnet proto kernel scope link src "
|
||||||
"10.0.0.2\r\n";
|
"10.0.0.2\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.2"));
|
assert(!strcmp(ip, "192.168.1.2"));
|
||||||
free(ip);
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_multiline_second_ok() {
|
static void test_get_ip_multiline_second_ok(void) {
|
||||||
char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
|
||||||
"10.0.0.3\r\n"
|
"10.0.0.3\r\n"
|
||||||
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.1.3\r\n";
|
"192.168.1.3\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.3"));
|
assert(!strcmp(ip, "192.168.1.3"));
|
||||||
free(ip);
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_no_wlan() {
|
static void test_get_ip_no_wlan(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
||||||
"192.168.12.34\r\r\n";
|
"192.168.12.34\r\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_no_wlan_without_eol() {
|
static void test_get_ip_no_wlan_without_eol(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
||||||
"192.168.12.34";
|
"192.168.12.34";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_truncated() {
|
static void test_get_ip_truncated(void) {
|
||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip(ip_route);
|
||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,4 +262,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_get_ip_no_wlan();
|
test_get_ip_no_wlan();
|
||||||
test_get_ip_no_wlan_without_eol();
|
test_get_ip_no_wlan_without_eol();
|
||||||
test_get_ip_truncated();
|
test_get_ip_truncated();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "util/binary.h"
|
||||||
|
|
||||||
|
static void test_write16be(void) {
|
||||||
|
uint16_t val = 0xABCD;
|
||||||
|
uint8_t buf[2];
|
||||||
|
|
||||||
|
sc_write16be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write32be(void) {
|
||||||
|
uint32_t val = 0xABCD1234;
|
||||||
|
uint8_t buf[4];
|
||||||
|
|
||||||
|
sc_write32be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
assert(buf[2] == 0x12);
|
||||||
|
assert(buf[3] == 0x34);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write64be(void) {
|
||||||
|
uint64_t val = 0xABCD1234567890EF;
|
||||||
|
uint8_t buf[8];
|
||||||
|
|
||||||
|
sc_write64be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
assert(buf[2] == 0x12);
|
||||||
|
assert(buf[3] == 0x34);
|
||||||
|
assert(buf[4] == 0x56);
|
||||||
|
assert(buf[5] == 0x78);
|
||||||
|
assert(buf[6] == 0x90);
|
||||||
|
assert(buf[7] == 0xEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read16be(void) {
|
||||||
|
uint8_t buf[2] = {0xAB, 0xCD};
|
||||||
|
|
||||||
|
uint16_t val = sc_read16be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read32be(void) {
|
||||||
|
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
|
||||||
|
|
||||||
|
uint32_t val = sc_read32be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD1234);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read64be(void) {
|
||||||
|
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
|
||||||
|
0x56, 0x78, 0x90, 0xEF};
|
||||||
|
|
||||||
|
uint64_t val = sc_read64be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD1234567890EF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_float_to_u16fp(void) {
|
||||||
|
assert(sc_float_to_u16fp(0.0f) == 0);
|
||||||
|
assert(sc_float_to_u16fp(0.03125f) == 0x800);
|
||||||
|
assert(sc_float_to_u16fp(0.0625f) == 0x1000);
|
||||||
|
assert(sc_float_to_u16fp(0.125f) == 0x2000);
|
||||||
|
assert(sc_float_to_u16fp(0.25f) == 0x4000);
|
||||||
|
assert(sc_float_to_u16fp(0.5f) == 0x8000);
|
||||||
|
assert(sc_float_to_u16fp(0.75f) == 0xc000);
|
||||||
|
assert(sc_float_to_u16fp(1.0f) == 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_float_to_i16fp(void) {
|
||||||
|
assert(sc_float_to_i16fp(0.0f) == 0);
|
||||||
|
assert(sc_float_to_i16fp(0.03125f) == 0x400);
|
||||||
|
assert(sc_float_to_i16fp(0.0625f) == 0x800);
|
||||||
|
assert(sc_float_to_i16fp(0.125f) == 0x1000);
|
||||||
|
assert(sc_float_to_i16fp(0.25f) == 0x2000);
|
||||||
|
assert(sc_float_to_i16fp(0.5f) == 0x4000);
|
||||||
|
assert(sc_float_to_i16fp(0.75f) == 0x6000);
|
||||||
|
assert(sc_float_to_i16fp(1.0f) == 0x7fff);
|
||||||
|
|
||||||
|
assert(sc_float_to_i16fp(-0.03125f) == -0x400);
|
||||||
|
assert(sc_float_to_i16fp(-0.0625f) == -0x800);
|
||||||
|
assert(sc_float_to_i16fp(-0.125f) == -0x1000);
|
||||||
|
assert(sc_float_to_i16fp(-0.25f) == -0x2000);
|
||||||
|
assert(sc_float_to_i16fp(-0.5f) == -0x4000);
|
||||||
|
assert(sc_float_to_i16fp(-0.75f) == -0x6000);
|
||||||
|
assert(sc_float_to_i16fp(-1.0f) == -0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_write16be();
|
||||||
|
test_write32be();
|
||||||
|
test_write64be();
|
||||||
|
test_read16be();
|
||||||
|
test_read32be();
|
||||||
|
test_read64be();
|
||||||
|
|
||||||
|
test_float_to_u16fp();
|
||||||
|
test_float_to_i16fp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
|
||||||
|
|
||||||
static void test_buffer_write16be(void) {
|
|
||||||
uint16_t val = 0xABCD;
|
|
||||||
uint8_t buf[2];
|
|
||||||
|
|
||||||
sc_write16be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_write32be(void) {
|
|
||||||
uint32_t val = 0xABCD1234;
|
|
||||||
uint8_t buf[4];
|
|
||||||
|
|
||||||
sc_write32be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
assert(buf[2] == 0x12);
|
|
||||||
assert(buf[3] == 0x34);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_write64be(void) {
|
|
||||||
uint64_t val = 0xABCD1234567890EF;
|
|
||||||
uint8_t buf[8];
|
|
||||||
|
|
||||||
sc_write64be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
assert(buf[2] == 0x12);
|
|
||||||
assert(buf[3] == 0x34);
|
|
||||||
assert(buf[4] == 0x56);
|
|
||||||
assert(buf[5] == 0x78);
|
|
||||||
assert(buf[6] == 0x90);
|
|
||||||
assert(buf[7] == 0xEF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read16be(void) {
|
|
||||||
uint8_t buf[2] = {0xAB, 0xCD};
|
|
||||||
|
|
||||||
uint16_t val = sc_read16be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read32be(void) {
|
|
||||||
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
|
|
||||||
|
|
||||||
uint32_t val = sc_read32be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD1234);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read64be(void) {
|
|
||||||
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
|
|
||||||
0x56, 0x78, 0x90, 0xEF};
|
|
||||||
|
|
||||||
uint64_t val = sc_read64be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD1234567890EF);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_buffer_write16be();
|
|
||||||
test_buffer_write32be();
|
|
||||||
test_buffer_write64be();
|
|
||||||
test_buffer_read16be();
|
|
||||||
test_buffer_read32be();
|
|
||||||
test_buffer_read64be();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/bytebuf.h"
|
||||||
|
|
||||||
|
void test_bytebuf_simple(void) {
|
||||||
|
struct sc_bytebuf buf;
|
||||||
|
uint8_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_bytebuf_init(&buf, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 5);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 4);
|
||||||
|
assert(!strncmp((char *) data, "hell", 4));
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 7);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 8);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, &data[4], 8);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||||
|
|
||||||
|
data[12] = '\0';
|
||||||
|
assert(!strcmp((char *) data, "hello world!"));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||||
|
|
||||||
|
sc_bytebuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bytebuf_boundaries(void) {
|
||||||
|
struct sc_bytebuf buf;
|
||||||
|
uint8_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_bytebuf_init(&buf, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 18);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 9);
|
||||||
|
assert(!strncmp((char *) data, "hello hel", 9));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||||
|
|
||||||
|
sc_bytebuf_skip(&buf, 3);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 12);
|
||||||
|
data[12] = '\0';
|
||||||
|
assert(!strcmp((char *) data, "hello world!"));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||||
|
|
||||||
|
sc_bytebuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bytebuf_overwrite(void) {
|
||||||
|
struct sc_bytebuf buf;
|
||||||
|
uint8_t data[10];
|
||||||
|
|
||||||
|
bool ok = sc_bytebuf_init(&buf, 10); // so actual capacity is 9
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "abcdef", sizeof("abcdef") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 9);
|
||||||
|
assert(!strncmp((char *) data, "lo abcdef", 9));
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "a very big buffer",
|
||||||
|
sizeof("a very big buffer") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 9);
|
||||||
|
assert(!strncmp((char *) data, "ig buffer", 9));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||||
|
|
||||||
|
sc_bytebuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bytebuf_two_steps_write(void) {
|
||||||
|
struct sc_bytebuf buf;
|
||||||
|
uint8_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_bytebuf_init(&buf, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||||
|
|
||||||
|
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 12); // write not committed yet
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 9);
|
||||||
|
assert(!strncmp((char *) data, "hello hel", 3));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 3);
|
||||||
|
|
||||||
|
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||||
|
|
||||||
|
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 9); // write not committed yet
|
||||||
|
|
||||||
|
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||||
|
|
||||||
|
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||||
|
|
||||||
|
sc_bytebuf_skip(&buf, 3);
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||||
|
|
||||||
|
sc_bytebuf_read(&buf, data, 12);
|
||||||
|
data[12] = '\0';
|
||||||
|
assert(!strcmp((char *) data, "hello world!"));
|
||||||
|
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||||
|
|
||||||
|
sc_bytebuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_bytebuf_simple();
|
||||||
|
test_bytebuf_boundaries();
|
||||||
|
test_bytebuf_overwrite();
|
||||||
|
test_bytebuf_two_steps_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ static void test_options(void) {
|
|||||||
char *argv[] = {
|
char *argv[] = {
|
||||||
"scrcpy",
|
"scrcpy",
|
||||||
"--always-on-top",
|
"--always-on-top",
|
||||||
"--bit-rate", "5M",
|
"--video-bit-rate", "5M",
|
||||||
"--crop", "100:200:300:400",
|
"--crop", "100:200:300:400",
|
||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
"--max-fps", "30",
|
"--max-fps", "30",
|
||||||
@@ -75,7 +75,7 @@ static void test_options(void) {
|
|||||||
|
|
||||||
const struct scrcpy_options *opts = &args.opts;
|
const struct scrcpy_options *opts = &args.opts;
|
||||||
assert(opts->always_on_top);
|
assert(opts->always_on_top);
|
||||||
assert(opts->bit_rate == 5000000);
|
assert(opts->video_bit_rate == 5000000);
|
||||||
assert(!strcmp(opts->crop, "100:200:300:400"));
|
assert(!strcmp(opts->crop, "100:200:300:400"));
|
||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(opts->max_fps == 30);
|
assert(opts->max_fps == 30);
|
||||||
|
|||||||
@@ -90,13 +90,14 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
.pressure = 1.0f,
|
.pressure = 1.0f,
|
||||||
|
.action_button = AMOTION_EVENT_BUTTON_PRIMARY,
|
||||||
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 28);
|
assert(size == 32);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
@@ -105,7 +106,8 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
|
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
0xff, 0xff, // pressure
|
0xff, 0xff, // pressure
|
||||||
0x00, 0x00, 0x00, 0x01 // AMOTION_EVENT_BUTTON_PRIMARY
|
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY (action button)
|
||||||
|
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY (buttons)
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@@ -132,14 +134,14 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
|
|
||||||
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 25);
|
assert(size == 21);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
0x7F, 0xFF, // 1 (float encoded as i16)
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
0x80, 0x00, // -1 (float encoded as i16)
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
0x00, 0x00, 0x00, 0x01, // 1
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
|||||||
+3
-3
@@ -4,10 +4,10 @@ buildscript {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
@@ -17,7 +17,7 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs << "-Xlint:deprecation"
|
options.compilerArgs << "-Xlint:deprecation"
|
||||||
|
|||||||
+3
-7
@@ -16,10 +16,6 @@ cpu = 'i686'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
ffmpeg_avcodec = 'avcodec-58'
|
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win32'
|
||||||
ffmpeg_avformat = 'avformat-58'
|
prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32'
|
||||||
ffmpeg_avutil = 'avutil-56'
|
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'
|
||||||
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
|
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32'
|
|
||||||
prebuilt_libusb_root = 'libusb-1.0.26'
|
|
||||||
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32'
|
|
||||||
|
|||||||
+3
-7
@@ -16,10 +16,6 @@ cpu = 'x86_64'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
ffmpeg_avcodec = 'avcodec-59'
|
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win64'
|
||||||
ffmpeg_avformat = 'avformat-59'
|
prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32'
|
||||||
ffmpeg_avutil = 'avutil-57'
|
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'
|
||||||
prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1'
|
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32'
|
|
||||||
prebuilt_libusb_root = 'libusb-1.0.26'
|
|
||||||
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64'
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
+3
-3
@@ -2,8 +2,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
BUILDDIR=build-auto
|
BUILDDIR=build-auto
|
||||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24
|
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25
|
||||||
PREBUILT_SERVER_SHA256=ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056
|
PREBUILT_SERVER_SHA256=ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb
|
||||||
|
|
||||||
echo "[scrcpy] Downloading prebuilt server..."
|
echo "[scrcpy] Downloading prebuilt server..."
|
||||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||||
@@ -12,7 +12,7 @@ echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check
|
|||||||
|
|
||||||
echo "[scrcpy] Building client..."
|
echo "[scrcpy] Building client..."
|
||||||
rm -rf "$BUILDDIR"
|
rm -rf "$BUILDDIR"
|
||||||
meson "$BUILDDIR" --buildtype=release --strip -Db_lto=true \
|
meson setup "$BUILDDIR" --buildtype=release --strip -Db_lto=true \
|
||||||
-Dprebuilt_server=scrcpy-server
|
-Dprebuilt_server=scrcpy-server
|
||||||
cd "$BUILDDIR"
|
cd "$BUILDDIR"
|
||||||
ninja
|
ninja
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.24',
|
version: '1.25',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
|||||||
+39
-45
@@ -11,7 +11,7 @@
|
|||||||
.PHONY: default clean \
|
.PHONY: default clean \
|
||||||
test \
|
test \
|
||||||
build-server \
|
build-server \
|
||||||
prepare-deps-win32 prepare-deps-win64 \
|
prepare-deps \
|
||||||
build-win32 build-win64 \
|
build-win32 build-win64 \
|
||||||
dist-win32 dist-win64 \
|
dist-win32 dist-win64 \
|
||||||
zip-win32 zip-win64 \
|
zip-win32 zip-win64 \
|
||||||
@@ -24,13 +24,13 @@ SERVER_BUILD_DIR := build-server
|
|||||||
WIN32_BUILD_DIR := build-win32
|
WIN32_BUILD_DIR := build-win32
|
||||||
WIN64_BUILD_DIR := build-win64
|
WIN64_BUILD_DIR := build-win64
|
||||||
|
|
||||||
DIST := dist
|
|
||||||
WIN32_TARGET_DIR := scrcpy-win32
|
|
||||||
WIN64_TARGET_DIR := scrcpy-win64
|
|
||||||
|
|
||||||
VERSION := $(shell git describe --tags --always)
|
VERSION := $(shell git describe --tags --always)
|
||||||
WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
|
|
||||||
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
|
DIST := dist
|
||||||
|
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
|
||||||
|
WIN64_TARGET_DIR := scrcpy-win64-$(VERSION)
|
||||||
|
WIN32_TARGET := $(WIN32_TARGET_DIR).zip
|
||||||
|
WIN64_TARGET := $(WIN64_TARGET_DIR).zip
|
||||||
|
|
||||||
RELEASE_DIR := release-$(VERSION)
|
RELEASE_DIR := release-$(VERSION)
|
||||||
|
|
||||||
@@ -53,39 +53,33 @@ clean:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
|
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
|
||||||
meson "$(TEST_BUILD_DIR)" -Db_sanitize=address )
|
meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address )
|
||||||
ninja -C "$(TEST_BUILD_DIR)"
|
ninja -C "$(TEST_BUILD_DIR)"
|
||||||
$(GRADLE) -p server check
|
$(GRADLE) -p server check
|
||||||
|
|
||||||
build-server:
|
build-server:
|
||||||
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
|
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
|
||||||
meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
|
meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
|
||||||
ninja -C "$(SERVER_BUILD_DIR)"
|
ninja -C "$(SERVER_BUILD_DIR)"
|
||||||
|
|
||||||
prepare-deps-win32:
|
prepare-deps:
|
||||||
@app/prebuilt-deps/prepare-adb.sh
|
@app/prebuilt-deps/prepare-adb.sh
|
||||||
@app/prebuilt-deps/prepare-sdl.sh
|
@app/prebuilt-deps/prepare-sdl.sh
|
||||||
@app/prebuilt-deps/prepare-ffmpeg-win32.sh
|
@app/prebuilt-deps/prepare-ffmpeg.sh
|
||||||
@app/prebuilt-deps/prepare-libusb.sh
|
@app/prebuilt-deps/prepare-libusb.sh
|
||||||
|
|
||||||
prepare-deps-win64:
|
build-win32: prepare-deps
|
||||||
@app/prebuilt-deps/prepare-adb.sh
|
|
||||||
@app/prebuilt-deps/prepare-sdl.sh
|
|
||||||
@app/prebuilt-deps/prepare-ffmpeg-win64.sh
|
|
||||||
@app/prebuilt-deps/prepare-libusb.sh
|
|
||||||
|
|
||||||
build-win32: prepare-deps-win32
|
|
||||||
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
|
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
|
||||||
meson "$(WIN32_BUILD_DIR)" \
|
meson setup "$(WIN32_BUILD_DIR)" \
|
||||||
--cross-file cross_win32.txt \
|
--cross-file cross_win32.txt \
|
||||||
--buildtype release --strip -Db_lto=true \
|
--buildtype release --strip -Db_lto=true \
|
||||||
-Dcompile_server=false \
|
-Dcompile_server=false \
|
||||||
-Dportable=true )
|
-Dportable=true )
|
||||||
ninja -C "$(WIN32_BUILD_DIR)"
|
ninja -C "$(WIN32_BUILD_DIR)"
|
||||||
|
|
||||||
build-win64: prepare-deps-win64
|
build-win64: prepare-deps
|
||||||
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
|
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
|
||||||
meson "$(WIN64_BUILD_DIR)" \
|
meson setup "$(WIN64_BUILD_DIR)" \
|
||||||
--cross-file cross_win64.txt \
|
--cross-file cross_win64.txt \
|
||||||
--buildtype release --strip -Db_lto=true \
|
--buildtype release --strip -Db_lto=true \
|
||||||
-Dcompile_server=false \
|
-Dcompile_server=false \
|
||||||
@@ -100,16 +94,16 @@ dist-win32: build-server build-win32
|
|||||||
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
|
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
|
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
|
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/SDL2-2.26.1/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
|
|
||||||
dist-win64: build-server build-win64
|
dist-win64: build-server build-win64
|
||||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
@@ -119,21 +113,21 @@ dist-win64: build-server build-win64
|
|||||||
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/SDL2-2.26.1/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
|
||||||
zip-win32: dist-win32
|
zip-win32: dist-win32
|
||||||
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
|
cd "$(DIST)"; \
|
||||||
zip -r "../$(WIN32_TARGET)" .
|
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
|
||||||
|
|
||||||
zip-win64: dist-win64
|
zip-win64: dist-win64
|
||||||
cd "$(DIST)/$(WIN64_TARGET_DIR)"; \
|
cd "$(DIST)"; \
|
||||||
zip -r "../$(WIN64_TARGET)" .
|
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
|
||||||
|
|||||||
+6
-6
@@ -1,13 +1,14 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
namespace 'com.genymobile.scrcpy'
|
||||||
|
compileSdkVersion 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 33
|
||||||
versionCode 12400
|
versionCode 12500
|
||||||
versionName "1.24"
|
versionName "1.25"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -19,8 +20,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'junit:junit:4.13.1'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
||||||
|
|||||||
@@ -12,25 +12,29 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.24
|
SCRCPY_VERSION_NAME=1.25
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-31}
|
PLATFORM=${ANDROID_PLATFORM:-33}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
|
||||||
|
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
|
||||||
|
|
||||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
CLASSES_DIR="$BUILD_DIR/classes"
|
CLASSES_DIR="$BUILD_DIR/classes"
|
||||||
|
GEN_DIR="$BUILD_DIR/gen"
|
||||||
SERVER_DIR=$(dirname "$0")
|
SERVER_DIR=$(dirname "$0")
|
||||||
SERVER_BINARY=scrcpy-server
|
SERVER_BINARY=scrcpy-server
|
||||||
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
|
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
|
||||||
|
LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar"
|
||||||
|
|
||||||
echo "Platform: android-$PLATFORM"
|
echo "Platform: android-$PLATFORM"
|
||||||
echo "Build-tools: $BUILD_TOOLS"
|
echo "Build-tools: $BUILD_TOOLS"
|
||||||
echo "Build dir: $BUILD_DIR"
|
echo "Build dir: $BUILD_DIR"
|
||||||
|
|
||||||
rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
|
rm -rf "$CLASSES_DIR" "$GEN_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
|
||||||
mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
|
mkdir -p "$CLASSES_DIR"
|
||||||
|
mkdir -p "$GEN_DIR/com/genymobile/scrcpy"
|
||||||
|
|
||||||
<< EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
|
<< EOF cat > "$GEN_DIR/com/genymobile/scrcpy/BuildConfig.java"
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
public final class BuildConfig {
|
public final class BuildConfig {
|
||||||
@@ -41,14 +45,15 @@ EOF
|
|||||||
|
|
||||||
echo "Generating java from aidl..."
|
echo "Generating java from aidl..."
|
||||||
cd "$SERVER_DIR/src/main/aidl"
|
cd "$SERVER_DIR/src/main/aidl"
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl
|
||||||
android/view/IRotationWatcher.aidl
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
|
||||||
android/content/IOnPrimaryClipChangedListener.aidl
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
cd ../java
|
cd ../java
|
||||||
javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
|
javac -bootclasspath "$ANDROID_JAR" \
|
||||||
|
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||||
|
-d "$CLASSES_DIR" \
|
||||||
-source 1.8 -target 1.8 \
|
-source 1.8 -target 1.8 \
|
||||||
com/genymobile/scrcpy/*.java \
|
com/genymobile/scrcpy/*.java \
|
||||||
com/genymobile/scrcpy/wrappers/*.java
|
com/genymobile/scrcpy/wrappers/*.java
|
||||||
@@ -59,8 +64,7 @@ cd "$CLASSES_DIR"
|
|||||||
if [[ $PLATFORM -lt 31 ]]
|
if [[ $PLATFORM -lt 31 ]]
|
||||||
then
|
then
|
||||||
# use dx
|
# use dx
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
||||||
--output "$BUILD_DIR/classes.dex" \
|
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
com/genymobile/scrcpy/*.class \
|
com/genymobile/scrcpy/*.class \
|
||||||
@@ -69,10 +73,10 @@ then
|
|||||||
echo "Archiving..."
|
echo "Archiving..."
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
jar cvf "$SERVER_BINARY" classes.dex
|
jar cvf "$SERVER_BINARY" classes.dex
|
||||||
rm -rf classes.dex classes
|
rm -rf classes.dex
|
||||||
else
|
else
|
||||||
# use d8
|
# use d8
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
|
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
|
||||||
--output "$BUILD_DIR/classes.zip" \
|
--output "$BUILD_DIR/classes.zip" \
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
@@ -81,7 +85,8 @@ else
|
|||||||
|
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
mv classes.zip "$SERVER_BINARY"
|
mv classes.zip "$SERVER_BINARY"
|
||||||
rm -rf classes
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
rm -rf "$GEN_DIR" "$CLASSES_DIR"
|
||||||
|
|
||||||
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
|
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@ if prebuilt_server == ''
|
|||||||
install_dir: 'share/scrcpy')
|
install_dir: 'share/scrcpy')
|
||||||
else
|
else
|
||||||
if not prebuilt_server.startswith('/')
|
if not prebuilt_server.startswith('/')
|
||||||
# relative path needs some trick
|
# prebuilt server path is relative to the root scrcpy directory
|
||||||
prebuilt_server = meson.source_root() + '/' + prebuilt_server
|
prebuilt_server = '../' + prebuilt_server
|
||||||
endif
|
endif
|
||||||
custom_target('scrcpy-server-prebuilt',
|
custom_target('scrcpy-server-prebuilt',
|
||||||
input: prebuilt_server,
|
input: prebuilt_server,
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
<!-- not a real Android application, it is run by app_process manually -->
|
<!-- not a real Android application, it is run by app_process manually -->
|
||||||
<manifest package="com.genymobile.scrcpy"/>
|
<manifest />
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
|
public enum AudioCodec implements Codec {
|
||||||
|
OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS),
|
||||||
|
AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC);
|
||||||
|
|
||||||
|
private final int id; // 4-byte ASCII representation of the name
|
||||||
|
private final String name;
|
||||||
|
private final String mimeType;
|
||||||
|
|
||||||
|
AudioCodec(int id, String name, String mimeType) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType() {
|
||||||
|
return Type.AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioCodec findByName(String name) {
|
||||||
|
for (AudioCodec codec : values()) {
|
||||||
|
if (codec.name.equals(name)) {
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioRecord;
|
||||||
|
import android.media.AudioTimestamp;
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
public final class AudioEncoder {
|
||||||
|
|
||||||
|
private static class InputTask {
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
InputTask(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OutputTask {
|
||||||
|
private final int index;
|
||||||
|
private final MediaCodec.BufferInfo bufferInfo;
|
||||||
|
|
||||||
|
OutputTask(int index, MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
this.index = index;
|
||||||
|
this.bufferInfo = bufferInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SAMPLE_RATE = 48000;
|
||||||
|
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
|
private static final int CHANNELS = 2;
|
||||||
|
private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
private static final int BYTES_PER_SAMPLE = 2;
|
||||||
|
|
||||||
|
private static final int BUFFER_MS = 5; // milliseconds
|
||||||
|
private static final int BUFFER_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * BUFFER_MS / 1000;
|
||||||
|
|
||||||
|
private final Streamer streamer;
|
||||||
|
private final int bitRate;
|
||||||
|
private final List<CodecOption> codecOptions;
|
||||||
|
private final String encoderName;
|
||||||
|
|
||||||
|
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4).
|
||||||
|
// So many pending tasks would lead to an unacceptable delay anyway.
|
||||||
|
private final BlockingQueue<InputTask> inputTasks = new ArrayBlockingQueue<>(64);
|
||||||
|
private final BlockingQueue<OutputTask> outputTasks = new ArrayBlockingQueue<>(64);
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private HandlerThread mediaCodecThread;
|
||||||
|
|
||||||
|
private Thread inputThread;
|
||||||
|
private Thread outputThread;
|
||||||
|
|
||||||
|
private boolean ended;
|
||||||
|
|
||||||
|
public AudioEncoder(Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
|
||||||
|
this.streamer = streamer;
|
||||||
|
this.bitRate = bitRate;
|
||||||
|
this.codecOptions = codecOptions;
|
||||||
|
this.encoderName = encoderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioFormat createAudioFormat() {
|
||||||
|
AudioFormat.Builder builder = new AudioFormat.Builder();
|
||||||
|
builder.setEncoding(FORMAT);
|
||||||
|
builder.setSampleRate(SAMPLE_RATE);
|
||||||
|
builder.setChannelMask(CHANNEL_CONFIG);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
@SuppressLint({"WrongConstant", "MissingPermission"})
|
||||||
|
private static AudioRecord createAudioRecord() {
|
||||||
|
AudioRecord.Builder builder = new AudioRecord.Builder();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
|
||||||
|
builder.setContext(FakeContext.get());
|
||||||
|
}
|
||||||
|
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
|
||||||
|
builder.setAudioFormat(createAudioFormat());
|
||||||
|
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
|
||||||
|
builder.setBufferSizeInBytes(minBufferSize);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
|
||||||
|
MediaFormat format = new MediaFormat();
|
||||||
|
format.setString(MediaFormat.KEY_MIME, mimeType);
|
||||||
|
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
||||||
|
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
|
||||||
|
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
|
||||||
|
|
||||||
|
if (codecOptions != null) {
|
||||||
|
for (CodecOption option : codecOptions) {
|
||||||
|
String key = option.getKey();
|
||||||
|
Object value = option.getValue();
|
||||||
|
CodecUtils.setCodecOption(format, key, value);
|
||||||
|
Ln.d("Audio codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private void inputThread(MediaCodec mediaCodec, AudioRecord recorder) throws IOException, InterruptedException {
|
||||||
|
final AudioTimestamp timestamp = new AudioTimestamp();
|
||||||
|
long previousPts = 0;
|
||||||
|
long nextPts = 0;
|
||||||
|
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
InputTask task = inputTasks.take();
|
||||||
|
ByteBuffer buffer = mediaCodec.getInputBuffer(task.index);
|
||||||
|
int r = recorder.read(buffer, BUFFER_SIZE);
|
||||||
|
if (r < 0) {
|
||||||
|
throw new IOException("Could not read audio: " + r);
|
||||||
|
}
|
||||||
|
|
||||||
|
long pts;
|
||||||
|
|
||||||
|
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
|
||||||
|
if (ret == AudioRecord.SUCCESS) {
|
||||||
|
pts = timestamp.nanoTime / 1000;
|
||||||
|
} else {
|
||||||
|
if (nextPts == 0) {
|
||||||
|
Ln.w("Could not get any audio timestamp");
|
||||||
|
}
|
||||||
|
// compute from previous timestamp and packet size
|
||||||
|
pts = nextPts;
|
||||||
|
}
|
||||||
|
|
||||||
|
long durationMs = r * 1000 / CHANNELS / SAMPLE_RATE;
|
||||||
|
nextPts = pts + durationMs;
|
||||||
|
|
||||||
|
if (previousPts != 0 && pts < previousPts) {
|
||||||
|
// Audio PTS may come from two sources:
|
||||||
|
// - recorder.getTimestamp() if the call works;
|
||||||
|
// - 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.
|
||||||
|
pts = previousPts + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaCodec.queueInputBuffer(task.index, 0, r, pts, 0);
|
||||||
|
|
||||||
|
previousPts = pts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
||||||
|
streamer.writeHeader();
|
||||||
|
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
OutputTask task = outputTasks.take();
|
||||||
|
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
||||||
|
try {
|
||||||
|
streamer.writePacket(buffer, task.bufferInfo);
|
||||||
|
} finally {
|
||||||
|
mediaCodec.releaseOutputBuffer(task.index, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
encode();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Ln.e("Audio encoding error", e);
|
||||||
|
} finally {
|
||||||
|
Ln.d("Audio encoder stopped");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (thread != null) {
|
||||||
|
// Just wake up the blocking wait from the thread, so that it properly releases all its resources and terminates
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void join() throws InterruptedException {
|
||||||
|
if (thread != null) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void end() {
|
||||||
|
ended = true;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void waitEnded() {
|
||||||
|
try {
|
||||||
|
while (!ended) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startWorkaroundAndroid11() {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
// Android 11 requires Apps to be at foreground to record audio.
|
||||||
|
// Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
|
||||||
|
// But Scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android
|
||||||
|
// shell ("com.android.shell").
|
||||||
|
// If there is an Activity from Android shell running at foreground, then the permission system will believe Scrcpy is also in the
|
||||||
|
// foreground.
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity"));
|
||||||
|
ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent);
|
||||||
|
// Wait for activity to start
|
||||||
|
SystemClock.sleep(150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void stopWorkaroundAndroid11() {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void encode() throws IOException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||||
|
streamer.writeDisableStream(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaCodec mediaCodec = null;
|
||||||
|
AudioRecord recorder = null;
|
||||||
|
|
||||||
|
boolean mediaCodecStarted = false;
|
||||||
|
boolean recorderStarted = false;
|
||||||
|
boolean configurationError = false;
|
||||||
|
try {
|
||||||
|
Codec codec = streamer.getCodec();
|
||||||
|
mediaCodec = createMediaCodec(codec, encoderName);
|
||||||
|
|
||||||
|
mediaCodecThread = new HandlerThread("AudioEncoder");
|
||||||
|
mediaCodecThread.start();
|
||||||
|
|
||||||
|
MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions);
|
||||||
|
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
||||||
|
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||||
|
|
||||||
|
startWorkaroundAndroid11();
|
||||||
|
try {
|
||||||
|
recorder = createAudioRecord();
|
||||||
|
recorder.startRecording();
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
Ln.e("Failed to start audio capture");
|
||||||
|
Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy.");
|
||||||
|
throw new ConfigurationException("Unsupported audio capture");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stopWorkaroundAndroid11();
|
||||||
|
}
|
||||||
|
recorderStarted = true;
|
||||||
|
|
||||||
|
final MediaCodec mediaCodecRef = mediaCodec;
|
||||||
|
final AudioRecord recorderRef = recorder;
|
||||||
|
inputThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
inputThread(mediaCodecRef, recorderRef);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
Ln.e("Audio capture error", e);
|
||||||
|
} finally {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
outputThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
outputThread(mediaCodecRef);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// this is expected on close
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Broken pipe is expected on close, because the socket is closed by the client
|
||||||
|
if (!IO.isBrokenPipe(e)) {
|
||||||
|
Ln.e("Audio encoding error", e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaCodec.start();
|
||||||
|
mediaCodecStarted = true;
|
||||||
|
inputThread.start();
|
||||||
|
outputThread.start();
|
||||||
|
|
||||||
|
waitEnded();
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||||
|
// Notify the error to scrcpy to make it exit
|
||||||
|
configurationError = true;
|
||||||
|
} finally {
|
||||||
|
if (!recorderStarted) {
|
||||||
|
// Notify the client that the audio could not be captured
|
||||||
|
streamer.writeDisableStream(configurationError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup everything (either at the end or on error at any step of the initialization)
|
||||||
|
if (mediaCodecThread != null) {
|
||||||
|
Looper looper = mediaCodecThread.getLooper();
|
||||||
|
if (looper != null) {
|
||||||
|
looper.quitSafely();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputThread != null) {
|
||||||
|
inputThread.interrupt();
|
||||||
|
}
|
||||||
|
if (outputThread != null) {
|
||||||
|
outputThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mediaCodecThread != null) {
|
||||||
|
mediaCodecThread.join();
|
||||||
|
}
|
||||||
|
if (inputThread != null) {
|
||||||
|
inputThread.join();
|
||||||
|
}
|
||||||
|
if (outputThread != null) {
|
||||||
|
outputThread.join();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Should never happen
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaCodec != null) {
|
||||||
|
if (mediaCodecStarted) {
|
||||||
|
mediaCodec.stop();
|
||||||
|
}
|
||||||
|
mediaCodec.release();
|
||||||
|
}
|
||||||
|
if (recorder != null) {
|
||||||
|
if (recorderStarted) {
|
||||||
|
recorder.stop();
|
||||||
|
}
|
||||||
|
recorder.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||||
|
if (encoderName != null) {
|
||||||
|
Ln.d("Creating audio encoder by name: '" + encoderName + "'");
|
||||||
|
try {
|
||||||
|
return MediaCodec.createByCodecName(encoderName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage());
|
||||||
|
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
||||||
|
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
|
||||||
|
return mediaCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EncoderCallback extends MediaCodec.Callback {
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
@Override
|
||||||
|
public void onInputBufferAvailable(MediaCodec codec, int index) {
|
||||||
|
try {
|
||||||
|
inputTasks.put(new InputTask(index));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
try {
|
||||||
|
outputTasks.put(new OutputTask(index, bufferInfo));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
|
||||||
|
Ln.e("MediaCodec error", e);
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class Binary {
|
||||||
|
private Binary() {
|
||||||
|
// not instantiable
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toUnsigned(short value) {
|
||||||
|
return value & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toUnsigned(byte value) {
|
||||||
|
return value & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert unsigned 16-bit fixed-point to a float between 0 and 1
|
||||||
|
*
|
||||||
|
* @param value encoded value
|
||||||
|
* @return Float value between 0 and 1
|
||||||
|
*/
|
||||||
|
public static float u16FixedPointToFloat(short value) {
|
||||||
|
int unsignedShort = Binary.toUnsigned(value);
|
||||||
|
// 0x1p16f is 2^16 as float
|
||||||
|
return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert signed 16-bit fixed-point to a float between -1 and 1
|
||||||
|
*
|
||||||
|
* @param value encoded value
|
||||||
|
* @return Float value between -1 and 1
|
||||||
|
*/
|
||||||
|
public static float i16FixedPointToFloat(short value) {
|
||||||
|
// 0x1p15f is 2^15 as float
|
||||||
|
return value == 0x7fff ? 1f : (value / 0x1p15f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
@@ -141,7 +139,7 @@ public final class CleanUp {
|
|||||||
builder.start();
|
builder.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void unlinkSelf() {
|
public static void unlinkSelf() {
|
||||||
try {
|
try {
|
||||||
new File(SERVER_PATH).delete();
|
new File(SERVER_PATH).delete();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -164,12 +162,10 @@ public final class CleanUp {
|
|||||||
Config config = Config.fromBase64(args[0]);
|
Config config = Config.fromBase64(args[0]);
|
||||||
|
|
||||||
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
|
||||||
Settings settings = new Settings(serviceManager);
|
|
||||||
if (config.disableShowTouches) {
|
if (config.disableShowTouches) {
|
||||||
Ln.i("Disabling \"show touches\"");
|
Ln.i("Disabling \"show touches\"");
|
||||||
try {
|
try {
|
||||||
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
Ln.e("Could not restore \"show_touches\"", e);
|
Ln.e("Could not restore \"show_touches\"", e);
|
||||||
}
|
}
|
||||||
@@ -177,7 +173,7 @@ public final class CleanUp {
|
|||||||
if (config.restoreStayOn != -1) {
|
if (config.restoreStayOn != -1) {
|
||||||
Ln.i("Restoring \"stay awake\"");
|
Ln.i("Restoring \"stay awake\"");
|
||||||
try {
|
try {
|
||||||
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
|
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public interface Codec {
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
VIDEO,
|
||||||
|
AUDIO,
|
||||||
|
}
|
||||||
|
|
||||||
|
Type getType();
|
||||||
|
|
||||||
|
int getId();
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
String getMimeType();
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CodecOption {
|
public class CodecOption {
|
||||||
private String key;
|
private final String key;
|
||||||
private Object value;
|
private final Object value;
|
||||||
|
|
||||||
public CodecOption(String key, Object value) {
|
public CodecOption(String key, Object value) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user