blob: b058d5bdfe32f240969eb0d3584ebfc9745801d0 [file] [log] [blame]
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +00001/* Copyright (C) 2004-2007 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00002 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18//
19// PollingManager.cxx
20//
21
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000022#include <stdio.h>
23#include <string.h>
24#include <time.h>
25#include <X11/Xlib.h>
26#include <rfb/LogWriter.h>
27#include <rfb/VNCServer.h>
28#include <rfb/Configuration.h>
29#include <rfb/ServerCore.h>
30
31#include <x0vncserver/PollingManager.h>
32
33static LogWriter vlog("PollingMgr");
34
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035const int PollingManager::m_pollingOrder[32] = {
36 0, 16, 8, 24, 4, 20, 12, 28,
37 10, 26, 18, 2, 22, 6, 30, 14,
38 1, 17, 9, 25, 7, 23, 15, 31,
39 19, 3, 27, 11, 29, 13, 5, 21
40};
41
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +000042// FIXME: Check that the parameter's value is in the allowed range.
43// This applies to all other parameters as well.
Constantin Kaplinsky1d378022007-12-25 17:43:09 +000044IntParameter PollingManager::m_videoPriority("VideoPriority",
45 "Priority of sending updates for video area (0..8)", 2);
46
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000047//
48// Constructor.
49//
50// Note that dpy and image should remain valid during the object
51// lifetime, while factory is used only in the constructor itself.
52//
Constantin Kaplinsky936c3692007-12-27 08:42:53 +000053// FIXME: Pass XPixelBuffer* instead of Image*.
54//
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000055
56PollingManager::PollingManager(Display *dpy, Image *image,
57 ImageFactory *factory,
58 int offsetLeft, int offsetTop)
59 : m_dpy(dpy), m_server(0), m_image(image),
60 m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
Constantin Kaplinsky1d378022007-12-25 17:43:09 +000061 m_numVideoPasses(0),
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +000062 m_pollingStep(0)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000063{
64 // Save width and height of the screen (and the image).
65 m_width = m_image->xim->width;
66 m_height = m_image->xim->height;
67
68 // Compute width and height in 32x32 tiles.
69 m_widthTiles = (m_width + 31) / 32;
70 m_heightTiles = (m_height + 31) / 32;
71
72 // Get initial screen image.
73 m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
74
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +000075 // Create additional images used in polling algorithm, warn if
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000076 // underlying class names are different from the class name of the
77 // primary image.
78 m_rowImage = factory->newImage(m_dpy, m_width, 1);
Constantin Kaplinskyed3cf5d2007-12-28 17:59:10 +000079 m_columnImage = factory->newImage(m_dpy, 1, m_height);
80 const char *primaryImgClass = m_image->className();
81 const char *rowImgClass = m_rowImage->className();
82 const char *columnImgClass = m_columnImage->className();
83 if (strcmp(rowImgClass, primaryImgClass) != 0 ||
84 strcmp(columnImgClass, primaryImgClass) != 0) {
85 vlog.error("Image types do not match (%s, %s, %s)",
86 primaryImgClass, rowImgClass, columnImgClass);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000087 }
88
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000089 int numTiles = m_widthTiles * m_heightTiles;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000090 m_rateMatrix = new char[numTiles];
91 m_videoFlags = new char[numTiles];
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000092 memset(m_rateMatrix, 0, numTiles);
93 memset(m_videoFlags, 0, numTiles);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000094}
95
96PollingManager::~PollingManager()
97{
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000098 delete[] m_videoFlags;
99 delete[] m_rateMatrix;
100
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000101 delete m_rowImage;
Constantin Kaplinskyed3cf5d2007-12-28 17:59:10 +0000102 delete m_columnImage;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000103}
104
105//
106// Register VNCServer object.
107//
108
109void PollingManager::setVNCServer(VNCServer *s)
110{
111 m_server = s;
112}
113
114//
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000115// DEBUG: Measuring time spent in the poll() function,
116// as well as time intervals between poll() calls.
117//
118
119#ifdef DEBUG
120void PollingManager::debugBeforePoll()
121{
122 TimeMillis timeNow;
123 int diff = timeNow.diffFrom(m_timeSaved);
124 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
125 m_timeSaved = timeNow;
126}
127
128void PollingManager::debugAfterPoll()
129{
130 TimeMillis timeNow;
131 int diff = timeNow.diffFrom(m_timeSaved);
132 fprintf(stderr, "[poll%4dms]\n", diff);
133 m_timeSaved = timeNow;
134}
135
136#endif
137
138//
139// Search for changed rectangles on the screen.
140//
141
142void PollingManager::poll()
143{
144#ifdef DEBUG
145 debugBeforePoll();
146#endif
147
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +0000148 // Perform polling and try update clients if changes were detected.
149 if (pollScreen())
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000150 m_server->tryUpdate();
151
152#ifdef DEBUG
153 debugAfterPoll();
154#endif
155}
156
Constantin Kaplinskybf11a2d2008-01-10 15:59:03 +0000157#ifdef DEBUG_REPORT_CHANGED_TILES
158#define DBG_REPORT_CHANGES(title) printChanges((title), changeFlags)
159#else
160#define DBG_REPORT_CHANGES(title)
161#endif
162
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000163bool PollingManager::pollScreen()
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000164{
165 if (!m_server)
166 return false;
167
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000168 // If video data should have higher priority, and video area was
169 // detected, perform special passes to send video data only. Such
170 // "video passes" will be performed between normal polling passes.
171 // No actual polling is performed in a video pass since we know that
172 // video is changing continuously.
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000173 //
174 // FIXME: Should we move this block into a separate function?
175 // FIXME: Giving higher priority to video area lengthens video
176 // detection cycles. Should we do something with that?
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000177 if ((int)m_videoPriority > 1 && !m_videoRect.is_empty()) {
178 if (m_numVideoPasses > 0) {
179 m_numVideoPasses--;
180 getScreenRect(m_videoRect);
181 return true; // we've got changes
182 } else {
183 // Normal pass now, but schedule video passes for next calls.
184 m_numVideoPasses = (int)m_videoPriority - 1;
185 }
186 }
187
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000188 // changeFlags[] array will hold boolean values corresponding to
189 // each 32x32 tile. If a value is true, then we've detected a change
190 // in that tile. Initially, we fill in the array with zero values.
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000191 //
192 // FIXME: Should we use a member variable in place of changeFlags?
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000193 bool *changeFlags = new bool[m_widthTiles * m_heightTiles];
194 memset(changeFlags, 0, m_widthTiles * m_heightTiles * sizeof(bool));
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000195
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000196 // First pass over the framebuffer. Here we scan 1/32 part of the
197 // framebuffer -- that is, one line in each (32 * m_width) stripe.
198 // We compare the pixels of that line with previous framebuffer
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000199 // contents and raise corresponding member values of changeFlags[].
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000200 int scanOffset = m_pollingOrder[m_pollingStep++ % 32];
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000201 bool *pChangeFlags = changeFlags;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000202 int nTilesChanged = 0;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000203 for (int y = scanOffset; y < m_height; y += 32) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000204 nTilesChanged += checkRow(0, y, m_width, pChangeFlags);
205 pChangeFlags += m_widthTiles;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000206 }
207
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000208 // Do the work related to video area detection, if enabled.
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000209 bool haveVideoRect = false;
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000210 if ((int)m_videoPriority != 0) {
211 handleVideo(changeFlags);
212 if (!m_videoRect.is_empty()) {
213 getScreenRect(m_videoRect);
214 haveVideoRect = true;
215 }
216 }
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000217
Constantin Kaplinskybf11a2d2008-01-10 15:59:03 +0000218 DBG_REPORT_CHANGES("After 1st pass");
219
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000220 // If some changes have been detected:
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000221 if (nTilesChanged) {
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000222 // Try to find more changes around. Before doing that, mark the
223 // video area as changed, to skip comparisons of its pixels.
224 flagVideoArea(changeFlags, true);
Constantin Kaplinskybf11a2d2008-01-10 15:59:03 +0000225 DBG_REPORT_CHANGES("Before checking neighbors");
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000226 checkNeighbors(changeFlags);
Constantin Kaplinskybf11a2d2008-01-10 15:59:03 +0000227 DBG_REPORT_CHANGES("After checking neighbors");
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000228
229 // Inform the server about the changes. This time, we mark the
230 // video area as NOT changed, to prevent reading its pixels again.
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000231 flagVideoArea(changeFlags, false);
Constantin Kaplinskybf11a2d2008-01-10 15:59:03 +0000232 DBG_REPORT_CHANGES("Before sending");
Constantin Kaplinsky1a032112008-01-09 10:22:42 +0000233 nTilesChanged = sendChanges(changeFlags);
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000234 }
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000235
236 // Cleanup.
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000237 delete[] changeFlags;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000238
Constantin Kaplinsky52f29d32007-12-28 18:30:54 +0000239#ifdef DEBUG_PRINT_NUM_CHANGED_TILES
240 printf("%3d ", nTilesChanged);
241 if (m_pollingStep % 32 == 0) {
242 printf("\n");
243 }
244#endif
245
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000246#ifdef DEBUG
247 if (nTilesChanged != 0) {
248 fprintf(stderr, "#%d# ", nTilesChanged);
249 }
250#endif
251
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000252 return (nTilesChanged != 0 || haveVideoRect);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000253}
254
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000255int PollingManager::checkRow(int x, int y, int w, bool *pChangeFlags)
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000256{
257 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
258 int bytesPerLine = m_image->xim->bytes_per_line;
259
Constantin Kaplinsky29d32052007-10-08 14:16:02 +0000260 if (x == 0 && w == m_width) {
261 getFullRow(y); // use more efficient method if possible
262 } else {
263 getRow(x, y, w);
264 }
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000265
266 char *ptr_old = m_image->xim->data + y * bytesPerLine + x * bytesPerPixel;
267 char *ptr_new = m_rowImage->xim->data;
268
269 int nTilesChanged = 0;
270 for (int i = 0; i < (w + 31) / 32; i++) {
271 int tile_w = (w - i * 32 >= 32) ? 32 : w - i * 32;
272 int nBytes = tile_w * bytesPerPixel;
273 if (memcmp(ptr_old, ptr_new, nBytes)) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000274 *pChangeFlags = true;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000275 nTilesChanged++;
276 }
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000277 pChangeFlags++;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000278 ptr_old += nBytes;
279 ptr_new += nBytes;
280 }
281
282 return nTilesChanged;
283}
284
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000285int PollingManager::checkColumn(int x, int y, int h, bool *pChangeFlags)
286{
287 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
288
289 getColumn(x, y, h);
290
291 int nTilesChanged = 0;
292 for (int nTile = 0; nTile < (h + 31) / 32; nTile++) {
293 if (!*pChangeFlags) {
294 int tile_h = (h - nTile * 32 >= 32) ? 32 : h - nTile * 32;
295 for (int i = 0; i < tile_h; i++) {
296 // FIXME: Provide an inline function Image::locatePixel(x, y).
297 // FIXME: Do not compute these pointers in the inner cycle.
298 char *ptr_old = (m_image->xim->data +
299 (y + nTile * 32 + i) * m_image->xim->bytes_per_line +
300 x * bytesPerPixel);
301 char *ptr_new = (m_columnImage->xim->data +
302 (nTile * 32 + i) * m_columnImage->xim->bytes_per_line);
303 if (memcmp(ptr_old, ptr_new, bytesPerPixel)) {
304 *pChangeFlags = true;
305 nTilesChanged++;
306 break;
307 }
308 }
309 }
310 pChangeFlags += m_widthTiles;
311 }
312
313 return nTilesChanged;
314}
315
Constantin Kaplinsky1a032112008-01-09 10:22:42 +0000316int PollingManager::sendChanges(const bool *pChangeFlags)
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000317{
Constantin Kaplinsky1a032112008-01-09 10:22:42 +0000318 int nTilesChanged = 0;
319
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000320 Rect rect;
321 for (int y = 0; y < m_heightTiles; y++) {
322 for (int x = 0; x < m_widthTiles; x++) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000323 if (*pChangeFlags++) {
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000324 // Count successive tiles marked as changed.
325 int count = 1;
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000326 while (x + count < m_widthTiles && *pChangeFlags++) {
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000327 count++;
328 }
Constantin Kaplinsky1a032112008-01-09 10:22:42 +0000329 nTilesChanged += count;
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000330 // Compute the coordinates and the size of this band.
331 rect.setXYWH(x * 32, y * 32, count * 32, 32);
332 if (rect.br.x > m_width)
333 rect.br.x = m_width;
334 if (rect.br.y > m_height)
335 rect.br.y = m_height;
336 // Add to the changed region maintained by the server.
337 getScreenRect(rect);
338 m_server->add_changed(rect);
339 // Skip processed tiles.
340 x += count;
341 }
342 }
343 }
Constantin Kaplinsky1a032112008-01-09 10:22:42 +0000344 return nTilesChanged;
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000345}
346
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000347void PollingManager::handleVideo(const bool *pChangeFlags)
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000348{
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000349 // Update counters in m_rateMatrix.
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000350 int numTiles = m_heightTiles * m_widthTiles;
351 for (int i = 0; i < numTiles; i++)
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000352 m_rateMatrix[i] += (pChangeFlags[i] != false);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000353
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000354 // Once per eight calls: detect video rectangle by examining
355 // m_rateMatrix[], then reset counters in m_rateMatrix[].
356 if (m_pollingStep % 8 == 0) {
357 detectVideo();
358 memset(m_rateMatrix, 0, numTiles);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000359 }
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000360}
361
Constantin Kaplinskybd390352007-12-28 08:44:59 +0000362void PollingManager::flagVideoArea(bool *pChangeFlags, bool value)
363{
Constantin Kaplinsky48792632007-12-28 18:21:42 +0000364 if (m_videoRect.is_empty())
365 return;
366
Constantin Kaplinskybd390352007-12-28 08:44:59 +0000367 Rect r(m_videoRect.tl.x / 32, m_videoRect.tl.y / 32,
368 m_videoRect.br.x / 32, m_videoRect.br.y / 32);
369
370 for (int y = r.tl.y; y < r.br.y; y++)
371 for (int x = r.tl.x; x < r.br.x; x++)
372 pChangeFlags[y * m_widthTiles + x] = value;
373}
374
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000375void
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000376PollingManager::checkNeighbors(bool *pChangeFlags)
377{
Constantin Kaplinsky474b12f2008-01-09 10:25:34 +0000378 int x, y;
379
380 // Check neighboring pixels above and below changed tiles.
381 // FIXME: Fast skip to the first changed tile (and to the last, too).
382 // FIXME: Check the full-width line above the first changed tile?
383 for (y = 0; y < m_heightTiles; y++) {
384 bool doneAbove = false;
385 bool doneBelow = false;
386 for (x = 0; x < m_widthTiles; x++) {
387 if (!doneAbove && y > 0 &&
388 pChangeFlags[y * m_widthTiles + x] &&
389 !pChangeFlags[(y - 1) * m_widthTiles + x]) {
390 // FIXME: Check pChangeFlags[] to decrease height of the row.
391 checkRow(x * 32, y * 32 - 1, m_width - x * 32,
392 &pChangeFlags[(y - 1) * m_widthTiles + x]);
393 doneAbove = true;
394 }
395 if (!doneBelow && y < m_heightTiles - 1 &&
396 pChangeFlags[y * m_widthTiles + x] &&
397 !pChangeFlags[(y + 1) * m_widthTiles + x]) {
398 // FIXME: Check pChangeFlags[] to decrease height of the row.
399 checkRow(x * 32, (y + 1) * 32, m_width - x * 32,
400 &pChangeFlags[(y + 1) * m_widthTiles + x]);
401 doneBelow = true;
402 }
403 if (doneBelow && doneAbove)
404 break;
405 }
406 }
407
408 // Check neighboring pixels at the right side of changed tiles.
409 for (x = 0; x < m_widthTiles - 1; x++) {
410 for (y = 0; y < m_heightTiles; y++) {
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000411 if (pChangeFlags[y * m_widthTiles + x] &&
412 !pChangeFlags[y * m_widthTiles + x + 1]) {
413 // FIXME: Check pChangeFlags[] to decrease height of the column.
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000414 checkColumn((x + 1) * 32, y * 32, m_height - y * 32,
415 &pChangeFlags[y * m_widthTiles + x + 1]);
416 break;
417 }
418 }
419 }
Constantin Kaplinsky474b12f2008-01-09 10:25:34 +0000420
421 // Check neighboring pixels at the left side of changed tiles.
422 for (x = m_widthTiles - 1; x > 0; x--) {
423 for (y = 0; y < m_heightTiles; y++) {
424 if (pChangeFlags[y * m_widthTiles + x] &&
425 !pChangeFlags[y * m_widthTiles + x - 1]) {
426 // FIXME: Check pChangeFlags[] to decrease height of the column.
427 checkColumn(x * 32 - 1, y * 32, m_height - y * 32,
428 &pChangeFlags[y * m_widthTiles + x - 1]);
429 break;
430 }
431 }
432 }
Constantin Kaplinsky553340c2007-12-28 18:37:04 +0000433}
434
435void
Constantin Kaplinskyf50bd7f2008-01-10 15:27:42 +0000436PollingManager::printChanges(const char *header, const bool *pChangeFlags)
437{
438 fprintf(stderr, "%s:", header);
439
440 for (int y = 0; y < m_heightTiles; y++) {
441 for (int x = 0; x < m_widthTiles; x++) {
442 if (*pChangeFlags++) {
443 // Count successive tiles marked as changed.
444 int count = 1;
445 while (x + count < m_widthTiles && *pChangeFlags++) {
446 count++;
447 }
448 // Print.
449 fprintf(stderr, " (%d,%d)*%d", x, y, count);
450 // Skip processed tiles.
451 x += count;
452 }
453 }
454 }
455
456 fprintf(stderr, "\n");
457}
458
459void
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000460PollingManager::detectVideo()
461{
462 // Configurable parameters.
463 const int VIDEO_THRESHOLD_0 = 3;
464 const int VIDEO_THRESHOLD_1 = 5;
465
Constantin Kaplinskybb563772007-12-25 11:25:07 +0000466 // In m_rateMatrix, clear counters corresponding to non-32x32 tiles.
467 // This will guarantee that the size of the video area is always a
468 // multiple of 32 pixels. This is important for hardware JPEG encoders.
469 int numTiles = m_heightTiles * m_widthTiles;
470 if (m_width % 32 != 0) {
471 for (int n = m_widthTiles - 1; n < numTiles; n += m_widthTiles)
472 m_rateMatrix[n] = 0;
473 }
474 if (m_height % 32 != 0) {
475 for (int n = numTiles - m_widthTiles; n < numTiles; n++)
476 m_rateMatrix[n] = 0;
477 }
478
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000479 // First, detect candidate region that looks like video. In other
480 // words, find a region that consists of continuously changing
481 // pixels. Save the result in m_videoFlags[].
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000482 for (int i = 0; i < numTiles; i++) {
483 if (m_rateMatrix[i] <= VIDEO_THRESHOLD_0) {
484 m_videoFlags[i] = 0;
485 } else if (m_rateMatrix[i] >= VIDEO_THRESHOLD_1) {
486 m_videoFlags[i] = 1;
487 }
488 }
489
490 // Now, choose the biggest rectangle from that candidate region.
491 Rect newRect;
492 getVideoAreaRect(&newRect);
493
494 // Does new rectangle differ from the previously detected one?
495 // If it does, save new rectangle and inform the server.
496 if (!newRect.equals(m_videoRect)) {
497 if (newRect.is_empty()) {
Constantin Kaplinskydab9a562007-10-10 01:33:39 +0000498 vlog.debug("No video detected");
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000499 } else {
Constantin Kaplinskydab9a562007-10-10 01:33:39 +0000500 vlog.debug("Detected video %dx%d at (%d,%d)",
501 newRect.width(), newRect.height(),
502 newRect.tl.x, newRect.tl.y);
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000503 }
504 m_videoRect = newRect;
505 m_server->set_video_area(newRect);
506 }
507}
508
509void
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000510PollingManager::getVideoAreaRect(Rect *result)
511{
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000512 int *mx_hlen, *mx_vlen;
513 constructLengthMatrices(&mx_hlen, &mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000514
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000515 int full_h = m_heightTiles;
516 int full_w = m_widthTiles;
517 int x, y;
518 Rect max_rect(0, 0, 0, 0);
519 Rect local_rect;
520
521 for (y = 0; y < full_h; y++) {
522 for (x = 0; x < full_w; x++) {
523 int max_w = mx_hlen[y * full_w + x];
524 int max_h = mx_vlen[y * full_w + x];
525 if (max_w > 2 && max_h > 1 && max_h * max_w > (int)max_rect.area()) {
526 local_rect.tl.x = x;
527 local_rect.tl.y = y;
528 findMaxLocalRect(&local_rect, mx_hlen, mx_vlen);
529 if (local_rect.area() > max_rect.area()) {
530 max_rect = local_rect;
531 }
532 }
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000533 }
534 }
535
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000536 destroyLengthMatrices(mx_hlen, mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000537
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000538 max_rect.tl.x *= 32;
539 max_rect.tl.y *= 32;
540 max_rect.br.x *= 32;
541 max_rect.br.y *= 32;
542 if (max_rect.br.x > m_width)
543 max_rect.br.x = m_width;
544 if (max_rect.br.y > m_height)
545 max_rect.br.y = m_height;
546 *result = max_rect;
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000547}
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000548
549void
550PollingManager::constructLengthMatrices(int **pmx_h, int **pmx_v)
551{
552 // Handy shortcuts.
553 int h = m_heightTiles;
554 int w = m_widthTiles;
555
556 // Allocate memory.
557 int *mx_h = new int[h * w];
558 memset(mx_h, 0, h * w * sizeof(int));
559 int *mx_v = new int[h * w];
560 memset(mx_v, 0, h * w * sizeof(int));
561
562 int x, y, len, i;
563
564 // Fill in horizontal length matrix.
565 for (y = 0; y < h; y++) {
566 for (x = 0; x < w; x++) {
567 len = 0;
568 while (x + len < w && m_videoFlags[y * w + x + len]) {
569 len++;
570 }
571 for (i = 0; i < len; i++) {
572 mx_h[y * w + x + i] = len - i;
573 }
574 x += len;
575 }
576 }
577
578 // Fill in vertical length matrix.
579 for (x = 0; x < w; x++) {
580 for (y = 0; y < h; y++) {
581 len = 0;
582 while (y + len < h && m_videoFlags[(y + len) * w + x]) {
583 len++;
584 }
585 for (i = 0; i < len; i++) {
586 mx_v[(y + i) * w + x] = len - i;
587 }
588 y += len;
589 }
590 }
591
592 *pmx_h = mx_h;
593 *pmx_v = mx_v;
594}
595
596void
597PollingManager::destroyLengthMatrices(int *mx_h, int *mx_v)
598{
599 delete[] mx_h;
600 delete[] mx_v;
601}
602
603// NOTE: This function assumes that current tile has non-zero in mx_h[],
604// otherwise we get division by zero.
605void
606PollingManager::findMaxLocalRect(Rect *r, int mx_h[], int mx_v[])
607{
608 int idx = r->tl.y * m_widthTiles + r->tl.x;
609
610 // NOTE: Rectangle's maximum width and height are 25 and 18
611 // (in tiles, where each tile is usually 32x32 pixels).
612 int max_w = mx_h[idx];
613 if (max_w > 25)
614 max_w = 25;
615 int cur_h = 18;
616
617 int best_w = max_w;
618 int best_area = 1 * best_w;
619
620 for (int i = 0; i < max_w; i++) {
621 int h = mx_v[idx + i];
622 if (h < cur_h) {
623 cur_h = h;
624 if (cur_h * max_w <= best_area)
625 break;
626 }
627 if (cur_h * (i + 1) > best_area) {
628 best_w = i + 1;
629 best_area = cur_h * best_w;
630 }
631 }
632
633 r->br.x = r->tl.x + best_w;
634 r->br.y = r->tl.y + best_area / best_w;
635}
636