diff options
Diffstat (limited to 'cmds/screenrecord/FrameOutput.cpp')
-rw-r--r-- | cmds/screenrecord/FrameOutput.cpp | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/cmds/screenrecord/FrameOutput.cpp b/cmds/screenrecord/FrameOutput.cpp new file mode 100644 index 0000000..06b1f70 --- /dev/null +++ b/cmds/screenrecord/FrameOutput.cpp @@ -0,0 +1,210 @@ +/* + * 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; + } + + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + mGlConsumer = new GLConsumer(consumer, 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 = producer; + + 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(); +} |