VNC: Add support for capturing frame buffer to file each time it is changed.
authorChris Emmons <chris.emmons@arm.com>
Thu, 1 Dec 2011 08:15:26 +0000 (00:15 -0800)
committerChris Emmons <chris.emmons@arm.com>
Thu, 1 Dec 2011 08:15:26 +0000 (00:15 -0800)
When a change in the frame buffer from the VNC server is detected, the new
frame is stored out to the m5out/frames_*/ directory.  Specifiy the flag
"--frame-capture" when running configs/example/fs.py to enable this behavior.

--HG--
extra : rebase_source : d4e08e83f4fa6ff79f3dc9c433fc1f0487e057fc

configs/example/fs.py
src/base/bitmap.cc
src/base/bitmap.hh
src/base/vnc/VncServer.py
src/base/vnc/convert.cc
src/base/vnc/convert.hh
src/base/vnc/vncserver.cc
src/base/vnc/vncserver.hh

index b8f50fc90becb48fffc4d11be4f4a35a704b53be..5945e5d9b6398a2f83785da572308808b784e3f4 100644 (file)
@@ -72,6 +72,10 @@ parser.add_option("--timesync", action="store_true",
 # System options
 parser.add_option("--kernel", action="store", type="string")
 parser.add_option("--script", action="store", type="string")
+parser.add_option("--frame-capture", action="store_true",
+        help="Stores changed frame buffers from the VNC server to compressed "\
+        "files in the gem5 output directory")
+
 if buildEnv['TARGET_ISA'] == "arm":
     parser.add_option("--bare-metal", action="store_true",
                help="Provide the raw system without the linux specific bits")
@@ -205,4 +209,7 @@ else:
 if options.timesync:
     root.time_sync_enable = True
 
+if options.frame_capture:
+    VncServer.frame_capture = True
+
 Simulation.run(options, root, test_sys, FutureClass)
index 0d2a9302bab89d94d7275277c7c3c905ba39d1f4..08425d74f1f317e4da2df1aacd992603c4e0aa9c 100644 (file)
@@ -36,6 +36,7 @@
  *
  * Authors: William Wang
  *          Ali Saidi
+ *          Chris Emmons
  */
 
 #include <cassert>
 #include "base/bitmap.hh"
 #include "base/misc.hh"
 
+const size_t Bitmap::sizeofHeaderBuffer = sizeof(Magic) + sizeof(Header) +
+                                        sizeof(Info);
+
 // bitmap class ctor
 Bitmap::Bitmap(VideoConvert::Mode _mode, uint16_t w, uint16_t h, uint8_t *d)
     : mode(_mode), height(h), width(w), data(d),
-    vc(mode, VideoConvert::rgb8888, width, height)
+    vc(mode, VideoConvert::rgb8888, width, height), headerBuffer(0)
 {
 }
 
+Bitmap::~Bitmap() {
+    if (headerBuffer)
+        delete [] headerBuffer;
+}
+
 void
-Bitmap::write(std::ostream *bmp)
+Bitmap::write(std::ostream *bmp) const
 {
     assert(data);
 
-    // For further information see: http://en.wikipedia.org/wiki/BMP_file_format
-    Magic  magic = {{'B','M'}};
-    Header header = {sizeof(VideoConvert::Rgb8888) * width * height , 0, 0, 54};
-    Info   info = {sizeof(Info), width, height, 1,
-                   sizeof(VideoConvert::Rgb8888) * 8, 0,
-                   sizeof(VideoConvert::Rgb8888) * width * height, 1, 1, 0, 0};
+    // header is always the same for a bitmap object; compute the info once per
+    //   bitmap object
+    if (!headerBuffer) {
+        // For further information see:
+        //   http://en.wikipedia.org/wiki/BMP_file_format
+        Magic magic = {{'B','M'}};
+        Header header = {sizeof(VideoConvert::Rgb8888) * width * height,
+                                0, 0, 54};
+        Info info = {sizeof(Info), width, height, 1,
+                    sizeof(VideoConvert::Rgb8888) * 8, 0,
+                    sizeof(VideoConvert::Rgb8888) * width * height, 1, 1, 0, 0};
+
+        char *p = headerBuffer = new char[sizeofHeaderBuffer];
+        memcpy(p, &magic, sizeof(Magic));
+        p += sizeof(Magic);
+        memcpy(p, &header, sizeof(Header));
+        p += sizeof(Header);
+        memcpy(p, &info,   sizeof(Info));
+    }
 
-    bmp->write(reinterpret_cast<char*>(&magic),  sizeof(magic));
-    bmp->write(reinterpret_cast<char*>(&header), sizeof(header));
-    bmp->write(reinterpret_cast<char*>(&info),   sizeof(info));
+    // 1.  write the header
+    bmp->write(headerBuffer, sizeofHeaderBuffer);
 
+    // 2.  write the bitmap data
     uint8_t *tmp = vc.convert(data);
     uint32_t *tmp32 = (uint32_t*)tmp;
 
index 9dfaa87a1c26ab8c5b0ea254616e44ed3c0df6e7..e9ad154732070af60e03ffa1c0ed961666d38e40 100644 (file)
@@ -36,6 +36,7 @@
  *
  * Authors: William Wang
  *          Ali Saidi
+ *          Chris Emmons
  */
 #ifndef __BASE_BITMAP_HH__
 #define __BASE_BITMAP_HH__
@@ -62,6 +63,9 @@ class  Bitmap
      */
     Bitmap(VideoConvert::Mode mode, uint16_t w, uint16_t h, uint8_t *d);
 
+    /** Destructor */
+    ~Bitmap();
+
     /** Provide the converter with the data that should be output. It will be
      * converted into rgb8888 and write out when write() is called.
      * @param d the data
@@ -71,7 +75,13 @@ class  Bitmap
     /** Write the provided data into the fstream provided
      * @param bmp stream to write to
      */
-    void write(std::ostream *bmp);
+    void write(std::ostream *bmp) const;
+
+    /** Gets a hash over the bitmap for quick comparisons to other bitmaps.
+     * @return hash of the bitmap
+     */
+    uint64_t getHash() const { return vc.getHash(data); }
+
 
   private:
     VideoConvert::Mode mode;
@@ -81,6 +91,9 @@ class  Bitmap
 
     VideoConvert vc;
 
+    mutable char *headerBuffer;
+    static const size_t sizeofHeaderBuffer;
+
     struct Magic
     {
         unsigned char magic_number[2];
index 21eb3ed28a55106a9d8bb10f0f6617c782baf0a5..6b746f2e222cd391195019f323da6b479a0a6003 100644 (file)
@@ -43,3 +43,4 @@ class VncServer(SimObject):
     type = 'VncServer'
     port = Param.TcpPort(5900, "listen port")
     number = Param.Int(0, "vnc client number")
+    frame_capture = Param.Bool(False, "capture changed frames to files")
index cd1502ce6b7868cd8a104a62dc038d31d8232165..915a99407e72fdf20fb4d6fe19340265b2c5e53f 100644 (file)
@@ -67,7 +67,7 @@ VideoConvert::~VideoConvert()
 }
 
 uint8_t*
-VideoConvert::convert(uint8_t *fb)
+VideoConvert::convert(const uint8_t *fb) const
 {
     switch (inputMode) {
       case bgr565:
@@ -82,7 +82,7 @@ VideoConvert::convert(uint8_t *fb)
 }
 
 uint8_t*
-VideoConvert::m565rgb8888(uint8_t *fb, bool bgr)
+VideoConvert::m565rgb8888(const uint8_t *fb, bool bgr) const
 {
     uint8_t *out = new uint8_t[area() * sizeof(uint32_t)];
     uint32_t *out32 = (uint32_t*)out;
@@ -113,7 +113,7 @@ VideoConvert::m565rgb8888(uint8_t *fb, bool bgr)
 
 
 uint8_t*
-VideoConvert::bgr8888rgb8888(uint8_t *fb)
+VideoConvert::bgr8888rgb8888(const uint8_t *fb) const
 {
     uint8_t *out = new uint8_t[area() * sizeof(uint32_t)];
     uint32_t *out32 = (uint32_t*)out;
@@ -136,4 +136,21 @@ VideoConvert::bgr8888rgb8888(uint8_t *fb)
 
     return out;
 }
+/*
+uint64_t
+VideoConvert::getHash(const uint8_t *fb) const
+{
+    const uint8_t *fb_e = fb + area();
+
+    uint64_t hash = 1;
+    while (fb < fb_e - 8) {
+        hash += *((const uint64_t*)fb);
+        fb += 8;
+    }
+
+    while (fb < fb_e) {
+        hash += *(fb++);
+    }
 
+    return hash;
+}*/
index 68a21d677c57a4b7b38f5c0c4c8245687a372fe3..17df0747b94e3da542081663072b9dc350aee165 100644 (file)
@@ -44,6 +44,7 @@
 #ifndef __BASE_VNC_CONVERT_HH__
 #define __BASE_VNC_CONVERT_HH__
 
+#include <zlib.h>
 #include "base/bitunion.hh"
 
 class VideoConvert
@@ -107,12 +108,21 @@ class VideoConvert
      * @param fb the frame buffer to convert
      * @return the converted data (user must free)
      */
-    uint8_t* convert(uint8_t *fb);
+    uint8_t* convert(const uint8_t *fb) const;
 
     /** Return the number of pixels that this buffer specifies
      * @return number of pixels
      */
-    int area() { return width * height; }
+    int area() const { return width * height; }
+
+    /**
+     * Returns a hash on the raw data.
+     *
+     * @return hash of the buffer
+     */
+    inline uint64_t getHash(const uint8_t *fb) const {
+        return adler32(0UL, fb, width * height);
+    }
 
   private:
 
@@ -121,7 +131,7 @@ class VideoConvert
      * @param fb the data to convert
      * @return converted data
      */
-    uint8_t* bgr8888rgb8888(uint8_t *fb);
+    uint8_t* bgr8888rgb8888(const uint8_t *fb) const;
 
     /**
      * Convert a bgr565 or rgb565 input to rgb8888.
@@ -129,7 +139,7 @@ class VideoConvert
      * @param bgr true if the input data is bgr565
      * @return converted data
      */
-    uint8_t* m565rgb8888(uint8_t *fb, bool bgr);
+    uint8_t* m565rgb8888(const uint8_t *fb, bool bgr) const;
 
     Mode inputMode;
     Mode outputMode;
index 18e581bfec859164da8758b2f08744a57973bc0c..b4a783219af6b34e5a52298aa3d84893cafa034c 100644 (file)
  */
 
 #include <sys/ioctl.h>
+#include <sys/stat.h>
 #include <sys/termios.h>
+#include <sys/types.h>
+#include <fcntl.h>
 #include <poll.h>
 #include <unistd.h>
 
 
 #include "base/vnc/vncserver.hh"
 #include "base/atomicio.hh"
+#include "base/bitmap.hh"
 #include "base/misc.hh"
+#include "base/output.hh"
 #include "base/socket.hh"
 #include "base/trace.hh"
 #include "debug/VNC.hh"
 #include "sim/byteswap.hh"
+#include "sim/core.hh"
 
 using namespace std;
 
@@ -98,14 +104,14 @@ VncServer::VncServer(const Params *p)
     : SimObject(p), listenEvent(NULL), dataEvent(NULL), number(p->number),
       dataFd(-1), _videoWidth(1), _videoHeight(1), clientRfb(0), keyboard(NULL),
       mouse(NULL), sendUpdate(false), videoMode(VideoConvert::UnknownMode),
-      vc(NULL)
+      vc(NULL), captureEnabled(p->frame_capture), captureCurrentFrame(0),
+      captureLastHash(0), captureBitmap(0)
 {
     if (p->port)
         listen(p->port);
 
     curState = WaitForProtocolVersion;
 
-
     // currently we only support this one pixel format
     // unpacked 32bit rgb (rgb888 + 8 bits of nothing/alpha)
     // keep it around for telling the client and making
@@ -121,6 +127,14 @@ VncServer::VncServer(const Params *p)
     pixelFormat.greenshift = 8;
     pixelFormat.blueshift = 0;
 
+    if (captureEnabled) {
+        // remove existing frame output directory if it exists, then create a
+        //   clean empty directory
+        const string FRAME_OUTPUT_SUBDIR = "frames_" + name();
+        simout.remove(FRAME_OUTPUT_SUBDIR, true);
+        captureOutputDirectory = simout.createSubdirectory(
+                                FRAME_OUTPUT_SUBDIR);
+    }
 
     DPRINTF(VNC, "Vnc server created at port %d\n", p->port);
 }
@@ -686,6 +700,16 @@ VncServer::setFrameBufferParams(VideoConvert::Mode mode, int width, int height)
         vc = new VideoConvert(mode, VideoConvert::rgb8888, videoWidth(),
                 videoHeight());
 
+        if (captureEnabled) {
+            // create bitmap of the frame with new attributes
+            if (captureBitmap)
+                delete captureBitmap;
+
+            assert(clientRfb);
+            captureBitmap = new Bitmap(videoMode, width, height, clientRfb);
+            assert(captureBitmap);
+        }
+
         if (dataFd > 0 && clientRfb && curState == NormalPhase) {
             if (supportsResizeEnc)
                 sendFrameBufferResized();
@@ -702,3 +726,29 @@ VncServerParams::create()
 {
     return new VncServer(this);
 }
+
+void
+VncServer::captureFrameBuffer()
+{
+    assert(captureBitmap);
+
+    // skip identical frames
+    uint64_t new_hash = captureBitmap->getHash();
+    if (captureLastHash == new_hash)
+        return;
+    captureLastHash = new_hash;
+
+    // get the filename for the current frame
+    char frameFilenameBuffer[64];
+    snprintf(frameFilenameBuffer, 64, "fb.%06d.%lld.bmp.gz",
+            captureCurrentFrame, static_cast<long long int>(curTick()));
+    const string frameFilename(frameFilenameBuffer);
+
+    // create the compressed framebuffer file
+    ostream *fb_out = simout.create(captureOutputDirectory + frameFilename,
+                    true);
+    captureBitmap->write(fb_out);
+    simout.close(fb_out);
+
+    ++captureCurrentFrame;
+}
index 96dbdeddaf865d960c0c3984f284cb39e7f53bad..33d833f263caedfdae2dac58f5d75df5a535a24b 100644 (file)
@@ -48,6 +48,7 @@
 #include <iostream>
 
 #include "base/vnc/convert.hh"
+#include "base/bitmap.hh"
 #include "base/circlebuf.hh"
 #include "base/pollevent.hh"
 #include "base/socket.hh"
@@ -55,6 +56,7 @@
 #include "params/VncServer.hh"
 #include "sim/sim_object.hh"
 
+
 /**
  * A device that expects to receive input from the vnc server should derrive
  * (through mulitple inheritence if necessary from VncKeyboard or VncMouse
@@ -316,7 +318,25 @@ class VncServer : public SimObject
     /** The video converter that transforms data for us */
     VideoConvert *vc;
 
+    /** Flag indicating whether to capture snapshots of frame buffer or not */
+    bool captureEnabled;
+
+    /** Current frame number being captured to a file */
+    int captureCurrentFrame;
+
+    /** Directory to store captured frames to */
+    std::string captureOutputDirectory;
+
+    /** Computed hash of the last captured frame */
+    uint64_t captureLastHash;
+
+    /** Cached bitmap object for writing out frame buffers to file */
+    Bitmap *captureBitmap;
+
   protected:
+    /** Captures the current frame buffer to a file */
+    void captureFrameBuffer();
+
     /**
      * vnc client Interface
      */
@@ -449,6 +469,8 @@ class VncServer : public SimObject
     setDirty()
     {
         sendUpdate = true;
+        if (captureEnabled)
+            captureFrameBuffer();
         sendFrameBufferUpdate();
     }