diff --git a/BUILD.md b/BUILD.md index d61518dc..a9a2e4a2 100644 --- a/BUILD.md +++ b/BUILD.md @@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.7.jar`][direct-scrcpy-server] - _(SHA-256: ee86ec8424f7dc50cacdf927312bdb46e0aa0d68611da584dc4b16d8057bc25e)_ + - [`scrcpy-server-v1.8.jar`][direct-scrcpy-server] + _(SHA-256: 839055ef905903bf98ead1b9b8a127fe402b39ad657a81f9a914b2dbcb2ce5c0)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-server-v1.7.jar +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-server-v1.8.jar Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/DEVELOP.md b/DEVELOP.md index 63d65b27..38c9c63e 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -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 `shell` on the Android device. -[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61 +[main]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100 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 @@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden components is provided by [_wrappers_ classes][wrappers] and [aidl]. [hidden]: https://stackoverflow.com/a/31908373/1987178 -[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.0/server/src/main/aidl/android/view +[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers +[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view ### 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 stream to the provided output stream (the socket connected to the client). -[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java [`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html -[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64 +[surface]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70 On device [rotation], the codec, surface and display are reinitialized, and a new video stream is produced. @@ -105,8 +105,9 @@ because it avoids to send unnecessary frames, but there are drawbacks: Both problems are [solved][repeat] by the flag [`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag]. -[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92 -[repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126 +[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90 +[repeat]: +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 @@ -124,11 +125,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 [`InputManager` wrapper][inject-wrapper]). -[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70 +[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66 [`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.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 -[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27 +[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27 @@ -153,7 +154,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 client _controls_ the device through the server. -However, the roles are inverted at the network level: +However, the roles are reversed at the network level: - the client opens a server socket and listen on a port before starting the server, @@ -162,6 +163,9 @@ However, the roles are inverted at the network level: This role inversion guarantees that the connection will not fail due to race 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 screen dimensions). Thus, the client may init the window and renderer, before the first frame is available. @@ -169,6 +173,8 @@ the first frame is available. To minimize startup time, SDL initialization is performed while listening for 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 @@ -177,17 +183,25 @@ the connection from the server (see commit [90a46b4]). The client uses 3 threads: - the **main** thread, executing the SDL event loop, - - the **decoder** thread, decoding video frames, + - the **stream** thread, receiving the video and used for decoding and + recording, - 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. -There are two [frames] simultaneously in memory: +### Stream + +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 **rendering** frame, rendered in a texture from the main thread. @@ -195,9 +209,23 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and rendering frame (with proper synchronization). Thus, it immediatly starts to decode a new frame while the main thread renders the last one. -[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c -[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h +If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw +H.264 packet to the output video file. +[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 @@ -211,10 +239,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 client. -[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h -[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h -[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h -[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h +[controller]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/controller.h +[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/control_event.h +[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/input_manager.h +[convert]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/convert.h ### UI and event loop @@ -225,9 +253,10 @@ thread. Events are handled in the [event loop], which either updates the [screen] or delegates to the [input manager][inputmanager]. -[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c -[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38 -[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h +[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c +[event loop]: +https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c#L187 +[screen]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/screen.h ## Hack diff --git a/README.md b/README.md index 1fdb2c83..7bf203ba 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.7) +# scrcpy (v1.8) 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. @@ -29,12 +29,10 @@ control it using keyboard and mouse. On Linux, you typically need to [build the app manually][BUILD]. Don't worry, it's not that hard. -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/) +For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link]. [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]. @@ -47,13 +45,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. For Windows, for simplicity, prebuilt archives with all the dependencies (including `adb`) are available: - - [`scrcpy-win32-v1.7.zip`][direct-win32] - _(SHA-256: 98ae36f2da0b8212c07066fd93139650554274f863d4cee0781501a0c84f7c23)_ - - [`scrcpy-win64-v1.7.zip`][direct-win64] - _(SHA-256: b41416547521062f19e3f3f539e89a70e713bd086e69ef1b29c128993f7aa462)_ + - [`scrcpy-win32-v1.8.zip`][direct-win32] + _(SHA-256: c0c29ed1c66deaa73bdadacd09e598aafb3a117929cf7a314cce1cc45e34de53)_ + - [`scrcpy-win64-v1.8.zip`][direct-win64] + _(SHA-256: 9cc980d07bd8f036ae4e91d0bc6fc3281d7fa8f9752d4913b643c0fb72a19fb7)_ -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win32-v1.7.zip -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win64-v1.7.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win32-v1.8.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win64-v1.8.zip You can also [build the app manually][BUILD]. @@ -165,6 +163,15 @@ scrcpy --record file.mp4 scrcpy -r file.mkv ``` +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 performance reasons). Frames are _timestamped_ on the device, so [packet delay variation] does not impact the recorded file. @@ -239,6 +246,17 @@ _scrcpy_ window. 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 Audio is not forwarded by _scrcpy_. @@ -267,6 +285,8 @@ you are interested, see [issue 14]. | click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) | | click on `POWER` | `Ctrl`+`p` | | turn screen on | _Right-click²_ | + | expand notification panel | `Ctrl`+`n` | + | collapse notification panel | `Ctrl`+`Shift`+`n` | | paste computer clipboard to device | `Ctrl`+`v` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | diff --git a/app/meson.build b/app/meson.build index 3286fde1..5942fd08 100644 --- a/app/meson.build +++ b/app/meson.build @@ -8,7 +8,6 @@ src = [ 'src/device.c', 'src/file_handler.c', 'src/fps_counter.c', - 'src/frames.c', 'src/input_manager.c', 'src/lock_util.c', 'src/net.c', @@ -18,6 +17,8 @@ src = [ 'src/server.c', 'src/str_util.c', 'src/tiny_xpm.c', + 'src/stream.c', + 'src/video_buffer.c', ] if not get_option('crossbuild_windows') diff --git a/app/src/buffer_util.h b/app/src/buffer_util.h index cfb3fa12..5d94deef 100644 --- a/app/src/buffer_util.h +++ b/app/src/buffer_util.h @@ -1,28 +1,33 @@ #ifndef BUFFER_UTIL_H #define BUFFER_UTIL_H -#include +#include +#include -static inline void buffer_write16be(Uint8 *buf, Uint16 value) { +static inline void +buffer_write16be(uint8_t *buf, uint16_t value) { buf[0] = value >> 8; buf[1] = value; } -static inline void buffer_write32be(Uint8 *buf, Uint32 value) { +static inline void +buffer_write32be(uint8_t *buf, uint32_t value) { buf[0] = value >> 24; buf[1] = value >> 16; buf[2] = value >> 8; buf[3] = value; } -static inline Uint32 buffer_read32be(Uint8 *buf) { +static inline uint32_t +buffer_read32be(uint8_t *buf) { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } -static inline Uint64 buffer_read64be(Uint8 *buf) { - Uint32 msb = buffer_read32be(buf); - Uint32 lsb = buffer_read32be(&buf[4]); - return ((Uint64) msb << 32) | lsb; +static inline +uint64_t buffer_read64be(uint8_t *buf) { + uint32_t msb = buffer_read32be(buf); + uint32_t lsb = buffer_read32be(&buf[4]); + return ((uint64_t) msb << 32) | lsb; } #endif diff --git a/app/src/command.c b/app/src/command.c index b8340ecc..2e116ac8 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -10,7 +10,8 @@ static const char *adb_command; -static inline const char *get_adb_command(void) { +static inline const char * +get_adb_command(void) { if (!adb_command) { adb_command = getenv("ADB"); if (!adb_command) @@ -19,7 +20,8 @@ static inline const char *get_adb_command(void) { return adb_command; } -static void show_adb_err_msg(enum process_result err) { +static void +show_adb_err_msg(enum process_result err) { switch (err) { case PROCESS_ERROR_GENERIC: LOGE("Failed to execute adb"); @@ -34,7 +36,8 @@ static void show_adb_err_msg(enum process_result err) { } } -process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) { +process_t +adb_execute(const char *serial, const char *const adb_cmd[], int len) { const char *cmd[len + 4]; int i; process_t process; @@ -57,7 +60,9 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) return process; } -process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { +process_t +adb_forward(const char *serial, uint16_t local_port, + const char *device_socket_name) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); @@ -66,14 +71,17 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t adb_forward_remove(const char *serial, uint16_t local_port) { +process_t +adb_forward_remove(const char *serial, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); const char *const adb_cmd[] = {"forward", "--remove", local}; return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { +process_t +adb_reverse(const char *serial, const char *device_socket_name, + uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); @@ -82,14 +90,16 @@ process_t adb_reverse(const char *serial, const char *device_socket_name, uint16 return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t adb_reverse_remove(const char *serial, const char *device_socket_name) { +process_t +adb_reverse_remove(const char *serial, const char *device_socket_name) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", "--remove", remote}; return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t adb_push(const char *serial, const char *local, const char *remote) { +process_t +adb_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) @@ -115,7 +125,8 @@ process_t adb_push(const char *serial, const char *local, const char *remote) { return proc; } -process_t adb_install(const char *serial, const char *local) { +process_t +adb_install(const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) @@ -135,10 +146,11 @@ process_t adb_install(const char *serial, const char *local) { return proc; } -SDL_bool process_check_success(process_t proc, const char *name) { +bool +process_check_success(process_t proc, const char *name) { if (proc == PROCESS_NONE) { LOGE("Could not execute \"%s\"", name); - return SDL_FALSE; + return false; } exit_code_t exit_code; if (!cmd_simple_wait(proc, &exit_code)) { @@ -147,7 +159,7 @@ SDL_bool process_check_success(process_t proc, const char *name) { } else { LOGE("\"%s\" exited unexpectedly", name); } - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } diff --git a/app/src/command.h b/app/src/command.h index 6264adf4..6681f5ea 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -1,12 +1,13 @@ #ifndef COMMAND_H #define COMMAND_H +#include #include -#include #ifdef _WIN32 -# include // not needed here, but must never be included AFTER windows.h + // not needed here, but winsock2.h must never be included AFTER windows.h +# include # include # define PRIexitcode "lu" // @@ -38,20 +39,41 @@ enum process_result { PROCESS_ERROR_MISSING_BINARY, }; -enum process_result 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); +enum process_result +cmd_execute(const char *path, const char *const argv[], process_t *process); -process_t 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); +bool +cmd_terminate(process_t pid); + +bool +cmd_simple_wait(process_t pid, exit_code_t *exit_code); + +process_t +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 // automatically log process errors with the provided process name -SDL_bool process_check_success(process_t process, const char *name); +bool +process_check_success(process_t process, const char *name); #endif diff --git a/app/src/common.h b/app/src/common.h index d3d000f9..8963f058 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -1,25 +1,26 @@ #ifndef COMMON_H #define COMMON_H -#include +#include #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y) struct size { - Uint16 width; - Uint16 height; + uint16_t width; + uint16_t height; }; struct point { - Sint32 x; - Sint32 y; + int32_t x; + int32_t y; }; struct position { // 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 accordingly. + // so store to which size the absolute position apply, to scale it + // accordingly. struct size screen_size; struct point point; }; diff --git a/app/src/control_event.c b/app/src/control_event.c index 015ce786..40de6efc 100644 --- a/app/src/control_event.c +++ b/app/src/control_event.c @@ -1,20 +1,21 @@ #include "control_event.h" -#include #include #include "buffer_util.h" #include "lock_util.h" #include "log.h" -static void write_position(Uint8 *buf, const struct position *position) { +static void +write_position(uint8_t *buf, const struct position *position) { buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[4], position->point.y); buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[10], position->screen_size.height); } -int control_event_serialize(const struct control_event *event, unsigned char *buf) { +int +control_event_serialize(const struct control_event *event, unsigned char *buf) { buf[0] = event->type; switch (event->type) { case CONTROL_EVENT_TYPE_KEYCODE: @@ -29,7 +30,7 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu // injecting a text takes time, so limit the text length len = TEXT_MAX_LENGTH; } - buffer_write16be(&buf[1], (Uint16) len); + buffer_write16be(&buf[1], (uint16_t) len); memcpy(&buf[3], event->text_event.text, len); return 3 + len; } @@ -40,8 +41,8 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu return 18; case CONTROL_EVENT_TYPE_SCROLL: write_position(&buf[1], &event->scroll_event.position); - buffer_write32be(&buf[13], (Uint32) event->scroll_event.hscroll); - buffer_write32be(&buf[17], (Uint32) event->scroll_event.vscroll); + buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll); + buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll); return 21; case CONTROL_EVENT_TYPE_COMMAND: buf[1] = event->command_event.action; @@ -52,28 +53,33 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu } } -void control_event_destroy(struct control_event *event) { +void +control_event_destroy(struct control_event *event) { if (event->type == CONTROL_EVENT_TYPE_TEXT) { SDL_free(event->text_event.text); } } -SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) { +bool +control_event_queue_is_empty(const struct control_event_queue *queue) { return queue->head == queue->tail; } -SDL_bool control_event_queue_is_full(const struct control_event_queue *queue) { +bool +control_event_queue_is_full(const struct control_event_queue *queue) { return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail; } -SDL_bool control_event_queue_init(struct control_event_queue *queue) { +bool +control_event_queue_init(struct control_event_queue *queue) { queue->head = 0; queue->tail = 0; // the current implementation may not fail - return SDL_TRUE; + return true; } -void control_event_queue_destroy(struct control_event_queue *queue) { +void +control_event_queue_destroy(struct control_event_queue *queue) { int i = queue->tail; while (i != queue->head) { control_event_destroy(&queue->data[i]); @@ -81,20 +87,24 @@ void control_event_queue_destroy(struct control_event_queue *queue) { } } -SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) { +bool +control_event_queue_push(struct control_event_queue *queue, + const struct control_event *event) { if (control_event_queue_is_full(queue)) { - return SDL_FALSE; + return false; } queue->data[queue->head] = *event; queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE; - return SDL_TRUE; + return true; } -SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) { +bool +control_event_queue_take(struct control_event_queue *queue, + struct control_event *event) { if (control_event_queue_is_empty(queue)) { - return SDL_FALSE; + return false; } *event = queue->data[queue->tail]; queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE; - return SDL_TRUE; + return true; } diff --git a/app/src/control_event.h b/app/src/control_event.h index 471a9a9b..2a33244b 100644 --- a/app/src/control_event.h +++ b/app/src/control_event.h @@ -1,8 +1,9 @@ #ifndef CONTROLEVENT_H #define CONTROLEVENT_H +#include +#include #include -#include #include "android/input.h" #include "android/keycodes.h" @@ -20,7 +21,11 @@ enum control_event_type { CONTROL_EVENT_TYPE_COMMAND, }; -#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0 +enum control_event_command { + CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON, + CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL, + CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL, +}; struct control_event { enum control_event_type type; @@ -40,11 +45,11 @@ struct control_event { } mouse_event; struct { struct position position; - Sint32 hscroll; - Sint32 vscroll; + int32_t hscroll; + int32_t vscroll; } scroll_event; struct { - int action; + enum control_event_command action; } command_event; }; }; @@ -56,18 +61,31 @@ struct control_event_queue { }; // buf size must be at least SERIALIZED_EVENT_MAX_SIZE -int control_event_serialize(const struct control_event *event, unsigned char *buf); +int +control_event_serialize(const struct control_event *event, unsigned char *buf); -SDL_bool control_event_queue_init(struct control_event_queue *queue); -void control_event_queue_destroy(struct control_event_queue *queue); +bool +control_event_queue_init(struct control_event_queue *queue); -SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue); -SDL_bool control_event_queue_is_full(const struct control_event_queue *queue); +void +control_event_queue_destroy(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 -SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event); -SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event); +bool +control_event_queue_push(struct control_event_queue *queue, + const struct control_event *event); -void control_event_destroy(struct control_event *event); +bool +control_event_queue_take(struct control_event_queue *queue, + struct control_event *event); + +void +control_event_destroy(struct control_event *event); #endif diff --git a/app/src/controller.c b/app/src/controller.c index f659d4b9..8b95e88c 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -1,40 +1,45 @@ #include "controller.h" #include + #include "config.h" #include "lock_util.h" #include "log.h" -SDL_bool controller_init(struct controller *controller, socket_t video_socket) { +bool +controller_init(struct controller *controller, socket_t video_socket) { if (!control_event_queue_init(&controller->queue)) { - return SDL_FALSE; + return false; } if (!(controller->mutex = SDL_CreateMutex())) { - return SDL_FALSE; + return false; } if (!(controller->event_cond = SDL_CreateCond())) { SDL_DestroyMutex(controller->mutex); - return SDL_FALSE; + return false; } controller->video_socket = video_socket; - controller->stopped = SDL_FALSE; + controller->stopped = false; - return SDL_TRUE; + return true; } -void controller_destroy(struct controller *controller) { +void +controller_destroy(struct controller *controller) { SDL_DestroyCond(controller->event_cond); SDL_DestroyMutex(controller->mutex); control_event_queue_destroy(&controller->queue); } -SDL_bool controller_push_event(struct controller *controller, const struct control_event *event) { - SDL_bool res; +bool +controller_push_event(struct controller *controller, + const struct control_event *event) { + bool res; mutex_lock(controller->mutex); - SDL_bool was_empty = control_event_queue_is_empty(&controller->queue); + bool was_empty = control_event_queue_is_empty(&controller->queue); res = control_event_queue_push(&controller->queue, event); if (was_empty) { cond_signal(controller->event_cond); @@ -43,22 +48,26 @@ SDL_bool controller_push_event(struct controller *controller, const struct contr return res; } -static SDL_bool process_event(struct controller *controller, const struct control_event *event) { +static bool +process_event(struct controller *controller, + const struct control_event *event) { unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE]; int length = control_event_serialize(event, serialized_event); if (!length) { - return SDL_FALSE; + return false; } int w = net_send_all(controller->video_socket, serialized_event, length); return w == length; } -static int run_controller(void *data) { +static int +run_controller(void *data) { struct controller *controller = data; for (;;) { mutex_lock(controller->mutex); - while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) { + while (!controller->stopped + && control_event_queue_is_empty(&controller->queue)) { cond_wait(controller->event_cond, controller->mutex); } if (controller->stopped) { @@ -67,11 +76,12 @@ static int run_controller(void *data) { break; } struct control_event event; - SDL_bool non_empty = control_event_queue_take(&controller->queue, &event); + bool non_empty = control_event_queue_take(&controller->queue, + &event); SDL_assert(non_empty); mutex_unlock(controller->mutex); - SDL_bool ok = process_event(controller, &event); + bool ok = process_event(controller, &event); control_event_destroy(&event); if (!ok) { LOGD("Cannot write event to socket"); @@ -81,25 +91,29 @@ static int run_controller(void *data) { return 0; } -SDL_bool controller_start(struct controller *controller) { +bool +controller_start(struct controller *controller) { LOGD("Starting controller thread"); - controller->thread = SDL_CreateThread(run_controller, "controller", controller); + controller->thread = SDL_CreateThread(run_controller, "controller", + controller); if (!controller->thread) { LOGC("Could not start controller thread"); - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } -void controller_stop(struct controller *controller) { +void +controller_stop(struct controller *controller) { mutex_lock(controller->mutex); - controller->stopped = SDL_TRUE; + controller->stopped = true; cond_signal(controller->event_cond); mutex_unlock(controller->mutex); } -void controller_join(struct controller *controller) { +void +controller_join(struct controller *controller) { SDL_WaitThread(controller->thread, NULL); } diff --git a/app/src/controller.h b/app/src/controller.h index 08d639a6..2f7696e3 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -3,8 +3,8 @@ #include "control_event.h" +#include #include -#include #include #include "net.h" @@ -14,18 +14,28 @@ struct controller { SDL_Thread *thread; SDL_mutex *mutex; SDL_cond *event_cond; - SDL_bool stopped; + bool stopped; struct control_event_queue queue; }; -SDL_bool controller_init(struct controller *controller, socket_t video_socket); -void controller_destroy(struct controller *controller); +bool +controller_init(struct controller *controller, socket_t video_socket); -SDL_bool controller_start(struct controller *controller); -void controller_stop(struct controller *controller); -void controller_join(struct controller *controller); +void +controller_destroy(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 -SDL_bool controller_push_event(struct controller *controller, const struct control_event *event); +bool +controller_push_event(struct controller *controller, + const struct control_event *event); #endif diff --git a/app/src/convert.c b/app/src/convert.c index 7d40b938..d20e255f 100644 --- a/app/src/convert.c +++ b/app/src/convert.c @@ -1,9 +1,10 @@ #include "convert.h" -#define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE -#define FAIL default: return SDL_FALSE +#define MAP(FROM, TO) case FROM: *to = TO; return true +#define FAIL default: return false -static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { +static bool +convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { switch (from) { MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); @@ -11,7 +12,8 @@ static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent } } -static enum android_metastate autocomplete_metastate(enum android_metastate metastate) { +static enum android_metastate +autocomplete_metastate(enum android_metastate metastate) { // fill dependant flags if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { metastate |= AMETA_SHIFT_ON; @@ -30,7 +32,8 @@ static enum android_metastate autocomplete_metastate(enum android_metastate meta } -static enum android_metastate convert_meta_state(SDL_Keymod mod) { +static enum android_metastate +convert_meta_state(SDL_Keymod mod) { enum android_metastate metastate = 0; if (mod & KMOD_LSHIFT) { metastate |= AMETA_SHIFT_LEFT_ON; @@ -70,7 +73,8 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) { return autocomplete_metastate(metastate); } -static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) { +static bool +convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) { switch (from) { MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); @@ -88,7 +92,7 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint MAP(SDLK_UP, AKEYCODE_DPAD_UP); } if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { - return SDL_FALSE; + return false; } // if ALT and META are not pressed, also handle letters and space switch (from) { @@ -123,7 +127,8 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint } } -static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { +static bool +convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { switch (from) { MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); @@ -131,7 +136,8 @@ static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motioneven } } -static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) { +static enum android_motionevent_buttons +convert_mouse_buttons(uint32_t state) { enum android_motionevent_buttons buttons = 0; if (state & SDL_BUTTON_LMASK) { buttons |= AMOTION_EVENT_BUTTON_PRIMARY; @@ -151,31 +157,33 @@ static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) { return buttons; } -SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, - struct control_event *to) { +bool +input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, + struct control_event *to) { to->type = CONTROL_EVENT_TYPE_KEYCODE; if (!convert_keycode_action(from->type, &to->keycode_event.action)) { - return SDL_FALSE; + return false; } - Uint16 mod = from->keysym.mod; + uint16_t mod = from->keysym.mod; if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) { - return SDL_FALSE; + return false; } to->keycode_event.metastate = convert_meta_state(mod); - return SDL_TRUE; + return true; } -SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, - struct size screen_size, - struct control_event *to) { +bool +mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, + struct size screen_size, + struct control_event *to) { to->type = CONTROL_EVENT_TYPE_MOUSE; if (!convert_mouse_action(from->type, &to->mouse_event.action)) { - return SDL_FALSE; + return false; } to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); @@ -183,12 +191,13 @@ SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.y = from->y; - return SDL_TRUE; + return true; } -SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, - struct size screen_size, - struct control_event *to) { +bool +mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, + struct size screen_size, + struct control_event *to) { to->type = CONTROL_EVENT_TYPE_MOUSE; to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->mouse_event.buttons = convert_mouse_buttons(from->state); @@ -196,12 +205,13 @@ SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.y = from->y; - return SDL_TRUE; + return true; } -SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, - struct position position, - struct control_event *to) { +bool +mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, + struct position position, + struct control_event *to) { to->type = CONTROL_EVENT_TYPE_SCROLL; to->scroll_event.position = position; @@ -213,5 +223,5 @@ SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, to->scroll_event.hscroll = -mul * from->x; to->scroll_event.vscroll = mul * from->y; - return SDL_TRUE; + return true; } diff --git a/app/src/convert.h b/app/src/convert.h index 30f0dd3d..22cf1023 100644 --- a/app/src/convert.h +++ b/app/src/convert.h @@ -1,8 +1,9 @@ #ifndef CONVERT_H #define CONVERT_H -#include +#include #include + #include "control_event.h" struct complete_mouse_motion_event { @@ -15,21 +16,26 @@ struct complete_mouse_wheel_event { struct point position; }; -SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, - 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 +input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, + 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 -SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, - struct size screen_size, - struct control_event *to); +bool +mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, + 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 -SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, - struct position position, - struct control_event *to); +bool +mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, + struct position position, + struct control_event *to); #endif diff --git a/app/src/decoder.c b/app/src/decoder.c index 9b3ca2f1..8fa218f4 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -12,130 +12,18 @@ #include "config.h" #include "buffer_util.h" #include "events.h" -#include "frames.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 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 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; -} +#include "video_buffer.h" // set the decoded frame as ready for rendering, and notify -static void push_frame(struct decoder *decoder) { - SDL_bool previous_frame_consumed = frames_offer_decoded_frame(decoder->frames); - if (!previous_frame_consumed) { +static void +push_frame(struct decoder *decoder) { + bool previous_frame_skipped; + video_buffer_offer_decoded_frame(decoder->video_buffer, + &previous_frame_skipped); + if (previous_frame_skipped) { // the previous EVENT_NEW_FRAME will consume this frame return; } @@ -145,181 +33,71 @@ static void push_frame(struct decoder *decoder) { SDL_PushEvent(&new_frame_event); } -static void notify_stopped(void) { - SDL_Event stop_event; - stop_event.type = EVENT_DECODER_STOPPED; - SDL_PushEvent(&stop_event); +void +decoder_init(struct decoder *decoder, struct video_buffer *vb) { + decoder->video_buffer = vb; } -static int run_decoder(void *data) { - struct decoder *decoder = data; - - 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) { +bool +decoder_open(struct decoder *decoder, const AVCodec *codec) { + decoder->codec_ctx = avcodec_alloc_context3(codec); + if (!decoder->codec_ctx) { LOGC("Could not allocate decoder context"); - goto run_end; + return false; } - if (avcodec_open2(codec_ctx, codec, NULL) < 0) { - LOGE("Could not open H.264 codec"); - goto run_finally_free_codec_ctx; + if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { + LOGE("Could not open codec"); + avcodec_free_context(&decoder->codec_ctx); + return false; } - AVFormatContext *format_ctx = avformat_alloc_context(); - if (!format_ctx) { - LOGC("Could not allocate format context"); - goto run_finally_close_codec; - } + return true; +} - unsigned char *buffer = av_malloc(BUFSIZE); - if (!buffer) { - LOGC("Could not allocate buffer"); - goto run_finally_free_format_ctx; - } +void +decoder_close(struct decoder *decoder) { + avcodec_close(decoder->codec_ctx); + avcodec_free_context(&decoder->codec_ctx); +} - // initialize the receiver state - 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)) { +bool +decoder_push(struct decoder *decoder, const AVPacket *packet) { // the new decoding/encoding API has been introduced by: // #ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API - int ret; - if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) { - LOGE("Could not send video packet: %d", ret); - goto run_quit; - } - ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame); - if (!ret) { - // a frame was received - push_frame(decoder); - } else if (ret != AVERROR(EAGAIN)) { - LOGE("Could not receive video frame: %d", ret); - av_packet_unref(&packet); - goto run_quit; - } + int ret; + if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { + LOGE("Could not send video packet: %d", ret); + return false; + } + ret = avcodec_receive_frame(decoder->codec_ctx, + decoder->video_buffer->decoding_frame); + if (!ret) { + // a frame was received + push_frame(decoder); + } else if (ret != AVERROR(EAGAIN)) { + LOGE("Could not receive video frame: %d", ret); + return false; + } #else - while (packet.size > 0) { - int got_picture; - int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet); - if (len < 0) { - LOGE("Could not decode video packet: %d", len); - av_packet_unref(&packet); - goto run_quit; - } - if (got_picture) { - push_frame(decoder); - } - packet.size -= len; - packet.data += len; - } + int got_picture; + int len = avcodec_decode_video2(decoder->codec_ctx, + decoder->video_buffer->decoding_frame, + &got_picture, + packet); + if (len < 0) { + LOGE("Could not decode video packet: %d", len); + return false; + } + if (got_picture) { + push_frame(decoder); + } #endif - - 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; + return true; } -void decoder_init(struct decoder *decoder, struct frames *frames, - socket_t video_socket, struct recorder *recorder) { - 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); +void +decoder_interrupt(struct decoder *decoder) { + video_buffer_interrupt(decoder->video_buffer); } diff --git a/app/src/decoder.h b/app/src/decoder.h index 610de000..76fee80e 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -1,36 +1,29 @@ #ifndef DECODER_H #define DECODER_H -#include -#include +#include +#include -#include "common.h" -#include "net.h" - -struct frames; - -struct frame_meta { - uint64_t pts; - struct frame_meta *next; -}; +struct video_buffer; struct decoder { - 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; + struct video_buffer *video_buffer; + AVCodecContext *codec_ctx; }; -void decoder_init(struct decoder *decoder, struct frames *frames, - socket_t video_socket, struct recorder *recoder); -SDL_bool decoder_start(struct decoder *decoder); -void decoder_stop(struct decoder *decoder); -void decoder_join(struct decoder *decoder); +void +decoder_init(struct decoder *decoder, struct video_buffer *vb); + +bool +decoder_open(struct decoder *decoder, const AVCodec *codec); + +void +decoder_close(struct decoder *decoder); + +bool +decoder_push(struct decoder *decoder, const AVPacket *packet); + +void +decoder_interrupt(struct decoder *decoder); #endif diff --git a/app/src/device.c b/app/src/device.c index 1e5ea625..8027ccbb 100644 --- a/app/src/device.c +++ b/app/src/device.c @@ -1,18 +1,22 @@ #include "device.h" #include "log.h" -SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) { +bool +device_read_info(socket_t device_socket, char *device_name, struct size *size) { unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; int r = net_recv_all(device_socket, buf, sizeof(buf)); if (r < DEVICE_NAME_FIELD_LENGTH + 4) { LOGE("Could not retrieve device information"); - return SDL_FALSE; + return false; } - buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage - // strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes - // and strlen(buf) < DEVICE_NAME_FIELD_LENGTH + // 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 and strlen(buf) < DEVICE_NAME_FIELD_LENGTH strcpy(device_name, (char *) buf); - size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1]; - size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3]; - return SDL_TRUE; + size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) + | buf[DEVICE_NAME_FIELD_LENGTH + 1]; + size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) + | buf[DEVICE_NAME_FIELD_LENGTH + 3]; + return true; } diff --git a/app/src/device.h b/app/src/device.h index 125dda3a..09934046 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -1,7 +1,7 @@ #ifndef DEVICE_H #define DEVICE_H -#include +#include #include "common.h" #include "net.h" @@ -10,6 +10,7 @@ #define DEVICE_SDCARD_PATH "/sdcard/" // name must be at least DEVICE_NAME_FIELD_LENGTH bytes -SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size); +bool +device_read_info(socket_t device_socket, char *name, struct size *frame_size); #endif diff --git a/app/src/events.h b/app/src/events.h index ff0c1a05..e9512048 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,3 +1,3 @@ #define EVENT_NEW_SESSION SDL_USEREVENT #define EVENT_NEW_FRAME (SDL_USEREVENT + 1) -#define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2) +#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index d1e0fd7b..d3dedcd2 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -13,7 +13,8 @@ struct request { const char *file; }; -static struct request *request_new(file_handler_action_t action, const char *file) { +static struct request * +request_new(file_handler_action_t action, const char *file) { struct request *req = SDL_malloc(sizeof(*req)); if (!req) { return NULL; @@ -23,7 +24,8 @@ static struct request *request_new(file_handler_action_t action, const char *fil return req; } -static void request_free(struct request *req) { +static void +request_free(struct request *req) { if (!req) { return; } @@ -31,21 +33,25 @@ static void request_free(struct request *req) { SDL_free((void *) req); } -static SDL_bool request_queue_is_empty(const struct request_queue *queue) { +static bool +request_queue_is_empty(const struct request_queue *queue) { return queue->head == queue->tail; } -static SDL_bool request_queue_is_full(const struct request_queue *queue) { +static bool +request_queue_is_full(const struct request_queue *queue) { return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail; } -static SDL_bool request_queue_init(struct request_queue *queue) { +static bool +request_queue_init(struct request_queue *queue) { queue->head = 0; queue->tail = 0; - return SDL_TRUE; + return true; } -static void request_queue_destroy(struct request_queue *queue) { +static void +request_queue_destroy(struct request_queue *queue) { int i = queue->tail; while (i != queue->head) { request_free(queue->reqs[i]); @@ -53,38 +59,41 @@ static void request_queue_destroy(struct request_queue *queue) { } } -static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) { +static bool +request_queue_push(struct request_queue *queue, struct request *req) { if (request_queue_is_full(queue)) { - return SDL_FALSE; + return false; } queue->reqs[queue->head] = req; queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE; - return SDL_TRUE; + return true; } -static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) { +static bool +request_queue_take(struct request_queue *queue, struct request **req) { if (request_queue_is_empty(queue)) { - return SDL_FALSE; + return false; } // transfer ownership *req = queue->reqs[queue->tail]; queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE; - return SDL_TRUE; + return true; } -SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) { +bool +file_handler_init(struct file_handler *file_handler, const char *serial) { if (!request_queue_init(&file_handler->queue)) { - return SDL_FALSE; + return false; } if (!(file_handler->mutex = SDL_CreateMutex())) { - return SDL_FALSE; + return false; } if (!(file_handler->event_cond = SDL_CreateCond())) { SDL_DestroyMutex(file_handler->mutex); - return SDL_FALSE; + return false; } if (serial) { @@ -92,58 +101,63 @@ SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial if (!file_handler->serial) { LOGW("Cannot strdup serial"); SDL_DestroyMutex(file_handler->mutex); - return SDL_FALSE; + return false; } } else { file_handler->serial = NULL; } // lazy initialization - file_handler->initialized = SDL_FALSE; + file_handler->initialized = false; - file_handler->stopped = SDL_FALSE; + file_handler->stopped = false; file_handler->current_process = PROCESS_NONE; - return SDL_TRUE; + return true; } -void file_handler_destroy(struct file_handler *file_handler) { +void +file_handler_destroy(struct file_handler *file_handler) { SDL_DestroyCond(file_handler->event_cond); SDL_DestroyMutex(file_handler->mutex); request_queue_destroy(&file_handler->queue); SDL_free((void *) file_handler->serial); } -static process_t install_apk(const char *serial, const char *file) { +static process_t +install_apk(const char *serial, const char *file) { return adb_install(serial, file); } -static process_t push_file(const char *serial, const char *file) { +static process_t +push_file(const char *serial, const char *file) { return adb_push(serial, file, DEVICE_SDCARD_PATH); } -SDL_bool file_handler_request(struct file_handler *file_handler, - file_handler_action_t action, - const char *file) { - SDL_bool res; +bool +file_handler_request(struct file_handler *file_handler, + file_handler_action_t action, + const char *file) { + bool res; // start file_handler if it's used for the first time if (!file_handler->initialized) { if (!file_handler_start(file_handler)) { - return SDL_FALSE; + return false; } - file_handler->initialized = SDL_TRUE; + file_handler->initialized = true; } - LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file); + LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", + file); struct request *req = request_new(action, file); if (!req) { LOGE("Could not create request"); - return SDL_FALSE; + return false; } mutex_lock(file_handler->mutex); - SDL_bool was_empty = request_queue_is_empty(&file_handler->queue); + bool was_empty = request_queue_is_empty(&file_handler->queue); res = request_queue_push(&file_handler->queue, req); if (was_empty) { cond_signal(file_handler->event_cond); @@ -152,13 +166,15 @@ SDL_bool file_handler_request(struct file_handler *file_handler, return res; } -static int run_file_handler(void *data) { +static int +run_file_handler(void *data) { struct file_handler *file_handler = data; for (;;) { mutex_lock(file_handler->mutex); file_handler->current_process = PROCESS_NONE; - while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) { + while (!file_handler->stopped + && request_queue_is_empty(&file_handler->queue)) { cond_wait(file_handler->event_cond, file_handler->mutex); } if (file_handler->stopped) { @@ -167,7 +183,7 @@ static int run_file_handler(void *data) { break; } struct request *req; - SDL_bool non_empty = request_queue_take(&file_handler->queue, &req); + bool non_empty = request_queue_take(&file_handler->queue, &req); SDL_assert(non_empty); process_t process; @@ -200,21 +216,24 @@ static int run_file_handler(void *data) { return 0; } -SDL_bool file_handler_start(struct file_handler *file_handler) { +bool +file_handler_start(struct file_handler *file_handler) { LOGD("Starting file_handler thread"); - file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler); + file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", + file_handler); if (!file_handler->thread) { LOGC("Could not start file_handler thread"); - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } -void file_handler_stop(struct file_handler *file_handler) { +void +file_handler_stop(struct file_handler *file_handler) { mutex_lock(file_handler->mutex); - file_handler->stopped = SDL_TRUE; + file_handler->stopped = true; cond_signal(file_handler->event_cond); if (file_handler->current_process != PROCESS_NONE) { if (!cmd_terminate(file_handler->current_process)) { @@ -226,6 +245,7 @@ void file_handler_stop(struct file_handler *file_handler) { mutex_unlock(file_handler->mutex); } -void file_handler_join(struct file_handler *file_handler) { +void +file_handler_join(struct file_handler *file_handler) { SDL_WaitThread(file_handler->thread, NULL); } diff --git a/app/src/file_handler.h b/app/src/file_handler.h index 9ce39367..111161dc 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -1,9 +1,10 @@ #ifndef FILE_HANDLER_H #define FILE_HANDLER_H +#include #include -#include #include + #include "command.h" #define REQUEST_QUEUE_SIZE 16 @@ -24,21 +25,30 @@ struct file_handler { SDL_Thread *thread; SDL_mutex *mutex; SDL_cond *event_cond; - SDL_bool stopped; - SDL_bool initialized; + bool stopped; + bool initialized; process_t current_process; struct request_queue queue; }; -SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial); -void file_handler_destroy(struct file_handler *file_handler); +bool +file_handler_init(struct file_handler *file_handler, const char *serial); -SDL_bool file_handler_start(struct file_handler *file_handler); -void file_handler_stop(struct file_handler *file_handler); -void file_handler_join(struct file_handler *file_handler); +void +file_handler_destroy(struct file_handler *file_handler); -SDL_bool file_handler_request(struct file_handler *file_handler, - file_handler_action_t action, - const char *file); +bool +file_handler_start(struct file_handler *file_handler); + +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 diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 27aa4ee0..6c8ef795 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -4,14 +4,16 @@ #include "log.h" -void fps_counter_init(struct fps_counter *counter) { - counter->started = SDL_FALSE; +void +fps_counter_init(struct fps_counter *counter) { + counter->started = false; // no need to initialize the other fields, they are meaningful only when // started is true } -void fps_counter_start(struct fps_counter *counter) { - counter->started = SDL_TRUE; +void +fps_counter_start(struct fps_counter *counter) { + counter->started = true; counter->slice_start = SDL_GetTicks(); counter->nr_rendered = 0; #ifdef SKIP_FRAMES @@ -19,14 +21,17 @@ void fps_counter_start(struct fps_counter *counter) { #endif } -void fps_counter_stop(struct fps_counter *counter) { - counter->started = SDL_FALSE; +void +fps_counter_stop(struct fps_counter *counter) { + counter->started = false; } -static void display_fps(struct fps_counter *counter) { +static void +display_fps(struct fps_counter *counter) { #ifdef SKIP_FRAMES if (counter->nr_skipped) { - LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped); + LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, + counter->nr_skipped); } else { #endif LOGI("%d fps", counter->nr_rendered); @@ -35,12 +40,13 @@ static void display_fps(struct fps_counter *counter) { #endif } -static void check_expired(struct fps_counter *counter) { - Uint32 now = SDL_GetTicks(); +static void +check_expired(struct fps_counter *counter) { + uint32_t now = SDL_GetTicks(); if (now - counter->slice_start >= 1000) { display_fps(counter); // add a multiple of one second - Uint32 elapsed_slices = (now - counter->slice_start) / 1000; + uint32_t elapsed_slices = (now - counter->slice_start) / 1000; counter->slice_start += 1000 * elapsed_slices; counter->nr_rendered = 0; #ifdef SKIP_FRAMES @@ -49,13 +55,15 @@ static void check_expired(struct fps_counter *counter) { } } -void fps_counter_add_rendered_frame(struct fps_counter *counter) { +void +fps_counter_add_rendered_frame(struct fps_counter *counter) { check_expired(counter); ++counter->nr_rendered; } #ifdef SKIP_FRAMES -void fps_counter_add_skipped_frame(struct fps_counter *counter) { +void +fps_counter_add_skipped_frame(struct fps_counter *counter) { check_expired(counter); ++counter->nr_skipped; } diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 27b16f28..dcdf10bf 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -1,26 +1,35 @@ #ifndef FPSCOUNTER_H #define FPSCOUNTER_H -#include +#include +#include #include "config.h" struct fps_counter { - SDL_bool started; - Uint32 slice_start; // initialized by SDL_GetTicks() + bool started; + uint32_t slice_start; // initialized by SDL_GetTicks() int nr_rendered; #ifdef SKIP_FRAMES int nr_skipped; #endif }; -void 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_init(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 -void fps_counter_add_skipped_frame(struct fps_counter *counter); +void +fps_counter_add_skipped_frame(struct fps_counter *counter); #endif #endif diff --git a/app/src/frames.c b/app/src/frames.c deleted file mode 100644 index 514d4788..00000000 --- a/app/src/frames.c +++ /dev/null @@ -1,110 +0,0 @@ -#include "frames.h" - -#include -#include -#include -#include - -#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 -} diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7099249e..f77d2d26 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -5,11 +5,13 @@ #include "lock_util.h" #include "log.h" -// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events) +// Convert window coordinates (as provided by SDL_GetMouseState() to renderer +// coordinates (as provided in SDL mouse events) // // See my question: // -static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) { +static void +convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) { SDL_Rect viewport; float scale_x, scale_y; SDL_RenderGetViewport(renderer, &viewport); @@ -18,7 +20,8 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y = (int) (*y / scale_y) - viewport.y; } -static struct point get_mouse_point(struct screen *screen) { +static struct point +get_mouse_point(struct screen *screen) { int x; int y; SDL_GetMouseState(&x, &y); @@ -32,7 +35,9 @@ static struct point get_mouse_point(struct screen *screen) { static const int ACTION_DOWN = 1; static const int ACTION_UP = 1 << 1; -static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) { +static void +send_keycode(struct controller *controller, enum android_keycode keycode, + int actions, const char *name) { // send DOWN event struct control_event control_event; control_event.type = CONTROL_EVENT_TYPE_KEYCODE; @@ -55,58 +60,93 @@ static void send_keycode(struct controller *controller, enum android_keycode key } } -static inline void action_home(struct controller *controller, int actions) { +static inline void +action_home(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); } -static inline void action_back(struct controller *controller, int actions) { +static inline void +action_back(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); } -static inline void action_app_switch(struct controller *controller, int actions) { +static inline void +action_app_switch(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); } -static inline void action_power(struct controller *controller, int actions) { +static inline void +action_power(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); } -static inline void action_volume_up(struct controller *controller, int actions) { +static inline void +action_volume_up(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); } -static inline void action_volume_down(struct controller *controller, int actions) { +static inline void +action_volume_down(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); } -static inline void action_menu(struct controller *controller, int actions) { +static inline void +action_menu(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); } // turn the screen on if it was off, press BACK otherwise -static void press_back_or_turn_screen_on(struct controller *controller) { +static void +press_back_or_turn_screen_on(struct controller *controller) { struct control_event control_event; control_event.type = CONTROL_EVENT_TYPE_COMMAND; - control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON; + control_event.command_event.action = + CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON; if (!controller_push_event(controller, &control_event)) { LOGW("Cannot turn screen on"); } } -static void switch_fps_counter_state(struct frames *frames) { - mutex_lock(frames->mutex); - if (frames->fps_counter.started) { - LOGI("FPS counter stopped"); - fps_counter_stop(&frames->fps_counter); - } else { - LOGI("FPS counter started"); - fps_counter_start(&frames->fps_counter); +static void +expand_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_EXPAND_NOTIFICATION_PANEL; + + if (!controller_push_event(controller, &control_event)) { + LOGW("Cannot expand notification panel"); } - mutex_unlock(frames->mutex); } -static void clipboard_paste(struct controller *controller) { +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"); + fps_counter_stop(&vb->fps_counter); + } else { + LOGI("FPS counter started"); + fps_counter_start(&vb->fps_counter); + } + mutex_unlock(vb->mutex); +} + +static void +clipboard_paste(struct controller *controller) { char *text = SDL_GetClipboardText(); if (!text) { LOGW("Cannot get clipboard text: %s", SDL_GetError()); @@ -127,8 +167,9 @@ static void clipboard_paste(struct controller *controller) { } } -void input_manager_process_text_input(struct input_manager *input_manager, - const SDL_TextInputEvent *event) { +void +input_manager_process_text_input(struct input_manager *input_manager, + const SDL_TextInputEvent *event) { char c = event->text[0]; if (isalpha(c) || c == ' ') { SDL_assert(event->text[1] == '\0'); @@ -148,11 +189,13 @@ void input_manager_process_text_input(struct input_manager *input_manager, } } -void input_manager_process_key(struct input_manager *input_manager, - const SDL_KeyboardEvent *event) { - SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); - SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); - SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); +void +input_manager_process_key(struct input_manager *input_manager, + const SDL_KeyboardEvent *event, + bool control) { + bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); + bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); + bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); if (alt) { // no shortcut involves Alt or Meta, and they should not be forwarded @@ -162,47 +205,42 @@ void input_manager_process_key(struct input_manager *input_manager, // capture all Ctrl events 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; int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP; - SDL_bool repeat = event->repeat; + bool repeat = event->repeat; + bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: - if (ctrl && !meta && !repeat) { + if (control && ctrl && !meta && !shift && !repeat) { action_home(input_manager->controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (ctrl && !meta && !repeat) { + if (control && ctrl && !meta && !shift && !repeat) { action_back(input_manager->controller, action); } return; case SDLK_s: - if (ctrl && !meta && !repeat) { + if (control && ctrl && !meta && !shift && !repeat) { action_app_switch(input_manager->controller, action); } return; case SDLK_m: - if (ctrl && !meta && !repeat) { + if (control && ctrl && !meta && !shift && !repeat) { action_menu(input_manager->controller, action); } return; case SDLK_p: - if (ctrl && !meta && !repeat) { + if (control && ctrl && !meta && !shift && !repeat) { action_power(input_manager->controller, action); } return; case SDLK_DOWN: #ifdef __APPLE__ - if (!ctrl && meta) { + if (control && !ctrl && meta && !shift) { #else - if (ctrl && !meta) { + if (control && ctrl && !meta && !shift) { #endif // forward repeated events action_volume_down(input_manager->controller, action); @@ -210,37 +248,52 @@ void input_manager_process_key(struct input_manager *input_manager, return; case SDLK_UP: #ifdef __APPLE__ - if (!ctrl && meta) { + if (control && !ctrl && meta && !shift) { #else - if (ctrl && !meta) { + if (control && ctrl && !meta && !shift) { #endif // forward repeated events action_volume_up(input_manager->controller, action); } return; case SDLK_v: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (control && ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { clipboard_paste(input_manager->controller); } return; case SDLK_f: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_switch_fullscreen(input_manager->screen); } return; case SDLK_x: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_resize_to_fit(input_manager->screen); } return; case SDLK_g: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_resize_to_pixel_perfect(input_manager->screen); } return; case SDLK_i: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { - switch_fps_counter_state(input_manager->frames); + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { + 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; } @@ -248,6 +301,10 @@ void input_manager_process_key(struct input_manager *input_manager, return; } + if (!control) { + return; + } + struct control_event control_event; if (input_key_from_sdl_to_android(event, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) { @@ -256,43 +313,48 @@ void input_manager_process_key(struct input_manager *input_manager, } } -void input_manager_process_mouse_motion(struct input_manager *input_manager, - const SDL_MouseMotionEvent *event) { +void +input_manager_process_mouse_motion(struct input_manager *input_manager, + const SDL_MouseMotionEvent *event) { if (!event->state) { // do not send motion events when no button is pressed return; } struct control_event control_event; - if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { + if (mouse_motion_from_sdl_to_android(event, + input_manager->screen->frame_size, + &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) { LOGW("Cannot send mouse motion event"); } } } -static SDL_bool is_outside_device_screen(struct input_manager *input_manager, - int x, int y) +static bool +is_outside_device_screen(struct input_manager *input_manager, int x, int y) { return x < 0 || x >= input_manager->screen->frame_size.width || y < 0 || y >= input_manager->screen->frame_size.height; } -void input_manager_process_mouse_button(struct input_manager *input_manager, - const SDL_MouseButtonEvent *event) { +void +input_manager_process_mouse_button(struct input_manager *input_manager, + const SDL_MouseButtonEvent *event, + bool control) { if (event->type == SDL_MOUSEBUTTONDOWN) { - if (event->button == SDL_BUTTON_RIGHT) { + if (control && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(input_manager->controller); return; } - if (event->button == SDL_BUTTON_MIDDLE) { + if (control && event->button == SDL_BUTTON_MIDDLE) { action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); return; } // double-click on black borders resize to fit the device screen if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { - SDL_bool outside= is_outside_device_screen(input_manager, - event->x, - event->y); + bool outside = is_outside_device_screen(input_manager, + event->x, + event->y); if (outside) { screen_resize_to_fit(input_manager->screen); return; @@ -301,16 +363,23 @@ void input_manager_process_mouse_button(struct input_manager *input_manager, // otherwise, send the click event to the device } + if (!control) { + return; + } + struct control_event control_event; - if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) { + if (mouse_button_from_sdl_to_android(event, + input_manager->screen->frame_size, + &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) { LOGW("Cannot send mouse button event"); } } } -void input_manager_process_mouse_wheel(struct input_manager *input_manager, - const SDL_MouseWheelEvent *event) { +void +input_manager_process_mouse_wheel(struct input_manager *input_manager, + const SDL_MouseWheelEvent *event) { struct position position = { .screen_size = input_manager->screen->frame_size, .point = get_mouse_point(input_manager->screen), diff --git a/app/src/input_manager.h b/app/src/input_manager.h index b9037aa1..83cb7405 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -1,27 +1,40 @@ #ifndef INPUTMANAGER_H #define INPUTMANAGER_H +#include + #include "common.h" #include "controller.h" #include "fps_counter.h" -#include "frames.h" +#include "video_buffer.h" #include "screen.h" struct input_manager { struct controller *controller; - struct frames *frames; + struct video_buffer *video_buffer; struct screen *screen; }; -void input_manager_process_text_input(struct input_manager *input_manager, - const SDL_TextInputEvent *event); -void input_manager_process_key(struct input_manager *input_manager, - const SDL_KeyboardEvent *event); -void 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); -void input_manager_process_mouse_wheel(struct input_manager *input_manager, - const SDL_MouseWheelEvent *event); +void +input_manager_process_text_input(struct input_manager *input_manager, + const SDL_TextInputEvent *event); + +void +input_manager_process_key(struct input_manager *input_manager, + const SDL_KeyboardEvent *event, + bool control); + +void +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 diff --git a/app/src/lock_util.c b/app/src/lock_util.c index 723d3b13..7b70ba6b 100644 --- a/app/src/lock_util.c +++ b/app/src/lock_util.c @@ -4,28 +4,32 @@ #include "log.h" -void mutex_lock(SDL_mutex *mutex) { +void +mutex_lock(SDL_mutex *mutex) { if (SDL_LockMutex(mutex)) { LOGC("Could not lock mutex"); abort(); } } -void mutex_unlock(SDL_mutex *mutex) { +void +mutex_unlock(SDL_mutex *mutex) { if (SDL_UnlockMutex(mutex)) { LOGC("Could not unlock mutex"); abort(); } } -void cond_wait(SDL_cond *cond, SDL_mutex *mutex) { +void +cond_wait(SDL_cond *cond, SDL_mutex *mutex) { if (SDL_CondWait(cond, mutex)) { LOGC("Could not wait on condition"); abort(); } } -void cond_signal(SDL_cond *cond) { +void +cond_signal(SDL_cond *cond) { if (SDL_CondSignal(cond)) { LOGC("Could not signal a condition"); abort(); diff --git a/app/src/lock_util.h b/app/src/lock_util.h index 255cafe4..99c1f8d6 100644 --- a/app/src/lock_util.h +++ b/app/src/lock_util.h @@ -5,9 +5,16 @@ typedef struct SDL_mutex SDL_mutex; typedef struct SDL_cond SDL_cond; -void mutex_lock(SDL_mutex *mutex); -void mutex_unlock(SDL_mutex *mutex); -void cond_wait(SDL_cond *cond, SDL_mutex *mutex); -void cond_signal(SDL_cond *cond); +void +mutex_lock(SDL_mutex *mutex); + +void +mutex_unlock(SDL_mutex *mutex); + +void +cond_wait(SDL_cond *cond, SDL_mutex *mutex); + +void +cond_signal(SDL_cond *cond); #endif diff --git a/app/src/main.c b/app/src/main.c index 35bb74ea..b2a6bf5e 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -1,6 +1,8 @@ #include "scrcpy.h" #include +#include +#include #include #include #include @@ -15,14 +17,16 @@ struct args { const char *crop; const char *record_filename; enum recorder_format record_format; - SDL_bool fullscreen; - SDL_bool help; - SDL_bool version; - SDL_bool show_touches; - Uint16 port; - Uint16 max_size; - Uint32 bit_rate; - SDL_bool always_on_top; + bool fullscreen; + bool no_control; + bool no_display; + bool help; + bool version; + bool show_touches; + uint16_t port; + uint16_t max_size; + uint32_t bit_rate; + bool always_on_top; }; static void usage(const char *arg0) { @@ -57,6 +61,13 @@ static void usage(const char *arg0) { " is preserved.\n" " Default is %d%s.\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" " Set the TCP port the client listens on.\n" " Default is %d.\n" @@ -120,6 +131,12 @@ static void usage(const char *arg0) { " Right-click (when screen is off)\n" " turn screen on\n" "\n" + " Ctrl+n\n" + " expand notification panel\n" + "\n" + " Ctrl+Shift+n\n" + " collapse notification panel\n" + "\n" " Ctrl+v\n" " paste computer clipboard to device\n" "\n" @@ -135,28 +152,37 @@ static void usage(const char *arg0) { DEFAULT_LOCAL_PORT); } -static void print_version(void) { +static void +print_version(void) { fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION); fprintf(stderr, "dependencies:\n"); - fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); - fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, 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); + fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, + SDL_PATCHLEVEL); + fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, + LIBAVCODEC_VERSION_MINOR, + 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 SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) { +static bool +parse_bit_rate(char *optarg, uint32_t *bit_rate) { char *endptr; if (*optarg == '\0') { LOGE("Bit-rate parameter is empty"); - return SDL_FALSE; + return false; } long value = strtol(optarg, &endptr, 0); int mul = 1; if (*endptr != '\0') { if (optarg == endptr) { LOGE("Invalid bit-rate: %s", optarg); - return SDL_FALSE; + return false; } if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') { mul = 1000000; @@ -164,70 +190,72 @@ static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) { mul = 1000; } else { LOGE("Invalid bit-rate unit: %s", optarg); - return SDL_FALSE; + return false; } } - if (value < 0 || ((Uint32) -1) / mul < value) { + if (value < 0 || ((uint32_t) -1) / mul < value) { LOGE("Bitrate must be positive and less than 2^32: %s", optarg); - return SDL_FALSE; + return false; } - *bit_rate = (Uint32) value * mul; - return SDL_TRUE; + *bit_rate = (uint32_t) value * mul; + return true; } -static SDL_bool parse_max_size(char *optarg, Uint16 *max_size) { +static bool +parse_max_size(char *optarg, uint16_t *max_size) { char *endptr; if (*optarg == '\0') { LOGE("Max size parameter is empty"); - return SDL_FALSE; + return false; } long value = strtol(optarg, &endptr, 0); if (*endptr != '\0') { LOGE("Invalid max size: %s", optarg); - return SDL_FALSE; + return false; } if (value & ~0xffff) { LOGE("Max size must be between 0 and 65535: %ld", value); - return SDL_FALSE; + return false; } - *max_size = (Uint16) value; - return SDL_TRUE; + *max_size = (uint16_t) value; + return true; } -static SDL_bool parse_port(char *optarg, Uint16 *port) { +static bool +parse_port(char *optarg, uint16_t *port) { char *endptr; if (*optarg == '\0') { LOGE("Invalid port parameter is empty"); - return SDL_FALSE; + return false; } long value = strtol(optarg, &endptr, 0); if (*endptr != '\0') { LOGE("Invalid port: %s", optarg); - return SDL_FALSE; + return false; } if (value & ~0xffff) { LOGE("Port out of range: %ld", value); - return SDL_FALSE; + return false; } - *port = (Uint16) value; - return SDL_TRUE; + *port = (uint16_t) value; + return true; } -static SDL_bool +static bool parse_record_format(const char *optarg, enum recorder_format *format) { if (!strcmp(optarg, "mp4")) { *format = RECORDER_FORMAT_MP4; - return SDL_TRUE; + return true; } if (!strcmp(optarg, "mkv")) { *format = RECORDER_FORMAT_MKV; - return SDL_TRUE; + return true; } LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); - return SDL_FALSE; + return false; } static enum recorder_format @@ -246,7 +274,8 @@ guess_record_format(const char *filename) { return 0; } -static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { +static bool +parse_args(struct args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"always-on-top", no_argument, NULL, 'T'}, {"bit-rate", required_argument, NULL, 'b'}, @@ -254,6 +283,8 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"max-size", required_argument, NULL, 'm'}, + {"no-control", no_argument, NULL, 'n'}, + {"no-display", no_argument, NULL, 'N'}, {"port", required_argument, NULL, 'p'}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, 'f'}, @@ -263,35 +294,42 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { {NULL, 0, NULL, 0 }, }; int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:p:r:s:tTv", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:tTv", long_options, + NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &args->bit_rate)) { - return SDL_FALSE; + return false; } break; case 'c': args->crop = optarg; break; case 'f': - args->fullscreen = SDL_TRUE; + args->fullscreen = true; break; case 'F': if (!parse_record_format(optarg, &args->record_format)) { - return SDL_FALSE; + return false; } break; case 'h': - args->help = SDL_TRUE; + args->help = true; break; case 'm': if (!parse_max_size(optarg, &args->max_size)) { - return SDL_FALSE; + return false; } break; + case 'n': + args->no_control = true; + break; + case 'N': + args->no_display = true; + break; case 'p': if (!parse_port(optarg, &args->port)) { - return SDL_FALSE; + return false; } break; case 'r': @@ -301,29 +339,39 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { args->serial = optarg; break; case 't': - args->show_touches = SDL_TRUE; + args->show_touches = true; break; case 'T': - args->always_on_top = SDL_TRUE; + args->always_on_top = true; break; case 'v': - args->version = SDL_TRUE; + args->version = true; break; default: // getopt prints the error message on stderr - return SDL_FALSE; + return 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; if (index < argc) { LOGE("Unexpected additional argument: %s", argv[index]); - return SDL_FALSE; + return false; } if (args->record_format && !args->record_filename) { LOGE("Record format specified without recording"); - return SDL_FALSE; + return false; } if (args->record_filename && !args->record_format) { @@ -331,14 +379,15 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { if (!args->record_format) { LOGE("No format specified for \"%s\" (try with -F mkv)", args->record_filename); - return SDL_FALSE; + return false; } } - return SDL_TRUE; + return true; } -int main(int argc, char *argv[]) { +int +main(int argc, char *argv[]) { #ifdef __WINDOWS__ // disable buffering, we want logs immediately // even line buffering (setvbuf() with mode _IOLBF) is not sufficient @@ -350,13 +399,15 @@ int main(int argc, char *argv[]) { .crop = NULL, .record_filename = NULL, .record_format = 0, - .help = SDL_FALSE, - .version = SDL_FALSE, - .show_touches = SDL_FALSE, + .help = false, + .version = false, + .show_touches = false, .port = DEFAULT_LOCAL_PORT, .max_size = DEFAULT_MAX_SIZE, .bit_rate = DEFAULT_BIT_RATE, - .always_on_top = SDL_FALSE, + .always_on_top = false, + .no_control = false, + .no_display = false, }; if (!parse_args(&args, argc, argv)) { return 1; @@ -395,6 +446,8 @@ int main(int argc, char *argv[]) { .show_touches = args.show_touches, .fullscreen = args.fullscreen, .always_on_top = args.always_on_top, + .no_control = args.no_control, + .no_display = args.no_display, }; int res = scrcpy(&options) ? 0 : 1; diff --git a/app/src/net.c b/app/src/net.c index ba9a7776..b5b227c2 100644 --- a/app/src/net.c +++ b/app/src/net.c @@ -18,7 +18,8 @@ typedef struct in_addr IN_ADDR; #endif -socket_t net_connect(Uint32 addr, Uint16 port) { +socket_t +net_connect(uint32_t addr, uint16_t port) { socket_t sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { perror("socket"); @@ -38,7 +39,8 @@ socket_t net_connect(Uint32 addr, Uint16 port) { return sock; } -socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { +socket_t +net_listen(uint32_t addr, uint16_t port, int backlog) { socket_t sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { perror("socket"); @@ -46,7 +48,8 @@ socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { } int reuse = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) { + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, + sizeof(reuse)) == -1) { perror("setsockopt(SO_REUSEADDR)"); } @@ -68,25 +71,30 @@ socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { return sock; } -socket_t net_accept(socket_t server_socket) { +socket_t +net_accept(socket_t server_socket) { SOCKADDR_IN csin; socklen_t sinsize = sizeof(csin); return accept(server_socket, (SOCKADDR *) &csin, &sinsize); } -ssize_t net_recv(socket_t socket, void *buf, size_t len) { +ssize_t +net_recv(socket_t socket, void *buf, size_t len) { return recv(socket, buf, len, 0); } -ssize_t net_recv_all(socket_t socket, void *buf, size_t len) { +ssize_t +net_recv_all(socket_t socket, void *buf, size_t len) { return recv(socket, buf, len, MSG_WAITALL); } -ssize_t net_send(socket_t socket, const void *buf, size_t len) { +ssize_t +net_send(socket_t socket, const void *buf, size_t len) { return send(socket, buf, len, 0); } -ssize_t net_send_all(socket_t socket, const void *buf, size_t len) { +ssize_t +net_send_all(socket_t socket, const void *buf, size_t len) { ssize_t w = 0; while (len > 0) { w = send(socket, buf, len, 0); @@ -99,6 +107,7 @@ ssize_t net_send_all(socket_t socket, const void *buf, size_t len) { return w; } -SDL_bool net_shutdown(socket_t socket, int how) { +bool +net_shutdown(socket_t socket, int how) { return !shutdown(socket, how); } diff --git a/app/src/net.h b/app/src/net.h index 1be1e815..dd82c083 100644 --- a/app/src/net.h +++ b/app/src/net.h @@ -1,8 +1,9 @@ #ifndef NET_H #define NET_H +#include +#include #include -#include #ifdef __WINDOWS__ # include @@ -16,20 +17,39 @@ typedef int socket_t; #endif -SDL_bool net_init(void); -void net_cleanup(void); +bool +net_init(void); -socket_t net_connect(Uint32 addr, Uint16 port); -socket_t net_listen(Uint32 addr, Uint16 port, int backlog); -socket_t net_accept(socket_t server_socket); +void +net_cleanup(void); + +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 -ssize_t 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 net_send_all(socket_t socket, const void *buf, size_t len); +ssize_t +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 +net_send_all(socket_t socket, const void *buf, size_t len); + // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) -SDL_bool net_shutdown(socket_t socket, int how); -SDL_bool net_close(socket_t socket); +bool +net_shutdown(socket_t socket, int how); + +bool +net_close(socket_t socket); #endif diff --git a/app/src/recorder.c b/app/src/recorder.c index 2d47487f..321a17ee 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -9,7 +9,8 @@ static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us -static const AVOutputFormat *find_muxer(const char *name) { +static const AVOutputFormat * +find_muxer(const char *name) { #ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API void *opaque = NULL; #endif @@ -25,24 +26,26 @@ static const AVOutputFormat *find_muxer(const char *name) { return oformat; } -SDL_bool recorder_init(struct recorder *recorder, - const char *filename, - enum recorder_format format, - struct size declared_frame_size) { +bool +recorder_init(struct recorder *recorder, + const char *filename, + enum recorder_format format, + struct size declared_frame_size) { recorder->filename = SDL_strdup(filename); if (!recorder->filename) { LOGE("Cannot strdup filename"); - return SDL_FALSE; + return false; } recorder->format = format; recorder->declared_frame_size = declared_frame_size; - recorder->header_written = SDL_FALSE; + recorder->header_written = false; - return SDL_TRUE; + return true; } -void recorder_destroy(struct recorder *recorder) { +void +recorder_destroy(struct recorder *recorder) { SDL_free(recorder->filename); } @@ -55,19 +58,20 @@ recorder_get_format_name(enum recorder_format format) { } } -SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { +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 SDL_FALSE; + return false; } recorder->ctx = avformat_alloc_context(); if (!recorder->ctx) { LOGE("Could not allocate output context"); - return SDL_FALSE; + return false; } // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() @@ -79,7 +83,7 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); if (!ostream) { avformat_free_context(recorder->ctx); - return SDL_FALSE; + return false; } #ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API @@ -102,15 +106,16 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { LOGE("Failed to open output file: %s", recorder->filename); // ostream will be cleaned up during context cleaning avformat_free_context(recorder->ctx); - return SDL_FALSE; + return false; } LOGI("Recording started to %s file: %s", format_name, recorder->filename); - return SDL_TRUE; + return true; } -void recorder_close(struct recorder *recorder) { +void +recorder_close(struct recorder *recorder) { int ret = av_write_trailer(recorder->ctx); if (ret < 0) { LOGE("Failed to write trailer to %s", recorder->filename); @@ -122,14 +127,14 @@ void recorder_close(struct recorder *recorder) { LOGI("Recording complete to %s file: %s", format_name, recorder->filename); } -static SDL_bool -recorder_write_header(struct recorder *recorder, AVPacket *packet) { +static bool +recorder_write_header(struct recorder *recorder, const AVPacket *packet) { AVStream *ostream = recorder->ctx->streams[0]; uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); if (!extradata) { LOGC("Cannot allocate extradata"); - return SDL_FALSE; + return false; } // copy the first packet to the extra data @@ -149,10 +154,10 @@ recorder_write_header(struct recorder *recorder, AVPacket *packet) { SDL_free(extradata); avio_closep(&recorder->ctx->pb); avformat_free_context(recorder->ctx); - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } static void @@ -161,13 +166,14 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) { av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); } -SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) { +bool +recorder_write(struct recorder *recorder, AVPacket *packet) { if (!recorder->header_written) { - SDL_bool ok = recorder_write_header(recorder, packet); + bool ok = recorder_write_header(recorder, packet); if (!ok) { - return SDL_FALSE; + return false; } - recorder->header_written = SDL_TRUE; + recorder->header_written = true; } recorder_rescale_packet(recorder, packet); diff --git a/app/src/recorder.h b/app/src/recorder.h index 203c51b4..26c4a3c3 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -1,8 +1,8 @@ #ifndef RECORDER_H #define RECORDER_H +#include #include -#include #include "common.h" @@ -16,19 +16,23 @@ struct recorder { enum recorder_format format; AVFormatContext *ctx; struct size declared_frame_size; - SDL_bool header_written; + bool header_written; }; -SDL_bool recorder_init(struct recorder *recoder, - const char *filename, - enum recorder_format format, - struct size declared_frame_size); +bool +recorder_init(struct recorder *recoder, const char *filename, + enum recorder_format format, struct size declared_frame_size); -void recorder_destroy(struct recorder *recorder); +void +recorder_destroy(struct recorder *recorder); -SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec); -void recorder_close(struct recorder *recorder); +bool +recorder_open(struct recorder *recorder, const AVCodec *input_codec); -SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet); +void +recorder_close(struct recorder *recorder); + +bool +recorder_write(struct recorder *recorder, AVPacket *packet); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9a1c29d5..61b3f8d9 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -14,7 +14,6 @@ #include "device.h" #include "events.h" #include "file_handler.h" -#include "frames.h" #include "fps_counter.h" #include "input_manager.h" #include "log.h" @@ -23,22 +22,59 @@ #include "recorder.h" #include "screen.h" #include "server.h" +#include "stream.h" #include "tiny_xpm.h" +#include "video_buffer.h" static struct server server = SERVER_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER; -static struct frames frames; +static struct video_buffer video_buffer; +static struct stream stream; static struct decoder decoder; +static struct recorder recorder; static struct controller controller; static struct file_handler file_handler; -static struct recorder recorder; static struct input_manager input_manager = { .controller = &controller, - .frames = &frames, + .video_buffer = &video_buffer, .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__) # define CONTINUOUS_RESIZING_WORKAROUND #endif @@ -49,8 +85,10 @@ static struct input_manager input_manager = { // // // -static int event_watcher(void *data, SDL_Event *event) { - if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { +static int +event_watcher(void *data, SDL_Event *event) { + if (event->type == SDL_WINDOWEVENT + && event->window.event == SDL_WINDOWEVENT_RESIZED) { // called from another thread, not very safe, but it's a workaround! screen_render(&screen); } @@ -58,75 +96,117 @@ static int event_watcher(void *data, SDL_Event *event) { } #endif -static SDL_bool is_apk(const char *file) { +static bool +is_apk(const char *file) { const char *ext = strrchr(file, '.'); return ext && !strcmp(ext, ".apk"); } -static SDL_bool event_loop(void) { +enum event_result { + 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 - SDL_AddEventWatch(event_watcher, NULL); + if (display) { + SDL_AddEventWatch(event_watcher, NULL); + } #endif SDL_Event event; while (SDL_WaitEvent(&event)) { - switch (event.type) { - case EVENT_DECODER_STOPPED: - LOGD("Video decoder stopped"); - return SDL_FALSE; - case SDL_QUIT: - LOGD("User requested to quit"); - 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; - } + enum event_result result = handle_event(&event, control); + switch (result) { + case EVENT_RESULT_STOPPED_BY_USER: + return true; + case EVENT_RESULT_STOPPED_BY_EOS: + return false; + case EVENT_RESULT_CONTINUE: 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 SDL_FALSE; + return false; } -static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) { +static process_t +set_show_touches_enabled(const char *serial, bool enabled) { const char *value = enabled ? "1" : "0"; const char *const adb_cmd[] = { "shell", "settings", "put", "system", "show_touches", value @@ -134,12 +214,14 @@ static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -static void wait_show_touches(process_t process) { +static void +wait_show_touches(process_t process) { // reap the process, ignore the result process_check_success(process, "show_touches"); } -static SDL_LogPriority sdl_priority_from_av_level(int level) { +static SDL_LogPriority +sdl_priority_from_av_level(int level) { switch (level) { case AV_LOG_PANIC: case AV_LOG_FATAL: @@ -173,67 +255,77 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { SDL_free(local_fmt); } -SDL_bool scrcpy(const struct scrcpy_options *options) { - SDL_bool send_frame_meta = !!options->record_filename; +bool +scrcpy(const struct scrcpy_options *options) { + bool record = !!options->record_filename; if (!server_start(&server, options->serial, options->port, options->max_size, options->bit_rate, options->crop, - send_frame_meta)) { - return SDL_FALSE; + record)) { + return false; } process_t proc_show_touches = PROCESS_NONE; - SDL_bool show_touches_waited; + bool show_touches_waited; if (options->show_touches) { LOGI("Enable show_touches"); - proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE); - show_touches_waited = SDL_FALSE; + proc_show_touches = set_show_touches_enabled(options->serial, true); + show_touches_waited = false; } - SDL_bool ret = SDL_TRUE; + bool ret = true; - if (!sdl_init_and_configure()) { - ret = SDL_FALSE; + bool display = !options->no_display; + bool control = !options->no_control; + + if (!sdl_init_and_configure(display)) { + ret = false; goto finally_destroy_server; } socket_t device_socket = server_connect_to(&server); if (device_socket == INVALID_SOCKET) { server_stop(&server); - ret = SDL_FALSE; + ret = false; goto finally_destroy_server; } char device_name[DEVICE_NAME_FIELD_LENGTH]; struct size frame_size; - // screenrecord does not send frames when the screen content does not change - // therefore, we transmit the screen size before the video stream, to be able - // to init the window immediately + // screenrecord does not send frames when the screen content does not + // change therefore, we transmit the screen size before the video stream, + // to be able to init the window immediately if (!device_read_info(device_socket, device_name, &frame_size)) { server_stop(&server); - ret = SDL_FALSE; + ret = false; goto finally_destroy_server; } - if (!frames_init(&frames)) { - server_stop(&server); - ret = SDL_FALSE; - goto finally_destroy_server; - } + struct decoder *dec = NULL; + if (display) { + if (!video_buffer_init(&video_buffer)) { + server_stop(&server); + ret = false; + goto finally_destroy_server; + } - if (!file_handler_init(&file_handler, server.serial)) { - ret = SDL_FALSE; - server_stop(&server); - goto finally_destroy_frames; + if (control && !file_handler_init(&file_handler, server.serial)) { + ret = false; + server_stop(&server); + goto finally_destroy_video_buffer; + } + + decoder_init(&decoder, &video_buffer); + dec = &decoder; } struct recorder *rec = NULL; - if (options->record_filename) { + if (record) { if (!recorder_init(&recorder, options->record_filename, options->record_format, frame_size)) { - ret = SDL_FALSE; + ret = false; server_stop(&server); goto finally_destroy_file_handler; } @@ -242,65 +334,78 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { av_log_set_callback(av_log_callback); - decoder_init(&decoder, &frames, device_socket, rec); + stream_init(&stream, device_socket, dec, rec); // now we consumed the header values, the socket receives the video stream - // start the decoder - if (!decoder_start(&decoder)) { - ret = SDL_FALSE; + // start the stream + if (!stream_start(&stream)) { + ret = false; server_stop(&server); goto finally_destroy_recorder; } - if (!controller_init(&controller, device_socket)) { - ret = SDL_FALSE; - goto finally_stop_decoder; - } + if (display) { + if (control) { + if (!controller_init(&controller, device_socket)) { + ret = false; + goto finally_stop_stream; + } - if (!controller_start(&controller)) { - ret = SDL_FALSE; - goto finally_destroy_controller; - } + if (!controller_start(&controller)) { + ret = false; + goto finally_destroy_controller; + } + } - if (!screen_init_rendering(&screen, device_name, frame_size, options->always_on_top)) { - ret = SDL_FALSE; - goto finally_stop_and_join_controller; + if (!screen_init_rendering(&screen, device_name, frame_size, + options->always_on_top)) { + ret = false; + goto finally_stop_and_join_controller; + } + + if (options->fullscreen) { + screen_switch_fullscreen(&screen); + } } if (options->show_touches) { wait_show_touches(proc_show_touches); - show_touches_waited = SDL_TRUE; + show_touches_waited = true; } - if (options->fullscreen) { - screen_switch_fullscreen(&screen); - } - - ret = event_loop(); + ret = event_loop(display, control); LOGD("quit..."); screen_destroy(&screen); finally_stop_and_join_controller: - controller_stop(&controller); - controller_join(&controller); + if (display && control) { + controller_stop(&controller); + controller_join(&controller); + } finally_destroy_controller: - controller_destroy(&controller); -finally_stop_decoder: - decoder_stop(&decoder); - // stop the server before decoder_join() to wake up the decoder + if (display && control) { + controller_destroy(&controller); + } +finally_stop_stream: + stream_stop(&stream); + // stop the server before stream_join() to wake up the stream server_stop(&server); - decoder_join(&decoder); -finally_destroy_file_handler: - file_handler_stop(&file_handler); - file_handler_join(&file_handler); - file_handler_destroy(&file_handler); + stream_join(&stream); finally_destroy_recorder: - if (options->record_filename) { + if (record) { recorder_destroy(&recorder); } -finally_destroy_frames: - frames_destroy(&frames); +finally_destroy_file_handler: + if (display && control) { + 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: if (options->show_touches) { if (!show_touches_waited) { @@ -308,7 +413,8 @@ finally_destroy_server: wait_show_touches(proc_show_touches); } LOGI("Disable show_touches"); - proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE); + proc_show_touches = set_show_touches_enabled(options->serial, + false); wait_show_touches(proc_show_touches); } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index aba1f336..114c12a4 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -1,7 +1,8 @@ #ifndef SCRCPY_H #define SCRCPY_H -#include +#include +#include #include struct scrcpy_options { @@ -9,14 +10,17 @@ struct scrcpy_options { const char *crop; const char *record_filename; enum recorder_format record_format; - Uint16 port; - Uint16 max_size; - Uint32 bit_rate; - SDL_bool show_touches; - SDL_bool fullscreen; - SDL_bool always_on_top; + uint16_t port; + uint16_t max_size; + uint32_t bit_rate; + bool show_touches; + bool fullscreen; + bool always_on_top; + bool no_control; + bool no_display; }; -SDL_bool scrcpy(const struct scrcpy_options *options); +bool +scrcpy(const struct scrcpy_options *options); #endif diff --git a/app/src/screen.c b/app/src/screen.c index 1632e27f..8476e94f 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -1,44 +1,21 @@ #include "screen.h" -#include #include +#include +#include "common.h" #include "compat.h" #include "icon.xpm" #include "lock_util.h" #include "log.h" #include "tiny_xpm.h" +#include "video_buffer.h" #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"); - } - -#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 SDL_TRUE; -} - // get the window size in a struct size -static struct size get_native_window_size(SDL_Window *window) { +static struct size +get_native_window_size(SDL_Window *window) { int width; int height; SDL_GetWindowSize(window, &width, &height); @@ -50,7 +27,8 @@ static struct size get_native_window_size(SDL_Window *window) { } // get the windowed window size -static struct size get_window_size(const struct screen *screen) { +static struct size +get_window_size(const struct screen *screen) { if (screen->fullscreen) { return screen->windowed_window_size; } @@ -58,7 +36,8 @@ static struct size get_window_size(const struct screen *screen) { } // set the window size to be applied when fullscreen is disabled -static void set_window_size(struct screen *screen, struct size new_size) { +static void +set_window_size(struct screen *screen, struct size new_size) { // setting the window size during fullscreen is implementation defined, // so apply the resize only after fullscreen is disabled if (screen->fullscreen) { @@ -70,7 +49,8 @@ static void set_window_size(struct screen *screen, struct size new_size) { } // get the preferred display bounds (i.e. the screen bounds with some margins) -static SDL_bool get_preferred_display_bounds(struct size *bounds) { +static bool +get_preferred_display_bounds(struct size *bounds) { SDL_Rect rect; #ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) @@ -79,19 +59,21 @@ static SDL_bool get_preferred_display_bounds(struct size *bounds) { #endif if (GET_DISPLAY_BOUNDS(0, &rect)) { LOGW("Could not get display usable bounds: %s", SDL_GetError()); - return SDL_FALSE; + return false; } bounds->width = MAX(0, rect.w - DISPLAY_MARGINS); bounds->height = MAX(0, rect.h - DISPLAY_MARGINS); - return SDL_TRUE; + return true; } // 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 crops the black borders) +// - it attempts to keep at least one dimension of the current_size (i.e. it +// crops the black borders) // - it keeps the aspect ratio // - it scales down to make it fit in the display_size -static struct size get_optimal_size(struct size current_size, struct size frame_size) { +static struct size +get_optimal_size(struct size current_size, struct size frame_size) { if (frame_size.width == 0 || frame_size.height == 0) { // avoid division by 0 return current_size; @@ -99,8 +81,8 @@ static struct size get_optimal_size(struct size current_size, struct size frame_ struct size display_size; // 32 bits because we need to multiply two 16 bits values - Uint32 w; - Uint32 h; + uint32_t w; + uint32_t h; if (!get_preferred_display_bounds(&display_size)) { // cannot get display bounds, do not constraint the size @@ -111,12 +93,13 @@ static struct size get_optimal_size(struct size current_size, struct size frame_ h = MIN(current_size.height, display_size.height); } - SDL_bool keep_width = frame_size.width * h > frame_size.height * w; + bool keep_width = frame_size.width * h > frame_size.height * w; if (keep_width) { // remove black borders on top and bottom h = frame_size.height * w / frame_size.width; } else { - // remove black borders on left and right (or none at all if it already fits) + // remove black borders on left and right (or none at all if it already + // fits) w = frame_size.width * h / frame_size.height; } @@ -126,33 +109,37 @@ static struct size get_optimal_size(struct size current_size, struct size frame_ } // same as get_optimal_size(), but read the current size from the window -static inline struct size get_optimal_window_size(const struct screen *screen, struct size frame_size) { +static inline struct size +get_optimal_window_size(const struct screen *screen, struct size frame_size) { struct size current_size = get_window_size(screen); return get_optimal_size(current_size, frame_size); } // initially, there is no current size, so use the frame size as current size -static inline struct size get_initial_optimal_size(struct size frame_size) { +static inline struct size +get_initial_optimal_size(struct size frame_size) { return get_optimal_size(frame_size, frame_size); } -void screen_init(struct screen *screen) { +void +screen_init(struct screen *screen) { *screen = (struct screen) SCREEN_INITIALIZER; } -static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) { - return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, +static inline SDL_Texture * +create_texture(SDL_Renderer *renderer, struct size frame_size) { + return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, + SDL_TEXTUREACCESS_STREAMING, frame_size.width, frame_size.height); } -SDL_bool screen_init_rendering(struct screen *screen, - const char *device_name, - struct size frame_size, - SDL_bool always_on_top) { +bool +screen_init_rendering(struct screen *screen, const char *device_name, + struct size frame_size, bool always_on_top) { screen->frame_size = frame_size; struct size window_size = get_initial_optimal_size(frame_size); - Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; + uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; #ifdef HIDPI_SUPPORT window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; #endif @@ -165,51 +152,58 @@ SDL_bool screen_init_rendering(struct screen *screen, #endif } - screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - window_size.width, window_size.height, window_flags); + screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + window_size.width, window_size.height, + window_flags); if (!screen->window) { LOGC("Could not create window: %s", SDL_GetError()); - return SDL_FALSE; + return false; } - screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED); + screen->renderer = SDL_CreateRenderer(screen->window, -1, + SDL_RENDERER_ACCELERATED); if (!screen->renderer) { LOGC("Could not create renderer: %s", SDL_GetError()); screen_destroy(screen); - return SDL_FALSE; + return false; } - if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) { + if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, + frame_size.height)) { LOGE("Could not set renderer logical size: %s", SDL_GetError()); screen_destroy(screen); - return SDL_FALSE; + return false; } SDL_Surface *icon = read_xpm(icon_xpm); if (!icon) { LOGE("Could not load icon: %s", SDL_GetError()); screen_destroy(screen); - return SDL_FALSE; + return false; } SDL_SetWindowIcon(screen->window, icon); SDL_FreeSurface(icon); - LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height); + LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, + frame_size.height); screen->texture = create_texture(screen->renderer, frame_size); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); screen_destroy(screen); - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } -void screen_show_window(struct screen *screen) { +void +screen_show_window(struct screen *screen) { SDL_ShowWindow(screen->window); } -void screen_destroy(struct screen *screen) { +void +screen_destroy(struct screen *screen) { if (screen->texture) { SDL_DestroyTexture(screen->texture); } @@ -222,11 +216,14 @@ void screen_destroy(struct screen *screen) { } // recreate the texture and resize the window if the frame size has changed -static SDL_bool 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 (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) { +static bool +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 (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, + new_frame_size.height)) { LOGE("Could not set renderer logical size: %s", SDL_GetError()); - return SDL_FALSE; + return false; } // frame dimension changed, destroy texture @@ -234,61 +231,67 @@ static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_s struct size current_size = get_window_size(screen); struct size target_size = { - (Uint32) current_size.width * new_frame_size.width / screen->frame_size.width, - (Uint32) current_size.height * new_frame_size.height / screen->frame_size.height, + (uint32_t) current_size.width * new_frame_size.width + / screen->frame_size.width, + (uint32_t) current_size.height * new_frame_size.height + / screen->frame_size.height, }; target_size = get_optimal_size(target_size, new_frame_size); set_window_size(screen, target_size); screen->frame_size = new_frame_size; - LOGD("New texture: %" PRIu16 "x%" PRIu16, + LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); screen->texture = create_texture(screen->renderer, new_frame_size); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); - return SDL_FALSE; + return false; } } - return SDL_TRUE; + return true; } // write the frame into the texture -static void update_texture(struct screen *screen, const AVFrame *frame) { +static void +update_texture(struct screen *screen, const AVFrame *frame) { SDL_UpdateYUVTexture(screen->texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); } -SDL_bool screen_update_frame(struct screen *screen, struct frames *frames) { - mutex_lock(frames->mutex); - const AVFrame *frame = frames_consume_rendered_frame(frames); +bool +screen_update_frame(struct screen *screen, struct video_buffer *vb) { + mutex_lock(vb->mutex); + const AVFrame *frame = video_buffer_consume_rendered_frame(vb); struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { - mutex_unlock(frames->mutex); - return SDL_FALSE; + mutex_unlock(vb->mutex); + return false; } update_texture(screen, frame); - mutex_unlock(frames->mutex); + mutex_unlock(vb->mutex); screen_render(screen); - return SDL_TRUE; + return true; } -void screen_render(struct screen *screen) { +void +screen_render(struct screen *screen) { SDL_RenderClear(screen->renderer); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderPresent(screen->renderer); } -void screen_switch_fullscreen(struct screen *screen) { +void +screen_switch_fullscreen(struct screen *screen) { if (!screen->fullscreen) { // going to fullscreen, store the current windowed window size screen->windowed_window_size = get_native_window_size(screen->window); } - Uint32 new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; + uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(screen->window, new_mode)) { LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); return; @@ -297,24 +300,30 @@ void screen_switch_fullscreen(struct screen *screen) { screen->fullscreen = !screen->fullscreen; if (!screen->fullscreen) { // fullscreen disabled, restore expected windowed window size - SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, screen->windowed_window_size.height); + SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, + screen->windowed_window_size.height); } LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); screen_render(screen); } -void screen_resize_to_fit(struct screen *screen) { +void +screen_resize_to_fit(struct screen *screen) { if (!screen->fullscreen) { - struct size optimal_size = get_optimal_window_size(screen, screen->frame_size); - SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); + struct size optimal_size = get_optimal_window_size(screen, + screen->frame_size); + SDL_SetWindowSize(screen->window, optimal_size.width, + optimal_size.height); LOGD("Resized to optimal size"); } } -void screen_resize_to_pixel_perfect(struct screen *screen) { +void +screen_resize_to_pixel_perfect(struct screen *screen) { if (!screen->fullscreen) { - SDL_SetWindowSize(screen->window, screen->frame_size.width, screen->frame_size.height); + SDL_SetWindowSize(screen->window, screen->frame_size.width, + screen->frame_size.height); LOGD("Resized to pixel-perfect"); } } diff --git a/app/src/screen.h b/app/src/screen.h index 05729a12..5734fdc2 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -1,11 +1,13 @@ #ifndef SCREEN_H #define SCREEN_H +#include #include #include #include "common.h" -#include "frames.h" + +struct video_buffer; struct screen { SDL_Window *window; @@ -14,8 +16,9 @@ struct screen { struct size frame_size; //used only in fullscreen mode to know the windowed window size struct size windowed_window_size; - SDL_bool has_frame; - SDL_bool fullscreen; + bool has_frame; + bool fullscreen; + bool no_window; }; #define SCREEN_INITIALIZER { \ @@ -30,41 +33,46 @@ struct screen { .width = 0, \ .height = 0, \ }, \ - .has_frame = SDL_FALSE, \ - .fullscreen = SDL_FALSE, \ + .has_frame = false, \ + .fullscreen = false, \ + .no_window = false, \ } -// init SDL and set appropriate hints -SDL_bool sdl_init_and_configure(void); - // initialize default values -void screen_init(struct screen *screen); +void +screen_init(struct screen *screen); // initialize screen, create window, renderer and texture (window is hidden) -SDL_bool screen_init_rendering(struct screen *screen, - const char *device_name, - struct size frame_size, - SDL_bool always_on_top); +bool +screen_init_rendering(struct screen *screen, const char *device_name, + struct size frame_size, bool always_on_top); // show the window -void screen_show_window(struct screen *screen); +void +screen_show_window(struct screen *screen); // destroy window, renderer and texture (if any) -void screen_destroy(struct screen *screen); +void +screen_destroy(struct screen *screen); // resize if necessary and write the rendered frame into the texture -SDL_bool screen_update_frame(struct screen *screen, struct frames *frames); +bool +screen_update_frame(struct screen *screen, struct video_buffer *vb); // render the texture to the renderer -void screen_render(struct screen *screen); +void +screen_render(struct screen *screen); // switch the fullscreen mode -void screen_switch_fullscreen(struct screen *screen); +void +screen_switch_fullscreen(struct screen *screen); // resize window to optimal size (remove black borders) -void screen_resize_to_fit(struct screen *screen); +void +screen_resize_to_fit(struct screen *screen); // resize window to 1:1 (pixel-perfect) -void screen_resize_to_pixel_perfect(struct screen *screen); +void +screen_resize_to_pixel_perfect(struct screen *screen); #endif diff --git a/app/src/server.c b/app/src/server.c index 7835e716..972fbbaa 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -21,7 +20,8 @@ #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" -static const char *get_server_path(void) { +static const char * +get_server_path(void) { const char *server_path = getenv("SCRCPY_SERVER_PATH"); if (!server_path) { server_path = DEFAULT_SERVER_PATH; @@ -29,52 +29,60 @@ static const char *get_server_path(void) { return server_path; } -static SDL_bool push_server(const char *serial) { +static bool +push_server(const char *serial) { process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH); return process_check_success(process, "adb push"); } -static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) { +static bool +enable_tunnel_reverse(const char *serial, uint16_t local_port) { process_t process = adb_reverse(serial, SOCKET_NAME, local_port); return process_check_success(process, "adb reverse"); } -static SDL_bool disable_tunnel_reverse(const char *serial) { +static bool +disable_tunnel_reverse(const char *serial) { process_t process = adb_reverse_remove(serial, SOCKET_NAME); return process_check_success(process, "adb reverse --remove"); } -static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) { +static bool +enable_tunnel_forward(const char *serial, uint16_t local_port) { process_t process = adb_forward(serial, local_port, SOCKET_NAME); return process_check_success(process, "adb forward"); } -static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) { +static bool +disable_tunnel_forward(const char *serial, uint16_t local_port) { process_t process = adb_forward_remove(serial, local_port); return process_check_success(process, "adb forward --remove"); } -static SDL_bool enable_tunnel(struct server *server) { +static bool +enable_tunnel(struct server *server) { if (enable_tunnel_reverse(server->serial, server->local_port)) { - return SDL_TRUE; + return true; } LOGW("'adb reverse' failed, fallback to 'adb forward'"); - server->tunnel_forward = SDL_TRUE; + server->tunnel_forward = true; return enable_tunnel_forward(server->serial, server->local_port); } -static SDL_bool disable_tunnel(struct server *server) { +static bool +disable_tunnel(struct server *server) { if (server->tunnel_forward) { return disable_tunnel_forward(server->serial, server->local_port); } return disable_tunnel_reverse(server->serial); } -static process_t execute_server(const char *serial, - Uint16 max_size, Uint32 bit_rate, - SDL_bool tunnel_forward, const char *crop, - SDL_bool send_frame_meta) { +static process_t +execute_server(const char *serial, + uint16_t max_size, uint32_t bit_rate, + bool tunnel_forward, const char *crop, + bool send_frame_meta) { char max_size_string[6]; char bit_rate_string[11]; sprintf(max_size_string, "%"PRIu16, max_size); @@ -96,11 +104,13 @@ static process_t execute_server(const char *serial, #define IPV4_LOCALHOST 0x7F000001 -static socket_t listen_on_port(Uint16 port) { +static socket_t +listen_on_port(uint16_t port) { return net_listen(IPV4_LOCALHOST, port, 1); } -static socket_t connect_and_read_byte(Uint16 port) { +static socket_t +connect_and_read_byte(uint16_t port) { socket_t socket = net_connect(IPV4_LOCALHOST, port); if (socket == INVALID_SOCKET) { return INVALID_SOCKET; @@ -116,7 +126,8 @@ static socket_t connect_and_read_byte(Uint16 port) { return socket; } -static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) { +static socket_t +connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { do { LOGD("Remaining connection attempts: %d", (int) attempts); socket_t socket = connect_and_read_byte(port); @@ -131,7 +142,8 @@ static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) { return INVALID_SOCKET; } -static void close_socket(socket_t *socket) { +static void +close_socket(socket_t *socket) { SDL_assert(*socket != INVALID_SOCKET); net_shutdown(*socket, SHUT_RDWR); if (!net_close(*socket)) { @@ -141,30 +153,32 @@ static void close_socket(socket_t *socket) { *socket = INVALID_SOCKET; } -void server_init(struct server *server) { +void +server_init(struct server *server) { *server = (struct server) SERVER_INITIALIZER; } -SDL_bool server_start(struct server *server, const char *serial, - Uint16 local_port, Uint16 max_size, Uint32 bit_rate, - const char *crop, SDL_bool send_frame_meta) { +bool +server_start(struct server *server, const char *serial, + uint16_t local_port, uint16_t max_size, uint32_t bit_rate, + const char *crop, bool send_frame_meta) { server->local_port = local_port; if (serial) { server->serial = SDL_strdup(serial); if (!server->serial) { - return SDL_FALSE; + return false; } } if (!push_server(serial)) { - SDL_free((void *) server->serial); - return SDL_FALSE; + SDL_free(server->serial); + return false; } if (!enable_tunnel(server)) { - SDL_free((void *) server->serial); - return SDL_FALSE; + SDL_free(server->serial); + return false; } // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to @@ -173,15 +187,16 @@ SDL_bool server_start(struct server *server, const char *serial, // At the application level, the device part is "the server" because it // serves video stream and control. However, at the network level, 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 need to - // try to connect until the server socket is listening on the device. + // client can listen before starting the server app, so there is no + // need to try to connect until the server socket is listening on the + // device. server->server_socket = listen_on_port(local_port); if (server->server_socket == INVALID_SOCKET) { LOGE("Could not listen on port %" PRIu16, local_port); disable_tunnel(server); - SDL_free((void *) server->serial); - return SDL_FALSE; + SDL_free(server->serial); + return false; } } @@ -196,21 +211,23 @@ SDL_bool server_start(struct server *server, const char *serial, } disable_tunnel(server); SDL_free((void *) server->serial); - return SDL_FALSE; + return false; } - server->tunnel_enabled = SDL_TRUE; + server->tunnel_enabled = true; - return SDL_TRUE; + return true; } -socket_t server_connect_to(struct server *server) { +socket_t +server_connect_to(struct server *server) { if (!server->tunnel_forward) { server->device_socket = net_accept(server->server_socket); } else { - Uint32 attempts = 100; - Uint32 delay = 100; // ms - server->device_socket = connect_to_server(server->local_port, attempts, delay); + uint32_t attempts = 100; + uint32_t delay = 100; // ms + server->device_socket = connect_to_server(server->local_port, attempts, + delay); } if (server->device_socket == INVALID_SOCKET) { @@ -224,12 +241,13 @@ socket_t server_connect_to(struct server *server) { // we don't need the adb tunnel anymore disable_tunnel(server); // ignore failure - server->tunnel_enabled = SDL_FALSE; + server->tunnel_enabled = false; return server->device_socket; } -void server_stop(struct server *server) { +void +server_stop(struct server *server) { SDL_assert(server->process != PROCESS_NONE); if (!cmd_terminate(server->process)) { @@ -245,12 +263,13 @@ void server_stop(struct server *server) { } } -void server_destroy(struct server *server) { +void +server_destroy(struct server *server) { if (server->server_socket != INVALID_SOCKET) { close_socket(&server->server_socket); } if (server->device_socket != INVALID_SOCKET) { close_socket(&server->device_socket); } - SDL_free((void *) server->serial); + SDL_free(server->serial); } diff --git a/app/src/server.h b/app/src/server.h index b9835e13..0f25d48f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -1,18 +1,21 @@ #ifndef SERVER_H #define SERVER_H +#include +#include + #include "command.h" #include "net.h" struct server { - const char *serial; + char *serial; process_t process; socket_t server_socket; // only used if !tunnel_forward socket_t device_socket; - Uint16 local_port; - SDL_bool tunnel_enabled; - SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse" - SDL_bool send_frame_meta; // request frame PTS to be able to record properly + uint16_t local_port; + bool tunnel_enabled; + bool tunnel_forward; // use "adb forward" instead of "adb reverse" + bool send_frame_meta; // request frame PTS to be able to record properly }; #define SERVER_INITIALIZER { \ @@ -21,26 +24,31 @@ struct server { .server_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \ .local_port = 0, \ - .tunnel_enabled = SDL_FALSE, \ - .tunnel_forward = SDL_FALSE, \ - .send_frame_meta = SDL_FALSE, \ + .tunnel_enabled = false, \ + .tunnel_forward = false, \ + .send_frame_meta = false, \ } // init default values -void server_init(struct server *server); +void +server_init(struct server *server); // push, enable tunnel et start the server -SDL_bool server_start(struct server *server, const char *serial, - Uint16 local_port, Uint16 max_size, Uint32 bit_rate, - const char *crop, SDL_bool send_frame_meta); +bool +server_start(struct server *server, const char *serial, + uint16_t local_port, uint16_t max_size, uint32_t bit_rate, + const char *crop, bool send_frame_meta); // block until the communication with the server is established -socket_t server_connect_to(struct server *server); +socket_t +server_connect_to(struct server *server); // disconnect and kill the server process -void server_stop(struct server *server); +void +server_stop(struct server *server); // close and release sockets -void server_destroy(struct server *server); +void +server_destroy(struct server *server); #endif diff --git a/app/src/str_util.c b/app/src/str_util.c index 892883a6..3509331a 100644 --- a/app/src/str_util.c +++ b/app/src/str_util.c @@ -8,7 +8,8 @@ # include #endif -size_t xstrncpy(char *dest, const char *src, size_t n) { +size_t +xstrncpy(char *dest, const char *src, size_t n) { size_t i; for (i = 0; i < n - 1 && src[i] != '\0'; ++i) dest[i] = src[i]; @@ -17,7 +18,8 @@ size_t xstrncpy(char *dest, const char *src, size_t n) { return src[i] == '\0' ? i : n; } -size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { +size_t +xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { const char *const *remaining = tokens; const char *token = *remaining++; size_t i = 0; @@ -40,7 +42,8 @@ truncated: return n; } -char *strquote(const char *src) { +char * +strquote(const char *src) { size_t len = strlen(src); char *quoted = malloc(len + 3); if (!quoted) { @@ -55,7 +58,8 @@ char *strquote(const char *src) { #ifdef _WIN32 -wchar_t *utf8_to_wide_char(const char *utf8) { +wchar_t * +utf8_to_wide_char(const char *utf8) { int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); if (!len) { return NULL; diff --git a/app/src/str_util.h b/app/src/str_util.h index 6f4004f1..9ef06cbf 100644 --- a/app/src/str_util.h +++ b/app/src/str_util.h @@ -9,21 +9,25 @@ // - it does not write useless bytes if strlen(src) < n // - it returns the number of chars actually written (max n-1) if src has // been copied completely, or n if src has been truncated -size_t xstrncpy(char *dest, const char *src, size_t n); +size_t +xstrncpy(char *dest, const char *src, size_t n); // join tokens by sep into dst // returns the number of chars actually written (max n-1) if no trucation // occurred, or n if truncated -size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); +size_t +xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); // quote a string // returns the new allocated string, to be freed by the caller -char *strquote(const char *src); +char * +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); +wchar_t * +utf8_to_wide_char(const char *utf8); #endif #endif diff --git a/app/src/stream.c b/app/src/stream.c new file mode 100644 index 00000000..7ed95ee8 --- /dev/null +++ b/app/src/stream.c @@ -0,0 +1,286 @@ +#include "stream.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 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); +} diff --git a/app/src/stream.h b/app/src/stream.h new file mode 100644 index 00000000..d5eda0ac --- /dev/null +++ b/app/src/stream.h @@ -0,0 +1,43 @@ +#ifndef STREAM_H +#define STREAM_H + +#include +#include +#include + +#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 diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index 9eb537b9..fa41571e 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -11,7 +11,8 @@ #include #include "log.h" -enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) { +enum process_result +cmd_execute(const char *path, const char *const argv[], pid_t *pid) { int fd[2]; if (pipe(fd) == -1) { @@ -72,15 +73,18 @@ end: return ret; } -SDL_bool cmd_terminate(pid_t pid) { +bool +cmd_terminate(pid_t pid) { if (pid <= 0) { - LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); + LOGC("Requested to kill %d, this is an error. Please report the bug.\n", + (int) pid); abort(); } return kill(pid, SIGTERM) != -1; } -SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) { +bool +cmd_simple_wait(pid_t pid, int *exit_code) { int status; int code; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { diff --git a/app/src/sys/unix/net.c b/app/src/sys/unix/net.c index f2c82505..199cd7c2 100644 --- a/app/src/sys/unix/net.c +++ b/app/src/sys/unix/net.c @@ -1,16 +1,19 @@ #include "net.h" -# include +#include -SDL_bool net_init(void) { +bool +net_init(void) { // do nothing - return SDL_TRUE; + return true; } -void net_cleanup(void) { +void +net_cleanup(void) { // do nothing } -SDL_bool net_close(socket_t socket) { +bool +net_close(socket_t socket) { return !close(socket); } diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index 5e9876cd..1cd7274f 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -4,7 +4,8 @@ #include "log.h" #include "str_util.h" -static int build_cmd(char *cmd, size_t len, const char *const argv[]) { +static int +build_cmd(char *cmd, size_t len, const char *const argv[]) { // Windows command-line parsing is WTF: // // only make it work for this very specific program @@ -17,7 +18,8 @@ static int build_cmd(char *cmd, size_t len, const char *const argv[]) { return 0; } -enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { +enum process_result +cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { STARTUPINFOW si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); @@ -40,7 +42,8 @@ enum process_result cmd_execute(const char *path, const char *const argv[], HAND #else int flags = 0; #endif - if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { + if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, + &pi)) { free(wide); *handle = NULL; if (GetLastError() == ERROR_FILE_NOT_FOUND) { @@ -54,13 +57,16 @@ enum process_result cmd_execute(const char *path, const char *const argv[], HAND return PROCESS_SUCCESS; } -SDL_bool cmd_terminate(HANDLE handle) { +bool +cmd_terminate(HANDLE handle) { return TerminateProcess(handle, 1) && CloseHandle(handle); } -SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) { +bool +cmd_simple_wait(HANDLE handle, DWORD *exit_code) { DWORD code; - if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { + if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 + || !GetExitCodeProcess(handle, &code)) { // cannot wait or retrieve the exit code code = -1; // max value, it's unsigned } diff --git a/app/src/sys/win/net.c b/app/src/sys/win/net.c index 140c1bce..dc483682 100644 --- a/app/src/sys/win/net.c +++ b/app/src/sys/win/net.c @@ -2,20 +2,23 @@ #include "log.h" -SDL_bool net_init(void) { +bool +net_init(void) { WSADATA wsa; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; if (res < 0) { LOGC("WSAStartup failed with error %d", res); - return SDL_FALSE; + return false; } - return SDL_TRUE; + return true; } -void net_cleanup(void) { +void +net_cleanup(void) { WSACleanup(); } -SDL_bool net_close(socket_t socket) { +bool +net_close(socket_t socket) { return !closesocket(socket); } diff --git a/app/src/tiny_xpm.c b/app/src/tiny_xpm.c index 3b80b88e..01fd280f 100644 --- a/app/src/tiny_xpm.c +++ b/app/src/tiny_xpm.c @@ -1,5 +1,7 @@ #include "tiny_xpm.h" +#include +#include #include #include @@ -7,19 +9,20 @@ struct index { char c; - Uint32 color; + uint32_t color; }; -static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) { +static bool +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 for (int i = 0; i < len; ++i) { if (index[i].c == c) { *color = index[i].color; - return SDL_TRUE; + return true; } } *color = 0; - return SDL_FALSE; + return false; } // We encounter some problems with SDL2_image on MSYS2 (Windows), @@ -30,7 +33,8 @@ static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) // // Parameter is not "const char *" because XPM formats are generally stored in a // (non-const) "char *" -SDL_Surface *read_xpm(char *xpm[]) { +SDL_Surface * +read_xpm(char *xpm[]) { #if SDL_ASSERT_LEVEL >= 2 // patch the XPM to change the icon color in debug mode xpm[2] = ". c #CC00CC"; @@ -69,7 +73,7 @@ SDL_Surface *read_xpm(char *xpm[]) { } // parse image - Uint32 *pixels = SDL_malloc(4 * width * height); + uint32_t *pixels = SDL_malloc(4 * width * height); if (!pixels) { LOGE("Could not allocate icon memory"); return NULL; @@ -78,23 +82,23 @@ SDL_Surface *read_xpm(char *xpm[]) { const char *line = xpm[1 + colors + y]; for (int x = 0; x < width; ++x) { char c = line[x]; - Uint32 color; - SDL_bool color_found = find_color(index, colors, c, &color); + uint32_t color; + bool color_found = find_color(index, colors, c, &color); SDL_assert(color_found); pixels[y * width + x] = color; } } #if SDL_BYTEORDER == SDL_BIG_ENDIAN - Uint32 amask = 0x000000ff; - Uint32 rmask = 0x0000ff00; - Uint32 gmask = 0x00ff0000; - Uint32 bmask = 0xff000000; + uint32_t amask = 0x000000ff; + uint32_t rmask = 0x0000ff00; + uint32_t gmask = 0x00ff0000; + uint32_t bmask = 0xff000000; #else // little endian, like x86 - Uint32 amask = 0xff000000; - Uint32 rmask = 0x00ff0000; - Uint32 gmask = 0x0000ff00; - Uint32 bmask = 0x000000ff; + uint32_t amask = 0xff000000; + uint32_t rmask = 0x00ff0000; + uint32_t gmask = 0x0000ff00; + uint32_t bmask = 0x000000ff; #endif SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels, diff --git a/app/src/tiny_xpm.h b/app/src/tiny_xpm.h index 4bee7052..85dea5c2 100644 --- a/app/src/tiny_xpm.h +++ b/app/src/tiny_xpm.h @@ -3,6 +3,7 @@ #include -SDL_Surface *read_xpm(char *xpm[]); +SDL_Surface * +read_xpm(char *xpm[]); #endif diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c new file mode 100644 index 00000000..8f1d1d9d --- /dev/null +++ b/app/src/video_buffer.c @@ -0,0 +1,116 @@ +#include "video_buffer.h" + +#include +#include +#include +#include + +#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 +} diff --git a/app/src/frames.h b/app/src/video_buffer.h similarity index 50% rename from app/src/frames.h rename to app/src/video_buffer.h index 437838fc..93222236 100644 --- a/app/src/frames.h +++ b/app/src/video_buffer.h @@ -1,8 +1,8 @@ -#ifndef FRAMES_H -#define FRAMES_H +#ifndef VIDEO_BUFFER_H +#define VIDEO_BUFFER_H +#include #include -#include #include "config.h" #include "fps_counter.h" @@ -10,33 +10,40 @@ // forward declarations typedef struct AVFrame AVFrame; -struct frames { +struct video_buffer { AVFrame *decoding_frame; AVFrame *rendering_frame; SDL_mutex *mutex; #ifndef SKIP_FRAMES - SDL_bool stopped; + bool interrupted; SDL_cond *rendering_frame_consumed_cond; #endif - SDL_bool rendering_frame_consumed; + bool rendering_frame_consumed; struct fps_counter fps_counter; }; -SDL_bool frames_init(struct frames *frames); -void frames_destroy(struct frames *frames); +bool +video_buffer_init(struct video_buffer *vb); -// set the decoder frame as ready for rendering +void +video_buffer_destroy(struct video_buffer *vb); + +// set the decoded frame as ready for rendering // this function locks frames->mutex during its execution -// returns true if the previous frame had been consumed -SDL_bool frames_offer_decoded_frame(struct frames *frames); +// the output flag is set to report whether the previous frame has been skipped +void +video_buffer_offer_decoded_frame(struct video_buffer *vb, + bool *previous_frame_skipped); // mark the rendering frame as consumed and return it // MUST be called with frames->mutex locked!!! // the caller is expected to render the returned frame to some texture before // unlocking frames->mutex -const AVFrame *frames_consume_rendered_frame(struct frames *frames); +const AVFrame * +video_buffer_consume_rendered_frame(struct video_buffer *vb); // wake up and avoid any blocking call -void frames_stop(struct frames *frames); +void +video_buffer_interrupt(struct video_buffer *vb); #endif diff --git a/meson.build b/meson.build index ee97b08a..eb7ee380 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.7', + version: '1.8', meson_version: '>= 0.37', default_options: 'c_std=c11') diff --git a/server/build.gradle b/server/build.gradle index 0a807865..65d1e558 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 27 - versionCode 8 - versionName "1.7" + versionCode 9 + versionName "1.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/meson.build b/server/meson.build index 202ec3a1..327e6aca 100644 --- a/server/meson.build +++ b/server/meson.build @@ -10,6 +10,10 @@ if prebuilt_server == '' install: true, install_dir: 'share/scrcpy') else + if not prebuilt_server.startswith('/') + # relative path needs some trick + prebuilt_server = meson.source_root() + '/' + prebuilt_server + endif custom_target('scrcpy-server-prebuilt', input: prebuilt_server, output: 'scrcpy-server.jar', diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java index 3c9cbda1..8b0f9e2b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java @@ -12,6 +12,8 @@ public final class ControlEvent { public static final int TYPE_COMMAND = 4; 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 String text; diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index d2862acb..60122c5a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -132,6 +132,14 @@ public final class Device { this.rotationListener = rotationListener; } + public void expandNotificationPanel() { + serviceManager.getStatusBarManager().expandNotificationsPanel(); + } + + public void collapsePanels() { + serviceManager.getStatusBarManager().collapsePanels(); + } + static Rect flipRect(Rect crop) { return new Rect(crop.top, crop.left, crop.bottom, crop.right); } diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index 547e20ca..5fa2cab2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.InputManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; import android.graphics.Point; import android.os.SystemClock; @@ -172,6 +173,12 @@ public class EventController { switch (action) { case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: return pressBackOrTurnScreenOn(); + case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL: + device.expandNotificationPanel(); + return true; + case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL: + device.collapsePanels(); + return true; default: Ln.w("Unsupported command: " + action); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index ec63e81d..22eb6a54 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -87,7 +87,6 @@ public class ScreenEncoder implements Device.RotationListener { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - while (!consumeRotationChange() && !eof) { int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 2d98d0a8..3bcdc0e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -14,6 +14,7 @@ public final class ServiceManager { private DisplayManager displayManager; private InputManager inputManager; private PowerManager powerManager; + private StatusBarManager statusBarManager; public ServiceManager() { try { @@ -60,4 +61,11 @@ public final class ServiceManager { } return powerManager; } + + public StatusBarManager getStatusBarManager() { + if (statusBarManager == null) { + statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService")); + } + return statusBarManager; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java new file mode 100644 index 00000000..aacd5f1a --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -0,0 +1,41 @@ +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); + } + } +}