From 55db053488cfef411006d1e6e45165b8c9dcbc1a Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Mon, 12 Jun 2017 11:27:18 -0700 Subject: [PATCH] Initial import of VNCFlinger --- .clang-format | 13 ++ Android.mk | 43 ++++++ src/EglWindow.cpp | 189 +++++++++++++++++++++++++ src/EglWindow.h | 87 ++++++++++++ src/EventQueue.cpp | 59 ++++++++ src/EventQueue.h | 67 +++++++++ src/Program.cpp | 307 +++++++++++++++++++++++++++++++++++++++++ src/Program.h | 94 +++++++++++++ src/README | 2 + src/VNCFlinger.cpp | 165 ++++++++++++++++++++++ src/VNCFlinger.h | 58 ++++++++ src/VirtualDisplay.cpp | 297 +++++++++++++++++++++++++++++++++++++++ src/VirtualDisplay.h | 130 +++++++++++++++++ src/main.cpp | 8 ++ 14 files changed, 1519 insertions(+) create mode 100644 .clang-format create mode 100644 Android.mk create mode 100644 src/EglWindow.cpp create mode 100644 src/EglWindow.h create mode 100644 src/EventQueue.cpp create mode 100644 src/EventQueue.h create mode 100644 src/Program.cpp create mode 100644 src/Program.h create mode 100644 src/README create mode 100644 src/VNCFlinger.cpp create mode 100644 src/VNCFlinger.h create mode 100644 src/VirtualDisplay.cpp create mode 100644 src/VirtualDisplay.h create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..fc4eb1b --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +BasedOnStyle: Google +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false + +AccessModifierOffset: -2 +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 +UseTab: Never +PenaltyExcessCharacter: 32 diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..4412088 --- /dev/null +++ b/Android.mk @@ -0,0 +1,43 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + src/EglWindow.cpp \ + src/EventQueue.cpp \ + src/Program.cpp \ + src/VirtualDisplay.cpp \ + src/VNCFlinger.cpp \ + src/main.cpp + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/src \ + external/libvncserver \ + external/zlib + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libcrypto \ + libcutils \ + libjpeg \ + libgui \ + libpng \ + libssl \ + libui \ + libutils \ + libz \ + libEGL \ + libGLESv2 + +LOCAL_STATIC_LIBRARIES += \ + libvncserver + +LOCAL_CFLAGS := -Ofast -Werror +LOCAL_CFLAGS += -DLOG_NDEBUG=0 + +#LOCAL_CXX := /usr/bin/include-what-you-use + +LOCAL_MODULE := vncflinger +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/src/EglWindow.cpp b/src/EglWindow.cpp new file mode 100644 index 0000000..c534b02 --- /dev/null +++ b/src/EglWindow.cpp @@ -0,0 +1,189 @@ +/* + * Copyright 2013 The Android Open Source 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. + */ + +#define LOG_TAG "VNC-EglWindow" +//#define LOG_NDEBUG 0 +#include + +#define EGL_EGLEXT_PROTOTYPES + +#include +#include +#include + +#include "EglWindow.h" + +#include +#include + +#include + +using namespace android; + + +status_t EglWindow::createWindow(const sp& surface) { + if (mEglSurface != EGL_NO_SURFACE) { + ALOGE("surface already created"); + return UNKNOWN_ERROR; + } + status_t err = eglSetupContext(false); + if (err != NO_ERROR) { + return err; + } + + // Cache the current dimensions. We're not expecting these to change. + surface->query(NATIVE_WINDOW_WIDTH, &mWidth); + surface->query(NATIVE_WINDOW_HEIGHT, &mHeight); + + // Output side (EGL surface to draw on). + sp anw = new Surface(surface); + mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, anw.get(), + NULL); + if (mEglSurface == EGL_NO_SURFACE) { + ALOGE("eglCreateWindowSurface error: %#x", eglGetError()); + eglRelease(); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +status_t EglWindow::createPbuffer(int width, int height) { + if (mEglSurface != EGL_NO_SURFACE) { + ALOGE("surface already created"); + return UNKNOWN_ERROR; + } + status_t err = eglSetupContext(true); + if (err != NO_ERROR) { + return err; + } + + mWidth = width; + mHeight = height; + + EGLint pbufferAttribs[] = { + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_NONE + }; + mEglSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, pbufferAttribs); + if (mEglSurface == EGL_NO_SURFACE) { + ALOGE("eglCreatePbufferSurface error: %#x", eglGetError()); + eglRelease(); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +status_t EglWindow::makeCurrent() const { + if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + ALOGE("eglMakeCurrent failed: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +status_t EglWindow::eglSetupContext(bool forPbuffer) { + EGLBoolean result; + + mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL_NO_DISPLAY) { + ALOGE("eglGetDisplay failed: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + + EGLint majorVersion, minorVersion; + result = eglInitialize(mEglDisplay, &majorVersion, &minorVersion); + if (result != EGL_TRUE) { + ALOGE("eglInitialize failed: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion); + + EGLint numConfigs = 0; + EGLint windowConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RECORDABLE_ANDROID, 1, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + }; + EGLint pbufferConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + result = eglChooseConfig(mEglDisplay, + forPbuffer ? pbufferConfigAttribs : windowConfigAttribs, + &mEglConfig, 1, &numConfigs); + if (result != EGL_TRUE) { + ALOGE("eglChooseConfig error: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + + EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, + contextAttribs); + if (mEglContext == EGL_NO_CONTEXT) { + ALOGE("eglCreateContext error: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +void EglWindow::eglRelease() { + ALOGV("EglWindow::eglRelease"); + if (mEglDisplay != EGL_NO_DISPLAY) { + eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + if (mEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mEglContext); + } + + if (mEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mEglSurface); + } + } + + mEglDisplay = EGL_NO_DISPLAY; + mEglContext = EGL_NO_CONTEXT; + mEglSurface = EGL_NO_SURFACE; + mEglConfig = NULL; + + eglReleaseThread(); +} + +// Sets the presentation time on the current EGL buffer. +void EglWindow::presentationTime(nsecs_t whenNsec) const { + eglPresentationTimeANDROID(mEglDisplay, mEglSurface, whenNsec); +} + +// Swaps the EGL buffer. +void EglWindow::swapBuffers() const { + eglSwapBuffers(mEglDisplay, mEglSurface); +} diff --git a/src/EglWindow.h b/src/EglWindow.h new file mode 100644 index 0000000..69d0c31 --- /dev/null +++ b/src/EglWindow.h @@ -0,0 +1,87 @@ +/* + * Copyright 2013 The Android Open Source 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. + */ + +#ifndef SCREENRECORD_EGL_WINDOW_H +#define SCREENRECORD_EGL_WINDOW_H + +#include +#include + +#include + +namespace android { + +/* + * Wraps EGL display, context, surface, config for a window surface. + * + * Not thread safe. + */ +class EglWindow { +public: + EglWindow() : + mEglDisplay(EGL_NO_DISPLAY), + mEglContext(EGL_NO_CONTEXT), + mEglSurface(EGL_NO_SURFACE), + mEglConfig(NULL), + mWidth(0), + mHeight(0) + {} + ~EglWindow() { eglRelease(); } + + // Creates an EGL window for the supplied surface. + status_t createWindow(const sp& surface); + + // Creates an EGL pbuffer surface. + status_t createPbuffer(int width, int height); + + // Return width and height values (obtained from IGBP). + int getWidth() const { return mWidth; } + int getHeight() const { return mHeight; } + + // Release anything we created. + void release() { eglRelease(); } + + // Make this context current. + status_t makeCurrent() const; + + // Sets the presentation time on the current EGL buffer. + void presentationTime(nsecs_t whenNsec) const; + + // Swaps the EGL buffer. + void swapBuffers() const; + +private: + EglWindow(const EglWindow&); + EglWindow& operator=(const EglWindow&); + + // Init display, create config and context. + status_t eglSetupContext(bool forPbuffer); + void eglRelease(); + + // Basic EGL goodies. + EGLDisplay mEglDisplay; + EGLContext mEglContext; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + + // Surface dimensions. + int mWidth; + int mHeight; +}; + +}; // namespace android + +#endif /*SCREENRECORD_EGL_WINDOW_H*/ diff --git a/src/EventQueue.cpp b/src/EventQueue.cpp new file mode 100644 index 0000000..a107f30 --- /dev/null +++ b/src/EventQueue.cpp @@ -0,0 +1,59 @@ +#define LOG_TAG "VNC-EventQueue" +#include + +#include "EventQueue.h" + +using namespace android; + +void EventQueue::enqueue(const Event& event) { + mQueue.push(event); + ALOGV("enqueue: mId=%d mData=%p qlen=%zu", event.mId, event.mData, mQueue.size()); + + Mutex::Autolock _l(mMutex); + mCondition.broadcast(); +} + +void EventQueue::await() { + Mutex::Autolock _l(mMutex); + + while (mRunning) { + ALOGV("begin wait"); + mCondition.wait(mMutex); + + ALOGV("queue active"); + while (!mQueue.empty()) { + Event event = mQueue.front(); + mQueue.pop(); + + mMutex.unlock(); + for (std::vector::iterator it = mListeners.begin(); + it != mListeners.end(); ++it) { + ALOGV("call listener: %p", *it); + (*it)->onEvent(event); + } + mMutex.lock(); + + } + } +} + +void EventQueue::shutdown() { + Mutex::Autolock _l(mMutex); + flush(); + mRunning = false; + mCondition.broadcast(); +} + +void EventQueue::flush() { + Mutex::Autolock _l(mMutex); + mQueue = {}; +} + +void EventQueue::addListener(EventListener *listener) { + mListeners.push_back(listener); + ALOGV("addListener: %p", listener); +} + +void EventQueue::removeListener(EventListener *listener) { + mListeners.erase(std::remove(mListeners.begin(), mListeners.end(), listener), mListeners.end()); +} diff --git a/src/EventQueue.h b/src/EventQueue.h new file mode 100644 index 0000000..e926831 --- /dev/null +++ b/src/EventQueue.h @@ -0,0 +1,67 @@ +#ifndef MQ_H +#define MQ_H + +#include +#include + +#include +#include + +#define EVENT_CLIENT_CONNECT 0 +#define EVENT_CLIENT_GONE 1 +#define EVENT_BUFFER_READY 2 + +namespace android { + +class Event { +public: + Event(unsigned int id, void* data = 0): + mId(id), + mData(data) {} + + Event(const Event& ev): + mId(ev.mId), + mData(ev.mData) {} + + unsigned int mId; + void* mData; +}; + +class EventListener { +public: + virtual ~EventListener() {} + + virtual void onEvent(const Event& event) = 0; +}; + +class EventQueue : public RefBase { +public: + EventQueue(): + mRunning(true) {} + + virtual void await(); + + virtual void shutdown(); + + virtual void enqueue(const Event& event); + + virtual void flush(); + + virtual void addListener(EventListener *listener); + + virtual void removeListener(EventListener *listener); + + virtual ~EventQueue() { } + +private: + Mutex mMutex; + Condition mCondition; + + std::queue mQueue; + std::vector mListeners; + + bool mRunning; +}; + +}; +#endif diff --git a/src/Program.cpp b/src/Program.cpp new file mode 100644 index 0000000..72c11aa --- /dev/null +++ b/src/Program.cpp @@ -0,0 +1,307 @@ +/* + * Copyright 2013 The Android Open Source 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. + */ + +#define LOG_TAG "VNC" +//#define LOG_NDEBUG 0 +#include + +#include "Program.h" + +#include +#include + +#include + +using namespace android; + +// 4x4 identity matrix +const float Program::kIdentity[] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f +}; + +// Simple vertex shader. Texture coord calc includes matrix for GLConsumer +// transform. +static const char* kVertexShader = + "uniform mat4 uMVPMatrix;\n" + "uniform mat4 uGLCMatrix;\n" + "attribute vec4 aPosition;\n" + "attribute vec4 aTextureCoord;\n" + "varying vec2 vTextureCoord;\n" + "void main() {\n" + " gl_Position = uMVPMatrix * aPosition;\n" + " vTextureCoord = (uGLCMatrix * aTextureCoord).xy;\n" + "}\n"; + +// Trivial fragment shader for external texture. +static const char* kExtFragmentShader = + "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + "varying vec2 vTextureCoord;\n" + "uniform samplerExternalOES uTexture;\n" + "void main() {\n" + " gl_FragColor = texture2D(uTexture, vTextureCoord);\n" + "}\n"; + +// Trivial fragment shader for mundane texture. +static const char* kFragmentShader = + "precision mediump float;\n" + "varying vec2 vTextureCoord;\n" + "uniform sampler2D uTexture;\n" + "void main() {\n" + " gl_FragColor = texture2D(uTexture, vTextureCoord);\n" + //" gl_FragColor = vec4(0.2, 1.0, 0.2, 1.0);\n" + "}\n"; + +status_t Program::setup(ProgramType type) { + ALOGV("Program::setup type=%d", type); + status_t err; + + mProgramType = type; + + GLuint program; + if (type == PROGRAM_TEXTURE_2D) { + err = createProgram(&program, kVertexShader, kFragmentShader); + } else { + err = createProgram(&program, kVertexShader, kExtFragmentShader); + } + if (err != NO_ERROR) { + return err; + } + assert(program != 0); + + maPositionLoc = glGetAttribLocation(program, "aPosition"); + maTextureCoordLoc = glGetAttribLocation(program, "aTextureCoord"); + muMVPMatrixLoc = glGetUniformLocation(program, "uMVPMatrix"); + muGLCMatrixLoc = glGetUniformLocation(program, "uGLCMatrix"); + muTextureLoc = glGetUniformLocation(program, "uTexture"); + if ((maPositionLoc | maTextureCoordLoc | muMVPMatrixLoc | + muGLCMatrixLoc | muTextureLoc) == -1) { + ALOGE("Attrib/uniform lookup failed: %#x", glGetError()); + glDeleteProgram(program); + return UNKNOWN_ERROR; + } + + mProgram = program; + return NO_ERROR; +} + +void Program::release() { + ALOGV("Program::release"); + if (mProgram != 0) { + glDeleteProgram(mProgram); + mProgram = 0; + } +} + +status_t Program::createProgram(GLuint* outPgm, const char* vertexShader, + const char* fragmentShader) { + GLuint vs, fs; + status_t err; + + err = compileShader(GL_VERTEX_SHADER, vertexShader, &vs); + if (err != NO_ERROR) { + return err; + } + err = compileShader(GL_FRAGMENT_SHADER, fragmentShader, &fs); + if (err != NO_ERROR) { + glDeleteShader(vs); + return err; + } + + GLuint program; + err = linkShaderProgram(vs, fs, &program); + glDeleteShader(vs); + glDeleteShader(fs); + if (err == NO_ERROR) { + *outPgm = program; + } + return err; +} + +status_t Program::compileShader(GLenum shaderType, const char* src, + GLuint* outShader) { + GLuint shader = glCreateShader(shaderType); + if (shader == 0) { + ALOGE("glCreateShader error: %#x", glGetError()); + return UNKNOWN_ERROR; + } + + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + ALOGE("Compile of shader type %d failed", shaderType); + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen) { + char* buf = new char[infoLen]; + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + ALOGE("Compile log: %s", buf); + delete[] buf; + } + } + glDeleteShader(shader); + return UNKNOWN_ERROR; + } + *outShader = shader; + return NO_ERROR; +} + +status_t Program::linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) { + GLuint program = glCreateProgram(); + if (program == 0) { + ALOGE("glCreateProgram error: %#x", glGetError()); + return UNKNOWN_ERROR; + } + + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + ALOGE("glLinkProgram failed"); + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char* buf = new char[bufLength]; + if (buf) { + glGetProgramInfoLog(program, bufLength, NULL, buf); + ALOGE("Link log: %s", buf); + delete[] buf; + } + } + glDeleteProgram(program); + return UNKNOWN_ERROR; + } + + *outPgm = program; + return NO_ERROR; +} + + + +status_t Program::blit(GLuint texName, const float* texMatrix, + int32_t x, int32_t y, int32_t w, int32_t h, bool invert) const { + ALOGV("Program::blit %d xy=%d,%d wh=%d,%d", texName, x, y, w, h); + + const float pos[] = { + float(x), float(y+h), + float(x+w), float(y+h), + float(x), float(y), + float(x+w), float(y), + }; + const float uv[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + status_t err; + + err = beforeDraw(texName, texMatrix, pos, uv, invert); + if (err == NO_ERROR) { + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + err = afterDraw(); + } + return err; +} + +status_t Program::drawTriangles(GLuint texName, const float* texMatrix, + const float* vertices, const float* texes, size_t count) const { + ALOGV("Program::drawTriangles texName=%d", texName); + + status_t err; + + err = beforeDraw(texName, texMatrix, vertices, texes, false); + if (err == NO_ERROR) { + glDrawArrays(GL_TRIANGLES, 0, count); + err = afterDraw(); + } + return err; +} + +status_t Program::beforeDraw(GLuint texName, const float* texMatrix, + const float* vertices, const float* texes, bool invert) const { + // Create an orthographic projection matrix based on viewport size. + GLint vp[4]; + glGetIntegerv(GL_VIEWPORT, vp); + float screenToNdc[16] = { + 2.0f/float(vp[2]), 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f/float(vp[3]), 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + }; + if (invert) { + screenToNdc[5] = -screenToNdc[5]; + screenToNdc[13] = -screenToNdc[13]; + } + + glUseProgram(mProgram); + + glVertexAttribPointer(maPositionLoc, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glVertexAttribPointer(maTextureCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, texes); + glEnableVertexAttribArray(maPositionLoc); + glEnableVertexAttribArray(maTextureCoordLoc); + + glUniformMatrix4fv(muMVPMatrixLoc, 1, GL_FALSE, screenToNdc); + glUniformMatrix4fv(muGLCMatrixLoc, 1, GL_FALSE, texMatrix); + + glActiveTexture(GL_TEXTURE0); + + switch (mProgramType) { + case PROGRAM_EXTERNAL_TEXTURE: + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texName); + break; + case PROGRAM_TEXTURE_2D: + glBindTexture(GL_TEXTURE_2D, texName); + break; + default: + ALOGE("unexpected program type %d", mProgramType); + return UNKNOWN_ERROR; + } + + glUniform1i(muTextureLoc, 0); + + GLenum glErr; + if ((glErr = glGetError()) != GL_NO_ERROR) { + ALOGE("GL error before draw: %#x", glErr); + glDisableVertexAttribArray(maPositionLoc); + glDisableVertexAttribArray(maTextureCoordLoc); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +status_t Program::afterDraw() const { + glDisableVertexAttribArray(maPositionLoc); + glDisableVertexAttribArray(maTextureCoordLoc); + + GLenum glErr; + if ((glErr = glGetError()) != GL_NO_ERROR) { + ALOGE("GL error after draw: %#x", glErr); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} diff --git a/src/Program.h b/src/Program.h new file mode 100644 index 0000000..558be8d --- /dev/null +++ b/src/Program.h @@ -0,0 +1,94 @@ +/* + * Copyright 2013 The Android Open Source 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. + */ + +#ifndef SCREENRECORD_PROGRAM_H +#define SCREENRECORD_PROGRAM_H + +#include + +#include +#include + +namespace android { + +/* + * Utility class for GLES rendering. + * + * Not thread-safe. + */ +class Program { +public: + enum ProgramType { PROGRAM_UNKNOWN=0, PROGRAM_EXTERNAL_TEXTURE, + PROGRAM_TEXTURE_2D }; + + Program() : + mProgramType(PROGRAM_UNKNOWN), + mProgram(0), + maPositionLoc(0), + maTextureCoordLoc(0), + muMVPMatrixLoc(0), + muGLCMatrixLoc(0), + muTextureLoc(0) + {} + ~Program() { release(); } + + // Initialize the program for use with the specified texture type. + status_t setup(ProgramType type); + + // Release the program and associated resources. + void release(); + + // Blit the specified texture to { x, y, x+w, y+h }. Inverts the + // content if "invert" is set. + status_t blit(GLuint texName, const float* texMatrix, + int32_t x, int32_t y, int32_t w, int32_t h, + bool invert = false) const; + + // Draw a number of triangles. + status_t drawTriangles(GLuint texName, const float* texMatrix, + const float* vertices, const float* texes, size_t count) const; + + static const float kIdentity[]; + +private: + Program(const Program&); + Program& operator=(const Program&); + + // Common code for draw functions. + status_t beforeDraw(GLuint texName, const float* texMatrix, + const float* vertices, const float* texes, bool invert) const; + status_t afterDraw() const; + + // GLES 2 shader utilities. + status_t createProgram(GLuint* outPgm, const char* vertexShader, + const char* fragmentShader); + static status_t compileShader(GLenum shaderType, const char* src, + GLuint* outShader); + static status_t linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm); + + ProgramType mProgramType; + GLuint mProgram; + + GLint maPositionLoc; + GLint maTextureCoordLoc; + GLint muMVPMatrixLoc; + GLint muGLCMatrixLoc; + GLint muTextureLoc; +}; + +}; // namespace android + +#endif /*SCREENRECORD_PROGRAM_H*/ diff --git a/src/README b/src/README new file mode 100644 index 0000000..e6cbb2b --- /dev/null +++ b/src/README @@ -0,0 +1,2 @@ +- The surfaceflinger method is present from version 2.3.X and should be supported by all devices. +- It connects with the surfaceflinger service through a Binder interface. diff --git a/src/VNCFlinger.cpp b/src/VNCFlinger.cpp new file mode 100644 index 0000000..a55b868 --- /dev/null +++ b/src/VNCFlinger.cpp @@ -0,0 +1,165 @@ +#define LOG_TAG "VNCFlinger" +#include + +#include +#include +#include + +#include +#include +#include + +#include "VNCFlinger.h" + +using namespace android; + +EventQueue *VNCFlinger::sQueue = new EventQueue(); + +status_t VNCFlinger::start() { + Mutex::Autolock _l(mMutex); + + status_t err = setup_l(); + if (err != NO_ERROR) { + ALOGE("Failed to start VNCFlinger: err=%d", err); + return err; + } + + ALOGD("VNCFlinger is running!"); + + rfbRunEventLoop(mVNCScreen, -1, true); + sQueue->await(); + + release_l(); + return NO_ERROR; +} + +status_t VNCFlinger::setup_l() { + + status_t err = NO_ERROR; + + mMainDpy = SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain); + err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mMainDpyInfo); + if (err != NO_ERROR) { + ALOGE("Unable to get display characteristics\n"); + return err; + } + + bool rotated = VirtualDisplay::isDeviceRotated(mMainDpyInfo.orientation); + if (mWidth == 0) { + mWidth = rotated ? mMainDpyInfo.h : mMainDpyInfo.w; + } + if (mHeight == 0) { + mHeight = rotated ? mMainDpyInfo.w : mMainDpyInfo.h; + } + + ALOGD("Display dimensions: %dx%d rotated=%d", mWidth, mHeight, rotated); + + sQueue->addListener(this); + + mVirtualDisplay = new VirtualDisplay(); + + mVNCBuf = new uint8_t[mWidth * mHeight * 4]; + + rfbLog = VNCFlinger::rfbLogger; + rfbErr = VNCFlinger::rfbLogger; + + // 32-bit color + mVNCScreen = rfbGetScreen(&mArgc, mArgv, mWidth, mHeight, 8, 3, 4); + if (mVNCScreen == NULL) { + ALOGE("Unable to create VNCScreen"); + return NO_INIT; + } + + mVNCScreen->desktopName = "VNCFlinger"; + mVNCScreen->frameBuffer = (char *)mVNCBuf; + mVNCScreen->alwaysShared = TRUE; + mVNCScreen->httpDir = NULL; + mVNCScreen->port = VNC_PORT; + mVNCScreen->newClientHook = (rfbNewClientHookPtr) VNCFlinger::onNewClient; + mVNCScreen->serverFormat.trueColour = true; + mVNCScreen->serverFormat.bitsPerPixel = 32; + mVNCScreen->handleEventsEagerly = true; + mVNCScreen->deferUpdateTime = 5; + + rfbInitServer(mVNCScreen); + + /* Mark as dirty since we haven't sent any updates at all yet. */ + rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight); + + return err; +} + +void VNCFlinger::release_l() { + sQueue->removeListener(this); + mVirtualDisplay.clear(); + + ALOGD("VNCFlinger released"); +} + +status_t VNCFlinger::stop() { + Mutex::Autolock _l(mMutex); + sQueue->shutdown(); + + return NO_ERROR; +} + +void VNCFlinger::onEvent(const Event& event) { + + ALOGV("onEvent: mId=%d mData=%p", event.mId, event.mData); + + switch(event.mId) { + case EVENT_CLIENT_CONNECT: + if (mClientCount == 0) { + mVirtualDisplay->start(mMainDpyInfo, sQueue); + } + mClientCount++; + + ALOGI("Client connected (%zu)", mClientCount); + break; + + case EVENT_CLIENT_GONE: + if (mClientCount > 0) { + mClientCount--; + if (mClientCount == 0) { + mVirtualDisplay->stop(); + } + } + + ALOGI("Client disconnected (%zu)", mClientCount); + break; + + case EVENT_BUFFER_READY: + //mVNCScreen->frameBuffer = (char *) event.mData; + memcpy(mVNCBuf, (uint8_t *) event.mData, mWidth * mHeight * 4); + rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight); + break; + + default: + ALOGE("Unhandled event: %d", event.mId); + break; + } +} + +ClientGoneHookPtr VNCFlinger::onClientGone(rfbClientPtr /* cl */) { + ALOGV("onClientGone"); + sQueue->enqueue(Event(EVENT_CLIENT_GONE)); + return 0; +} + +enum rfbNewClientAction VNCFlinger::onNewClient(rfbClientPtr cl) { + ALOGV("onNewClient"); + cl->clientGoneHook = (ClientGoneHookPtr) VNCFlinger::onClientGone; + sQueue->enqueue(Event(EVENT_CLIENT_CONNECT)); + return RFB_CLIENT_ACCEPT; +} + +void VNCFlinger::rfbLogger(const char *format, ...) { + va_list args; + char buf[256]; + + va_start(args, format); + vsprintf(buf, format, args); + ALOGI("%s", buf); + va_end(args); +} diff --git a/src/VNCFlinger.h b/src/VNCFlinger.h new file mode 100644 index 0000000..45057b5 --- /dev/null +++ b/src/VNCFlinger.h @@ -0,0 +1,58 @@ +#ifndef VNCFLINGER_H +#define VNCFLINGER_H + +#include "EventQueue.h" +#include "VirtualDisplay.h" + +#include + +#include "rfb/rfb.h" + +#define VNC_PORT 5901 + +namespace android { + +class VNCFlinger : public EventListener { +public: + VNCFlinger(int argc, char **argv) : + mArgc(argc), + mArgv(argv), + mClientCount(0) { + } + + virtual void onEvent(const Event& event); + + virtual status_t start(); + virtual status_t stop(); + + static EventQueue *sQueue; + +private: + virtual status_t setup_l(); + virtual void release_l(); + + static ClientGoneHookPtr onClientGone(rfbClientPtr cl); + static enum rfbNewClientAction onNewClient(rfbClientPtr cl); + static void rfbLogger(const char *format, ...); + + rfbScreenInfoPtr mVNCScreen; + uint8_t *mVNCBuf; + + uint32_t mWidth, mHeight; + bool mRotate; + + sp mMainDpy; + DisplayInfo mMainDpyInfo; + + Mutex mMutex; + + sp mVirtualDisplay; + + int mArgc; + char **mArgv; + + size_t mClientCount; +}; + +}; +#endif diff --git a/src/VirtualDisplay.cpp b/src/VirtualDisplay.cpp new file mode 100644 index 0000000..369d271 --- /dev/null +++ b/src/VirtualDisplay.cpp @@ -0,0 +1,297 @@ +/* + * Copyright 2014 The Android Open Source 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. + */ + +#define LOG_TAG "VNC-VirtualDisplay" +//#define LOG_NDEBUG 0 +#include + +#include +#include + +#include + +#include +#include +#include + +#include + +#include "VirtualDisplay.h" + +using namespace android; + +static const int kGlBytesPerPixel = 4; // GL_RGBA + + +/* + * Returns "true" if the device is rotated 90 degrees. + */ +bool VirtualDisplay::isDeviceRotated(int orientation) { + return orientation != DISPLAY_ORIENTATION_0 && + orientation != DISPLAY_ORIENTATION_180; +} + +/* + * Sets the display projection, based on the display dimensions, video size, + * and device orientation. + */ +status_t VirtualDisplay::setDisplayProjection(const sp& dpy, + const DisplayInfo& mMainDpyInfo) { + status_t err; + + // Set the region of the layer stack we're interested in, which in our + // case is "all of it". If the app is rotated (so that the width of the + // app is based on the height of the display), reverse width/height. + bool deviceRotated = isDeviceRotated(mMainDpyInfo.orientation); + uint32_t sourceWidth, sourceHeight; + if (!deviceRotated) { + sourceWidth = mMainDpyInfo.w; + sourceHeight = mMainDpyInfo.h; + } else { + ALOGV("using rotated width/height"); + sourceHeight = mMainDpyInfo.w; + sourceWidth = mMainDpyInfo.h; + } + Rect layerStackRect(sourceWidth, sourceHeight); + + // We need to preserve the aspect ratio of the display. + float displayAspect = (float) sourceHeight / (float) sourceWidth; + + + // Set the way we map the output onto the display surface (which will + // be e.g. 1280x720 for a 720p video). The rect is interpreted + // post-rotation, so if the display is rotated 90 degrees we need to + // "pre-rotate" it by flipping width/height, so that the orientation + // adjustment changes it back. + // + // We might want to encode a portrait display as landscape to use more + // of the screen real estate. (If players respect a 90-degree rotation + // hint, we can essentially get a 720x1280 video instead of 1280x720.) + // In that case, we swap the configured video width/height and then + // supply a rotation value to the display projection. + uint32_t videoWidth, videoHeight; + uint32_t outWidth, outHeight; + if (!mRotate) { + videoWidth = mWidth; + videoHeight = mHeight; + } else { + videoWidth = mHeight; + videoHeight = mWidth; + } + if (videoHeight > (uint32_t)(videoWidth * displayAspect)) { + // limited by narrow width; reduce height + outWidth = videoWidth; + outHeight = (uint32_t)(videoWidth * displayAspect); + } else { + // limited by short height; restrict width + outHeight = videoHeight; + outWidth = (uint32_t)(videoHeight / displayAspect); + } + uint32_t offX, offY; + offX = (videoWidth - outWidth) / 2; + offY = (videoHeight - outHeight) / 2; + Rect displayRect(offX, offY, offX + outWidth, offY + outHeight); + + if (mRotate) { + printf("Rotated content area is %ux%u at offset x=%d y=%d\n", + outHeight, outWidth, offY, offX); + } else { + printf("Content area is %ux%u at offset x=%d y=%d\n", + outWidth, outHeight, offX, offY); + } + + SurfaceComposerClient::setDisplayProjection(dpy, + mRotate ? DISPLAY_ORIENTATION_90 : DISPLAY_ORIENTATION_0, + layerStackRect, displayRect); + return NO_ERROR; +} + +status_t VirtualDisplay::start(const DisplayInfo& mainDpyInfo, EventQueue *queue) { + + Mutex::Autolock _l(mMutex); + + mQueue = queue; + + mRotate = isDeviceRotated(mainDpyInfo.orientation); + mWidth = mRotate ? mainDpyInfo.h : mainDpyInfo.w; + mHeight = mRotate ? mainDpyInfo.w : mainDpyInfo.h; + + sp self = ProcessState::self(); + self->startThreadPool(); + + run("vnc-virtualdisplay"); + + mState = INIT; + while (mState == INIT) { + mStartCond.wait(mMutex); + } + + if (mThreadResult != NO_ERROR) { + ALOGE("Failed to start VDS thread: err=%d", mThreadResult); + return mThreadResult; + } + assert(mState == RUNNING); + + mDpy = SurfaceComposerClient::createDisplay( + String8("VNCFlinger"), false /*secure*/); + + SurfaceComposerClient::openGlobalTransaction(); + SurfaceComposerClient::setDisplaySurface(mDpy, mProducer); + setDisplayProjection(mDpy, mainDpyInfo); + SurfaceComposerClient::setDisplayLayerStack(mDpy, 0); // default stack + SurfaceComposerClient::closeGlobalTransaction(); + + ALOGV("VirtualDisplay::start successful"); + return NO_ERROR; +} + +status_t VirtualDisplay::stop() { + Mutex::Autolock _l(mMutex); + mState = STOPPING; + mEventCond.signal(); + return NO_ERROR; +} + +bool VirtualDisplay::threadLoop() { + Mutex::Autolock _l(mMutex); + + mThreadResult = setup_l(); + + if (mThreadResult != NO_ERROR) { + ALOGW("Aborting VDS thread"); + mState = STOPPED; + release_l(); + mStartCond.broadcast(); + return false; + } + + ALOGV("VDS thread running"); + mState = RUNNING; + mStartCond.broadcast(); + + while (mState == RUNNING) { + mEventCond.wait(mMutex); + ALOGD("Awake, frame available"); + void* ptr = processFrame_l(); + const Event ev(EVENT_BUFFER_READY, ptr); + mQueue->enqueue(ev); + } + + ALOGV("VDS thread stopping"); + release_l(); + mState = STOPPED; + return false; // stop +} + +status_t VirtualDisplay::setup_l() { + status_t err; + + err = mEglWindow.createPbuffer(mWidth, mHeight); + if (err != NO_ERROR) { + return err; + } + mEglWindow.makeCurrent(); + + glViewport(0, 0, mWidth, mHeight); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + // Shader for rendering the external texture. + err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE); + if (err != NO_ERROR) { + return err; + } + + // Input side (buffers from virtual display). + glGenTextures(1, &mExtTextureName); + if (mExtTextureName == 0) { + ALOGE("glGenTextures failed: %#x", glGetError()); + return UNKNOWN_ERROR; + } + + mBufSize = mWidth * mHeight * kGlBytesPerPixel; + + // pixel buffer for image copy + mPBO = new GLuint[NUM_PBO]; + glGenBuffers(NUM_PBO, mPBO); + + for (unsigned int i = 0; i < NUM_PBO; i++) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[i]); + glBufferData(GL_PIXEL_PACK_BUFFER, mBufSize, 0, GL_DYNAMIC_READ); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + + sp consumer; + BufferQueue::createBufferQueue(&mProducer, &consumer); + mGlConsumer = new GLConsumer(consumer, mExtTextureName, + GL_TEXTURE_EXTERNAL_OES, true, false); + mGlConsumer->setName(String8("virtual display")); + mGlConsumer->setDefaultBufferSize(mWidth, mHeight); + mProducer->setMaxDequeuedBufferCount(4); + mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE); + + mGlConsumer->setFrameAvailableListener(this); + + ALOGD("VirtualDisplay::setup_l OK"); + return NO_ERROR; +} + +void* VirtualDisplay::processFrame_l() { + ALOGD("processFrame_l\n"); + + float texMatrix[16]; + mGlConsumer->updateTexImage(); + mGlConsumer->getTransformMatrix(texMatrix); + + // The data is in an external texture, so we need to render it to the + // pbuffer to get access to RGB pixel data. We also want to flip it + // upside-down for easy conversion to a bitmap. + int width = mEglWindow.getWidth(); + int height = mEglWindow.getHeight(); + mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0, mWidth, mHeight, true); + + GLenum glErr; + glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]); + glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0); + if ((glErr = glGetError()) != GL_NO_ERROR) { + ALOGE("glReadPixels failed: %#x", glErr); + return NULL; + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]); + void* ptr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, mBufSize, GL_MAP_READ_BIT); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + mIndex = (mIndex + 1) % NUM_PBO; + return ptr; +} + +void VirtualDisplay::release_l() { + ALOGD("release_l"); + mGlConsumer.clear(); + mProducer.clear(); + mExtTexProgram.release(); + mEglWindow.release(); + SurfaceComposerClient::destroyDisplay(mDpy); +} + +// Callback; executes on arbitrary thread. +void VirtualDisplay::onFrameAvailable(const BufferItem& item) { + Mutex::Autolock _l(mMutex); + mEventCond.signal(); + ALOGD("mTimestamp=%ld mFrameNumber=%ld", item.mTimestamp, item.mFrameNumber); +} diff --git a/src/VirtualDisplay.h b/src/VirtualDisplay.h new file mode 100644 index 0000000..ccfb718 --- /dev/null +++ b/src/VirtualDisplay.h @@ -0,0 +1,130 @@ +/* + * Copyright 2014 The Android Open Source 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. + */ + +#ifndef VDS_H +#define VDS_H + +#include "EventQueue.h" +#include "Program.h" +#include "EglWindow.h" + +#include +#include +#include +#include +#include + + +#define NUM_PBO 2 + +namespace android { + +/* + * Support for "frames" output format. + */ +class VirtualDisplay : public GLConsumer::FrameAvailableListener, Thread { +public: + VirtualDisplay() : Thread(false), + mThreadResult(UNKNOWN_ERROR), + mState(UNINITIALIZED), + mIndex(0) + {} + + // Create an "input surface", similar in purpose to a MediaCodec input + // surface, that the virtual display can send buffers to. Also configures + // EGL with a pbuffer surface on the current thread. + status_t start(const DisplayInfo& mainDpyInfo, EventQueue *queue); + + status_t stop(); + + static bool isDeviceRotated(int orientation); + +private: + VirtualDisplay(const VirtualDisplay&); + VirtualDisplay& operator=(const VirtualDisplay&); + + // Destruction via RefBase. + virtual ~VirtualDisplay() { + assert(mState == UNINITIALIZED || mState == STOPPED); + } + + virtual status_t setDisplayProjection(const sp& dpy, + const DisplayInfo& mainDpyInfo); + + // (overrides GLConsumer::FrameAvailableListener method) + virtual void onFrameAvailable(const BufferItem& item); + + // (overrides Thread method) + virtual bool threadLoop(); + + // One-time setup (essentially object construction on the overlay thread). + status_t setup_l(); + + // Release all resources held. + void release_l(); + + // Process a frame received from the virtual display. + void* processFrame_l(); + + uint32_t mHeight, mWidth; + bool mRotate; + + EventQueue *mQueue; + + // Used to wait for the FrameAvailableListener callback. + Mutex mMutex; + + // Initialization gate. + Condition mStartCond; + + // Thread status, mostly useful during startup. + status_t mThreadResult; + // Overlay thread state. States advance from left to right; object may + // not be restarted. + enum { UNINITIALIZED, INIT, RUNNING, STOPPING, STOPPED } mState; + + // Event notification. Overlay thread sleeps on this until a frame + // arrives or it's time to shut down. + Condition mEventCond; + + // Producer side of queue, passed into the virtual display. + // The consumer end feeds into our GLConsumer. + sp mProducer; + + // This receives frames from the virtual display and makes them available + // as an external texture. + sp mGlConsumer; + + // EGL display / context / surface. + EglWindow mEglWindow; + + // GL rendering support. + Program mExtTexProgram; + + // External texture, updated by GLConsumer. + GLuint mExtTextureName; + + // Pixel data buffers. + size_t mBufSize; + GLuint* mPBO; + unsigned int mIndex; + + sp mDpy; +}; + +}; // namespace android + +#endif /* VDS_H */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..0223f78 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,8 @@ +#include "VNCFlinger.h" + +using namespace android; + +int main(int argc, char **argv) { + VNCFlinger flinger(argc, argv); + flinger.start(); +}