blob: b3c4f5d1d9fa220461c90cccab204ee67bb2d0ea [file] [log] [blame]
Pierre Ossman8738e8a2015-02-11 13:49:04 +01001/* Copyright 2015 Pierre Ossman <ossman@cendio.se> for Cendio AB
DRCb4c4a382015-02-21 11:28:37 -06002 *
Pierre Ossman8738e8a2015-02-11 13:49:04 +01003 * 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.
DRCb4c4a382015-02-21 11:28:37 -06007 *
Pierre Ossman8738e8a2015-02-11 13:49:04 +01008 * 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.
DRCb4c4a382015-02-21 11:28:37 -060012 *
Pierre Ossman8738e8a2015-02-11 13:49:04 +010013 * 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/*
20 * This program reads files produced by TightVNC's/TurboVNC's
21 * fbs-dump, which in turn takes files from rfbproxy. It is
22 * basically a dump of the RFB protocol from the server side after
23 * the ServerInit message. Mostly this consists of FramebufferUpdate
24 * message using the HexTile encoding. Screen size and pixel format
25 * are not encoded in the file and must be specified by the user.
26 */
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <math.h>
31
32#include <rdr/Exception.h>
33#include <rdr/OutStream.h>
34#include <rdr/FileInStream.h>
35
36#include <rfb/PixelFormat.h>
37
38#include <rfb/CConnection.h>
39#include <rfb/CMsgReader.h>
40#include <rfb/Decoder.h>
41#include <rfb/UpdateTracker.h>
42
43#include <rfb/EncodeManager.h>
44#include <rfb/SConnection.h>
45#include <rfb/SMsgWriter.h>
46
47#include "util.h"
48
49static rfb::IntParameter width("width", "Frame buffer width", 0);
50static rfb::IntParameter height("height", "Frame buffer height", 0);
51
52static rfb::StringParameter format("format", "Pixel format (e.g. bgr888)", "");
53
54// The frame buffer (and output) is always this format
55static const rfb::PixelFormat fbPF(32, 24, false, true, 255, 255, 255, 0, 8, 16);
56
57// Encodings to use
58static const rdr::S32 encodings[] = {
59 rfb::encodingTight, rfb::encodingCopyRect, rfb::encodingRRE,
60 rfb::encodingHextile, rfb::encodingZRLE, rfb::pseudoEncodingLastRect,
61 rfb::pseudoEncodingQualityLevel0 + 8 };
62
63class DummyOutStream : public rdr::OutStream {
64public:
65 DummyOutStream();
66
67 virtual int length();
68 virtual void flush();
69
70private:
71 virtual int overrun(int itemSize, int nItems);
72
73 int offset;
74 rdr::U8 buf[131072];
75};
76
77class CConn : public rfb::CConnection {
78public:
79 CConn(const char *filename);
80 ~CConn();
81
82 double getRatio();
83
84 virtual void setDesktopSize(int w, int h);
85 virtual void setCursor(int, int, const rfb::Point&, void*, void*);
86 virtual void framebufferUpdateStart();
87 virtual void framebufferUpdateEnd();
88 virtual void dataRect(const rfb::Rect&, int);
89 virtual void setColourMapEntries(int, int, rdr::U16*);
90 virtual void bell();
91 virtual void serverCutText(const char*, rdr::U32);
92
93public:
94 double decodeTime;
95 double encodeTime;
96
97protected:
98 rdr::FileInStream *in;
DRCb4c4a382015-02-21 11:28:37 -060099 rfb::Decoder *decoders[rfb::encodingMax + 1];
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100100 rfb::ManagedPixelBuffer pb;
101 rfb::SimpleUpdateTracker updates;
102 class SConn *sc;
103};
104
105class Manager : public rfb::EncodeManager {
106public:
107 Manager(class rfb::SConnection *conn);
108
109 double getRatio();
110};
111
112class SConn : public rfb::SConnection {
113public:
114 SConn();
115 ~SConn();
116
117 void writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb);
118
119 double getRatio();
120
121 virtual void setAccessRights(AccessRights ar);
122
123 virtual void setDesktopSize(int fb_width, int fb_height,
124 const rfb::ScreenSet& layout);
125
126protected:
127 DummyOutStream *out;
128 Manager *manager;
129};
130
131DummyOutStream::DummyOutStream()
132{
133 offset = 0;
134 ptr = buf;
135 end = buf + sizeof(buf);
136}
137
138int DummyOutStream::length()
139{
140 flush();
141 return offset;
142}
143
144void DummyOutStream::flush()
145{
146 offset += ptr - buf;
147 ptr = buf;
148}
149
150int DummyOutStream::overrun(int itemSize, int nItems)
151{
152 flush();
153}
154
155CConn::CConn(const char *filename)
156{
157 int i;
158
159 decodeTime = 0.0;
160 encodeTime = 0.0;
161
162 in = new rdr::FileInStream(filename);
163 setStreams(in, NULL);
164
165 memset(decoders, 0, sizeof(decoders));
DRCb4c4a382015-02-21 11:28:37 -0600166 for (i = 0; i < rfb::encodingMax; i++) {
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100167 if (!rfb::Decoder::supported(i))
168 continue;
169
170 decoders[i] = rfb::Decoder::createDecoder(i, this);
171 }
172
173 pb.setPF(fbPF);
174
175 // Need to skip the initial handshake and ServerInit
176 setState(RFBSTATE_NORMAL);
177 // That also means that the reader and writer weren't setup
178 setReader(new rfb::CMsgReader(this, in));
179 // Nor the frame buffer size and format
180 setDesktopSize(width, height);
181 rfb::PixelFormat pf;
182 pf.parse(format);
183 setPixelFormat(pf);
184
185 sc = new SConn();
186 sc->cp.setPF(pb.getPF());
DRCb4c4a382015-02-21 11:28:37 -0600187 sc->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings);
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100188}
189
190CConn::~CConn()
191{
192 int i;
193
194 delete sc;
195
196 delete in;
197
DRCb4c4a382015-02-21 11:28:37 -0600198 for (i = 0; i < rfb::encodingMax; i++)
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100199 delete decoders[i];
200}
201
202double CConn::getRatio()
203{
204 return sc->getRatio();
205}
206
207void CConn::setDesktopSize(int w, int h)
208{
209 CConnection::setDesktopSize(w, h);
210
211 pb.setSize(cp.width, cp.height);
212}
213
214void CConn::setCursor(int, int, const rfb::Point&, void*, void*)
215{
216}
217
218void CConn::framebufferUpdateStart()
219{
220 updates.clear();
221}
222
223void CConn::framebufferUpdateEnd()
224{
225 rfb::UpdateInfo ui;
226 rfb::Region clip(pb.getRect());
227
228 updates.getUpdateInfo(&ui, clip);
229
230 startCpuCounter();
231 sc->writeUpdate(ui, &pb);
232 endCpuCounter();
233
234 encodeTime += getCpuCounter();
235}
236
237void CConn::dataRect(const rfb::Rect &r, int encoding)
238{
239 if (!decoders[encoding])
240 throw rdr::Exception("Unknown encoding");
241
242 startCpuCounter();
243 decoders[encoding]->readRect(r, &pb);
244 endCpuCounter();
245
246 decodeTime += getCpuCounter();
247
248 if (encoding != rfb::encodingCopyRect) // FIXME
249 updates.add_changed(rfb::Region(r));
250}
251
252void CConn::setColourMapEntries(int, int, rdr::U16*)
253{
254}
255
256void CConn::bell()
257{
258}
259
260void CConn::serverCutText(const char*, rdr::U32)
261{
262}
263
264Manager::Manager(class rfb::SConnection *conn) :
265 EncodeManager(conn)
266{
267}
268
269double Manager::getRatio()
270{
271 StatsVector::iterator iter;
272 unsigned long long bytes, equivalent;
273
274 bytes = equivalent = 0;
DRCb4c4a382015-02-21 11:28:37 -0600275 for (iter = stats.begin(); iter != stats.end(); ++iter) {
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100276 StatsVector::value_type::iterator iter2;
DRCb4c4a382015-02-21 11:28:37 -0600277 for (iter2 = iter->begin(); iter2 != iter->end(); ++iter2) {
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100278 bytes += iter2->bytes;
279 equivalent += iter2->equivalent;
280 }
281 }
282
283 return (double)equivalent / bytes;
284}
285
286SConn::SConn()
287{
288 out = new DummyOutStream;
289 setStreams(NULL, out);
290
291 setWriter(new rfb::SMsgWriter(&cp, out));
292
293 manager = new Manager(this);
294}
295
296SConn::~SConn()
297{
298 delete manager;
299 delete out;
300}
301
302void SConn::writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb)
303{
304 manager->writeUpdate(ui, pb, NULL);
305}
306
307double SConn::getRatio()
308{
309 return manager->getRatio();
310}
311
312void SConn::setAccessRights(AccessRights ar)
313{
314}
315
316void SConn::setDesktopSize(int fb_width, int fb_height,
317 const rfb::ScreenSet& layout)
318{
319}
320
321static double runTest(const char *fn, double *ratio)
322{
323 CConn *cc;
324 double time;
325
326 cc = new CConn(fn);
327
328 try {
329 while (true)
330 cc->processMsg();
331 } catch (rdr::EndOfStream e) {
332 } catch (rdr::Exception e) {
333 fprintf(stderr, "Failed to run rfb file: %s\n", e.str());
334 exit(1);
335 }
336
337 time = cc->encodeTime;
338 *ratio = cc->getRatio();
339
340 delete cc;
341
342 return time;
343}
344
345static void sort(double *array, int count)
346{
347 bool sorted;
348 int i;
349 do {
350 sorted = true;
DRCb4c4a382015-02-21 11:28:37 -0600351 for (i = 1; i < count; i++) {
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100352 if (array[i-1] > array[i]) {
353 double d;
354 d = array[i];
DRCb4c4a382015-02-21 11:28:37 -0600355 array[i] = array[i - 1];
356 array[i - 1] = d;
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100357 sorted = false;
358 }
359 }
360 } while (!sorted);
361}
362
363static const int runCount = 9;
364
365static void usage(const char *argv0)
366{
367 fprintf(stderr, "Syntax: %s [options] <rfb file>\n", argv0);
368 fprintf(stderr, "Options:\n");
369 rfb::Configuration::listParams(79, 14);
370 exit(1);
371}
372
373int main(int argc, char **argv)
374{
375 int i;
376
377 const char *fn;
378
379 double times[runCount], dev[runCount];
380 double median, meddev, ratio;
381
382 fn = NULL;
383 for (i = 1; i < argc; i++) {
384 if (rfb::Configuration::setParam(argv[i]))
385 continue;
386
387 if (argv[i][0] == '-') {
DRCb4c4a382015-02-21 11:28:37 -0600388 if (i + 1 < argc) {
389 if (rfb::Configuration::setParam(&argv[i][1], argv[i + 1])) {
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100390 i++;
391 continue;
392 }
393 }
394 usage(argv[0]);
395 }
396
397 if (fn != NULL)
398 usage(argv[0]);
399
400 fn = argv[i];
401 }
402
403 if (fn == NULL) {
404 fprintf(stderr, "No file specified!\n\n");
405 usage(argv[0]);
406 }
407
408 if (!format.hasBeenSet()) {
409 fprintf(stderr, "Pixel format not specified!\n\n");
410 usage(argv[0]);
411 }
412
413 if (!width.hasBeenSet() || !height.hasBeenSet()) {
414 fprintf(stderr, "Frame buffer size not specified!\n\n");
415 usage(argv[0]);
416 }
417
418 // Warmup
419 runTest(fn, &ratio);
420
421 // Multiple runs to get a good average
DRCb4c4a382015-02-21 11:28:37 -0600422 for (i = 0; i < runCount; i++)
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100423 times[i] = runTest(fn, &ratio);
424
425 // Calculate median and median deviation
426 sort(times, runCount);
DRCb4c4a382015-02-21 11:28:37 -0600427 median = times[runCount / 2];
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100428
DRCb4c4a382015-02-21 11:28:37 -0600429 for (i = 0; i < runCount; i++)
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100430 dev[i] = fabs((times[i] - median) / median) * 100;
431
432 sort(dev, runCount);
DRCb4c4a382015-02-21 11:28:37 -0600433 meddev = dev[runCount / 2];
Pierre Ossman8738e8a2015-02-11 13:49:04 +0100434
435 printf("CPU time: %g s (+/- %g %)\n", median, meddev);
436 printf("Ratio: %g\n", ratio);
437
438 return 0;
439}