Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
57687bdfcd Write header file with correct extradata
When recording, the header must be written with extradata set to the
content of the very first packet.

Suggested-by: Steve Lhomme <robux4@ycbcr.xyz>
2019-02-09 12:54:12 +01:00
65 changed files with 1273 additions and 2277 deletions

View File

@@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.8.jar`][direct-scrcpy-server] - [`scrcpy-server-v1.6.jar`][direct-scrcpy-server]
_(SHA-256: 839055ef905903bf98ead1b9b8a127fe402b39ad657a81f9a914b2dbcb2ce5c0)_ _(SHA-256: 08df924bf6d10943df9eaacfff548a99871ebfca4641f8c7ddddb73f27cb905b)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-server-v1.8.jar [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-server-v1.6.jar
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:

View File

@@ -32,7 +32,7 @@ The server is a Java application (with a [`public static void main(String...
args)`][main] method), compiled against the Android framework, and executed as args)`][main] method), compiled against the Android framework, and executed as
`shell` on the Android device. `shell` on the Android device.
[main]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100 [main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
To run such a Java application, the classes must be [_dexed_][dex] (typically, To run such a Java application, the classes must be [_dexed_][dex] (typically,
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
components is provided by [_wrappers_ classes][wrappers] and [aidl]. components is provided by [_wrappers_ classes][wrappers] and [aidl].
[hidden]: https://stackoverflow.com/a/31908373/1987178 [hidden]: https://stackoverflow.com/a/31908373/1987178
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers [wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view [aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
### Threading ### Threading
@@ -89,9 +89,9 @@ The video is encoded using the [`MediaCodec`] API. The codec takes its input
from a [surface] associated to the display, and writes the resulting H.264 from a [surface] associated to the display, and writes the resulting H.264
stream to the provided output stream (the socket connected to the client). stream to the provided output stream (the socket connected to the client).
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java [`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html [`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70 [surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
On device [rotation], the codec, surface and display are reinitialized, and a On device [rotation], the codec, surface and display are reinitialized, and a
new video stream is produced. new video stream is produced.
@@ -105,9 +105,8 @@ because it avoids to send unnecessary frames, but there are drawbacks:
Both problems are [solved][repeat] by the flag Both problems are [solved][repeat] by the flag
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag]. [`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90 [rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
[repeat]: [repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER [repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
@@ -125,11 +124,11 @@ All of them may need to inject input events to the system. To do so, they use
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
[`InputManager` wrapper][inject-wrapper]). [`InputManager` wrapper][inject-wrapper]).
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66 [`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html [`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html [`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857 [`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27 [inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
@@ -154,7 +153,7 @@ Note that the client-server roles are expressed at the application level:
- the server _serves_ video stream and handle requests from the client, - the server _serves_ video stream and handle requests from the client,
- the client _controls_ the device through the server. - the client _controls_ the device through the server.
However, the roles are reversed at the network level: However, the roles are inverted at the network level:
- the client opens a server socket and listen on a port before starting the - the client opens a server socket and listen on a port before starting the
server, server,
@@ -163,9 +162,6 @@ However, the roles are reversed at the network level:
This role inversion guarantees that the connection will not fail due to race This role inversion guarantees that the connection will not fail due to race
conditions, and avoids polling. conditions, and avoids polling.
_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb
reverse`. See commit [1038bad] and [issue #5].)_
Once the server is connected, it sends the device information (name and initial Once the server is connected, it sends the device information (name and initial
screen dimensions). Thus, the client may init the window and renderer, before screen dimensions). Thus, the client may init the window and renderer, before
the first frame is available. the first frame is available.
@@ -173,8 +169,6 @@ the first frame is available.
To minimize startup time, SDL initialization is performed while listening for To minimize startup time, SDL initialization is performed while listening for
the connection from the server (see commit [90a46b4]). the connection from the server (see commit [90a46b4]).
[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172
[issue #5]: https://github.com/Genymobile/scrcpy/issues/5
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e [90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
@@ -183,25 +177,17 @@ the connection from the server (see commit [90a46b4]).
The client uses 3 threads: The client uses 3 threads:
- the **main** thread, executing the SDL event loop, - the **main** thread, executing the SDL event loop,
- the **stream** thread, receiving the video and used for decoding and - the **decoder** thread, decoding video frames,
recording,
- the **controller** thread, sending _control events_ to the server. - the **controller** thread, sending _control events_ to the server.
In addition, another thread can be started if necessary to handle APK
installation or file push requests (via drag&drop on the main window).
### Decoder
The [decoder] runs in a separate thread. It uses _libav_ to decode the H.264
stream from the socket, and notifies the main thread when a new frame is
available.
### Stream There are two [frames] simultaneously in memory:
The video [stream] is received from the socket (connected to the server on the
device) in a separate thread.
If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_
to decode the H.264 stream from the socket, and notifies the main thread when a
new frame is available.
There are two [frames][video_buffer] simultaneously in memory:
- the **decoding** frame, written by the decoder from the decoder thread, - the **decoding** frame, written by the decoder from the decoder thread,
- the **rendering** frame, rendered in a texture from the main thread. - the **rendering** frame, rendered in a texture from the main thread.
@@ -209,23 +195,9 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediatly starts rendering frame (with proper synchronization). Thus, it immediatly starts
to decode a new frame while the main thread renders the last one. to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw [decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
H.264 packet to the output video file. [frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
[stream]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/stream.h
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/decoder.h
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/video_buffer.h
[recorder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/recorder.h
```
+----------+ +----------+
---> | decoder | ---> | screen |
+---------+ / +----------+ +----------+
socket ---> | stream | ----
+---------+ \ +----------+
---> | recorder |
+----------+
```
### Controller ### Controller
@@ -239,10 +211,10 @@ events_ to a blocking queue hold by the controller. On its own thread, the
controller takes events from the queue, that it serializes and sends to the controller takes events from the queue, that it serializes and sends to the
client. client.
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/controller.h [controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/control_event.h [controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/input_manager.h [inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/convert.h [convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
### UI and event loop ### UI and event loop
@@ -253,10 +225,9 @@ thread.
Events are handled in the [event loop], which either updates the [screen] or Events are handled in the [event loop], which either updates the [screen] or
delegates to the [input manager][inputmanager]. delegates to the [input manager][inputmanager].
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c [scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
[event loop]: [event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c#L187 [screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/screen.h
## Hack ## Hack

View File

@@ -34,7 +34,7 @@ WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
release: clean zip-win32 zip-win64 sums release: clean zip-win32 zip-win64 sums
@echo "Windows archives generated in $(DIST)/" @echo "Release created in $(DIST)/."
clean: clean:
$(GRADLE) clean $(GRADLE) clean

View File

@@ -1,4 +1,4 @@
# scrcpy (v1.8) # scrcpy (v1.6)
This application provides display and control of Android devices connected on This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -29,10 +29,12 @@ control it using keyboard and mouse.
On Linux, you typically need to [build the app manually][BUILD]. Don't worry, On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard. it's not that hard.
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link]. For Arch Linux, two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
@@ -45,13 +47,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available: (including `adb`) are available:
- [`scrcpy-win32-v1.8.zip`][direct-win32] - [`scrcpy-win32-v1.6.zip`][direct-win32]
_(SHA-256: c0c29ed1c66deaa73bdadacd09e598aafb3a117929cf7a314cce1cc45e34de53)_ _(SHA-256: 4ca0c5924ab2ebf19b70f6598b2e546f65ba469a72ded2d1b213df3380fb46b1)_
- [`scrcpy-win64-v1.8.zip`][direct-win64] - [`scrcpy-win64-v1.6.zip`][direct-win64]
_(SHA-256: 9cc980d07bd8f036ae4e91d0bc6fc3281d7fa8f9752d4913b643c0fb72a19fb7)_ _(SHA-256: f66b7eace8dd6537a9a27176fd824704a284d8e82077ccc903344396043f90c9)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win32-v1.8.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-win32-v1.6.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win64-v1.8.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-win64-v1.6.zip
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -160,16 +162,7 @@ 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.mp4
```
To disable mirroring while recording:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
# Ctrl+C does not terminate properly on Windows, so disconnect the device
``` ```
"Skipped frames" are recorded, even if they are not displayed in real time (for "Skipped frames" are recorded, even if they are not displayed in real time (for
@@ -246,17 +239,6 @@ _scrcpy_ window.
There is no visual feedback, a log is printed to the console. There is no visual feedback, a log is printed to the console.
### Read-only
To disable controls (everything which can interact with the device: input keys,
mouse events, drag&drop files):
```bash
scrcpy --no-control
scrcpy -n
```
### Forward audio ### Forward audio
Audio is not forwarded by _scrcpy_. Audio is not forwarded by _scrcpy_.
@@ -285,8 +267,6 @@ you are interested, see [issue 14].
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) | | click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `POWER` | `Ctrl`+`p` | | click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ | | turn screen on | _Right-click²_ |
| expand notification panel | `Ctrl`+`n` |
| collapse notification panel | `Ctrl`+`Shift`+`n` |
| paste computer clipboard to device | `Ctrl`+`v` | | paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` |

View File

@@ -8,6 +8,7 @@ src = [
'src/device.c', 'src/device.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frames.c',
'src/input_manager.c', 'src/input_manager.c',
'src/lock_util.c', 'src/lock_util.c',
'src/net.c', 'src/net.c',
@@ -17,8 +18,6 @@ src = [
'src/server.c', 'src/server.c',
'src/str_util.c', 'src/str_util.c',
'src/tiny_xpm.c', 'src/tiny_xpm.c',
'src/stream.c',
'src/video_buffer.c',
] ]
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')

View File

@@ -1,33 +1,28 @@
#ifndef BUFFER_UTIL_H #ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H #define BUFFER_UTIL_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
static inline void static inline void buffer_write16be(Uint8 *buf, Uint16 value) {
buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
buf[1] = value; buf[1] = value;
} }
static inline void static inline void buffer_write32be(Uint8 *buf, Uint32 value) {
buffer_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24; buf[0] = value >> 24;
buf[1] = value >> 16; buf[1] = value >> 16;
buf[2] = value >> 8; buf[2] = value >> 8;
buf[3] = value; buf[3] = value;
} }
static inline uint32_t static inline Uint32 buffer_read32be(Uint8 *buf) {
buffer_read32be(uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline static inline Uint64 buffer_read64be(Uint8 *buf) {
uint64_t buffer_read64be(uint8_t *buf) { Uint32 msb = buffer_read32be(buf);
uint32_t msb = buffer_read32be(buf); Uint32 lsb = buffer_read32be(&buf[4]);
uint32_t lsb = buffer_read32be(&buf[4]); return ((Uint64) msb << 32) | lsb;
return ((uint64_t) msb << 32) | lsb;
} }
#endif #endif

View File

@@ -10,8 +10,7 @@
static const char *adb_command; static const char *adb_command;
static inline const char * static inline const char *get_adb_command(void) {
get_adb_command(void) {
if (!adb_command) { if (!adb_command) {
adb_command = getenv("ADB"); adb_command = getenv("ADB");
if (!adb_command) if (!adb_command)
@@ -20,8 +19,7 @@ get_adb_command(void) {
return adb_command; return adb_command;
} }
static void static void show_adb_err_msg(enum process_result err) {
show_adb_err_msg(enum process_result err) {
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb"); LOGE("Failed to execute adb");
@@ -36,8 +34,7 @@ show_adb_err_msg(enum process_result err) {
} }
} }
process_t process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4]; const char *cmd[len + 4];
int i; int i;
process_t process; process_t process;
@@ -60,9 +57,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], int len) {
return process; return process;
} }
process_t process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@@ -71,17 +66,14 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_forward_remove(const char *serial, uint16_t local_port) {
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local}; const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@@ -90,16 +82,14 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_reverse_remove(const char *serial, const char *device_socket_name) {
adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote}; const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_push(const char *serial, const char *local, const char *remote) {
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@@ -125,8 +115,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
return proc; return proc;
} }
process_t process_t adb_install(const char *serial, const char *local) {
adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@@ -146,11 +135,10 @@ adb_install(const char *serial, const char *local) {
return proc; return proc;
} }
bool SDL_bool process_check_success(process_t proc, const char *name) {
process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return SDL_FALSE;
} }
exit_code_t exit_code; exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) { if (!cmd_simple_wait(proc, &exit_code)) {
@@ -159,7 +147,7 @@ process_check_success(process_t proc, const char *name) {
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }

View File

@@ -1,36 +1,35 @@
#ifndef COMMAND_H #ifndef COMMAND_H
#define COMMAND_H #define COMMAND_H
#include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_platform.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
#ifdef _WIN32
# define PRIexitcode "lu"
# ifdef _WIN64 # ifdef _WIN64
# define PRIsizet PRIu64 # define PRIsizet PRIu64
# else # else
# define PRIsizet PRIu32 # define PRIsizet PRIu32
# endif # endif
#else
# define PRIsizet "zu"
# define PRIexitcode "d"
#endif
#ifdef __WINDOWS__
# include <winsock2.h> // not needed here, but must never be included AFTER windows.h
# include <windows.h>
# define PROCESS_NONE NULL # define PROCESS_NONE NULL
typedef HANDLE process_t; typedef HANDLE process_t;
typedef DWORD exit_code_t; typedef DWORD exit_code_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1 # define PROCESS_NONE -1
typedef pid_t process_t; typedef pid_t process_t;
typedef int exit_code_t; typedef int exit_code_t;
#endif #endif
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
enum process_result { enum process_result {
@@ -39,41 +38,20 @@ enum process_result {
PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
cmd_execute(const char *path, const char *const argv[], process_t *process); SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
bool process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
cmd_terminate(process_t pid); process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
bool process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
cmd_simple_wait(process_t pid, exit_code_t *exit_code); process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t adb_push(const char *serial, const char *local, const char *remote);
process_t process_t adb_install(const char *serial, const char *local);
adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
bool SDL_bool process_check_success(process_t process, const char *name);
process_check_success(process_t process, const char *name);
#endif #endif

View File

@@ -1,26 +1,25 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include <stdint.h> #include <SDL2/SDL_stdinc.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
struct size { struct size {
uint16_t width; Uint16 width;
uint16_t height; Uint16 height;
}; };
struct point { struct point {
int32_t x; Sint32 x;
int32_t y; Sint32 y;
}; };
struct position { struct position {
// The video screen size may be different from the real device screen size, // The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it // so store to which size the absolute position apply, to scale it accordingly.
// accordingly.
struct size screen_size; struct size screen_size;
struct point point; struct point point;
}; };

View File

@@ -1,46 +0,0 @@
#ifndef COMPAT_H
#define COMPAT_H
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif
// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
// av_register_all(), av_iformat_next(), av_oformat_next().
// Add av_demuxer_iterate(), and av_muxer_iterate().
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
# define SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
#else
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#endif

View File

@@ -1,21 +1,20 @@
#include "control_event.h" #include "control_event.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h> #include <string.h>
#include "buffer_util.h" #include "buffer_util.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
static void static void write_position(Uint8 *buf, const struct position *position) {
write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y); buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height); buffer_write16be(&buf[10], position->screen_size.height);
} }
int int control_event_serialize(const struct control_event *event, unsigned char *buf) {
control_event_serialize(const struct control_event *event, unsigned char *buf) {
buf[0] = event->type; buf[0] = event->type;
switch (event->type) { switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE: case CONTROL_EVENT_TYPE_KEYCODE:
@@ -30,7 +29,7 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
// injecting a text takes time, so limit the text length // injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH; len = TEXT_MAX_LENGTH;
} }
buffer_write16be(&buf[1], (uint16_t) len); buffer_write16be(&buf[1], (Uint16) len);
memcpy(&buf[3], event->text_event.text, len); memcpy(&buf[3], event->text_event.text, len);
return 3 + len; return 3 + len;
} }
@@ -41,8 +40,8 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
return 18; return 18;
case CONTROL_EVENT_TYPE_SCROLL: case CONTROL_EVENT_TYPE_SCROLL:
write_position(&buf[1], &event->scroll_event.position); write_position(&buf[1], &event->scroll_event.position);
buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll); buffer_write32be(&buf[13], (Uint32) event->scroll_event.hscroll);
buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll); buffer_write32be(&buf[17], (Uint32) event->scroll_event.vscroll);
return 21; return 21;
case CONTROL_EVENT_TYPE_COMMAND: case CONTROL_EVENT_TYPE_COMMAND:
buf[1] = event->command_event.action; buf[1] = event->command_event.action;
@@ -53,33 +52,28 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
} }
} }
void void control_event_destroy(struct control_event *event) {
control_event_destroy(struct control_event *event) {
if (event->type == CONTROL_EVENT_TYPE_TEXT) { if (event->type == CONTROL_EVENT_TYPE_TEXT) {
SDL_free(event->text_event.text); SDL_free(event->text_event.text);
} }
} }
bool SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
control_event_queue_is_empty(const struct control_event_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
bool SDL_bool control_event_queue_is_full(const struct control_event_queue *queue) {
control_event_queue_is_full(const struct control_event_queue *queue) {
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail; return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
} }
bool SDL_bool control_event_queue_init(struct control_event_queue *queue) {
control_event_queue_init(struct control_event_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
// the current implementation may not fail // the current implementation may not fail
return true; return SDL_TRUE;
} }
void void control_event_queue_destroy(struct control_event_queue *queue) {
control_event_queue_destroy(struct control_event_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
control_event_destroy(&queue->data[i]); control_event_destroy(&queue->data[i]);
@@ -87,24 +81,20 @@ control_event_queue_destroy(struct control_event_queue *queue) {
} }
} }
bool SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {
control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event) {
if (control_event_queue_is_full(queue)) { if (control_event_queue_is_full(queue)) {
return false; return SDL_FALSE;
} }
queue->data[queue->head] = *event; queue->data[queue->head] = *event;
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
bool SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) {
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event) {
if (control_event_queue_is_empty(queue)) { if (control_event_queue_is_empty(queue)) {
return false; return SDL_FALSE;
} }
*event = queue->data[queue->tail]; *event = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true; return SDL_TRUE;
} }

View File

@@ -1,9 +1,8 @@
#ifndef CONTROLEVENT_H #ifndef CONTROLEVENT_H
#define CONTROLEVENT_H #define CONTROLEVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
@@ -21,11 +20,7 @@ enum control_event_type {
CONTROL_EVENT_TYPE_COMMAND, CONTROL_EVENT_TYPE_COMMAND,
}; };
enum control_event_command { #define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON,
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL,
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL,
};
struct control_event { struct control_event {
enum control_event_type type; enum control_event_type type;
@@ -45,11 +40,11 @@ struct control_event {
} mouse_event; } mouse_event;
struct { struct {
struct position position; struct position position;
int32_t hscroll; Sint32 hscroll;
int32_t vscroll; Sint32 vscroll;
} scroll_event; } scroll_event;
struct { struct {
enum control_event_command action; int action;
} command_event; } command_event;
}; };
}; };
@@ -61,31 +56,18 @@ struct control_event_queue {
}; };
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE // buf size must be at least SERIALIZED_EVENT_MAX_SIZE
int int control_event_serialize(const struct control_event *event, unsigned char *buf);
control_event_serialize(const struct control_event *event, unsigned char *buf);
bool SDL_bool control_event_queue_init(struct control_event_queue *queue);
control_event_queue_init(struct control_event_queue *queue); void control_event_queue_destroy(struct control_event_queue *queue);
void SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue);
control_event_queue_destroy(struct control_event_queue *queue); SDL_bool control_event_queue_is_full(const struct control_event_queue *queue);
bool
control_event_queue_is_empty(const struct control_event_queue *queue);
bool
control_event_queue_is_full(const struct control_event_queue *queue);
// event is copied, the queue does not use the event after the function returns // event is copied, the queue does not use the event after the function returns
bool SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event);
control_event_queue_push(struct control_event_queue *queue, SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
const struct control_event *event);
bool void control_event_destroy(struct control_event *event);
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event);
void
control_event_destroy(struct control_event *event);
#endif #endif

View File

@@ -1,45 +1,40 @@
#include "controller.h" #include "controller.h"
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h" #include "config.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
bool SDL_bool controller_init(struct controller *controller, socket_t video_socket) {
controller_init(struct controller *controller, socket_t video_socket) {
if (!control_event_queue_init(&controller->queue)) { if (!control_event_queue_init(&controller->queue)) {
return false; return SDL_FALSE;
} }
if (!(controller->mutex = SDL_CreateMutex())) { if (!(controller->mutex = SDL_CreateMutex())) {
return false; return SDL_FALSE;
} }
if (!(controller->event_cond = SDL_CreateCond())) { if (!(controller->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
return false; return SDL_FALSE;
} }
controller->video_socket = video_socket; controller->video_socket = video_socket;
controller->stopped = false; controller->stopped = SDL_FALSE;
return true; return SDL_TRUE;
} }
void void controller_destroy(struct controller *controller) {
controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->event_cond); SDL_DestroyCond(controller->event_cond);
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
control_event_queue_destroy(&controller->queue); control_event_queue_destroy(&controller->queue);
} }
bool SDL_bool controller_push_event(struct controller *controller, const struct control_event *event) {
controller_push_event(struct controller *controller, SDL_bool res;
const struct control_event *event) {
bool res;
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
bool was_empty = control_event_queue_is_empty(&controller->queue); SDL_bool was_empty = control_event_queue_is_empty(&controller->queue);
res = control_event_queue_push(&controller->queue, event); res = control_event_queue_push(&controller->queue, event);
if (was_empty) { if (was_empty) {
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
@@ -48,26 +43,22 @@ controller_push_event(struct controller *controller,
return res; return res;
} }
static bool static SDL_bool process_event(struct controller *controller, const struct control_event *event) {
process_event(struct controller *controller,
const struct control_event *event) {
unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE]; unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE];
int length = control_event_serialize(event, serialized_event); int length = control_event_serialize(event, serialized_event);
if (!length) { if (!length) {
return false; return SDL_FALSE;
} }
int w = net_send_all(controller->video_socket, serialized_event, length); int w = net_send_all(controller->video_socket, serialized_event, length);
return w == length; return w == length;
} }
static int static int run_controller(void *data) {
run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
while (!controller->stopped while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
&& control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex); cond_wait(controller->event_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
@@ -76,12 +67,11 @@ run_controller(void *data) {
break; break;
} }
struct control_event event; struct control_event event;
bool non_empty = control_event_queue_take(&controller->queue, SDL_bool non_empty = control_event_queue_take(&controller->queue, &event);
&event);
SDL_assert(non_empty); SDL_assert(non_empty);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
bool ok = process_event(controller, &event); SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event); control_event_destroy(&event);
if (!ok) { if (!ok) {
LOGD("Cannot write event to socket"); LOGD("Cannot write event to socket");
@@ -91,29 +81,25 @@ run_controller(void *data) {
return 0; return 0;
} }
bool SDL_bool controller_start(struct controller *controller) {
controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", controller->thread = SDL_CreateThread(run_controller, "controller", controller);
controller);
if (!controller->thread) { if (!controller->thread) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void controller_stop(struct controller *controller) {
controller_stop(struct controller *controller) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
controller->stopped = true; controller->stopped = SDL_TRUE;
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
} }
void void controller_join(struct controller *controller) {
controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
} }

View File

@@ -3,8 +3,8 @@
#include "control_event.h" #include "control_event.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "net.h" #include "net.h"
@@ -14,28 +14,18 @@ struct controller {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
bool stopped; SDL_bool stopped;
struct control_event_queue queue; struct control_event_queue queue;
}; };
bool SDL_bool controller_init(struct controller *controller, socket_t video_socket);
controller_init(struct controller *controller, socket_t video_socket); void controller_destroy(struct controller *controller);
void SDL_bool controller_start(struct controller *controller);
controller_destroy(struct controller *controller); void controller_stop(struct controller *controller);
void controller_join(struct controller *controller);
bool
controller_start(struct controller *controller);
void
controller_stop(struct controller *controller);
void
controller_join(struct controller *controller);
// expose simple API to hide control_event_queue // expose simple API to hide control_event_queue
bool SDL_bool controller_push_event(struct controller *controller, const struct control_event *event);
controller_push_event(struct controller *controller,
const struct control_event *event);
#endif #endif

View File

@@ -1,10 +1,9 @@
#include "convert.h" #include "convert.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true #define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE
#define FAIL default: return false #define FAIL default: return SDL_FALSE
static bool static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
@@ -12,8 +11,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
} }
} }
static enum android_metastate static enum android_metastate autocomplete_metastate(enum android_metastate metastate) {
autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags // fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON; metastate |= AMETA_SHIFT_ON;
@@ -32,8 +30,7 @@ autocomplete_metastate(enum android_metastate metastate) {
} }
static enum android_metastate static enum android_metastate convert_meta_state(SDL_Keymod mod) {
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON; metastate |= AMETA_SHIFT_LEFT_ON;
@@ -73,8 +70,7 @@ convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static bool static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) {
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
switch (from) { switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@@ -92,7 +88,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
MAP(SDLK_UP, AKEYCODE_DPAD_UP); MAP(SDLK_UP, AKEYCODE_DPAD_UP);
} }
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false; return SDL_FALSE;
} }
// if ALT and META are not pressed, also handle letters and space // if ALT and META are not pressed, also handle letters and space
switch (from) { switch (from) {
@@ -127,8 +123,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
} }
} }
static bool static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
@@ -136,8 +131,7 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
} }
} }
static enum android_motionevent_buttons static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) { if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY; buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
@@ -157,33 +151,31 @@ convert_mouse_buttons(uint32_t state) {
return buttons; return buttons;
} }
bool SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_KEYCODE; to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) { if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
return false; return SDL_FALSE;
} }
uint16_t mod = from->keysym.mod; Uint16 mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) { if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
return false; return SDL_FALSE;
} }
to->keycode_event.metastate = convert_meta_state(mod); to->keycode_event.metastate = convert_meta_state(mod);
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, struct size screen_size,
struct size screen_size, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
if (!convert_mouse_action(from->type, &to->mouse_event.action)) { if (!convert_mouse_action(from->type, &to->mouse_event.action)) {
return false; return SDL_FALSE;
} }
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -191,13 +183,12 @@ mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, struct size screen_size,
struct size screen_size, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->mouse_event.buttons = convert_mouse_buttons(from->state); to->mouse_event.buttons = convert_mouse_buttons(from->state);
@@ -205,13 +196,12 @@ mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, struct position position,
struct position position, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_SCROLL; to->type = CONTROL_EVENT_TYPE_SCROLL;
to->scroll_event.position = position; to->scroll_event.position = position;
@@ -223,5 +213,5 @@ mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.hscroll = -mul * from->x; to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * from->y; to->scroll_event.vscroll = mul * from->y;
return true; return SDL_TRUE;
} }

