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    sp<IGraphicBufferProducer> producer;
71    sp<IGraphicBufferConsumer> consumer;
72    BufferQueue::createBufferQueue(&producer, &consumer);
73    mGlConsumer = new GLConsumer(consumer, mExtTextureName,
74                GL_TEXTURE_EXTERNAL_OES, true, false);
75    mGlConsumer->setName(String8("virtual display"));
76    mGlConsumer->setDefaultBufferSize(width, height);
77    producer->setMaxDequeuedBufferCount(4);
78    mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
79
80    mGlConsumer->setFrameAvailableListener(this);
81
82    mPixelBuf = new uint8_t[width * height * kGlBytesPerPixel];
83
84    *pBufferProducer = producer;
85
86    ALOGD("FrameOutput::createInputSurface OK");
87    return NO_ERROR;
88}
89
90status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec, bool rawFrames) {
91    Mutex::Autolock _l(mMutex);
92    ALOGV("copyFrame %ld\n", timeoutUsec);
93
94    if (!mFrameAvailable) {
95        nsecs_t timeoutNsec = (nsecs_t)timeoutUsec * 1000;
96        int cc = mEventCond.waitRelative(mMutex, timeoutNsec);
97        if (cc == -ETIMEDOUT) {
98            ALOGV("cond wait timed out");
99            return ETIMEDOUT;
100        } else if (cc != 0) {
101            ALOGW("cond wait returned error %d", cc);
102            return cc;
103        }
104    }
105    if (!mFrameAvailable) {
106        // This happens when Ctrl-C is hit.  Apparently POSIX says that the
107        // pthread wait call doesn't return EINTR, treating this instead as
108        // an instance of a "spurious wakeup".  We didn't get a frame, so
109        // we just treat it as a timeout.
110        return ETIMEDOUT;
111    }
112
113    // A frame is available.  Clear the flag for the next round.
114    mFrameAvailable = false;
115
116    float texMatrix[16];
117    mGlConsumer->updateTexImage();
118    mGlConsumer->getTransformMatrix(texMatrix);
119
120    // The data is in an external texture, so we need to render it to the
121    // pbuffer to get access to RGB pixel data.  We also want to flip it
122    // upside-down for easy conversion to a bitmap.
123    int width = mEglWindow.getWidth();
124    int height = mEglWindow.getHeight();
125    status_t err = mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0,
126            width, height, true);
127    if (err != NO_ERROR) {
128        return err;
129    }
130
131    // GLES only guarantees that glReadPixels() will work with GL_RGBA, so we
132    // need to get 4 bytes/pixel and reduce it.  Depending on the size of the
133    // screen and the device capabilities, this can take a while.
134    int64_t startWhenNsec, pixWhenNsec, endWhenNsec;
135    if (kShowTiming) {
136        startWhenNsec = systemTime(CLOCK_MONOTONIC);
137    }
138    GLenum glErr;
139    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, mPixelBuf);
140    if ((glErr = glGetError()) != GL_NO_ERROR) {
141        ALOGE("glReadPixels failed: %#x", glErr);
142        return UNKNOWN_ERROR;
143    }
144    if (kShowTiming) {
145        pixWhenNsec = systemTime(CLOCK_MONOTONIC);
146    }
147    reduceRgbaToRgb(mPixelBuf, width * height);
148    if (kShowTiming) {
149        endWhenNsec = systemTime(CLOCK_MONOTONIC);
150        ALOGD("got pixels (get=%.3f ms, reduce=%.3fms)",
151                (pixWhenNsec - startWhenNsec) / 1000000.0,
152                (endWhenNsec - pixWhenNsec) / 1000000.0);
153    }
154
155    size_t rgbDataLen = width * height * kOutBytesPerPixel;
156
157    if (!rawFrames) {
158        // Fill out the header.
159        size_t headerLen = sizeof(uint32_t) * 5;
160        size_t packetLen = headerLen - sizeof(uint32_t) + rgbDataLen;
161        uint8_t header[headerLen];
162        setValueLE(&header[0], packetLen);
163        setValueLE(&header[4], width);
164        setValueLE(&header[8], height);
165        setValueLE(&header[12], width * kOutBytesPerPixel);
166        setValueLE(&header[16], HAL_PIXEL_FORMAT_RGB_888);
167        fwrite(header, 1, headerLen, fp);
168    }
169
170    // Currently using buffered I/O rather than writev().  Not expecting it
171    // to make much of a difference, but it might be worth a test for larger
172    // frame sizes.
173    if (kShowTiming) {
174        startWhenNsec = systemTime(CLOCK_MONOTONIC);
175    }
176    fwrite(mPixelBuf, 1, rgbDataLen, fp);
177    fflush(fp);
178    if (kShowTiming) {
179        endWhenNsec = systemTime(CLOCK_MONOTONIC);
180        ALOGD("wrote pixels (%.3f ms)",
181                (endWhenNsec - startWhenNsec) / 1000000.0);
182    }
183
184    if (ferror(fp)) {
185        // errno may not be useful; log it anyway
186        ALOGE("write failed (errno=%d)", errno);
187        return UNKNOWN_ERROR;
188    }
189
190    return NO_ERROR;
191}
192
193void FrameOutput::reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount) {
194    // Convert RGBA to RGB.
195    //
196    // Unaligned 32-bit accesses are allowed on ARM, so we could do this
197    // with 32-bit copies advancing at different rates (taking care at the
198    // end to not go one byte over).
199    const uint8_t* readPtr = buf;
200    for (unsigned int i = 0; i < pixelCount; i++) {
201        *buf++ = *readPtr++;
202        *buf++ = *readPtr++;
203        *buf++ = *readPtr++;
204        readPtr++;
205    }
206}
207
208// Callback; executes on arbitrary thread.
209void FrameOutput::onFrameAvailable(const BufferItem& /* item */) {
210    Mutex::Autolock _l(mMutex);
211    mFrameAvailable = true;
212    mEventCond.signal();
213}
214