summaryrefslogtreecommitdiffstats
path: root/cmds/screenrecord/FrameOutput.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cmds/screenrecord/FrameOutput.cpp')
-rw-r--r--cmds/screenrecord/FrameOutput.cpp208
1 files changed, 208 insertions, 0 deletions
diff --git a/cmds/screenrecord/FrameOutput.cpp b/cmds/screenrecord/FrameOutput.cpp
new file mode 100644
index 0000000..b5cf2f9
--- /dev/null
+++ b/cmds/screenrecord/FrameOutput.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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();
+}