FrameOutput.cpp revision 21bde57f0099fed5cca78d9357571dc015a63227
1/*
2 * Copyright 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "ScreenRecord"
18//#define LOG_NDEBUG 0
19#include <utils/Log.h>
20
21#include <GLES2/gl2.h>
22#include <GLES2/gl2ext.h>
23
24#include "FrameOutput.h"
25
26using namespace android;
27
28static const bool kShowTiming = false;      // set to "true" for debugging
29static const int kGlBytesPerPixel = 4;      // GL_RGBA
30static const int kOutBytesPerPixel = 3;     // RGB only
31
32inline void FrameOutput::setValueLE(uint8_t* buf, uint32_t value) {
33    // Since we're running on an Android device, we're (almost) guaranteed
34    // to be little-endian, and (almost) guaranteed that unaligned 32-bit
35    // writes will work without any performance penalty... but do it
36    // byte-by-byte anyway.
37    buf[0] = (uint8_t) value;
38    buf[1] = (uint8_t) (value >> 8);
39    buf[2] = (uint8_t) (value >> 16);
40    buf[3] = (uint8_t) (value >> 24);
41}
42
43status_t FrameOutput::createInputSurface(int width, int height,
44        sp<IGraphicBufferProducer>* pBufferProducer) {
45    status_t err;
46
47    err = mEglWindow.createPbuffer(width, height);
48    if (err != NO_ERROR) {
49        return err;
50    }
51    mEglWindow.makeCurrent();
52
53    glViewport(0, 0, width, height);
54    glDisable(GL_DEPTH_TEST);
55    glDisable(GL_CULL_FACE);
56
57    // Shader for rendering the external texture.
58    err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE);
59    if (err != NO_ERROR) {
60        return err;
61    }
62
63    // Input side (buffers from virtual display).
64    glGenTextures(1, &mExtTextureName);
65    if (mExtTextureName == 0) {
66        ALOGE("glGenTextures failed: %#x", glGetError());
67        return UNKNOWN_ERROR;
68    }
69
70    mBufferQueue = new BufferQueue(/*new GraphicBufferAlloc()*/);
71    mGlConsumer = new GLConsumer(mBufferQueue, mExtTextureName,
72                GL_TEXTURE_EXTERNAL_OES);
73    mGlConsumer->setName(String8("virtual display"));
74    mGlConsumer->setDefaultBufferSize(width, height);
75    mGlConsumer->setDefaultMaxBufferCount(5);
76    mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
77
78    mGlConsumer->setFrameAvailableListener(this);
79
80    mPixelBuf = new uint8_t[width * height * kGlBytesPerPixel];
81
82    *pBufferProducer = mBufferQueue;
83
84    ALOGD("FrameOutput::createInputSurface OK");
85    return NO_ERROR;
86}
87
88status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) {
89    Mutex::Autolock _l(mMutex);
90    ALOGV("copyFrame %ld\n", timeoutUsec);
91
92    if (!mFrameAvailable) {
93        nsecs_t timeoutNsec = (nsecs_t)timeoutUsec * 1000;
94        int cc = mEventCond.waitRelative(mMutex, timeoutNsec);
95        if (cc == -ETIMEDOUT) {
96            ALOGV("cond wait timed out");
97            return ETIMEDOUT;
98        } else if (cc != 0) {
99            ALOGW("cond wait returned error %d", cc);
100            return cc;
101        }
102    }
103    if (!mFrameAvailable) {
104        // This happens when Ctrl-C is hit.  Apparently POSIX says that the
105        // pthread wait call doesn't return EINTR, treating this instead as
106        // an instance of a "spurious wakeup".  We didn't get a frame, so
107        // we just treat it as a timeout.
108        return ETIMEDOUT;
109    }
110
111    // A frame is available.  Clear the flag for the next round.
112    mFrameAvailable = false;
113
114    float texMatrix[16];
115    mGlConsumer->updateTexImage();
116    mGlConsumer->getTransformMatrix(texMatrix);
117
118    // The data is in an external texture, so we need to render it to the
119    // pbuffer to get access to RGB pixel data.  We also want to flip it
120    // upside-down for easy conversion to a bitmap.
121    int width = mEglWindow.getWidth();
122    int height = mEglWindow.getHeight();
123    status_t err = mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0,
124            width, height, true);
125    if (err != NO_ERROR) {
126        return err;
127    }
128
129    // GLES only guarantees that glReadPixels() will work with GL_RGBA, so we
130    // need to get 4 bytes/pixel and reduce it.  Depending on the size of the
131    // screen and the device capabilities, this can take a while.
132    int64_t startWhenNsec, pixWhenNsec, endWhenNsec;
133    if (kShowTiming) {
134        startWhenNsec = systemTime(CLOCK_MONOTONIC);
135    }
136    GLenum glErr;
137    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, mPixelBuf);
138    if ((glErr = glGetError()) != GL_NO_ERROR) {
139        ALOGE("glReadPixels failed: %#x", glErr);
140        return UNKNOWN_ERROR;
141    }
142    if (kShowTiming) {
143        pixWhenNsec = systemTime(CLOCK_MONOTONIC);
144    }
145    reduceRgbaToRgb(mPixelBuf, width * height);
146    if (kShowTiming) {
147        endWhenNsec = systemTime(CLOCK_MONOTONIC);
148        ALOGD("got pixels (get=%.3f ms, reduce=%.3fms)",
149                (pixWhenNsec - startWhenNsec) / 1000000.0,
150                (endWhenNsec - pixWhenNsec) / 1000000.0);
151    }
152
153    // Fill out the header.
154    size_t headerLen = sizeof(uint32_t) * 5;
155    size_t rgbDataLen = width * height * kOutBytesPerPixel;
156    size_t packetLen = headerLen - sizeof(uint32_t) + rgbDataLen;
157    uint8_t header[headerLen];
158    setValueLE(&header[0], packetLen);
159    setValueLE(&header[4], width);
160    setValueLE(&header[8], height);
161    setValueLE(&header[12], width * kOutBytesPerPixel);
162    setValueLE(&header[16], HAL_PIXEL_FORMAT_RGB_888);
163
164    // Currently using buffered I/O rather than writev().  Not expecting it
165    // to make much of a difference, but it might be worth a test for larger
166    // frame sizes.
167    if (kShowTiming) {
168        startWhenNsec = systemTime(CLOCK_MONOTONIC);
169    }
170    fwrite(header, 1, headerLen, fp);
171    fwrite(mPixelBuf, 1, rgbDataLen, fp);
172    fflush(fp);
173    if (kShowTiming) {
174        endWhenNsec = systemTime(CLOCK_MONOTONIC);
175        ALOGD("wrote pixels (%.3f ms)",
176                (endWhenNsec - startWhenNsec) / 1000000.0);
177    }
178
179    if (ferror(fp)) {
180        // errno may not be useful; log it anyway
181        ALOGE("write failed (errno=%d)", errno);
182        return UNKNOWN_ERROR;
183    }
184
185    return NO_ERROR;
186}
187
188void FrameOutput::reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount) {
189    // Convert RGBA to RGB.
190    //
191    // Unaligned 32-bit accesses are allowed on ARM, so we could do this
192    // with 32-bit copies advancing at different rates (taking care at the
193    // end to not go one byte over).
194    const uint8_t* readPtr = buf;
195    for (unsigned int i = 0; i < pixelCount; i++) {
196        *buf++ = *readPtr++;
197        *buf++ = *readPtr++;
198        *buf++ = *readPtr++;
199        readPtr++;
200    }
201}
202
203// Callback; executes on arbitrary thread.
204void FrameOutput::onFrameAvailable() {
205    Mutex::Autolock _l(mMutex);
206    mFrameAvailable = true;
207    mEventCond.signal();
208}
209