Showing preview only (345K chars total). Download the full file or copy to clipboard to get everything.
Repository: DeviceFarmer/minicap
Branch: master
Commit: f3d40d65da0c
Files: 87
Total size: 320.2 KB
Directory structure:
gitextract_xy_v5_46/
├── .github/
│ └── dependabot.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .semaphore/
│ ├── publish.yml
│ └── semaphore.yml
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── Makefile
├── README.md
├── build-remote.sh
├── example/
│ ├── .gitignore
│ ├── README.md
│ ├── app.js
│ ├── package.json
│ └── public/
│ └── index.html
├── experimental/
│ ├── .gitignore
│ ├── app/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ └── logback.xml
│ │ ├── java/
│ │ │ └── io/
│ │ │ └── devicefarmer/
│ │ │ └── minicap/
│ │ │ ├── Main.kt
│ │ │ ├── SimpleServer.kt
│ │ │ ├── output/
│ │ │ │ ├── DisplayOutput.kt
│ │ │ │ ├── MinicapClientOutput.kt
│ │ │ │ └── ScreenshotOutput.kt
│ │ │ ├── provider/
│ │ │ │ ├── BaseProvider.kt
│ │ │ │ └── SurfaceProvider.kt
│ │ │ └── utils/
│ │ │ ├── DisplayInfo.kt
│ │ │ ├── DisplayManager.kt
│ │ │ ├── DisplayManagerGlobal.kt
│ │ │ └── SurfaceControl.kt
│ │ └── res/
│ │ ├── drawable/
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── values-night/
│ │ └── themes.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── minicap
│ └── settings.gradle
├── gdb.sh
├── jni/
│ ├── Android.mk
│ ├── Application.mk
│ ├── minicap/
│ │ ├── Android.mk
│ │ ├── JpgEncoder.cpp
│ │ ├── JpgEncoder.hpp
│ │ ├── Projection.hpp
│ │ ├── SimpleServer.cpp
│ │ ├── SimpleServer.hpp
│ │ ├── minicap.cpp
│ │ └── util/
│ │ ├── debug.h
│ │ └── formatter.hpp
│ ├── minicap-shared/
│ │ ├── Android.mk
│ │ ├── README.md
│ │ ├── aosp/
│ │ │ ├── .rsync-filter
│ │ │ ├── Android.mk
│ │ │ ├── Makefile
│ │ │ ├── include/
│ │ │ │ ├── Minicap.hpp
│ │ │ │ └── mcdebug.h
│ │ │ └── src/
│ │ │ ├── minicap_14.cpp
│ │ │ ├── minicap_16.cpp
│ │ │ ├── minicap_17.cpp
│ │ │ ├── minicap_18.cpp
│ │ │ ├── minicap_19.cpp
│ │ │ ├── minicap_21.cpp
│ │ │ ├── minicap_22.cpp
│ │ │ ├── minicap_23.cpp
│ │ │ ├── minicap_24.cpp
│ │ │ ├── minicap_25.cpp
│ │ │ ├── minicap_26.cpp
│ │ │ ├── minicap_27.cpp
│ │ │ ├── minicap_28.cpp
│ │ │ ├── minicap_29.cpp
│ │ │ ├── minicap_30.cpp
│ │ │ ├── minicap_31.cpp
│ │ │ └── minicap_9.cpp
│ │ └── mock/
│ │ └── Minicap.cpp
│ └── vendor/
│ └── Android.mk
├── package.json
└── run.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
================================================
FILE: .gitignore
================================================
.gradle
/*.tgz
/.env
/gdb.setup
/libs/
/libs/
/obj/
/prebuilt/
/temp/
================================================
FILE: .gitmodules
================================================
[submodule "libjpeg-turbo"]
path = jni/vendor/libjpeg-turbo
url = https://github.com/devicefarmer/android-libjpeg-turbo.git
================================================
FILE: .npmignore
================================================
/.env
/.gitmodules
/.npmignore
/.semaphore
/*.tgz
/build-remote.sh
/CONTRIBUTING.md
/example/
/gdb.setup
/gdb.sh
/jni/
/libs/
/Makefile
/obj/
/run.sh
/temp/
/yarn-error.log
/experimental
================================================
FILE: .semaphore/publish.yml
================================================
version: v1.0
name: Publish
blocks:
- name: Publish
task:
jobs:
- name: NPM publish
commands:
- checkout
- git submodule init
- git submodule update
- 'wget https://dl.google.com/android/repository/commandlinetools-linux-6514223_latest.zip -O ~/android-commandline-tools.zip '
- mkdir -p ~/android-sdk/cmdline-tools/
- unzip ~/android-commandline-tools.zip -d ~/android-sdk/cmdline-tools
- 'export PATH=$PATH:~/android-sdk/cmdline-tools/tools/bin'
- yes | sdkmanager "ndk;21.3.6528147"
- 'export PATH=$PATH:~/android-sdk/ndk/21.3.6528147/'
- 'export ANDROID_SDK_ROOT=~/android-sdk'
- sem-version node 12
- sem-version java 17
- cache restore
- npm install
- cache store
- npm install
- npm publish --access public
secrets:
- name: npmjs
agent:
machine:
type: e1-standard-2
os_image: ubuntu2204
================================================
FILE: .semaphore/semaphore.yml
================================================
version: v1.0
name: NPM test
agent:
machine:
type: e1-standard-2
os_image: ubuntu2204
blocks:
- name: Test
task:
jobs:
- name: Test
commands:
- checkout
- sem-version java 17
- git submodule init
- git submodule update
- 'wget https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip -O ~/android-commandline-tools.zip '
- mkdir -p ~/android-sdk/cmdline-tools/
- unzip ~/android-commandline-tools.zip -d ~/android-sdk/cmdline-tools
- 'export PATH=$PATH:~/android-sdk/cmdline-tools/cmdline-tools/bin'
- yes | sdkmanager "ndk;27.2.12479018"
- 'export PATH=$PATH:~/android-sdk/ndk/27.2.12479018/'
- export ANDROID_SDK_ROOT=~/android-sdk
- sem-version node 12
- cache restore
- npm install
- cache store
- npm run build --if-present
- npm test
promotions:
- name: NPM publish
pipeline_file: publish.yml
auto_promote:
when: (branch = 'master' OR tag =~ '.*') AND result = 'passed'
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
We are happy to accept any contributions that make sense and respect the rules listed below.
## How to contribute
1. Fork the repo.
2. Create a feature branch for your contribution out of the `master` branch. Only one contribution per branch is accepted.
3. Implement your contribution while respecting our rules (see below).
4. Make sure that your contribution builds and all necessary files have been committed.
5. Submit a pull request against our `master` branch!
## Rules
* **Do** use feature branches.
* **Do** conform to existing coding style so that your contribution fits in.
* **Do** use [EditorConfig](http://editorconfig.org/) to enforce our [whitespace rules](.editorconfig). If your editor is not supported, enforce the settings manually.
* **Do not** commit any generated files, unless already in the repo. If absolutely necessary, explain why.
* **Do not** create any top level files or directories. If absolutely necessary, explain why and update [.gitignore](.gitignore) if appropriate.
## License
By contributing your code, you agree to license your contribution under our [LICENSE](LICENSE).
================================================
FILE: ISSUE_TEMPLATE.md
================================================
**What is the issue or idea you have?**
**Have you tried STF?**
<!-- Minicap was made for STF, and STF contains many workarounds for edge cases. -->
<!-- If something doesn't work, try STF first. We only accept bug reports for issues that don't work in STF. -->
================================================
FILE: LICENSE
================================================
Copyright © 2013 CyberAgent, Inc.
Copyright © 2016 The OpenSTF Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
.PHONY: default clean prebuilt
NDKBUILT := \
libs/arm64-v8a/minicap \
libs/arm64-v8a/minicap-nopie \
libs/armeabi-v7a/minicap \
libs/armeabi-v7a/minicap-nopie \
libs/x86/minicap \
libs/x86/minicap-nopie \
libs/x86_64/minicap \
libs/x86_64/minicap-nopie \
GRADLEBUILT := \
experimental/app/build/outputs/apk/debug/minicap-debug.apk
default: prebuilt
clean:
ndk-build clean
rm -rf prebuilt
experimental/gradlew -p experimental clean
$(NDKBUILT):
ndk-build
$(GRADLEBUILT):
experimental/gradlew -p experimental assembleDebug
# It may feel a bit redundant to list everything here. However it also
# acts as a safeguard to make sure that we really are including everything
# that is supposed to be there.
prebuilt: \
prebuilt/armeabi-v7a/bin/minicap \
prebuilt/armeabi-v7a/bin/minicap-nopie \
prebuilt/armeabi-v7a/lib/android-9/minicap.so \
prebuilt/armeabi-v7a/lib/android-10/minicap.so \
prebuilt/armeabi-v7a/lib/android-14/minicap.so \
prebuilt/armeabi-v7a/lib/android-15/minicap.so \
prebuilt/armeabi-v7a/lib/android-16/minicap.so \
prebuilt/armeabi-v7a/lib/android-17/minicap.so \
prebuilt/armeabi-v7a/lib/android-18/minicap.so \
prebuilt/armeabi-v7a/lib/android-19/minicap.so \
prebuilt/armeabi-v7a/lib/android-21/minicap.so \
prebuilt/armeabi-v7a/lib/android-22/minicap.so \
prebuilt/armeabi-v7a/lib/android-23/minicap.so \
prebuilt/armeabi-v7a/lib/android-24/minicap.so \
prebuilt/armeabi-v7a/lib/android-25/minicap.so \
prebuilt/armeabi-v7a/lib/android-26/minicap.so \
prebuilt/armeabi-v7a/lib/android-27/minicap.so \
prebuilt/armeabi-v7a/lib/android-28/minicap.so \
prebuilt/armeabi-v7a/lib/android-29/minicap.so \
prebuilt/armeabi-v7a/lib/android-30/minicap.so \
prebuilt/arm64-v8a/bin/minicap \
prebuilt/arm64-v8a/bin/minicap-nopie \
prebuilt/arm64-v8a/lib/android-21/minicap.so \
prebuilt/arm64-v8a/lib/android-22/minicap.so \
prebuilt/arm64-v8a/lib/android-23/minicap.so \
prebuilt/arm64-v8a/lib/android-24/minicap.so \
prebuilt/arm64-v8a/lib/android-25/minicap.so \
prebuilt/arm64-v8a/lib/android-26/minicap.so \
prebuilt/arm64-v8a/lib/android-27/minicap.so \
prebuilt/arm64-v8a/lib/android-28/minicap.so \
prebuilt/arm64-v8a/lib/android-29/minicap.so \
prebuilt/arm64-v8a/lib/android-30/minicap.so \
prebuilt/x86/bin/minicap \
prebuilt/x86/bin/minicap-nopie \
prebuilt/x86/lib/android-14/minicap.so \
prebuilt/x86/lib/android-15/minicap.so \
prebuilt/x86/lib/android-16/minicap.so \
prebuilt/x86/lib/android-17/minicap.so \
prebuilt/x86/lib/android-18/minicap.so \
prebuilt/x86/lib/android-19/minicap.so \
prebuilt/x86/lib/android-21/minicap.so \
prebuilt/x86/lib/android-22/minicap.so \
prebuilt/x86/lib/android-23/minicap.so \
prebuilt/x86/lib/android-24/minicap.so \
prebuilt/x86/lib/android-25/minicap.so \
prebuilt/x86/lib/android-26/minicap.so \
prebuilt/x86/lib/android-27/minicap.so \
prebuilt/x86/lib/android-28/minicap.so \
prebuilt/x86/lib/android-29/minicap.so \
prebuilt/x86/lib/android-30/minicap.so \
prebuilt/x86_64/bin/minicap \
prebuilt/x86_64/bin/minicap-nopie \
prebuilt/x86_64/lib/android-21/minicap.so \
prebuilt/x86_64/lib/android-22/minicap.so \
prebuilt/x86_64/lib/android-23/minicap.so \
prebuilt/x86_64/lib/android-24/minicap.so \
prebuilt/x86_64/lib/android-25/minicap.so \
prebuilt/x86_64/lib/android-26/minicap.so \
prebuilt/x86_64/lib/android-27/minicap.so \
prebuilt/x86_64/lib/android-28/minicap.so \
prebuilt/x86_64/lib/android-29/minicap.so \
prebuilt/x86_64/lib/android-30/minicap.so \
prebuilt/noarch/minicap.apk \
prebuilt/%/bin/minicap: libs/%/minicap
mkdir -p $(@D)
cp $^ $@
prebuilt/%/bin/minicap-nopie: libs/%/minicap-nopie
mkdir -p $(@D)
cp $^ $@
prebuilt/armeabi-v7a/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/armeabi-v7a/minicap.so
mkdir -p $(@D)
cp $^ $@
prebuilt/arm64-v8a/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/arm64-v8a/minicap.so
mkdir -p $(@D)
cp $^ $@
prebuilt/x86/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/x86/minicap.so
mkdir -p $(@D)
cp $^ $@
prebuilt/x86_64/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/x86_64/minicap.so
mkdir -p $(@D)
cp $^ $@
prebuilt/noarch/minicap.apk: experimental/app/build/outputs/apk/debug/minicap-debug.apk
mkdir -p $(@D)
cp $^ $@
================================================
FILE: README.md
================================================
# minicap
Minicap provides a socket interface for streaming realtime screen capture data out of Android devices. It is meant to be used as a component in a larger program and is therefore not immensely useful just by itself. For example, it is being used in [STF](https://github.com/openstf/stf) for remote control.
Minicap works without root if started via [ADB](http://developer.android.com/tools/help/adb.html) on SDK 28 (Android 9.0) and lower. The lowest SDK level we build for is 9 (i.e. Android 2.3). Minicap also works on Android Wear. Developer previews are, in general, supported once Google releases the source code for that release. _Only the latest Developer Preview is supported, and only until there's a stable release._ Emulators are not supported. Note that Android 3.x is not supported since those versions were never open sourced.
To capture the screen we currently use two methods. For older Android versions we use the ScreenshotClient, a private API in AOSP. For newer versions we use a virtual display, which also requires access to private APIs. The frames are then encoded using SIMD-enabled [libjpeg-turbo](http://libjpeg-turbo.virtualgl.org/) and sent over a socket interface. A planned future improvement to allow for even higher FPS is to use MediaRecorder and friends to take advantage of hardware encoding.
In principle, every device should work. However, since minicap relies on private APIs, some may not. Please let us know by creating a GitHub issue about that device.
The project consists of two parts. There's the main binary that can be built using NDK alone. The other part is a shared library that's built for each SDK level and each architecture inside the AOSP source tree. We ship precompiled libraries in this repo, but any modifications to the code used by these shared libraries require a recompile against the corresponding AOSP branches. This can be a major pain, but we have several utilities to help with the ordeal. If you're interested in that, [read the build instructions here](jni/minicap-shared/README.md).
## Features
* Usable to very smooth FPS depending on device. Older, weaker devices running an old version of Android can reach 10-20 FPS. Newer devices running recent versions of Android can usually reach 30-40 FPS fairly easily, but there are some exceptions. For maximum FPS we recommend running minicap at half the real vertical and horizontal resolution.
* Decent and usable but non-zero latency. Depending on encoding performance and USB transfer speed it may be one to a few frames behind the physical screen.
* On Android 4.2+, frames are only sent when something changes on the screen. On older versions frames are sent as a constant stream, whether there are changes or not.
* Easy socket interface.
## Requirements
* [NDK](https://developer.android.com/tools/sdk/ndk/index.html), Revision 10e (May 2015)
* [make](http://www.gnu.org/software/make/)
## Building
We include [libjpeg-turbo as a Git submodule](https://github.com/openstf/android-libjpeg-turbo), so first make sure you've fetched it.
```
git submodule init
git submodule update
```
You're now ready to proceed.
Building requires [NDK](https://developer.android.com/tools/sdk/ndk/index.html), and is known to work with at least with NDK Revision 10e (May 2015). Older versions do not work due to the lack of `.asm` file support for x86_64. **Important note: NDK 15 no longer supports Android platforms earlier than SDK level 14. This means that if compiled on NDK 15 or later, the binaries may or may not work on earlier platforms (e.g. Android 2.3, Android 2.3.3).**
Then it's simply a matter of invoking `ndk-build`.
```
ndk-build
```
You should now have the binaries available in `./libs`.
If you've modified the shared library, you'll also need to [build that](jni/minicap-shared/README.md).
## Running
You'll need to [build](#building) first.
### The easy way
You can then use the included [run.sh](run.sh) script to run the right binary on your device. It will make sure the correct binary and shared library get copied to your device. If you have multiple devices connected, set `ANDROID_SERIAL` before running the script.
```bash
# Run a preliminary check to see whether your device will work
./run.sh autosize -t
# Check help
./run.sh autosize -h
# Start minicap
./run.sh autosize
```
_The `autosize` command is for selecting the correct screen size automatically. This is done by the script instead of the binary itself. To understand why this is necessary, read the manual instructions below._
Finally we simply need to create a local forward so that we can connect to the socket.
```bash
adb forward tcp:1313 localabstract:minicap
```
Now you'll be able to connect to the socket locally on port 1313.
Then just see [usage](#usage) below.
### The hard way
To run manually, you have to first figure out which ABI your device supports:
```bash
ABI=$(adb shell getprop ro.product.cpu.abi | tr -d '\r')
```
_Note that as Android shell always ends lines with CRLF, you'll have to remove the CR like above or the rest of the commands will not work properly._
_Also note that if you've got multiple devices connected, setting `ANDROID_SERIAL` will make things quite a bit easier as you won't have to specify the `-s <serial>` option every time._
Now, push the appropriate binary to the device:
```bash
adb push libs/$ABI/minicap /data/local/tmp/
```
Note that for SDK <16, you will have to use the `minicap-nopie` executable which comes without [PIE](http://en.wikipedia.org/wiki/Position-independent_code#Position-independent_executables) support. Check [run.sh](run.sh) for a scripting example.
The binary enough is not enough. We'll also need to select and push the correct shared library to the device. This can be done as follows.
```bash
SDK=$(adb shell getprop ro.build.version.sdk | tr -d '\r')
adb push jni/minicap-shared/aosp/libs/android-$SDK/$ABI/minicap.so /data/local/tmp/
```
At this point it might be useful to check the usage:
```bash
adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -h
```
Note that you'll need to set `LD_LIBRARY_PATH` every time you call minicap or it won't find the shared library.
Also, you'll need to specify the size of the display and the projection every time you use minicap. This is because the private APIs we would have to use to access that information segfault on many Samsung devices (whereas minicap itself runs fine). The [run.sh](run.sh) helper script provides the `autosize` helper as mentioned above.
So, let's assume that your device has a 1080x1920 screen. First, let's run a quick check to see if your device is able to run the current version of minicap:
```bash
adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x1920@1080x1920/0 -t
```
_The format of the -P argument is: {RealWidth}x{RealHeight}@{VirtualWidth}x{VirtualHeight}/{Orientation}. The "virtual" size is the size of the desired projection. The orientation argument tells minicap what the current orientation of the device is (in degrees), which is required so that we can report the correct orientation over the socket interface to the frame consumer. One way to get the current orientation (or rotation) is [RotationWatcher.apk](https://github.com/openstf/RotationWatcher.apk)._
If the command outputs "OK", then everything should be fine. If instead it segfaults (possibly after hanging for a while), your device is not supported and [we'd like to know about it](https://github.com/openstf/minicap/issues).
Finally, let's start minicap. It will start listening on an abstract unix domain socket.
```bash
adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x1920@1080x1920/0
```
Now we simply need to create a local forward so that we can connect to the socket.
```bash
adb forward tcp:1313 localabstract:minicap
```
Now you can connect to the socket using the local port. Note that currently **only one connection at a time is supported.** It doesn't really make sense to have more than one connection anyway, as the USB bus would get saturated very quickly. So, let's connect.
```bash
nc localhost 1313
```
This will give you binary output that will be explained in the next section.
## Usage
It is assumed that you now have an open connection to the minicap socket. If not, follow the [instructions](#running) above.
The minicap protocol is a simple push-based binary protocol. When you first connect to the socket, you get a global header followed by the first frame. The global header will not appear again. More frames keep getting sent until you stop minicap.
### Global header binary format
Appears once.
| Bytes | Length | Type | Explanation |
|-------|--------|------|-------------|
| 0 | 1 | unsigned char | Version (currently 1) |
| 1 | 1 | unsigned char | Size of the header (from byte 0) |
| 2-5 | 4 | uint32 (low endian) | Pid of the process |
| 6-9 | 4 | uint32 (low endian) | Real display width in pixels |
| 10-13 | 4 | uint32 (low endian) | Real display height in pixels |
| 14-17 | 4 | uint32 (low endian) | Virtual display width in pixels |
| 18-21 | 4 | uint32 (low endian) | Virtual display height in pixels |
| 22 | 1 | unsigned char | Display orientation |
| 23 | 1 | unsigned char | Quirk bitflags (see below) |
#### Quirk bitflags
Currently, the following quirks may be reported:
| Value | Name | Explanation |
|-------|------|-------------|
| 1 | QUIRK_DUMB | Frames will get sent even if there are no changes from the previous frame. Informative, doesn't require any actions on your part. You can limit the capture rate by reading frame data slower in your own code if you wish. |
| 2 | QUIRK_ALWAYS_UPRIGHT | The frame will always be in upright orientation regardless of the device orientation. This needs to be taken into account when rendering the image. |
| 4 | QUIRK_TEAR | Frame tear might be visible. Informative, no action required. Neither of our current two methods exhibit this behavior. |
### Frame binary format
Appears a potentially unlimited number of times.
| Bytes | Length | Type | Explanation |
|-------|--------|------|-------------|
| 0-3 | 4 | uint32 (low endian) | Frame size in bytes (=n) |
| 4-(n+4) | n | unsigned char[] | Frame in JPG format |
## Debugging
You can use `gdb` to debug more complex issues. It is assumed that you already know how to use it. Here's how to get it running.
First, if you haven't already, make sure to build your project with the `NDK_DEBUG` option enabled, as follows.
```bash
ndk-build NDK_DEBUG=1
```
This will create `gdb.setup` and the `gdb` binaries themselves.
If you don't have `NDK_ROOT` set up yet, do it now like below. You may have to change the path, the sample path below works on OS X with Android Studio's NDK bundle (which you'll have to install as well).
```bash
export NDK_ROOT="$HOME/Library/Android/sdk/ndk-bundle"
```
Next, be sure that minicap is actually running. Just running `./run.sh autosize` is enough.
Finally, you should be able to just run `./ndk.sh` in a separate shell (you'll have to keep minicap running at the same time). It will pull all necessary libraries from the device to ease debugging, and you should end up at a functioning gdb shell. The `ndk.sh` script has been adapted from `ndk-gdb` for stand-alone binaries.
Improvements are welcome.
## Contributing
_As a small disclaimer, minicap was the first time the author used C++, so even any non-functional changes to make the code more idiomatic (preferably without introducing new dependencies) are also very welcome, in addition to bug fixes and new features._
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
See [LICENSE](LICENSE).
Copyright © The OpenSTF Project. All Rights Reserved.
================================================
FILE: build-remote.sh
================================================
#!/usr/bin/env bash
set -xueo pipefail
builder=$1
rsync \
--recursive \
--copy-links \
--perms \
--times \
-FF ./jni/minicap-shared/aosp/ "$builder":minicap/
ssh -T "$builder" "cd minicap && make -j 1"
rsync \
--recursive \
--copy-links \
--perms \
--times \
"$builder":minicap/libs/ ./jni/minicap-shared/aosp/libs/
================================================
FILE: example/.gitignore
================================================
/node_modules/
================================================
FILE: example/README.md
================================================
# Example: minicap over WebSockets
A quick and dirty example to show how minicap might be used as part of an application. Also useful for testing.
## Requirements
* [Node.js](https://nodejs.org/) >= 0.12 (for this example only)
* [ADB](http://developer.android.com/intl/ja/tools/help/adb.html)
* An Android device with USB debugging enabled.
## Running
1. Check that your device is connected and ADB is running with `adb devices`. The following steps may not work properly if you don't.
```
adb devices
```
2. Set up a forward for the server we'll soon have running inside the device. Note that due to laziness the port is currently fixed to 1717.
```
adb forward tcp:1717 localabstract:minicap
```
3. Get information about your display. Unfortunately the easy API methods we could use for automatic detection segfault on some Samsung devices, presumably due to maker customizations. You'll need to know the display width and height in pixels. Here are some ways to do it:
```
adb shell wm size
adb shell dumpsys display
```
4. Start the minicap server. The most convenient way is to use the helper script at the root of this repo.
```
# Try ./run.sh -h for help
./run.sh -P 720x1280@720x1280/0
```
The first set is the true size of your display, and the second set is the size of the desired projection. Larger projections require more processing power and bandwidth. The final argument is the rotation of the display. Note that this is not the rotation you want it to have, it simply specifies the display's current rotation, which is used to normalize the output frames between Android versions. If the rotation changes you have to restart the server.
5. Start the example app.
```
PORT=9002 node app.js
```
6. Open http://localhost:9002 in your browser.
================================================
FILE: example/app.js
================================================
var WebSocketServer = require('ws').Server
, http = require('http')
, express = require('express')
, path = require('path')
, net = require('net')
, app = express()
var PORT = process.env.PORT || 9002
app.use(express.static(path.join(__dirname, '/public')))
var server = http.createServer(app)
var wss = new WebSocketServer({ server: server })
wss.on('connection', function(ws) {
console.info('Got a client')
var stream = net.connect({
port: 1717
})
stream.on('error', function() {
console.error('Be sure to run `adb forward tcp:1717 localabstract:minicap`')
process.exit(1)
})
var readBannerBytes = 0
var bannerLength = 2
var readFrameBytes = 0
var frameBodyLength = 0
var frameBody = new Buffer(0)
var banner = {
version: 0
, length: 0
, pid: 0
, realWidth: 0
, realHeight: 0
, virtualWidth: 0
, virtualHeight: 0
, orientation: 0
, quirks: 0
}
function tryRead() {
for (var chunk; (chunk = stream.read());) {
console.info('chunk(length=%d)', chunk.length)
for (var cursor = 0, len = chunk.length; cursor < len;) {
if (readBannerBytes < bannerLength) {
switch (readBannerBytes) {
case 0:
// version
banner.version = chunk[cursor]
break
case 1:
// length
banner.length = bannerLength = chunk[cursor]
break
case 2:
case 3:
case 4:
case 5:
// pid
banner.pid +=
(chunk[cursor] << ((readBannerBytes - 2) * 8)) >>> 0
break
case 6:
case 7:
case 8:
case 9:
// real width
banner.realWidth +=
(chunk[cursor] << ((readBannerBytes - 6) * 8)) >>> 0
break
case 10:
case 11:
case 12:
case 13:
// real height
banner.realHeight +=
(chunk[cursor] << ((readBannerBytes - 10) * 8)) >>> 0
break
case 14:
case 15:
case 16:
case 17:
// virtual width
banner.virtualWidth +=
(chunk[cursor] << ((readBannerBytes - 14) * 8)) >>> 0
break
case 18:
case 19:
case 20:
case 21:
// virtual height
banner.virtualHeight +=
(chunk[cursor] << ((readBannerBytes - 18) * 8)) >>> 0
break
case 22:
// orientation
banner.orientation += chunk[cursor] * 90
break
case 23:
// quirks
banner.quirks = chunk[cursor]
break
}
cursor += 1
readBannerBytes += 1
if (readBannerBytes === bannerLength) {
console.log('banner', banner)
}
}
else if (readFrameBytes < 4) {
frameBodyLength += (chunk[cursor] << (readFrameBytes * 8)) >>> 0
cursor += 1
readFrameBytes += 1
console.info('headerbyte%d(val=%d)', readFrameBytes, frameBodyLength)
}
else {
if (len - cursor >= frameBodyLength) {
console.info('bodyfin(len=%d,cursor=%d)', frameBodyLength, cursor)
frameBody = Buffer.concat([
frameBody
, chunk.slice(cursor, cursor + frameBodyLength)
])
// Sanity check for JPG header, only here for debugging purposes.
if (frameBody[0] !== 0xFF || frameBody[1] !== 0xD8) {
console.error(
'Frame body does not start with JPG header', frameBody)
process.exit(1)
}
ws.send(frameBody, {
binary: true
})
cursor += frameBodyLength
frameBodyLength = readFrameBytes = 0
frameBody = new Buffer(0)
}
else {
console.info('body(len=%d)', len - cursor)
frameBody = Buffer.concat([
frameBody
, chunk.slice(cursor, len)
])
frameBodyLength -= len - cursor
readFrameBytes += len - cursor
cursor = len
}
}
}
}
}
stream.on('readable', tryRead)
ws.on('close', function() {
console.info('Lost a client')
stream.end()
})
})
server.listen(PORT)
console.info('Listening on port %d', PORT)
================================================
FILE: example/package.json
================================================
{
"dependencies": {
"express": "^4.12.3",
"ws": "^0.7.1"
}
}
================================================
FILE: example/public/index.html
================================================
<!doctype html>
<canvas id="canvas" style="border: 1px solid red;"></canvas>
<script>
/*jshint browser:true*/
var BLANK_IMG =
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
var canvas = document.getElementById('canvas')
, g = canvas.getContext('2d')
var ws = new WebSocket('ws://localhost:9002', 'minicap')
ws.binaryType = 'blob'
ws.onclose = function() {
console.log('onclose', arguments)
}
ws.onerror = function() {
console.log('onerror', arguments)
}
ws.onmessage = function(message) {
var blob = new Blob([message.data], {type: 'image/jpeg'})
var URL = window.URL || window.webkitURL
var img = new Image()
img.onload = function() {
console.log(img.width, img.height)
canvas.width = img.width
canvas.height = img.height
g.drawImage(img, 0, 0)
img.onload = null
img.src = BLANK_IMG
img = null
u = null
blob = null
}
var u = URL.createObjectURL(blob)
img.src = u
}
ws.onopen = function() {
console.log('onopen', arguments)
ws.send('1920x1080/0')
}
</script>
================================================
FILE: experimental/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: experimental/app/.gitignore
================================================
/build
================================================
FILE: experimental/app/build.gradle
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'org.jetbrains.dokka'
}
android {
compileSdkVersion 30
defaultConfig {
applicationId "io.devicefarmer.minicap"
minSdkVersion 21
targetSdkVersion 30
versionCode 2
versionName "0.7"
archivesBaseName = "minicap"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
}
namespace 'io.devicefarmer.minicap'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'com.github.tony19:logback-android:2.0.0'
}
================================================
FILE: experimental/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: experimental/app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Minicap" />
</manifest>
================================================
FILE: experimental/app/src/main/assets/logback.xml
================================================
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/data/local/tmp/minicap.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="LOGCAT" class="ch.qos.logback.classic.android.LogcatAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<!--appender-ref ref="FILE" /-->
<appender-ref ref="LOGCAT" />
<appender-ref ref="STDERR" />
</root>
</configuration>
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/Main.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap
import android.os.Looper
import android.util.Size
import io.devicefarmer.minicap.provider.SurfaceProvider
import kotlin.math.roundToInt
import kotlin.system.exitProcess
/**
* Main entry point that can be launched as follow:
* adb shell CLASSPATH=/data/local/tmp/minicap.apk app_process /system/bin io.devicefarmer.minicap.Main
*/
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val provider: SurfaceProvider
//called because we are not run from the Android environment
Looper.prepareMainLooper()
val params = args.fold(Pair(Parameters.Builder(), "")) { (p, lastKey), elem ->
if (elem.startsWith("-")) {
when (elem) {
"-s" -> p.screenshot(true)
"-i" -> p.displayInfo(true)
"-h" -> showHelp().also { System.exit(0) }
}
Pair(p, elem)
} else {
when (lastKey) {
"-d" -> p.displayId(elem.toInt())
"-n" -> p.socket(elem)
"-P" -> p.projection(elem)
"-Q" -> p.quality(elem.toInt())
"-r" -> p.frameRate(elem.toFloat())
}
Pair(p, lastKey)
}
}.first.build()
provider = if (params.projection == null) {
SurfaceProvider(params.displayId)
} else {
params.projection.forceAspectRatio()
SurfaceProvider(
params.displayId,
Size(
params.projection.targetSize.width,
params.projection.targetSize.height
),
angleToRotation(params.projection.rotation)
)
}
provider.quality = params.quality
provider.frameRate = params.frameRate
when {
params.displayInfo -> {
println("${provider.displayInfo}")
exitProcess(0)
}
params.screenshot -> {
//for now this is asynchronous and requires the main looper to run
provider.screenshot(System.out)
}
else -> {
//the stf process reads this
System.err.println("PID: ${android.os.Process.myPid()}")
System.err.println("INFO: ${params.projection}")
val server = SimpleServer(params.socket, provider)
server.start()
}
}
Looper.loop()
}
private fun showHelp() {
System.out.println(
"Usage: %s [-h] [-n <name>]\n" +
" -d <id>: Display ID. (%d)\n" +
" -n <name>: Change the name of the abtract unix domain socket. (%s)\n" +
" -P <value>: Display projection (<w>x<h>@<w>x<h>/{0|90|180|270}).\n" +
" -Q <value>: JPEG quality (0-100).\n" +
" -s: Take a screenshot and output it to stdout. Needs -P.\n" +
" -S: Skip frames when they cannot be consumed quickly enough.\n" +
" -r <value>: Frame rate (frames/s)" +
" -t: Attempt to get the capture method running, then exit.\n" +
" -i: Get display information in JSON format. May segfault.\n" +
" -h: Show help.\n"
)
}
}
}
private fun angleToRotation(value: Int): Int =
when(value) {
0 -> 0
90 -> 1
180 -> 2
270 -> 3
else -> throw IllegalStateException("Invalid rotation")
}
data class Projection(
val realSize: Size, var targetSize: Size,
val rotation: Int
) {
fun forceAspectRatio() {
val aspect = realSize.width.toFloat() / realSize.height.toFloat()
targetSize = if (targetSize.height > targetSize.width / aspect) {
Size(targetSize.width, ((targetSize.width / aspect)).roundToInt())
} else {
Size((targetSize.height * aspect).roundToInt(), targetSize.height)
}
}
override fun toString(): String =
"${realSize.width}x${realSize.height}@${targetSize.width}x${targetSize.height}/${rotation}"
}
class Parameters private constructor(
val projection: Projection?,
val screenshot: Boolean,
val socket: String,
val quality: Int,
val displayInfo: Boolean,
val frameRate: Float,
val displayId: Int
) {
data class Builder(
var projection: Projection? = null,
var screenshot: Boolean = false,
var socket: String = "minicap",
var quality: Int = 100,
var displayInfo: Boolean = false,
var frameRate: Float = Float.MAX_VALUE,
var displayId: Int = 0
) {
//TODO make something more robust
fun projection(p: String) = apply {
this.projection = p.run {
val s = this.split('@', '/', 'x')
Projection(
Size(s[0].toInt(), s[1].toInt()),
Size(s[2].toInt(), s[3].toInt()),
s[4].toInt()
)
}
}
fun screenshot(s: Boolean) = apply { this.screenshot = s }
fun socket(name: String) = apply { this.socket = name }
fun quality(value: Int) = apply { this.quality = value }
fun displayInfo(enabled: Boolean) = apply { this.displayInfo = enabled }
fun frameRate(value: Float) = apply { this.frameRate = value }
fun displayId(value: Int) = apply { this.displayId = value }
fun build() = Parameters(projection, screenshot, socket, quality, displayInfo, frameRate, displayId)
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/SimpleServer.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap
import android.net.LocalServerSocket
import android.net.LocalSocket
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.IOException
/**
* Minimalist "server" to bootstrap development
*/
class SimpleServer(private val socket: String, private val listener: Listener) {
companion object {
val log: Logger = LoggerFactory.getLogger(Main::class.java.simpleName)
}
interface Listener {
fun onConnection(socket: LocalSocket)
}
fun start() {
try {
val serverSocket = LocalServerSocket(socket)
log.info("Listening on socket : ${socket}")
val clientSocket: LocalSocket = serverSocket.accept()
listener.onConnection(clientSocket)
} catch (e: IOException) {
log.error("error waiting connection", e)
}
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/output/DisplayOutput.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.output
import java.io.ByteArrayOutputStream
abstract class DisplayOutput {
val imageBuffer: ByteArrayOutputStream = ByteArrayOutputStream()
abstract fun send()
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/output/MinicapClientOutput.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.output
import android.net.LocalSocket
import android.util.Size
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Provides method to send data to the client that connected
*
* Basically implements the "minicap" protocol
*/
@ExperimentalUnsignedTypes
class MinicapClientOutput(
private val socket: LocalSocket
) :
DisplayOutput() {
companion object {
const val BANNER_VERSION = 1
const val BANNER_SIZE = 24
const val QUIRK_ALWAYS_UPRIGHT = 2
}
/**
* Sends the banner required at connection time
*/
fun sendBanner(screenSize: Size, targetSize: Size, rotation: Int ) {
val byteArray = ByteArray(BANNER_SIZE)
ByteBuffer.wrap(byteArray).apply {
order(ByteOrder.LITTLE_ENDIAN)
put(BANNER_VERSION.toByte())
put(BANNER_SIZE.toByte())
putInt(android.os.Process.myPid()) //PID
putInt(screenSize.width)
putInt(screenSize.height)
putInt(targetSize.width)
putInt(targetSize.height)
put(rotation.toByte()) //as per libui ui::Rotation enum
put(QUIRK_ALWAYS_UPRIGHT.toByte()) //quirk
}
with(socket.outputStream) {
write(byteArray)
flush()
}
}
/**
* Sends a buffer containing a jpg image
*/
override fun send() {
val data = imageBuffer.toByteArray()
val payload = ByteArray(data.size + 4) //size: 32bit integer
ByteBuffer.wrap(payload).apply {
order(ByteOrder.LITTLE_ENDIAN)
putInt(data.size)
put(data)
}
with(socket.outputStream) {
write(payload)
flush()
}
imageBuffer.reset()
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/output/ScreenshotOutput.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.output
import java.io.PrintStream
class ScreenshotOutput(private val output: PrintStream) : DisplayOutput() {
override fun send() {
output.use {
it.write(imageBuffer.toByteArray())
it.flush()
}
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/provider/BaseProvider.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.provider
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.media.Image
import android.media.ImageReader
import android.net.LocalSocket
import android.util.Size
import io.devicefarmer.minicap.output.DisplayOutput
import io.devicefarmer.minicap.output.MinicapClientOutput
import io.devicefarmer.minicap.SimpleServer
import io.devicefarmer.minicap.utils.DisplayManagerGlobal
import org.slf4j.LoggerFactory
import java.io.OutputStream
import java.io.PrintStream
import java.nio.ByteBuffer
/**
* Base class to provide images of the screen. Those captures can be setup from SurfaceControl - as
* it currently is - but could as well comes from MediaProjection API if useful in a future use case.
* It basically receives screen images, do whatever processing needed (here, encodes in jpeg format)
* and sends the results to an output (could be a file for screenshot, or a minicap client receiving the
* jpeg stream)
*/
abstract class BaseProvider(private val displayId: Int, private val targetSize: Size, val rotation: Int) : SimpleServer.Listener,
ImageReader.OnImageAvailableListener {
companion object {
val log = LoggerFactory.getLogger(BaseProvider::class.java.simpleName)
}
private lateinit var clientOutput: DisplayOutput
private lateinit var imageReader: ImageReader
private var previousTimeStamp: Long = 0L
private var framePeriodMs: Long = 0
private var bitmap: Bitmap? = null //is used to compress the images
var quality: Int = 100
var frameRate: Float = Float.MAX_VALUE
set(value) {
this.framePeriodMs = (1000 / value).toLong()
log.info("framePeriodMs: $framePeriodMs")
field = value
}
abstract fun screenshot(printer: PrintStream)
abstract fun getScreenSize(): Size
fun getTargetSize(): Size = if(rotation%2 != 0) Size(targetSize.height, targetSize.width) else targetSize
fun getImageReader(): ImageReader = imageReader
fun init(out: DisplayOutput) {
imageReader = ImageReader.newInstance(
getTargetSize().width,
getTargetSize().height,
PixelFormat.RGBA_8888,
2
)
clientOutput = out
}
override fun onConnection(socket: LocalSocket) {
clientOutput = MinicapClientOutput(socket).apply {
sendBanner(getScreenSize(),getTargetSize(),rotation)
}
init(clientOutput)
}
override fun onImageAvailable(reader: ImageReader) {
val image = reader.acquireLatestImage()
val currentTime = System.currentTimeMillis()
if (image != null) {
if (currentTime - previousTimeStamp > framePeriodMs) {
previousTimeStamp = currentTime
encode(image, quality, clientOutput.imageBuffer)
clientOutput.send()
} else {
log.debug("skipping frame ($currentTime/$previousTimeStamp)")
}
image.close()
} else {
log.debug("no image available")
}
}
private fun encode(image: Image, q: Int, out: OutputStream) {
with(image) {
val planes: Array<Image.Plane> = planes
val buffer: ByteBuffer = planes[0].buffer
val pixelStride: Int = planes[0].pixelStride
val rowStride: Int = planes[0].rowStride
val rowPadding: Int = rowStride - pixelStride * width
// createBitmap can be resources consuming
bitmap ?: Bitmap.createBitmap(
width + rowPadding / pixelStride,
height,
Bitmap.Config.ARGB_8888
).apply {
copyPixelsFromBuffer(buffer)
}.run {
//the image need to be cropped
Bitmap.createBitmap(this, 0, 0, getTargetSize().width, getTargetSize().height)
}.apply {
compress(Bitmap.CompressFormat.JPEG, q, out)
}
}
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/provider/SurfaceProvider.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.provider
import android.graphics.Rect
import android.media.ImageReader
import android.net.LocalSocket
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.util.Size
import io.devicefarmer.minicap.output.ScreenshotOutput
import io.devicefarmer.minicap.utils.DisplayInfo
import io.devicefarmer.minicap.utils.DisplayManagerGlobal
import io.devicefarmer.minicap.utils.SurfaceControl
import java.io.PrintStream
import kotlin.system.exitProcess
import android.view.Surface
import android.hardware.display.VirtualDisplay
import io.devicefarmer.minicap.utils.DisplayManager
/**
* Provides screen images using [SurfaceControl]. This is pretty similar to the native version
* of minicap but here it is done at a higher level making things a bit easier.
*/
class SurfaceProvider(displayId: Int, targetSize: Size, orientation: Int) : BaseProvider(displayId, targetSize, orientation) {
constructor(display: Int) : this(display, currentScreenSize(), currentRotation())
private var virtualDisplay: VirtualDisplay? = null
private var displayManager: DisplayManager? = null
private var m_displayId : Int = 0;
companion object {
private fun currentScreenSize(): Size {
return currentDisplayInfo().run {
Size(this.size.width, this.size.height)
}
}
private fun currentRotation(): Int = currentDisplayInfo().rotation
private fun currentDisplayInfo(): DisplayInfo {
return DisplayManagerGlobal.getDisplayInfo(0)
}
}
private val handler: Handler = Handler(Looper.getMainLooper())
private var display: IBinder? = null
val displayInfo: DisplayInfo = DisplayManagerGlobal.getDisplayInfo(displayId)
override fun getScreenSize(): Size = displayInfo.size
override fun screenshot(printer: PrintStream) {
init(ScreenshotOutput(printer))
initSurface {
super.onImageAvailable(it)
exitProcess(0)
}
}
/**
*
*/
override fun onConnection(socket: LocalSocket) {
super.onConnection(socket)
initSurface()
}
/**
* Setup the Surface between the display and an ImageReader so that we can grab the
* screen.
*/
private fun initSurface(l: ImageReader.OnImageAvailableListener) {
//must be done on the main thread
// Support Android 12 (preview),and resolve black screen problem
try {
val secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Build.VERSION.SDK_INT == Build.VERSION_CODES.R && "S" != Build.VERSION.CODENAME
display = SurfaceControl.createDisplay("minicap", secure)
//initialise the surface to get the display in the ImageReader
SurfaceControl.openTransaction()
SurfaceControl.setDisplaySurface(display, getImageReader().surface)
SurfaceControl.setDisplayProjection(display, 0, Rect(0, 0, getScreenSize().width, getScreenSize().height), Rect(0, 0, getTargetSize().width, getTargetSize().height) )
SurfaceControl.setDisplayLayerStack(display, displayInfo.layerStack)
} catch (e: NoSuchMethodException) {
System.err.println("Handle the exception gracefully")
System.err.println("NoSuchMethodException Method not found: ${e.message}")
System.err.println("Try Display: using DisplayManager API")
try {
//Varun Kumar:- Android 15 Support
if (displayManager == null) {
displayManager = DisplayManager.create();
}
virtualDisplay = displayManager!!.createVirtualDisplay("minicap", getScreenSize().width, getScreenSize().height, m_displayId, getImageReader().surface);
virtualDisplay!!.surface =getImageReader().surface;
} catch (displayManagerException : Exception) {
System.err.println("$displayManagerException Could not create display using DisplayManager")
}
}
finally {
SurfaceControl.closeTransaction()
}
getImageReader().setOnImageAvailableListener(l, handler)
}
private fun initSurface() {
initSurface(this)
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayInfo.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.utils
import android.util.Size
data class DisplayInfo(
val displayId: Int,
val size: Size,
val rotation: Int,
val layerStack: Int,
val density: Int,
val xdpi: Float,
val ydpi: Float
) {
override fun toString(): String = "{\n" +
" \"id\": ${displayId},\n" +
" \"width\": ${size.width},\n" +
" \"height\": ${size.height},\n" +
" \"xdpi\": ${xdpi},\n" +
" \"ydpi\": ${ydpi},\n" +
" \"density\": ${density},\n" +
" \"rotation\": ${rotation}\n" +
"}\n"
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayManager.kt
================================================
package io.devicefarmer.minicap.utils
import android.annotation.SuppressLint
import android.hardware.display.VirtualDisplay
import android.view.Display
import android.view.Surface
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.regex.Pattern
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
class DisplayManager private constructor(private val manager: Any) {
private var createVirtualDisplayMethod: Method? = null
companion object {
fun create(): DisplayManager {
try {
val clazz = Class.forName("android.hardware.display.DisplayManagerGlobal")
val getInstanceMethod = clazz.getDeclaredMethod("getInstance")
val dmg = getInstanceMethod.invoke(null)
return DisplayManager(dmg)
} catch (e: ReflectiveOperationException) {
throw AssertionError(e)
}
}
}
private fun parseDisplayFlags(text: String?): Int {
val regex = Pattern.compile("FLAG_[A-Z_]+")
var flags = 0
if (text == null) return flags
val m = regex.matcher(text)
while (m.find()) {
val flagString = m.group()
try {
val filed: Field = Display::class.java.getDeclaredField(flagString)
flags = flags or filed.getInt(null)
} catch (e: ReflectiveOperationException) {
// Silently ignore, some flags reported by "dumpsys display" are @TestApi
}
}
return flags
}
fun getDisplayIds(): IntArray {
return try {
manager.javaClass.getMethod("getDisplayIds").invoke(manager) as IntArray
} catch (e: ReflectiveOperationException) {
throw AssertionError(e)
}
}
@Throws(NoSuchMethodException::class)
private fun getCreateVirtualDisplayMethod(): Method {
if (createVirtualDisplayMethod == null) {
createVirtualDisplayMethod = android.hardware.display.DisplayManager::class.java
.getMethod("createVirtualDisplay", String::class.java, Int::class.java, Int::class.java, Int::class.java, Surface::class.java)
}
return createVirtualDisplayMethod!!
}
@Throws(Exception::class)
fun createVirtualDisplay(name: String, width: Int, height: Int, displayIdToMirror: Int, surface: Surface): VirtualDisplay {
val method: Method = getCreateVirtualDisplayMethod()
return method.invoke(null, name, width, height, displayIdToMirror, surface) as VirtualDisplay
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayManagerGlobal.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.utils
import android.annotation.SuppressLint
import android.util.Size
/**
* Get DisplayInfo through DisplayManagerGlobal singleton (similar to what is done in MinitouchAgent)
*/
@SuppressLint("PrivateApi")
object DisplayManagerGlobal {
private var displayManager: Any?
private var clazz: Class<*>?
init {
try {
clazz = Class.forName("android.hardware.display.DisplayManagerGlobal")
val getInstance = clazz!!.getMethod("getInstance")
displayManager = getInstance.invoke(null)
} catch (e: ClassNotFoundException) {
throw AssertionError(e)
}
}
fun getDisplayInfo(displayId: Int): DisplayInfo {
return try {
val displayInfo =
displayManager!!.javaClass.getMethod("getDisplayInfo", Int::class.javaPrimitiveType)
.invoke(displayManager, displayId)
val cls: Class<*> = displayInfo.javaClass
val width = cls.getDeclaredField("logicalWidth").getInt(displayInfo)
val height = cls.getDeclaredField("logicalHeight").getInt(displayInfo)
val rotation = cls.getDeclaredField("rotation").getInt(displayInfo)
val layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo)
val density = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo)
val xdpi = cls.getDeclaredField("physicalXDpi").getFloat(displayInfo)
val ydpi = cls.getDeclaredField("physicalYDpi").getFloat(displayInfo)
DisplayInfo(displayId, Size(width, height), rotation, layerStack, density, xdpi, ydpi)
} catch (e: Exception) {
throw AssertionError(e)
}
}
fun getDisplayIds(): IntArray {
return try {
displayManager!!.javaClass.getMethod("getDisplayIds")
.invoke(displayManager) as IntArray
} catch (e: Exception) {
throw AssertionError(e)
}
}
}
================================================
FILE: experimental/app/src/main/java/io/devicefarmer/minicap/utils/SurfaceControl.kt
================================================
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devicefarmer.minicap.utils
import android.graphics.Rect
import android.os.IBinder
import android.view.Surface
import org.slf4j.Logger
import org.slf4j.LoggerFactory
/**
* Provide access to the SurfaceControl which is not part of the Android SDK using reflection.
* This SurfaceControl relies on a jni bindings that manages the SurfaceComposerClient that is
* in use in minicap-shared library.
*/
object SurfaceControl {
private val log: Logger = LoggerFactory.getLogger(SurfaceControl::class.java.simpleName)
private var clazz: Class<*>? = null
init {
try {
clazz = Class.forName("android.view.SurfaceControl")
} catch (e: ClassNotFoundException) {
log.error("could not instantiate SurfaceControl", e)
throw Error(e)
}
}
private fun logAndThrow(e: Exception) {
log.error("SurfaceControl error", e)
throw Error(e)
}
fun openTransaction() = try {
clazz!!.getMethod("openTransaction").invoke(null)
} catch (e: Exception) {
logAndThrow(e)
}
fun closeTransaction() = try {
clazz!!.getMethod("closeTransaction").invoke(null)
} catch (e: Exception) {
logAndThrow(e)
}
fun setDisplaySurface(display: IBinder?, consumer: Surface?) = try {
clazz!!.getMethod("setDisplaySurface", IBinder::class.java, Surface::class.java)
.invoke(null, display, consumer)
} catch (e: Exception) {
logAndThrow(e)
}
fun setDisplayLayerStack(displayToken: IBinder?, layerStack: Int) = try {
clazz!!.getMethod("setDisplayLayerStack", IBinder::class.java, Int::class.javaPrimitiveType)
.invoke(null, displayToken, layerStack)
} catch (e: Exception) {
logAndThrow(e)
}
fun setDisplayProjection(
displayToken: IBinder?,
rotation: Int, //Rotation0 = 0, Rotation90 = 1, Rotation180 = 2, Rotation270 = 3
layerStackRect: Rect?,
displayRect: Rect?
) {
try {
clazz!!.getMethod(
"setDisplayProjection", IBinder::class.java,
Int::class.javaPrimitiveType, Rect::class.java, Rect::class.java
)
.invoke(null, displayToken, rotation, layerStackRect, displayRect)
} catch (e: Exception) {
logAndThrow(e)
}
}
fun createDisplay(name: String?, secure: Boolean): IBinder {
return clazz!!.getMethod("createDisplay", String::class.java, Boolean::class.javaPrimitiveType ).invoke(null, name, secure) as IBinder
}
}
================================================
FILE: experimental/app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="0.09760314"
android:scaleY="0.09760314"
android:translateX="29.16"
android:translateY="45.764736">
<group android:translateY="133.59375">
<path android:pathData="M22.078125,-76.078125L22.429688,-67.640625Q30.796875,-77.484375,45,-77.484375Q60.960938,-77.484375,66.72656,-65.25Q70.52344,-70.734375,76.60547,-74.109375Q82.6875,-77.484375,90.984375,-77.484375Q116.015625,-77.484375,116.4375,-50.976562L116.4375,0L103.42969,0L103.42969,-50.203125Q103.42969,-58.359375,99.703125,-62.402344Q95.97656,-66.44531,87.1875,-66.44531Q79.94531,-66.44531,75.16406,-62.121094Q70.38281,-57.796875,69.609375,-50.484375L69.609375,0L56.53125,0L56.53125,-49.851562Q56.53125,-66.44531,40.289062,-66.44531Q27.492188,-66.44531,22.78125,-55.546875L22.78125,0L9.7734375,0L9.7734375,-76.078125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M149.97656,0L136.96875,0L136.96875,-76.078125L149.97656,-76.078125ZM135.91406,-96.25781Q135.91406,-99.421875,137.84766,-101.60156Q139.78125,-103.78125,143.57812,-103.78125Q147.375,-103.78125,149.34375,-101.60156Q151.3125,-99.421875,151.3125,-96.25781Q151.3125,-93.09375,149.34375,-90.984375Q147.375,-88.875,143.57812,-88.875Q139.78125,-88.875,137.84766,-90.984375Q135.91406,-93.09375,135.91406,-96.25781Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M183.14844,-76.078125L183.57031,-66.515625Q192.28906,-77.484375,206.35156,-77.484375Q230.46875,-77.484375,230.67969,-50.273438L230.67969,0L217.67188,0L217.67188,-50.34375Q217.60156,-58.570312,213.91016,-62.507812Q210.21875,-66.44531,202.41406,-66.44531Q196.08594,-66.44531,191.30469,-63.070312Q186.52344,-59.695312,183.85156,-54.210938L183.85156,0L170.84375,0L170.84375,-76.078125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M263.97656,0L250.96875,0L250.96875,-76.078125L263.97656,-76.078125ZM249.91406,-96.25781Q249.91406,-99.421875,251.84766,-101.60156Q253.78125,-103.78125,257.57812,-103.78125Q261.375,-103.78125,263.34375,-101.60156Q265.3125,-99.421875,265.3125,-96.25781Q265.3125,-93.09375,263.34375,-90.984375Q261.375,-88.875,257.57812,-88.875Q253.78125,-88.875,251.84766,-90.984375Q249.91406,-93.09375,249.91406,-96.25781Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M315.35938,-9.2109375Q322.3203,-9.2109375,327.52344,-13.4296875Q332.72656,-17.648438,333.28906,-23.976562L345.59375,-23.976562Q345.2422,-17.4375,341.09375,-11.53125Q336.9453,-5.625,330.01953,-2.109375Q323.09375,1.40625,315.35938,1.40625Q299.8203,1.40625,290.64453,-8.964844Q281.46875,-19.335938,281.46875,-37.335938L281.46875,-39.515625Q281.46875,-50.625,285.54688,-59.273438Q289.625,-67.921875,297.2539,-72.703125Q304.8828,-77.484375,315.28906,-77.484375Q328.08594,-77.484375,336.5586,-69.82031Q345.03125,-62.15625,345.59375,-49.921875L333.28906,-49.921875Q332.72656,-57.304688,327.69922,-62.05078Q322.67188,-66.796875,315.28906,-66.796875Q305.375,-66.796875,299.92578,-59.660156Q294.47656,-52.523438,294.47656,-39.023438L294.47656,-36.5625Q294.47656,-23.414062,299.89062,-16.3125Q305.3047,-9.2109375,315.35938,-9.2109375Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M406.8125,0Q405.6875,-2.25,404.98438,-8.015625Q395.91406,1.40625,383.32812,1.40625Q372.07812,1.40625,364.8711,-4.9570312Q357.66406,-11.3203125,357.66406,-21.09375Q357.66406,-32.976562,366.69922,-39.55078Q375.73438,-46.125,392.1172,-46.125L404.77344,-46.125L404.77344,-52.101562Q404.77344,-58.921875,400.6953,-62.964844Q396.6172,-67.00781,388.67188,-67.00781Q381.71094,-67.00781,377,-63.492188Q372.28906,-59.976562,372.28906,-54.984375L359.21094,-54.984375Q359.21094,-60.679688,363.2539,-65.98828Q367.29688,-71.296875,374.22266,-74.390625Q381.14844,-77.484375,389.4453,-77.484375Q402.59375,-77.484375,410.04688,-70.91016Q417.5,-64.33594,417.78125,-52.804688L417.78125,-17.789062Q417.78125,-7.3125,420.45312,-1.125L420.45312,0ZM385.22656,-9.9140625Q391.34375,-9.9140625,396.82812,-13.078125Q402.3125,-16.242188,404.77344,-21.304688L404.77344,-36.914062L394.57812,-36.914062Q370.67188,-36.914062,370.67188,-22.921875Q370.67188,-16.804688,374.75,-13.359375Q378.82812,-9.9140625,385.22656,-9.9140625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M502.10938,-37.195312Q502.10938,-19.828125,494.16406,-9.2109375Q486.21875,1.40625,472.64844,1.40625Q458.79688,1.40625,450.85156,-7.3828125L450.85156,29.25L437.84375,29.25L437.84375,-76.078125L449.72656,-76.078125L450.35938,-67.640625Q458.3047,-77.484375,472.4375,-77.484375Q486.14844,-77.484375,494.1289,-67.14844Q502.10938,-56.8125,502.10938,-38.390625ZM489.10156,-38.671875Q489.10156,-51.539062,483.6172,-58.992188Q478.1328,-66.44531,468.5703,-66.44531Q456.7578,-66.44531,450.85156,-55.96875L450.85156,-19.617188Q456.6875,-9.2109375,468.71094,-9.2109375Q478.0625,-9.2109375,483.58203,-16.628906Q489.10156,-24.046875,489.10156,-38.671875Z"
android:fillColor="#FFFFFF"/>
</group>
</group>
</vector>
================================================
FILE: experimental/app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
================================================
FILE: experimental/app/src/main/res/values/strings.xml
================================================
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">Minicap</string>
</resources>
================================================
FILE: experimental/app/src/main/res/values/themes.xml
================================================
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Minicap" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
================================================
FILE: experimental/app/src/main/res/values-night/themes.xml
================================================
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Minicap" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
================================================
FILE: experimental/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.6.21"
repositories {
google()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.8.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.30"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: experimental/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: experimental/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
# When configured, Gradle will run in incubating parallel mode.
# 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
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
================================================
FILE: experimental/gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: experimental/gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: experimental/minicap
================================================
#!/bin/sh
CLASSPATH=/data/local/tmp/minicap-0.1-debug.apk app_process /system/bin io.devicefarmer.minicap.Main
================================================
FILE: experimental/settings.gradle
================================================
pluginManagement {
repositories {
gradlePluginPortal()
jcenter()
}
}
include ':app'
rootProject.name = "Minicap"
================================================
FILE: gdb.sh
================================================
#!/usr/bin/env bash
# Fail on error, verbose output
set -exo pipefail
# Check NDK setup
if [ -z "$NDK_ROOT" ]; then
echo 'Environment variable $NDK_ROOT required for operation' >&2
exit 1
fi
# Figure out which ABIs the device supports
abi=$(adb shell getprop ro.product.cpu.abi | tr -d '\r')
sdk=$(adb shell getprop ro.build.version.sdk | tr -d '\r')
# PIE is only supported since SDK 16
if (($sdk >= 16)); then
bin=minicap
else
bin=minicap-nopie
fi
# Check if we're debuggable
gdb_setup=$(make --no-print-dir -f "$NDK_ROOT/build/core/build-local.mk" -C "$PWD" DUMP_NDK_APP_GDBSETUP APP_ABI=$abi)
if [ ! -f "$gdb_setup" ]; then
echo "Unable to find $gdb_setup, rebuild with 'ndk-build NDK_DEBUG=1'"
exit 4
fi
# Get output directory
out=$(make --no-print-dir -f "$NDK_ROOT/build/core/build-local.mk" -C "$PWD" DUMP_TARGET_OUT APP_ABI=$abi)
# ABI-specific config
prebabi=$abi
libpath=lib
case $abi in
"armeabi" | "armeabi-v7a")
prebabi=arm
;;
"arm64-v8a")
libpath=lib64
prebabi=arm64
;;
"x86_64")
libpath=lib64
;;
"mips64")
libpath=lib64
;;
esac
# Validate prebuilt mapping
if [ ! -e "$NDK_ROOT/prebuilt/android-$prebabi" ]; then
echo "Unable to find prebuilts in $NDK_ROOT/prebuilt/android-$prebabi; incorrect mapping?" >&2
exit 3
fi
# Find toolchain
toolchain=$(make --no-print-dir -f "$NDK_ROOT/build/core/build-local.mk" -C "$PWD" DUMP_TOOLCHAIN_PREFIX APP_ABI=$abi)
gdbclient="${toolchain}gdb"
gdbserver="$NDK_ROOT/prebuilt/android-$prebabi/gdbserver"
# Create a directory for our resources
dir=/data/local/tmp/minicap-gdb
adb shell "mkdir $dir 2>/dev/null"
# Find pid
pid=$(adb shell ps | tr -d '\r' | awk '$NF ~ /\/minicap$/ {print $2}' | tail -n1)
if [ -z "$pid" ]; then
echo 'Unable to find minicap pid' >&2
exit 2
fi
# Upload gdbserver
adb push "$gdbserver" $dir
# Pull libs
adb pull /system/bin/linker $out/linker
adb pull /system/$libpath $out
# Launch gdbserver
adb shell $dir/gdbserver :5039 --attach $pid &
gdbserver_pid=$!
trap "kill $gdbserver_pid 2>/dev/null" EXIT
# Set up the forward
adb forward tcp:5039 tcp:5039
# Find initial gdb.setup
cp -f "$gdb_setup" gdb.setup
# Augment gdb.setup
cat <<EOT >>gdb.setup
file libs/$abi/$bin
target remote :5039
EOT
# Launch gdb
"$gdbclient" -x gdb.setup
# Clean up
adb shell rm -r $dir
================================================
FILE: jni/Android.mk
================================================
include $(call all-subdir-makefiles)
================================================
FILE: jni/Application.mk
================================================
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# Get C++11 working
APP_CPPFLAGS += -std=c++11 -fexceptions
APP_STL := c++_static
# Disable PIE for SDK <16 support. Enable manually for >=5.0
# where necessary.
APP_PIE := false
APP_CFLAGS += \
-Ofast \
-funroll-loops \
-fno-strict-aliasing \
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
# http://community.arm.com/groups/tools/blog/2013/04/15/arm-cortex-a-processors-and-gcc-command-lines
APP_CFLAGS += \
-march=armv7-a \
-mfpu=neon \
-mfloat-abi=softfp \
-marm \
-fprefetch-loop-arrays \
-DHAVE_NEON=1 \
else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a-hard)
# http://community.arm.com/groups/tools/blog/2013/04/15/arm-cortex-a-processors-and-gcc-command-lines
APP_CFLAGS += \
-march=armv7-a \
-mfpu=neon \
-mfloat-abi=hard \
-marm \
-fprefetch-loop-arrays \
-DHAVE_NEON=1 \
-mhard-float \
-D_NDK_MATH_NO_SOFTFP=1 \
APP_LDFLAGS += -lm_hard
else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
# http://community.arm.com/groups/tools/blog/2013/04/15/arm-cortex-a-processors-and-gcc-command-lines
APP_CFLAGS += \
-mcpu=cortex-a15 \
-mfpu=neon-vfpv4 \
-mfloat-abi=softfp \
-DHAVE_NEON=1 \
else ifeq ($(TARGET_ARCH_ABI),x86)
# From Android on x86: An Introduction to Optimizing for Intel® Architecture
# Chapter 12
APP_CFLAGS += \
-fprefetch-loop-arrays \
-fno-short-enums \
-finline-limit=300 \
-fomit-frame-pointer \
-mssse3 \
-mfpmath=sse \
-masm=intel \
-DHAVE_NEON=1 \
endif
================================================
FILE: jni/minicap/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := minicap-common
LOCAL_SRC_FILES := \
JpgEncoder.cpp \
SimpleServer.cpp \
minicap.cpp \
LOCAL_STATIC_LIBRARIES := \
libjpeg-turbo \
LOCAL_SHARED_LIBRARIES := \
minicap-shared \
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
# Enable PIE manually. Will get reset on $(CLEAR_VARS).
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie
LOCAL_MODULE := minicap
LOCAL_STATIC_LIBRARIES := minicap-common
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE := minicap-nopie
LOCAL_STATIC_LIBRARIES := minicap-common
include $(BUILD_EXECUTABLE)
================================================
FILE: jni/minicap/JpgEncoder.cpp
================================================
#include <stdexcept>
#include "JpgEncoder.hpp"
#include "util/debug.h"
JpgEncoder::JpgEncoder(unsigned int prePadding, unsigned int postPadding)
: mTjHandle(tjInitCompress()),
mSubsampling(TJSAMP_420),
mEncodedData(NULL),
mPrePadding(prePadding),
mPostPadding(postPadding),
mMaxWidth(0),
mMaxHeight(0)
{
}
JpgEncoder::~JpgEncoder() {
tjFree(mEncodedData);
}
bool
JpgEncoder::encode(Minicap::Frame* frame, unsigned int quality) {
unsigned char* offset = getEncodedData();
return 0 == tjCompress2(
mTjHandle,
(unsigned char*) frame->data,
frame->width,
frame->stride * frame->bpp,
frame->height,
convertFormat(frame->format),
&offset,
&mEncodedSize,
mSubsampling,
quality,
TJFLAG_FASTDCT | TJFLAG_NOREALLOC
);
}
int
JpgEncoder::getEncodedSize() {
return mEncodedSize;
}
unsigned char*
JpgEncoder::getEncodedData() {
return mEncodedData + mPrePadding;
}
bool
JpgEncoder::reserveData(uint32_t width, uint32_t height) {
if (width == mMaxWidth && height == mMaxHeight) {
return true;
}
tjFree(mEncodedData);
unsigned long maxSize = mPrePadding + mPostPadding + tjBufSize(
width,
height,
mSubsampling
);
MCINFO("Allocating %ld bytes for JPG encoder", maxSize);
mEncodedData = tjAlloc(maxSize);
if (mEncodedData == NULL) {
return false;
}
mMaxWidth = width;
mMaxHeight = height;
return true;
}
int
JpgEncoder::convertFormat(Minicap::Format format) {
switch (format) {
case Minicap::FORMAT_RGBA_8888:
return TJPF_RGBA;
case Minicap::FORMAT_RGBX_8888:
return TJPF_RGBX;
case Minicap::FORMAT_RGB_888:
return TJPF_RGB;
case Minicap::FORMAT_BGRA_8888:
return TJPF_BGRA;
default:
throw std::runtime_error("Unsupported pixel format");
}
}
================================================
FILE: jni/minicap/JpgEncoder.hpp
================================================
#ifndef MINICAP_JPG_ENCODER_HPP
#define MINICAP_JPG_ENCODER_HPP
#include <turbojpeg.h>
#include "Minicap.hpp"
class JpgEncoder {
public:
JpgEncoder(unsigned int prePadding, unsigned int postPadding);
~JpgEncoder();
bool
encode(Minicap::Frame* frame, unsigned int quality);
int
getEncodedSize();
unsigned char*
getEncodedData();
bool
reserveData(uint32_t width, uint32_t height);
private:
tjhandle mTjHandle;
int mSubsampling;
unsigned int mPrePadding;
unsigned int mPostPadding;
unsigned int mMaxWidth;
unsigned int mMaxHeight;
unsigned char* mEncodedData;
unsigned long mEncodedSize;
static int
convertFormat(Minicap::Format format);
};
#endif
================================================
FILE: jni/minicap/Projection.hpp
================================================
#ifndef MINICAP_PROJECTION_HPP
#define MINICAP_PROJECTION_HPP
#include <cmath>
#include <ostream>
class Projection {
public:
class Parser {
public:
Parser(): mState(real_width_start) {
}
bool
parse(Projection& proj, const char* lo, const char* hi) {
consume: while (lo < hi) {
switch (mState) {
case real_width_start:
if (isDigit(*lo)) {
proj.realWidth += (*lo - 48);
mState = real_width_continued;
lo += 1;
goto consume;
}
return false;
case real_width_continued:
if (isDigit(*lo)) {
proj.realWidth *= 10;
proj.realWidth += (*lo - 48);
lo += 1;
goto consume;
}
if (*lo == 'x') {
mState = real_height_start;
lo += 1;
goto consume;
}
return false;
case real_height_start:
if (isDigit(*lo)) {
proj.realHeight += (*lo - 48);
mState = real_height_continued;
lo += 1;
goto consume;
}
return false;
case real_height_continued:
if (isDigit(*lo)) {
proj.realHeight *= 10;
proj.realHeight += (*lo - 48);
lo += 1;
goto consume;
}
if (*lo == '@') {
mState = virtual_width_start;
lo += 1;
goto consume;
}
return false;
case virtual_width_start:
if (isDigit(*lo)) {
proj.virtualWidth += (*lo - 48);
mState = virtual_width_continued;
lo += 1;
goto consume;
}
return false;
case virtual_width_continued:
if (isDigit(*lo)) {
proj.virtualWidth *= 10;
proj.virtualWidth += (*lo - 48);
lo += 1;
goto consume;
}
if (*lo == 'x') {
mState = virtual_height_start;
lo += 1;
goto consume;
}
return false;
case virtual_height_start:
if (isDigit(*lo)) {
proj.virtualHeight += (*lo - 48);
mState = virtual_height_continued;
lo += 1;
goto consume;
}
return false;
case virtual_height_continued:
if (isDigit(*lo)) {
proj.virtualHeight *= 10;
proj.virtualHeight += (*lo - 48);
lo += 1;
goto consume;
}
if (*lo == '/') {
mState = rotation_start;
lo += 1;
goto consume;
}
return false;
case rotation_start:
if (*lo == '0') {
proj.rotation = 0;
mState = satisfied;
lo += 1;
goto consume;
}
if (*lo == '9') {
mState = rotation_90_2;
lo += 1;
goto consume;
}
if (*lo == '1') {
mState = rotation_180_2;
lo += 1;
goto consume;
}
if (*lo == '2') {
mState = rotation_270_2;
lo += 1;
goto consume;
}
return false;
case rotation_90_2:
if (*lo == '0') {
proj.rotation = 1;
mState = satisfied;
lo += 1;
goto consume;
}
return false;
case rotation_180_2:
if (*lo == '8') {
mState = rotation_180_3;
lo += 1;
goto consume;
}
return false;
case rotation_180_3:
if (*lo == '0') {
proj.rotation = 2;
mState = satisfied;
lo += 1;
goto consume;
}
return false;
case rotation_270_2:
if (*lo == '7') {
mState = rotation_270_3;
lo += 1;
goto consume;
}
return false;
case rotation_270_3:
if (*lo == '0') {
proj.rotation = 3;
mState = satisfied;
lo += 1;
goto consume;
}
return false;
case satisfied:
return false;
}
}
return mState == satisfied;
}
private:
enum State {
real_width_start,
real_width_continued,
real_height_start,
real_height_continued,
virtual_width_start,
virtual_width_continued,
virtual_height_start,
virtual_height_continued,
rotation_start,
rotation_90_2,
rotation_180_2,
rotation_180_3,
rotation_270_2,
rotation_270_3,
satisfied,
};
State mState;
inline bool
isDigit(int input) {
return input >= '0' && input <= '9';
}
};
const uint32_t MAX_WIDTH = 10000;
const uint32_t MAX_HEIGHT = 10000;
uint32_t realWidth;
uint32_t realHeight;
uint32_t virtualWidth;
uint32_t virtualHeight;
uint32_t rotation;
Projection()
: realWidth(0),
realHeight(0),
virtualWidth(0),
virtualHeight(0),
rotation(0) {
}
void
forceMaximumSize() {
if (virtualWidth > realWidth) {
virtualWidth = realWidth;
}
if (virtualHeight > realHeight) {
virtualHeight = realHeight;
}
}
void
forceAspectRatio() {
double aspect = static_cast<double>(realWidth) / static_cast<double>(realHeight);
if (virtualHeight > (uint32_t) (virtualWidth / aspect)) {
virtualHeight = static_cast<uint32_t>(round(virtualWidth / aspect));
}
else {
virtualWidth = static_cast<uint32_t>(round(virtualHeight * aspect));
}
}
bool
valid() {
return realWidth > 0 && realHeight > 0 &&
virtualWidth > 0 && virtualHeight > 0 &&
virtualWidth <= realWidth && virtualHeight <= realHeight;
}
friend std::ostream&
operator<< (std::ostream& stream, const Projection& proj) {
stream << proj.realWidth << 'x' << proj.realHeight << '@'
<< proj.virtualWidth << 'x' << proj.virtualHeight << '/' << proj.rotation;
return stream;
}
};
#endif
================================================
FILE: jni/minicap/SimpleServer.cpp
================================================
#include "SimpleServer.hpp"
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
SimpleServer::SimpleServer(): mFd(0) {
}
SimpleServer::~SimpleServer() {
if (mFd > 0) {
::close(mFd);
}
}
int
SimpleServer::start(const char* sockname) {
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd < 0) {
return sfd;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(&addr.sun_path[1], sockname, strlen(sockname));
if (::bind(sfd, (struct sockaddr*) &addr,
sizeof(sa_family_t) + strlen(sockname) + 1) < 0) {
::close(sfd);
return -1;
}
::listen(sfd, 1);
mFd = sfd;
return mFd;
}
int
SimpleServer::accept() {
struct sockaddr_un addr;
socklen_t addr_len = sizeof(addr);
return ::accept(mFd, (struct sockaddr *) &addr, &addr_len);
}
================================================
FILE: jni/minicap/SimpleServer.hpp
================================================
#ifndef MINICAP_SIMPLE_SERVER_HPP
#define MINICAP_SIMPLE_SERVER_HPP
class SimpleServer {
public:
SimpleServer();
~SimpleServer();
int
start(const char* sockname);
int accept();
private:
int mFd;
};
#endif
================================================
FILE: jni/minicap/minicap.cpp
================================================
#include <fcntl.h>
#include <getopt.h>
#include <linux/fb.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <thread>
#include <cmath>
#include <condition_variable>
#include <chrono>
#include <future>
#include <iostream>
#include <mutex>
#include <thread>
#include <Minicap.hpp>
#include "util/debug.h"
#include "JpgEncoder.hpp"
#include "SimpleServer.hpp"
#include "Projection.hpp"
#define BANNER_VERSION 1
#define BANNER_SIZE 24
#define DEFAULT_SOCKET_NAME "minicap"
#define DEFAULT_DISPLAY_ID 0
#define DEFAULT_JPG_QUALITY 80
enum {
QUIRK_DUMB = 1,
QUIRK_ALWAYS_UPRIGHT = 2,
QUIRK_TEAR = 4,
};
static void
usage(const char* pname) {
fprintf(stderr,
"Usage: %s [-h] [-n <name>]\n"
" -d <id>: Display ID. (%d)\n"
" -n <name>: Change the name of the abtract unix domain socket. (%s)\n"
" -P <value>: Display projection (<w>x<h>@<w>x<h>/{0|90|180|270}).\n"
" -Q <value>: JPEG quality (0-100).\n"
" -s: Take a screenshot and output it to stdout. Needs -P.\n"
" -S: Skip frames when they cannot be consumed quickly enough.\n"
" -r <value>: Frame rate (frames/s)"
" -t: Attempt to get the capture method running, then exit.\n"
" -i: Get display information in JSON format. May segfault.\n"
" -h: Show help.\n",
pname, DEFAULT_DISPLAY_ID, DEFAULT_SOCKET_NAME
);
}
class FrameWaiter: public Minicap::FrameAvailableListener {
public:
FrameWaiter()
: mPendingFrames(0),
mTimeout(std::chrono::milliseconds(100)),
mStopped(false) {
}
int
waitForFrame() {
std::unique_lock<std::mutex> lock(mMutex);
while (!mStopped) {
if (mCondition.wait_for(lock, mTimeout, [this]{return mPendingFrames > 0;})) {
return mPendingFrames--;
}
}
return 0;
}
void
reportExtraConsumption(int count) {
std::unique_lock<std::mutex> lock(mMutex);
mPendingFrames -= count;
}
void
onFrameAvailable() {
std::unique_lock<std::mutex> lock(mMutex);
mPendingFrames += 1;
mCondition.notify_one();
}
void
stop() {
mStopped = true;
}
bool
isStopped() {
return mStopped;
}
private:
std::mutex mMutex;
std::condition_variable mCondition;
std::chrono::milliseconds mTimeout;
int mPendingFrames;
bool mStopped;
};
static int
pumps(int fd, unsigned char* data, size_t length) {
do {
// Make sure that we don't generate a SIGPIPE even if the socket doesn't
// exist anymore. We'll still get an EPIPE which is perfect.
int wrote = send(fd, data, length, MSG_NOSIGNAL);
if (wrote < 0) {
return wrote;
}
data += wrote;
length -= wrote;
}
while (length > 0);
return 0;
}
static int
pumpf(int fd, unsigned char* data, size_t length) {
do {
int wrote = write(fd, data, length);
if (wrote < 0) {
return wrote;
}
data += wrote;
length -= wrote;
}
while (length > 0);
return 0;
}
static int
putUInt32LE(unsigned char* data, int value) {
data[0] = (value & 0x000000FF) >> 0;
data[1] = (value & 0x0000FF00) >> 8;
data[2] = (value & 0x00FF0000) >> 16;
data[3] = (value & 0xFF000000) >> 24;
return 0;
}
static int
try_get_framebuffer_display_info(uint32_t displayId, Minicap::DisplayInfo* info) {
char path[64];
sprintf(path, "/dev/graphics/fb%d", displayId);
int fd = open(path, O_RDONLY);
if (fd < 0) {
MCERROR("Cannot open %s", path);
return -1;
}
fb_var_screeninfo vinfo;
if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) {
close(fd);
MCERROR("Cannot get FBIOGET_VSCREENINFO of %s", path);
return -1;
}
info->width = vinfo.xres;
info->height = vinfo.yres;
info->orientation = vinfo.rotate;
info->xdpi = static_cast<float>(vinfo.xres) / static_cast<float>(vinfo.width) * 25.4;
info->ydpi = static_cast<float>(vinfo.yres) / static_cast<float>(vinfo.height) * 25.4;
info->size = std::sqrt(
(static_cast<float>(vinfo.width) * static_cast<float>(vinfo.width)) +
(static_cast<float>(vinfo.height) * static_cast<float>(vinfo.height))) / 25.4;
info->density = std::sqrt(
(static_cast<float>(vinfo.xres) * static_cast<float>(vinfo.xres)) +
(static_cast<float>(vinfo.yres) * static_cast<float>(vinfo.yres))) / info->size;
info->secure = false;
info->fps = 0;
close(fd);
return 0;
}
static FrameWaiter gWaiter;
static void
signal_handler(int signum) {
switch (signum) {
case SIGINT:
MCINFO("Received SIGINT, stopping");
gWaiter.stop();
break;
case SIGTERM:
MCINFO("Received SIGTERM, stopping");
gWaiter.stop();
break;
default:
abort();
break;
}
}
int
main(int argc, char* argv[]) {
const char* pname = argv[0];
const char* sockname = DEFAULT_SOCKET_NAME;
uint32_t displayId = DEFAULT_DISPLAY_ID;
unsigned int quality = DEFAULT_JPG_QUALITY;
int framePeriodMs = 0;
bool showInfo = false;
bool takeScreenshot = false;
bool skipFrames = false;
bool testOnly = false;
Projection proj;
int opt;
while ((opt = getopt(argc, argv, "d:n:P:Q:r:siSth")) != -1) {
float frameRate;
switch (opt) {
case 'd':
displayId = atoi(optarg);
break;
case 'n':
sockname = optarg;
break;
case 'P': {
Projection::Parser parser;
if (!parser.parse(proj, optarg, optarg + strlen(optarg))) {
std::cerr << "ERROR: invalid format for -P, need <w>x<h>@<w>x<h>/{0|90|180|270}" << std::endl;
return EXIT_FAILURE;
}
break;
}
case 'Q':
quality = atoi(optarg);
break;
case 's':
takeScreenshot = true;
break;
case 'i':
showInfo = true;
break;
case 'S':
skipFrames = true;
break;
case 'r':
frameRate = atof(optarg);
if(frameRate <= 0.0) {
MCINFO("Invalid framerate '%s', expecting a float > 0", optarg);
return EXIT_FAILURE;
} else {
framePeriodMs = 1000/frameRate;
skipFrames = true;
MCINFO("framerate: %.2f (period %d ms)", frameRate, framePeriodMs);
}
break;
case 't':
testOnly = true;
break;
case 'h':
usage(pname);
return EXIT_SUCCESS;
case '?':
default:
usage(pname);
return EXIT_FAILURE;
}
}
// Set up signal handler.
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
// Start Android's thread pool so that it will be able to serve our requests.
minicap_start_thread_pool();
if (showInfo) {
Minicap::DisplayInfo info;
if (minicap_try_get_display_info(displayId, &info) != 0) {
if (try_get_framebuffer_display_info(displayId, &info) != 0) {
MCERROR("Unable to get display info");
return EXIT_FAILURE;
}
}
int rotation;
switch (info.orientation) {
case Minicap::ORIENTATION_0:
rotation = 0;
break;
case Minicap::ORIENTATION_90:
rotation = 90;
break;
case Minicap::ORIENTATION_180:
rotation = 180;
break;
case Minicap::ORIENTATION_270:
rotation = 270;
break;
}
std::cout.precision(2);
std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
std::cout << "{" << std::endl
<< " \"id\": " << displayId << "," << std::endl
<< " \"width\": " << info.width << "," << std::endl
<< " \"height\": " << info.height << "," << std::endl
<< " \"xdpi\": " << info.xdpi << "," << std::endl
<< " \"ydpi\": " << info.ydpi << "," << std::endl
<< " \"size\": " << info.size << "," << std::endl
<< " \"density\": " << info.density << "," << std::endl
<< " \"fps\": " << info.fps << "," << std::endl
<< " \"secure\": " << (info.secure ? "true" : "false") << "," << std::endl
<< " \"rotation\": " << rotation << std::endl
<< "}" << std::endl;
return EXIT_SUCCESS;
}
proj.forceMaximumSize();
proj.forceAspectRatio();
if (!proj.valid()) {
std::cerr << "ERROR: missing or invalid -P" << std::endl;
return EXIT_FAILURE;
}
std::cerr << "PID: " << getpid() << std::endl;
std::cerr << "INFO: Using projection " << proj << std::endl;
// Disable STDOUT buffering.
setbuf(stdout, NULL);
// Set real display size.
Minicap::DisplayInfo realInfo;
realInfo.width = proj.realWidth;
realInfo.height = proj.realHeight;
// Figure out desired display size.
Minicap::DisplayInfo desiredInfo;
desiredInfo.width = proj.virtualWidth;
desiredInfo.height = proj.virtualHeight;
desiredInfo.orientation = proj.rotation;
// Leave a 4-byte padding to the encoder so that we can inject the size
// to the same buffer.
JpgEncoder encoder(4, 0);
Minicap::Frame frame;
bool haveFrame = false;
// Server config.
SimpleServer server;
// Set up minicap.
Minicap* minicap = minicap_create(displayId);
if (minicap == NULL) {
return EXIT_FAILURE;
}
// Figure out the quirks the current capture method has.
unsigned char quirks = 0;
switch (minicap->getCaptureMethod()) {
case Minicap::METHOD_FRAMEBUFFER:
quirks |= QUIRK_DUMB | QUIRK_TEAR;
break;
case Minicap::METHOD_SCREENSHOT:
quirks |= QUIRK_DUMB;
break;
case Minicap::METHOD_VIRTUAL_DISPLAY:
quirks |= QUIRK_ALWAYS_UPRIGHT;
break;
}
if (minicap->setRealInfo(realInfo) != 0) {
MCERROR("Minicap did not accept real display info");
goto disaster;
}
if (minicap->setDesiredInfo(desiredInfo) != 0) {
MCERROR("Minicap did not accept desired display info");
goto disaster;
}
minicap->setFrameAvailableListener(&gWaiter);
if (minicap->applyConfigChanges() != 0) {
MCERROR("Unable to start minicap with current config");
goto disaster;
}
if (!encoder.reserveData(realInfo.width, realInfo.height)) {
MCERROR("Unable to reserve data for JPG encoder");
goto disaster;
}
if (takeScreenshot) {
if (!gWaiter.waitForFrame()) {
MCERROR("Unable to wait for frame");
goto disaster;
}
int err;
if ((err = minicap->consumePendingFrame(&frame)) != 0) {
MCERROR("Unable to consume pending frame");
goto disaster;
}
if (!encoder.encode(&frame, quality)) {
MCERROR("Unable to encode frame");
goto disaster;
}
if (pumpf(STDOUT_FILENO, encoder.getEncodedData(), encoder.getEncodedSize()) < 0) {
MCERROR("Unable to output encoded frame data");
goto disaster;
}
return EXIT_SUCCESS;
}
if (testOnly) {
if (gWaiter.waitForFrame() <= 0) {
MCERROR("Did not receive any frames");
std::cout << "FAIL" << std::endl;
return EXIT_FAILURE;
}
minicap_free(minicap);
std::cout << "OK" << std::endl;
return EXIT_SUCCESS;
}
if (!server.start(sockname)) {
MCERROR("Unable to start server on namespace '%s'", sockname);
goto disaster;
}
// Prepare banner for clients.
unsigned char banner[BANNER_SIZE];
banner[0] = (unsigned char) BANNER_VERSION;
banner[1] = (unsigned char) BANNER_SIZE;
putUInt32LE(banner + 2, getpid());
putUInt32LE(banner + 6, realInfo.width);
putUInt32LE(banner + 10, realInfo.height);
putUInt32LE(banner + 14, desiredInfo.width);
putUInt32LE(banner + 18, desiredInfo.height);
banner[22] = (unsigned char) desiredInfo.orientation;
banner[23] = quirks;
int fd;
while (!gWaiter.isStopped() && (fd = server.accept()) > 0) {
MCINFO("New client connection");
if (pumps(fd, banner, BANNER_SIZE) < 0) {
close(fd);
continue;
}
int pending, err;
while (!gWaiter.isStopped() && (pending = gWaiter.waitForFrame()) > 0) {
auto frameAvailableAt = std::chrono::steady_clock::now();
if (skipFrames && pending > 1) {
// Skip frames if we have too many. Not particularly thread safe,
// but this loop should be the only consumer anyway (i.e. nothing
// else decreases the frame count).
gWaiter.reportExtraConsumption(pending - 1);
while (--pending >= 1) {
if ((err = minicap->consumePendingFrame(&frame)) != 0) {
if (err == -EINTR) {
MCINFO("Frame consumption interrupted by EINTR");
goto close;
}
else {
MCERROR("Unable to skip pending frame");
goto disaster;
}
}
minicap->releaseConsumedFrame(&frame);
}
}
if ((err = minicap->consumePendingFrame(&frame)) != 0) {
if (err == -EINTR) {
MCINFO("Frame consumption interrupted by EINTR");
goto close;
}
else {
MCERROR("Unable to consume pending frame");
goto disaster;
}
}
haveFrame = true;
// Encode the frame.
if (!encoder.encode(&frame, quality)) {
MCERROR("Unable to encode frame");
goto disaster;
}
// Push it out synchronously because it's fast and we don't care
// about other clients.
unsigned char* data = encoder.getEncodedData() - 4;
size_t size = encoder.getEncodedSize();
putUInt32LE(data, size);
if (pumps(fd, data, size + 4) < 0) {
break;
}
// This will call onFrameAvailable() on older devices, so we have
// to do it here or the loop will stop.
minicap->releaseConsumedFrame(&frame);
haveFrame = false;
if(framePeriodMs > 0) {
std::this_thread::sleep_until(frameAvailableAt + std::chrono::milliseconds(framePeriodMs));
}
}
close:
MCINFO("Closing client connection");
close(fd);
// Have we consumed one frame but are still holding it?
if (haveFrame) {
minicap->releaseConsumedFrame(&frame);
}
}
minicap_free(minicap);
return EXIT_SUCCESS;
disaster:
if (haveFrame) {
minicap->releaseConsumedFrame(&frame);
}
minicap_free(minicap);
return EXIT_FAILURE;
}
================================================
FILE: jni/minicap/util/debug.h
================================================
#ifndef __minicap_dbg_h__
#define __minicap_dbg_h__
// These macros were originally from
// http://c.learncodethehardway.org/book/ex20.html
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef NDEBUG
#define MCDEBUG(M, ...)
#else
#define MCDEBUG(M, ...) fprintf(stderr, "DEBUG: %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif
#define MCCLEAN_ERRNO() (errno == 0 ? "None" : strerror(errno))
#define MCERROR(M, ...) fprintf(stderr, "ERROR: (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, MCCLEAN_ERRNO(), ##__VA_ARGS__)
#define MCWARN(M, ...) fprintf(stderr, "WARN: (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, MCCLEAN_ERRNO(), ##__VA_ARGS__)
#define MCINFO(M, ...) fprintf(stderr, "INFO: (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define MCCHECK(A, M, ...) if(!(A)) { MCERROR(M, ##__VA_ARGS__); errno=0; goto error; }
#define MCSENTINEL(M, ...) { MCERROR(M, ##__VA_ARGS__); errno=0; goto error; }
#define MCCHECK_MEM(A) check((A), "Out of memory.")
#define MCCHECK_DEBUG(A, M, ...) if(!(A)) { MCDEBUG(M, ##__VA_ARGS__); errno=0; goto error; }
#endif
================================================
FILE: jni/minicap/util/formatter.hpp
================================================
#ifndef MINICAP_UTIL_FORMATTER_H
#define MINICAP_UTIL_FORMATTER_H
// Originally from http://stackoverflow.com/a/12262626/1540573
class formatter
{
public:
formatter() {}
~formatter() {}
template <typename Type>
formatter & operator << (const Type & value)
{
stream_ << value;
return *this;
}
std::string str() const { return stream_.str(); }
operator std::string () const { return stream_.str(); }
enum ConvertToString
{
to_str
};
std::string operator >> (ConvertToString) { return stream_.str(); }
private:
std::stringstream stream_;
formatter(const formatter &);
formatter & operator = (formatter &);
};
#endif
================================================
FILE: jni/minicap-shared/Android.mk
================================================
LOCAL_PATH := $(abspath $(call my-dir))
include $(CLEAR_VARS)
LOCAL_MODULE := minicap-shared
LOCAL_MODULE_FILENAME := minicap
LOCAL_SRC_FILES := \
mock/Minicap.cpp \
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/aosp/include \
LOCAL_EXPORT_C_INCLUDES := \
$(LOCAL_PATH)/aosp/include \
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: jni/minicap-shared/README.md
================================================
# minicap-shared
This module provides the shared library used by minicap. Due to the use of private APIs, it must be built inside the AOSP source tree for each SDK level and architecture. We commit and ship prebuilt libraries inside the source tree for convenience purposes, as getting them compiled can be a major obstacle. The rest of this README assumes that you wish to compile the libraries by yourself, possibly due to trust issues and/or modifications.
## Requirements
There are several ways to set everything up and build the libraries, so we'll just cover the way we've done it. You may adjust the process however you want, but don't expect us to hold your hand if something goes wrong. Overall getting everything set up takes a considerable amount of time, with moderate skill requirements as well. It would be best if you follow the guide unless you're very confident you can do it.
### Operating system
Let's start by saying that **compiling under OS X will not work**. AOSP requires a case-sensitive file system. Furthermore, different branches of the AOSP source tree require different versions of Xcode and JDK, making the process nearly impossible. Try if you're crazy.
What we use is Linux, latest stable CoreOS to be precise. Our process relies on Docker containers to do the actual compile. The source code is copied via rsync to the Linux machine and then compiled. Any artifacts are transmitted back and added to our source tree so that normal people will not have to go through the whole process.
Using this setup, the minimum server-side machine requirements are:
* [docker](https://www.docker.com/)
* [SSH](http://www.openssh.com/)
* A user account with SSH public key authentication and sudo-less access to docker
* Depending on the SDK version, 25-60GB of disk space per built branch, and approximately 120GB for a full mirror. We'll get to the branches later. We recommend a 1TB SSD, possibly more if you wish to compile other things as well (i.e. not just minicap). With branch and mirror sizes ever growing, 512GB will soon not be enough, so don't bother if you want to keep everything on one disk.
You'll also need [rsync](https://rsync.samba.org/) and [SSH](http://www.openssh.com/) properly set up on your development machine.
### Docker images
Pull the required docker images on the build server as follows:
```bash
docker pull openstf/aosp:jdk6
docker pull openstf/aosp:jdk7
docker pull openstf/aosp:jdk8
```
These images will be used to both check out code as well as to compile it.
**A note about security:** containers created from these images currently run under the root user by default. If this is not acceptable to you, you will need to modify the `Makefile` and most of the commands below as you see fit.
### AOSP branches
Currently the following branches are required to build the libraries for all supported SDK levels and architectures:
| Branch | SDK | Docker image to build with |
|---------------------|-----|-------------------------------|
| android-2.3_r1 | 9 | openstf/aosp:jdk6 |
| android-2.3.3_r1 | 10 | openstf/aosp:jdk6 |
| android-4.0.1_r1 | 14 | openstf/aosp:jdk6 |
| android-4.0.3_r1 | 15 | openstf/aosp:jdk6 |
| android-4.1.1_r1 | 16 | openstf/aosp:jdk6 |
| android-4.2_r1 | 17 | openstf/aosp:jdk6 |
| android-4.3_r1 | 18 | openstf/aosp:jdk6 |
| android-4.4_r1 | 19 | openstf/aosp:jdk6 |
| android-5.0.1_r1 | 21 | openstf/aosp:jdk7 |
| android-5.1.0_r1 | 22 | openstf/aosp:jdk7 |
| android-6.0.0_r1 | 23 | openstf/aosp:jdk7 |
| android-7.0.0_r1 | 24 | openstf/aosp:jdk8 |
| android-7.1.0_r1 | 25 | openstf/aosp:jdk8 |
| android-8.0.0_r1 | 26 | openstf/aosp:jdk8 |
| android-8.1.0_r1 | 27 | openstf/aosp:jdk8 |
| android-9.0.0_r1 | 28 | openstf/aosp:jdk8 |
Furthermore, to make use of our provided Makefile, you should check out the branches to `/media/aosp` for maximum ease of use.
To check out the branches, you have two options. To reduce download time and avoid bandwidth caps (on the server side), it would be advisable to fetch a full local mirror and then checkout out the individual branches from there. What tends to happen, though, is that the mirror manifest does not get updated quickly enough for new branches, and may be missing a repository or two, making it practically impossible to check out the branch you want. Additionally, the mirror takes over 120GB of disk space in addition to the checkouts.
You can also skip the mirror and download each branch directly, but that will stress the AOSP server unnecessarily.
Our recommended solution is to use a mirror and use it for all the branches you can, and if a branch fails due to a missing repo, then checkout that branch directly from the server.
### AOSP authentication
AOSP has fairly recently introduced heavy bandwidth caps on their repos. You may have to authenticate yourself [as explained](https://source.android.com/source/downloading.html#using-authentication) by the download guide. Use the link in the guide to get your `.gitcookies` file.
You'll need to put the file on your build machine. It needs to have correct permissions (i.e. `0600`) and belong to the user checking out the code. Since our process uses the root user inside the containers, make sure to set the owner and group as well.
```bash
chown root:root .gitcookies
chmod 0600 .gitcookies
```
Use sudo where required.
We will later mount this file on our containers when checking out code. Note that if you still run into bandwidth caps you may just have to wait.
### Get familiar with the checkout/build wrapper script
There are bundled helper scripts inside the docker images. To see what commands you have available, run the following command.
```bash
docker run -ti --rm openstf/aosp:jdk7 /aosp.sh help
```
### Creating an AOSP mirror
Now that we're a bit more familiar with the helper script, let's start fetching our mirror.
```bash
docker run -ti --rm \
-v /media/aosp/mirror:/mirror \
-v $PWD/.gitcookies:/root/.gitcookies:ro \
openstf/aosp:jdk7 \
/aosp.sh create-mirror
```
This will take a LONG time, easily several hours. You may wish to leave it running overnight. If an error occurs (it will tell you), run the same command again and again until it finishes without errors.
When the command is done, you should have a copy of the latest mirror in `/media/aosp/mirror`. We will mount this mirror when checking out individual branches.
You should rerun the command whenever a new branch you're interested in gets added to AOSP to sync the mirrored repos.
### Check out branches (using mirror)
We had a table of the needed AOSP branches earlier. The docker image for each SDK level may be different, but it should not make much difference when simply checking out code, so you should be able to use one image to check out all branches. However, if you do run into problems, you may also wish to try using the proper JDK when checking out. Check out the branch table above to see which one to use for each branch.
For each branch in the table, run the following command:
```bash
docker run -ti --rm \
-v /media/aosp/mirror:/mirror \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $PWD/.gitcookies:/root/.gitcookies:ro \
openstf/aosp:jdk7 \
/aosp.sh checkout-branch android-5.1.0_r1
```
Note that the branch name is both in the mounted folder name and an option to the checkout-branch command. You have to replace both when checking out other branches.
Checking out a single branch should not take that long, perhaps 10-20 minutes per branch. Use a faster disk for better results.
Should an error occur, you can try running the command again. However, since we're using the mirror, errors shouldn't really happen. Like explained earlier, the mirror may be incomplete. If that's the case, you may have to try a direct download instead, explained next.
### Check out branches (using direct download)
For each branch you wish to download directly from the AOSP servers, run the following command:
```bash
docker run -ti --rm \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $PWD/.gitcookies:/root/.gitcookies:ro \
openstf/aosp:jdk7 \
/aosp.sh checkout-branch --no-mirror android-5.1.0_r1
```
Note that the branch name is both in the mounted folder name and an option to the checkout-branch command. You have to replace both when checking out other branches.
Should an error occur, you can try running the command again. Depending on your luck you may have to run the command several times, and if you hit a bandwidth cap, distribute your downloads over several days.
Checking out a single branch will easily take a few hours, but it is considerably faster than setting up a full mirror.
### Saving disk space (optional)
If you're sure that you'll never need to use the Android `repo` tool on a branch you've already checked out, you can save ~40-50% of disk space by deleting the `.repo` folder inside each branch. However, be very careful not to delete the mirror's `.repo`, since you'll probably want to update it at some point in the future.
### Building
There's a `Makefile` in the `aosp` folder containing a build command for each SDK level and architecture we're interested in. If you're developing on Linux directly you may be able to `make` the libraries directly.
More likely, though, you'll actually want to use the `build-remote.sh` script at the root of the minicap repo. It's not very pretty but it works. It transmits the source code to the target machine and uses one of the docker images to run `make` inside a docker container (which is done because CoreOS doesn't come with make).
First, you'll need to tell the script where you're building.
```bash
export BUILD_HOST=core@stf-build001
```
This will be passed to SSH and rsync, and is able to benefit from your SSH config (which you must set up yourself as you like).
Now, run the build script:
```bash
./build-remote.sh
```
The first compile will take a long, long time, since it needs to compile all dependencies on the first run, for each architecture.
### Rebuilding
Any recompile after the first time will benefit from the already build dependencies and will go a lot faster, although it will still take a considerable amount of time. The `Makefile` attemps to be intelligent about which branches need to be rebuilt, but touching the common code base or the `Android.mk` file will usually require a recompile on each SDK and architecture. That by itself is reasonably fast, but setting up the AOSP build takes the most time anyway. It may take 5-15 minutes in total to do an "empty run" for each branch and architecture.
Our advice? Be damn sure your code compiles before actually compiling :)
Alternatively, you may wish to temporarily remove other targets from the `Makefile` all target when working on bigger changes but focusing on a single branch.
In any case, congratulations, you're done!
### Tips
Here's a systemd unit to keep an external disk mounted in `/media/aosp`. It assumes you have a btrfs-formatted partition labeled `AOSP` on a disk somewhere, mine's on an external SSD. Example of how to create one:
```sh
mkfs.btrfs -L AOSP /dev/sdc1
```
And here's the corresponding unit:
```systemd
[Mount]
What=/dev/disk/by-label/AOSP
Where=/media/aosp
Type=btrfs
[Install]
WantedBy=multi-user.target
```
Now just run `systemctl enable media-aosp.mount && systemctl start media-aosp.mount`.
================================================
FILE: jni/minicap-shared/aosp/.rsync-filter
================================================
- .git
- libs
================================================
FILE: jni/minicap-shared/aosp/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := minicap
LOCAL_MODULE_TAGS := optional
ifneq ($(OVERRIDE_PLATFORM_SDK_VERSION),)
LOCAL_SRC_FILES += src/minicap_$(OVERRIDE_PLATFORM_SDK_VERSION).cpp
else ifeq ($(PLATFORM_SDK_VERSION),31)
LOCAL_SRC_FILES += src/minicap_31.cpp
else ifeq ($(PLATFORM_SDK_VERSION),30)
LOCAL_SRC_FILES += src/minicap_30.cpp
else ifeq ($(PLATFORM_SDK_VERSION),29)
LOCAL_SRC_FILES += src/minicap_29.cpp
else ifeq ($(PLATFORM_SDK_VERSION),28)
LOCAL_SRC_FILES += src/minicap_28.cpp
else ifeq ($(PLATFORM_SDK_VERSION),27)
LOCAL_SRC_FILES += src/minicap_27.cpp
else ifeq ($(PLATFORM_SDK_VERSION),26)
LOCAL_SRC_FILES += src/minicap_26.cpp
else ifeq ($(PLATFORM_SDK_VERSION),25)
LOCAL_SRC_FILES += src/minicap_25.cpp
else ifeq ($(PLATFORM_SDK_VERSION),24)
LOCAL_SRC_FILES += src/minicap_24.cpp
else ifeq ($(PLATFORM_SDK_VERSION),23)
LOCAL_SRC_FILES += src/minicap_23.cpp
else ifeq ($(PLATFORM_SDK_VERSION),22)
LOCAL_SRC_FILES += src/minicap_22.cpp
else ifeq ($(PLATFORM_SDK_VERSION),21)
LOCAL_SRC_FILES += src/minicap_21.cpp
else ifeq ($(PLATFORM_SDK_VERSION),19)
LOCAL_SRC_FILES += src/minicap_19.cpp
else ifeq ($(PLATFORM_SDK_VERSION),18)
LOCAL_SRC_FILES += src/minicap_18.cpp
else ifeq ($(PLATFORM_SDK_VERSION),17)
LOCAL_SRC_FILES += src/minicap_17.cpp
else ifeq ($(PLATFORM_SDK_VERSION),16)
LOCAL_SRC_FILES += src/minicap_16.cpp
else ifeq ($(PLATFORM_SDK_VERSION),15)
LOCAL_SRC_FILES += src/minicap_14.cpp
else ifeq ($(PLATFORM_SDK_VERSION),14)
LOCAL_SRC_FILES += src/minicap_14.cpp
else ifeq ($(PLATFORM_SDK_VERSION),10)
LOCAL_SRC_FILES += src/minicap_9.cpp
else ifeq ($(PLATFORM_SDK_VERSION),9)
LOCAL_SRC_FILES += src/minicap_9.cpp
endif
LOCAL_PRELINK_MODULE := false
LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils \
libbinder \
libui \
liblog \
ifeq ($(PLATFORM_SDK_VERSION),$(filter $(PLATFORM_SDK_VERSION),10 9))
LOCAL_SHARED_LIBRARIES += libsurfaceflinger_client
else
LOCAL_SHARED_LIBRARIES += libgui
endif
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)/include \
LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: jni/minicap-shared/aosp/Makefile
================================================
this_dir = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
SOURCES := Android.mk include/Minicap.hpp
all: \
libs/android-9/armeabi-v7a/minicap.so \
libs/android-10/armeabi-v7a/minicap.so \
libs/android-14/armeabi-v7a/minicap.so \
libs/android-14/x86/minicap.so \
libs/android-15/armeabi-v7a/minicap.so \
libs/android-15/x86/minicap.so \
libs/android-16/armeabi-v7a/minicap.so \
libs/android-16/x86/minicap.so \
libs/android-17/armeabi-v7a/minicap.so \
libs/android-17/x86/minicap.so \
libs/android-18/armeabi-v7a/minicap.so \
libs/android-18/x86/minicap.so \
libs/android-19/armeabi-v7a/minicap.so \
libs/android-19/x86/minicap.so \
libs/android-21/armeabi-v7a/minicap.so \
libs/android-21/arm64-v8a/minicap.so \
libs/android-21/x86/minicap.so \
libs/android-21/x86_64/minicap.so \
libs/android-22/armeabi-v7a/minicap.so \
libs/android-22/arm64-v8a/minicap.so \
libs/android-22/x86/minicap.so \
libs/android-22/x86_64/minicap.so \
libs/android-23/armeabi-v7a/minicap.so \
libs/android-23/arm64-v8a/minicap.so \
libs/android-23/x86/minicap.so \
libs/android-23/x86_64/minicap.so \
libs/android-24/armeabi-v7a/minicap.so \
libs/android-24/arm64-v8a/minicap.so \
libs/android-24/x86/minicap.so \
libs/android-24/x86_64/minicap.so \
libs/android-25/armeabi-v7a/minicap.so \
libs/android-25/arm64-v8a/minicap.so \
libs/android-25/x86/minicap.so \
libs/android-25/x86_64/minicap.so \
libs/android-26/armeabi-v7a/minicap.so \
libs/android-26/arm64-v8a/minicap.so \
libs/android-26/x86/minicap.so \
libs/android-26/x86_64/minicap.so \
libs/android-27/armeabi-v7a/minicap.so \
libs/android-27/arm64-v8a/minicap.so \
libs/android-27/x86/minicap.so \
libs/android-27/x86_64/minicap.so \
libs/android-28/armeabi-v7a/minicap.so \
libs/android-28/arm64-v8a/minicap.so \
libs/android-28/x86/minicap.so \
libs/android-28/x86_64/minicap.so \
libs/android-29/arm64-v8a/minicap.so \
libs/android-31/armeabi-v7a/minicap.so \
libs/android-31/arm64-v8a/minicap.so \
libs/android-31/x86/minicap.so \
libs/android-31/x86_64/minicap.so \
libs/android-9/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_9.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-2.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build generic-eng minicap
libs/android-10/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_9.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-2.3.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build generic-eng minicap
libs/android-14/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_14.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full-eng minicap
libs/android-14/x86/minicap.so: $(SOURCES) src/minicap_14.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full_x86-eng minicap
libs/android-15/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_14.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.0.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full-eng minicap
libs/android-15/x86/minicap.so: $(SOURCES) src/minicap_14.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.0.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full_x86-eng minicap
libs/android-16/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_16.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.1.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full-eng minicap
libs/android-16/x86/minicap.so: $(SOURCES) src/minicap_16.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.1.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full_x86-eng minicap
libs/android-17/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_17.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.2_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full-eng minicap
libs/android-17/x86/minicap.so: $(SOURCES) src/minicap_17.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.2_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build full_x86-eng minicap
libs/android-18/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_18.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build aosp_arm-eng minicap
libs/android-18/x86/minicap.so: $(SOURCES) src/minicap_18.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.3_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build aosp_x86-eng minicap
libs/android-19/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_19.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.4_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build aosp_arm-eng minicap
libs/android-19/x86/minicap.so: $(SOURCES) src/minicap_19.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-4.4_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk6 /aosp.sh build aosp_x86-eng minicap
libs/android-21/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm-eng minicap
libs/android-21/arm64-v8a/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm64-eng minicap
libs/android-21/x86/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86-eng minicap
libs/android-21/x86_64/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86_64-eng minicap
libs/android-21/mips/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips-eng minicap
libs/android-21/mips64/minicap.so: $(SOURCES) src/minicap_21.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.0.1_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips64-eng minicap
libs/android-22/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm-eng minicap
libs/android-22/arm64-v8a/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm64-eng minicap
libs/android-22/x86/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86-eng minicap
libs/android-22/x86_64/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86_64-eng minicap
libs/android-22/mips/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips-eng minicap
libs/android-22/mips64/minicap.so: $(SOURCES) src/minicap_22.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-5.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips64-eng minicap
libs/android-23/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm-eng minicap
libs/android-23/arm64-v8a/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_arm64-eng minicap
libs/android-23/x86/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86-eng minicap
libs/android-23/x86_64/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_x86_64-eng minicap
libs/android-23/mips/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips-eng minicap
libs/android-23/mips64/minicap.so: $(SOURCES) src/minicap_23.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-6.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk7 /aosp.sh build aosp_mips64-eng minicap
libs/android-24/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-24/arm64-v8a/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-24/x86/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-24/x86_64/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
libs/android-24/mips/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips-eng minicap
libs/android-24/mips64/minicap.so: $(SOURCES) src/minicap_24.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips64-eng minicap
libs/android-25/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-25/arm64-v8a/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-25/x86/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-25/x86_64/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
libs/android-25/mips/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips-eng minicap
libs/android-25/mips64/minicap.so: $(SOURCES) src/minicap_25.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-7.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips64-eng minicap
libs/android-26/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-26/arm64-v8a/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-26/x86/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-26/x86_64/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
libs/android-26/mips/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips-eng minicap
libs/android-26/mips64/minicap.so: $(SOURCES) src/minicap_26.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips64-eng minicap
libs/android-27/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-27/arm64-v8a/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-27/x86/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-27/x86_64/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
libs/android-27/mips/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips-eng minicap
libs/android-27/mips64/minicap.so: $(SOURCES) src/minicap_27.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-8.1.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips64-eng minicap
libs/android-28/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-28/arm64-v8a/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-28/x86/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-28/x86_64/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
libs/android-28/mips/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips-eng minicap
libs/android-28/mips64/minicap.so: $(SOURCES) src/minicap_28.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-9.0.0_r1:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_mips64-eng minicap
libs/android-29/arm64-v8a/minicap.so: $(SOURCES) src/minicap_29.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-10.0.0_r2:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-31/armeabi-v7a/minicap.so: $(SOURCES) src/minicap_31.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-12.0.0_r2:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm-eng minicap
libs/android-31/arm64-v8a/minicap.so: $(SOURCES) src/minicap_31.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-12.0.0_r2:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_arm64-eng minicap
libs/android-31/x86/minicap.so: $(SOURCES) src/minicap_31.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-12.0.0_r2:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86-eng minicap
libs/android-31/x86_64/minicap.so: $(SOURCES) src/minicap_31.cpp
mkdir -p $(@D)
docker run --rm \
-a stdout -a stderr \
-v /media/aosp/android-12.0.0_r2:/aosp \
-v $(this_dir):/app \
-v $(this_dir)$(@D):/artifacts \
openstf/aosp:jdk8 /aosp.sh build aosp_x86_64-eng minicap
================================================
FILE: jni/minicap-shared/aosp/include/Minicap.hpp
================================================
#ifndef MINICAP_HPP
#define MINICAP_HPP
#include <cstdint>
class Minicap {
public:
enum CaptureMethod {
METHOD_FRAMEBUFFER = 1,
METHOD_SCREENSHOT = 2,
METHOD_VIRTUAL_DISPLAY = 3,
};
enum Format {
FORMAT_NONE = 0x01,
FORMAT_CUSTOM = 0x02,
FORMAT_TRANSLUCENT = 0x03,
FORMAT_TRANSPARENT = 0x04,
FORMAT_OPAQUE = 0x05,
FORMAT_RGBA_8888 = 0x06,
FORMAT_RGBX_8888 = 0x07,
FORMAT_RGB_888 = 0x08,
FORMAT_RGB_565 = 0x09,
FORMAT_BGRA_8888 = 0x0a,
FORMAT_RGBA_5551 = 0x0b,
FORMAT_RGBA_4444 = 0x0c,
FORMAT_UNKNOWN = 0x00,
};
enum Orientation {
ORIENTATION_0 = 0,
ORIENTATION_90 = 1,
ORIENTATION_180 = 2,
ORIENTATION_270 = 3,
};
struct DisplayInfo {
uint32_t width;
uint32_t height;
float fps;
float density;
float xdpi;
float ydpi;
float size;
uint8_t orientation;
bool secure;
};
struct Frame {
void const* data;
Format format;
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t bpp;
size_t size;
};
struct FrameAvailableListener {
virtual
~FrameAvailableListener() {}
virtual void
onFrameAvailable() = 0;
};
Minicap() {}
virtual
~Minicap() {}
// Applies changes made by setDesiredInfo() and setRealInfo(). Must be
// called before attempting to wait or consume frames.
virtual int
applyConfigChanges() = 0;
// Consumes a frame. Must be called after waitForFrame().
virtual int
consumePendingFrame(Frame* frame) = 0;
// Peek behind the scenes to see which capture method is actually
// being used.
virtual CaptureMethod
getCaptureMethod() = 0;
// Get display ID.
virtual int32_t
getDisplayId() = 0;
// Release all resources.
virtual void
release() = 0;
// Releases a consumed frame so that it can be reused by Android again.
// Must be called before consumePendingFrame() is called again.
virtual void
releaseConsumedFrame(Frame* frame) = 0;
// Set desired information about the display. Currently, only the
// following properties are actually used: width, height and orientation.
// After the configuration has been applied, new frames should satisfy
// the requirements.
virtual int
setDesiredInfo(const DisplayInfo& info) = 0;
// Sets the frame available listener.
virtual void
setFrameAvailableListener(FrameAvailableListener* listener) = 0;
// Set the display's real information. This cannot be accessed automatically
// due to manufacturers (mainly Samsung) having customized
// android::DisplayInfo. The information has to be gathered somehow and then
// passed on here. Currently only the following properties are actually
// used: width and height.
virtual int
setRealInfo(const DisplayInfo& info) = 0;
};
// Attempt to get information about the given display. This may segfault
// on some devices due to manufacturer (mainly Samsung) customizations.
int
minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info);
// Creates a new Minicap instance for the current platform.
Minicap*
minicap_create(int32_t displayId);
// Frees a Minicap instance. Don't call delete yourself as it won't have
// access to the platform-specific modifications.
void
minicap_free(Minicap* mc);
// Starts an Android thread pool. Must be called before doing anything else.
void
minicap_start_thread_pool();
#endif
================================================
FILE: jni/minicap-shared/aosp/include/mcdebug.h
================================================
#ifndef __minicap_dbg_h__
#define __minicap_dbg_h__
// These macros were originally from
// http://c.learncodethehardway.org/book/ex20.html
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef NDEBUG
#define MCDEBUG(M, ...)
#else
#define MCDEBUG(M, ...) fprintf(stderr, "DEBUG: %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif
#define MCCLEAN_ERRNO() (errno == 0 ? "None" : strerror(errno))
#define MCERROR(M, ...) fprintf(stderr, "ERROR: (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, MCCLEAN_ERRNO(), ##__VA_ARGS__)
#define MCWARN(M, ...) fprintf(stderr, "WARN: (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, MCCLEAN_ERRNO(), ##__VA_ARGS__)
#define MCINFO(M, ...) fprintf(stderr, "INFO: (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define MCCHECK(A, M, ...) if(!(A)) { MCERROR(M, ##__VA_ARGS__); errno=0; goto error; }
#define MCSENTINEL(M, ...) { MCERROR(M, ##__VA_ARGS__); errno=0; goto error; }
#define MCCHECK_MEM(A) check((A), "Out of memory.")
#define MCCHECK_DEBUG(A, M, ...) if(!(A)) { MCDEBUG(M, ##__VA_ARGS__); errno=0; goto error; }
#endif
================================================
FILE: jni/minicap-shared/aosp/src/minicap_14.cpp
================================================
#include "Minicap.hpp"
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>
#include <surfaceflinger/ISurfaceComposer.h>
#include <surfaceflinger/SurfaceComposerClient.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
#include "mcdebug.h"
static const char*
error_name(int32_t err) {
switch (err) {
case android::NO_ERROR: // also android::OK
return "NO_ERROR";
case android::UNKNOWN_ERROR:
return "UNKNOWN_ERROR";
case android::NO_MEMORY:
return "NO_MEMORY";
case android::INVALID_OPERATION:
return "INVALID_OPERATION";
case android::BAD_VALUE:
return "BAD_VALUE";
case android::BAD_TYPE:
return "BAD_TYPE";
case android::NAME_NOT_FOUND:
return "NAME_NOT_FOUND";
case android::PERMISSION_DENIED:
return "PERMISSION_DENIED";
case android::NO_INIT:
return "NO_INIT";
case android::ALREADY_EXISTS:
return "ALREADY_EXISTS";
case android::DEAD_OBJECT: // also android::JPARKS_BROKE_IT
return "DEAD_OBJECT";
case android::FAILED_TRANSACTION:
return "FAILED_TRANSACTION";
case android::BAD_INDEX:
return "BAD_INDEX";
case android::NOT_ENOUGH_DATA:
return "NOT_ENOUGH_DATA";
case android::WOULD_BLOCK:
return "WOULD_BLOCK";
case android::TIMED_OUT:
return "TIMED_OUT";
case android::UNKNOWN_TRANSACTION:
return "UNKNOWN_TRANSACTION";
case android::FDS_NOT_ALLOWED:
return "FDS_NOT_ALLOWED";
default:
return "UNMAPPED_ERROR";
}
}
class MinicapImpl: public Minicap {
public:
MinicapImpl(int32_t displayId)
: mDisplayId(displayId),
mComposer(android::ComposerService::getComposerService()),
mDesiredWidth(0),
mDesiredHeight(0) {
}
virtual
~MinicapImpl() {
release();
}
virtual int
applyConfigChanges() {
mUserFrameAvailableListener->onFrameAvailable();
return 0;
}
virtual int
consumePendingFrame(Minicap::Frame* frame) {
uint32_t width, height;
android::PixelFormat format;
android::status_t err;
mHeap = NULL;
err = mComposer->captureScreen(mDisplayId, &mHeap,
&width, &height, &format, mDesiredWidth, mDesiredHeight, 0, -1UL);
if (err != android::NO_ERROR) {
MCERROR("ComposerService::captureScreen() failed %s", error_name(err));
return err;
}
frame->data = mHeap->getBase();
frame->width = width;
frame->height = height;
frame->format = convertFormat(format);
frame->stride = width;
frame->bpp = android::bytesPerPixel(format);
frame->size = mHeap->getSize();
return 0;
}
virtual Minicap::CaptureMethod
getCaptureMethod() {
return METHOD_SCREENSHOT;
}
virtual int32_t
getDisplayId() {
return mDisplayId;
}
virtual void
release() {
mHeap = NULL;
}
virtual void
releaseConsumedFrame(Minicap::Frame* /* frame */) {
mHeap = NULL;
return mUserFrameAvailableListener->onFrameAvailable();
}
virtual int
setDesiredInfo(const Minicap::DisplayInfo& info) {
mDesiredWidth = info.width;
mDesiredHeight = info.height;
return 0;
}
virtual void
setFrameAvailableListener(Minicap::FrameAvailableListener* listener) {
mUserFrameAvailableListener = listener;
}
virtual int
setRealInfo(const Minicap::DisplayInfo& info) {
return 0;
}
private:
int32_t mDisplayId;
android::sp<android::ISurfaceComposer> mComposer;
android::sp<android::IMemoryHeap> mHeap;
uint32_t mDesiredWidth;
uint32_t mDesiredHeight;
Minicap::FrameAvailableListener* mUserFrameAvailableListener;
static Minicap::Format
convertFormat(android::PixelFormat format) {
switch (format) {
case android::PIXEL_FORMAT_NONE:
return FORMAT_NONE;
case android::PIXEL_FORMAT_CUSTOM:
return FORMAT_CUSTOM;
case android::PIXEL_FORMAT_TRANSLUCENT:
return FORMAT_TRANSLUCENT;
case android::PIXEL_FORMAT_TRANSPARENT:
return FORMAT_TRANSPARENT;
case android::PIXEL_FORMAT_OPAQUE:
return FORMAT_OPAQUE;
case android::PIXEL_FORMAT_RGBA_8888:
return FORMAT_RGBA_8888;
case android::PIXEL_FORMAT_RGBX_8888:
return FORMAT_RGBX_8888;
case android::PIXEL_FORMAT_RGB_888:
return FORMAT_RGB_888;
case android::PIXEL_FORMAT_RGB_565:
return FORMAT_RGB_565;
case android::PIXEL_FORMAT_BGRA_8888:
return FORMAT_BGRA_8888;
case android::PIXEL_FORMAT_RGBA_5551:
return FORMAT_RGBA_5551;
case android::PIXEL_FORMAT_RGBA_4444:
return FORMAT_RGBA_4444;
default:
return FORMAT_UNKNOWN;
}
}
};
int
minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info) {
android::DisplayInfo dinfo;
android::status_t err = android::SurfaceComposerClient::getDisplayInfo(displayId, &dinfo);
if (err != android::NO_ERROR) {
MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err);
return err;
}
info->width = dinfo.w;
info->height = dinfo.h;
info->orientation = dinfo.orientation;
info->fps = dinfo.fps;
info->density = dinfo.density;
info->xdpi = dinfo.xdpi;
info->ydpi = dinfo.ydpi;
info->secure = false;
info->size = sqrt(pow(dinfo.w / dinfo.xdpi, 2) + pow(dinfo.h / dinfo.ydpi, 2));
return 0;
}
Minicap*
minicap_create(int32_t displayId) {
return new MinicapImpl(displayId);
}
void
minicap_free(Minicap* mc) {
delete mc;
}
void
minicap_start_thread_pool() {
android::ProcessState::self()->startThreadPool();
}
================================================
FILE: jni/minicap-shared/aosp/src/minicap_16.cpp
================================================
#include "Minicap.hpp"
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
#include "mcdebug.h"
static const char*
error_name(int32_t err) {
switch (err) {
case android::NO_ERROR: // also android::OK
return "NO_ERROR";
case android::UNKNOWN_ERROR:
return "UNKNOWN_ERROR";
case android::NO_MEMORY:
return "NO_MEMORY";
case android::INVALID_OPERATION:
return "INVALID_OPERATION";
case android::BAD_VALUE:
return "BAD_VALUE";
case android::BAD_TYPE:
return "BAD_TYPE";
case android::NAME_NOT_FOUND:
return "NAME_NOT_FOUND";
case android::PERMISSION_DENIED:
return "PERMISSION_DENIED";
case android::NO_INIT:
return "NO_INIT";
case android::ALREADY_EXISTS:
return "ALREADY_EXISTS";
case android::DEAD_OBJECT: // also android::JPARKS_BROKE_IT
return "DEAD_OBJECT";
case android::FAILED_TRANSACTION:
return "FAILED_TRANSACTION";
case android::BAD_INDEX:
return "BAD_INDEX";
case android::NOT_ENOUGH_DATA:
return "NOT_ENOUGH_DATA";
case android::WOULD_BLOCK:
return "WOULD_BLOCK";
case android::TIMED_OUT:
return "TIMED_OUT";
case android::UNKNOWN_TRANSACTION:
return "UNKNOWN_TRANSACTION";
case android::FDS_NOT_ALLOWED:
return "FDS_NOT_ALLOWED";
default:
return "UNMAPPED_ERROR";
}
}
class MinicapImpl: public Minicap {
public:
MinicapImpl(int32_t displayId)
: mDisplayId(displayId),
mComposer(android::ComposerService::getComposerService()),
mDesiredWidth(0),
mDesiredHeight(0) {
}
virtual
~MinicapImpl() {
release();
}
virtual int
applyConfigChanges() {
mUserFrameAvailableListener->onFrameAvailable();
return 0;
}
virtual int
consumePendingFrame(Minicap::Frame* frame) {
uint32_t width, height;
android::PixelFormat format;
android::status_t err;
mHeap = NULL;
err = mComposer->captureScreen(mDisplayId, &mHeap,
&width, &height, &format, mDesiredWidth, mDesiredHeight, 0, -1UL);
if (err != android::NO_ERROR) {
MCERROR("ComposerService::captureScreen() failed %s", error_name(err));
return err;
}
frame->data = mHeap->getBase();
frame->width = width;
frame->height = height;
frame->format = convertFormat(format);
frame->stride = width;
frame->bpp = android::bytesPerPixel(format);
frame->size = mHeap->getSize();
return 0;
}
virtual Minicap::CaptureMethod
getCaptureMethod() {
return METHOD_SCREENSHOT;
}
virtual int32_t
getDisplayId() {
return mDisplayId;
}
virtual void
release() {
mHeap = NULL;
}
virtual void
releaseConsumedFrame(Minicap::Frame* /* frame */) {
mHeap = NULL;
return mUserFrameAvailableListener->onFrameAvailable();
}
virtual int
setDesiredInfo(const Minicap::DisplayInfo& info) {
mDesiredWidth = info.width;
mDesiredHeight = info.height;
return 0;
}
virtual void
setFrameAvailableListener(Minicap::FrameAvailableListener* listener) {
mUserFrameAvailableListener = listener;
}
virtual int
setRealInfo(const Minicap::DisplayInfo& info) {
return 0;
}
private:
int32_t mDisplayId;
android::sp<android::ISurfaceComposer> mComposer;
android::sp<android::IMemoryHeap> mHeap;
uint32_t mDesiredWidth;
uint32_t mDesiredHeight;
Minicap::FrameAvailableListener* mUserFrameAvailableListener;
static Minicap::Format
convertFormat(android::PixelFormat format) {
switch (format) {
case android::PIXEL_FORMAT_NONE:
return FORMAT_NONE;
case android::PIXEL_FORMAT_CUSTOM:
return FORMAT_CUSTOM;
case android::PIXEL_FORMAT_TRANSLUCENT:
return FORMAT_TRANSLUCENT;
case android::PIXEL_FORMAT_TRANSPARENT:
return FORMAT_TRANSPARENT;
case android::PIXEL_FORMAT_OPAQUE:
return FORMAT_OPAQUE;
case android::PIXEL_FORMAT_RGBA_8888:
return FORMAT_RGBA_8888;
case android::PIXEL_FORMAT_RGBX_8888:
return FORMAT_RGBX_8888;
case android::PIXEL_FORMAT_RGB_888:
return FORMAT_RGB_888;
case android::PIXEL_FORMAT_RGB_565:
return FORMAT_RGB_565;
case android::PIXEL_FORMAT_BGRA_8888:
return FORMAT_BGRA_8888;
case android::PIXEL_FORMAT_RGBA_5551:
return FORMAT_RGBA_5551;
case android::PIXEL_FORMAT_RGBA_4444:
return FORMAT_RGBA_4444;
default:
return FORMAT_UNKNOWN;
}
}
};
int
minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info) {
android::DisplayInfo dinfo;
android::status_t err = android::SurfaceComposerClient::getDisplayInfo(displayId, &dinfo);
if (err != android::NO_ERROR) {
MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err);
return err;
}
info->width = dinfo.w;
info->height = dinfo.h;
info->orientation = dinfo.orientation;
info->fps = dinfo.fps;
info->density = dinfo.density;
info->xdpi = dinfo.xdpi;
info->ydpi = dinfo.ydpi;
info->secure = false;
info->size = sqrt(pow(dinfo.w / dinfo.xdpi, 2) + pow(dinfo.h / dinfo.ydpi, 2));
return 0;
}
Minicap*
minicap_create(int32_t displayId) {
return new MinicapImpl(displayId);
}
void
minicap_free(Minicap* mc) {
delete mc;
}
void
minicap_start_thread_pool() {
android::ProcessState::self()->startThreadPool();
}
================================================
FILE: jni/minicap-shared/aosp/src/minicap_17.cpp
================================================
#include "Minicap.hpp"
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>
#include <gui/BufferQueue.h>
#include <gui/CpuConsumer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
#include "mcdebug.h"
static const char*
error_name(int32_t err) {
switch (err) {
case android::NO_ERROR: // also android::OK
return "NO_ERROR";
case android::UNKNOWN_ERROR:
return "UNKNOWN_ERROR";
case android::NO_MEMORY:
return "NO_MEMORY";
case android::INVALID_OPERATION:
return "INVALID_OPERATION";
case android::BAD_VALUE:
return "BAD_VALUE";
case android::BAD_TYPE:
return "BAD_TYPE";
case android::NAME_NOT_FOUND:
return "NAME_NOT_FOUND";
case android::PERMISSION_DENIED:
return "PERMISSION_DENIED";
case android::NO_INIT:
return "NO_INIT";
case android::ALREADY_EXISTS:
return "ALREADY_EXISTS";
case android::DEAD_OBJECT: // also android::JPARKS_BROKE_IT
return "DEAD_OBJECT";
case android::FAILED_TRANSACTION:
return "FAILED_TRANSACTION";
case android::BAD_INDEX:
return "BAD_INDEX";
case android::NOT_ENOUGH_DATA:
return "NOT_ENOUGH_DATA";
case android::WOULD_BLOCK:
return "WOULD_BLOCK";
case android::TIMED_OUT:
return "TIMED_OUT";
case android::UNKNOWN_TRANSACTION:
return "UNKNOWN_TRANSACTION";
case android::FDS_NOT_ALLOWED:
return "FDS_NOT_ALLOWED";
default:
return "UNMAPPED_ERROR";
}
}
class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
public:
FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(listener) {
}
virtual void
onFrameAvailable() {
mUserListener->onFrameAvailable();
}
private:
Minicap::FrameAvailableListener* mUserListener;
};
class MinicapImpl: public Minicap
{
public:
MinicapImpl(int32_t displayId)
: mDisplayId(displayId),
mRealWidth(0),
mRealHeight(0),
mDesiredWidth(0),
mDesiredHeight(0),
mDesiredOrientation(0),
mHaveBuffer(false),
mHaveRunningDisplay(false) {
}
virtual
~MinicapImpl() {
release();
}
virtual int
applyConfigChanges() {
if (mHaveRunningDisplay) {
destroyVirtualDisplay();
}
return createVirtualDisplay();
}
virtual int
consumePendingFrame(Minicap::Frame* frame) {
android::status_t err;
if ((err = mConsumer->lockNextBuffer(&mBuffer)) != android::NO_ERROR) {
if (err == -EINTR) {
return err;
}
else {
MCERROR("Unable to lock next buffer %s (%d)", error_name(err), err);
return err;
}
}
frame->data = mBuffer.data;
frame->format = convertFormat(mBuffer.format);
frame->width = mBuffer.width;
frame->height = mBuffer.height;
frame->stride = mBuffer.stride;
frame->bpp = android::bytesPerPixel(mBuffer.format);
frame->size = mBuffer.stride * mBuffer.height * frame->bpp;
mHaveBuffer = true;
return 0;
}
virtual Minicap::CaptureMethod
getCaptureMethod() {
return METHOD_VIRTUAL_DISPLAY;
}
virtual int32_t
getDisplayId() {
return mDisplayId;
}
virtual void
release() {
destroyVirtualDisplay();
}
virtual void
releaseConsumedFrame(Minicap::Frame* /* frame */) {
if (mHaveBuffer) {
mConsumer->unlockBuffer(mBuffer);
mHaveBuffer = false;
}
}
virtual int
setDesiredInfo(const Minicap::DisplayInfo& info) {
mDesiredWidth = info.width;
mDesiredHeight = info.height;
mDesiredOrientation = info.orientation;
return 0;
}
virtual void
setFrameAvailableListener(Minicap::FrameAvailableListener* listener) {
mUserFrameAvailableListener = listener;
}
virtual int
setRealInfo(const Minicap::DisplayInfo& info) {
mRealWidth = info.width;
mRealHeight = info.height;
return 0;
}
private:
int32_t mDisplayId;
uint32_t mRealWidth;
uint32_t mRealHeight;
uint32_t mDesiredWidth;
uint32_t mDesiredHeight;
uint8_t mDesiredOrientation;
android::sp<android::BufferQueue> mBufferQueue;
android::sp<android::CpuConsumer> mConsumer;
android::sp<android::IBinder> mVirtualDisplay;
android::sp<FrameProxy> mFrameProxy;
Minicap::FrameAvailableListener* mUserFrameAvailableListener;
bool mHaveBuffer;
bool mHaveRunningDisplay;
android::CpuConsumer::LockedBuffer mBuffer;
int
createVirtualDisplay() {
uint32_t sourceWidth, sourceHeight;
uint32_t targetWidth, targetHeight;
android::status_t err;
switch (mDesiredOrientation) {
case Minicap::ORIENTATION_90:
sourceWidth = mRealHeight;
sourceHeight = mRealWidth;
targetWidth = mDesiredHeight;
targetHeight = mDesiredWidth;
break;
case Minicap::ORIENTATION_270:
sourceWidth = mRealHeight;
sourceHeight = mRealWidth;
targetWidth = mDesiredHeight;
targetHeight = mDesiredWidth;
break;
case Minicap::ORIENTATION_180:
sourceWidth = mRealWidth;
sourceHeight = mRealHeight;
targetWidth = mDesiredWidth;
targetHeight = mDesiredHeight;
break;
case Minicap::ORIENTATION_0:
default:
sourceWidth = mRealWidth;
sourceHeight = mRealHeight;
targetWidth = mDesiredWidth;
targetHeight = mDesiredHeight;
break;
}
// Set up virtual display size.
android::Rect layerStackRect(sourceWidth, sourceHeight);
android::Rect visibleRect(targetWidth, targetHeight);
// Create a Surface for the virtual display to write to.
MCINFO("Creating SurfaceComposerClient");
android::sp<android::SurfaceComposerClient> sc = new android::SurfaceComposerClient();
MCINFO("Performing SurfaceComposerClient init check");
if ((err = sc->initCheck()) != android::NO_ERROR) {
MCERROR("Unable to initialize SurfaceComposerClient");
return err;
}
// Create virtual display.
MCINFO("Creating virtual display");
mVirtualDisplay = android::SurfaceComposerClient::createDisplay(
/* const String8& displayName */ android::String8("minicap"),
/* bool secure */ true
);
MCINFO("Creating CPU consumer");
// Some devices have a modified, larger CpuConsumer. Try to account
// for that by increasing the size.
mConsumer = new(operator new(sizeof(android::CpuConsumer) + 100)) android::CpuConsumer(3);
mConsumer->setName(android::String8("minicap"));
MCINFO("Creating buffer queue");
mBufferQueue = mConsumer->getBufferQueue();
mBufferQueue->setSynchronousMode(false);
mBufferQueue->setDefaultBufferSize(targetWidth, targetHeight);
mBufferQueue->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888);
MCINFO("Creating frame waiter");
mFrameProxy = new FrameProxy(mUserFrameAvailableListener);
mConsumer->setFrameAvailableListener(mFrameProxy);
MCINFO("Publishing virtual display");
android::SurfaceComposerClient::openGlobalTransaction();
android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferQueue);
android::SurfaceComposerClient::setDisplayProjection(mVirtualDisplay,
android::DISPLAY_ORIENTATION_0, layerStackRect, visibleRect);
android::SurfaceComposerClient::setDisplayLayerStack(mVirtualDisplay, 0); // default stack
android::SurfaceComposerClient::closeGlobalTransaction();
mHaveRunningDisplay = true;
return 0;
}
void
destroyVirtualDisplay() {
MCINFO("Destroying virtual display");
if (mHaveBuffer) {
mConsumer->unlockBuffer(mBuffer);
mHaveBuffer = false;
}
mBufferQueue = NULL;
mConsumer = NULL;
mFrameProxy = NULL;
mVirtualDisplay = NULL;
mHaveRunningDisplay = false;
}
static Minicap::Format
convertFormat(android::PixelFormat format) {
switch (format) {
case android::PIXEL_FORMAT_NONE:
return FORMAT_NONE;
case android::PIXEL_FORMAT_CUSTOM:
return FORMAT_CUSTOM;
case android::PIXEL_FORMAT_TRANSLUCENT:
return FORMAT_TRANSLUCENT;
case android::PIXEL_FORMAT_TRANSPARENT:
return FORMAT_TRANSPARENT;
case android::PIXEL_FORMAT_OPAQUE:
return FORMAT_OPAQUE;
case android::PIXEL_FORMAT_RGBA_8888:
return FORMAT_RGBA_8888;
case android::PIXEL_FORMAT_RGBX_8888:
return FORMAT_RGBX_8888;
case android::PIXEL_FORMAT_RGB_888:
return FORMAT_RGB_888;
case android::PIXEL_FORMAT_RGB_565:
return FORMAT_RGB_565;
case android::PIXEL_FORMAT_BGRA_8888:
return FORMAT_BGRA_8888;
case android::PIXEL_FORMAT_RGBA_5551:
return FORMAT_RGBA_5551;
case android::PIXEL_FORMAT_RGBA_4444:
return FORMAT_RGBA_4444;
default:
return FORMAT_UNKNOWN;
}
}
};
int
minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info) {
android::sp<android::IBinder> dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId);
android::DisplayInfo dinfo;
android::status_t err = android::SurfaceComposerClient::getDisplayInfo(dpy, &dinfo);
if (err != android::NO_ERROR) {
MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err);
return err;
}
info->width = dinfo.w;
info->height = dinfo.h;
info->orientation = dinfo.orientation;
info->fps = dinfo.fps;
info->density = dinfo.density;
info->xdpi = dinfo.xdpi;
info->ydpi = dinfo.ydpi;
info->secure = dinfo.secure;
info->size = sqrt(pow(dinfo.w / dinfo.xdpi, 2) + pow(dinfo.h / dinfo.ydpi, 2));
return 0;
}
Minicap*
minicap_create(int32_t displayId) {
return new MinicapImpl(displayId);
}
void
minicap_free(Minicap* mc) {
delete mc;
}
void
minicap_start_thread_pool() {
android::ProcessState::self()->startThreadPool();
}
================================================
FILE: jni/minicap-shared/aosp/src/minicap_18.cpp
================================================
#include "Minicap.hpp"
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>
#include <gui/BufferQueue.h>
#include <gui/CpuConsumer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
#include "mcdebug.h"
static const char*
error_name(int32_t err) {
switch (err) {
case android::NO_ERROR: // also android::OK
return "NO_ERROR";
case android::UNKNOWN_ERROR:
return "UNKNOWN_ERROR";
case android::NO_MEMORY:
return "NO_MEMORY";
case android::INVALID_OPERATION:
return "INVALID_OPERATION";
case android::BAD_VALUE:
return "BAD_VALUE";
case android::BAD_TYPE:
return "BAD_TYPE";
case android::NAME_NOT_FOUND:
return "NAME_NOT_FOUND";
case android::PERMISSION_DENIED:
return "PERMISSION_DENIED";
case android::NO_INIT:
return "NO_INIT";
case android::ALREADY_EXISTS:
return "ALREADY_EXISTS";
case android::DEAD_OBJECT: // also android::JPARKS_BROKE_IT
return "DEAD_OBJECT";
case android::FAILED_TRANSACTION:
return "FAILED_TRANSACTION";
case android::BAD_INDEX:
return "BAD_INDEX";
case android::NOT_ENOUGH_DATA:
return "NOT_ENOUGH_DATA";
case android::WOULD_BLOCK:
return "WOULD_BLOCK";
case android::TIMED_OUT:
return "TIMED_OUT";
case android::UNKNOWN_TRANSACTION:
return "UNKNOWN_TRANSACTION";
case android::FDS_NOT_ALLOWED:
return "FDS_NOT_ALLOWED";
default:
return "UNMAPPED_ERROR";
}
}
class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
public:
FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(listener) {
}
virtual void
onFrameAvailable() {
mUserListener->onFrameAvailable();
}
private:
Minicap::FrameAvailableListener* mUserListener;
};
class MinicapImpl: public Minicap
{
public:
MinicapImpl(int32_t displayId)
: mDisplayId(displayId),
mRealWidth(0),
mRealHeight(0),
mDesiredWidth(0),
mDesiredHeight(0),
mDesiredOrientation(0),
mHaveBuffer(false),
mHaveRunningDisplay(false) {
}
virtual
~MinicapImpl() {
release();
}
virtual int
applyConfigChanges() {
if (mHaveRunningDisplay) {
destroyVirtualDisplay();
}
return createVirtualDisplay();
}
virtual int
consumePendingFrame(Minicap::Frame* frame) {
android::status_t err;
if ((err = mConsumer->lockNextBuffer(&mBuffer)) != android::NO_ERROR) {
if (err == -EINTR) {
return err;
}
else {
MCERROR("Unable to lock next buffer %s (%d)", error_name(err), err);
return err;
}
}
frame->data = mBuffer.data;
frame->format = convertFormat(mBuffer.format);
frame->width = mBuffer.width;
frame->height = mBuffer.height;
frame->stride = mBuffer.stride;
frame->bpp = android::bytesPerPixel(mBuffer.format);
frame->size = mBuffer.stride * mBuffer.height * frame->bpp;
mHaveBuffer = true;
return 0;
}
virtual Minicap::CaptureMethod
getCaptureMethod() {
return METHOD_VIRTUAL_DISPLAY;
}
virtual int32_t
getDisplayId() {
return mDisplayId;
}
virtual void
release() {
destroyVirtualDisplay();
}
virtual void
releaseConsumedFrame(Minicap::Frame* /* frame */) {
if (mHaveBuffer) {
mConsumer->unlockBuffer(mBuffer);
mHaveBuffer = false;
}
}
virtual int
setDesiredInfo(const Minicap::DisplayInfo& info) {
mDesiredWidth = info.width;
mDesiredHeight = info.height;
mDesiredOrientation = info.orientation;
return 0;
}
virtual void
setFrameAvailableListener(Minicap::FrameAvailableListener* listener) {
mUserFrameAvailableListener = listener;
}
virtual int
setRealInfo(const Minicap::DisplayInfo& info) {
mRealWidth = info.width;
mRealHeight = info.height;
return 0;
}
private:
int32_t mDisplayId;
uint32_t mRealWidth;
uint32_t mRealHeight;
uint32_t mDesiredWidth;
uint32_t mDesiredHeight;
uint8_t mDesiredOrientation;
android::sp<android::BufferQueue> mBufferQueue;
android::sp<android::CpuConsumer> mConsumer;
android::sp<android::IBinder> mVirtualDisplay;
android::sp<FrameProxy> mFrameProxy;
Minicap::FrameAvailableListener* mUserFrameAvailableListener;
bool mHaveBuffer;
bool mHaveRunningDisplay;
android::CpuConsumer::LockedBuffer mBuffer;
int
createVirtualDisplay() {
uint32_t sourceWidth, sourceHeight;
uint32_t targetWidth, targetHeight;
android::status_t err;
switch (mDesiredOrientation) {
case Minicap::ORIENTATION_90:
sourceWidth = mRealHeight;
sourceHeight = mRealWidth;
targetWidth = mDesiredHeight;
targetHeight = mDesiredWidth;
break;
case Minicap::ORIENTATION_270:
sourceWidth = mRealHeight;
sourceHeight = mRealWidth;
targetWidth = mDesiredHeight;
targetHeight = mDesiredWidth;
break;
case Minicap::ORIENTATION_180:
sourceWidth = mRealWidth;
sourceHeight = mRealHeight;
targetWidth = mDesiredWidth;
targetHeight = mDesiredHeight;
break;
case Minicap::ORIENTATION_0:
default:
sourceWidth = mRealWidth;
sourceHeight = mRealHeight;
targetWidth = mDesiredWidth;
targetHeight = mDesiredHeight;
break;
}
// Set up virtual display size.
android::Rect layerStackRect(sourceWidth, sourceHeight);
android::Rect visibleRect(targetWidth, targetHeight);
// Create a Surface for the virtual display to write to.
MCINFO("Creating SurfaceComposerClient");
android::sp<android::SurfaceComposerClient> sc = new android::SurfaceComposerClient();
MCINFO("Performing SurfaceComposerClient init check");
if ((err = sc->initCheck()) != android::NO_ERROR) {
MCERROR("Unable to initialize SurfaceComposerClient");
return err;
}
// Create virtual display.
MCINFO("Creating virtual display");
mVirtualDisplay = android::SurfaceComposerClient::createDisplay(
/* const String8& displayName */ android::String8("minicap"),
/* bool secure */ true
);
MCINFO("Creating CPU consumer");
mConsumer = new android::CpuConsumer(3, false);
mConsumer->setName(android::String8("minicap"));
MCINFO("Creating buffer queue");
mBufferQueue = mConsumer->getBufferQueue();
mBufferQueue->setDefaultBufferSize(targetWidth, targetHeight);
mBufferQueue->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888);
MCINFO("Creating frame waiter");
mFrameProxy = new FrameProxy(mUserFrameAvailableListener);
mConsumer->setFrameAvailableListener(mFrameProxy);
MCINFO("Publishing virtual display");
android::SurfaceComposerClient::openGlobalTransaction();
android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferQueue);
android::SurfaceComposerClient::setDisplayProjection(mVirtualDisplay,
android::DISPLAY_ORIENTATION_0, layerStackRect, visibleRect);
android::SurfaceComposerClient::setDisplayLayerStack(mVirtualDisplay, 0); // default stack
android::SurfaceComposerClient::closeGlobalTransaction();
mHaveRunningDisplay = true;
return 0;
}
void
destroyVirtualDisplay() {
MCINFO("Destroying virtual display");
if (mHaveBuffer) {
mConsumer->unlockBuffer(mBuffer);
mHaveBuffer = false;
}
mBufferQueue = NULL;
mConsumer = NULL;
mFrameProxy = NULL;
mVirtualDisplay = NULL;
mHaveRunningDisplay = false;
}
static Minicap::Format
convertFormat(android::PixelFormat format) {
switch (format) {
case android::PIXEL_FORMAT_NONE:
return FORMAT_NONE;
case android::PIXEL_FORMAT_CUSTOM:
return FORMAT_CUSTOM;
case android::PIXEL_FORMAT_TRANSLUCENT:
return FORMAT_TRANSLUCENT;
case android::PIXEL_FORMAT_TRANSPARENT:
return FORMAT_TRANSPARENT;
case android::PIXEL_FORMAT_OPAQUE:
return FORMAT_OPAQUE;
case android::PIXEL_FORMAT_RGBA_8888:
return FORMAT_RGBA_8888;
case android::PIXEL_FORMAT_RGBX_8888:
return FORMAT_RGBX_8888;
case android::PIXEL_FORMAT_RGB_888:
return FORMAT_RGB_888;
case android::PIXEL_FORMAT_RGB_565:
return FORMAT_RGB_565;
case android::PIXEL_FORMAT_BGRA_8888:
return FORMAT_BGRA_8888;
case android::PIXEL_FORMAT_RGBA_5551:
return FORMAT_RGBA_5551;
case android::PIXEL_FORMAT_RGBA_4444:
return FORMAT_RGBA_4444;
default:
return FORMAT_UNKNOWN;
}
}
};
int
minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info) {
android::sp<android::IBinder> dpy = android::SurfaceComposerClient::getBuil
gitextract_xy_v5_46/ ├── .github/ │ └── dependabot.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .semaphore/ │ ├── publish.yml │ └── semaphore.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Makefile ├── README.md ├── build-remote.sh ├── example/ │ ├── .gitignore │ ├── README.md │ ├── app.js │ ├── package.json │ └── public/ │ └── index.html ├── experimental/ │ ├── .gitignore │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── logback.xml │ │ ├── java/ │ │ │ └── io/ │ │ │ └── devicefarmer/ │ │ │ └── minicap/ │ │ │ ├── Main.kt │ │ │ ├── SimpleServer.kt │ │ │ ├── output/ │ │ │ │ ├── DisplayOutput.kt │ │ │ │ ├── MinicapClientOutput.kt │ │ │ │ └── ScreenshotOutput.kt │ │ │ ├── provider/ │ │ │ │ ├── BaseProvider.kt │ │ │ │ └── SurfaceProvider.kt │ │ │ └── utils/ │ │ │ ├── DisplayInfo.kt │ │ │ ├── DisplayManager.kt │ │ │ ├── DisplayManagerGlobal.kt │ │ │ └── SurfaceControl.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── values-night/ │ │ └── themes.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── minicap │ └── settings.gradle ├── gdb.sh ├── jni/ │ ├── Android.mk │ ├── Application.mk │ ├── minicap/ │ │ ├── Android.mk │ │ ├── JpgEncoder.cpp │ │ ├── JpgEncoder.hpp │ │ ├── Projection.hpp │ │ ├── SimpleServer.cpp │ │ ├── SimpleServer.hpp │ │ ├── minicap.cpp │ │ └── util/ │ │ ├── debug.h │ │ └── formatter.hpp │ ├── minicap-shared/ │ │ ├── Android.mk │ │ ├── README.md │ │ ├── aosp/ │ │ │ ├── .rsync-filter │ │ │ ├── Android.mk │ │ │ ├── Makefile │ │ │ ├── include/ │ │ │ │ ├── Minicap.hpp │ │ │ │ └── mcdebug.h │ │ │ └── src/ │ │ │ ├── minicap_14.cpp │ │ │ ├── minicap_16.cpp │ │ │ ├── minicap_17.cpp │ │ │ ├── minicap_18.cpp │ │ │ ├── minicap_19.cpp │ │ │ ├── minicap_21.cpp │ │ │ ├── minicap_22.cpp │ │ │ ├── minicap_23.cpp │ │ │ ├── minicap_24.cpp │ │ │ ├── minicap_25.cpp │ │ │ ├── minicap_26.cpp │ │ │ ├── minicap_27.cpp │ │ │ ├── minicap_28.cpp │ │ │ ├── minicap_29.cpp │ │ │ ├── minicap_30.cpp │ │ │ ├── minicap_31.cpp │ │ │ └── minicap_9.cpp │ │ └── mock/ │ │ └── Minicap.cpp │ └── vendor/ │ └── Android.mk ├── package.json └── run.sh
SYMBOL INDEX (391 symbols across 26 files)
FILE: example/app.js
function tryRead (line 44) | function tryRead() {
FILE: jni/minicap-shared/aosp/include/Minicap.hpp
class Minicap (line 6) | class Minicap {
type CaptureMethod (line 8) | enum CaptureMethod {
type Format (line 14) | enum Format {
type Orientation (line 30) | enum Orientation {
type DisplayInfo (line 37) | struct DisplayInfo {
type Frame (line 49) | struct Frame {
type FrameAvailableListener (line 59) | struct FrameAvailableListener {
method Minicap (line 67) | Minicap() {}
FILE: jni/minicap-shared/aosp/src/minicap_14.cpp
class MinicapImpl (line 66) | class MinicapImpl: public Minicap {
method MinicapImpl (line 68) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 80) | virtual int
method consumePendingFrame (line 86) | virtual int
method getCaptureMethod (line 112) | virtual Minicap::CaptureMethod
method getDisplayId (line 117) | virtual int32_t
method release (line 122) | virtual void
method releaseConsumedFrame (line 127) | virtual void
method setDesiredInfo (line 133) | virtual int
method setFrameAvailableListener (line 140) | virtual void
method setRealInfo (line 145) | virtual int
method convertFormat (line 158) | static Minicap::Format
function minicap_try_get_display_info (line 191) | int
function Minicap (line 214) | Minicap*
function minicap_free (line 219) | void
function minicap_start_thread_pool (line 224) | void
FILE: jni/minicap-shared/aosp/src/minicap_16.cpp
class MinicapImpl (line 68) | class MinicapImpl: public Minicap {
method MinicapImpl (line 70) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 82) | virtual int
method consumePendingFrame (line 88) | virtual int
method getCaptureMethod (line 114) | virtual Minicap::CaptureMethod
method getDisplayId (line 119) | virtual int32_t
method release (line 124) | virtual void
method releaseConsumedFrame (line 129) | virtual void
method setDesiredInfo (line 135) | virtual int
method setFrameAvailableListener (line 142) | virtual void
method setRealInfo (line 147) | virtual int
method convertFormat (line 160) | static Minicap::Format
function minicap_try_get_display_info (line 193) | int
function Minicap (line 216) | Minicap*
function minicap_free (line 221) | void
function minicap_start_thread_pool (line 226) | void
FILE: jni/minicap-shared/aosp/src/minicap_17.cpp
class FrameProxy (line 72) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 74) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 77) | virtual void
class MinicapImpl (line 86) | class MinicapImpl: public Minicap
method MinicapImpl (line 89) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 105) | virtual int
method consumePendingFrame (line 114) | virtual int
method getCaptureMethod (line 141) | virtual Minicap::CaptureMethod
method getDisplayId (line 146) | virtual int32_t
method release (line 151) | virtual void
method releaseConsumedFrame (line 156) | virtual void
method setDesiredInfo (line 164) | virtual int
method setFrameAvailableListener (line 172) | virtual void
method setRealInfo (line 177) | virtual int
method createVirtualDisplay (line 200) | int
method destroyVirtualDisplay (line 284) | void
method convertFormat (line 301) | static Minicap::Format
function minicap_try_get_display_info (line 334) | int
function Minicap (line 359) | Minicap*
function minicap_free (line 364) | void
function minicap_start_thread_pool (line 369) | void
FILE: jni/minicap-shared/aosp/src/minicap_18.cpp
class FrameProxy (line 72) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 74) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 77) | virtual void
class MinicapImpl (line 86) | class MinicapImpl: public Minicap
method MinicapImpl (line 89) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 105) | virtual int
method consumePendingFrame (line 114) | virtual int
method getCaptureMethod (line 141) | virtual Minicap::CaptureMethod
method getDisplayId (line 146) | virtual int32_t
method release (line 151) | virtual void
method releaseConsumedFrame (line 156) | virtual void
method setDesiredInfo (line 164) | virtual int
method setFrameAvailableListener (line 172) | virtual void
method setRealInfo (line 177) | virtual int
method createVirtualDisplay (line 200) | int
method destroyVirtualDisplay (line 281) | void
method convertFormat (line 298) | static Minicap::Format
function minicap_try_get_display_info (line 331) | int
function Minicap (line 356) | Minicap*
function minicap_free (line 361) | void
function minicap_start_thread_pool (line 366) | void
FILE: jni/minicap-shared/aosp/src/minicap_19.cpp
class FrameProxy (line 78) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 80) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 83) | virtual void
class MinicapImpl (line 92) | class MinicapImpl: public Minicap
method MinicapImpl (line 95) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 111) | virtual int
method consumePendingFrame (line 120) | virtual int
method getCaptureMethod (line 147) | virtual Minicap::CaptureMethod
method getDisplayId (line 152) | virtual int32_t
method release (line 157) | virtual void
method releaseConsumedFrame (line 162) | virtual void
method setDesiredInfo (line 170) | virtual int
method setFrameAvailableListener (line 178) | virtual void
method setRealInfo (line 183) | virtual int
method createVirtualDisplay (line 207) | int
method destroyVirtualDisplay (line 295) | void
method convertFormat (line 313) | static Minicap::Format
function minicap_try_get_display_info (line 346) | int
function Minicap (line 371) | Minicap*
function minicap_free (line 376) | void
function minicap_start_thread_pool (line 381) | void
FILE: jni/minicap-shared/aosp/src/minicap_21.cpp
class FrameProxy (line 72) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 74) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 77) | virtual void
class MinicapImpl (line 86) | class MinicapImpl: public Minicap
method MinicapImpl (line 89) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 105) | virtual int
method consumePendingFrame (line 114) | virtual int
method getCaptureMethod (line 141) | virtual Minicap::CaptureMethod
method getDisplayId (line 146) | virtual int32_t
method release (line 151) | virtual void
method releaseConsumedFrame (line 156) | virtual void
method setDesiredInfo (line 164) | virtual int
method setFrameAvailableListener (line 172) | virtual void
method setRealInfo (line 177) | virtual int
method createVirtualDisplay (line 201) | int
method destroyVirtualDisplay (line 282) | void
method convertFormat (line 301) | static Minicap::Format
function minicap_try_get_display_info (line 334) | int
function Minicap (line 366) | Minicap*
function minicap_free (line 371) | void
function minicap_start_thread_pool (line 376) | void
FILE: jni/minicap-shared/aosp/src/minicap_22.cpp
type CompatFrameAvailableListener (line 74) | struct CompatFrameAvailableListener : public virtual android::RefBase {
class FrameProxy (line 79) | class FrameProxy: public CompatFrameAvailableListener {
method FrameProxy (line 81) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 84) | virtual void
class MinicapImpl (line 93) | class MinicapImpl: public Minicap
method MinicapImpl (line 96) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 112) | virtual int
method consumePendingFrame (line 121) | virtual int
method getCaptureMethod (line 148) | virtual Minicap::CaptureMethod
method getDisplayId (line 153) | virtual int32_t
method release (line 158) | virtual void
method releaseConsumedFrame (line 163) | virtual void
method setDesiredInfo (line 171) | virtual int
method setFrameAvailableListener (line 179) | virtual void
method setRealInfo (line 184) | virtual int
method createVirtualDisplay (line 208) | int
method destroyVirtualDisplay (line 289) | void
method convertFormat (line 308) | static Minicap::Format
function minicap_try_get_display_info (line 341) | int
function Minicap (line 373) | Minicap*
function minicap_free (line 378) | void
function minicap_start_thread_pool (line 383) | void
FILE: jni/minicap-shared/aosp/src/minicap_23.cpp
class FrameProxy (line 72) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 74) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 77) | virtual void
class MinicapImpl (line 86) | class MinicapImpl: public Minicap
method MinicapImpl (line 89) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 105) | virtual int
method consumePendingFrame (line 114) | virtual int
method getCaptureMethod (line 141) | virtual Minicap::CaptureMethod
method getDisplayId (line 146) | virtual int32_t
method release (line 151) | virtual void
method releaseConsumedFrame (line 156) | virtual void
method setDesiredInfo (line 164) | virtual int
method setFrameAvailableListener (line 172) | virtual void
method setRealInfo (line 177) | virtual int
method createVirtualDisplay (line 201) | int
method destroyVirtualDisplay (line 282) | void
method convertFormat (line 301) | static Minicap::Format
function minicap_try_get_display_info (line 334) | int
function Minicap (line 366) | Minicap*
function minicap_free (line 371) | void
function minicap_start_thread_pool (line 376) | void
FILE: jni/minicap-shared/aosp/src/minicap_24.cpp
class FrameProxy (line 72) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 74) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 77) | virtual void
class MinicapImpl (line 86) | class MinicapImpl: public Minicap
method MinicapImpl (line 89) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 105) | virtual int
method consumePendingFrame (line 114) | virtual int
method getCaptureMethod (line 141) | virtual Minicap::CaptureMethod
method getDisplayId (line 146) | virtual int32_t
method release (line 151) | virtual void
method releaseConsumedFrame (line 156) | virtual void
method setDesiredInfo (line 164) | virtual int
method setFrameAvailableListener (line 172) | virtual void
method setRealInfo (line 177) | virtual int
method createVirtualDisplay (line 201) | int
method destroyVirtualDisplay (line 282) | void
method convertFormat (line 301) | static Minicap::Format
function minicap_try_get_display_info (line 334) | int
function Minicap (line 366) | Minicap*
function minicap_free (line 371) | void
function minicap_start_thread_pool (line 376) | void
FILE: jni/minicap-shared/aosp/src/minicap_25.cpp
class FrameProxy (line 73) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 75) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 78) | virtual void
class MinicapImpl (line 87) | class MinicapImpl: public Minicap
method MinicapImpl (line 90) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 106) | virtual int
method consumePendingFrame (line 115) | virtual int
method getCaptureMethod (line 142) | virtual Minicap::CaptureMethod
method getDisplayId (line 147) | virtual int32_t
method release (line 152) | virtual void
method releaseConsumedFrame (line 157) | virtual void
method setDesiredInfo (line 165) | virtual int
method setFrameAvailableListener (line 173) | virtual void
method setRealInfo (line 178) | virtual int
method createVirtualDisplay (line 202) | int
method destroyVirtualDisplay (line 306) | void
method convertFormat (line 325) | static Minicap::Format
function minicap_try_get_display_info (line 358) | int
function Minicap (line 390) | Minicap*
function minicap_free (line 395) | void
function minicap_start_thread_pool (line 400) | void
FILE: jni/minicap-shared/aosp/src/minicap_26.cpp
class FrameProxy (line 73) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 75) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 78) | virtual void
class MinicapImpl (line 87) | class MinicapImpl: public Minicap
method MinicapImpl (line 90) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 106) | virtual int
method consumePendingFrame (line 115) | virtual int
method getCaptureMethod (line 142) | virtual Minicap::CaptureMethod
method getDisplayId (line 147) | virtual int32_t
method release (line 152) | virtual void
method releaseConsumedFrame (line 157) | virtual void
method setDesiredInfo (line 165) | virtual int
method setFrameAvailableListener (line 173) | virtual void
method setRealInfo (line 178) | virtual int
method createVirtualDisplay (line 202) | int
method destroyVirtualDisplay (line 289) | void
method convertFormat (line 308) | static Minicap::Format
function minicap_try_get_display_info (line 341) | int
function Minicap (line 373) | Minicap*
function minicap_free (line 378) | void
function minicap_start_thread_pool (line 383) | void
FILE: jni/minicap-shared/aosp/src/minicap_27.cpp
class FrameProxy (line 73) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 75) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 78) | virtual void
class MinicapImpl (line 87) | class MinicapImpl: public Minicap
method MinicapImpl (line 90) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 106) | virtual int
method consumePendingFrame (line 115) | virtual int
method getCaptureMethod (line 142) | virtual Minicap::CaptureMethod
method getDisplayId (line 147) | virtual int32_t
method release (line 152) | virtual void
method releaseConsumedFrame (line 157) | virtual void
method setDesiredInfo (line 165) | virtual int
method setFrameAvailableListener (line 173) | virtual void
method setRealInfo (line 178) | virtual int
method createVirtualDisplay (line 202) | int
method destroyVirtualDisplay (line 289) | void
method convertFormat (line 308) | static Minicap::Format
function minicap_try_get_display_info (line 341) | int
function Minicap (line 373) | Minicap*
function minicap_free (line 378) | void
function minicap_start_thread_pool (line 383) | void
FILE: jni/minicap-shared/aosp/src/minicap_28.cpp
class FrameProxy (line 73) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 75) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 78) | virtual void
class MinicapImpl (line 87) | class MinicapImpl: public Minicap
method MinicapImpl (line 90) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 106) | virtual int
method consumePendingFrame (line 115) | virtual int
method getCaptureMethod (line 142) | virtual Minicap::CaptureMethod
method getDisplayId (line 147) | virtual int32_t
method release (line 152) | virtual void
method releaseConsumedFrame (line 157) | virtual void
method setDesiredInfo (line 165) | virtual int
method setFrameAvailableListener (line 173) | virtual void
method setRealInfo (line 178) | virtual int
method createVirtualDisplay (line 202) | int
method destroyVirtualDisplay (line 289) | void
method convertFormat (line 308) | static Minicap::Format
function minicap_try_get_display_info (line 341) | int
function Minicap (line 373) | Minicap*
function minicap_free (line 378) | void
function minicap_start_thread_pool (line 383) | void
FILE: jni/minicap-shared/aosp/src/minicap_29.cpp
class FrameProxy (line 73) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 75) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 78) | virtual void
class MinicapImpl (line 87) | class MinicapImpl: public Minicap
method MinicapImpl (line 90) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 106) | virtual int
method consumePendingFrame (line 115) | virtual int
method getCaptureMethod (line 142) | virtual Minicap::CaptureMethod
method getDisplayId (line 147) | virtual int32_t
method release (line 152) | virtual void
method releaseConsumedFrame (line 157) | virtual void
method setDesiredInfo (line 165) | virtual int
method setFrameAvailableListener (line 173) | virtual void
method setRealInfo (line 178) | virtual int
method createVirtualDisplay (line 202) | int
method destroyVirtualDisplay (line 289) | void
method convertFormat (line 308) | static Minicap::Format
function minicap_try_get_display_info (line 341) | int
function Minicap (line 377) | Minicap*
function minicap_free (line 382) | void
function minicap_start_thread_pool (line 387) | void
FILE: jni/minicap-shared/aosp/src/minicap_30.cpp
class FrameProxy (line 75) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 77) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 80) | virtual void
class MinicapImpl (line 89) | class MinicapImpl: public Minicap
method MinicapImpl (line 92) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 108) | virtual int
method consumePendingFrame (line 117) | virtual int
method getCaptureMethod (line 144) | virtual Minicap::CaptureMethod
method getDisplayId (line 149) | virtual int32_t
method release (line 154) | virtual void
method releaseConsumedFrame (line 159) | virtual void
method setDesiredInfo (line 167) | virtual int
method setFrameAvailableListener (line 175) | virtual void
method setRealInfo (line 180) | virtual int
method createVirtualDisplay (line 204) | int
method destroyVirtualDisplay (line 291) | void
method convertFormat (line 310) | static Minicap::Format
function minicap_try_get_display_info (line 343) | int
function Minicap (line 387) | Minicap*
function minicap_free (line 392) | void
function minicap_start_thread_pool (line 397) | void
FILE: jni/minicap-shared/aosp/src/minicap_31.cpp
class FrameProxy (line 77) | class FrameProxy: public android::ConsumerBase::FrameAvailableListener {
method FrameProxy (line 79) | FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(l...
method onFrameAvailable (line 82) | virtual void
class MinicapImpl (line 91) | class MinicapImpl: public Minicap
method MinicapImpl (line 94) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 110) | virtual int
method consumePendingFrame (line 119) | virtual int
method getCaptureMethod (line 146) | virtual Minicap::CaptureMethod
method getDisplayId (line 151) | virtual int32_t
method release (line 156) | virtual void
method releaseConsumedFrame (line 161) | virtual void
method setDesiredInfo (line 169) | virtual int
method setFrameAvailableListener (line 177) | virtual void
method setRealInfo (line 182) | virtual int
method createVirtualDisplay (line 206) | int
method destroyVirtualDisplay (line 293) | void
method convertFormat (line 312) | static Minicap::Format
function minicap_try_get_display_info (line 345) | int
function Minicap (line 389) | Minicap*
function minicap_free (line 394) | void
function minicap_start_thread_pool (line 399) | void
FILE: jni/minicap-shared/aosp/src/minicap_9.cpp
class MinicapImpl (line 64) | class MinicapImpl: public Minicap {
method MinicapImpl (line 66) | MinicapImpl(int32_t displayId)
method applyConfigChanges (line 78) | virtual int
method consumePendingFrame (line 84) | virtual int
method getCaptureMethod (line 110) | virtual Minicap::CaptureMethod
method getDisplayId (line 115) | virtual int32_t
method release (line 120) | virtual void
method releaseConsumedFrame (line 125) | virtual void
method setDesiredInfo (line 131) | virtual int
method setFrameAvailableListener (line 138) | virtual void
method setRealInfo (line 143) | virtual int
method convertFormat (line 156) | static Minicap::Format
function minicap_try_get_display_info (line 189) | int
function Minicap (line 212) | Minicap*
function minicap_free (line 217) | void
function minicap_start_thread_pool (line 222) | void
FILE: jni/minicap-shared/mock/Minicap.cpp
function minicap_try_get_display_info (line 9) | int
function Minicap (line 14) | Minicap*
function minicap_free (line 19) | void
function minicap_start_thread_pool (line 23) | void
FILE: jni/minicap/JpgEncoder.hpp
class JpgEncoder (line 8) | class JpgEncoder {
FILE: jni/minicap/Projection.hpp
class Projection (line 7) | class Projection {
class Parser (line 9) | class Parser {
method Parser (line 11) | Parser(): mState(real_width_start) {
method parse (line 14) | bool
type State (line 172) | enum State {
method isDigit (line 192) | inline bool
method Projection (line 207) | Projection()
method forceMaximumSize (line 215) | void
method forceAspectRatio (line 226) | void
method valid (line 238) | bool
FILE: jni/minicap/SimpleServer.cpp
type sockaddr_un (line 28) | struct sockaddr_un
type sockaddr (line 33) | struct sockaddr
type sockaddr_un (line 48) | struct sockaddr_un
type sockaddr (line 50) | struct sockaddr
FILE: jni/minicap/SimpleServer.hpp
class SimpleServer (line 4) | class SimpleServer {
FILE: jni/minicap/minicap.cpp
function usage (line 38) | static void
class FrameWaiter (line 56) | class FrameWaiter: public Minicap::FrameAvailableListener {
method FrameWaiter (line 58) | FrameWaiter()
method waitForFrame (line 64) | int
method reportExtraConsumption (line 77) | void
method onFrameAvailable (line 83) | void
method stop (line 90) | void
method isStopped (line 95) | bool
function pumps (line 108) | static int
function pumpf (line 127) | static int
function putUInt32LE (line 144) | static int
function try_get_framebuffer_display_info (line 153) | static int
function signal_handler (line 192) | static void
function main (line 209) | int
FILE: jni/minicap/util/formatter.hpp
class formatter (line 5) | class formatter
method formatter (line 8) | formatter() {}
method formatter (line 12) | formatter & operator << (const Type & value)
method str (line 18) | std::string str() const { return stream_.str(); }
type ConvertToString (line 21) | enum ConvertToString
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (348K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 125,
"preview": "version: 2\nupdates:\n- package-ecosystem: npm\n directory: \"/\"\n schedule:\n interval: daily\n open-pull-requests-limit"
},
{
"path": ".gitignore",
"chars": 70,
"preview": ".gradle\n/*.tgz\n/.env\n/gdb.setup\n/libs/\n/libs/\n/obj/\n/prebuilt/\n/temp/\n"
},
{
"path": ".gitmodules",
"chars": 126,
"preview": "[submodule \"libjpeg-turbo\"]\n\tpath = jni/vendor/libjpeg-turbo\n\turl = https://github.com/devicefarmer/android-libjpeg-turb"
},
{
"path": ".npmignore",
"chars": 187,
"preview": "/.env\n/.gitmodules\n/.npmignore\n/.semaphore\n/*.tgz\n/build-remote.sh\n/CONTRIBUTING.md\n/example/\n/gdb.setup\n/gdb.sh\n/jni/\n/"
},
{
"path": ".semaphore/publish.yml",
"chars": 1052,
"preview": "version: v1.0\nname: Publish\nblocks:\n - name: Publish\n task:\n jobs:\n - name: NPM publish\n comman"
},
{
"path": ".semaphore/semaphore.yml",
"chars": 1163,
"preview": "version: v1.0\nname: NPM test\nagent:\n machine:\n type: e1-standard-2\n os_image: ubuntu2204\nblocks:\n - name: Test\n "
},
{
"path": "CONTRIBUTING.md",
"chars": 1134,
"preview": "# Contributing\n\nWe are happy to accept any contributions that make sense and respect the rules listed below.\n\n## How to "
},
{
"path": "ISSUE_TEMPLATE.md",
"chars": 264,
"preview": "**What is the issue or idea you have?**\n\n**Have you tried STF?**\n<!-- Minicap was made for STF, and STF contains many wo"
},
{
"path": "LICENSE",
"chars": 596,
"preview": "Copyright © 2013 CyberAgent, Inc.\nCopyright © 2016 The OpenSTF Project\n\nLicensed under the Apache License, Version 2.0 ("
},
{
"path": "Makefile",
"chars": 4256,
"preview": ".PHONY: default clean prebuilt\n\nNDKBUILT := \\\n\tlibs/arm64-v8a/minicap \\\n\tlibs/arm64-v8a/minicap-nopie \\\n\tlibs/armeabi-v7"
},
{
"path": "README.md",
"chars": 11804,
"preview": "# minicap\n\nMinicap provides a socket interface for streaming realtime screen capture data out of Android devices. It is "
},
{
"path": "build-remote.sh",
"chars": 341,
"preview": "#!/usr/bin/env bash\n\nset -xueo pipefail\n\nbuilder=$1\n\nrsync \\\n --recursive \\\n --copy-links \\\n --perms \\\n --times \\\n "
},
{
"path": "example/.gitignore",
"chars": 15,
"preview": "/node_modules/\n"
},
{
"path": "example/README.md",
"chars": 1763,
"preview": "# Example: minicap over WebSockets\n\nA quick and dirty example to show how minicap might be used as part of an applicatio"
},
{
"path": "example/app.js",
"chars": 4475,
"preview": "var WebSocketServer = require('ws').Server\n , http = require('http')\n , express = require('express')\n , path = requir"
},
{
"path": "example/package.json",
"chars": 73,
"preview": "{\n \"dependencies\": {\n \"express\": \"^4.12.3\",\n \"ws\": \"^0.7.1\"\n }\n}\n"
},
{
"path": "example/public/index.html",
"chars": 1060,
"preview": "<!doctype html>\n<canvas id=\"canvas\" style=\"border: 1px solid red;\"></canvas>\n\n<script>\n/*jshint browser:true*/\n\nvar BLAN"
},
{
"path": "experimental/.gitignore",
"chars": 232,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": "experimental/app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "experimental/app/build.gradle",
"chars": 1799,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/proguard-rules.pro",
"chars": 751,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "experimental/app/src/main/AndroidManifest.xml",
"chars": 1005,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version"
},
{
"path": "experimental/app/src/main/assets/logback.xml",
"chars": 1580,
"preview": "<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not u"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/Main.kt",
"chars": 6700,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/SimpleServer.kt",
"chars": 1468,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/output/DisplayOutput.kt",
"chars": 798,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/output/MinicapClientOutput.kt",
"chars": 2392,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/output/ScreenshotOutput.kt",
"chars": 875,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/provider/BaseProvider.kt",
"chars": 4625,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/provider/SurfaceProvider.kt",
"chars": 4945,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayInfo.kt",
"chars": 1221,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayManager.kt",
"chars": 2584,
"preview": "package io.devicefarmer.minicap.utils\n\nimport android.annotation.SuppressLint\nimport android.hardware.display.VirtualDis"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/utils/DisplayManagerGlobal.kt",
"chars": 2603,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/java/io/devicefarmer/minicap/utils/SurfaceControl.kt",
"chars": 3185,
"preview": "/*\n * Copyright (C) 2020 Orange\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use th"
},
{
"path": "experimental/app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 5837,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version"
},
{
"path": "experimental/app/src/main/res/values/colors.xml",
"chars": 990,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version"
},
{
"path": "experimental/app/src/main/res/values/strings.xml",
"chars": 681,
"preview": "<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not u"
},
{
"path": "experimental/app/src/main/res/values/themes.xml",
"chars": 1441,
"preview": "<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not u"
},
{
"path": "experimental/app/src/main/res/values-night/themes.xml",
"chars": 1441,
"preview": "<!--\n ~ Copyright (C) 2020 Orange\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not u"
},
{
"path": "experimental/build.gradle",
"chars": 771,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n ex"
},
{
"path": "experimental/gradle/wrapper/gradle-wrapper.properties",
"chars": 253,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "experimental/gradle.properties",
"chars": 1542,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "experimental/gradlew",
"chars": 8669,
"preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "experimental/gradlew.bat",
"chars": 2868,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "experimental/minicap",
"chars": 111,
"preview": "#!/bin/sh\nCLASSPATH=/data/local/tmp/minicap-0.1-debug.apk app_process /system/bin io.devicefarmer.minicap.Main\n"
},
{
"path": "experimental/settings.gradle",
"chars": 137,
"preview": "pluginManagement {\n repositories {\n gradlePluginPortal()\n jcenter()\n }\n}\ninclude ':app'\nrootProject."
},
{
"path": "gdb.sh",
"chars": 2300,
"preview": "#!/usr/bin/env bash\n\n# Fail on error, verbose output\nset -exo pipefail\n\n# Check NDK setup\nif [ -z \"$NDK_ROOT\" ]; then\n "
},
{
"path": "jni/Android.mk",
"chars": 37,
"preview": "include $(call all-subdir-makefiles)\n"
},
{
"path": "jni/Application.mk",
"chars": 1435,
"preview": "APP_ABI := armeabi-v7a arm64-v8a x86 x86_64\n\n# Get C++11 working\nAPP_CPPFLAGS += -std=c++11 -fexceptions\nAPP_STL := c++_"
},
{
"path": "jni/minicap/Android.mk",
"chars": 635,
"preview": "LOCAL_PATH := $(call my-dir)\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := minicap-common\n\nLOCAL_SRC_FILES := \\\n\tJpgEncoder.cpp"
},
{
"path": "jni/minicap/JpgEncoder.cpp",
"chars": 1800,
"preview": "#include <stdexcept>\n\n#include \"JpgEncoder.hpp\"\n#include \"util/debug.h\"\n\nJpgEncoder::JpgEncoder(unsigned int prePadding,"
},
{
"path": "jni/minicap/JpgEncoder.hpp",
"chars": 696,
"preview": "#ifndef MINICAP_JPG_ENCODER_HPP\n#define MINICAP_JPG_ENCODER_HPP\n\n#include <turbojpeg.h>\n\n#include \"Minicap.hpp\"\n\nclass J"
},
{
"path": "jni/minicap/Projection.hpp",
"chars": 6183,
"preview": "#ifndef MINICAP_PROJECTION_HPP\n#define MINICAP_PROJECTION_HPP\n\n#include <cmath>\n#include <ostream>\n\nclass Projection {\np"
},
{
"path": "jni/minicap/SimpleServer.cpp",
"chars": 923,
"preview": "#include \"SimpleServer.hpp\"\n\n#include <fcntl.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#in"
},
{
"path": "jni/minicap/SimpleServer.hpp",
"chars": 222,
"preview": "#ifndef MINICAP_SIMPLE_SERVER_HPP\n#define MINICAP_SIMPLE_SERVER_HPP\n\nclass SimpleServer {\npublic:\n SimpleServer();\n ~S"
},
{
"path": "jni/minicap/minicap.cpp",
"chars": 14323,
"preview": "#include <fcntl.h>\n#include <getopt.h>\n#include <linux/fb.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <signal.h>"
},
{
"path": "jni/minicap/util/debug.h",
"chars": 1101,
"preview": "#ifndef __minicap_dbg_h__\n#define __minicap_dbg_h__\n\n// These macros were originally from\n// http://c.learncodethehardwa"
},
{
"path": "jni/minicap/util/formatter.hpp",
"chars": 714,
"preview": "#ifndef MINICAP_UTIL_FORMATTER_H\n#define MINICAP_UTIL_FORMATTER_H\n\n// Originally from http://stackoverflow.com/a/1226262"
},
{
"path": "jni/minicap-shared/Android.mk",
"chars": 316,
"preview": "LOCAL_PATH := $(abspath $(call my-dir))\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := minicap-shared\n\nLOCAL_MODULE_FILENAME := "
},
{
"path": "jni/minicap-shared/README.md",
"chars": 11677,
"preview": "# minicap-shared\n\nThis module provides the shared library used by minicap. Due to the use of private APIs, it must be bu"
},
{
"path": "jni/minicap-shared/aosp/.rsync-filter",
"chars": 14,
"preview": "- .git\n- libs\n"
},
{
"path": "jni/minicap-shared/aosp/Android.mk",
"chars": 2112,
"preview": "LOCAL_PATH := $(call my-dir)\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := minicap\n\nLOCAL_MODULE_TAGS := optional\n\nifneq ($(OVE"
},
{
"path": "jni/minicap-shared/aosp/Makefile",
"chars": 21061,
"preview": "this_dir = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))\nSOURCES := Android.mk include/Minicap.hpp\n\nall: \\\n\tlibs/androi"
},
{
"path": "jni/minicap-shared/aosp/include/Minicap.hpp",
"chars": 3486,
"preview": "#ifndef MINICAP_HPP\n#define MINICAP_HPP\n\n#include <cstdint>\n\nclass Minicap {\npublic:\n enum CaptureMethod {\n METHOD_F"
},
{
"path": "jni/minicap-shared/aosp/include/mcdebug.h",
"chars": 1101,
"preview": "#ifndef __minicap_dbg_h__\n#define __minicap_dbg_h__\n\n// These macros were originally from\n// http://c.learncodethehardwa"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_14.cpp",
"chars": 5569,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_16.cpp",
"chars": 5589,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_17.cpp",
"chars": 9949,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_18.cpp",
"chars": 9749,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_19.cpp",
"chars": 10498,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_21.cpp",
"chars": 10334,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_22.cpp",
"chars": 10765,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_23.cpp",
"chars": 10371,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_24.cpp",
"chars": 10371,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_25.cpp",
"chars": 11704,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_26.cpp",
"chars": 10565,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_27.cpp",
"chars": 10565,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_28.cpp",
"chars": 10418,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_29.cpp",
"chars": 10593,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_30.cpp",
"chars": 10988,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_31.cpp",
"chars": 11079,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n#"
},
{
"path": "jni/minicap-shared/aosp/src/minicap_9.cpp",
"chars": 5497,
"preview": "#include \"Minicap.hpp\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <math.h>\n\n"
},
{
"path": "jni/minicap-shared/mock/Minicap.cpp",
"chars": 579,
"preview": "// The only purpose of this file is to make the project build without any\n// complaints about missing libraries (which e"
},
{
"path": "jni/vendor/Android.mk",
"chars": 37,
"preview": "include $(call all-subdir-makefiles)\n"
},
{
"path": "package.json",
"chars": 534,
"preview": "{\n \"name\": \"@devicefarmer/minicap-prebuilt\",\n \"version\": \"2.7.3\",\n \"description\": \"Prebuilt binaries of minicap.\",\n "
},
{
"path": "run.sh",
"chars": 1994,
"preview": "#!/usr/bin/env bash\n\n# Fail on error, verbose output\nset -exo pipefail\n\n# Build project\nexperimental/gradlew -p experime"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the DeviceFarmer/minicap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (320.2 KB), approximately 89.9k tokens, and a symbol index with 391 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.