diff options
Diffstat (limited to 'cmds/screenrecord/Overlay.cpp')
-rw-r--r-- | cmds/screenrecord/Overlay.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/cmds/screenrecord/Overlay.cpp b/cmds/screenrecord/Overlay.cpp new file mode 100644 index 0000000..f2d8b59 --- /dev/null +++ b/cmds/screenrecord/Overlay.cpp @@ -0,0 +1,399 @@ +/* + * 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 "ScreenRecord" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <gui/BufferQueue.h> +#include <gui/GraphicBufferAlloc.h> +#include <gui/Surface.h> +#include <cutils/properties.h> +#include <utils/misc.h> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <stdlib.h> + +#include "screenrecord.h" +#include "Overlay.h" +#include "TextRenderer.h" + +using namespace android; + +// System properties to look up and display on the info screen. +const char* Overlay::kPropertyNames[] = { + "ro.build.description", + // includes ro.build.id, ro.build.product, ro.build.tags, ro.build.type, + // and ro.build.version.release + "ro.product.manufacturer", + "ro.product.model", + "ro.board.platform", + "ro.revision", + "dalvik.vm.heapgrowthlimit", + "dalvik.vm.heapsize", + "persist.sys.dalvik.vm.lib", + //"ro.product.cpu.abi", + //"ro.bootloader", + //"this-never-appears!", +}; + + +status_t Overlay::start(const sp<IGraphicBufferProducer>& outputSurface, + sp<IGraphicBufferProducer>* pBufferProducer) { + ALOGV("Overlay::start"); + mOutputSurface = outputSurface; + + // Grab the current monotonic time and the current wall-clock time so we + // can map one to the other. This allows the overlay counter to advance + // by the exact delay between frames, but if the wall clock gets adjusted + // we won't track it, which means we'll gradually go out of sync with the + // times in logcat. + mStartMonotonicNsecs = systemTime(CLOCK_MONOTONIC); + mStartRealtimeNsecs = systemTime(CLOCK_REALTIME); + + // Start the thread. Traffic begins immediately. + run("overlay"); + + Mutex::Autolock _l(mMutex); + mState = INIT; + while (mState == INIT) { + mStartCond.wait(mMutex); + } + + if (mThreadResult != NO_ERROR) { + ALOGE("Failed to start overlay thread: err=%d", mThreadResult); + return mThreadResult; + } + assert(mState == READY); + + ALOGV("Overlay::start successful"); + *pBufferProducer = mBufferQueue; + return NO_ERROR; +} + +status_t Overlay::stop() { + ALOGV("Overlay::stop"); + Mutex::Autolock _l(mMutex); + mState = STOPPING; + mEventCond.signal(); + return NO_ERROR; +} + +bool Overlay::threadLoop() { + Mutex::Autolock _l(mMutex); + + mThreadResult = setup_l(); + + if (mThreadResult != NO_ERROR) { + ALOGW("Aborting overlay thread"); + mState = STOPPED; + release_l(); + mStartCond.broadcast(); + return false; + } + + ALOGV("Overlay thread running"); + mState = RUNNING; + mStartCond.broadcast(); + + while (mState == RUNNING) { + mEventCond.wait(mMutex); + if (mFrameAvailable) { + ALOGV("Awake, frame available"); + processFrame_l(); + mFrameAvailable = false; + } else { + ALOGV("Awake, frame not available"); + } + } + + ALOGV("Overlay thread stopping"); + release_l(); + mState = STOPPED; + return false; // stop +} + +status_t Overlay::setup_l() { + status_t err; + + err = mEglWindow.createWindow(mOutputSurface); + if (err != NO_ERROR) { + return err; + } + mEglWindow.makeCurrent(); + + int width = mEglWindow.getWidth(); + int height = mEglWindow.getHeight(); + + glViewport(0, 0, width, height); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + // Shaders for rendering from different types of textures. + err = mTexProgram.setup(Program::PROGRAM_TEXTURE_2D); + if (err != NO_ERROR) { + return err; + } + err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE); + if (err != NO_ERROR) { + return err; + } + + err = mTextRenderer.loadIntoTexture(); + if (err != NO_ERROR) { + return err; + } + mTextRenderer.setScreenSize(width, height); + + // Input side (buffers from virtual display). + glGenTextures(1, &mExtTextureName); + if (mExtTextureName == 0) { + ALOGE("glGenTextures failed: %#x", glGetError()); + return UNKNOWN_ERROR; + } + + mBufferQueue = new BufferQueue(/*new GraphicBufferAlloc()*/); + mGlConsumer = new GLConsumer(mBufferQueue, mExtTextureName, + GL_TEXTURE_EXTERNAL_OES); + mGlConsumer->setName(String8("virtual display")); + mGlConsumer->setDefaultBufferSize(width, height); + mGlConsumer->setDefaultMaxBufferCount(5); + mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE); + + mGlConsumer->setFrameAvailableListener(this); + + return NO_ERROR; +} + + +void Overlay::release_l() { + ALOGV("Overlay::release_l"); + mOutputSurface.clear(); + mGlConsumer.clear(); + mBufferQueue.clear(); + + mTexProgram.release(); + mExtTexProgram.release(); + mEglWindow.release(); +} + +void Overlay::processFrame_l() { + float texMatrix[16]; + + mGlConsumer->updateTexImage(); + mGlConsumer->getTransformMatrix(texMatrix); + nsecs_t monotonicNsec = mGlConsumer->getTimestamp(); + nsecs_t frameNumber = mGlConsumer->getFrameNumber(); + int64_t droppedFrames = 0; + + if (mLastFrameNumber > 0) { + mTotalDroppedFrames += size_t(frameNumber - mLastFrameNumber) - 1; + } + mLastFrameNumber = frameNumber; + + mTextRenderer.setProportionalScale(35); + + if (false) { // DEBUG - full blue background + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + } + + int width = mEglWindow.getWidth(); + int height = mEglWindow.getHeight(); + if (false) { // DEBUG - draw inset + mExtTexProgram.blit(mExtTextureName, texMatrix, + 100, 100, width-200, height-200); + } else { + mExtTexProgram.blit(mExtTextureName, texMatrix, + 0, 0, width, height); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + if (false) { // DEBUG - show entire font bitmap + mTexProgram.blit(mTextRenderer.getTextureName(), Program::kIdentity, + 100, 100, width-200, height-200); + } + + char textBuf[64]; + getTimeString_l(monotonicNsec, textBuf, sizeof(textBuf)); + String8 timeStr(String8::format("%s f=%lld (%zd)", + textBuf, frameNumber, mTotalDroppedFrames)); + mTextRenderer.drawString(mTexProgram, Program::kIdentity, 0, 0, timeStr); + + glDisable(GL_BLEND); + + if (false) { // DEBUG - add red rectangle in lower-left corner + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 200, 200); + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + } + + mEglWindow.presentationTime(monotonicNsec); + mEglWindow.swapBuffers(); +} + +void Overlay::getTimeString_l(nsecs_t monotonicNsec, char* buf, size_t bufLen) { + //const char* format = "%m-%d %T"; // matches log output + const char* format = "%T"; + struct tm tm; + + // localtime/strftime is not the fastest way to do this, but a trivial + // benchmark suggests that the cost is negligible. + int64_t realTime = mStartRealtimeNsecs + + (monotonicNsec - mStartMonotonicNsecs); + time_t secs = (time_t) (realTime / 1000000000); + localtime_r(&secs, &tm); + strftime(buf, bufLen, format, &tm); + + int32_t msec = (int32_t) ((realTime % 1000000000) / 1000000); + char tmpBuf[5]; + snprintf(tmpBuf, sizeof(tmpBuf), ".%03d", msec); + strlcat(buf, tmpBuf, bufLen); +} + +// Callback; executes on arbitrary thread. +void Overlay::onFrameAvailable() { + ALOGV("Overlay::onFrameAvailable"); + Mutex::Autolock _l(mMutex); + mFrameAvailable = true; + mEventCond.signal(); +} + + +/*static*/ status_t Overlay::drawInfoPage( + const sp<IGraphicBufferProducer>& outputSurface) { + status_t err; + + EglWindow window; + err = window.createWindow(outputSurface); + if (err != NO_ERROR) { + return err; + } + window.makeCurrent(); + + int width = window.getWidth(); + int height = window.getHeight(); + glViewport(0, 0, width, height); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + // Shaders for rendering. + Program texProgram; + err = texProgram.setup(Program::PROGRAM_TEXTURE_2D); + if (err != NO_ERROR) { + return err; + } + TextRenderer textRenderer; + err = textRenderer.loadIntoTexture(); + if (err != NO_ERROR) { + return err; + } + textRenderer.setScreenSize(width, height); + + doDrawInfoPage(window, texProgram, textRenderer); + + // Destroy the surface. This causes a disconnect. + texProgram.release(); + window.release(); + + return NO_ERROR; +} + +/*static*/ void Overlay::doDrawInfoPage(const EglWindow& window, + const Program& texProgram, TextRenderer& textRenderer) { + const nsecs_t holdTime = 250000000LL; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + int width = window.getWidth(); + int height = window.getHeight(); + + // Draw a thin border around the screen. Some players, e.g. browser + // plugins, make it hard to see where the edges are when the device + // is using a black background, so this gives the viewer a frame of + // reference. + // + // This is a clumsy way to do it, but we're only doing it for one frame, + // and it's easier than actually drawing lines. + const int lineWidth = 4; + glEnable(GL_SCISSOR_TEST); + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); + glScissor(0, 0, width, lineWidth); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(0, height - lineWidth, width, lineWidth); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(0, 0, lineWidth, height); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(width - lineWidth, 0, lineWidth, height); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + + //glEnable(GL_BLEND); + //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + textRenderer.setProportionalScale(30); + + float xpos = 0; + float ypos = 0; + ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, + String8::format("Android screenrecord v%d.%d", + kVersionMajor, kVersionMinor)); + + // Show date/time + time_t now = time(0); + struct tm tm; + localtime_r(&now, &tm); + char timeBuf[64]; + strftime(timeBuf, sizeof(timeBuf), "%a, %d %b %Y %T %z", &tm); + String8 header("Started "); + header += timeBuf; + ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, header); + ypos += 8 * textRenderer.getScale(); // slight padding + + // Show selected system property values + for (int i = 0; i < NELEM(kPropertyNames); i++) { + char valueBuf[PROPERTY_VALUE_MAX]; + + property_get(kPropertyNames[i], valueBuf, ""); + if (valueBuf[0] == '\0') { + continue; + } + String8 str(String8::format("%s: [%s]", kPropertyNames[i], valueBuf)); + ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, str); + } + ypos += 8 * textRenderer.getScale(); // slight padding + + // Show GL info + String8 glStr("OpenGL: "); + glStr += (char*) glGetString(GL_VENDOR); + glStr += " / "; + glStr += (char*) glGetString(GL_RENDERER); + glStr += ", "; + glStr += (char*) glGetString(GL_VERSION); + ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, glStr); + + //glDisable(GL_BLEND); + + // Set a presentation time slightly in the past. This will cause the + // player to hold the frame on screen. + window.presentationTime(systemTime(CLOCK_MONOTONIC) - holdTime); + window.swapBuffers(); +} |