blob: 3f24f1ee9fb3aa0c42cb5713437bf15b78943f5b [file] [log] [blame]
Brian Silverman9dd793b2020-01-31 23:52:21 -08001#include "y2020/vision/v4l2_reader.h"
2
3#include <fcntl.h>
4#include <linux/videodev2.h>
5#include <sys/ioctl.h>
6#include <sys/stat.h>
7#include <sys/types.h>
8
Jim Ostrowski8565b402020-02-29 20:26:53 -08009DEFINE_bool(ignore_timestamps, false,
10 "Don't require timestamps on images. Used to allow webcams");
11
Brian Silverman9dd793b2020-01-31 23:52:21 -080012namespace frc971 {
13namespace vision {
14
15V4L2Reader::V4L2Reader(aos::EventLoop *event_loop,
16 const std::string &device_name)
17 : fd_(open(device_name.c_str(), O_RDWR | O_NONBLOCK)) {
18 PCHECK(fd_.get() != -1);
19
20 // First, clean up after anybody else who left the device streaming.
Brian Silverman8f24adb2020-02-02 17:15:58 -080021 StreamOff();
Brian Silverman9dd793b2020-01-31 23:52:21 -080022
23 {
24 struct v4l2_format format;
25 memset(&format, 0, sizeof(format));
26 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
27 format.fmt.pix.width = cols_;
28 format.fmt.pix.height = rows_;
29 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
30 // This means we want to capture from a progressive (non-interlaced) source.
31 format.fmt.pix.field = V4L2_FIELD_NONE;
32 PCHECK(Ioctl(VIDIOC_S_FMT, &format) == 0);
33 CHECK_EQ(static_cast<int>(format.fmt.pix.width), cols_);
34 CHECK_EQ(static_cast<int>(format.fmt.pix.height), rows_);
35 CHECK_EQ(static_cast<int>(format.fmt.pix.bytesperline),
36 cols_ * 2 /* bytes per pixel */);
37 CHECK_EQ(format.fmt.pix.sizeimage, ImageSize());
38 }
39
40 {
41 struct v4l2_requestbuffers request;
42 memset(&request, 0, sizeof(request));
43 request.count = buffers_.size();
44 request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
45 request.memory = V4L2_MEMORY_USERPTR;
46 PCHECK(Ioctl(VIDIOC_REQBUFS, &request) == 0);
47 CHECK_EQ(request.count, buffers_.size())
48 << ": Kernel refused to give us the number of buffers we asked for";
49 }
50
51 for (size_t i = 0; i < buffers_.size(); ++i) {
52 buffers_[i].sender = event_loop->MakeSender<CameraImage>("/camera");
53 EnqueueBuffer(i);
54 }
55
56 {
57 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
58 PCHECK(Ioctl(VIDIOC_STREAMON, &type) == 0);
59 }
60}
61
Brian Silverman967e5df2020-02-09 16:43:34 -080062bool V4L2Reader::ReadLatestImage() {
Brian Silverman9dd793b2020-01-31 23:52:21 -080063 // First, enqueue any old buffer we already have. This is the one which may
64 // have been sent.
Brian Silverman967e5df2020-02-09 16:43:34 -080065 if (saved_buffer_) {
66 EnqueueBuffer(saved_buffer_.index);
67 saved_buffer_.Clear();
Brian Silverman9dd793b2020-01-31 23:52:21 -080068 }
69 while (true) {
Brian Silverman967e5df2020-02-09 16:43:34 -080070 const BufferInfo previous_buffer = saved_buffer_;
Brian Silverman9dd793b2020-01-31 23:52:21 -080071 saved_buffer_ = DequeueBuffer();
Brian Silverman967e5df2020-02-09 16:43:34 -080072 if (saved_buffer_) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080073 // We got a new buffer. Return the previous one (if relevant) and keep
74 // going.
Brian Silverman967e5df2020-02-09 16:43:34 -080075 if (previous_buffer) {
76 EnqueueBuffer(previous_buffer.index);
Brian Silverman9dd793b2020-01-31 23:52:21 -080077 }
78 continue;
79 }
Brian Silverman967e5df2020-02-09 16:43:34 -080080 if (!previous_buffer) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080081 // There were no images to read. Return an indication of that.
Brian Silverman967e5df2020-02-09 16:43:34 -080082 return false;
Brian Silverman9dd793b2020-01-31 23:52:21 -080083 }
84 // We didn't get a new one, but we already got one in a previous
85 // iteration, which means we found an image so return it.
86 saved_buffer_ = previous_buffer;
Brian Silverman967e5df2020-02-09 16:43:34 -080087 buffers_[saved_buffer_.index].PrepareMessage(rows_, cols_, ImageSize(),
88 saved_buffer_.monotonic_eof);
89 return true;
Brian Silverman9dd793b2020-01-31 23:52:21 -080090 }
91}
92
Brian Silverman967e5df2020-02-09 16:43:34 -080093void V4L2Reader::SendLatestImage() { buffers_[saved_buffer_.index].Send(); }
94
milind-udaebe9b2022-01-09 18:25:24 -080095void V4L2Reader::SetExposure(size_t duration) {
96 v4l2_control manual_control;
97 manual_control.id = V4L2_CID_EXPOSURE_AUTO;
98 manual_control.value = V4L2_EXPOSURE_MANUAL;
99 PCHECK(Ioctl(VIDIOC_S_CTRL, &manual_control) == 0);
100
101 v4l2_control exposure_control;
102 exposure_control.id = V4L2_CID_EXPOSURE_ABSOLUTE;
103 exposure_control.value = static_cast<int>(duration); // 100 micro s units
104 PCHECK(Ioctl(VIDIOC_S_CTRL, &exposure_control) == 0);
105}
106
107void V4L2Reader::UseAutoExposure() {
108 v4l2_control control;
109 control.id = V4L2_CID_EXPOSURE_AUTO;
110 control.value = V4L2_EXPOSURE_AUTO;
111 PCHECK(Ioctl(VIDIOC_S_CTRL, &control) == 0);
112}
113
Brian Silverman967e5df2020-02-09 16:43:34 -0800114void V4L2Reader::Buffer::InitializeMessage(size_t max_image_size) {
115 message_offset = flatbuffers::Offset<CameraImage>();
116 builder = aos::Sender<CameraImage>::Builder();
117 builder = sender.MakeBuilder();
118 // The kernel has an undocumented requirement that the buffer is aligned
119 // to 64 bytes. If you give it a nonaligned pointer, it will return EINVAL
120 // and only print something in dmesg with the relevant dynamic debug
121 // prints turned on.
122 builder.fbb()->StartIndeterminateVector(max_image_size, 1, 64, &data_pointer);
123 CHECK_EQ(reinterpret_cast<uintptr_t>(data_pointer) % 64, 0u)
124 << ": Flatbuffers failed to align things as requested";
125}
126
127void V4L2Reader::Buffer::PrepareMessage(
128 int rows, int cols, size_t image_size,
129 aos::monotonic_clock::time_point monotonic_eof) {
130 CHECK(data_pointer != nullptr);
131 data_pointer = nullptr;
132
133 const auto data_offset = builder.fbb()->EndIndeterminateVector(image_size, 1);
134 auto image_builder = builder.MakeBuilder<CameraImage>();
135 image_builder.add_data(data_offset);
136 image_builder.add_rows(rows);
137 image_builder.add_cols(cols);
138 image_builder.add_monotonic_timestamp_ns(
139 std::chrono::nanoseconds(monotonic_eof.time_since_epoch()).count());
140 message_offset = image_builder.Finish();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800141}
142
143int V4L2Reader::Ioctl(unsigned long number, void *arg) {
144 return ioctl(fd_.get(), number, arg);
145}
146
Brian Silverman967e5df2020-02-09 16:43:34 -0800147V4L2Reader::BufferInfo V4L2Reader::DequeueBuffer() {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800148 struct v4l2_buffer buffer;
149 memset(&buffer, 0, sizeof(buffer));
150 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
151 buffer.memory = V4L2_MEMORY_USERPTR;
152 const int result = Ioctl(VIDIOC_DQBUF, &buffer);
153 if (result == -1 && errno == EAGAIN) {
Brian Silverman967e5df2020-02-09 16:43:34 -0800154 return BufferInfo();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800155 }
156 PCHECK(result == 0) << ": VIDIOC_DQBUF failed";
157 CHECK_LT(buffer.index, buffers_.size());
Brian Silverman9dd793b2020-01-31 23:52:21 -0800158 CHECK_EQ(reinterpret_cast<uintptr_t>(buffers_[buffer.index].data_pointer),
159 buffer.m.userptr);
160 CHECK_EQ(ImageSize(), buffer.length);
Brian Silverman967e5df2020-02-09 16:43:34 -0800161 CHECK(buffer.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC);
Jim Ostrowski8565b402020-02-29 20:26:53 -0800162 if (!FLAGS_ignore_timestamps) {
163 // Require that we have good timestamp on images
164 CHECK_EQ(buffer.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK,
165 static_cast<uint32_t>(V4L2_BUF_FLAG_TSTAMP_SRC_EOF));
166 }
Brian Silverman967e5df2020-02-09 16:43:34 -0800167 return {static_cast<int>(buffer.index),
168 aos::time::from_timeval(buffer.timestamp)};
Brian Silverman9dd793b2020-01-31 23:52:21 -0800169}
170
171void V4L2Reader::EnqueueBuffer(int buffer_number) {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800172 CHECK_GE(buffer_number, 0);
173 CHECK_LT(buffer_number, static_cast<int>(buffers_.size()));
174 buffers_[buffer_number].InitializeMessage(ImageSize());
175 struct v4l2_buffer buffer;
176 memset(&buffer, 0, sizeof(buffer));
177 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
178 buffer.memory = V4L2_MEMORY_USERPTR;
179 buffer.index = buffer_number;
180 buffer.m.userptr =
181 reinterpret_cast<uintptr_t>(buffers_[buffer_number].data_pointer);
182 buffer.length = ImageSize();
183 PCHECK(Ioctl(VIDIOC_QBUF, &buffer) == 0);
184}
185
Brian Silverman8f24adb2020-02-02 17:15:58 -0800186void V4L2Reader::StreamOff() {
187 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
188 const int result = Ioctl(VIDIOC_STREAMOFF, &type);
189 if (result == 0) {
190 return;
191 }
192 // Some devices (like Alex's webcam) return this if streaming isn't currently
193 // on, unlike what the documentations says should happen.
194 if (errno == EBUSY) {
195 return;
196 }
197 PLOG(FATAL) << "VIDIOC_STREAMOFF failed";
198}
199
Brian Silverman9dd793b2020-01-31 23:52:21 -0800200} // namespace vision
201} // namespace frc971