View File

@@ -1,9 +1,8 @@
#ifndef CONVERT_H #ifndef CONVERT_H
#define CONVERT_H #define CONVERT_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "control_event.h" #include "control_event.h"
struct complete_mouse_motion_event { struct complete_mouse_motion_event {
@@ -16,26 +15,21 @@ struct complete_mouse_wheel_event {
struct point position; struct point position;
}; };
bool SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, struct control_event *to);
struct control_event *to); SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to);
bool // the video size may be different from the real device size, so we need the size
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, // to which the absolute position apply, to scale it accordingly
struct size screen_size, SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct control_event *to); struct size screen_size,
struct control_event *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to);
// on Android, a scroll event requires the current mouse position // on Android, a scroll event requires the current mouse position
bool SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, struct position position,
struct position position, struct control_event *to);
struct control_event *to);
#endif #endif

View File

@@ -8,22 +8,133 @@
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "events.h" #include "events.h"
#include "frames.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
#include "video_buffer.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static SDL_bool receiver_state_push_meta(struct receiver_state *state,
uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return SDL_FALSE;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return SDL_TRUE;
}
static uint64_t receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
struct receiver_state *state = &decoder->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining)
buf_size = state->remaining;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
// set the decoded frame as ready for rendering, and notify // set the decoded frame as ready for rendering, and notify
static void static void push_frame(struct decoder *decoder) {
push_frame(struct decoder *decoder) { SDL_bool previous_frame_consumed = frames_offer_decoded_frame(decoder->frames);
bool previous_frame_skipped; if (!previous_frame_consumed) {
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame // the previous EVENT_NEW_FRAME will consume this frame
return; return;
} }
@@ -33,71 +144,181 @@ push_frame(struct decoder *decoder) {
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
void static void notify_stopped(void) {
decoder_init(struct decoder *decoder, struct video_buffer *vb) { SDL_Event stop_event;
decoder->video_buffer = vb; stop_event.type = EVENT_DECODER_STOPPED;
SDL_PushEvent(&stop_event);
} }
bool static int run_decoder(void *data) {
decoder_open(struct decoder *decoder, const AVCodec *codec) { struct decoder *decoder = data;
decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto run_end;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGC("Could not allocate decoder context"); LOGC("Could not allocate decoder context");
return false; goto run_end;
} }
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec"); LOGE("Could not open H.264 codec");
avcodec_free_context(&decoder->codec_ctx); goto run_finally_free_codec_ctx;
return false;
} }
return true; AVFormatContext *format_ctx = avformat_alloc_context();
} if (!format_ctx) {
LOGC("Could not allocate format context");
goto run_finally_close_codec;
}
void unsigned char *buffer = av_malloc(BUFSIZE);
decoder_close(struct decoder *decoder) { if (!buffer) {
avcodec_close(decoder->codec_ctx); LOGC("Could not allocate buffer");
avcodec_free_context(&decoder->codec_ctx); goto run_finally_free_format_ctx;
} }
bool // initialize the receiver state
decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder->receiver_state.frame_meta_queue = NULL;
decoder->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
decoder->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto run_finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto run_finally_free_avio_ctx;
}
if (decoder->recorder &&
!recorder_open(decoder->recorder, codec)) {
LOGE("Could not open recorder");
goto run_finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
// the new decoding/encoding API has been introduced by: // the new decoding/encoding API has been introduced by:
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726> // <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
int ret; int ret;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
return false; goto run_quit;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame);
decoder->video_buffer->decoding_frame); if (!ret) {
if (!ret) { // a frame was received
// a frame was received push_frame(decoder);
push_frame(decoder); } else if (ret != AVERROR(EAGAIN)) {
} else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive video frame: %d", ret);
LOGE("Could not receive video frame: %d", ret); av_packet_unref(&packet);
return false; goto run_quit;
} }
#else #else
int got_picture; while (packet.size > 0) {
int len = avcodec_decode_video2(decoder->codec_ctx, int got_picture;
decoder->video_buffer->decoding_frame, int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
&got_picture, if (len < 0) {
packet); LOGE("Could not decode video packet: %d", len);
if (len < 0) { av_packet_unref(&packet);
LOGE("Could not decode video packet: %d", len); goto run_quit;
return false; }
} if (got_picture) {
if (got_picture) { push_frame(decoder);
push_frame(decoder); }
} packet.size -= len;
packet.data += len;
}
#endif #endif
return true;
if (decoder->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&decoder->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(decoder->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto run_quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
run_quit:
if (decoder->recorder) {
recorder_close(decoder->recorder);
}
run_finally_close_input:
avformat_close_input(&format_ctx);
run_finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
run_finally_free_format_ctx:
avformat_free_context(format_ctx);
run_finally_close_codec:
avcodec_close(codec_ctx);
run_finally_free_codec_ctx:
avcodec_free_context(&codec_ctx);
notify_stopped();
run_end:
return 0;
} }
void void decoder_init(struct decoder *decoder, struct frames *frames,
decoder_interrupt(struct decoder *decoder) { socket_t video_socket, struct recorder *recorder) {
video_buffer_interrupt(decoder->video_buffer); decoder->frames = frames;
decoder->video_socket = video_socket;
decoder->recorder = recorder;
}
SDL_bool decoder_start(struct decoder *decoder) {
LOGD("Starting decoder thread");
decoder->thread = SDL_CreateThread(run_decoder, "video_decoder", decoder);
if (!decoder->thread) {
LOGC("Could not start decoder thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void decoder_stop(struct decoder *decoder) {
frames_stop(decoder->frames);
}
void decoder_join(struct decoder *decoder) {
SDL_WaitThread(decoder->thread, NULL);
} }

View File

@@ -1,29 +1,36 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <libavformat/avformat.h> #include <SDL2/SDL_thread.h>
struct video_buffer; #include "common.h"
#include "net.h"
struct decoder { struct frames;
struct video_buffer *video_buffer;
AVCodecContext *codec_ctx; struct frame_meta {
uint64_t pts;
struct frame_meta *next;
}; };
void struct decoder {
decoder_init(struct decoder *decoder, struct video_buffer *vb); struct frames *frames;
socket_t video_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
bool void decoder_init(struct decoder *decoder, struct frames *frames,
decoder_open(struct decoder *decoder, const AVCodec *codec); socket_t video_socket, struct recorder *recoder);
SDL_bool decoder_start(struct decoder *decoder);
void void decoder_stop(struct decoder *decoder);
decoder_close(struct decoder *decoder); void decoder_join(struct decoder *decoder);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View File

@@ -1,22 +1,18 @@
#include "device.h" #include "device.h"
#include "log.h" #include "log.h"
bool SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) {
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf)); int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) { if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information"); LOGE("Could not retrieve device information");
return false; return SDL_FALSE;
} }
// in case the client sends garbage buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
// strcpy is safe here, since name contains at least // and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf); strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1];
| buf[DEVICE_NAME_FIELD_LENGTH + 1]; size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) return SDL_TRUE;
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
} }

