Initial import of VNCFlinger
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 <utils/Log.h>
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <gui/BufferQueue.h>
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/Surface.h>
+
+#include "EglWindow.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <assert.h>
+
+using namespace android;
+
+
+status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& 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<ANativeWindow> 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 <gui/BufferQueue.h>
+#include <utils/Errors.h>
+
+#include <EGL/egl.h>
+
+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<IGraphicBufferProducer>& 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 <utils/Log.h>
+
+#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<EventListener *>::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 <queue>
+#include <vector>
+
+#include <utils/RefBase.h>
+#include <utils/Thread.h>
+
+#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<const Event> mQueue;
+ std::vector<EventListener *> 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 <utils/Log.h>
+
+#include "Program.h"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <assert.h>
+
+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 <utils/Errors.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+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 <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/IGraphicBufferProducer.h>
+
+#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 <ui/DisplayInfo.h>
+
+#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<IBinder> mMainDpy;
+ DisplayInfo mMainDpyInfo;
+
+ Mutex mMutex;
+
+ sp<VirtualDisplay> 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 <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+
+#include <ui/Rect.h>
+
+#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<IBinder>& 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<ProcessState> 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<IGraphicBufferConsumer> 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 <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <ui/DisplayInfo.h>
+#include <utils/Thread.h>
+
+
+#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<IBinder>& 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<IGraphicBufferProducer> mProducer;
+
+ // This receives frames from the virtual display and makes them available
+ // as an external texture.
+ sp<GLConsumer> 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<IBinder> 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();
+}