Changes to aos/vision/debug in preparation for the competition.

- blob_log-source: Support multiple key-commands for seeking to a target
 also added JustCheckForTarget which can be implemented to run faster
 for slow computer (will implement in followup).
- camera-source + debug_framework: Takes in camera parameters from the
caller.
- debug_window -> Scale text up on smaller monitors.
- overlay.h: Ben changed width to measure point to point on the cross.
  And added circles to the pixel-lines overlay.
- tcp-source: Drop packects when the computer can't keep up.
- debug_framework -> detect smaller monitors and scale down the viewport.

Change-Id: Iae65a0799231d006b38694a8a9cb3894e252033c
diff --git a/aos/vision/debug/blob_log-source.cc b/aos/vision/debug/blob_log-source.cc
index f2bfbb5..337a764 100644
--- a/aos/vision/debug/blob_log-source.cc
+++ b/aos/vision/debug/blob_log-source.cc
@@ -9,12 +9,28 @@
 #include <string>
 
 #include "aos/vision/blob/codec.h"
+#include "aos/vision/blob/stream_view.h"
+#include "aos/vision/debug/overlay.h"
 
 namespace aos {
 namespace vision {
 
 namespace {
 
+__attribute__((format(printf, 1, 2))) std::string SPrintf(const char *format,
+                                                          ...) {
+  va_list arglist;
+  va_start(arglist, format);
+  size_t count = vsnprintf(nullptr, 0, format, arglist);
+  va_end(arglist);
+
+  char out[count + 1];
+  va_start(arglist, format);
+  vsnprintf(out, count + 1, format, arglist);
+  va_end(arglist);
+  return std::string(&out[0], &out[count]);
+}
+
 long GetFileSize(const std::string &filename) {
   struct stat stat_buf;
   int rc = stat(filename.c_str(), &stat_buf);
@@ -45,7 +61,8 @@
   }
 
   bool ReadNext(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
-    if (buf_ - &tmp_buf_[0] >= len_) return false;
+    if (buf_ - &tmp_buf_[0] + sizeof(uint32_t) >= static_cast<size_t>(len_))
+      return false;
     if (prev_ != nullptr) prev_frames_.emplace_back(prev_);
     prev_ = buf_;
     DoRead(blob_list, fmt, timestamp);
@@ -56,7 +73,6 @@
     if (prev_frames_.empty()) return false;
     buf_ = prev_frames_.back();
     prev_frames_.pop_back();
-    buf_ += sizeof(uint32_t);
     DoRead(blob_list, fmt, timestamp);
     prev_ = nullptr;
     return true;
@@ -64,7 +80,11 @@
 
  private:
   void DoRead(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
-    buf_ += sizeof(uint32_t);
+    uint32_t size_delta = Int32Codec::Read(&buf_);
+    if (buf_ - &tmp_buf_[0] + size_delta > len_) {
+      fprintf(stderr, "Corrupted last record.\n");
+      exit(-1);
+    }
     *timestamp = Int64Codec::Read(&buf_);
     fmt->w = Int32Codec::Read(&buf_);
     fmt->h = Int32Codec::Read(&buf_);
@@ -132,6 +152,60 @@
   std::function<bool()> callback_;
 };
 
+class FrameStats {
+ public:
+  void UpdateStats(int64_t timestamp, bool has_target) {
+    if (has_target != last_has_target_) {
+      if (has_target && timestamp > last_timestamp_) {
+        ++frame_count_;
+      }
+      if (!has_target && timestamp < last_timestamp_) {
+        --frame_count_;
+      }
+    }
+    if (has_target && first_frame_timestamp_ == -1) {
+      first_frame_timestamp_ = timestamp;
+    }
+
+    last_timestamp_ = timestamp;
+    last_has_target_ = has_target;
+  }
+
+  void SetStartTimestamp(int64_t start_timestamp) {
+    start_timestamp_ = start_timestamp;
+  }
+
+  std::string Summary() const {
+    return SPrintf(" frame_count: %ld\n time_since_first_target: %7.3f",
+                   frame_count_,
+                   (last_timestamp_ - GetStartTimestamp()) / 1.0e9);
+  }
+
+  int64_t GetStartTimestamp() const {
+    if (first_frame_timestamp_ != -1) return first_frame_timestamp_;
+    return start_timestamp_;
+  }
+
+ private:
+  int64_t start_timestamp_;
+  int64_t frame_count_ = 0;
+  int64_t first_frame_timestamp_ = -1;
+  int64_t last_timestamp_ = -1;
+  bool last_has_target_ = 0;
+};
+
+// TODO: display this on the screen when they press help.
+const char *kHudText2 = &R"(
+commands:
+ h - display this message.
+ space - pause / unpause at normal speed (No extra mode).
+ s - Skip forward fast to the next target.
+ p - Skip backwards fast to the previous target.
+ left_arrow - single-step backwards.
+ right_arrow - single-step forwards.
+
+)"[1];
+
 class BlobLogImageSource : public ImageSource {
  public:
   void Init(const std::string &blob_log_filename,
@@ -144,26 +218,95 @@
     cb_.Reset(1000 / 25, [this]() { return Tick(); });
 
     frame_.ReadNext(image_source_.get());
+    start_timestamp_ = frame_.timestamp;
+    frame_stats_.SetStartTimestamp(start_timestamp_);
+
     interface_->NewBlobList(frame_.blob_list, frame_.fmt);
     interface_->InstallKeyPress([this](uint32_t keyval) {
       if (keyval == GDK_KEY_Left) {
-        frame_.ReadPrev(image_source_.get());
-        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        ReadPrevFrame();
+        bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        frame_stats_.UpdateStats(frame_.timestamp, has_target);
       } else if (keyval == GDK_KEY_Right) {
-        frame_.ReadNext(image_source_.get());
-        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        ReadNextFrame();
+        bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        frame_stats_.UpdateStats(frame_.timestamp, has_target);
+      } else if (keyval == GDK_KEY_space) {
+        if (mode_ != PAUSED) {
+          mode_ = PAUSED;
+        } else {
+          mode_ = NORMAL_MODE;
+        }
+      } else if (keyval == GDK_KEY_s) {
+        controller_.reset(new FastForwardUntilFrameController(this));
+        mode_ = FAST_MODE;
+      } else if (keyval == GDK_KEY_p) {
+        controller_.reset(new FastForwardUntilFrameController(this));
+        mode_ = FAST_MODE_REV;
       } else {
         return;
       }
     });
+    interface_->viewer()->AddOverlay(&overlay_);
+    overlay_.draw_fn = [this](RenderInterface *cr, double w, double h) {
+      (void)w;
+      (void)h;
+      cr->SetSourceRGB(1, 0, 0);
+      auto text = SPrintf(" time: %7.3f\n",
+                          (frame_.timestamp - start_timestamp_) / 1.0e9);
+      text += frame_stats_.Summary();
+      cr->Text(2, h - 100, 0, 0, text);
+    };
   }
 
   bool Tick() {
-    frame_.ReadNext(image_source_.get());
-    interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+    if (need_timestamp_print_) {
+      fprintf(stderr, "time: %g\n",
+              (frame_.timestamp - start_timestamp_) / 1.0e9);
+      need_timestamp_print_ = false;
+    }
+    for (int i = 0; i < GetSpeed(); ++i) {
+      if (Direction()) {
+        ReadNextFrame();
+      } else {
+        ReadPrevFrame();
+      }
+      bool has_target =
+          interface_->JustCheckForTarget(frame_.blob_list, frame_.fmt);
+      frame_stats_.UpdateStats(frame_.timestamp, has_target);
+      if (controller_) {
+        controller_->NewFrame(has_target);
+      }
+      // Draw on the last frame:
+      if (i + 1 >= GetSpeed()) {
+        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+      }
+    }
     return true;
   }
 
+  int GetSpeed() {
+    if (mode_ == PAUSED) return 0;
+    if (mode_ == NORMAL_MODE) return 1;
+    if (mode_ == FAST_MODE || mode_ == FAST_MODE_REV) return 60;
+    return 0;
+  }
+
+  bool Direction() {
+    if (mode_ == FAST_MODE_REV) return false;
+    return true;
+  }
+
+  void ReadNextFrame() {
+    frame_.ReadNext(image_source_.get());
+    need_timestamp_print_ = true;
+  }
+
+  void ReadPrevFrame() {
+    frame_.ReadPrev(image_source_.get());
+    need_timestamp_print_ = true;
+  }
+
   const char *GetHelpMessage() override {
     return &R"(
     format_spec is the name of a file in blob list format.
@@ -172,10 +315,50 @@
   }
 
  private:
+  bool need_timestamp_print_ = true;
+  uint64_t start_timestamp_ = 0;
+
+  class Controller {
+   public:
+    virtual ~Controller() {}
+    virtual void NewFrame(bool has_target) = 0;
+  };
+
+  class FastForwardUntilFrameController : public Controller {
+   public:
+    FastForwardUntilFrameController(BlobLogImageSource *proxy)
+        : proxy_(proxy) {}
+
+    void NewFrame(bool has_target) override {
+      if (!has_target) inside_target = false;
+      if (!inside_target && has_target) {
+        proxy_->mode_ = PAUSED;
+        proxy_->controller_.reset(nullptr);
+      }
+    }
+
+    BlobLogImageSource *proxy_;
+    bool inside_target = true;
+  };
+
+  std::unique_ptr<Controller> controller_;
+
+  FrameStats frame_stats_;
+
+  enum Mode {
+    PAUSED,
+    NORMAL_MODE,
+    FAST_MODE,
+    FAST_MODE_REV,
+  };
+  Mode mode_ = PAUSED;
+
+  // LambdaOverlay text_overlay_;
   TimeoutCallback cb_;
   DebugFrameworkInterface *interface_ = nullptr;
   std::unique_ptr<InputFile> image_source_;
   BlobStreamFrame frame_;
+  LambdaOverlay overlay_;
 };
 
 REGISTER_IMAGE_SOURCE("blob_log", BlobLogImageSource);
diff --git a/aos/vision/debug/camera-source.cc b/aos/vision/debug/camera-source.cc
index 77bd6ed..5eeabfc 100644
--- a/aos/vision/debug/camera-source.cc
+++ b/aos/vision/debug/camera-source.cc
@@ -14,17 +14,17 @@
  public:
   void Init(const std::string &jpeg_list_filename,
             DebugFrameworkInterface *interface) override {
-    // TODO: Get these params from a config file passed in through the
-    // constructor.
-    image_stream_.reset(new ImageStream(
-        jpeg_list_filename, aos::vision::CameraParams(), interface));
+    // TODO: These camera params make this ugly and less generic.
+    image_stream_.reset(new ImageStream(jpeg_list_filename,
+                                        interface->camera_params(), interface));
   }
 
   const char *GetHelpMessage() override {
     return &R"(
     format_spec is filename of the camera device.
     example: camera:/dev/video0
-    This viewer source will stream video from a usb camera of your choice.)"[1];
+    This viewer source will stream video from a usb camera of your choice.
+)"[1];
   }
 
   class ImageStream : public ImageStreamEvent {
@@ -38,9 +38,9 @@
         // Takes a picture when you press 'a'.
         // TODO(parker): Allow setting directory.
         if (keyval == GDK_KEY_a) {
-          std::ofstream ofs(
-              std::string("/tmp/out_jpegs/test") + std::to_string(i_) + ".jpg",
-              std::ofstream::out);
+          std::ofstream ofs(std::string("/tmp/debug_viewer_jpeg_") +
+                                std::to_string(i_) + ".jpg",
+                            std::ofstream::out);
           ofs << prev_data_;
           ofs.close();
           ++i_;
diff --git a/aos/vision/debug/debug_framework.cc b/aos/vision/debug/debug_framework.cc
index 64f370c..d4ba0d2 100644
--- a/aos/vision/debug/debug_framework.cc
+++ b/aos/vision/debug/debug_framework.cc
@@ -12,6 +12,27 @@
 namespace aos {
 namespace vision {
 
+// Detect screen height on smaller monitors.
+int GetScreenHeight() {
+  fprintf(stderr, "gtk version_info: %d.%d.%d\n", gtk_get_major_version(),
+          gtk_get_minor_version(), gtk_get_micro_version());
+
+  GdkScreen *screen = gdk_screen_get_default();
+  GdkRectangle dimensions;
+// Deprecated in newer versions of GTK and missing from older versions.
+#if GTK_CHECK_VERSION(3, 22, 7)
+  GdkDisplay *display = gdk_screen_get_display(screen);
+  GdkMonitor *monitor = gdk_display_get_primary_monitor(display);
+  gdk_monitor_get_geometry(monitor, &dimensions);
+#else
+  dimensions.height = gdk_screen_get_height(screen);
+  dimensions.width = gdk_screen_get_width(screen);
+#endif
+  fprintf(stdout, "Monitor dimensions: %dx%d\n", dimensions.width,
+          dimensions.height);
+  return dimensions.height;
+}
+
 bool DecodeJpeg(aos::vision::DataRef data,
                 aos::vision::BlobStreamViewer *view) {
   auto fmt = aos::vision::GetFmt(data);
@@ -24,37 +45,55 @@
 
 class DebugFramework : public DebugFrameworkInterface {
  public:
-  explicit DebugFramework(FilterHarness *filter) : filter_(filter) {
+  explicit DebugFramework(FilterHarness *filter, CameraParams camera_params)
+      : camera_params_(camera_params), filter_(filter) {
     view_.key_press_event = [this](uint32_t keyval) {
       for (const auto &event : key_press_events()) {
         event(keyval);
       }
     };
     filter->InstallViewer(&view_);
+    auto key_press = filter->RegisterKeyPress();
+    if (key_press) {
+      InstallKeyPress(key_press);
+    }
+    if (GetScreenHeight() < 1024) {
+      view_.SetScale(0.75);
+    }
   }
 
   // This the first stage in the pipeline that takes
-  void NewJpeg(DataRef data) override {
+  bool NewJpeg(DataRef data) override {
     DecodeJpeg(data, &view_);
 
     auto fmt = view_.img().fmt();
-    HandleBlobs(FindBlobs(filter_->Threshold(view_.img())), fmt);
+    return HandleBlobs(FindBlobs(filter_->Threshold(view_.img())), fmt);
   }
 
-  void NewBlobList(BlobList blob_list, ImageFormat fmt) override {
+  bool NewBlobList(BlobList blob_list, ImageFormat fmt) override {
     view_.SetFormatAndClear(fmt);
 
-    HandleBlobs(std::move(blob_list), fmt);
+    return HandleBlobs(std::move(blob_list), fmt);
   }
 
-  void HandleBlobs(BlobList blob_list, ImageFormat fmt) {
-    filter_->HandleBlobs(std::move(blob_list), fmt);
+  bool JustCheckForTarget(BlobList blob_list, ImageFormat fmt) override {
+    return filter_->JustCheckForTarget(std::move(blob_list), fmt);
+  }
+
+  bool HandleBlobs(BlobList blob_list, ImageFormat fmt) {
+    bool result = filter_->HandleBlobs(std::move(blob_list), fmt);
     view_.Redraw();
+    return result;
   }
 
   aos::events::EpollLoop *Loop() override { return &loop_; }
 
+  const CameraParams &camera_params() override { return camera_params_; }
+
+  BlobStreamViewer *viewer() override { return &view_; }
+
  private:
+  CameraParams camera_params_;
   FilterHarness *filter_;
   BlobStreamViewer view_;
 
@@ -93,7 +132,8 @@
 parameter. A single command line argument help will print this message.
 )";
 
-void DebugFrameworkMain(int argc, char **argv, FilterHarness *filter) {
+void DebugFrameworkMain(int argc, char **argv, FilterHarness *filter,
+                        CameraParams camera_params) {
   ::aos::logging::Init();
   ::aos::logging::AddImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
@@ -116,7 +156,7 @@
     exit(-1);
   }
 
-  DebugFramework replay(filter);
+  DebugFramework replay(filter, camera_params);
 
   std::unique_ptr<ImageSource> image_source = MakeImageSource(argv[1], &replay);
 
diff --git a/aos/vision/debug/debug_framework.h b/aos/vision/debug/debug_framework.h
index 2f0fcd1..e838913 100644
--- a/aos/vision/debug/debug_framework.h
+++ b/aos/vision/debug/debug_framework.h
@@ -4,6 +4,7 @@
 #include "aos/common/util/global_factory.h"
 #include "aos/vision/blob/range_image.h"
 #include "aos/vision/events/epoll_events.h"
+#include "aos/vision/image/camera_params.pb.h"
 #include "aos/vision/image/image_types.h"
 
 namespace aos {
@@ -27,6 +28,17 @@
 
   // One frame worth of blobs. Returns if the frame is "interesting".
   virtual bool HandleBlobs(BlobList imgs, ImageFormat fmt) = 0;
+
+  // One frame worth of blobs. Returns if the frame is "interesting".
+  // Fast version that does no drawing.
+  virtual bool JustCheckForTarget(BlobList imgs, ImageFormat fmt) {
+    return HandleBlobs(std::move(imgs), fmt);
+  }
+
+  // Register key press handler.
+  virtual std::function<void(uint32_t)> RegisterKeyPress() {
+    return std::function<void(uint32_t)>();
+  }
 };
 
 // For ImageSource implementations only. Allows registering key press events
@@ -39,13 +51,21 @@
     key_press_events_.emplace_back(std::move(key_press_event));
   }
 
-  virtual void NewJpeg(DataRef data) = 0;
+  // The return value bool here for all of these is
+  // if the frame is "interesting" ie has a target.
+  virtual bool NewJpeg(DataRef data) = 0;
 
-  virtual void NewBlobList(BlobList blob_list, ImageFormat fmt) = 0;
+  virtual bool NewBlobList(BlobList blob_list, ImageFormat fmt) = 0;
+
+  virtual bool JustCheckForTarget(BlobList imgs, ImageFormat fmt) = 0;
 
   // Expose a EpollLoop to allow waiting for events.
   virtual aos::events::EpollLoop *Loop() = 0;
 
+  virtual const CameraParams &camera_params() = 0;
+
+  virtual BlobStreamViewer *viewer() = 0;
+
  protected:
   const std::vector<std::function<void(uint32_t)>> &key_press_events() {
     return key_press_events_;
@@ -77,7 +97,8 @@
 
 // Runs loop and never returns.
 // Feeds into a generic filter.
-void DebugFrameworkMain(int argc, char **argv, FilterHarness *filter);
+void DebugFrameworkMain(int argc, char **argv, FilterHarness *filter,
+                        CameraParams camera_params);
 
 }  // namespace vision
 }  // namespace aos
diff --git a/aos/vision/debug/debug_window.cc b/aos/vision/debug/debug_window.cc
index acbaf8e..4729939 100644
--- a/aos/vision/debug/debug_window.cc
+++ b/aos/vision/debug/debug_window.cc
@@ -48,13 +48,19 @@
     if (overlays) {
       for (const auto &ov : *overlays) {
         cairo_save(cr);
-        CairoRender render(cr);
-        // move the drawing to match the window size
+        CairoRender render(cr, 1.0 / scale_factor);
         ov->Draw(&render, w, h);
         cairo_restore(cr);
       }
     }
 
+    for (const auto &ov : overlays_internal_) {
+      cairo_save(cr);
+      CairoRender render(cr, 1.0 / scale_factor);
+      ov->Draw(&render, w, h);
+      cairo_restore(cr);
+    }
+
     return FALSE;
   }
 
@@ -64,6 +70,7 @@
   bool needs_draw = true;
   GtkWidget *window;
   std::vector<OverlayBase *> *overlays = nullptr;
+  std::vector<OverlayBase *> overlays_internal_;
   double scale_factor;
 
   // flip the image rows on drawing
@@ -77,6 +84,10 @@
   self->overlays = overlays;
 }
 
+void DebugWindow::AddOverlay(OverlayBase *overlay) {
+  self->overlays_internal_.push_back(overlay);
+}
+
 void DebugWindow::Redraw() {
   if (!self->needs_draw) {
     gtk_widget_queue_draw(self->drawing_area);
@@ -158,11 +169,15 @@
 
 void CairoRender::Text(int x, int y, int /*text_x*/, int /*text_y*/,
                        const std::string &text) {
-  auto *pango_lay = pango_cairo_create_layout(cr_);
+  cairo_save(cr_);
   cairo_move_to(cr_, x, y);
+  cairo_scale(cr_, text_scale_, text_scale_);
+
+  auto *pango_lay = pango_cairo_create_layout(cr_);
   pango_layout_set_text(pango_lay, text.data(), text.size());
   pango_cairo_show_layout(cr_, pango_lay);
   g_object_unref(pango_lay);
+  cairo_restore(cr_);
 }
 
 }  // namespace vision
diff --git a/aos/vision/debug/debug_window.h b/aos/vision/debug/debug_window.h
index 906bf3b..aa31a8c 100644
--- a/aos/vision/debug/debug_window.h
+++ b/aos/vision/debug/debug_window.h
@@ -12,7 +12,8 @@
 // Implement Cairo version of RenderInterface.
 class CairoRender : public RenderInterface {
  public:
-  explicit CairoRender(cairo_t *cr) : cr_(cr) {}
+  explicit CairoRender(cairo_t *cr, double text_scale)
+      : cr_(cr), text_scale_(text_scale) {}
   virtual ~CairoRender() {}
 
   void Translate(double x, double y) override { cairo_translate(cr_, x, y); }
@@ -36,6 +37,7 @@
 
  private:
   cairo_t *cr_;
+  double text_scale_ = 1.0;
 };
 
 // Simple debug view window.
@@ -55,6 +57,13 @@
   // See overlay.h for more info.
   void SetOverlays(std::vector<OverlayBase *> *overlay);
 
+  void AddOverlay(OverlayBase *overlay);
+  void AddOverlays(const std::vector<OverlayBase *> &overlays) {
+    for (auto *overlay : overlays) {
+      AddOverlay(overlay);
+    }
+  }
+
   // Resizes the window.
   void SetScale(double scale_factor);
 
diff --git a/aos/vision/debug/overlay.h b/aos/vision/debug/overlay.h
index 5a9804f..0122c94 100644
--- a/aos/vision/debug/overlay.h
+++ b/aos/vision/debug/overlay.h
@@ -128,10 +128,10 @@
   void DrawCross(aos::vision::Vector<2> center, int width,
                  aos::vision::PixelRef color) {
     using namespace aos::vision;
-    AddLine(Vector<2>(center.x() - width, center.y()),
-            Vector<2>(center.x() + width, center.y()), color);
-    AddLine(Vector<2>(center.x(), center.y() - width),
-            Vector<2>(center.x(), center.y() + width), color);
+    AddLine(Vector<2>(center.x() - width / 2, center.y()),
+            Vector<2>(center.x() + width / 2, center.y()), color);
+    AddLine(Vector<2>(center.x(), center.y() - width / 2),
+            Vector<2>(center.x(), center.y() + width / 2), color);
   }
 
   void DrawBBox(const ImageBBox &box, aos::vision::PixelRef color) {
@@ -146,6 +146,12 @@
             color);
   }
 
+  // Build a circle as a point and radius.
+  void DrawCircle(Vector<2> center, double radius, PixelRef newColor) {
+    circles_.emplace_back(std::pair<Vector<2>, std::pair<double, PixelRef>>(
+        center, std::pair<double, PixelRef>(radius, newColor)));
+  }
+
   void StartNewProfile() { start_profile = true; }
 
   // add a new point connected to the last point in the line
@@ -171,14 +177,25 @@
       render->LineTo(ln.first.B().x(), ln.first.B().y());
       render->Stroke();
     }
+    for (const auto &circle : circles_) {
+      PixelRef localColor = circle.second.second;
+      render->SetSourceRGB(localColor.r / 255.0, localColor.g / 255.0,
+                           localColor.b / 255.0);
+      render->Circle(circle.first.x(), circle.first.y(), circle.second.first);
+      render->Stroke();
+    }
   }
 
   // Empting the list will blank the whole overlay.
-  void Reset() override { lines_.clear(); }
+  void Reset() override {
+    lines_.clear();
+    circles_.clear();
+  }
 
  private:
   // Lines in this overlay.
   std::vector<std::pair<Segment<2>, PixelRef>> lines_;
+  std::vector<std::pair<Vector<2>, std::pair<double, PixelRef>>> circles_;
   bool start_profile = false;
 };
 
diff --git a/aos/vision/debug/tcp-source.cc b/aos/vision/debug/tcp-source.cc
index 37c38d5..b95e189 100644
--- a/aos/vision/debug/tcp-source.cc
+++ b/aos/vision/debug/tcp-source.cc
@@ -15,6 +15,9 @@
 namespace aos {
 namespace vision {
 
+// Reads packets in the form:
+// uint32 length
+// string text (of size length)
 class BufferedLengthDelimReader {
  public:
   union data_len {
@@ -27,33 +30,44 @@
   }
   template <typename Lamb>
   void up(int fd, Lamb lam) {
-    ssize_t count;
-    if (img_read_ < 0) {
-      count = read(fd, &len_.buf[num_read_], sizeof(len_.buf) - num_read_);
-      if (count < 0) return;
-      num_read_ += count;
-      if (num_read_ < 4) return;
-      num_read_ = 0;
-      img_read_ = 0;
-      data_.clear();
-      if (len_.len > 200000) {
-        printf("bad size: %d\n", len_.len);
-        exit(-1);
+    // Drops older messages with this while loop until we've seeked to
+    // be current.
+    while (true) {
+      ssize_t count;
+      if (img_read_ < 0) {
+        count = read(fd, &len_.buf[num_read_], sizeof(len_.buf) - num_read_);
+        if (count < 0) break;
+        num_read_ += count;
+        if (num_read_ < 4) break;
+        num_read_ = 0;
+        img_read_ = 0;
+        data_.clear();
+        if (len_.len > 200000) {
+          printf("bad size: %d\n", len_.len);
+          exit(-1);
+        }
+        data_.resize(len_.len);
+      } else {
+        count = read(fd, &data_[img_read_], len_.len - img_read_);
+        if (count < 0) break;
+        img_read_ += count;
+        if (img_read_ < (int)len_.len) return;
+        std::swap(prev_data_, data_);
+        has_prev_ = true;
+        img_read_ = -1;
       }
-      data_.resize(len_.len);
-    } else {
-      count = read(fd, &data_[img_read_], len_.len - img_read_);
-      if (count < 0) return;
-      img_read_ += count;
-      if (img_read_ < (int)len_.len) return;
-      lam(DataRef{&data_[0], len_.len});
-      img_read_ = -1;
+    }
+    if (has_prev_) {
+      lam(DataRef{&prev_data_[0], len_.len});
+      has_prev_ = false;
     }
   }
 
  private:
   data_len len_;
   int num_read_;
+  bool has_prev_ = false;
+  std::vector<char> prev_data_;
   std::vector<char> data_;
   int img_read_;
 };
@@ -92,6 +106,10 @@
         buf = ParseBlobList(&blobl, buf);
         interface_->NewBlobList(blobl, fmt);
       });
+      if (errno != EAGAIN) {
+        fprintf(stderr, "disconnected\n");
+        loop()->Delete(this);
+      }
     }
 
     BufferedLengthDelimReader read_;
diff --git a/y2017/vision/debug_viewer.cc b/y2017/vision/debug_viewer.cc
index 83f306f..10dd57e 100644
--- a/y2017/vision/debug_viewer.cc
+++ b/y2017/vision/debug_viewer.cc
@@ -125,5 +125,6 @@
 
 int main(int argc, char **argv) {
   y2017::vision::FilterHarnessExample filter_harness;
-  aos::vision::DebugFrameworkMain(argc, argv, &filter_harness);
+  aos::vision::DebugFrameworkMain(argc, argv, &filter_harness,
+                                  aos::vision::CameraParams());
 }