View File

@@ -1,7 +1,7 @@
#ifndef DEVICE_H #ifndef DEVICE_H
#define DEVICE_H #define DEVICE_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include "common.h" #include "common.h"
#include "net.h" #include "net.h"
@@ -10,7 +10,6 @@
#define DEVICE_SDCARD_PATH "/sdcard/" #define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes // name must be at least DEVICE_NAME_FIELD_LENGTH bytes
bool SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size);
device_read_info(socket_t device_socket, char *name, struct size *frame_size);
#endif #endif

View File

@@ -1,3 +1,3 @@
#define EVENT_NEW_SESSION SDL_USEREVENT #define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1) #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2) #define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2)

View File

@@ -13,8 +13,7 @@ struct request {
const char *file; const char *file;
}; };
static struct request * static struct request *request_new(file_handler_action_t action, const char *file) {
request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req)); struct request *req = SDL_malloc(sizeof(*req));
if (!req) { if (!req) {
return NULL; return NULL;
@@ -24,8 +23,7 @@ request_new(file_handler_action_t action, const char *file) {
return req; return req;
} }
static void static void request_free(struct request *req) {
request_free(struct request *req) {
if (!req) { if (!req) {
return; return;
} }
@@ -33,25 +31,21 @@ request_free(struct request *req) {
SDL_free((void *) req); SDL_free((void *) req);
} }
static bool static SDL_bool request_queue_is_empty(const struct request_queue *queue) {
request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
static bool static SDL_bool request_queue_is_full(const struct request_queue *queue) {
request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail; return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
} }
static bool static SDL_bool request_queue_init(struct request_queue *queue) {
request_queue_init(struct request_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
return true; return SDL_TRUE;
} }
static void static void request_queue_destroy(struct request_queue *queue) {
request_queue_destroy(struct request_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
request_free(queue->reqs[i]); request_free(queue->reqs[i]);
@@ -59,41 +53,38 @@ request_queue_destroy(struct request_queue *queue) {
} }
} }
static bool static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) {
request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) { if (request_queue_is_full(queue)) {
return false; return SDL_FALSE;
} }
queue->reqs[queue->head] = req; queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE; queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
static bool static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) { if (request_queue_is_empty(queue)) {
return false; return SDL_FALSE;
} }
// transfer ownership // transfer ownership
*req = queue->reqs[queue->tail]; *req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE; queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
bool SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) {
file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) { if (!request_queue_init(&file_handler->queue)) {
return false; return SDL_FALSE;
} }
if (!(file_handler->mutex = SDL_CreateMutex())) { if (!(file_handler->mutex = SDL_CreateMutex())) {
return false; return SDL_FALSE;
} }
if (!(file_handler->event_cond = SDL_CreateCond())) { if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return SDL_FALSE;
} }
if (serial) { if (serial) {
@@ -101,63 +92,58 @@ file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Cannot strdup serial"); LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return SDL_FALSE;
} }
} else { } else {
file_handler->serial = NULL; file_handler->serial = NULL;
} }
// lazy initialization // lazy initialization
file_handler->initialized = false; file_handler->initialized = SDL_FALSE;
file_handler->stopped = false; file_handler->stopped = SDL_FALSE;
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
return true; return SDL_TRUE;
} }
void void file_handler_destroy(struct file_handler *file_handler) {
file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue); request_queue_destroy(&file_handler->queue);
SDL_free((void *) file_handler->serial); SDL_free((void *) file_handler->serial);
} }
static process_t static process_t install_apk(const char *serial, const char *file) {
install_apk(const char *serial, const char *file) {
return adb_install(serial, file); return adb_install(serial, file);
} }
static process_t static process_t push_file(const char *serial, const char *file) {
push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH); return adb_push(serial, file, DEVICE_SDCARD_PATH);
} }
bool SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_request(struct file_handler *file_handler, file_handler_action_t action,
file_handler_action_t action, const char *file) {
const char *file) { SDL_bool res;
bool res;
// start file_handler if it's used for the first time // start file_handler if it's used for the first time
if (!file_handler->initialized) { if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) { if (!file_handler_start(file_handler)) {
return false; return SDL_FALSE;
} }
file_handler->initialized = true; file_handler->initialized = SDL_TRUE;
} }
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file);
file);
struct request *req = request_new(action, file); struct request *req = request_new(action, file);
if (!req) { if (!req) {
LOGE("Could not create request"); LOGE("Could not create request");
return false; return SDL_FALSE;
} }
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
bool was_empty = request_queue_is_empty(&file_handler->queue); SDL_bool was_empty = request_queue_is_empty(&file_handler->queue);
res = request_queue_push(&file_handler->queue, req); res = request_queue_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
@@ -166,15 +152,13 @@ file_handler_request(struct file_handler *file_handler,
return res; return res;
} }
static int static int run_file_handler(void *data) {
run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) {
&& request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex); cond_wait(file_handler->event_cond, file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
@@ -183,7 +167,7 @@ run_file_handler(void *data) {
break; break;
} }
struct request *req; struct request *req;
bool non_empty = request_queue_take(&file_handler->queue, &req); SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty); SDL_assert(non_empty);
process_t process; process_t process;
@@ -216,24 +200,21 @@ run_file_handler(void *data) {
return 0; return 0;
} }
bool SDL_bool file_handler_start(struct file_handler *file_handler) {
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler);
file_handler);
if (!file_handler->thread) { if (!file_handler->thread) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void file_handler_stop(struct file_handler *file_handler) {
file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = SDL_TRUE;
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) { if (!cmd_terminate(file_handler->current_process)) {
@@ -245,7 +226,6 @@ file_handler_stop(struct file_handler *file_handler) {
mutex_unlock(file_handler->mutex); mutex_unlock(file_handler->mutex);
} }
void void file_handler_join(struct file_handler *file_handler) {
file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL); SDL_WaitThread(file_handler->thread, NULL);
} }

View File

@@ -1,10 +1,9 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HANDLER_H #define FILE_HANDLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "command.h" #include "command.h"
#define REQUEST_QUEUE_SIZE 16 #define REQUEST_QUEUE_SIZE 16
@@ -25,30 +24,21 @@ struct file_handler {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
bool stopped; SDL_bool stopped;
bool initialized; SDL_bool initialized;
process_t current_process; process_t current_process;
struct request_queue queue; struct request_queue queue;
}; };
bool SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial);
file_handler_init(struct file_handler *file_handler, const char *serial); void file_handler_destroy(struct file_handler *file_handler);
void SDL_bool file_handler_start(struct file_handler *file_handler);
file_handler_destroy(struct file_handler *file_handler); void file_handler_stop(struct file_handler *file_handler);
void file_handler_join(struct file_handler *file_handler);
bool SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_start(struct file_handler *file_handler); file_handler_action_t action,
const char *file);
void
file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif #endif

View File

@@ -4,16 +4,14 @@
#include "log.h" #include "log.h"
void void fps_counter_init(struct fps_counter *counter) {
fps_counter_init(struct fps_counter *counter) { counter->started = SDL_FALSE;
counter->started = false;
// no need to initialize the other fields, they are meaningful only when // no need to initialize the other fields, they are meaningful only when
// started is true // started is true
} }
void void fps_counter_start(struct fps_counter *counter) {
fps_counter_start(struct fps_counter *counter) { counter->started = SDL_TRUE;
counter->started = true;
counter->slice_start = SDL_GetTicks(); counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@@ -21,17 +19,14 @@ fps_counter_start(struct fps_counter *counter) {
#endif #endif
} }
void void fps_counter_stop(struct fps_counter *counter) {
fps_counter_stop(struct fps_counter *counter) { counter->started = SDL_FALSE;
counter->started = false;
} }
static void static void display_fps(struct fps_counter *counter) {
display_fps(struct fps_counter *counter) {
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped);
counter->nr_skipped);
} else { } else {
#endif #endif
LOGI("%d fps", counter->nr_rendered); LOGI("%d fps", counter->nr_rendered);
@@ -40,13 +35,12 @@ display_fps(struct fps_counter *counter) {
#endif #endif
} }
static void static void check_expired(struct fps_counter *counter) {
check_expired(struct fps_counter *counter) { Uint32 now = SDL_GetTicks();
uint32_t now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) { if (now - counter->slice_start >= 1000) {
display_fps(counter); display_fps(counter);
// add a multiple of one second // add a multiple of one second
uint32_t elapsed_slices = (now - counter->slice_start) / 1000; Uint32 elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices; counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@@ -55,15 +49,13 @@ check_expired(struct fps_counter *counter) {
} }
} }
void void fps_counter_add_rendered_frame(struct fps_counter *counter) {
fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_rendered; ++counter->nr_rendered;
} }
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void void fps_counter_add_skipped_frame(struct fps_counter *counter) {
fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_skipped; ++counter->nr_skipped;
} }

View File

