Implement optional per-frame CRC calculation to save bandwidth

Current implementation is very CPU-efficient as it's not processing
JPEG-encoded data received from V4L2 at all.

This patch implements an optional mode where for each frame that's about
to be sent a CRC32 is calculated and if this client has already received
it before, the transmission is skipped. This of course adds some
noticeable CPU load (proportional to the frame rate requested and the
encoded JPEG size); on AST2500 it's taking about 10 % CPU when showing a
relatively complex GUI login screen with 15 FPS.

Reducing required bandwidth to 0 for static images helps a lot when
using IP KVM via poor connections, e.g. provided by cellular network
operators or some hotels, so can be beneficial for certain common
usecases.

The next optimisation to try is to dynamically alter the frame rate,
automatically ramping it up as soon as the changes start happening and
lowering after a period of inactivity; it's not yet clear if V4L2 allows
this.

Signed-off-by: Paul Fertser <fercerpav@gmail.com>
Change-Id: I74b887cf83b5c8676f5792412805de08e1a54f32
diff --git a/ikvm_args.cpp b/ikvm_args.cpp
index ad5b4f7..21373aa 100644
--- a/ikvm_args.cpp
+++ b/ikvm_args.cpp
@@ -8,15 +8,17 @@
 namespace ikvm
 {
 
-Args::Args(int argc, char* argv[]) : frameRate(30), commandLine(argc, argv)
+Args::Args(int argc, char* argv[]) : frameRate(30), calcFrameCRC{false},
+                                     commandLine(argc, argv)
 {
     int option;
-    const char* opts = "f:h:k:p:v:";
+    const char* opts = "f:h:k:p:v:c";
     struct option lopts[] = {{"frameRate", 1, 0, 'f'},
                              {"help", 0, 0, 'h'},
                              {"keyboard", 1, 0, 'k'},
                              {"mouse", 1, 0, 'p'},
                              {"videoDevice", 1, 0, 'v'},
+                             {"calcCRC", 0, 0, 'c'},
                              {0, 0, 0, 0}};
 
     while ((option = getopt_long(argc, argv, opts, lopts, NULL)) != -1)
@@ -40,6 +42,9 @@
             case 'v':
                 videoPath = std::string(optarg);
                 break;
+            case 'c':
+                calcFrameCRC = true;
+                break;
         }
     }
 }
@@ -54,6 +59,7 @@
     fprintf(stderr, "-k device              HID keyboard gadget device\n");
     fprintf(stderr, "-p device              HID mouse gadget device\n");
     fprintf(stderr, "-v device              V4L2 device\n");
+    fprintf(stderr, "-c, --calcCRC          Calculate CRC for each frame to save bandwidth\n");
     rfbUsage();
 }
 
diff --git a/ikvm_args.hpp b/ikvm_args.hpp
index f877d32..fbe19ad 100644
--- a/ikvm_args.hpp
+++ b/ikvm_args.hpp
@@ -101,6 +101,16 @@
         return videoPath;
     }
 
+    /*
+     * @brief Get the identical frames detection setting
+     *
+     * @return True if identical frames detection is enabled
+     */
+    inline bool getCalcFrameCRC() const
+    {
+        return calcFrameCRC;
+    }
+
   private:
     /* @brief Prints the application usage to stderr */
     void printUsage();
@@ -116,6 +126,8 @@
     std::string pointerPath;
     /* @brief Path to the V4L2 video device */
     std::string videoPath;
+    /* @brief Identical frames detection */
+    bool calcFrameCRC;
     /* @brief Original command line arguments passed to the application */
     CommandLine commandLine;
 };
diff --git a/ikvm_server.cpp b/ikvm_server.cpp
index 7be99e4..804eb39 100644
--- a/ikvm_server.cpp
+++ b/ikvm_server.cpp
@@ -7,6 +7,8 @@
 #include <phosphor-logging/log.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 
+#include <boost/crc.hpp>
+
 namespace ikvm
 {
 
@@ -54,6 +56,8 @@
     server->ptrAddEvent = Input::pointerEvent;
 
     processTime = (1000000 / video.getFrameRate()) - 100;
+
+    calcFrameCRC = args.getCalcFrameCRC();
 }
 
 Server::~Server()
@@ -93,6 +97,7 @@
     char* data = video.getData();
     rfbClientIteratorPtr it;
     rfbClientPtr cl;
+    int64_t frame_crc = -1;
 
     if (!data || pendingResize)
     {
@@ -121,6 +126,26 @@
         {
             continue;
         }
+
+        if (calcFrameCRC)
+        {
+            if (frame_crc == -1)
+            {
+                /* JFIF header contains some varying data so skip it for
+                 * checksum calculation */
+                frame_crc = boost::crc<32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF,
+                                       true, true>(data + 0x30,
+                                                   video.getFrameSize() - 0x30);
+            }
+
+            if (cd->last_crc == frame_crc)
+            {
+                continue;
+            }
+
+            cd->last_crc = frame_crc;
+        }
+
         cd->needUpdate = false;
 
         if (cl->enableLastRectEncoding)
diff --git a/ikvm_server.hpp b/ikvm_server.hpp
index ebe4ad2..bfeb73b 100644
--- a/ikvm_server.hpp
+++ b/ikvm_server.hpp
@@ -30,7 +30,7 @@
          * @param[in] s - Number of frames to skip when client connects
          * @param[in] i - Pointer to Input object
          */
-        ClientData(int s, Input* i) : skipFrame(s), input(i)
+        ClientData(int s, Input* i) : skipFrame(s), input(i), last_crc{-1}
         {
             needUpdate = false;
         }
@@ -43,6 +43,7 @@
         int skipFrame;
         Input* input;
         bool needUpdate;
+        int64_t last_crc;
     };
 
     /*
@@ -127,6 +128,8 @@
     Video& video;
     /* @brief Default framebuffer storage */
     std::vector<char> framebuffer;
+    /* @brief Identical frames detection */
+    bool calcFrameCRC;
     /* @brief Cursor bitmap width */
     static constexpr int cursorWidth = 20;
     /* @brief Cursor bitmap height */