1/*
2 * Copyright 2013 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#include <assert.h>
18#include <inttypes.h>
19#include <stdlib.h>
20
21#define LOG_TAG "ScreenRecord"
22//#define LOG_NDEBUG 0
23#include <utils/Log.h>
24
25#include <gui/BufferQueue.h>
26#include <gui/GraphicBufferAlloc.h>
27#include <gui/Surface.h>
28#include <cutils/properties.h>
29#include <utils/misc.h>
30
31#include <GLES2/gl2.h>
32#include <GLES2/gl2ext.h>
33
34#include "screenrecord.h"
35#include "Overlay.h"
36#include "TextRenderer.h"
37
38using namespace android;
39
40// System properties to look up and display on the info screen.
41const char* Overlay::kPropertyNames[] = {
42        "ro.build.description",
43        // includes ro.build.id, ro.build.product, ro.build.tags, ro.build.type,
44        // and ro.build.version.release
45        "ro.product.manufacturer",
46        "ro.product.model",
47        "ro.board.platform",
48        "ro.revision",
49        "dalvik.vm.heapgrowthlimit",
50        "dalvik.vm.heapsize",
51        "persist.sys.dalvik.vm.lib.2",
52        //"ro.product.cpu.abi",
53        //"ro.bootloader",
54        //"this-never-appears!",
55};
56
57
58status_t Overlay::start(const sp<IGraphicBufferProducer>& outputSurface,
59        sp<IGraphicBufferProducer>* pBufferProducer) {
60    ALOGV("Overlay::start");
61    mOutputSurface = outputSurface;
62
63    // Grab the current monotonic time and the current wall-clock time so we
64    // can map one to the other.  This allows the overlay counter to advance
65    // by the exact delay between frames, but if the wall clock gets adjusted
66    // we won't track it, which means we'll gradually go out of sync with the
67    // times in logcat.
68    mStartMonotonicNsecs = systemTime(CLOCK_MONOTONIC);
69    mStartRealtimeNsecs = systemTime(CLOCK_REALTIME);
70
71    Mutex::Autolock _l(mMutex);
72
73    // Start the thread.  Traffic begins immediately.
74    run("overlay");
75
76    mState = INIT;
77    while (mState == INIT) {
78        mStartCond.wait(mMutex);
79    }
80
81    if (mThreadResult != NO_ERROR) {
82        ALOGE("Failed to start overlay thread: err=%d", mThreadResult);
83        return mThreadResult;
84    }
85    assert(mState == RUNNING);
86
87    ALOGV("Overlay::start successful");
88    *pBufferProducer = mProducer;
89    return NO_ERROR;
90}
91
92status_t Overlay::stop() {
93    ALOGV("Overlay::stop");
94    Mutex::Autolock _l(mMutex);
95    mState = STOPPING;
96    mEventCond.signal();
97    return NO_ERROR;
98}
99
100bool Overlay::threadLoop() {
101    Mutex::Autolock _l(mMutex);
102
103    mThreadResult = setup_l();
104
105    if (mThreadResult != NO_ERROR) {
106        ALOGW("Aborting overlay thread");
107        mState = STOPPED;
108        release_l();
109        mStartCond.broadcast();
110        return false;
111    }
112
113    ALOGV("Overlay thread running");
114    mState = RUNNING;
115    mStartCond.broadcast();
116
117    while (mState == RUNNING) {
118        mEventCond.wait(mMutex);
119        if (mFrameAvailable) {
120            ALOGV("Awake, frame available");
121            processFrame_l();
122            mFrameAvailable = false;
123        } else {
124            ALOGV("Awake, frame not available");
125        }
126    }
127
128    ALOGV("Overlay thread stopping");
129    release_l();
130    mState = STOPPED;
131    return false;       // stop
132}
133
134status_t Overlay::setup_l() {
135    status_t err;
136
137    err = mEglWindow.createWindow(mOutputSurface);
138    if (err != NO_ERROR) {
139        return err;
140    }
141    mEglWindow.makeCurrent();
142
143    int width = mEglWindow.getWidth();
144    int height = mEglWindow.getHeight();
145
146    glViewport(0, 0, width, height);
147    glDisable(GL_DEPTH_TEST);
148    glDisable(GL_CULL_FACE);
149
150    // Shaders for rendering from different types of textures.
151    err = mTexProgram.setup(Program::PROGRAM_TEXTURE_2D);
152    if (err != NO_ERROR) {
153        return err;
154    }
155    err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE);
156    if (err != NO_ERROR) {
157        return err;
158    }
159
160    err = mTextRenderer.loadIntoTexture();
161    if (err != NO_ERROR) {
162        return err;
163    }
164    mTextRenderer.setScreenSize(width, height);
165
166    // Input side (buffers from virtual display).
167    glGenTextures(1, &mExtTextureName);
168    if (mExtTextureName == 0) {
169        ALOGE("glGenTextures failed: %#x", glGetError());
170        return UNKNOWN_ERROR;
171    }
172
173    sp<IGraphicBufferConsumer> consumer;
174    BufferQueue::createBufferQueue(&mProducer, &consumer);
175    mGlConsumer = new GLConsumer(consumer, mExtTextureName,
176                GL_TEXTURE_EXTERNAL_OES, true, false);
177    mGlConsumer->setName(String8("virtual display"));
178    mGlConsumer->setDefaultBufferSize(width, height);
179    mProducer->setMaxDequeuedBufferCount(4);
180    mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
181
182    mGlConsumer->setFrameAvailableListener(this);
183
184    return NO_ERROR;
185}
186
187
188void Overlay::release_l() {
189    ALOGV("Overlay::release_l");
190    mOutputSurface.clear();
191    mGlConsumer.clear();
192    mProducer.clear();
193
194    mTexProgram.release();
195    mExtTexProgram.release();
196    mEglWindow.release();
197}
198
199void Overlay::processFrame_l() {
200    float texMatrix[16];
201
202    mGlConsumer->updateTexImage();
203    mGlConsumer->getTransformMatrix(texMatrix);
204    nsecs_t monotonicNsec = mGlConsumer->getTimestamp();
205    nsecs_t frameNumber = mGlConsumer->getFrameNumber();
206    int64_t droppedFrames = 0;
207
208    if (mLastFrameNumber > 0) {
209        mTotalDroppedFrames += size_t(frameNumber - mLastFrameNumber) - 1;
210    }
211    mLastFrameNumber = frameNumber;
212
213    mTextRenderer.setProportionalScale(35);
214
215    if (false) {  // DEBUG - full blue background
216        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
217        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
218    }
219
220    int width = mEglWindow.getWidth();
221    int height = mEglWindow.getHeight();
222    if (false) {  // DEBUG - draw inset
223        mExtTexProgram.blit(mExtTextureName, texMatrix,
224                100, 100, width-200, height-200);
225    } else {
226        mExtTexProgram.blit(mExtTextureName, texMatrix,
227                0, 0, width, height);
228    }
229
230    glEnable(GL_BLEND);
231    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
232    if (false) {  // DEBUG - show entire font bitmap
233        mTexProgram.blit(mTextRenderer.getTextureName(), Program::kIdentity,
234                100, 100, width-200, height-200);
235    }
236
237    char textBuf[64];
238    getTimeString_l(monotonicNsec, textBuf, sizeof(textBuf));
239    String8 timeStr(String8::format("%s f=%" PRId64 " (%zd)",
240            textBuf, frameNumber, mTotalDroppedFrames));
241    mTextRenderer.drawString(mTexProgram, Program::kIdentity, 0, 0, timeStr);
242
243    glDisable(GL_BLEND);
244
245    if (false) {  // DEBUG - add red rectangle in lower-left corner
246        glEnable(GL_SCISSOR_TEST);
247        glScissor(0, 0, 200, 200);
248        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
249        glClear(GL_COLOR_BUFFER_BIT);
250        glDisable(GL_SCISSOR_TEST);
251    }
252
253    mEglWindow.presentationTime(monotonicNsec);
254    mEglWindow.swapBuffers();
255}
256
257void Overlay::getTimeString_l(nsecs_t monotonicNsec, char* buf, size_t bufLen) {
258    //const char* format = "%m-%d %T";    // matches log output
259    const char* format = "%T";
260    struct tm tm;
261
262    // localtime/strftime is not the fastest way to do this, but a trivial
263    // benchmark suggests that the cost is negligible.
264    int64_t realTime = mStartRealtimeNsecs +
265            (monotonicNsec - mStartMonotonicNsecs);
266    time_t secs = (time_t) (realTime / 1000000000);
267    localtime_r(&secs, &tm);
268    strftime(buf, bufLen, format, &tm);
269
270    int32_t msec = (int32_t) ((realTime % 1000000000) / 1000000);
271    char tmpBuf[5];
272    snprintf(tmpBuf, sizeof(tmpBuf), ".%03d", msec);
273    strlcat(buf, tmpBuf, bufLen);
274}
275
276// Callback; executes on arbitrary thread.
277void Overlay::onFrameAvailable(const BufferItem& /* item */) {
278    ALOGV("Overlay::onFrameAvailable");
279    Mutex::Autolock _l(mMutex);
280    mFrameAvailable = true;
281    mEventCond.signal();
282}
283
284
285/*static*/ status_t Overlay::drawInfoPage(
286        const sp<IGraphicBufferProducer>& outputSurface) {
287    status_t err;
288
289    EglWindow window;
290    err = window.createWindow(outputSurface);
291    if (err != NO_ERROR) {
292        return err;
293    }
294    window.makeCurrent();
295
296    int width = window.getWidth();
297    int height = window.getHeight();
298    glViewport(0, 0, width, height);
299    glDisable(GL_DEPTH_TEST);
300    glDisable(GL_CULL_FACE);
301
302    // Shaders for rendering.
303    Program texProgram;
304    err = texProgram.setup(Program::PROGRAM_TEXTURE_2D);
305    if (err != NO_ERROR) {
306        return err;
307    }
308    TextRenderer textRenderer;
309    err = textRenderer.loadIntoTexture();
310    if (err != NO_ERROR) {
311        return err;
312    }
313    textRenderer.setScreenSize(width, height);
314
315    doDrawInfoPage(window, texProgram, textRenderer);
316
317    // Destroy the surface.  This causes a disconnect.
318    texProgram.release();
319    window.release();
320
321    return NO_ERROR;
322}
323
324/*static*/ void Overlay::doDrawInfoPage(const EglWindow& window,
325        const Program& texProgram, TextRenderer& textRenderer) {
326    const nsecs_t holdTime = 250000000LL;
327
328    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
329    glClear(GL_COLOR_BUFFER_BIT);
330
331    int width = window.getWidth();
332    int height = window.getHeight();
333
334    // Draw a thin border around the screen.  Some players, e.g. browser
335    // plugins, make it hard to see where the edges are when the device
336    // is using a black background, so this gives the viewer a frame of
337    // reference.
338    //
339    // This is a clumsy way to do it, but we're only doing it for one frame,
340    // and it's easier than actually drawing lines.
341    const int lineWidth = 4;
342    glEnable(GL_SCISSOR_TEST);
343    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
344    glScissor(0, 0, width, lineWidth);
345    glClear(GL_COLOR_BUFFER_BIT);
346    glScissor(0, height - lineWidth, width, lineWidth);
347    glClear(GL_COLOR_BUFFER_BIT);
348    glScissor(0, 0, lineWidth, height);
349    glClear(GL_COLOR_BUFFER_BIT);
350    glScissor(width - lineWidth, 0, lineWidth, height);
351    glClear(GL_COLOR_BUFFER_BIT);
352    glDisable(GL_SCISSOR_TEST);
353
354    //glEnable(GL_BLEND);
355    //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
356    textRenderer.setProportionalScale(30);
357
358    float xpos = 0;
359    float ypos = 0;
360    ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos,
361            String8::format("Android screenrecord v%d.%d",
362                    kVersionMajor, kVersionMinor));
363
364    // Show date/time
365    time_t now = time(0);
366    struct tm tm;
367    localtime_r(&now, &tm);
368    char timeBuf[64];
369    strftime(timeBuf, sizeof(timeBuf), "%a, %d %b %Y %T %z", &tm);
370    String8 header("Started ");
371    header += timeBuf;
372    ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, header);
373    ypos += 8 * textRenderer.getScale();    // slight padding
374
375    // Show selected system property values
376    for (int i = 0; i < NELEM(kPropertyNames); i++) {
377        char valueBuf[PROPERTY_VALUE_MAX];
378
379        property_get(kPropertyNames[i], valueBuf, "");
380        if (valueBuf[0] == '\0') {
381            continue;
382        }
383        String8 str(String8::format("%s: [%s]", kPropertyNames[i], valueBuf));
384        ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, str);
385    }
386    ypos += 8 * textRenderer.getScale();    // slight padding
387
388    // Show GL info
389    String8 glStr("OpenGL: ");
390    glStr += (char*) glGetString(GL_VENDOR);
391    glStr += " / ";
392    glStr += (char*) glGetString(GL_RENDERER);
393    glStr += ", ";
394    glStr += (char*) glGetString(GL_VERSION);
395    ypos = textRenderer.drawWrappedString(texProgram, xpos, ypos, glStr);
396
397    //glDisable(GL_BLEND);
398
399    // Set a presentation time slightly in the past.  This will cause the
400    // player to hold the frame on screen.
401    window.presentationTime(systemTime(CLOCK_MONOTONIC) - holdTime);
402    window.swapBuffers();
403}
404