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?** ================================================ 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 ` 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 ================================================ ================================================ 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 ================================================ ================================================ FILE: experimental/app/src/main/assets/logback.xml ================================================ System.err %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n /data/local/tmp/minicap.log true %-4relative [%thread] %-5level %logger{35} - %msg%n %msg%n ================================================ 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) { 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 ]\n" + " -d : Display ID. (%d)\n" + " -n : Change the name of the abtract unix domain socket. (%s)\n" + " -P : Display projection (x@x/{0|90|180|270}).\n" + " -Q : 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 : 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 = 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 ================================================ ================================================ FILE: experimental/app/src/main/res/values/colors.xml ================================================ #FFBB86FC #FF6200EE #FF3700B3 #FF03DAC5 #FF018786 #FF000000 #FFFFFFFF ================================================ FILE: experimental/app/src/main/res/values/strings.xml ================================================ Minicap ================================================ FILE: experimental/app/src/main/res/values/themes.xml ================================================ ================================================ FILE: experimental/app/src/main/res/values-night/themes.xml ================================================ ================================================ 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 <>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 #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 #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 #include 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(realWidth) / static_cast(realHeight); if (virtualHeight > (uint32_t) (virtualWidth / aspect)) { virtualHeight = static_cast(round(virtualWidth / aspect)); } else { virtualWidth = static_cast(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 #include #include #include #include #include #include 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ]\n" " -d : Display ID. (%d)\n" " -n : Change the name of the abtract unix domain socket. (%s)\n" " -P : Display projection (x@x/{0|90|180|270}).\n" " -Q : 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 : 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 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 lock(mMutex); mPendingFrames -= count; } void onFrameAvailable() { std::unique_lock 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(vinfo.xres) / static_cast(vinfo.width) * 25.4; info->ydpi = static_cast(vinfo.yres) / static_cast(vinfo.height) * 25.4; info->size = std::sqrt( (static_cast(vinfo.width) * static_cast(vinfo.width)) + (static_cast(vinfo.height) * static_cast(vinfo.height))) / 25.4; info->density = std::sqrt( (static_cast(vinfo.xres) * static_cast(vinfo.xres)) + (static_cast(vinfo.yres) * static_cast(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 x@x/{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 #include #include #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 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 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 #include #include #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 #include #include #include #include #include #include #include #include #include #include #include #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 mComposer; android::sp 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 mComposer; android::sp 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 mBufferQueue; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 mBufferQueue; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 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_19.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include // Terrible hack to access ScreenshotClient's mBufferQueue. It's too risky // to do `new android::BufferQueue()` by ourselves, makers tend to customize // it. #define private public #include #undef private #include #include #include #include #include #include #include #include #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 mBufferQueue; android::sp mConsumer; android::sp mVirtualDisplay; android::sp mFrameProxy; Minicap::FrameAvailableListener* mUserFrameAvailableListener; bool mHaveBuffer; bool mHaveRunningDisplay; android::CpuConsumer::LockedBuffer mBuffer; android::ScreenshotClient mScreenshotClient; 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 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 buffer queue"); // Many devices fail if we create a BufferQueue by ourselves. // Fortunately, we can steal one from the very stable ScreenshotClient // and repurpose it. mScreenshotClient.getCpuConsumer(); mBufferQueue = mScreenshotClient.mBufferQueue; MCINFO("Creating CPU consumer"); // Some devices have a modified, larger CpuConsumer. Try to account // for that by increasing the size. Example devices include Asus MeMO // Pad 7 (ME176). mConsumer = new(operator new(sizeof(android::CpuConsumer) + 100)) android::CpuConsumer(mBufferQueue, 3, false); mConsumer->setName(android::String8("minicap")); mConsumer->setDefaultBufferSize(targetWidth, targetHeight); mConsumer->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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); 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 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_21.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_22.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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"; } } // This trick is needed for many Samsung devices running 5.1. Examples include // Galaxy S5 Neo, Galaxy J1, Galaxy A8 and so on. struct CompatFrameAvailableListener : public virtual android::RefBase { virtual void onFrameAvailable(const android::BufferItem& item) = 0; virtual void onFrameReplaced() {}; }; class FrameProxy: public CompatFrameAvailableListener { public: FrameProxy(Minicap::FrameAvailableListener* listener): mUserListener(listener) { } virtual void onFrameAvailable(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = reinterpret_cast(new FrameProxy(mUserFrameAvailableListener)); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_23.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_24.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_25.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 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 buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); // Well, this is just horrible. Thanks 7.1 Developer Preview. typedef void (*setDisplaySurfaceAOSP_t)(android::sp const&, android::sp const&); typedef void (*setDisplaySurface71DP_t)(android::sp const&, android::sp); // This is the standard AOSP symbol. setDisplaySurfaceAOSP_t setDisplaySurfaceAOSP = (setDisplaySurfaceAOSP_t) dlsym(RTLD_DEFAULT, "_ZN7android21SurfaceComposerClient17setDisplaySurfaceERKNS_2spINS_7IBinderEEERKNS1_INS_22IGraphicBufferProducerEEE"); if (setDisplaySurfaceAOSP != NULL) { // Yay! Things are like they're supposed to be! setDisplaySurfaceAOSP(mVirtualDisplay, mBufferProducer); } else { // This is the 7.1 Developer Preview symbol. setDisplaySurface71DP_t setDisplaySurface71DP = (setDisplaySurface71DP_t) dlsym(RTLD_DEFAULT, "_ZN7android21SurfaceComposerClient17setDisplaySurfaceERKNS_2spINS_7IBinderEEENS1_INS_22IGraphicBufferProducerEEE"); if (setDisplaySurface71DP != NULL) { MCINFO("Found 7.1 Developer Preview SurfaceComposerClient::setDisplaySurface"); setDisplaySurface71DP(mVirtualDisplay, mBufferProducer); } else { MCERROR("Unable to find AOSP or 7.1 DP style SurfaceComposerClient::setDisplaySurface"); return android::NAME_NOT_FOUND; } } 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_26.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ true ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_27.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ true ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::openGlobalTransaction(); android::SurfaceComposerClient::setDisplaySurface(mVirtualDisplay, mBufferProducer); 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"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_28.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ true ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::Transaction t; t.setDisplaySurface(mVirtualDisplay, mBufferProducer); t.setDisplayProjection(mVirtualDisplay, android::DISPLAY_ORIENTATION_0, layerStackRect, visibleRect); t.setDisplayLayerStack(mVirtualDisplay, 0); // default stack t.apply(); mHaveRunningDisplay = true; return 0; } void destroyVirtualDisplay() { MCINFO("Destroying virtual display"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getBuiltInDisplay(displayId); android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_29.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ true ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::Transaction t; t.setDisplaySurface(mVirtualDisplay, mBufferProducer); t.setDisplayProjection(mVirtualDisplay, android::DISPLAY_ORIENTATION_0, layerStackRect, visibleRect); t.setDisplayLayerStack(mVirtualDisplay, 0); // default stack t.apply(); mHaveRunningDisplay = true; return 0; } void destroyVirtualDisplay() { MCINFO("Destroying virtual display"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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 dpy = android::SurfaceComposerClient::getPhysicalDisplayToken(displayId); if(!dpy) { MCINFO("could not get display for id: %d, using internal display", displayId); dpy = android::SurfaceComposerClient::getInternalDisplayToken(); } android::Vector configs; android::status_t err = android::SurfaceComposerClient::getDisplayConfigs(dpy, &configs); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } int activeConfig = android::SurfaceComposerClient::getActiveConfig(dpy); if(static_cast(activeConfig) >= configs.size()) { MCERROR("Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return android::BAD_VALUE; } android::DisplayInfo dinfo = configs[activeConfig]; 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_30.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ true ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::Transaction t; t.setDisplaySurface(mVirtualDisplay, mBufferProducer); t.setDisplayProjection(mVirtualDisplay, android::ui::ROTATION_0, layerStackRect, visibleRect); t.setDisplayLayerStack(mVirtualDisplay, 0); // default stack t.apply(); mHaveRunningDisplay = true; return 0; } void destroyVirtualDisplay() { MCINFO("Destroying virtual display"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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::status_t err; android::sp dpy = android::SurfaceComposerClient::getPhysicalDisplayToken(displayId); if(!dpy) { MCINFO("could not get display for id: %d, using internal display", displayId); dpy = android::SurfaceComposerClient::getInternalDisplayToken(); } android::DisplayInfo dinfo; err = android::SurfaceComposerClient::getDisplayInfo(dpy, &dinfo); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } android::ui::DisplayState dstate; err = android::SurfaceComposerClient::getDisplayState(dpy, &dstate); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient:::getDisplayState() failed: %s (%d)\n", error_name(err), err); return err; } android::DisplayConfig dconfig; err = android::SurfaceComposerClient::getActiveDisplayConfig(dpy, &dconfig); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getActiveDisplayConfig() failed: %s (%d)\n", error_name(err), err); return err; } const android::ui::Size& viewport = dstate.viewport; info->width = viewport.getWidth(); info->height = viewport.getHeight(); info->orientation = android::ui::toRotationInt(dstate.orientation); info->fps = dconfig.refreshRate; info->density = dinfo.density; info->xdpi = dconfig.xDpi; info->ydpi = dconfig.yDpi; info->secure = dinfo.secure; info->size = sqrt(pow(viewport.getWidth() / dconfig.xDpi, 2) + pow(viewport.getWidth() / dconfig.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_31.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(const android::BufferItem& /* item */) { 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 mBufferProducer; android::sp mBufferConsumer; android::sp mConsumer; android::sp mVirtualDisplay; android::sp 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 sc = new android::SurfaceComposerClient(); MCINFO("Performing SurfaceComposerClient init check"); if ((err = sc->initCheck()) != android::NO_ERROR) { MCERROR("Unable to initialize SurfaceComposerClient"); return err; } // This is now REQUIRED in O Developer Preview 1 or there's a segfault // when the sp goes out of scope. sc = NULL; // Create virtual display. MCINFO("Creating virtual display"); mVirtualDisplay = android::SurfaceComposerClient::createDisplay( /* const String8& displayName */ android::String8("minicap"), /* bool secure */ false ); MCINFO("Creating buffer queue"); android::BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer, false); MCINFO("Setting buffer options"); mBufferConsumer->setDefaultBufferSize(targetWidth, targetHeight); mBufferConsumer->setDefaultBufferFormat(android::PIXEL_FORMAT_RGBA_8888); MCINFO("Creating CPU consumer"); mConsumer = new android::CpuConsumer(mBufferConsumer, 3, false); mConsumer->setName(android::String8("minicap")); MCINFO("Creating frame waiter"); mFrameProxy = new FrameProxy(mUserFrameAvailableListener); mConsumer->setFrameAvailableListener(mFrameProxy); MCINFO("Publishing virtual display"); android::SurfaceComposerClient::Transaction t; t.setDisplaySurface(mVirtualDisplay, mBufferProducer); t.setDisplayProjection(mVirtualDisplay, android::ui::ROTATION_0, layerStackRect, visibleRect); t.setDisplayLayerStack(mVirtualDisplay, 0); // default stack t.apply(); mHaveRunningDisplay = true; return 0; } void destroyVirtualDisplay() { MCINFO("Destroying virtual display"); android::SurfaceComposerClient::destroyDisplay(mVirtualDisplay); if (mHaveBuffer) { mConsumer->unlockBuffer(mBuffer); mHaveBuffer = false; } mBufferProducer = NULL; mBufferConsumer = 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::status_t err; android::sp dpy = android::SurfaceComposerClient::getPhysicalDisplayToken(android::PhysicalDisplayId(displayId)); if(!dpy) { MCINFO("could not get display for id: %d, using internal display", displayId); dpy = android::SurfaceComposerClient::getInternalDisplayToken(); } android::ui::StaticDisplayInfo dinfo; err = android::SurfaceComposerClient::getStaticDisplayInfo(dpy, &dinfo); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getStaticDisplayInfo() failed: %s (%d)\n", error_name(err), err); return err; } android::ui::DisplayState dstate; err = android::SurfaceComposerClient::getDisplayState(dpy, &dstate); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient:::getDisplayState() failed: %s (%d)\n", error_name(err), err); return err; } android::ui::DisplayMode dconfig; err = android::SurfaceComposerClient::getActiveDisplayMode(dpy, &dconfig); if (err != android::NO_ERROR) { MCERROR("SurfaceComposerClient::getActiveDisplayMode() failed: %s (%d)\n", error_name(err), err); return err; } const android::ui::Size& viewport = dstate.layerStackSpaceRect; info->width = viewport.getWidth(); info->height = viewport.getHeight(); info->orientation = android::ui::toRotationInt(dstate.orientation); info->fps = dconfig.refreshRate; info->density = dinfo.density; info->xdpi = dconfig.xDpi; info->ydpi = dconfig.yDpi; info->secure = dinfo.secure; info->size = sqrt(pow(viewport.getWidth() / dconfig.xDpi, 2) + pow(viewport.getWidth() / dconfig.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_9.cpp ================================================ #include "Minicap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #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"; 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); 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 mComposer; android::sp 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/mock/Minicap.cpp ================================================ // The only purpose of this file is to make the project build without any // complaints about missing libraries (which exist on the device side). // While we could also use one of the actual built libs, they might depend // on other shared libraries, making this right here the easiest way to // enable interoperability. #include "Minicap.hpp" int minicap_try_get_display_info(int32_t displayId, Minicap::DisplayInfo* info) { return 0; } Minicap* minicap_create(int32_t displayId) { return NULL; } void minicap_free(Minicap* mc) { } void minicap_start_thread_pool() { } ================================================ FILE: jni/vendor/Android.mk ================================================ include $(call all-subdir-makefiles) ================================================ FILE: package.json ================================================ { "name": "@devicefarmer/minicap-prebuilt", "version": "2.7.3", "description": "Prebuilt binaries of minicap.", "keywords": [ "minicap" ], "bugs": { "url": "https://github.com/DeviceFarmer/minicap/issues" }, "license": "Apache-2.0", "author": { "name": "DeviceFarmer", "email": "support@devicefarmer.com", "url": "https://devicefarmer.github.io" }, "repository": { "type": "git", "url": "https://github.com/DeviceFarmer/minicap.git" }, "scripts": { "prepublish": "make" } } ================================================ FILE: run.sh ================================================ #!/usr/bin/env bash # Fail on error, verbose output set -exo pipefail # Build project experimental/gradlew -p experimental assembleDebug ndk-build NDK_DEBUG=1 1>&2 # Figure out which ABI and SDK the device has abi=$(adb shell getprop ro.product.cpu.abi | tr -d '\r') sdk=$(adb shell getprop ro.build.version.sdk | tr -d '\r') pre=$(adb shell getprop ro.build.version.preview_sdk | tr -d '\r') rel=$(adb shell getprop ro.build.version.release | tr -d '\r') if [[ -n "$pre" && "$pre" > "0" ]]; then sdk=$(($sdk + 1)) fi # PIE is only supported since SDK 16 if (($sdk >= 16)); then bin=minicap else bin=minicap-nopie fi apk="app_process /system/bin io.devicefarmer.minicap.Main" args= if [ "$1" = "autosize" ]; then set +o pipefail size=$(adb shell dumpsys window | grep -Eo 'init=[0-9]+x[0-9]+' | head -1 | cut -d= -f 2) if [ "$size" = "" ]; then w=$(adb shell dumpsys window | grep -Eo 'DisplayWidth=[0-9]+' | head -1 | cut -d= -f 2) h=$(adb shell dumpsys window | grep -Eo 'DisplayHeight=[0-9]+' | head -1 | cut -d= -f 2) size="${w}x${h}" fi args="-P $size@$size/0" set -o pipefail shift fi # Create a directory for our resources dir=/data/local/tmp/minicap-devel # Keep compatible with older devices that don't have `mkdir -p`. adb shell "mkdir $dir 2>/dev/null || true" # Upload the binary adb push libs/$abi/$bin $dir # Upload the shared library if [ -e jni/minicap-shared/aosp/libs/android-$rel/$abi/minicap.so ]; then adb push jni/minicap-shared/aosp/libs/android-$rel/$abi/minicap.so $dir adb shell LD_LIBRARY_PATH=$dir $dir/$bin $args "$@" else if [ -e jni/minicap-shared/aosp/libs/android-$sdk/$abi/minicap.so ]; then adb push jni/minicap-shared/aosp/libs/android-$sdk/$abi/minicap.so $dir adb shell LD_LIBRARY_PATH=$dir $dir/$bin $args "$@" else adb push experimental/app/build/outputs/apk/debug/minicap-debug.apk $dir adb shell CLASSPATH=$dir/minicap-debug.apk $apk $args "$@" fi fi # Clean up adb shell rm -r $dir