diff --git a/jni/minicap-shared/aosp-soong/Android.bp b/jni/minicap-shared/aosp-soong/Android.bp new file mode 100644 index 0000000..2d85640 --- /dev/null +++ b/jni/minicap-shared/aosp-soong/Android.bp @@ -0,0 +1,25 @@ +cc_library_shared { + name: "minicap-shared", + shared_libs: [ + ], + cflags: [ + "-Werror", + "-Wno-macro-redefined", + "-Wno-sign-compare", + ], + srcs: ["src/*.cpp"], + export_include_dirs: ["."], + target: { + android: { + shared_libs: [ + "libEGL", + "libcutils", + "libutils", + "libbinder", + "libui", + "liblog", + "libgui" + ] + }, + }, +} diff --git a/jni/minicap-shared/aosp-soong/Minicap.hpp b/jni/minicap-shared/aosp-soong/Minicap.hpp new file mode 100644 index 0000000..970968a --- /dev/null +++ b/jni/minicap-shared/aosp-soong/Minicap.hpp @@ -0,0 +1,137 @@ +#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 diff --git a/jni/minicap-shared/aosp-soong/lib/x86_64/minicap-shared.so b/jni/minicap-shared/aosp-soong/lib/x86_64/minicap-shared.so new file mode 100755 index 0000000..b178ba5 Binary files /dev/null and b/jni/minicap-shared/aosp-soong/lib/x86_64/minicap-shared.so differ diff --git a/jni/minicap-shared/aosp-soong/mcdebug.h b/jni/minicap-shared/aosp-soong/mcdebug.h new file mode 100644 index 0000000..0c6f285 --- /dev/null +++ b/jni/minicap-shared/aosp-soong/mcdebug.h @@ -0,0 +1,33 @@ +#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 diff --git a/jni/minicap-shared/aosp-soong/src/minicap_33.cpp b/jni/minicap-shared/aosp-soong/src/minicap_33.cpp new file mode 100644 index 0000000..6d51060 --- /dev/null +++ b/jni/minicap-shared/aosp-soong/src/minicap_33.cpp @@ -0,0 +1,401 @@ +#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, android::ui::DEFAULT_LAYER_STACK); // 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; + auto mDisplayId = android::DisplayId::fromValue(static_cast(displayId)); + android::sp dpy = android::SurfaceComposerClient::getPhysicalDisplayToken(*android::PhysicalDisplayId::tryCast(*mDisplayId)); + 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(); +}