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
38 using namespace android;
39
40 // System properties to look up and display on the info screen.
41 const 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
start(const sp<IGraphicBufferProducer> & outputSurface,sp<IGraphicBufferProducer> * pBufferProducer)58 status_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
stop()92 status_t Overlay::stop() {
93 ALOGV("Overlay::stop");
94 Mutex::Autolock _l(mMutex);
95 mState = STOPPING;
96 mEventCond.signal();
97 return NO_ERROR;
98 }
99
threadLoop()100 bool 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
setup_l()134 status_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
release_l()188 void 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
processFrame_l()199 void 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
getTimeString_l(nsecs_t monotonicNsec,char * buf,size_t bufLen)257 void 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.
onFrameAvailable(const BufferItem &)277 void Overlay::onFrameAvailable(const BufferItem& /* item */) {
278 ALOGV("Overlay::onFrameAvailable");
279 Mutex::Autolock _l(mMutex);
280 mFrameAvailable = true;
281 mEventCond.signal();
282 }
283
284
drawInfoPage(const sp<IGraphicBufferProducer> & outputSurface)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
doDrawInfoPage(const EglWindow & window,const Program & texProgram,TextRenderer & textRenderer)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