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

Commit 4ae3cfd8 authored by Andy McFadden's avatar Andy McFadden Committed by Android Git Automerger
Browse files

am 21bde57f: Add frame dump output

* commit '21bde57f':
  Add frame dump output
parents c1b0056e 21bde57f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
	screenrecord.cpp \
	EglWindow.cpp \
	FrameOutput.cpp \
	TextRenderer.cpp \
	Overlay.cpp \
	Program.cpp
+56 −12
Original line number Diff line number Diff line
@@ -35,11 +35,16 @@ using namespace android;


status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) {
    status_t err = eglSetupContext();
    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);

@@ -56,6 +61,34 @@ status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) {
    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());
@@ -64,7 +97,7 @@ status_t EglWindow::makeCurrent() const {
    return NO_ERROR;
}

status_t EglWindow::eglSetupContext() {
status_t EglWindow::eglSetupContext(bool forPbuffer) {
    EGLBoolean result;

    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -82,17 +115,28 @@ status_t EglWindow::eglSetupContext() {
    ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion);

    EGLint numConfigs = 0;
    EGLint configAttribs[] = {
    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,
            // no alpha
            EGL_NONE
    };
    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, configAttribs, &mEglConfig, 1,
            &numConfigs);
    result = eglChooseConfig(mEglDisplay,
            forPbuffer ? pbufferConfigAttribs : windowConfigAttribs,
            &mEglConfig, 1, &numConfigs);
    if (result != EGL_TRUE) {
        ALOGE("eglChooseConfig error: %#x", eglGetError());
        return UNKNOWN_ERROR;
+4 −1
Original line number Diff line number Diff line
@@ -44,6 +44,9 @@ public:
    // 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; }
@@ -65,7 +68,7 @@ private:
    EglWindow& operator=(const EglWindow&);

    // Init display, create config and context.
    status_t eglSetupContext();
    status_t eglSetupContext(bool forPbuffer);
    void eglRelease();

    // Basic EGL goodies.
+208 −0
Original line number Diff line number Diff line
/*
 * 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 "ScreenRecord"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include "FrameOutput.h"

using namespace android;

static const bool kShowTiming = false;      // set to "true" for debugging
static const int kGlBytesPerPixel = 4;      // GL_RGBA
static const int kOutBytesPerPixel = 3;     // RGB only

inline void FrameOutput::setValueLE(uint8_t* buf, uint32_t value) {
    // Since we're running on an Android device, we're (almost) guaranteed
    // to be little-endian, and (almost) guaranteed that unaligned 32-bit
    // writes will work without any performance penalty... but do it
    // byte-by-byte anyway.
    buf[0] = (uint8_t) value;
    buf[1] = (uint8_t) (value >> 8);
    buf[2] = (uint8_t) (value >> 16);
    buf[3] = (uint8_t) (value >> 24);
}

status_t FrameOutput::createInputSurface(int width, int height,
        sp<IGraphicBufferProducer>* pBufferProducer) {
    status_t err;

    err = mEglWindow.createPbuffer(width, height);
    if (err != NO_ERROR) {
        return err;
    }
    mEglWindow.makeCurrent();

    glViewport(0, 0, width, height);
    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;
    }

    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);

    mPixelBuf = new uint8_t[width * height * kGlBytesPerPixel];

    *pBufferProducer = mBufferQueue;

    ALOGD("FrameOutput::createInputSurface OK");
    return NO_ERROR;
}

status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) {
    Mutex::Autolock _l(mMutex);
    ALOGV("copyFrame %ld\n", timeoutUsec);

    if (!mFrameAvailable) {
        nsecs_t timeoutNsec = (nsecs_t)timeoutUsec * 1000;
        int cc = mEventCond.waitRelative(mMutex, timeoutNsec);
        if (cc == -ETIMEDOUT) {
            ALOGV("cond wait timed out");
            return ETIMEDOUT;
        } else if (cc != 0) {
            ALOGW("cond wait returned error %d", cc);
            return cc;
        }
    }
    if (!mFrameAvailable) {
        // This happens when Ctrl-C is hit.  Apparently POSIX says that the
        // pthread wait call doesn't return EINTR, treating this instead as
        // an instance of a "spurious wakeup".  We didn't get a frame, so
        // we just treat it as a timeout.
        return ETIMEDOUT;
    }

    // A frame is available.  Clear the flag for the next round.
    mFrameAvailable = false;

    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();
    status_t err = mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0,
            width, height, true);
    if (err != NO_ERROR) {
        return err;
    }

    // GLES only guarantees that glReadPixels() will work with GL_RGBA, so we
    // need to get 4 bytes/pixel and reduce it.  Depending on the size of the
    // screen and the device capabilities, this can take a while.
    int64_t startWhenNsec, pixWhenNsec, endWhenNsec;
    if (kShowTiming) {
        startWhenNsec = systemTime(CLOCK_MONOTONIC);
    }
    GLenum glErr;
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, mPixelBuf);
    if ((glErr = glGetError()) != GL_NO_ERROR) {
        ALOGE("glReadPixels failed: %#x", glErr);
        return UNKNOWN_ERROR;
    }
    if (kShowTiming) {
        pixWhenNsec = systemTime(CLOCK_MONOTONIC);
    }
    reduceRgbaToRgb(mPixelBuf, width * height);
    if (kShowTiming) {
        endWhenNsec = systemTime(CLOCK_MONOTONIC);
        ALOGD("got pixels (get=%.3f ms, reduce=%.3fms)",
                (pixWhenNsec - startWhenNsec) / 1000000.0,
                (endWhenNsec - pixWhenNsec) / 1000000.0);
    }

    // Fill out the header.
    size_t headerLen = sizeof(uint32_t) * 5;
    size_t rgbDataLen = width * height * kOutBytesPerPixel;
    size_t packetLen = headerLen - sizeof(uint32_t) + rgbDataLen;
    uint8_t header[headerLen];
    setValueLE(&header[0], packetLen);
    setValueLE(&header[4], width);
    setValueLE(&header[8], height);
    setValueLE(&header[12], width * kOutBytesPerPixel);
    setValueLE(&header[16], HAL_PIXEL_FORMAT_RGB_888);

    // Currently using buffered I/O rather than writev().  Not expecting it
    // to make much of a difference, but it might be worth a test for larger
    // frame sizes.
    if (kShowTiming) {
        startWhenNsec = systemTime(CLOCK_MONOTONIC);
    }
    fwrite(header, 1, headerLen, fp);
    fwrite(mPixelBuf, 1, rgbDataLen, fp);
    fflush(fp);
    if (kShowTiming) {
        endWhenNsec = systemTime(CLOCK_MONOTONIC);
        ALOGD("wrote pixels (%.3f ms)",
                (endWhenNsec - startWhenNsec) / 1000000.0);
    }

    if (ferror(fp)) {
        // errno may not be useful; log it anyway
        ALOGE("write failed (errno=%d)", errno);
        return UNKNOWN_ERROR;
    }

    return NO_ERROR;
}

void FrameOutput::reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount) {
    // Convert RGBA to RGB.
    //
    // Unaligned 32-bit accesses are allowed on ARM, so we could do this
    // with 32-bit copies advancing at different rates (taking care at the
    // end to not go one byte over).
    const uint8_t* readPtr = buf;
    for (unsigned int i = 0; i < pixelCount; i++) {
        *buf++ = *readPtr++;
        *buf++ = *readPtr++;
        *buf++ = *readPtr++;
        readPtr++;
    }
}

// Callback; executes on arbitrary thread.
void FrameOutput::onFrameAvailable() {
    Mutex::Autolock _l(mMutex);
    mFrameAvailable = true;
    mEventCond.signal();
}
+101 −0
Original line number Diff line number Diff line
/*
 * 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 SCREENRECORD_FRAMEOUTPUT_H
#define SCREENRECORD_FRAMEOUTPUT_H

#include "Program.h"
#include "EglWindow.h"

#include <gui/BufferQueue.h>
#include <gui/GLConsumer.h>

namespace android {

/*
 * Support for "frames" output format.
 */
class FrameOutput : public GLConsumer::FrameAvailableListener {
public:
    FrameOutput() : mFrameAvailable(false),
        mExtTextureName(0),
        mPixelBuf(NULL)
        {}
    virtual ~FrameOutput() {
        delete[] mPixelBuf;
    }

    // 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 createInputSurface(int width, int height,
            sp<IGraphicBufferProducer>* pBufferProducer);

    // Copy one from input to output.  If no frame is available, this will wait up to the
    // specified number of microseconds.
    //
    // Returns ETIMEDOUT if the timeout expired before we found a frame.
    status_t copyFrame(FILE* fp, long timeoutUsec);

    // Prepare to copy frames.  Makes the EGL context used by this object current.
    void prepareToCopy() {
        mEglWindow.makeCurrent();
    }

private:
    FrameOutput(const FrameOutput&);
    FrameOutput& operator=(const FrameOutput&);

    // (overrides GLConsumer::FrameAvailableListener method)
    virtual void onFrameAvailable();

    // Reduces RGBA to RGB, in place.
    static void reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount);

    // Put a 32-bit value into a buffer, in little-endian byte order.
    static void setValueLE(uint8_t* buf, uint32_t value);

    // Used to wait for the FrameAvailableListener callback.
    Mutex mMutex;
    Condition mEventCond;

    // Set by the FrameAvailableListener callback.
    bool mFrameAvailable;

    // Our queue.  The producer side is passed to the virtual display, the
    // consumer side feeds into our GLConsumer.
    sp<BufferQueue> mBufferQueue;

    // 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 buffer.
    uint8_t* mPixelBuf;
};

}; // namespace android

#endif /*SCREENRECORD_FRAMEOUTPUT_H*/
Loading