@@ -1,35 +1,26 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
#include "config.h" #include "config.h"
struct fps_counter { struct fps_counter {
bool started; SDL_bool started;
uint32_t slice_start; // initialized by SDL_GetTicks() Uint32 slice_start; // initialized by SDL_GetTicks()
int nr_rendered; int nr_rendered;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
int nr_skipped; int nr_skipped;
#endif #endif
}; };
void void fps_counter_init(struct fps_counter *counter);
fps_counter_init(struct fps_counter *counter); void fps_counter_start(struct fps_counter *counter);
void fps_counter_stop(struct fps_counter *counter);
void
fps_counter_start(struct fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void fps_counter_add_rendered_frame(struct fps_counter *counter);
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void void fps_counter_add_skipped_frame(struct fps_counter *counter);
fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif #endif
#endif #endif

110
app/src/frames.c Normal file
View File

@@ -0,0 +1,110 @@
#include "frames.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {
if (!(frames->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(frames->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(frames->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(frames->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(frames->mutex);
goto error_2;
}
frames->stopped = SDL_FALSE;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
frames->rendering_frame_consumed = SDL_TRUE;
fps_counter_init(&frames->fps_counter);
return SDL_TRUE;
error_2:
av_frame_free(&frames->rendering_frame);
error_1:
av_frame_free(&frames->decoding_frame);
error_0:
return SDL_FALSE;
}
void frames_destroy(struct frames *frames) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(frames->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(frames->mutex);
av_frame_free(&frames->rendering_frame);
av_frame_free(&frames->decoding_frame);
}
static void frames_swap(struct frames *frames) {
AVFrame *tmp = frames->decoding_frame;
frames->decoding_frame = frames->rendering_frame;
frames->rendering_frame = tmp;
}
SDL_bool frames_offer_decoded_frame(struct frames *frames) {
mutex_lock(frames->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!frames->rendering_frame_consumed && !frames->stopped) {
cond_wait(frames->rendering_frame_consumed_cond, frames->mutex);
}
#else
if (frames->fps_counter.started && !frames->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&frames->fps_counter);
}
#endif
frames_swap(frames);
SDL_bool previous_frame_consumed = frames->rendering_frame_consumed;
frames->rendering_frame_consumed = SDL_FALSE;
mutex_unlock(frames->mutex);
return previous_frame_consumed;
}
const AVFrame *frames_consume_rendered_frame(struct frames *frames) {
SDL_assert(!frames->rendering_frame_consumed);
frames->rendering_frame_consumed = SDL_TRUE;
if (frames->fps_counter.started) {
fps_counter_add_rendered_frame(&frames->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(frames->rendering_frame_consumed_cond);
#endif
return frames->rendering_frame;
}
void frames_stop(struct frames *frames) {
#ifdef SKIP_FRAMES
(void) frames; // unused
#else
mutex_lock(frames->mutex);
frames->stopped = SDL_TRUE;
mutex_unlock(frames->mutex);
// wake up blocking wait
cond_signal(frames->rendering_frame_consumed_cond);
#endif
}

View File

@@ -1,8 +1,8 @@
#ifndef VIDEO_BUFFER_H #ifndef FRAMES_H
#define VIDEO_BUFFER_H #define FRAMES_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "config.h" #include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
@@ -10,40 +10,33 @@
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
struct video_buffer { struct frames {
AVFrame *decoding_frame; AVFrame *decoding_frame;
AVFrame *rendering_frame; AVFrame *rendering_frame;
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef SKIP_FRAMES #ifndef SKIP_FRAMES
bool interrupted; SDL_bool stopped;
SDL_cond *rendering_frame_consumed_cond; SDL_cond *rendering_frame_consumed_cond;
#endif #endif
bool rendering_frame_consumed; SDL_bool rendering_frame_consumed;
struct fps_counter fps_counter; struct fps_counter fps_counter;
}; };
bool SDL_bool frames_init(struct frames *frames);
video_buffer_init(struct video_buffer *vb); void frames_destroy(struct frames *frames);
void // set the decoder frame as ready for rendering
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution // this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped // returns true if the previous frame had been consumed
void SDL_bool frames_offer_decoded_frame(struct frames *frames);
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// mark the rendering frame as consumed and return it // mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!! // MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before // the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex // unlocking frames->mutex
const AVFrame * const AVFrame *frames_consume_rendered_frame(struct frames *frames);
video_buffer_consume_rendered_frame(struct video_buffer *vb);
// wake up and avoid any blocking call // wake up and avoid any blocking call
void void frames_stop(struct frames *frames);
video_buffer_interrupt(struct video_buffer *vb);
#endif #endif

View File

@@ -5,13 +5,11 @@
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer // Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
// coordinates (as provided in SDL mouse events)
// //
// See my question: // See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event> // <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport; SDL_Rect viewport;
float scale_x, scale_y; float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport); SDL_RenderGetViewport(renderer, &viewport);
@@ -20,8 +18,7 @@ convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
*y = (int) (*y / scale_y) - viewport.y; *y = (int) (*y / scale_y) - viewport.y;
} }
static struct point static struct point get_mouse_point(struct screen *screen) {
get_mouse_point(struct screen *screen) {
int x; int x;
int y; int y;
SDL_GetMouseState(&x, &y); SDL_GetMouseState(&x, &y);
@@ -35,9 +32,7 @@ get_mouse_point(struct screen *screen) {
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
static void static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
// send DOWN event // send DOWN event
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE; control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
@@ -60,93 +55,58 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
} }
} }
static inline void static inline void action_home(struct controller *controller, int actions) {
action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
} }
static inline void static inline void action_back(struct controller *controller, int actions) {
action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
} }
static inline void static inline void action_app_switch(struct controller *controller, int actions) {
action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
} }
static inline void static inline void action_power(struct controller *controller, int actions) {
action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
} }
static inline void static inline void action_volume_up(struct controller *controller, int actions) {
action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
} }
static inline void static inline void action_volume_down(struct controller *controller, int actions) {
action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
} }
static inline void static inline void action_menu(struct controller *controller, int actions) {
action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
static void static void press_back_or_turn_screen_on(struct controller *controller) {
press_back_or_turn_screen_on(struct controller *controller) {
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND; control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action = control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
if (!controller_push_event(controller, &control_event)) { if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot turn screen on"); LOGW("Cannot turn screen on");
} }
} }
static void static void switch_fps_counter_state(struct frames *frames) {
expand_notification_panel(struct controller *controller) { mutex_lock(frames->mutex);
struct control_event control_event; if (frames->fps_counter.started) {
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot expand notification panel");
}
}
static void
collapse_notification_panel(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot collapse notification panel");
}
}
static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
LOGI("FPS counter stopped"); LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter); fps_counter_stop(&frames->fps_counter);
} else { } else {
LOGI("FPS counter started"); LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter); fps_counter_start(&frames->fps_counter);
} }
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
} }
static void static void clipboard_paste(struct controller *controller) {
clipboard_paste(struct controller *controller) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
LOGW("Cannot get clipboard text: %s", SDL_GetError()); LOGW("Cannot get clipboard text: %s", SDL_GetError());
@@ -167,9 +127,8 @@ clipboard_paste(struct controller *controller) {
} }
} }
void void input_manager_process_text_input(struct input_manager *input_manager,
input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event) {
const SDL_TextInputEvent *event) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0'); SDL_assert(event->text[1] == '\0');
@@ -189,13 +148,11 @@ input_manager_process_text_input(struct input_manager *input_manager,
} }
} }
void void input_manager_process_key(struct input_manager *input_manager,
input_manager_process_key(struct input_manager *input_manager, const SDL_KeyboardEvent *event) {
const SDL_KeyboardEvent *event, SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool control) { SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) { if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded // no shortcut involves Alt or Meta, and they should not be forwarded
@@ -205,42 +162,47 @@ input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events // capture all Ctrl events
if (ctrl | meta) { if (ctrl | meta) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP; int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat; SDL_bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_home(input_manager->controller, action); action_home(input_manager->controller, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_back(input_manager->controller, action); action_back(input_manager->controller, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_app_switch(input_manager->controller, action); action_app_switch(input_manager->controller, action);
} }
return; return;
case SDLK_m: case SDLK_m:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_menu(input_manager->controller, action); action_menu(input_manager->controller, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_power(input_manager->controller, action); action_power(input_manager->controller, action);
} }
return; return;
case SDLK_DOWN: case SDLK_DOWN:
#ifdef __APPLE__ #ifdef __APPLE__
if (control && !ctrl && meta && !shift) { if (!ctrl && meta) {
#else #else
if (control && ctrl && !meta && !shift) { if (ctrl && !meta) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_down(input_manager->controller, action); action_volume_down(input_manager->controller, action);
@@ -248,52 +210,37 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
case SDLK_UP: case SDLK_UP:
#ifdef __APPLE__ #ifdef __APPLE__
if (control && !ctrl && meta && !shift) { if (!ctrl && meta) {
#else #else
if (control && ctrl && !meta && !shift) { if (ctrl && !meta) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_up(input_manager->controller, action); action_volume_up(input_manager->controller, action);
} }
return; return;
case SDLK_v: case SDLK_v:
if (control && ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller); clipboard_paste(input_manager->controller);
} }
return; return;
case SDLK_f: case SDLK_f:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(input_manager->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) { switch_fps_counter_state(input_manager->frames);
switch_fps_counter_state(input_manager->video_buffer);
}
return;
case SDLK_n:
if (control && ctrl && !meta
&& !repeat && event->type == SDL_KEYDOWN) {
if (shift) {
collapse_notification_panel(input_manager->controller);
} else {
expand_notification_panel(input_manager->controller);
}
} }
return; return;
} }
@@ -301,10 +248,6 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
} }
if (!control) {
return;
}
struct control_event control_event; struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) { if (input_key_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
@@ -313,48 +256,43 @@ input_manager_process_key(struct input_manager *input_manager,
} }
} }
void void input_manager_process_mouse_motion(struct input_manager *input_manager,
input_manager_process_mouse_motion(struct input_manager *input_manager, const SDL_MouseMotionEvent *event) {
const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
// do not send motion events when no button is pressed // do not send motion events when no button is pressed
return; return;
} }
struct control_event control_event; struct control_event control_event;
if (mouse_motion_from_sdl_to_android(event, if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
input_manager->screen->frame_size,
&control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send mouse motion event"); LOGW("Cannot send mouse motion event");
} }
} }
} }
static bool static SDL_bool is_outside_device_screen(struct input_manager *input_manager,
is_outside_device_screen(struct input_manager *input_manager, int x, int y) int x, int y)
{ {
return x < 0 || x >= input_manager->screen->frame_size.width || return x < 0 || x >= input_manager->screen->frame_size.width ||
y < 0 || y >= input_manager->screen->frame_size.height; y < 0 || y >= input_manager->screen->frame_size.height;
} }
void void input_manager_process_mouse_button(struct input_manager *input_manager,
input_manager_process_mouse_button(struct input_manager *input_manager, const SDL_MouseButtonEvent *event) {
const SDL_MouseButtonEvent *event,
bool control) {
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) { if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(input_manager->controller);
return; return;
} }
if (control && event->button == SDL_BUTTON_MIDDLE) { if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
bool outside = is_outside_device_screen(input_manager, SDL_bool outside= is_outside_device_screen(input_manager,
event->x, event->x,
event->y); event->y);
if (outside) { if (outside) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
return; return;
@@ -363,23 +301,16 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
// otherwise, send the click event to the device // otherwise, send the click event to the device
} }
if (!control) {
return;
}
struct control_event control_event; struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
input_manager->screen->frame_size,
&control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send mouse button event"); LOGW("Cannot send mouse button event");
} }
} }
} }
void void input_manager_process_mouse_wheel(struct input_manager *input_manager,
input_manager_process_mouse_wheel(struct input_manager *input_manager, const SDL_MouseWheelEvent *event) {
const SDL_MouseWheelEvent *event) {
struct position position = { struct position position = {
.screen_size = input_manager->screen->frame_size, .screen_size = input_manager->screen->frame_size,
.point = get_mouse_point(input_manager->screen), .point = get_mouse_point(input_manager->screen),

View File

@@ -1,40 +1,27 @@
#ifndef INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define INPUTMANAGER_H #define INPUTMANAGER_H
#include <stdbool.h>
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "video_buffer.h" #include "frames.h"
#include "screen.h" #include "screen.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer; struct frames *frames;
struct screen *screen; struct screen *screen;
}; };
void void input_manager_process_text_input(struct input_manager *input_manager,
input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event);
const SDL_TextInputEvent *event); void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event);
void void input_manager_process_mouse_motion(struct input_manager *input_manager,
input_manager_process_key(struct input_manager *input_manager, const SDL_MouseMotionEvent *event);
const SDL_KeyboardEvent *event, void input_manager_process_mouse_button(struct input_manager *input_manager,
bool control); const SDL_MouseButtonEvent *event);
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
void const SDL_MouseWheelEvent *event);
input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event);
void
input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control);
void
input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event);
#endif #endif

View File

