Skip to content
Snippets Groups Projects
Commit 77754523 authored by Steve Kondik's avatar Steve Kondik
Browse files

vnc: Simplify everything

Previous design was using extra threads needlessly and had some
hacky data sharing going on. Slim this down to just two classes
and utilize threads appropriately.
parent ef4e8650
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,6 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
src/InputDevice.cpp \
src/VirtualDisplay.cpp \
src/VNCFlinger.cpp \
src/main.cpp
......
- 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.
......@@ -12,14 +12,26 @@
#include "InputDevice.h"
#include "VNCFlinger.h"
using namespace android;
Mutex VNCFlinger::sUpdateMutex;
using namespace android;
status_t VNCFlinger::start() {
Mutex::Autolock _l(mMutex);
sp<ProcessState> self = ProcessState::self();
self->startThreadPool();
status_t err = NO_ERROR;
mMainDpy = SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain);
err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mMainDpyInfo);
if (err != NO_ERROR) {
ALOGE("Failed to get display characteristics\n");
return err;
}
mHeight = mMainDpyInfo.h;
mWidth = mMainDpyInfo.w;
status_t err = setup_l();
err = createVNCServer();
if (err != NO_ERROR) {
ALOGE("Failed to start VNCFlinger: err=%d", err);
return err;
......@@ -29,33 +41,77 @@ status_t VNCFlinger::start() {
rfbRunEventLoop(mVNCScreen, -1, true);
mCondition.wait(mMutex);
eventLoop();
release_l();
return NO_ERROR;
}
status_t VNCFlinger::setup_l() {
void VNCFlinger::eventLoop() {
mRunning = true;
status_t err = NO_ERROR;
Mutex::Autolock _l(mEventMutex);
while (mRunning) {
mEventCond.wait(mEventMutex);
mMainDpy = SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain);
err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mMainDpyInfo);
if (err != NO_ERROR) {
ALOGE("Unable to get display characteristics\n");
return err;
}
// spurious wakeup? never.
if (mClientCount == 0) {
continue;
}
bool rotated = VirtualDisplay::isDeviceRotated(mMainDpyInfo.orientation);
if (mWidth == 0) {
mWidth = rotated ? mMainDpyInfo.h : mMainDpyInfo.w;
}
if (mHeight == 0) {
mHeight = rotated ? mMainDpyInfo.w : mMainDpyInfo.h;
// this is a new client, so fire everything up
status_t err = createVirtualDisplay();
if (err != NO_ERROR) {
ALOGE("Failed to create virtual display: err=%d", err);
}
// loop while clients are connected and process frames
// on the main thread when signalled
while (mClientCount > 0) {
mEventCond.wait(mEventMutex);
if (mFrameAvailable) {
processFrame();
mFrameAvailable = false;
}
}
destroyVirtualDisplay();
}
}
status_t VNCFlinger::createVirtualDisplay() {
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&mProducer, &consumer);
mCpuConsumer = new CpuConsumer(consumer, 1);
mCpuConsumer->setName(String8("vds-to-cpu"));
mCpuConsumer->setDefaultBufferSize(mWidth, mHeight);
mProducer->setMaxDequeuedBufferCount(4);
mListener = new FrameListener(this);
mCpuConsumer->setFrameAvailableListener(mListener);
ALOGD("Display dimensions: %dx%d rotated=%d", mWidth, mHeight, rotated);
mDpy = SurfaceComposerClient::createDisplay(
String8("VNC-VirtualDisplay"), false /*secure*/);
SurfaceComposerClient::openGlobalTransaction();
SurfaceComposerClient::setDisplaySurface(mDpy, mProducer);
//setDisplayProjection(mDpy, mainDpyInfo);
SurfaceComposerClient::setDisplayLayerStack(mDpy, 0); // default stack
SurfaceComposerClient::closeGlobalTransaction();
ALOGV("Virtual display created");
return NO_ERROR;
}
status_t VNCFlinger::destroyVirtualDisplay() {
mCpuConsumer.clear();
mProducer.clear();
SurfaceComposerClient::destroyDisplay(mDpy);
return NO_ERROR;
}
status_t VNCFlinger::createVNCServer() {
status_t err = NO_ERROR;
rfbLog = VNCFlinger::rfbLogger;
rfbErr = VNCFlinger::rfbLogger;
......@@ -88,32 +144,27 @@ status_t VNCFlinger::setup_l() {
/* Mark as dirty since we haven't sent any updates at all yet. */
rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
mVirtualDisplay = new VirtualDisplay(mVNCScreen, &sUpdateMutex);
return err;
}
void VNCFlinger::release_l() {
mVirtualDisplay.clear();
status_t VNCFlinger::stop() {
Mutex::Autolock _L(mEventMutex);
ALOGD("VNCFlinger released");
}
mClientCount = 0;
mRunning = false;
status_t VNCFlinger::stop() {
Mutex::Autolock _l(mMutex);
mCondition.signal();
mEventCond.signal();
return NO_ERROR;
}
size_t VNCFlinger::addClient() {
Mutex::Autolock _l(mMutex);
Mutex::Autolock _l(mEventMutex);
if (mClientCount == 0) {
mClientCount++;
InputDevice::start(mWidth, mHeight);
mVirtualDisplay->start(mMainDpyInfo);
mEventCond.signal();
}
mClientCount++;
ALOGI("Client connected (%zu)", mClientCount);
......@@ -121,12 +172,12 @@ size_t VNCFlinger::addClient() {
}
size_t VNCFlinger::removeClient() {
Mutex::Autolock _l(mMutex);
Mutex::Autolock _l(mEventMutex);
if (mClientCount > 0) {
mClientCount--;
if (mClientCount == 0) {
mVirtualDisplay->stop();
InputDevice::stop();
mEventCond.signal();
}
}
......@@ -150,23 +201,18 @@ enum rfbNewClientAction VNCFlinger::onNewClient(rfbClientPtr cl) {
return RFB_CLIENT_ACCEPT;
}
void VNCFlinger::onFrameStart(rfbClientPtr /* cl */) {
sUpdateMutex.lock();
void VNCFlinger::onFrameStart(rfbClientPtr cl) {
VNCFlinger *vf = (VNCFlinger *)cl->screen->screenData;
vf->mUpdateMutex.lock();
ALOGV("frame start");
}
void VNCFlinger::onFrameDone(rfbClientPtr /* cl */, int status) {
sUpdateMutex.unlock();
void VNCFlinger::onFrameDone(rfbClientPtr cl, int status) {
VNCFlinger *vf = (VNCFlinger *)cl->screen->screenData;
vf->mUpdateMutex.unlock();
ALOGV("frame done! %d", status);
}
void VNCFlinger::markFrame(void* frame, size_t stride) {
Mutex::Autolock _l(sUpdateMutex);
mVNCScreen->frameBuffer = (char *)frame;
mVNCScreen->paddedWidthInBytes = stride * 4;
rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
}
void VNCFlinger::rfbLogger(const char *format, ...) {
va_list args;
char buf[256];
......@@ -176,3 +222,51 @@ void VNCFlinger::rfbLogger(const char *format, ...) {
ALOGI("%s", buf);
va_end(args);
}
void VNCFlinger::FrameListener::onFrameAvailable(const BufferItem& item) {
Mutex::Autolock _l(mVNC->mEventMutex);
mVNC->mFrameAvailable = true;
mVNC->mEventCond.signal();
ALOGV("onFrameAvailable: mTimestamp=%ld mFrameNumber=%ld",
item.mTimestamp, item.mFrameNumber);
}
void VNCFlinger::processFrame() {
ALOGV("processFrame\n");
// Take the update mutex. This ensures that we don't dequeue
// a new buffer and blow away the one being sent to a client.
// The BufferQueue is self-regulating and will drop frames
// automatically for us.
Mutex::Autolock _l(mUpdateMutex);
CpuConsumer::LockedBuffer imgBuffer;
status_t res = mCpuConsumer->lockNextBuffer(&imgBuffer);
if (res != OK) {
ALOGE("Failed to lock next buffer: %s (%d)", strerror(-res), res);
return;
}
ALOGV("processFrame: ptr: %p format: %x (%dx%d, stride=%d)",
imgBuffer.data, imgBuffer.format, imgBuffer.width,
imgBuffer.height, imgBuffer.stride);
void* vncbuf = mVNCScreen->frameBuffer;
void* imgbuf = imgBuffer.data;
// Copy the frame to the server's buffer
if (imgBuffer.stride > mWidth) {
// Image has larger stride, so we need to copy row by row
for (size_t y = 0; y < mHeight; y++) {
memcpy(vncbuf, imgbuf, mWidth * 4);
vncbuf = (void *)((char *)vncbuf + mWidth * 4);
imgbuf = (void *)((char *)imgbuf + imgBuffer.stride * 4);
}
} else {
memcpy(vncbuf, imgbuf, mWidth * mHeight * 4);
}
rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
mCpuConsumer->unlockBuffer(imgBuffer);
}
#ifndef VNCFLINGER_H
#define VNCFLINGER_H
#include "VirtualDisplay.h"
#include <gui/CpuConsumer.h>
#include <ui/DisplayInfo.h>
#include "rfb/rfb.h"
......@@ -27,39 +26,67 @@ public:
virtual size_t addClient();
virtual size_t removeClient();
virtual void markFrame(void* frame, size_t stride);
private:
virtual status_t setup_l();
virtual void release_l();
class FrameListener : public CpuConsumer::FrameAvailableListener {
public:
FrameListener(VNCFlinger *vnc) : mVNC(vnc) {}
virtual void onFrameAvailable(const BufferItem& item);
private:
FrameListener(FrameListener&) {}
VNCFlinger *mVNC;
};
virtual void eventLoop();
virtual status_t createVirtualDisplay();
virtual status_t destroyVirtualDisplay();
virtual status_t createVNCServer();
virtual void processFrame();
// vncserver callbacks
static ClientGoneHookPtr onClientGone(rfbClientPtr cl);
static enum rfbNewClientAction onNewClient(rfbClientPtr cl);
static void onFrameStart(rfbClientPtr cl);
static void onFrameDone(rfbClientPtr cl, int result);
static void rfbLogger(const char *format, ...);
Condition mCondition;
bool mRunning;
bool mFrameAvailable;
Mutex mEventMutex;
Mutex mUpdateMutex;
Condition mEventCond;
rfbScreenInfoPtr mVNCScreen;
uint8_t *mVNCBuf;
uint32_t mWidth, mHeight;
bool mRotate;
sp<IBinder> mMainDpy;
DisplayInfo mMainDpyInfo;
Mutex mMutex;
static Mutex sUpdateMutex;
sp<VirtualDisplay> mVirtualDisplay;
int mArgc;
char **mArgv;
size_t mClientCount;
sp<FrameListener> mListener;
// Producer side of queue, passed into the virtual display.
sp<IGraphicBufferProducer> mProducer;
// This receives frames from the virtual display and makes them available
sp<CpuConsumer> mCpuConsumer;
// The virtual display instance
sp<IBinder> mDpy;
};
};
......
/*
* 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 <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.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) {
ALOGV("Rotated content area is %ux%u at offset x=%d y=%d\n",
outHeight, outWidth, offY, offX);
} else {
ALOGV("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) {
Mutex::Autolock _l(mMutex);
ALOGV("Orientation: %d", mainDpyInfo.orientation);
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");
processFrame_l();
}
ALOGV("VDS thread stopping");
release_l();
mState = STOPPED;
return false; // stop
}
status_t VirtualDisplay::setup_l() {
status_t err;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&mProducer, &consumer);
mCpuConsumer = new CpuConsumer(consumer, 1);
mCpuConsumer->setName(String8("vds-to-cpu"));
mCpuConsumer->setDefaultBufferSize(mWidth, mHeight);
mProducer->setMaxDequeuedBufferCount(4);
mCpuConsumer->setFrameAvailableListener(this);
ALOGD("VirtualDisplay::setup_l OK");
return NO_ERROR;
}
void* VirtualDisplay::processFrame_l() {
ALOGD("processFrame_l\n");
mUpdateMutex->lock();
CpuConsumer::LockedBuffer imgBuffer;
status_t res = mCpuConsumer->lockNextBuffer(&imgBuffer);
if (res != OK) {
ALOGE("Failed to lock next buffer: %s (%d)", strerror(-res), res);
return nullptr;
}
ALOGV("imgBuffer ptr: %p format: %x (%dx%d, stride=%d)", imgBuffer.data, imgBuffer.format, imgBuffer.width, imgBuffer.height, imgBuffer.stride);
void* vncbuf = mVNCScreen->frameBuffer;
void* imgbuf = imgBuffer.data;
for (size_t y = 0; y < mHeight; y++) {
memcpy(vncbuf, imgbuf, mWidth * 4);
vncbuf = (void *)((char *)vncbuf + mWidth * 4);
imgbuf = (void *)((char *)imgbuf + imgBuffer.stride * 4);
}
ALOGD("buf copied");
mVNCScreen->frameBuffer = (char *)imgBuffer.data;
mVNCScreen->paddedWidthInBytes = imgBuffer.stride * 4;
rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
mCpuConsumer->unlockBuffer(imgBuffer);
mUpdateMutex->unlock();
return nullptr;
}
void VirtualDisplay::release_l() {
ALOGD("release_l");
mCpuConsumer.clear();
mProducer.clear();
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);
}
/*
* 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 <gui/BufferQueue.h>
#include <gui/CpuConsumer.h>
#include <gui/IGraphicBufferProducer.h>
#include <ui/DisplayInfo.h>
#include <utils/Thread.h>
#include <rfb/rfb.h>
#define NUM_PBO 2
namespace android {
/*
* Support for "frames" output format.
*/
class VirtualDisplay : public CpuConsumer::FrameAvailableListener, Thread {
public:
VirtualDisplay(rfbScreenInfoPtr vncScreen, Mutex *updateMutex) : Thread(false),
mVNCScreen(vncScreen),
mUpdateMutex(updateMutex),
mThreadResult(UNKNOWN_ERROR),
mState(UNINITIALIZED)
{}
// 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.
virtual status_t start(const DisplayInfo& mainDpyInfo);
virtual 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();
rfbScreenInfoPtr mVNCScreen;
Mutex *mUpdateMutex;
uint32_t mHeight, mWidth;
bool mRotate;
// 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
sp<CpuConsumer> mCpuConsumer;
sp<IBinder> mDpy;
};
}; // namespace android
#endif /* VDS_H */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment