Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
e3a07c3309 update-on-event 2018-09-13 22:24:26 +02:00
Romain Vimont
2e40285244 hidpi-scale-wip 2018-09-13 20:37:45 +02:00
38 changed files with 402 additions and 1115 deletions

251
BUILD.md
View File

@@ -1,251 +0,0 @@
# Build scrcpy
Here are the instructions to build _scrcpy_ (client and server).
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server
## Requirements
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions.
[adb]: https://developer.android.com/studio/command-line/adb.html
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## System-specific steps
### Linux
Install the required packages from your package manager.
#### Debian/Ubuntu
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson ninja-build \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install
it from `pip3`:
```bash
sudo apt install python3-pip
pip3 install meson
```
#### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
### Windows
#### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
#### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
### Mac OS
Install the packages with [Homebrew]:
[Homebrew]: https://brew.sh/
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps
If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_HOME` to its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
If you don't want to build the server, use the [prebuilt server].
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
install` must be run as root)._
[ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a
### Run
To run without installing:
```bash
./run x [options]
```
### Install
After a successful build, you can install _scrcpy_ on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.5.jar`][direct-scrcpy-server]
_(SHA-256: d97aab6f60294e33e7ff79c2856ad3e01f912892395131f4f337e9ece03c24de)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-server-v1.5.jar
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```

3
FAQ.md
View File

@@ -26,11 +26,10 @@ If you still encounter problems, please see [issue 9].
### I get a black screen for some applications like Silence
This is expected, they requested to [protect] the screen.
This is expected, they requested to protect the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[protect]: https://developer.android.com/reference/android/view/Display#FLAG_SECURE
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].

View File

@@ -107,7 +107,7 @@ dist-win32: build-server build-win32 build-win32-noconsole
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.9/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.8/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@@ -121,7 +121,7 @@ dist-win64: build-server build-win64 build-win64-noconsole
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.9/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.8/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \

398
README.md
View File