@@ -4,32 +4,28 @@
#include "log.h" #include "log.h"
void void mutex_lock(SDL_mutex *mutex) {
mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) { if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex"); LOGC("Could not lock mutex");
abort(); abort();
} }
} }
void void mutex_unlock(SDL_mutex *mutex) {
mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) { if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex"); LOGC("Could not unlock mutex");
abort(); abort();
} }
} }
void void cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) { if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition"); LOGC("Could not wait on condition");
abort(); abort();
} }
} }
void void cond_signal(SDL_cond *cond) {
cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) { if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition"); LOGC("Could not signal a condition");
abort(); abort();

View File

@@ -5,16 +5,9 @@
typedef struct SDL_mutex SDL_mutex; typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond; typedef struct SDL_cond SDL_cond;
void void mutex_lock(SDL_mutex *mutex);
mutex_lock(SDL_mutex *mutex); void mutex_unlock(SDL_mutex *mutex);
void cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void void cond_signal(SDL_cond *cond);
mutex_unlock(SDL_mutex *mutex);
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void
cond_signal(SDL_cond *cond);
#endif #endif

View File

@@ -1,32 +1,25 @@
#include "scrcpy.h" #include "scrcpy.h"
#include <getopt.h> #include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "recorder.h"
struct args { struct args {
const char *serial; const char *serial;
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; SDL_bool fullscreen;
bool fullscreen; SDL_bool help;
bool no_control; SDL_bool version;
bool no_display; SDL_bool show_touches;
bool help; Uint16 port;
bool version; Uint16 max_size;
bool show_touches; Uint32 bit_rate;
uint16_t port; SDL_bool always_on_top;
uint16_t max_size;
uint32_t bit_rate;
bool always_on_top;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@@ -49,9 +42,6 @@ static void usage(const char *arg0) {
" -f, --fullscreen\n" " -f, --fullscreen\n"
" Start in fullscreen.\n" " Start in fullscreen.\n"
"\n" "\n"
" -F, --record-format\n"
" Force recording format (either mp4 or mkv).\n"
"\n"
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
@@ -61,21 +51,12 @@ static void usage(const char *arg0) {
" is preserved.\n" " is preserved.\n"
" Default is %d%s.\n" " Default is %d%s.\n"
"\n" "\n"
" -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n"
"\n"
" -N, --no-display\n"
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" -p, --port port\n" " -p, --port port\n"
" Set the TCP port the client listens on.\n" " Set the TCP port the client listens on.\n"
" Default is %d.\n" " Default is %d.\n"
"\n" "\n"
" -r, --record file.mp4\n" " -r, --record file.mp4\n"
" Record screen to file.\n" " Record screen to file.\n"
" The format is determined by the -F/--record-format option if\n"
" set, or by the file extension (.mp4 or .mkv).\n"
"\n" "\n"
" -s, --serial\n" " -s, --serial\n"
" The device serial number. Mandatory only if several devices\n" " The device serial number. Mandatory only if several devices\n"
@@ -131,12 +112,6 @@ static void usage(const char *arg0) {
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" turn screen on\n" " turn screen on\n"
"\n" "\n"
" Ctrl+n\n"
" expand notification panel\n"
"\n"
" Ctrl+Shift+n\n"
" collapse notification panel\n"
"\n"
" Ctrl+v\n" " Ctrl+v\n"
" paste computer clipboard to device\n" " paste computer clipboard to device\n"
"\n" "\n"
@@ -152,37 +127,28 @@ static void usage(const char *arg0) {
DEFAULT_LOCAL_PORT); DEFAULT_LOCAL_PORT);
} }
static void static void print_version(void) {
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION); fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n"); fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
SDL_PATCHLEVEL); fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO);
LIBAVCODEC_VERSION_MINOR, fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO);
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
} }
static bool static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) {
parse_bit_rate(char *optarg, uint32_t *bit_rate) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Bit-rate parameter is empty"); LOGE("Bit-rate parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
int mul = 1; int mul = 1;
if (*endptr != '\0') { if (*endptr != '\0') {
if (optarg == endptr) { if (optarg == endptr) {
LOGE("Invalid bit-rate: %s", optarg); LOGE("Invalid bit-rate: %s", optarg);
return false; return SDL_FALSE;
} }
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') { if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
mul = 1000000; mul = 1000000;
@@ -190,92 +156,59 @@ parse_bit_rate(char *optarg, uint32_t *bit_rate) {
mul = 1000; mul = 1000;
} else { } else {
LOGE("Invalid bit-rate unit: %s", optarg); LOGE("Invalid bit-rate unit: %s", optarg);
return false; return SDL_FALSE;
} }
} }
if (value < 0 || ((uint32_t) -1) / mul < value) { if (value < 0 || ((Uint32) -1) / mul < value) {
LOGE("Bitrate must be positive and less than 2^32: %s", optarg); LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
return false; return SDL_FALSE;
} }
*bit_rate = (uint32_t) value * mul; *bit_rate = (Uint32) value * mul;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_max_size(char *optarg, Uint16 *max_size) {
parse_max_size(char *optarg, uint16_t *max_size) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Max size parameter is empty"); LOGE("Max size parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid max size: %s", optarg); LOGE("Invalid max size: %s", optarg);
return false; return SDL_FALSE;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Max size must be between 0 and 65535: %ld", value); LOGE("Max size must be between 0 and 65535: %ld", value);
return false; return SDL_FALSE;
} }
*max_size = (uint16_t) value; *max_size = (Uint16) value;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_port(char *optarg, Uint16 *port) {
parse_port(char *optarg, uint16_t *port) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Invalid port parameter is empty"); LOGE("Invalid port parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid port: %s", optarg); LOGE("Invalid port: %s", optarg);
return false; return SDL_FALSE;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Port out of range: %ld", value); LOGE("Port out of range: %ld", value);
return false; return SDL_FALSE;
} }
*port = (uint16_t) value; *port = (Uint16) value;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum recorder_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
return 0;
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV;
}
return 0;
}
static bool
parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, 'T'}, {"always-on-top", no_argument, NULL, 'T'},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
@@ -283,53 +216,38 @@ parse_args(struct args *args, int argc, char *argv[]) {
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, 'f'},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'}, {"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:tTv", long_options, while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tTv", long_options, NULL)) != -1) {
NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &args->bit_rate)) {
return false; return SDL_FALSE;
} }
break; break;
case 'c': case 'c':
args->crop = optarg; args->crop = optarg;
break; break;
case 'f': case 'f':
args->fullscreen = true; args->fullscreen = SDL_TRUE;
break;
case 'F':
if (!parse_record_format(optarg, &args->record_format)) {
return false;
}
break; break;
case 'h': case 'h':
args->help = true; args->help = SDL_TRUE;
break; break;
case 'm': case 'm':
if (!parse_max_size(optarg, &args->max_size)) { if (!parse_max_size(optarg, &args->max_size)) {
return false; return SDL_FALSE;
} }
break; break;
case 'n':
args->no_control = true;
break;
case 'N':
args->no_display = true;
break;
case 'p': case 'p':
if (!parse_port(optarg, &args->port)) { if (!parse_port(optarg, &args->port)) {
return false; return SDL_FALSE;
} }
break; break;
case 'r': case 'r':
@@ -339,55 +257,29 @@ parse_args(struct args *args, int argc, char *argv[]) {
args->serial = optarg; args->serial = optarg;
break; break;
case 't': case 't':
args->show_touches = true; args->show_touches = SDL_TRUE;
break; break;
case 'T': case 'T':
args->always_on_top = true; args->always_on_top = SDL_TRUE;
break; break;
case 'v': case 'v':
args->version = true; args->version = SDL_TRUE;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return SDL_FALSE;
} }
} }
if (args->no_display && !args->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
if (args->no_display && args->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind; int index = optind;
if (index < argc) { if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]); LOGE("Unexpected additional argument: %s", argv[index]);
return false; return SDL_FALSE;
} }
return SDL_TRUE;
if (args->record_format && !args->record_filename) {
LOGE("Record format specified without recording");
return false;
}
if (args->record_filename && !args->record_format) {
args->record_format = guess_record_format(args->record_filename);
if (!args->record_format) {
LOGE("No format specified for \"%s\" (try with -F mkv)",
args->record_filename);
return false;
}
}
return true;
} }
int int main(int argc, char *argv[]) {
main(int argc, char *argv[]) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// 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
@@ -398,16 +290,13 @@ main(int argc, char *argv[]) {
.serial = NULL, .serial = NULL,
.crop = NULL, .crop = NULL,
.record_filename = NULL, .record_filename = NULL,
.record_format = 0, .help = SDL_FALSE,
.help = false, .version = SDL_FALSE,
.version = false, .show_touches = SDL_FALSE,
.show_touches = false,
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
.always_on_top = false, .always_on_top = SDL_FALSE,
.no_control = false,
.no_display = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@@ -423,7 +312,7 @@ main(int argc, char *argv[]) {
return 0; return 0;
} }
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all(); av_register_all();
#endif #endif
@@ -440,14 +329,11 @@ main(int argc, char *argv[]) {
.crop = args.crop, .crop = args.crop,
.port = args.port, .port = args.port,
.record_filename = args.record_filename, .record_filename = args.record_filename,
.record_format = args.record_format,
.max_size = args.max_size, .max_size = args.max_size,
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen, .fullscreen = args.fullscreen,
.always_on_top = args.always_on_top, .always_on_top = args.always_on_top,
.no_control = args.no_control,
.no_display = args.no_display,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View File

@@ -18,8 +18,7 @@
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
#endif #endif
socket_t socket_t net_connect(Uint32 addr, Uint16 port) {
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@@ -39,8 +38,7 @@ net_connect(uint32_t addr, uint16_t port) {
return sock; return sock;
} }
socket_t socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@@ -48,8 +46,7 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
} }
int reuse = 1; int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) {
sizeof(reuse)) == -1) {
perror("setsockopt(SO_REUSEADDR)"); perror("setsockopt(SO_REUSEADDR)");
} }
@@ -71,30 +68,25 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
return sock; return sock;
} }
socket_t socket_t net_accept(socket_t server_socket) {
net_accept(socket_t server_socket) {
SOCKADDR_IN csin; SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin); socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize); return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
} }
ssize_t ssize_t net_recv(socket_t socket, void *buf, size_t len) {
net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0); return recv(socket, buf, len, 0);
} }
ssize_t ssize_t net_recv_all(socket_t socket, void *buf, size_t len) {
net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL); return recv(socket, buf, len, MSG_WAITALL);
} }
ssize_t ssize_t net_send(socket_t socket, const void *buf, size_t len) {
net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0); return send(socket, buf, len, 0);
} }
ssize_t ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0; ssize_t w = 0;
while (len > 0) { while (len > 0) {
w = send(socket, buf, len, 0); w = send(socket, buf, len, 0);
@@ -107,7 +99,6 @@ net_send_all(socket_t socket, const void *buf, size_t len) {
return w; return w;
} }
bool SDL_bool net_shutdown(socket_t socket, int how) {
net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how); return !shutdown(socket, how);
} }

View File

@@ -1,9 +1,8 @@
#ifndef NET_H #ifndef NET_H
#define NET_H #define NET_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include <SDL2/SDL_stdinc.h>
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <winsock2.h> # include <winsock2.h>
@@ -17,39 +16,20 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
bool SDL_bool net_init(void);
net_init(void); void net_cleanup(void);
void socket_t net_connect(Uint32 addr, Uint16 port);
net_cleanup(void); socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
socket_t net_accept(socket_t server_socket);
socket_t
net_connect(uint32_t addr, uint16_t port);
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog);
socket_t
net_accept(socket_t server_socket);
// the _all versions wait/retry until len bytes have been written/read // the _all versions wait/retry until len bytes have been written/read
ssize_t ssize_t net_recv(socket_t socket, void *buf, size_t len);
net_recv(socket_t socket, void *buf, size_t len); ssize_t net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, const void *buf, size_t len);
ssize_t ssize_t net_send_all(socket_t socket, const void *buf, size_t len);
net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t
net_send(socket_t socket, const void *buf, size_t len);
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
bool SDL_bool net_shutdown(socket_t socket, int how);
net_shutdown(socket_t socket, int how); SDL_bool net_close(socket_t socket);
bool
net_close(socket_t socket);
#endif #endif

View File

@@ -1,92 +1,80 @@
#include "recorder.h" #include "recorder.h"
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us // In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define LAVF_NEW_CODEC_API
#endif
static const AVOutputFormat * static const AVOutputFormat *find_mp4_muxer(void) {
find_muxer(const char *name) { #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
void *opaque = NULL; void *opaque = NULL;
#endif #endif
const AVOutputFormat *oformat = NULL; const AVOutputFormat *oformat = NULL;
do { do {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
oformat = av_muxer_iterate(&opaque); oformat = av_muxer_iterate(&opaque);
#else #else
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or with name "mp4" // until null or with name "mp4"
} while (oformat && strcmp(oformat->name, name)); } while (oformat && strcmp(oformat->name, "mp4"));
return oformat; return oformat;
} }
bool SDL_bool recorder_init(struct recorder *recorder, const char *filename,
recorder_init(struct recorder *recorder, struct size declared_frame_size) {
const char *filename,
enum recorder_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename); recorder->filename = SDL_strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Cannot strdup filename"); LOGE("Cannot strdup filename");
return false; return SDL_FALSE;
} }
recorder->format = format;
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false; recorder->header_written = SDL_FALSE;
return true; return SDL_TRUE;
} }
void void recorder_destroy(struct recorder *recorder) {
recorder_destroy(struct recorder *recorder) {
SDL_free(recorder->filename); SDL_free(recorder->filename);
} }
static const char * SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
recorder_get_format_name(enum recorder_format format) { const AVOutputFormat *mp4 = find_mp4_muxer();
switch (format) { if (!mp4) {
case RECORDER_FORMAT_MP4: return "mp4"; LOGE("Could not find mp4 muxer");
case RECORDER_FORMAT_MKV: return "matroska"; return SDL_FALSE;
default: return NULL;
}
}
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format);
SDL_assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
} }
recorder->ctx = avformat_alloc_context(); recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) { if (!recorder->ctx) {
LOGE("Could not allocate output context"); LOGE("Could not allocate output context");
return false; return SDL_FALSE;
} }
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly) // still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd> // <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format; recorder->ctx->oformat = (AVOutputFormat *) mp4;
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) { if (!ostream) {
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return false; return SDL_FALSE;
} }
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API #ifdef LAVF_NEW_CODEC_API
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id; ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->format = AV_PIX_FMT_YUV420P;
@@ -99,6 +87,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
ostream->codec->width = recorder->declared_frame_size.width; ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height; ostream->codec->height = recorder->declared_frame_size.height;
#endif #endif
ostream->time_base = (AVRational) {1, 1000000}; // timestamps in us
int ret = avio_open(&recorder->ctx->pb, recorder->filename, int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE); AVIO_FLAG_WRITE);
@@ -106,41 +95,34 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
LOGE("Failed to open output file: %s", recorder->filename); LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning // ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return false; return SDL_FALSE;
} }
LOGI("Recording started to %s file: %s", format_name, recorder->filename); return SDL_TRUE;
return true;
} }
void void recorder_close(struct recorder *recorder) {
recorder_close(struct recorder *recorder) {
int ret = av_write_trailer(recorder->ctx); int ret = av_write_trailer(recorder->ctx);
if (ret < 0) { if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename); LOGE("Failed to write trailer to %s", recorder->filename);
} }
avio_close(recorder->ctx->pb); avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
} }
static bool SDL_bool recorder_write_header(struct recorder *recorder, AVPacket *packet) {
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); uint8_t *extradata = SDL_malloc(packet->size * sizeof(uint8_t));
if (!extradata) { if (!extradata) {
LOGC("Cannot allocate extradata"); LOGC("Cannot allocate extradata");
return false; return SDL_FALSE;
} }
// copy the first packet to the extra data // copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size); memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API #ifdef LAVF_NEW_CODEC_API
ostream->codecpar->extradata = extradata; ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size; ostream->codecpar->extradata_size = packet->size;
#else #else
@@ -154,28 +136,20 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
SDL_free(extradata); SDL_free(extradata);
avio_closep(&recorder->ctx->pb); avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
static void SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) {
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
bool ok = recorder_write_header(recorder, packet); SDL_bool ok = recorder_write_header(recorder, packet);
if (!ok) { if (!ok) {
return false; return SDL_FALSE;
} }
recorder->header_written = true; recorder->header_written = SDL_TRUE;
} }
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0; return av_write_frame(recorder->ctx, packet) >= 0;
} }

