blob: 26e7ca159a30a271bde1c966db969ce49170ce66 [file] [log] [blame]
Austin Schuh8d5fff42018-05-30 20:44:12 -07001#include <sys/stat.h>
Philipp Schrader790cb542023-07-05 21:06:52 -07002
Austin Schuh8d5fff42018-05-30 20:44:12 -07003#include <deque>
4#include <fstream>
5#include <string>
6
Philipp Schrader790cb542023-07-05 21:06:52 -07007#include "gflags/gflags.h"
8
John Park33858a32018-09-28 23:05:48 -07009#include "aos/logging/implementations.h"
10#include "aos/logging/logging.h"
Parker Schuhc1975fc2018-04-07 15:27:07 -070011#include "aos/vision/blob/codec.h"
Austin Schuh8d5fff42018-05-30 20:44:12 -070012#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"
Austin Schuh8d5fff42018-05-30 20:44:12 -070015#include "aos/vision/image/reader.h"
Austin Schuh8d5fff42018-05-30 20:44:12 -070016#include "y2018/vision.pb.h"
Parker Schuhc1975fc2018-04-07 15:27:07 -070017
Philipp Schrader790cb542023-07-05 21:06:52 -070018using ::aos::monotonic_clock;
Austin Schuh8d5fff42018-05-30 20:44:12 -070019using ::aos::events::DataSocket;
20using ::aos::events::RXUdpSocket;
21using ::aos::events::TCPServer;
22using ::aos::vision::DataRef;
23using ::aos::vision::Int32Codec;
Austin Schuh8d5fff42018-05-30 20:44:12 -070024using ::y2018::VisionControl;
Parker Schuhc1975fc2018-04-07 15:27:07 -070025
Austin Schuh8d5fff42018-05-30 20:44:12 -070026DEFINE_bool(single_camera, true, "If true, only use video0");
27DEFINE_int32(camera0_exposure, 300, "Exposure for video0");
28DEFINE_int32(camera1_exposure, 300, "Exposure for video1");
29
30aos::vision::DataRef mjpg_header =
31 "HTTP/1.0 200 OK\r\n"
32 "Server: YourServerName\r\n"
33 "Connection: close\r\n"
34 "Max-Age: 0\r\n"
35 "Expires: 0\r\n"
36 "Cache-Control: no-cache, private\r\n"
37 "Pragma: no-cache\r\n"
38 "Content-Type: multipart/x-mixed-replace; "
39 "boundary=--boundary\r\n\r\n";
Parker Schuhc1975fc2018-04-07 15:27:07 -070040
41struct Frame {
42 std::string data;
43};
44
45inline bool FileExist(const std::string &name) {
46 struct stat buffer;
47 return (stat(name.c_str(), &buffer) == 0);
48}
49
50class BlobLog {
51 public:
52 explicit BlobLog(const char *prefix, const char *extension) {
53 int index = 0;
54 while (true) {
55 std::string file = prefix + std::to_string(index) + extension;
56 if (FileExist(file)) {
57 index++;
58 } else {
59 printf("Logging to file (%s)\n", file.c_str());
60 ofst_.open(file);
61 assert(ofst_.is_open());
62 break;
63 }
64 }
65 }
66
67 ~BlobLog() { ofst_.close(); }
68
69 void WriteLogEntry(DataRef data) { ofst_.write(&data[0], data.size()); }
70
71 private:
72 std::ofstream ofst_;
73};
74
Austin Schuh8d5fff42018-05-30 20:44:12 -070075class UdpClient : public ::aos::events::EpollEvent {
76 public:
77 UdpClient(int port, ::std::function<void(void *, size_t)> callback)
78 : ::aos::events::EpollEvent(RXUdpSocket::SocketBindListenOnPort(port)),
79 callback_(callback) {}
80
81 private:
82 ::std::function<void(void *, size_t)> callback_;
83
84 void ReadEvent() override {
85 char data[1024];
86 size_t received_data_size = Recv(data, sizeof(data));
87 callback_(data, received_data_size);
88 }
89
90 size_t Recv(void *data, int size) {
Austin Schuhf257f3c2019-10-27 21:00:43 -070091 return AOS_PCHECK(recv(fd(), static_cast<char *>(data), size, 0));
Austin Schuh8d5fff42018-05-30 20:44:12 -070092 }
93};
94
95template <typename PB>
96class ProtoUdpClient : public UdpClient {
97 public:
98 ProtoUdpClient(int port, ::std::function<void(const PB &)> proto_callback)
99 : UdpClient(port, ::std::bind(&ProtoUdpClient::ReadData, this,
100 ::std::placeholders::_1,
101 ::std::placeholders::_2)),
102 proto_callback_(proto_callback) {}
103
104 private:
105 ::std::function<void(const PB &)> proto_callback_;
106
107 void ReadData(void *data, size_t size) {
108 PB pb;
109 pb.ParseFromArray(data, size);
110 proto_callback_(pb);
111 }
112};
113
Parker Schuhc1975fc2018-04-07 15:27:07 -0700114class MjpegDataSocket : public aos::events::SocketConnection {
115 public:
Austin Schuh8d5fff42018-05-30 20:44:12 -0700116 MjpegDataSocket(aos::events::TCPServerBase *server, int fd)
117 : aos::events::SocketConnection(server, fd) {
Parker Schuhc1975fc2018-04-07 15:27:07 -0700118 SetEvents(EPOLLOUT | EPOLLET);
119 }
120
121 ~MjpegDataSocket() { printf("Closed connection on descriptor %d\n", fd()); }
122
123 void DirectEvent(uint32_t events) override {
124 if (events & EPOLLOUT) {
125 NewDataToSend();
126 events &= ~EPOLLOUT;
127 }
Austin Schuh8d5fff42018-05-30 20:44:12 -0700128 // Other end hung up. Ditch the connection.
129 if (events & EPOLLHUP) {
130 CloseConnection();
131 events &= ~EPOLLHUP;
132 return;
133 }
Parker Schuhc1975fc2018-04-07 15:27:07 -0700134 if (events) {
135 aos::events::EpollEvent::DirectEvent(events);
136 }
137 }
138
139 void ReadEvent() override {
140 // Ignore reads, but don't leave them pending.
141 ssize_t count;
142 char buf[512];
143 while (true) {
144 count = read(fd(), &buf, sizeof buf);
145 if (count <= 0) {
146 if (errno != EAGAIN) {
147 CloseConnection();
148 return;
149 }
150 break;
151 } else if (!ready_to_recieve_) {
152 // This 4 should match "\r\n\r\n".length();
153 if (match_i_ >= 4) {
154 printf("reading after last match\n");
155 continue;
156 }
157 for (char c : aos::vision::DataRef(&buf[0], count)) {
158 if (c == "\r\n\r\n"[match_i_]) {
159 ++match_i_;
160 if (match_i_ >= 4) {
161 if (!ready_to_recieve_) {
162 ready_to_recieve_ = true;
163 RasterHeader();
164 }
165 }
166 } else if (match_i_ != 0) {
167 if (c == '\r') match_i_ = 1;
168 }
169 }
170 }
171 }
172 }
173
174 void RasterHeader() {
175 output_buffer_.push_back(mjpg_header);
176 NewDataToSend();
177 }
178
179 void RasterFrame(std::shared_ptr<Frame> frame) {
180 if (!output_buffer_.empty() || !ready_to_recieve_) return;
181 sending_frame_ = frame;
182 aos::vision::DataRef data = frame->data;
183
184 size_t n_written = snprintf(data_header_tmp_, sizeof(data_header_tmp_),
Austin Schuh8d5fff42018-05-30 20:44:12 -0700185 "--boundary\r\n"
186 "Content-type: image/jpg\r\n"
187 "Content-Length: %zu\r\n\r\n",
188 data.size());
Parker Schuhc1975fc2018-04-07 15:27:07 -0700189 // This should never happen because the buffer should be properly sized.
190 if (n_written == sizeof(data_header_tmp_)) {
191 fprintf(stderr, "wrong sized buffer\n");
192 exit(-1);
193 }
194 output_buffer_.push_back(aos::vision::DataRef(data_header_tmp_, n_written));
195 output_buffer_.push_back(data);
196 output_buffer_.push_back("\r\n\r\n");
197 NewDataToSend();
198 }
199
Austin Schuh8d5fff42018-05-30 20:44:12 -0700200 void NewFrame(std::shared_ptr<Frame> frame) { RasterFrame(std::move(frame)); }
Parker Schuhc1975fc2018-04-07 15:27:07 -0700201
202 void NewDataToSend() {
203 while (!output_buffer_.empty()) {
Austin Schuh8d5fff42018-05-30 20:44:12 -0700204 auto &data = *output_buffer_.begin();
Parker Schuhc1975fc2018-04-07 15:27:07 -0700205
206 while (!data.empty()) {
207 int len = send(fd(), data.data(), data.size(), MSG_NOSIGNAL);
208 if (len == -1) {
209 if (errno == EAGAIN) {
210 // Next thinggy will pick this up.
211 return;
212 } else {
213 CloseConnection();
214 return;
215 }
216 } else {
217 data.remove_prefix(len);
218 }
219 }
220 output_buffer_.pop_front();
221 }
222 }
223
224 private:
225 char data_header_tmp_[512];
226 std::shared_ptr<Frame> sending_frame_;
227 std::deque<aos::vision::DataRef> output_buffer_;
228
229 bool ready_to_recieve_ = false;
230 void CloseConnection() {
231 loop()->Delete(this);
232 close(fd());
233 delete this;
234 }
235 size_t match_i_ = 0;
236};
237
Austin Schuh8d5fff42018-05-30 20:44:12 -0700238class CameraStream : public ::aos::vision::ImageStreamEvent {
Parker Schuhc1975fc2018-04-07 15:27:07 -0700239 public:
Austin Schuh8d5fff42018-05-30 20:44:12 -0700240 CameraStream(::aos::vision::CameraParams params, const ::std::string &fname,
241 TCPServer<MjpegDataSocket> *tcp_server, bool log,
242 ::std::function<void()> frame_callback)
243 : ImageStreamEvent(fname, params),
244 tcp_server_(tcp_server),
245 frame_callback_(frame_callback) {
246 if (log) {
247 log_.reset(new BlobLog("./logging/blob_record_", ".dat"));
248 }
249 }
Parker Schuhc1975fc2018-04-07 15:27:07 -0700250
Austin Schuh8d5fff42018-05-30 20:44:12 -0700251 void set_active(bool active) { active_ = active; }
252
253 bool active() const { return active_; }
254
255 void ProcessImage(DataRef data,
256 monotonic_clock::time_point /*monotonic_now*/) {
Parker Schuhc1975fc2018-04-07 15:27:07 -0700257 ++sampling;
258 // 20 is the sampling rate.
259 if (sampling == 20) {
260 int tmp_size = data.size() + sizeof(int32_t);
261 char *buf;
262 std::string log_record;
263 log_record.resize(tmp_size, 0);
264 {
265 buf = Int32Codec::Write(&log_record[0], tmp_size);
266 data.copy(buf, data.size());
267 }
Austin Schuh8d5fff42018-05-30 20:44:12 -0700268 if (log_) {
269 log_->WriteLogEntry(log_record);
270 }
Parker Schuhc1975fc2018-04-07 15:27:07 -0700271 sampling = 0;
272 }
273
Austin Schuh8d5fff42018-05-30 20:44:12 -0700274 if (active_) {
275 auto frame = std::make_shared<Frame>(Frame{std::string(data)});
276 tcp_server_->Broadcast(
277 [frame](MjpegDataSocket *event) { event->NewFrame(frame); });
278 }
279 frame_callback_();
Parker Schuhc1975fc2018-04-07 15:27:07 -0700280 }
Austin Schuh8d5fff42018-05-30 20:44:12 -0700281
Parker Schuhc1975fc2018-04-07 15:27:07 -0700282 private:
283 int sampling = 0;
Austin Schuh8d5fff42018-05-30 20:44:12 -0700284 TCPServer<MjpegDataSocket> *tcp_server_;
285 ::std::unique_ptr<BlobLog> log_;
286 ::std::function<void()> frame_callback_;
287 bool active_ = false;
Parker Schuhc1975fc2018-04-07 15:27:07 -0700288};
289
Philipp Schrader790cb542023-07-05 21:06:52 -0700290int main(int argc, char **argv) {
Austin Schuh8d5fff42018-05-30 20:44:12 -0700291 gflags::ParseCommandLineFlags(&argc, &argv, false);
Parker Schuhc1975fc2018-04-07 15:27:07 -0700292
Austin Schuh8d5fff42018-05-30 20:44:12 -0700293 TCPServer<MjpegDataSocket> tcp_server_(80);
294 aos::vision::CameraParams params0;
295 params0.set_exposure(FLAGS_camera0_exposure);
296 params0.set_brightness(-40);
297 params0.set_width(320);
Philipp Schrader790cb542023-07-05 21:06:52 -0700298 // params0.set_fps(10);
Austin Schuh8d5fff42018-05-30 20:44:12 -0700299 params0.set_height(240);
300
301 aos::vision::CameraParams params1 = params0;
302 params1.set_exposure(FLAGS_camera1_exposure);
303
304 ::y2018::VisionStatus vision_status;
305 ::aos::events::ProtoTXUdpSocket<::y2018::VisionStatus> status_socket(
306 "10.9.71.2", 5001);
307
308 ::std::unique_ptr<CameraStream> camera1;
309 ::std::unique_ptr<CameraStream> camera0(new CameraStream(
310 params0, "/dev/video0", &tcp_server_, true,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800311 [&camera0, &status_socket, &vision_status]() {
Austin Schuh8d5fff42018-05-30 20:44:12 -0700312 vision_status.set_low_frame_count(vision_status.low_frame_count() + 1);
Austin Schuhf257f3c2019-10-27 21:00:43 -0700313 AOS_LOG(INFO, "Got a frame cam0\n");
Austin Schuh8d5fff42018-05-30 20:44:12 -0700314 if (camera0->active()) {
315 status_socket.Send(vision_status);
316 }
317 }));
318 if (!FLAGS_single_camera) {
319 camera1.reset(new CameraStream(
320 // params,
321 // "/dev/v4l/by-path/platform-tegra-xhci-usb-0:3.1:1.0-video-index0",
322 params1, "/dev/video1", &tcp_server_, false,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800323 [&camera1, &status_socket, &vision_status]() {
Austin Schuh8d5fff42018-05-30 20:44:12 -0700324 vision_status.set_high_frame_count(vision_status.high_frame_count() +
325 1);
Austin Schuhf257f3c2019-10-27 21:00:43 -0700326 AOS_LOG(INFO, "Got a frame cam1\n");
Austin Schuh8d5fff42018-05-30 20:44:12 -0700327 if (camera1->active()) {
328 status_socket.Send(vision_status);
329 }
330 }));
331 }
332
333 ProtoUdpClient<VisionControl> udp_client(
334 5000, [&camera0, &camera1](const VisionControl &vision_control) {
Austin Schuha8de4a62018-09-03 18:04:28 -0700335 bool cam0_active = false;
Austin Schuh8d5fff42018-05-30 20:44:12 -0700336 if (camera1) {
Austin Schuha8de4a62018-09-03 18:04:28 -0700337 cam0_active = !vision_control.high_video();
Austin Schuh8d5fff42018-05-30 20:44:12 -0700338 camera0->set_active(!vision_control.high_video());
339 camera1->set_active(vision_control.high_video());
340 } else {
Austin Schuha8de4a62018-09-03 18:04:28 -0700341 cam0_active = true;
Austin Schuh8d5fff42018-05-30 20:44:12 -0700342 camera0->set_active(true);
343 }
Austin Schuhf257f3c2019-10-27 21:00:43 -0700344 AOS_LOG(INFO, "Got control packet, cam%d active\n",
345 cam0_active ? 0 : 1);
Austin Schuh8d5fff42018-05-30 20:44:12 -0700346 });
347
348 // Default to camera0
349 camera0->set_active(true);
350 if (camera1) {
351 camera1->set_active(false);
352 }
Parker Schuhc1975fc2018-04-07 15:27:07 -0700353
354 aos::events::EpollLoop loop;
Austin Schuh8d5fff42018-05-30 20:44:12 -0700355 loop.Add(&tcp_server_);
356 loop.Add(camera0.get());
357 if (camera1) {
358 loop.Add(camera1.get());
359 }
360 loop.Add(&udp_client);
Parker Schuhc1975fc2018-04-07 15:27:07 -0700361
362 printf("Running Camera\n");
363 loop.Run();
364}