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