Parker Schuh | 9064111 | 2017-02-25 12:18:36 -0800 | [diff] [blame] | 1 | #include "aos/vision/debug/debug_framework.h" |
| 2 | |
| 3 | #include <gdk/gdk.h> |
| 4 | #include <gtk/gtk.h> |
| 5 | #include <sys/stat.h> |
| 6 | #include <unistd.h> |
| 7 | #include <fstream> |
| 8 | #include <functional> |
| 9 | #include <string> |
| 10 | |
| 11 | #include "aos/vision/blob/codec.h" |
| 12 | |
| 13 | namespace aos { |
| 14 | namespace vision { |
| 15 | |
| 16 | namespace { |
| 17 | |
Parker Schuh | ef47dbf | 2017-03-04 16:59:30 -0800 | [diff] [blame^] | 18 | long GetFileSize(const std::string &filename) { |
Parker Schuh | 9064111 | 2017-02-25 12:18:36 -0800 | [diff] [blame] | 19 | struct stat stat_buf; |
| 20 | int rc = stat(filename.c_str(), &stat_buf); |
| 21 | return rc == 0 ? stat_buf.st_size : -1; |
| 22 | } |
| 23 | |
| 24 | // Parses the blob-log file format. |
| 25 | // File format goes: |
| 26 | // |
| 27 | // Repeated: |
| 28 | // |
| 29 | // frame_length. |
| 30 | // timestamp. |
| 31 | // fmt.w |
| 32 | // fmt.h |
| 33 | // Encoded blob. |
| 34 | class InputFile { |
| 35 | public: |
| 36 | InputFile(const std::string &fname) |
| 37 | : ifs_(fname, std::ifstream::in), len_(GetFileSize(fname)) { |
| 38 | if (len_ <= 0) { |
| 39 | LOG(FATAL, "File (%s) not found. Size (%d)\n", fname.c_str(), (int)len_); |
| 40 | } |
| 41 | // assert(len_ > 0); |
| 42 | tmp_buf_.resize(len_, 0); |
| 43 | ifs_.read(&tmp_buf_[0], len_); |
| 44 | buf_ = &tmp_buf_[0]; |
| 45 | } |
| 46 | |
| 47 | bool ReadNext(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) { |
| 48 | if (buf_ - &tmp_buf_[0] >= len_) return false; |
| 49 | if (prev_ != nullptr) prev_frames_.emplace_back(prev_); |
| 50 | prev_ = buf_; |
| 51 | DoRead(blob_list, fmt, timestamp); |
| 52 | return true; |
| 53 | } |
| 54 | |
| 55 | bool ReadPrev(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) { |
| 56 | if (prev_frames_.empty()) return false; |
| 57 | buf_ = prev_frames_.back(); |
| 58 | prev_frames_.pop_back(); |
| 59 | buf_ += sizeof(uint32_t); |
| 60 | DoRead(blob_list, fmt, timestamp); |
| 61 | prev_ = nullptr; |
| 62 | return true; |
| 63 | } |
| 64 | |
| 65 | private: |
| 66 | void DoRead(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) { |
| 67 | buf_ += sizeof(uint32_t); |
| 68 | *timestamp = Int64Codec::Read(&buf_); |
| 69 | fmt->w = Int32Codec::Read(&buf_); |
| 70 | fmt->h = Int32Codec::Read(&buf_); |
| 71 | buf_ = ParseBlobList(blob_list, buf_); |
| 72 | } |
| 73 | std::vector<const char *> prev_frames_; |
| 74 | const char *buf_; |
| 75 | const char *prev_ = nullptr; |
| 76 | std::ifstream ifs_; |
| 77 | |
| 78 | long len_; |
| 79 | std::vector<char> tmp_buf_; |
| 80 | }; |
| 81 | |
| 82 | // A single parsed frame. |
| 83 | class BlobStreamFrame { |
| 84 | public: |
| 85 | BlobList blob_list; |
| 86 | ImageFormat fmt; |
| 87 | uint64_t timestamp; |
| 88 | void ReadNext(InputFile *fin) { |
| 89 | blob_list.clear(); |
| 90 | if (!fin->ReadNext(&blob_list, &fmt, ×tamp)) { |
| 91 | exit(0); |
| 92 | return; |
| 93 | } |
| 94 | } |
| 95 | bool ReadPrev(InputFile *fin) { |
| 96 | blob_list.clear(); |
| 97 | return fin->ReadPrev(&blob_list, &fmt, ×tamp); |
| 98 | } |
| 99 | }; |
| 100 | |
| 101 | } // namespace |
| 102 | |
| 103 | // class for installing a lambda as a gtk timeout. |
| 104 | class TimeoutCallback { |
| 105 | public: |
| 106 | TimeoutCallback() {} |
| 107 | |
| 108 | void Reset(guint32 interval, std::function<bool()> callback) { |
| 109 | Stop(); |
| 110 | callback_ = callback; |
| 111 | timeout_key_ = g_timeout_add(interval, &TimeoutCallback::Callback, this); |
| 112 | } |
| 113 | void Stop() { |
| 114 | if (callback_) { |
| 115 | g_source_remove(timeout_key_); |
| 116 | } |
| 117 | callback_ = std::function<bool()>(); |
| 118 | } |
| 119 | |
| 120 | private: |
| 121 | static gint Callback(void *self) { |
| 122 | return reinterpret_cast<TimeoutCallback *>(self)->Callback(); |
| 123 | } |
| 124 | gint Callback() { |
| 125 | auto callback = callback_; |
| 126 | if (!callback()) { |
| 127 | return FALSE; |
| 128 | } |
| 129 | return TRUE; |
| 130 | } |
| 131 | gint timeout_key_; |
| 132 | std::function<bool()> callback_; |
| 133 | }; |
| 134 | |
| 135 | class BlobLogImageSource : public ImageSource { |
| 136 | public: |
| 137 | void Init(const std::string &blob_log_filename, |
| 138 | DebugFrameworkInterface *interface) override { |
| 139 | interface_ = interface; |
| 140 | image_source_.reset(new InputFile(blob_log_filename)); |
| 141 | |
| 142 | // Tick 25 fps. |
| 143 | // TODO(parker): Make this FPS configurable. |
| 144 | cb_.Reset(1000 / 25, [this]() { return Tick(); }); |
| 145 | |
| 146 | frame_.ReadNext(image_source_.get()); |
| 147 | interface_->NewBlobList(frame_.blob_list, frame_.fmt); |
| 148 | interface_->InstallKeyPress([this](uint32_t keyval) { |
| 149 | if (keyval == GDK_KEY_Left) { |
| 150 | frame_.ReadPrev(image_source_.get()); |
| 151 | interface_->NewBlobList(frame_.blob_list, frame_.fmt); |
| 152 | } else if (keyval == GDK_KEY_Right) { |
| 153 | frame_.ReadNext(image_source_.get()); |
| 154 | interface_->NewBlobList(frame_.blob_list, frame_.fmt); |
| 155 | } else { |
| 156 | return; |
| 157 | } |
| 158 | }); |
| 159 | } |
| 160 | |
| 161 | bool Tick() { |
| 162 | frame_.ReadNext(image_source_.get()); |
| 163 | interface_->NewBlobList(frame_.blob_list, frame_.fmt); |
| 164 | return true; |
| 165 | } |
| 166 | |
| 167 | const char *GetHelpMessage() override { |
| 168 | return &R"( |
| 169 | format_spec is the name of a file in blob list format. |
| 170 | This viewer source will stream blobs from the log. |
| 171 | )"[1]; |
| 172 | } |
| 173 | |
| 174 | private: |
| 175 | TimeoutCallback cb_; |
| 176 | DebugFrameworkInterface *interface_ = nullptr; |
| 177 | std::unique_ptr<InputFile> image_source_; |
| 178 | BlobStreamFrame frame_; |
| 179 | }; |
| 180 | |
| 181 | REGISTER_IMAGE_SOURCE("blob_log", BlobLogImageSource); |
| 182 | |
| 183 | } // namespace vision |
| 184 | } // namespace aos |