View File

@@ -1,38 +1,25 @@
#ifndef RECORDER_H #ifndef RECORDER_H
#define RECORDER_H #define RECORDER_H
#include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_stdinc.h>
#include "common.h" #include "common.h"
enum recorder_format {
RECORDER_FORMAT_MP4 = 1,
RECORDER_FORMAT_MKV,
};
struct recorder { struct recorder {
char *filename; char *filename;
enum recorder_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
bool header_written; SDL_bool header_written;
}; };
bool SDL_bool recorder_init(struct recorder *recoder, const char *filename,
recorder_init(struct recorder *recoder, const char *filename, struct size declared_frame_size);
enum recorder_format format, struct size declared_frame_size); void recorder_destroy(struct recorder *recorder);
void SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec);
recorder_destroy(struct recorder *recorder); void recorder_close(struct recorder *recorder);
bool SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet);
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_write(struct recorder *recorder, AVPacket *packet);
#endif #endif

View File

@@ -14,6 +14,7 @@
#include "device.h" #include "device.h"
#include "events.h" #include "events.h"
#include "file_handler.h" #include "file_handler.h"
#include "frames.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "input_manager.h" #include "input_manager.h"
#include "log.h" #include "log.h"
@@ -22,59 +23,22 @@
#include "recorder.h" #include "recorder.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
static struct server server = SERVER_INITIALIZER; static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER;
static struct video_buffer video_buffer; static struct frames frames;
static struct stream stream;
static struct decoder decoder; static struct decoder decoder;
static struct recorder recorder;
static struct controller controller; static struct controller controller;
static struct file_handler file_handler; static struct file_handler file_handler;
static struct recorder recorder;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.video_buffer = &video_buffer, .frames = &frames,
.screen = &screen, .screen = &screen,
}; };
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!display) {
return true;
}
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return true;
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@@ -85,10 +49,8 @@ sdl_init_and_configure(bool display) {
// //
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077> // <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>
static int static int event_watcher(void *data, SDL_Event *event) {
event_watcher(void *data, SDL_Event *event) { if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround! // called from another thread, not very safe, but it's a workaround!
screen_render(&screen); screen_render(&screen);
} }
@@ -96,117 +58,75 @@ event_watcher(void *data, SDL_Event *event) {
} }
#endif #endif
static bool static SDL_bool is_apk(const char *file) {
is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk"); return ext && !strcmp(ext, ".apk");
} }
enum event_result { static SDL_bool event_loop(void) {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(SDL_Event *event, bool control) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return false;
}
break;
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key, control);
break;
case SDL_MOUSEMOTION:
if (!control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button,
control);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
file_handler_action_t action;
if (is_apk(event->drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event->drop.file);
break;
}
}
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(bool display, bool control) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
if (display) { SDL_AddEventWatch(event_watcher, NULL);
SDL_AddEventWatch(event_watcher, NULL);
}
#endif #endif
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, control); switch (event.type) {
switch (result) { case EVENT_DECODER_STOPPED:
case EVENT_RESULT_STOPPED_BY_USER: LOGD("Video decoder stopped");
return true; return SDL_FALSE;
case EVENT_RESULT_STOPPED_BY_EOS: case SDL_QUIT:
return false; LOGD("User requested to quit");
case EVENT_RESULT_CONTINUE: return SDL_TRUE;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
}
break; break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
input_manager_process_text_input(&input_manager, &event.text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
input_manager_process_key(&input_manager, &event.key);
break;
case SDL_MOUSEMOTION:
input_manager_process_mouse_motion(&input_manager, &event.motion);
break;
case SDL_MOUSEWHEEL:
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
case SDL_DROPFILE: {
file_handler_action_t action;
if (is_apk(event.drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event.drop.file);
break;
}
} }
} }
return false; return SDL_FALSE;
} }
static process_t static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
set_show_touches_enabled(const char *serial, bool enabled) {
const char *value = enabled ? "1" : "0"; const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = { const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value "shell", "settings", "put", "system", "show_touches", value
@@ -214,14 +134,12 @@ set_show_touches_enabled(const char *serial, bool enabled) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static void static void wait_show_touches(process_t process) {
wait_show_touches(process_t process) {
// reap the process, ignore the result // reap the process, ignore the result
process_check_success(process, "show_touches"); process_check_success(process, "show_touches");
} }
static SDL_LogPriority static SDL_LogPriority sdl_priority_from_av_level(int level) {
sdl_priority_from_av_level(int level) {
switch (level) { switch (level) {
case AV_LOG_PANIC: case AV_LOG_PANIC:
case AV_LOG_FATAL: case AV_LOG_FATAL:
@@ -255,77 +173,64 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
SDL_free(local_fmt); SDL_free(local_fmt);
} }
bool SDL_bool scrcpy(const struct scrcpy_options *options) {
scrcpy(const struct scrcpy_options *options) { SDL_bool send_frame_meta = !!options->record_filename;
bool record = !!options->record_filename;
if (!server_start(&server, options->serial, options->port, if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop, options->max_size, options->bit_rate, options->crop,
record)) { send_frame_meta)) {
return false; return SDL_FALSE;
} }
process_t proc_show_touches = PROCESS_NONE; process_t proc_show_touches = PROCESS_NONE;
bool show_touches_waited; SDL_bool show_touches_waited;
if (options->show_touches) { if (options->show_touches) {
LOGI("Enable show_touches"); LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, true); proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE);
show_touches_waited = false; show_touches_waited = SDL_FALSE;
} }
bool ret = true; SDL_bool ret = SDL_TRUE;
bool display = !options->no_display; if (!sdl_init_and_configure()) {
bool control = !options->no_control; ret = SDL_FALSE;
if (!sdl_init_and_configure(display)) {
ret = false;
goto finally_destroy_server; goto finally_destroy_server;
} }
socket_t device_socket = server_connect_to(&server); socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) { if (device_socket == INVALID_SOCKET) {
server_stop(&server); server_stop(&server);
ret = false; ret = SDL_FALSE;
goto finally_destroy_server; goto finally_destroy_server;
} }
char device_name[DEVICE_NAME_FIELD_LENGTH]; char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size; struct size frame_size;
// screenrecord does not send frames when the screen content does not // screenrecord does not send frames when the screen content does not change
// change therefore, we transmit the screen size before the video stream, // therefore, we transmit the screen size before the video stream, to be able
// to be able to init the window immediately // to init the window immediately
if (!device_read_info(device_socket, device_name, &frame_size)) { if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server); server_stop(&server);
ret = false; ret = SDL_FALSE;
goto finally_destroy_server; goto finally_destroy_server;
} }
struct decoder *dec = NULL; if (!frames_init(&frames)) {
if (display) { server_stop(&server);
if (!video_buffer_init(&video_buffer)) { ret = SDL_FALSE;
server_stop(&server); goto finally_destroy_server;
ret = false; }
goto finally_destroy_server;
}
if (control && !file_handler_init(&file_handler, server.serial)) { if (!file_handler_init(&file_handler, server.serial)) {
ret = false; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_video_buffer; goto finally_destroy_frames;
}
decoder_init(&decoder, &video_buffer);
dec = &decoder;
} }
struct recorder *rec = NULL; struct recorder *rec = NULL;
if (record) { if (options->record_filename) {
if (!recorder_init(&recorder, if (!recorder_init(&recorder, options->record_filename, frame_size)) {
options->record_filename, ret = SDL_FALSE;
options->record_format,
frame_size)) {
ret = false;
server_stop(&server); server_stop(&server);
goto finally_destroy_file_handler; goto finally_destroy_file_handler;
} }
@@ -334,78 +239,65 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, device_socket, dec, rec); decoder_init(&decoder, &frames, device_socket, rec);
// 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 stream // start the decoder
if (!stream_start(&stream)) { if (!decoder_start(&decoder)) {
ret = false; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_recorder; goto finally_destroy_recorder;
} }
if (display) { if (!controller_init(&controller, device_socket)) {
if (control) { ret = SDL_FALSE;
if (!controller_init(&controller, device_socket)) { goto finally_stop_decoder;
ret = false; }
goto finally_stop_stream;
}
if (!controller_start(&controller)) { if (!controller_start(&controller)) {
ret = false; ret = SDL_FALSE;
goto finally_destroy_controller; goto finally_destroy_controller;
} }
}
if (!screen_init_rendering(&screen, device_name, frame_size, if (!screen_init_rendering(&screen, device_name, frame_size, options->always_on_top)) {
options->always_on_top)) { ret = SDL_FALSE;
ret = false; goto finally_stop_and_join_controller;
goto finally_stop_and_join_controller;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
} }
if (options->show_touches) { if (options->show_touches) {
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
show_touches_waited = true; show_touches_waited = SDL_TRUE;
} }
ret = event_loop(display, control); if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen); screen_destroy(&screen);
finally_stop_and_join_controller: finally_stop_and_join_controller:
if (display && control) { controller_stop(&controller);
controller_stop(&controller); controller_join(&controller);
controller_join(&controller);
}
finally_destroy_controller: finally_destroy_controller:
if (display && control) { controller_destroy(&controller);
controller_destroy(&controller); finally_stop_decoder:
} decoder_stop(&decoder);
finally_stop_stream: // stop the server before decoder_join() to wake up the decoder
stream_stop(&stream);
// stop the server before stream_join() to wake up the stream
server_stop(&server); server_stop(&server);
stream_join(&stream); decoder_join(&decoder);
finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_recorder: finally_destroy_recorder:
if (record) { if (options->record_filename) {
recorder_destroy(&recorder); recorder_destroy(&recorder);
} }
finally_destroy_file_handler: finally_destroy_frames:
if (display && control) { frames_destroy(&frames);
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
}
finally_destroy_video_buffer:
if (display) {
video_buffer_destroy(&video_buffer);
}
finally_destroy_server: finally_destroy_server:
if (options->show_touches) { if (options->show_touches) {
if (!show_touches_waited) { if (!show_touches_waited) {
@@ -413,8 +305,7 @@ finally_destroy_server:
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }
LOGI("Disable show_touches"); LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE);
false);
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }

View File

@@ -1,26 +1,20 @@
#ifndef SCRCPY_H #ifndef SCRCPY_H
#define SCRCPY_H #define SCRCPY_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
#include <recorder.h>
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; Uint16 port;
uint16_t port; Uint16 max_size;
uint16_t max_size; Uint32 bit_rate;
uint32_t bit_rate; SDL_bool show_touches;
bool show_touches; SDL_bool fullscreen;
bool fullscreen; SDL_bool always_on_top;
bool always_on_top;
bool no_control;
bool no_display;
}; };
bool SDL_bool scrcpy(const struct scrcpy_options *options);
scrcpy(const struct scrcpy_options *options);
#endif #endif

View File

