blob: cbb4788660800c51f405b143eb17ce4ea5f4dd6a [file] [log] [blame]
Michael Schuh5a1a7582019-03-01 13:03:47 -08001#include <sys/stat.h>
Philipp Schrader790cb542023-07-05 21:06:52 -07002
Michael Schuh5a1a7582019-03-01 13:03:47 -08003#include <deque>
4#include <fstream>
5#include <string>
6
Philipp Schrader790cb542023-07-05 21:06:52 -07007#include "gflags/gflags.h"
8
Michael Schuh5a1a7582019-03-01 13:03:47 -08009#include "aos/logging/implementations.h"
10#include "aos/logging/logging.h"
11#include "aos/vision/blob/codec.h"
12#include "aos/vision/events/socket_types.h"
13#include "aos/vision/events/udp.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070014#include "aos/vision/image/image_stream.h"
Michael Schuh5a1a7582019-03-01 13:03:47 -080015#include "aos/vision/image/reader.h"
Tyler Chatowe0241452019-03-08 21:07:50 -080016#include "y2019/image_streamer/flip_image.h"
Michael Schuh5a1a7582019-03-01 13:03:47 -080017#include "y2019/vision.pb.h"
18
Philipp Schrader790cb542023-07-05 21:06:52 -070019using ::aos::monotonic_clock;
Michael Schuh5a1a7582019-03-01 13:03:47 -080020using ::aos::events::DataSocket;
21using ::aos::events::RXUdpSocket;
22using ::aos::events::TCPServer;
23using ::aos::vision::DataRef;
24using ::aos::vision::Int32Codec;
Michael Schuh5a1a7582019-03-01 13:03:47 -080025using ::y2019::VisionControl;
26
27DEFINE_string(roborio_ip, "10.9.71.2", "RoboRIO IP Address");
28DEFINE_string(log, "",
29 "If non-empty, log images to the specified prefix with the image "
30 "index appended to the filename");
31DEFINE_bool(single_camera, true, "If true, only use video0");
32DEFINE_int32(camera0_exposure, 600, "Exposure for video0");
33DEFINE_int32(camera1_exposure, 600, "Exposure for video1");
34
35aos::vision::DataRef mjpg_header =
36 "HTTP/1.0 200 OK\r\n"
37 "Server: YourServerName\r\n"
38 "Connection: close\r\n"
39 "Max-Age: 0\r\n"
40 "Expires: 0\r\n"
41 "Cache-Control: no-cache, private\r\n"
42 "Pragma: no-cache\r\n"
43 "Content-Type: multipart/x-mixed-replace; "
44 "boundary=--boundary\r\n\r\n";
45
46struct Frame {
47 std::string data;
48};
49
50inline bool FileExist(const std::string &name) {
51 struct stat buffer;
52 return (stat(name.c_str(), &buffer) == 0);
53}
54
55class BlobLog {
56 public:
57 explicit BlobLog(const char *prefix, const char *extension) {
58 int index = 0;
59 while (true) {
60 std::string file = prefix + std::to_string(index) + extension;
61 if (FileExist(file)) {
62 index++;
63 } else {
64 printf("Logging to file (%s)\n", file.c_str());
65 ofst_.open(file);
66 assert(ofst_.is_open());
67 break;
68 }
69 }
70 }
71
72 ~BlobLog() { ofst_.close(); }
73
74 void WriteLogEntry(DataRef data) { ofst_.write(&data[0], data.size()); }
75
76 private:
77 std::ofstream ofst_;
78};
79
80class UdpClient : public ::aos::events::EpollEvent {
81 public:
82 UdpClient(int port, ::std::function<void(void *, size_t)> callback)
83 : ::aos::events::EpollEvent(RXUdpSocket::SocketBindListenOnPort(port)),
84 callback_(callback) {}
85
86 private:
87 ::std::function<void(void *, size_t)> callback_;
88
89 void ReadEvent() override {
90 char data[1024];
91 size_t received_data_size = Recv(data, sizeof(data));
92 callback_(data, received_data_size);
93 }
94
95 size_t Recv(void *data, int size) {
Austin Schuhf257f3c2019-10-27 21:00:43 -070096 return AOS_PCHECK(recv(fd(), static_cast<char *>(data), size, 0));
Michael Schuh5a1a7582019-03-01 13:03:47 -080097 }
98};
99
100// TODO(aschuh & michael) Pull this out.
101template <typename PB>
102class ProtoUdpClient : public UdpClient {
103 public:
104 ProtoUdpClient(int port, ::std::function<void(const PB &)> proto_callback)
105 : UdpClient(port, ::std::bind(&ProtoUdpClient::ReadData, this,
106 ::std::placeholders::_1,
107 ::std::placeholders::_2)),
108 proto_callback_(proto_callback) {}
109
110 private:
111 ::std::function<void(const PB &)> proto_callback_;
112
113 void ReadData(void *data, size_t size) {
114 PB pb;
Brian Silverman05271722019-03-09 16:09:31 -0800115 // TODO(Brian): Do something useful if parsing fails.
Michael Schuh5a1a7582019-03-01 13:03:47 -0800116 pb.ParseFromArray(data, size);
117 proto_callback_(pb);
118 }
119};
120
121class MjpegDataSocket : public aos::events::SocketConnection {
122 public:
123 MjpegDataSocket(aos::events::TCPServerBase *server, int fd)
124 : aos::events::SocketConnection(server, fd) {
125 SetEvents(EPOLLOUT | EPOLLET);
126 }
127
128 ~MjpegDataSocket() { printf("Closed connection on descriptor %d\n", fd()); }
129
130 void DirectEvent(uint32_t events) override {
131 if (events & EPOLLOUT) {
132 NewDataToSend();
133 events &= ~EPOLLOUT;
134 }
135 // Other end hung up. Ditch the connection.
136 if (events & EPOLLHUP) {
137 CloseConnection();
138 events &= ~EPOLLHUP;
139 return;
140 }
141 if (events) {
142 aos::events::EpollEvent::DirectEvent(events);
143 }
144 }
145
146 void ReadEvent() override {
Michael Schuh5a1a7582019-03-01 13:03:47 -0800147 ssize_t count;
148 char buf[512];
149 while (true) {
Brian Silverman05271722019-03-09 16:09:31 -0800150 // Always read everything so epoll won't return immediately.
Michael Schuh5a1a7582019-03-01 13:03:47 -0800151 count = read(fd(), &buf, sizeof buf);
152 if (count <= 0) {
153 if (errno != EAGAIN) {
154 CloseConnection();
155 return;
156 }
157 break;
158 } else if (!ready_to_receive_) {
159 // This 4 should match "\r\n\r\n".length();
160 if (match_i_ >= 4) {
161 printf("reading after last match\n");
162 continue;
163 }
164 for (char c : aos::vision::DataRef(&buf[0], count)) {
165 if (c == "\r\n\r\n"[match_i_]) {
166 ++match_i_;
167 if (match_i_ >= 4) {
168 if (!ready_to_receive_) {
169 ready_to_receive_ = true;
170 RasterHeader();
171 }
172 }
173 } else if (match_i_ != 0) {
174 if (c == '\r') match_i_ = 1;
175 }
176 }
177 }
178 }
179 }
180
181 void RasterHeader() {
182 output_buffer_.push_back(mjpg_header);
183 NewDataToSend();
184 }
185
186 void RasterFrame(std::shared_ptr<Frame> frame) {
187 if (!output_buffer_.empty() || !ready_to_receive_) return;
188 sending_frame_ = frame;
189 aos::vision::DataRef data = frame->data;
190
191 size_t n_written = snprintf(data_header_tmp_, sizeof(data_header_tmp_),
192 "--boundary\r\n"
193 "Content-type: image/jpg\r\n"
194 "Content-Length: %zu\r\n\r\n",
195 data.size());
196 // This should never happen because the buffer should be properly sized.
197 if (n_written == sizeof(data_header_tmp_)) {
198 fprintf(stderr, "wrong sized buffer\n");
199 exit(-1);
200 }
Austin Schuhf257f3c2019-10-27 21:00:43 -0700201 AOS_LOG(INFO, "Frame size in bytes: data.size() = %zu\n", data.size());
Michael Schuh5a1a7582019-03-01 13:03:47 -0800202 output_buffer_.push_back(aos::vision::DataRef(data_header_tmp_, n_written));
203 output_buffer_.push_back(data);
204 output_buffer_.push_back("\r\n\r\n");
205 NewDataToSend();
206 }
207
208 void NewFrame(std::shared_ptr<Frame> frame) { RasterFrame(std::move(frame)); }
209
210 void NewDataToSend() {
211 while (!output_buffer_.empty()) {
212 auto &data = *output_buffer_.begin();
213
214 while (!data.empty()) {
215 int len = send(fd(), data.data(), data.size(), MSG_NOSIGNAL);
216 if (len == -1) {
217 if (errno == EAGAIN) {
218 // Next thinggy will pick this up.
219 return;
220 } else {
221 CloseConnection();
222 return;
223 }
224 } else {
225 data.remove_prefix(len);
226 }
227 }
228 output_buffer_.pop_front();
229 }
230 }
231
232 private:
233 char data_header_tmp_[512];
234 std::shared_ptr<Frame> sending_frame_;
235 std::deque<aos::vision::DataRef> output_buffer_;
236
237 bool ready_to_receive_ = false;
238 void CloseConnection() {
239 loop()->Delete(this);
240 close(fd());
241 delete this;
242 }
243 size_t match_i_ = 0;
244};
245
246class CameraStream : public ::aos::vision::ImageStreamEvent {
247 public:
248 CameraStream(::aos::vision::CameraParams params, const ::std::string &fname,
249 TCPServer<MjpegDataSocket> *tcp_server, bool log,
250 ::std::function<void()> frame_callback)
251 : ImageStreamEvent(fname, params),
252 tcp_server_(tcp_server),
253 frame_callback_(frame_callback) {
254 if (log) {
255 log_.reset(new BlobLog(FLAGS_log.c_str(), ".dat"));
256 }
257 }
258
259 void set_active(bool active) { active_ = active; }
260
Tyler Chatowe0241452019-03-08 21:07:50 -0800261 void set_flip(bool flip) { flip_ = flip; }
262
Michael Schuh5a1a7582019-03-01 13:03:47 -0800263 bool active() const { return active_; }
264
265 void ProcessImage(DataRef data,
266 monotonic_clock::time_point /*monotonic_now*/) {
267 ++sampling;
268 // 20 is the sampling rate.
269 if (sampling == 20) {
270 int tmp_size = data.size() + sizeof(int32_t);
271 char *buf;
272 std::string log_record;
273 log_record.resize(tmp_size, 0);
274 {
275 buf = Int32Codec::Write(&log_record[0], tmp_size);
276 data.copy(buf, data.size());
277 }
278 if (log_) {
279 log_->WriteLogEntry(log_record);
280 }
281 sampling = 0;
282 }
283
Austin Schuh9c03a532019-03-17 18:14:45 -0700284 ::std::string image_out;
Tyler Chatowe0241452019-03-08 21:07:50 -0800285
Austin Schuh9c03a532019-03-17 18:14:45 -0700286 unsigned int out_size = image_buffer_out_.size();
287 flip_image(data.data(), data.size(), &image_buffer_out_[0], &out_size,
288 flip_);
289 image_out.assign(&image_buffer_out_[0], &image_buffer_out_[out_size]);
Tyler Chatowe0241452019-03-08 21:07:50 -0800290
Michael Schuh5a1a7582019-03-01 13:03:47 -0800291 if (active_) {
Tyler Chatowe0241452019-03-08 21:07:50 -0800292 auto frame = std::make_shared<Frame>(Frame{image_out});
Michael Schuh5a1a7582019-03-01 13:03:47 -0800293 tcp_server_->Broadcast(
294 [frame](MjpegDataSocket *event) { event->NewFrame(frame); });
295 }
296 frame_callback_();
297 }
298
299 private:
300 int sampling = 0;
301 TCPServer<MjpegDataSocket> *tcp_server_;
302 ::std::unique_ptr<BlobLog> log_;
303 ::std::function<void()> frame_callback_;
304 bool active_ = false;
Tyler Chatowe0241452019-03-08 21:07:50 -0800305 bool flip_ = false;
306 std::array<JOCTET, 100000> image_buffer_out_;
Michael Schuh5a1a7582019-03-01 13:03:47 -0800307};
308
309int main(int argc, char **argv) {
310 gflags::ParseCommandLineFlags(&argc, &argv, false);
Michael Schuh5a1a7582019-03-01 13:03:47 -0800311 TCPServer<MjpegDataSocket> tcp_server_(80);
312 aos::vision::CameraParams params0;
313 params0.set_exposure(FLAGS_camera0_exposure);
314 params0.set_brightness(-40);
315 params0.set_width(320);
316 // params0.set_fps(10);
317 params0.set_height(240);
318
319 aos::vision::CameraParams params1 = params0;
320 params1.set_exposure(FLAGS_camera1_exposure);
321
322 ::y2019::VisionStatus vision_status;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700323 AOS_LOG(INFO,
324 "The UDP socket should be on port 5001 to 10.9.71.2 for "
325 "the competition robot.\n");
326 AOS_LOG(INFO, "Starting UDP socket on port 5001 to %s\n",
327 FLAGS_roborio_ip.c_str());
Michael Schuh5a1a7582019-03-01 13:03:47 -0800328 ::aos::events::ProtoTXUdpSocket<::y2019::VisionStatus> status_socket(
329 FLAGS_roborio_ip.c_str(), 5001);
330
331 ::std::unique_ptr<CameraStream> camera1;
332 ::std::unique_ptr<CameraStream> camera0(new CameraStream(
333 params0, "/dev/video0", &tcp_server_, !FLAGS_log.empty(),
Brian Silverman05271722019-03-09 16:09:31 -0800334 [&camera0, &status_socket, &vision_status]() {
Michael Schuh5a1a7582019-03-01 13:03:47 -0800335 vision_status.set_low_frame_count(vision_status.low_frame_count() + 1);
Austin Schuhf257f3c2019-10-27 21:00:43 -0700336 AOS_LOG(INFO, "Got a frame cam0\n");
Michael Schuh5a1a7582019-03-01 13:03:47 -0800337 if (camera0->active()) {
338 status_socket.Send(vision_status);
339 }
340 }));
341 if (!FLAGS_single_camera) {
342 camera1.reset(new CameraStream(
343 params1, "/dev/video1", &tcp_server_, false,
Brian Silverman05271722019-03-09 16:09:31 -0800344 [&camera1, &status_socket, &vision_status]() {
Michael Schuh5a1a7582019-03-01 13:03:47 -0800345 vision_status.set_high_frame_count(vision_status.high_frame_count() +
346 1);
Austin Schuhf257f3c2019-10-27 21:00:43 -0700347 AOS_LOG(INFO, "Got a frame cam1\n");
Michael Schuh5a1a7582019-03-01 13:03:47 -0800348 if (camera1->active()) {
349 status_socket.Send(vision_status);
350 }
351 }));
352 }
353
354 ProtoUdpClient<VisionControl> udp_client(
355 5000, [&camera0, &camera1](const VisionControl &vision_control) {
356 bool cam0_active = false;
Tyler Chatowe0241452019-03-08 21:07:50 -0800357 camera0->set_flip(vision_control.flip_image());
Michael Schuh5a1a7582019-03-01 13:03:47 -0800358 if (camera1) {
Tyler Chatowe0241452019-03-08 21:07:50 -0800359 camera1->set_flip(vision_control.flip_image());
Michael Schuh5a1a7582019-03-01 13:03:47 -0800360 cam0_active = !vision_control.high_video();
361 camera0->set_active(!vision_control.high_video());
362 camera1->set_active(vision_control.high_video());
363 } else {
364 cam0_active = true;
365 camera0->set_active(true);
366 }
Austin Schuhf257f3c2019-10-27 21:00:43 -0700367 AOS_LOG(INFO, "Got control packet, cam%d active\n",
368 cam0_active ? 0 : 1);
Michael Schuh5a1a7582019-03-01 13:03:47 -0800369 });
370
371 // Default to camera0
372 camera0->set_active(true);
373 if (camera1) {
374 camera1->set_active(false);
375 }
376
377 aos::events::EpollLoop loop;
378 loop.Add(&tcp_server_);
379 loop.Add(camera0.get());
380 if (camera1) {
381 loop.Add(camera1.get());
382 }
383 loop.Add(&udp_client);
384
385 printf("Running Camera\n");
386 loop.Run();
387}