Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aaa3f358 authored by Andy McFadden's avatar Andy McFadden
Browse files

Add "--bugreport" option to screenrecord

The --bugreport option adds two visible features: (1) a timestamp
overlay that (mostly) matches logcat, making it easier to match what
appears in the video with what's in the log, and (2) an "info page"
at the start of the video that shows the system configuration.

Enabling this option adds an additional composition step,
increasing the overhead of screenrecord.  Depending on the device
and circumstances, this may be unnoticeable or very pronounced.
If --bugreport is not enabled, the overhead of screenrecord is
unchanged.

We also now track device orientation changes.  This is currently
detected by polling surfaceflinger, which is suboptimal.  As a
result, we detect the rotation too late, and get a weird mixed
frame before the start of the animation for 90-degree changes.

Also, allow the bit rate to be specified as e.g. "4M" for 4Mbps.

Also, --rotate is now deprecated.

Bug 11220305
Bug 11136964

(cherry pick from Ibb94b81d2f73547b95d7a47e027da75fab187a4f)

Change-Id: I829a91aaca5ab82a07c14172d9e188ec38f14e57
parent e2d617f5
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -18,10 +18,14 @@ include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
	screenrecord.cpp \
	EglWindow.cpp \
	TextRenderer.cpp \
	Overlay.cpp \
	Program.cpp

LOCAL_SHARED_LIBRARIES := \
	libstagefright libmedia libutils libbinder libstagefright_foundation \
	libjpeg libgui libcutils liblog
	libjpeg libgui libcutils liblog libEGL libGLESv2

LOCAL_C_INCLUDES := \
	frameworks/av/media/libstagefright \
+146 −0
Original line number Diff line number Diff line
/*
 * 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>

#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) {
    status_t err = eglSetupContext();
    if (err != NO_ERROR) {
        return err;
    }

    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::makeCurrent() const {
    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
        ALOGE("eglMakeCurrent failed: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}

status_t EglWindow::eglSetupContext() {
    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 configAttribs[] = {
        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_NONE
    };
    result = eglChooseConfig(mEglDisplay, configAttribs, &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);
}
+84 −0
Original line number Diff line number Diff line
/*
 * 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);

    // 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();
    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*/
+6571 −0

File added.

Preview size limit exceeded, changes collapsed.

+399 −0
Original line number Diff line number Diff line
/*
 * 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();
}
Loading