@@ -1,21 +1,43 @@
#include "screen.h" #include "screen.h"
#include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <string.h>
#include "common.h"
#include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
SDL_bool sdl_init_and_configure(void) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return SDL_FALSE;
}
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#if SDL_VERSION_ATLEAST(2, 0, 5)
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return SDL_TRUE;
}
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size get_native_window_size(SDL_Window *window) {
get_native_window_size(SDL_Window *window) {
int width; int width;
int height; int height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
@@ -27,8 +49,7 @@ get_native_window_size(SDL_Window *window) {
} }
// get the windowed window size // get the windowed window size
static struct size static struct size get_window_size(const struct screen *screen) {
get_window_size(const struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return screen->windowed_window_size; return screen->windowed_window_size;
} }
@@ -36,8 +57,7 @@ get_window_size(const struct screen *screen) {
} }
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void set_window_size(struct screen *screen, struct size new_size) {
set_window_size(struct screen *screen, struct size new_size) {
// setting the window size during fullscreen is implementation defined, // setting the window size during fullscreen is implementation defined,
// so apply the resize only after fullscreen is disabled // so apply the resize only after fullscreen is disabled
if (screen->fullscreen) { if (screen->fullscreen) {
@@ -49,31 +69,28 @@ set_window_size(struct screen *screen, struct size new_size) {
} }
// get the preferred display bounds (i.e. the screen bounds with some margins) // get the preferred display bounds (i.e. the screen bounds with some margins)
static bool static SDL_bool get_preferred_display_bounds(struct size *bounds) {
get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect; SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS #if SDL_VERSION_ATLEAST(2, 0, 5)
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else #else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif #endif
if (GET_DISPLAY_BOUNDS(0, &rect)) { if (GET_DISPLAY_BOUNDS(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError()); LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS); bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS); bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
return true; return SDL_TRUE;
} }
// return the optimal size of the window, with the following constraints: // return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it // - it attempts to keep at least one dimension of the current_size (i.e. it crops the black borders)
// crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size static struct size get_optimal_size(struct size current_size, struct size frame_size) {
get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) { if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
@@ -81,8 +98,8 @@ get_optimal_size(struct size current_size, struct size frame_size) {
struct size display_size; struct size display_size;
// 32 bits because we need to multiply two 16 bits values // 32 bits because we need to multiply two 16 bits values
uint32_t w; Uint32 w;
uint32_t h; Uint32 h;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// cannot get display bounds, do not constraint the size // cannot get display bounds, do not constraint the size
@@ -93,13 +110,12 @@ get_optimal_size(struct size current_size, struct size frame_size) {
h = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
bool keep_width = frame_size.width * h > frame_size.height * w; SDL_bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
h = frame_size.height * w / frame_size.width; h = frame_size.height * w / frame_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already // remove black borders on left and right (or none at all if it already fits)
// fits)
w = frame_size.width * h / frame_size.height; w = frame_size.width * h / frame_size.height;
} }
@@ -109,101 +125,85 @@ get_optimal_size(struct size current_size, struct size frame_size) {
} }
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size get_optimal_window_size(const struct screen *screen, struct size frame_size) {
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
return get_optimal_size(current_size, frame_size); return get_optimal_size(current_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
static inline struct size static inline struct size get_initial_optimal_size(struct size frame_size) {
get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size); return get_optimal_size(frame_size, frame_size);
} }
void void screen_init(struct screen *screen) {
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
} }
static inline SDL_Texture * static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) {
create_texture(SDL_Renderer *renderer, struct size frame_size) { return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height); frame_size.width, frame_size.height);
} }
bool SDL_bool screen_init_rendering(struct screen *screen,
screen_init_rendering(struct screen *screen, const char *device_name, const char *device_name,
struct size frame_size, bool always_on_top) { struct size frame_size,
SDL_bool always_on_top) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size); struct size window_size = get_initial_optimal_size(frame_size);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif #endif
if (always_on_top) { if (always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
} }
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, window_size.width, window_size.height, window_flags);
window_size.width, window_size.height,
window_flags);
if (!screen->window) { if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError()); LOGC("Could not create window: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
screen->renderer = SDL_CreateRenderer(screen->window, -1, screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED);
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (!icon) { if (!icon) {
LOGE("Could not load icon: %s", SDL_GetError()); LOGE("Could not load icon: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon); SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size); screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void screen_show_window(struct screen *screen) {
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void void screen_destroy(struct screen *screen) {
screen_destroy(struct screen *screen) {
if (screen->texture) { if (screen->texture) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
} }
@@ -216,14 +216,11 @@ screen_destroy(struct screen *screen) {
} }
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
prepare_for_frame(struct screen *screen, struct size new_frame_size) { if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) {
if (screen->frame_size.width != new_frame_size.width if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) {
|| screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@@ -231,67 +228,61 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) current_size.width * new_frame_size.width (Uint32) current_size.width * new_frame_size.width / screen->frame_size.width,
/ screen->frame_size.width, (Uint32) current_size.height * new_frame_size.height / screen->frame_size.height,
(uint32_t) current_size.height * new_frame_size.height
/ screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
screen->frame_size = new_frame_size; screen->frame_size = new_frame_size;
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGD("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size); screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
} }
return true; return SDL_TRUE;
} }
// write the frame into the texture // write the frame into the texture
static void static void update_texture(struct screen *screen, const AVFrame *frame) {
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL, SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
} }
bool SDL_bool screen_update_frame(struct screen *screen, struct frames *frames) {
screen_update_frame(struct screen *screen, struct video_buffer *vb) { mutex_lock(frames->mutex);
mutex_lock(vb->mutex); const AVFrame *frame = frames_consume_rendered_frame(frames);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
return false; return SDL_FALSE;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
screen_render(screen); screen_render(screen);
return true; return SDL_TRUE;
} }
void void screen_render(struct screen *screen) {
screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
void void screen_switch_fullscreen(struct screen *screen) {
screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size // going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window); screen->windowed_window_size = get_native_window_size(screen->window);
} }
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; Uint32 new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
return; return;
@@ -300,30 +291,24 @@ screen_switch_fullscreen(struct screen *screen) {
screen->fullscreen = !screen->fullscreen; screen->fullscreen = !screen->fullscreen;
if (!screen->fullscreen) { if (!screen->fullscreen) {
// fullscreen disabled, restore expected windowed window size // fullscreen disabled, restore expected windowed window size
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, screen->windowed_window_size.height);
screen->windowed_window_size.height);
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen); screen_render(screen);
} }
void void screen_resize_to_fit(struct screen *screen) {
screen_resize_to_fit(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
struct size optimal_size = get_optimal_window_size(screen, struct size optimal_size = get_optimal_window_size(screen, screen->frame_size);
screen->frame_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
LOGD("Resized to optimal size"); LOGD("Resized to optimal size");
} }
} }
void void screen_resize_to_pixel_perfect(struct screen *screen) {
screen_resize_to_pixel_perfect(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
SDL_SetWindowSize(screen->window, screen->frame_size.width, SDL_SetWindowSize(screen->window, screen->frame_size.width, screen->frame_size.height);
screen->frame_size.height);
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }

View File

@@ -1,13 +1,11 @@
#ifndef SCREEN_H #ifndef SCREEN_H
#define SCREEN_H #define SCREEN_H
#include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "common.h" #include "common.h"
#include "frames.h"
struct video_buffer;
struct screen { struct screen {
SDL_Window *window; SDL_Window *window;
@@ -16,9 +14,8 @@ struct screen {
struct size frame_size; struct size frame_size;
//used only in fullscreen mode to know the windowed window size //used only in fullscreen mode to know the windowed window size
struct size windowed_window_size; struct size windowed_window_size;
bool has_frame; SDL_bool has_frame;
bool fullscreen; SDL_bool fullscreen;
bool no_window;
}; };
#define SCREEN_INITIALIZER { \ #define SCREEN_INITIALIZER { \
@@ -33,46 +30,41 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.has_frame = false, \ .has_frame = SDL_FALSE, \
.fullscreen = false, \ .fullscreen = SDL_FALSE, \
.no_window = false, \
} }
// init SDL and set appropriate hints
SDL_bool sdl_init_and_configure(void);
// initialize default values // initialize default values
void void screen_init(struct screen *screen);
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool SDL_bool screen_init_rendering(struct screen *screen,
screen_init_rendering(struct screen *screen, const char *device_name, const char *device_name,
struct size frame_size, bool always_on_top); struct size frame_size,
SDL_bool always_on_top);
// show the window // show the window
void void screen_show_window(struct screen *screen);
screen_show_window(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void screen_destroy(struct screen *screen);
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture // resize if necessary and write the rendered frame into the texture
bool SDL_bool screen_update_frame(struct screen *screen, struct frames *frames);
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
void void screen_render(struct screen *screen);
screen_render(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void screen_switch_fullscreen(struct screen *screen);
screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders) // resize window to optimal size (remove black borders)
void void screen_resize_to_fit(struct screen *screen);
screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void void screen_resize_to_pixel_perfect(struct screen *screen);
screen_resize_to_pixel_perfect(struct screen *screen);
#endif #endif

View File

@@ -2,6 +2,7 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
@@ -20,8 +21,7 @@
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static const char * static const char *get_server_path(void) {
get_server_path(void) {
const char *server_path = getenv("SCRCPY_SERVER_PATH"); const char *server_path = getenv("SCRCPY_SERVER_PATH");
if (!server_path) { if (!server_path) {
server_path = DEFAULT_SERVER_PATH; server_path = DEFAULT_SERVER_PATH;
@@ -29,60 +29,52 @@ get_server_path(void) {
return server_path; return server_path;
} }
static bool static SDL_bool push_server(const char *serial) {
push_server(const char *serial) {
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH); process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
return process_check_success(process, "adb push"); return process_check_success(process, "adb push");
} }
static bool static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse"); return process_check_success(process, "adb reverse");
} }
static bool static SDL_bool disable_tunnel_reverse(const char *serial) {
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove"); return process_check_success(process, "adb reverse --remove");
} }
static bool static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward"); return process_check_success(process, "adb forward");
} }
static bool static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) {
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove"); return process_check_success(process, "adb forward --remove");
} }
static bool static SDL_bool enable_tunnel(struct server *server) {
enable_tunnel(struct server *server) {
if (enable_tunnel_reverse(server->serial, server->local_port)) { if (enable_tunnel_reverse(server->serial, server->local_port)) {
return true; return SDL_TRUE;
} }
LOGW("'adb reverse' failed, fallback to 'adb forward'"); LOGW("'adb reverse' failed, fallback to 'adb forward'");
server->tunnel_forward = true; server->tunnel_forward = SDL_TRUE;
return enable_tunnel_forward(server->serial, server->local_port); return enable_tunnel_forward(server->serial, server->local_port);
} }
static bool static SDL_bool disable_tunnel(struct server *server) {
disable_tunnel(struct server *server) {
if (server->tunnel_forward) { if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port); return disable_tunnel_forward(server->serial, server->local_port);
} }
return disable_tunnel_reverse(server->serial); return disable_tunnel_reverse(server->serial);
} }
static process_t static process_t execute_server(const char *serial,
execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate,
uint16_t max_size, uint32_t bit_rate, SDL_bool tunnel_forward, const char *crop,
bool tunnel_forward, const char *crop, SDL_bool send_frame_meta) {
bool send_frame_meta) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size); sprintf(max_size_string, "%"PRIu16, max_size);
@@ -104,13 +96,11 @@ execute_server(const char *serial,
#define IPV4_LOCALHOST 0x7F000001 #define IPV4_LOCALHOST 0x7F000001
static socket_t static socket_t listen_on_port(Uint16 port) {
listen_on_port(uint16_t port) {
return net_listen(IPV4_LOCALHOST, port, 1); return net_listen(IPV4_LOCALHOST, port, 1);
} }
static socket_t static socket_t connect_and_read_byte(Uint16 port) {
connect_and_read_byte(uint16_t port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port); socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) { if (socket == INVALID_SOCKET) {
return INVALID_SOCKET; return INVALID_SOCKET;
@@ -126,8 +116,7 @@ connect_and_read_byte(uint16_t port) {
return socket; return socket;
} }
static socket_t static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
do { do {
LOGD("Remaining connection attempts: %d", (int) attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port); socket_t socket = connect_and_read_byte(port);
@@ -142,8 +131,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
static void static void close_socket(socket_t *socket) {
close_socket(socket_t *socket) {
SDL_assert(*socket != INVALID_SOCKET); SDL_assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR); net_shutdown(*socket, SHUT_RDWR);
if (!net_close(*socket)) { if (!net_close(*socket)) {
@@ -153,32 +141,30 @@ close_socket(socket_t *socket) {
*socket = INVALID_SOCKET; *socket = INVALID_SOCKET;
} }
void void server_init(struct server *server) {
server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER; *server = (struct server) SERVER_INITIALIZER;
} }
bool SDL_bool server_start(struct server *server, const char *serial,
server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate, const char *crop, SDL_bool send_frame_meta) {
const char *crop, bool send_frame_meta) {
server->local_port = local_port; server->local_port = local_port;
if (serial) { if (serial) {
server->serial = SDL_strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) { if (!server->serial) {
return false; return SDL_FALSE;
} }
} }
if (!push_server(serial)) { if (!push_server(serial)) {
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
if (!enable_tunnel(server)) { if (!enable_tunnel(server)) {
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
@@ -187,16 +173,15 @@ server_start(struct server *server, const char *serial,
// At the application level, the device part is "the server" because it // At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the // serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the // client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no // client can listen before starting the server app, so there is no need to
// need to try to connect until the server socket is listening on the // try to connect until the server socket is listening on the device.
// device.
server->server_socket = listen_on_port(local_port); server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) { if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port); LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server); disable_tunnel(server);
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
} }
@@ -211,23 +196,21 @@ server_start(struct server *server, const char *serial,
} }
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
server->tunnel_enabled = true; server->tunnel_enabled = SDL_TRUE;
return true; return SDL_TRUE;
} }
socket_t socket_t server_connect_to(struct server *server) {
server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket); server->device_socket = net_accept(server->server_socket);
} else { } else {
uint32_t attempts = 100; Uint32 attempts = 100;
uint32_t delay = 100; // ms Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, server->device_socket = connect_to_server(server->local_port, attempts, delay);
delay);
} }
if (server->device_socket == INVALID_SOCKET) { if (server->device_socket == INVALID_SOCKET) {
@@ -241,13 +224,12 @@ server_connect_to(struct server *server) {
// we don't need the adb tunnel anymore // we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure disable_tunnel(server); // ignore failure
server->tunnel_enabled = false; server->tunnel_enabled = SDL_FALSE;
return server->device_socket; return server->device_socket;
} }
void void server_stop(struct server *server) {
server_stop(struct server *server) {
SDL_assert(server->process != PROCESS_NONE); SDL_assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) { if (!cmd_terminate(server->process)) {
@@ -263,13 +245,12 @@ server_stop(struct server *server) {
} }
} }
void void server_destroy(struct server *server) {
server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) { if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
if (server->device_socket != INVALID_SOCKET) { if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket); close_socket(&server->device_socket);
} }
SDL_free(server->serial); SDL_free((void *) server->serial);
} }

View File

@@ -1,21 +1,18 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include <stdbool.h>
#include <stdint.h>
#include "command.h" #include "command.h"
#include "net.h" #include "net.h"
struct server { struct server {
char *serial; const char *serial;
process_t process; process_t process;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t device_socket; socket_t device_socket;
uint16_t local_port; Uint16 local_port;
bool tunnel_enabled; SDL_bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
bool send_frame_meta; // request frame PTS to be able to record properly SDL_bool send_frame_meta; // request frame PTS to be able to record properly
}; };
#define SERVER_INITIALIZER { \ #define SERVER_INITIALIZER { \
@@ -24,31 +21,26 @@ struct server {
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \
.local_port = 0, \ .local_port = 0, \
.tunnel_enabled = false, \ .tunnel_enabled = SDL_FALSE, \
.tunnel_forward = false, \ .tunnel_forward = SDL_FALSE, \
.send_frame_meta = false, \ .send_frame_meta = SDL_FALSE, \
} }
// init default values // init default values
void void server_init(struct server *server);
server_init(struct server *server);
// push, enable tunnel et start the server // push, enable tunnel et start the server
bool SDL_bool server_start(struct server *server, const char *serial,
server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate, const char *crop, SDL_bool send_frame_meta);
const char *crop, bool send_frame_meta);
// block until the communication with the server is established // block until the communication with the server is established
socket_t socket_t server_connect_to(struct server *server);
server_connect_to(struct server *server);
// disconnect and kill the server process // disconnect and kill the server process
void void server_stop(struct server *server);
server_stop(struct server *server);
// close and release sockets // close and release sockets
void void server_destroy(struct server *server);
server_destroy(struct server *server);
#endif #endif

View File

