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