@@ -1,4 +1,4 @@
# scrcpy (v1.5)
# scrcpy (v1.3)
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.
@@ -11,54 +11,156 @@ It works on _GNU/Linux_, _Windows_ and _MacOS_.
The Android part requires at least API 21 (Android 5.0).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, just [download scrcpy for Windows](#windows), `adb` is included.
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg] and [LibSDL2]. On Windows, they are included in the
[prebuilt application](#windows).
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
On some devices, you also need to enable [an additional option][control] to
control it using keyboard and mouse.
## Build and install
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### System-specific steps
#### Linux
## Get the app
Install the required packages from your package manager.
##### Debian/Ubuntu
### Linux
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
For Arch Linux, two [AUR] packages have been created by users:
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Windows
#### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-win32-v1.5.zip`][direct-win32]
_(SHA-256: 46ae0d4c1c6bd049ec4a30080d2ad91a32b31d3f758afdca2c3a915ecabf02c1)_
- [`scrcpy-win64-v1.5.zip`][direct-win64]
_(SHA-256: 89daa07325129617cf943a84bc4e304ee5e57118416fe265b9b5d4a1bf87c501)_
- [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win32-v1.5.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win64-v1.5.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win32-v1.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win64-v1.3.zip
You can also [build the app manually][BUILD].
Instead, you may want to build it manually.
In that case, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
##### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
### Mac OS
##### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Mac OS
The application is available in [Homebrew]. Just install it:
@@ -68,18 +170,106 @@ The application is available in [Homebrew]. Just install it:
brew install scrcpy
```
You need `adb`, accessible from your `PATH`. If you don't have it yet:
Instead, you may want to build it manually. Install the packages:
```bash
brew cask install android-platform-tools
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
You can also [build the app manually][BUILD].
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
### Common steps
Install the [Android SDK] (_Android Studio_), and set `ANDROID_HOME` to
its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
```bash
ninja run
```
Or you can install it on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
#### Prebuilt server
Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.jar
In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
Plug an Android device, and execute:
_At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
```bash
scrcpy
@@ -91,155 +281,50 @@ It accepts command-line arguments, listed by:
scrcpy --help
```
## Features
### Reduce size
Sometimes, it is useful to mirror an Android device at a lower definition to
increase performances.
To limit both width and height to some value (e.g. 1024):
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # short version
scrcpy -b 2M
```
The other dimension is computed to that the device aspect-ratio is preserved.
That way, a device in 1920×1080 will be mirrored at 1024×576.
### Change bit-rate
The default bit-rate is 8Mbps. To change the video bitrate (e.g. to 2Mbps):
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
screen is smaller, or cannot decode such a high definition):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # short version
scrcpy -m 1024
```
### Crop
The device screen may be cropped to mirror only part of the screen.
This is useful for example to mirror only 1 eye of the Oculus Go:
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
scrcpy -c 1224:1440:0:0 # short version
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If `--max-size` is also specified, resizing is applied after cropping.
### Wireless
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP:
1. Connect the device to the same Wi-Fi as your computer.
2. Get your device IP address (in Settings → About phone → Status).
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
4. Unplug your device.
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
6. Run `scrcpy` as usual.
It may be useful to decrease the bit-rate and the definition:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # short version
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### Record screen
It is possible to record the screen while mirroring:
```bash
scrcpy --record file.mp4
scrcpy -r file.mp4
```
"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.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Multi-devices
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
scrcpy -s 0123456789abcdef
```
You can start several instances of _scrcpy_ for several devices.
### Fullscreen
The app may be started directly in fullscreen:
To show physical touches while scrcpy is running:
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
### Show touches
For presentations, it may be useful to show physical touches (on the physical
device).
Android provides this feature in _Developers options_.
_Scrcpy_ provides an option to enable this feature on start and disable on exit:
```bash
scrcpy --show-touches
scrcpy -t
```
Note that it only shows _physical_ touches (with the finger on the device).
The app may be started directly in fullscreen:
```
scrcpy -f
```
### Install APK
To run without installing:
To install an APK, drag & drop an APK file (ending with `.apk`) to the _scrcpy_
window.
```bash
./run x [options]
```
There is no visual feedback, a log is printed to the console.
### Push file to device
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
_scrcpy_ window.
There is no visual feedback, a log is printed to the console.
### Forward audio
Audio is not forwarded by _scrcpy_.
There is a limited solution using [AOA], implemented in the [`audio`] branch. If
you are interested, see [issue 14].
[AOA]: https://source.android.com/devices/accessories/aoa2
[`audio`]: https://github.com/Genymobile/scrcpy/commits/audio
[issue 14]: https://github.com/Genymobile/scrcpy/issues/14
(where `x` is your build directory).
## Shortcuts
@@ -253,12 +338,14 @@ you are interested, see [issue 14].
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ (`Cmd`+`↑` on MacOS) |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| install APK from computer | drag & drop APK file |
| push file to `/sdcard/` | drag & drop non-APK file |
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
@@ -274,13 +361,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## How to build?
See [BUILD].
[BUILD]: BUILD.md
## Common issues
See the [FAQ](FAQ.md).

View File

@@ -12,7 +12,6 @@ src = [
'src/input_manager.c',
'src/lock_util.c',
'src/net.c',
'src/recorder.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
@@ -86,7 +85,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
conf.set_quoted('SCRCPY_VERSION', '1.3')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))

View File

@@ -1,28 +0,0 @@
#ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H
#include <SDL2/SDL_stdinc.h>
static inline void buffer_write16be(Uint8 *buf, Uint16 value) {
buf[0] = value >> 8;
buf[1] = value;
}
static inline void buffer_write32be(Uint8 *buf, Uint32 value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
buf[3] = value;
}
static inline Uint32 buffer_read32be(Uint8 *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;
}
#endif

View File

@@ -6,11 +6,10 @@
#include "common.h"
#include "log.h"
#include "str_util.h"
static const char *adb_command;
static inline const char *get_adb_command(void) {
static inline const char *get_adb_command() {
if (!adb_command) {
adb_command = getenv("ADB");
if (!adb_command)
@@ -90,49 +89,23 @@ 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) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"push", local, remote};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return proc;
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
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)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
// Windows will parse the string, so the local name must be quoted (see sys/win/command.c)
size_t len = strlen(local);
char quoted[len + 3];
memcpy(&quoted[1], local, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
local = quoted;
#endif
const char *const adb_cmd[] = {"install", "-r", local};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
#endif
return proc;
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_remove_path(const char *serial, const char *path) {

View File

@@ -3,15 +3,26 @@
#include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "buffer_util.h"
#include "lock_util.h"
#include "log.h"
static inline void write16(Uint8 *buf, Uint16 value) {
buf[0] = value >> 8;
buf[1] = value;
}
static inline void write32(Uint8 *buf, Uint32 value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
buf[3] = value;
}
static void write_position(Uint8 *buf, const struct position *position) {
buffer_write16be(&buf[0], position->point.x);
buffer_write16be(&buf[2], position->point.y);
buffer_write16be(&buf[4], position->screen_size.width);
buffer_write16be(&buf[6], position->screen_size.height);
write16(&buf[0], position->point.x);
write16(&buf[2], position->point.y);
write16(&buf[4], position->screen_size.width);
write16(&buf[6], position->screen_size.height);
}
int control_event_serialize(const struct control_event *event, unsigned char *buf) {
@@ -19,8 +30,8 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE:
buf[1] = event->keycode_event.action;
buffer_write32be(&buf[2], event->keycode_event.keycode);
buffer_write32be(&buf[6], event->keycode_event.metastate);
write32(&buf[2], event->keycode_event.keycode);
write32(&buf[6], event->keycode_event.metastate);
return 10;
case CONTROL_EVENT_TYPE_TEXT: {
// write length (1 byte) + date (non nul-terminated)
@@ -29,19 +40,19 @@ 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);
write16(&buf[1], (Uint16) len);
memcpy(&buf[3], event->text_event.text, len);
return 3 + len;
}
case CONTROL_EVENT_TYPE_MOUSE:
buf[1] = event->mouse_event.action;
buffer_write32be(&buf[2], event->mouse_event.buttons);
write32(&buf[2], event->mouse_event.buttons);
write_position(&buf[6], &event->mouse_event.position);
return 14;
case CONTROL_EVENT_TYPE_SCROLL:
write_position(&buf[1], &event->scroll_event.position);
buffer_write32be(&buf[9], (Uint32) event->scroll_event.hscroll);
buffer_write32be(&buf[13], (Uint32) event->scroll_event.vscroll);
write32(&buf[9], (Uint32) event->scroll_event.hscroll);
write32(&buf[13], (Uint32) event->scroll_event.vscroll);
return 17;
case CONTROL_EVENT_TYPE_COMMAND:
buf[1] = event->command_event.action;

View File

@@ -70,7 +70,7 @@ 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 SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@@ -86,39 +86,6 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return SDL_FALSE;
}
// if ALT and META are not pressed, also handle letters and space
switch (from) {
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
}
}
@@ -159,12 +126,11 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
return SDL_FALSE;
}
Uint16 mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
}
to->keycode_event.metastate = convert_meta_state(mod);
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
return SDL_TRUE;
}

View File

@@ -1,134 +1,22 @@
#include "decoder.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "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) {
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
struct receiver_state *state = &decoder->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining)
buf_size = state->remaining;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
return net_recv(decoder->video_socket, buf, buf_size);
}
// set the decoded frame as ready for rendering, and notify
@@ -182,15 +70,7 @@ static int run_decoder(void *data) {
goto run_finally_free_format_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);
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'
@@ -206,12 +86,6 @@ static int run_decoder(void *data) {
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;
@@ -241,7 +115,6 @@ static int run_decoder(void *data) {
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) {
@@ -251,23 +124,6 @@ static int run_decoder(void *data) {
packet.data += len;
}
#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) {
@@ -278,10 +134,6 @@ static int run_decoder(void *data) {
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_freep(&avio_ctx);
@@ -296,11 +148,9 @@ run_end:
return 0;
}
void decoder_init(struct decoder *decoder, struct frames *frames,
socket_t video_socket, struct recorder *recorder) {
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
decoder->frames = frames;
decoder->video_socket = video_socket;
decoder->recorder = recorder;
}
SDL_bool decoder_start(struct decoder *decoder) {
@@ -311,6 +161,7 @@ SDL_bool decoder_start(struct decoder *decoder) {
LOGC("Could not start decoder thread");
return SDL_FALSE;
}
return SDL_TRUE;
}

View File

@@ -4,31 +4,18 @@
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "common.h"
#include "net.h"
struct frames;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
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;
};
void decoder_init(struct decoder *decoder, struct frames *frames,
socket_t video_socket, struct recorder *recoder);
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket);
SDL_bool decoder_start(struct decoder *decoder);
void decoder_stop(struct decoder *decoder);
void decoder_join(struct decoder *decoder);

View File

@@ -1,6 +1,5 @@
#include "input_manager.h"
#include <SDL2/SDL_assert.h>
#include "convert.h"
#include "lock_util.h"
#include "log.h"
@@ -18,9 +17,16 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int
*y = (int) (*y / scale_y) - viewport.y;
}
static void get_mouse_point(struct screen *screen, int *x, int *y) {
SDL_GetMouseState(x, y);
convert_to_renderer_coordinates(screen->renderer, x, y);
static struct point get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
return (struct point) {
.x = (Uint16) x,
.y = (Uint16) y,
};
}
static const int ACTION_DOWN = 1;
@@ -123,12 +129,6 @@ static void clipboard_paste(struct controller *controller) {
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');
// letters and space are handled as raw key event
return;
}
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -137,7 +137,6 @@ void input_manager_process_text_input(struct input_manager *input_manager,
return;
}
if (!controller_push_event(input_manager->controller, &control_event)) {
SDL_free(control_event.text_event.text);
LOGW("Cannot send text event");
}
}
@@ -145,17 +144,9 @@ 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);
if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded
// to the device
return;
}
// capture all Ctrl events
if (ctrl | meta) {
if (ctrl) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
@@ -167,73 +158,61 @@ void input_manager_process_key(struct input_manager *input_manager,
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
if (ctrl && !meta && !repeat) {
if (!repeat) {
action_home(input_manager->controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (ctrl && !meta && !repeat) {
if (!repeat) {
action_back(input_manager->controller, action);
}
return;
case SDLK_s:
if (ctrl && !meta && !repeat) {
if (!repeat) {
action_app_switch(input_manager->controller, action);
}
return;
case SDLK_m:
if (ctrl && !meta && !repeat) {
if (!repeat) {
action_menu(input_manager->controller, action);
}
return;
case SDLK_p:
if (ctrl && !meta && !repeat) {
if (!repeat) {
action_power(input_manager->controller, action);
}
return;
case SDLK_DOWN:
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_down(input_manager->controller, action);
}
// forward repeated events
action_volume_down(input_manager->controller, action);
return;
case SDLK_UP:
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_up(input_manager->controller, action);
}
// forward repeated events
action_volume_up(input_manager->controller, action);
return;
case SDLK_v:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
return;
case SDLK_f:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
return;
case SDLK_x:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
if (!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 (!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) {
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
return;
@@ -264,18 +243,8 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
}
}
static SDL_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) {
SDL_bool outside_device_screen = is_outside_device_screen(input_manager,
event->x,
event->y);
if (event->type == SDL_MOUSEBUTTONDOWN) {
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller);
@@ -286,19 +255,17 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2
&& outside_device_screen) {
screen_resize_to_fit(input_manager->screen);
return;
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
SDL_bool outside_device_screen =
event->x < 0 || event->x >= input_manager->screen->frame_size.width ||
event->y < 0 || event->y >= input_manager->screen->frame_size.height;
if (outside_device_screen) {
screen_resize_to_fit(input_manager->screen);
return;
}
// otherwise, send the click event to the device
}
// otherwise, send the click event to the device
}
if (outside_device_screen) {
// ignore
return;
}
struct control_event 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)) {
@@ -309,22 +276,9 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event) {
int x;
int y;
get_mouse_point(input_manager->screen, &x, &y);
if (is_outside_device_screen(input_manager, x, y)) {
// ignore
return;
}
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
struct position position = {
.screen_size = input_manager->screen->frame_size,
.point = {
.x = (Uint16) x,
.y = (Uint16) y,
},
.point = get_mouse_point(input_manager->screen),
};
struct control_event control_event;
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {

View File

@@ -11,7 +11,6 @@
struct args {
const char *serial;
const char *crop;
const char *record_filename;
SDL_bool fullscreen;
SDL_bool help;
SDL_bool version;
@@ -54,9 +53,6 @@ static void usage(const char *arg0) {
" Set the TCP port the client listens on.\n"
" Default is %d.\n"
"\n"
" -r, --record file.mp4\n"
" Record screen to file.\n"
"\n"
" -s, --serial\n"
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
@@ -124,7 +120,7 @@ static void usage(const char *arg0) {
}
static void print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "scrcpy v%s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
@@ -212,14 +208,13 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"record", required_argument, NULL, 'r'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
};
int c;
while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tv", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
@@ -245,9 +240,6 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
return SDL_FALSE;
}
break;
case 'r':
args->record_filename = optarg;
break;
case 's':
args->serial = optarg;
break;
@@ -281,7 +273,6 @@ int main(int argc, char *argv[]) {
struct args args = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
@@ -319,7 +310,6 @@ int main(int argc, char *argv[]) {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.record_filename = args.record_filename,
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
@@ -329,11 +319,5 @@ int main(int argc, char *argv[]) {
avformat_network_deinit(); // ignore failure
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
getchar();
}
#endif
return res;
}

View File

@@ -1,118 +0,0 @@
#include "recorder.h"
#include <libavutil/time.h>
#include "config.h"
#include "log.h"
static const AVOutputFormat *find_mp4_muxer(void) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
void *opaque = NULL;
#endif
const AVOutputFormat *oformat = NULL;
do {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
oformat = av_muxer_iterate(&opaque);
#else
oformat = av_oformat_next(oformat);
#endif
// until null or with name "mp4"
} while (oformat && strcmp(oformat->name, "mp4"));
return oformat;
}
SDL_bool recorder_init(struct recorder *recorder, const char *filename,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
if (!recorder->filename) {
LOGE("Cannot strdup filename");
return SDL_FALSE;
}
recorder->declared_frame_size = declared_frame_size;
return SDL_TRUE;
}
void recorder_destroy(struct recorder *recorder) {
SDL_free(recorder->filename);
}
SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
const AVOutputFormat *mp4 = find_mp4_muxer();
if (!mp4) {
LOGE("Could not find mp4 muxer");
return SDL_FALSE;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
return SDL_FALSE;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) mp4;
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
#else
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codec->codec_id = input_codec->id;
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height;
#endif
ostream->time_base = (AVRational) {1, 1000000}; // timestamps in us
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
return SDL_TRUE;
}
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);
}
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
}
SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) {
return av_write_frame(recorder->ctx, packet) >= 0;
}

View File

@@ -1,24 +0,0 @@
#ifndef RECORDER_H
#define RECORDER_H
#include <libavformat/avformat.h>
#include <SDL2/SDL_stdinc.h>
#include "common.h"
struct recorder {
char *filename;
AVFormatContext *ctx;
struct size declared_frame_size;
};
SDL_bool recorder_init(struct recorder *recoder, const char *filename,
struct size declared_frame_size);
void recorder_destroy(struct recorder *recorder);
SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec);
void recorder_close(struct recorder *recorder);
SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet);
#endif

View File

@@ -20,7 +20,6 @@
#include "log.h"
#include "lock_util.h"
#include "net.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "tiny_xpm.h"
@@ -31,7 +30,6 @@ static struct frames frames;
static struct decoder decoder;
static struct controller controller;
static struct file_handler file_handler;
static struct recorder recorder;
static struct input_manager input_manager = {
.controller = &controller,
@@ -90,6 +88,7 @@ static SDL_bool event_loop(void) {
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_update_scale(&screen);
screen_render(&screen);
break;
}
@@ -140,10 +139,8 @@ static void wait_show_touches(process_t process) {
}
SDL_bool scrcpy(const struct scrcpy_options *options) {
SDL_bool send_frame_meta = !!options->record_filename;
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop,
send_frame_meta)) {
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
}
@@ -157,6 +154,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
SDL_bool ret = SDL_TRUE;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
}
if (!sdl_init_and_configure()) {
ret = SDL_FALSE;
goto finally_destroy_server;
@@ -193,24 +194,14 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_frames;
}
struct recorder *rec = NULL;
if (options->record_filename) {
if (!recorder_init(&recorder, options->record_filename, frame_size)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_file_handler;
}
rec = &recorder;
}
decoder_init(&decoder, &frames, device_socket, rec);
decoder_init(&decoder, &frames, device_socket);
// now we consumed the header values, the socket receives the video stream
// start the decoder
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_recorder;
goto finally_destroy_file_handler;
}
if (!controller_init(&controller, device_socket)) {
@@ -256,10 +247,6 @@ finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_recorder:
if (options->record_filename) {
recorder_destroy(&recorder);
}
finally_destroy_frames:
frames_destroy(&frames);
finally_destroy_server:

View File

@@ -6,7 +6,6 @@
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;

View File

@@ -132,6 +132,17 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size);
}
// apply hidpi scaling and call SDL_RenderSetLogicalSize
static inline SDL_bool render_set_scaled_logical_size(struct screen *screen, struct size size) {
int w, h, dw, dh;
SDL_GL_GetDrawableSize(screen->window, &w, &h);
SDL_GetWindowSize(screen->window, &dw, &dh);
// use 32 bits unsigned not to lose precision (width and height fit in 16 bits)
int scaled_x = (Uint32) size.width * (Uint32) w / (Uint32) dw;
int scaled_y = (Uint32) size.height * (Uint32) h / (Uint32) dh;
return SDL_RenderSetLogicalSize(screen->renderer, scaled_x, scaled_y);
}
void screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
@@ -163,7 +174,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
return SDL_FALSE;
}
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
if (render_set_scaled_logical_size(screen, frame_size)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return SDL_FALSE;
@@ -208,7 +219,7 @@ 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)) {
if (render_set_scaled_logical_size(screen, new_frame_size)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return SDL_FALSE;
}
@@ -302,3 +313,7 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
LOGD("Resized to pixel-perfect");
}
}
void screen_update_scale(struct screen *screen) {
render_set_scaled_logical_size(screen, screen->frame_size);
}

View File

@@ -66,4 +66,8 @@ void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen);
// recompute the scale in case the window moved from/to another screen with a
// different HiDPI
void screen_update_scale(struct screen *screen);
#endif

View File

@@ -78,9 +78,7 @@ static SDL_bool disable_tunnel(struct server *server) {
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) {
LOGD("tunnel_forward=%d\n", (int) tunnel_forward);
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -94,8 +92,7 @@ static process_t execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "''",
send_frame_meta ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@@ -151,9 +148,8 @@ 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) {
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop) {
server->local_port = local_port;
if (serial) {
@@ -194,10 +190,8 @@ SDL_bool server_start(struct server *server, const char *serial,
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate,
server->tunnel_forward, crop,
send_frame_meta);
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);

View File

@@ -12,7 +12,6 @@ struct server {
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
SDL_bool server_copied_to_device;
};
@@ -24,7 +23,6 @@ struct server {
.local_port = 0, \
.tunnel_enabled = SDL_FALSE, \
.tunnel_forward = SDL_FALSE, \
.send_frame_meta = SDL_FALSE, \
.server_copied_to_device = SDL_FALSE, \
}
@@ -32,9 +30,8 @@ struct 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);
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop);
// block until the communication with the server is established
socket_t server_connect_to(struct server *server);

View File

@@ -1,8 +1,5 @@
#include "str_util.h"
#include <stdlib.h>
#include <string.h>
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
@@ -34,16 +31,3 @@ truncated:
dst[n - 1] = '\0';
return n;
}
char *strquote(const char *src) {
size_t len = strlen(src);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
memcpy(&quoted[1], src, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
return quoted;
}

View File

@@ -16,8 +16,4 @@ size_t xstrncpy(char *dest, const char *src, size_t n);
// occurred, or n if truncated
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);
#endif

View File

@@ -1,5 +1,3 @@
#define _POSIX_SOURCE // for kill()
#include "command.h"
#include <errno.h>

View File

@@ -4,27 +4,20 @@
#include "log.h"
#include "str_util.h"
static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return -1;
}
return 0;
}
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
char cmd[256];
if (build_cmd(cmd, sizeof(cmd), argv)) {
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
*handle = NULL;
return PROCESS_ERROR_GENERIC;
}

View File

@@ -3,7 +3,7 @@
#include "control_event.h"
static void test_control_event_queue_empty(void) {
static void test_control_event_queue_empty() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -25,7 +25,7 @@ static void test_control_event_queue_empty(void) {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_full(void) {
static void test_control_event_queue_full() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -43,7 +43,7 @@ static void test_control_event_queue_full(void) {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_push_take(void) {
static void test_control_event_queue_push_take() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -87,7 +87,7 @@ static void test_control_event_queue_push_take(void) {
control_event_queue_destroy(&queue);
}
int main(void) {
int main() {
test_control_event_queue_empty();
test_control_event_queue_full();
test_control_event_queue_push_take();

View File

@@ -3,7 +3,7 @@
#include "control_event.h"
static void test_serialize_keycode_event(void) {
static void test_serialize_keycode_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_KEYCODE,
.keycode_event = {
@@ -26,7 +26,7 @@ static void test_serialize_keycode_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_text_event(void) {
static void test_serialize_text_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_TEXT,
.text_event = {
@@ -46,7 +46,7 @@ static void test_serialize_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_long_text_event(void) {
static void test_serialize_long_text_event() {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH];
@@ -66,7 +66,7 @@ static void test_serialize_long_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_mouse_event(void) {
static void test_serialize_mouse_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_MOUSE,
.mouse_event = {
@@ -99,7 +99,7 @@ static void test_serialize_mouse_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_scroll_event(void) {
static void test_serialize_scroll_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_SCROLL,
.scroll_event = {
@@ -132,10 +132,11 @@ static void test_serialize_scroll_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(void) {
int main() {
test_serialize_keycode_event();
test_serialize_text_event();
test_serialize_long_text_event();
test_serialize_mouse_event();
test_serialize_scroll_event();
return 0;
}

View File

@@ -3,7 +3,7 @@
#include "str_util.h"
static void test_xstrncpy_simple(void) {
static void test_xstrncpy_simple() {
char s[] = "xxxxxxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -20,7 +20,7 @@ static void test_xstrncpy_simple(void) {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_just_fit(void) {
static void test_xstrncpy_just_fit() {
char s[] = "xxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -34,7 +34,7 @@ static void test_xstrncpy_just_fit(void) {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_truncated(void) {
static void test_xstrncpy_truncated() {
char s[] = "xxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -48,7 +48,7 @@ static void test_xstrncpy_truncated(void) {
assert(!strncmp("abcdef", s, 3));
}
static void test_xstrjoin_simple(void) {
static void test_xstrjoin_simple() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -66,7 +66,7 @@ static void test_xstrjoin_simple(void) {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_just_fit(void) {
static void test_xstrjoin_just_fit() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -81,7 +81,7 @@ static void test_xstrjoin_just_fit(void) {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_truncated_in_token(void) {
static void test_xstrjoin_truncated_in_token() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -96,7 +96,7 @@ static void test_xstrjoin_truncated_in_token(void) {
assert(!strcmp("abc d", s));
}
static void test_xstrjoin_truncated_before_sep(void) {
static void test_xstrjoin_truncated_before_sep() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -111,7 +111,7 @@ static void test_xstrjoin_truncated_before_sep(void) {
assert(!strcmp("abc de", s));
}
static void test_xstrjoin_truncated_after_sep(void) {
static void test_xstrjoin_truncated_after_sep() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -126,7 +126,7 @@ static void test_xstrjoin_truncated_after_sep(void) {
assert(!strcmp("abc de ", s));
}
int main(void) {
int main() {
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();

View File

@@ -17,4 +17,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.9/i686-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'

View File

@@ -17,4 +17,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.9/x86_64-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'

View File

@@ -15,4 +15,3 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

View File

@@ -1,7 +1,4 @@
project('scrcpy', 'c',
version: '1.5',
meson_version: '>= 0.37',
default_options: 'c_std=c11')
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c11')
if get_option('build_app')
subdir('app')

View File

@@ -30,11 +30,11 @@ prepare-ffmpeg-dev-win64:
ffmpeg-4.0.2-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.9-mingw.tar.gz \
0f9f00d0f2a9a95dfb5cce929718210c3f85432cc2e9d4abade4adcb7f6bb39d \
SDL2-2.0.9
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \
db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.0-windows.zip \
e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
platform-tools

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 27
versionCode 6
versionName "1.5"
versionCode 4
versionName "1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -14,18 +14,9 @@ public class IO {
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
// ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
// count the remaining bytes manually.
// See <https://github.com/Genymobile/scrcpy/issues/291>.
int remaining = from.remaining();
while (remaining > 0) {
while (from.hasRemaining()) {
try {
int w = Os.write(fd, from);
if (BuildConfig.DEBUG && w < 0) {
// w should not be negative, since an exception is thrown on error
throw new AssertionError("Os.write() returned a negative value (" + w + ")");
}
remaining -= w;
Os.write(fd, from);
} catch (ErrnoException e) {
if (e.errno != OsConstants.EINTR) {
throw new IOException(e);

View File

@@ -7,7 +7,6 @@ public class Options {
private int bitRate;
private boolean tunnelForward;
private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly
public int getMaxSize() {
return maxSize;
@@ -40,12 +39,4 @@ public class Options {
public void setCrop(Rect crop) {
this.crop = crop;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
}

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect;
import android.media.MediaMuxer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
@@ -23,26 +22,21 @@ public class ScreenEncoder implements Device.RotationListener {
private static final int REPEAT_FRAME_DELAY = 6; // repeat after 6 frames
private static final int MICROSECONDS_IN_ONE_SECOND = 1_000_000;
private static final int NO_PTS = -1;
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private int bitRate;
private int frameRate;
private int iFrameInterval;
private boolean sendFrameMeta;
private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int frameRate, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta;
public ScreenEncoder(int bitRate, int frameRate, int iFrameInterval) {
this.bitRate = bitRate;
this.frameRate = frameRate;
this.iFrameInterval = iFrameInterval;
}
public ScreenEncoder(boolean sendFrameMeta, int bitRate) {
this(sendFrameMeta, bitRate, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL);
public ScreenEncoder(int bitRate) {
this(bitRate, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL);
}
@Override
@@ -86,8 +80,6 @@ public class ScreenEncoder implements Device.RotationListener {
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
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;
@@ -98,11 +90,6 @@ public class ScreenEncoder implements Device.RotationListener {
}
if (outputBufferId >= 0) {
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
if (sendFrameMeta) {
writeFrameMeta(fd, bufferInfo, codecBuffer.remaining());
}
IO.writeFully(fd, codecBuffer);
}
} finally {
@@ -115,25 +102,6 @@ public class ScreenEncoder implements Device.RotationListener {
return !eof;
}
private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException {
headerBuffer.clear();
long pts;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
pts = NO_PTS; // non-media data packet
} else {
if (ptsOrigin == 0) {
ptsOrigin = bufferInfo.presentationTimeUs;
}
pts = bufferInfo.presentationTimeUs - ptsOrigin;
}
headerBuffer.putLong(pts);
headerBuffer.putInt(packetSize);
headerBuffer.flip();
IO.writeFully(fd, headerBuffer);
}
private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType("video/avc");
}

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException;
import java.util.Arrays;
public final class Server {
@@ -15,7 +14,7 @@ public final class Server {
final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate());
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
// asynchronous
startEventController(device, connection);
@@ -64,7 +63,6 @@ public final class Server {
}
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[2]);
Ln.d("tunnelForward = " + tunnelForward);
options.setTunnelForward(tunnelForward);
if (args.length < 4) {
@@ -73,12 +71,6 @@ public final class Server {
Rect crop = parseCrop(args[3]);
options.setCrop(crop);
if (args.length < 5) {
return options;
}
boolean sendFrameMeta = Boolean.parseBoolean(args[4]);
options.setSendFrameMeta(sendFrameMeta);
return options;
}
@@ -106,8 +98,6 @@ public final class Server {
}
});
Ln.d("args: " + Arrays.toString(args));
Options options = createOptions(args);
scrcpy(options);
}