@@ -3,13 +3,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifdef _WIN32 size_t xstrncpy(char *dest, const char *src, size_t n) {
# include <windows.h>
# include <tchar.h>
#endif
size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i) for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i]; dest[i] = src[i];
@@ -18,8 +12,7 @@ xstrncpy(char *dest, const char *src, size_t n) {
return src[i] == '\0' ? i : n; return src[i] == '\0' ? i : n;
} }
size_t size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens; const char *const *remaining = tokens;
const char *token = *remaining++; const char *token = *remaining++;
size_t i = 0; size_t i = 0;
@@ -42,8 +35,7 @@ truncated:
return n; return n;
} }
char * char *strquote(const char *src) {
strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
@@ -55,23 +47,3 @@ strquote(const char *src) {
quoted[len + 2] = '\0'; quoted[len + 2] = '\0';
return quoted; return quoted;
} }
#ifdef _WIN32
wchar_t *
utf8_to_wide_char(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) {
return NULL;
}
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len);
return wide;
}
#endif

View File

@@ -9,25 +9,15 @@
// - it does not write useless bytes if strlen(src) < n // - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has // - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated // been copied completely, or n if src has been truncated
size_t size_t xstrncpy(char *dest, const char *src, size_t n);
xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst // join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no trucation // returns the number of chars actually written (max n-1) if no trucation
// occurred, or n if truncated // occurred, or n if truncated
size_t size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string // quote a string
// returns the new allocated string, to be freed by the caller // returns the new allocated string, to be freed by the caller
char * char *strquote(const char *src);
strquote(const char *src);
#ifdef _WIN32
// convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
#endif
#endif #endif

View File

@@ -1,286 +0,0 @@
#include "stream.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "compat.h"
#include "config.h"
#include "buffer_util.h"
#include "decoder.h"
#include "events.h"
#include "lock_util.h"
#include "log.h"
#include "recorder.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *
frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void
frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static bool
receiver_state_push_meta(struct receiver_state *state, uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return false;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return true;
}
static uint64_t
receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int
read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
struct receiver_state *state = &stream->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining) {
buf_size = state->remaining;
}
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int
read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVFormatContext *format_ctx = avformat_alloc_context();
if (!format_ctx) {
LOGC("Could not allocate format context");
goto end;
}
unsigned char *buffer = av_malloc(BUFSIZE);
if (!buffer) {
LOGC("Could not allocate buffer");
goto finally_free_format_ctx;
}
// initialize the receiver state
stream->receiver_state.frame_meta_queue = NULL;
stream->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
stream->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, stream,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto finally_free_avio_ctx;
}
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
LOGE("Could not open decoder");
goto finally_close_input;
}
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
if (stream->decoder && !decoder_push(stream->decoder, &packet)) {
av_packet_unref(&packet);
goto quit;
}
if (stream->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&stream->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(stream->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
quit:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_input:
avformat_close_input(&format_ctx);
finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
finally_free_format_ctx:
avformat_free_context(format_ctx);
end:
notify_stopped();
return 0;
}
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
}

View File

@@ -1,43 +0,0 @@
#ifndef STREAM_H
#define STREAM_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "net.h"
struct video_buffer;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
bool
stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@@ -11,8 +11,7 @@
#include <unistd.h> #include <unistd.h>
#include "log.h" #include "log.h"
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2]; int fd[2];
if (pipe(fd) == -1) { if (pipe(fd) == -1) {
@@ -73,18 +72,15 @@ end:
return ret; return ret;
} }
bool SDL_bool cmd_terminate(pid_t pid) {
cmd_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid);
(int) pid);
abort(); abort();
} }
return kill(pid, SIGTERM) != -1; return kill(pid, SIGTERM) != -1;
} }
bool SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
cmd_simple_wait(pid_t pid, int *exit_code) {
int status; int status;
int code; int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {

View File

@@ -1,19 +1,16 @@
#include "net.h" #include "net.h"
#include <unistd.h> # include <unistd.h>
bool SDL_bool net_init(void) {
net_init(void) {
// do nothing // do nothing
return true; return SDL_TRUE;
} }
void void net_cleanup(void) {
net_cleanup(void) {
// do nothing // do nothing
} }
bool SDL_bool net_close(socket_t socket) {
net_close(socket_t socket) {
return !close(socket); return !close(socket);
} }

View File

@@ -4,8 +4,7 @@
#include "log.h" #include "log.h"
#include "str_util.h" #include "str_util.h"
static int static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF: // Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS> // <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program // only make it work for this very specific program
@@ -18,9 +17,8 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
return 0; return 0;
} }
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { STARTUPINFO si;
STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
@@ -31,20 +29,12 @@ cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
wchar_t *wide = utf8_to_wide_char(cmd);
if (!wide) {
LOGC("Cannot allocate wide char string");
return PROCESS_ERROR_GENERIC;
}
#ifdef WINDOWS_NOCONSOLE #ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW; int flags = CREATE_NO_WINDOW;
#else #else
int flags = 0; int flags = 0;
#endif #endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
&pi)) {
free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY; return PROCESS_ERROR_MISSING_BINARY;
@@ -52,21 +42,17 @@ cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
bool SDL_bool cmd_terminate(HANDLE handle) {
cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle); return TerminateProcess(handle, 1) && CloseHandle(handle);
} }
bool SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) {
|| !GetExitCodeProcess(handle, &code)) {
// cannot wait or retrieve the exit code // cannot wait or retrieve the exit code
code = -1; // max value, it's unsigned code = -1; // max value, it's unsigned
} }

View File

@@ -2,23 +2,20 @@
#include "log.h" #include "log.h"
bool SDL_bool net_init(void) {
net_init(void) {
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
LOGC("WSAStartup failed with error %d", res); LOGC("WSAStartup failed with error %d", res);
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void net_cleanup(void) {
net_cleanup(void) {
WSACleanup(); WSACleanup();
} }
bool SDL_bool net_close(socket_t socket) {
net_close(socket_t socket) {
return !closesocket(socket); return !closesocket(socket);
} }

View File

@@ -1,7 +1,5 @@
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -9,20 +7,19 @@
struct index { struct index {
char c; char c;
uint32_t color; Uint32 color;
}; };
static bool static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) {
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array // there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
if (index[i].c == c) { if (index[i].c == c) {
*color = index[i].color; *color = index[i].color;
return true; return SDL_TRUE;
} }
} }
*color = 0; *color = 0;
return false; return SDL_FALSE;
} }
// We encounter some problems with SDL2_image on MSYS2 (Windows), // We encounter some problems with SDL2_image on MSYS2 (Windows),
@@ -33,8 +30,7 @@ find_color(struct index *index, int len, char c, uint32_t *color) {
// //
// Parameter is not "const char *" because XPM formats are generally stored in a // Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *" // (non-const) "char *"
SDL_Surface * SDL_Surface *read_xpm(char *xpm[]) {
read_xpm(char *xpm[]) {
#if SDL_ASSERT_LEVEL >= 2 #if SDL_ASSERT_LEVEL >= 2
// patch the XPM to change the icon color in debug mode // patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC"; xpm[2] = ". c #CC00CC";
@@ -73,7 +69,7 @@ read_xpm(char *xpm[]) {
} }
// parse image // parse image
uint32_t *pixels = SDL_malloc(4 * width * height); Uint32 *pixels = SDL_malloc(4 * width * height);
if (!pixels) { if (!pixels) {
LOGE("Could not allocate icon memory"); LOGE("Could not allocate icon memory");
return NULL; return NULL;
@@ -82,23 +78,23 @@ read_xpm(char *xpm[]) {
const char *line = xpm[1 + colors + y]; const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
char c = line[x]; char c = line[x];
uint32_t color; Uint32 color;
bool color_found = find_color(index, colors, c, &color); SDL_bool color_found = find_color(index, colors, c, &color);
SDL_assert(color_found); SDL_assert(color_found);
pixels[y * width + x] = color; pixels[y * width + x] = color;
} }
} }
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff; Uint32 amask = 0x000000ff;
uint32_t rmask = 0x0000ff00; Uint32 rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000; Uint32 gmask = 0x00ff0000;
uint32_t bmask = 0xff000000; Uint32 bmask = 0xff000000;
#else // little endian, like x86 #else // little endian, like x86
uint32_t amask = 0xff000000; Uint32 amask = 0xff000000;
uint32_t rmask = 0x00ff0000; Uint32 rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00; Uint32 gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff; Uint32 bmask = 0x000000ff;
#endif #endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels, SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
SDL_Surface * SDL_Surface *read_xpm(char *xpm[]);
read_xpm(char *xpm[]);
#endif #endif

View File

@@ -1,116 +0,0 @@
#include "video_buffer.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
bool
video_buffer_init(struct video_buffer *vb) {
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
goto error_2;
}
vb->interrupted = false;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);
return true;
error_2:
av_frame_free(&vb->rendering_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
mutex_lock(vb->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
#else
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
#endif
video_buffer_swap_frames(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
mutex_unlock(vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(vb->rendering_frame_consumed_cond);
#endif
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
#ifdef SKIP_FRAMES
(void) vb; // unused
#else
mutex_lock(vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
#endif
}

View File

@@ -87,13 +87,13 @@ static void test_serialize_mouse_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf); int size = control_event_serialize(&event, buf);
assert(size == 18); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
0x02, // CONTROL_EVENT_TYPE_MOUSE 0x02, // CONTROL_EVENT_TYPE_MOUSE
0x00, // AKEY_EVENT_ACTION_DOWN 0x00, // AKEY_EVENT_ACTION_DOWN
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY 0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@@ -120,11 +120,11 @@ static void test_serialize_scroll_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf); int size = control_event_serialize(&event, buf);
assert(size == 21); assert(size == 17);
const unsigned char expected[] = { const unsigned char expected[] = {
0x03, // CONTROL_EVENT_TYPE_SCROLL 0x03, // CONTROL_EVENT_TYPE_SCROLL
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1 0x00, 0x00, 0x00, 0x01, // 1
0xFF, 0xFF, 0xFF, 0xFF, // -1 0xFF, 0xFF, 0xFF, 0xFF, // -1

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.8', version: '1.6',
meson_version: '>= 0.37', meson_version: '>= 0.37',
default_options: 'c_std=c11') default_options: 'c_std=c11')

View File

@@ -1,35 +0,0 @@
#!/bin/bash
set -e
# build and test locally
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
ninja test
cd -
# build Windows releases
make -f Makefile.CrossWindows
# the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win32/scrcpy-server.jar
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win64/scrcpy-server.jar
# get version name
TAG=$(git describe --tags --always)
# create release directory
mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server.jar" "release-$TAG/scrcpy-server-$TAG.jar"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums
cd "release-$TAG"
sha256sum "scrcpy-server-$TAG.jar" \
"scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
echo "Release generated in release-$TAG/"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 27
versionCode 9 versionCode 7
versionName "1.8" versionName "1.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -10,10 +10,6 @@ if prebuilt_server == ''
install: true, install: true,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')
else else
if not prebuilt_server.startswith('/')
# relative path needs some trick
prebuilt_server = meson.source_root() + '/' + prebuilt_server
endif
custom_target('scrcpy-server-prebuilt', custom_target('scrcpy-server-prebuilt',
input: prebuilt_server, input: prebuilt_server,
output: 'scrcpy-server.jar', output: 'scrcpy-server.jar',

View File

@@ -12,8 +12,6 @@ public final class ControlEvent {
public static final int TYPE_COMMAND = 4; public static final int TYPE_COMMAND = 4;
public static final int COMMAND_BACK_OR_SCREEN_ON = 0; public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1;
public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2;
private int type; private int type;
private String text; private String text;

View File

@@ -132,14 +132,6 @@ public final class Device {
this.rotationListener = rotationListener; this.rotationListener = rotationListener;
} }
public void expandNotificationPanel() {
serviceManager.getStatusBarManager().expandNotificationsPanel();
}
public void collapsePanels() {
serviceManager.getStatusBarManager().collapsePanels();
}
static Rect flipRect(Rect crop) { static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right); return new Rect(crop.top, crop.left, crop.bottom, crop.right);
} }

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point; import android.graphics.Point;
import android.os.SystemClock; import android.os.SystemClock;
@@ -173,12 +172,6 @@ public class EventController {
switch (action) { switch (action) {
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
return pressBackOrTurnScreenOn(); return pressBackOrTurnScreenOn();
case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel();
return true;
case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
return true;
default: default:
Ln.w("Unsupported command: " + action); Ln.w("Unsupported command: " + action);
} }

View File

@@ -57,8 +57,6 @@ public class ScreenEncoder implements Device.RotationListener {
public void streamScreen(Device device, FileDescriptor fd) throws IOException { public void streamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
IBinder d = SurfaceControl.getBuiltInDisplay(0);
SurfaceControl.setDisplayPowerMode(d, SurfaceControl.POWER_MODE_OFF);
boolean alive; boolean alive;
try { try {
do { do {
@@ -89,6 +87,7 @@ public class ScreenEncoder implements Device.RotationListener {
boolean eof = false; boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) { while (!consumeRotationChange() && !eof) {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;

View File

@@ -14,7 +14,6 @@ public final class ServiceManager {
private DisplayManager displayManager; private DisplayManager displayManager;
private InputManager inputManager; private InputManager inputManager;
private PowerManager powerManager; private PowerManager powerManager;
private StatusBarManager statusBarManager;
public ServiceManager() { public ServiceManager() {
try { try {
@@ -61,11 +60,4 @@ public final class ServiceManager {
} }
return powerManager; return powerManager;
} }
public StatusBarManager getStatusBarManager() {
if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
}
return statusBarManager;
}
} }

View File

@@ -1,41 +0,0 @@
package com.genymobile.scrcpy.wrappers;
import android.annotation.SuppressLint;
import android.os.IInterface;
import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StatusBarManager {
private final IInterface manager;
private final Method expandNotificationsPanelMethod;
private final Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) {
this.manager = manager;
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public void expandNotificationsPanel() {
try {
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public void collapsePanels() {
try {
collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -10,11 +10,6 @@ public final class SurfaceControl {
private static final Class<?> CLASS; private static final Class<?> CLASS;
public static final int POWER_MODE_OFF = 0;
public static final int POWER_MODE_DOZE = 1;
public static final int POWER_MODE_NORMAL = 2;
public static final int POWER_MODE_DOZE_SUSPEND = 3;
static { static {
try { try {
CLASS = Class.forName("android.view.SurfaceControl"); CLASS = Class.forName("android.view.SurfaceControl");
@@ -76,22 +71,6 @@ public final class SurfaceControl {
} }
} }
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
try {
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
try {
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void destroyDisplay(IBinder displayToken) { public static void destroyDisplay(IBinder displayToken) {
try { try {
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken); CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);