blob: 29471d4f943cce55f1ca78fb8c31380c167b7a8c [file] [log] [blame]
Jim Ostrowski977850f2022-01-22 21:04:22 -08001#include "frc971/vision/v4l2_reader.h"
Brian Silverman9dd793b2020-01-31 23:52:21 -08002
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)) {
Jim Ostrowskifec0c332022-02-06 23:28:26 -080018 PCHECK(fd_.get() != -1) << " Failed to open device " << device_name;
Brian Silverman9dd793b2020-01-31 23:52:21 -080019
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
Jim Ostrowski1ac95742022-10-01 17:21:11 -070023 // Don't know why this magic call to SetExposure is required (before the
24 // camera settings are configured) to make things work on boot of the pi, but
25 // it seems to be-- without it, the image exposure is wrong (too dark). Note--
26 // any valid value seems to work-- just choosing 1 for now
27 SetExposure(1);
28
Brian Silverman9dd793b2020-01-31 23:52:21 -080029 {
30 struct v4l2_format format;
31 memset(&format, 0, sizeof(format));
32 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
33 format.fmt.pix.width = cols_;
34 format.fmt.pix.height = rows_;
35 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
Jim Ostrowski1ac95742022-10-01 17:21:11 -070036 // This means we want to capture from a progressive (non-interlaced)
37 // source.
Brian Silverman9dd793b2020-01-31 23:52:21 -080038 format.fmt.pix.field = V4L2_FIELD_NONE;
39 PCHECK(Ioctl(VIDIOC_S_FMT, &format) == 0);
40 CHECK_EQ(static_cast<int>(format.fmt.pix.width), cols_);
41 CHECK_EQ(static_cast<int>(format.fmt.pix.height), rows_);
42 CHECK_EQ(static_cast<int>(format.fmt.pix.bytesperline),
43 cols_ * 2 /* bytes per pixel */);
44 CHECK_EQ(format.fmt.pix.sizeimage, ImageSize());
45 }
46
47 {
48 struct v4l2_requestbuffers request;
49 memset(&request, 0, sizeof(request));
50 request.count = buffers_.size();
51 request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
52 request.memory = V4L2_MEMORY_USERPTR;
53 PCHECK(Ioctl(VIDIOC_REQBUFS, &request) == 0);
54 CHECK_EQ(request.count, buffers_.size())
55 << ": Kernel refused to give us the number of buffers we asked for";
56 }
57
58 for (size_t i = 0; i < buffers_.size(); ++i) {
59 buffers_[i].sender = event_loop->MakeSender<CameraImage>("/camera");
60 EnqueueBuffer(i);
61 }
62
63 {
64 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
65 PCHECK(Ioctl(VIDIOC_STREAMON, &type) == 0);
66 }
67}
68
Brian Silverman967e5df2020-02-09 16:43:34 -080069bool V4L2Reader::ReadLatestImage() {
Jim Ostrowski977850f2022-01-22 21:04:22 -080070 // First, enqueue any old buffer we already have. This is the one which
71 // may have been sent.
Brian Silverman967e5df2020-02-09 16:43:34 -080072 if (saved_buffer_) {
73 EnqueueBuffer(saved_buffer_.index);
74 saved_buffer_.Clear();
Brian Silverman9dd793b2020-01-31 23:52:21 -080075 }
76 while (true) {
Brian Silverman967e5df2020-02-09 16:43:34 -080077 const BufferInfo previous_buffer = saved_buffer_;
Brian Silverman9dd793b2020-01-31 23:52:21 -080078 saved_buffer_ = DequeueBuffer();
Brian Silverman967e5df2020-02-09 16:43:34 -080079 if (saved_buffer_) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080080 // We got a new buffer. Return the previous one (if relevant) and keep
81 // going.
Brian Silverman967e5df2020-02-09 16:43:34 -080082 if (previous_buffer) {
83 EnqueueBuffer(previous_buffer.index);
Brian Silverman9dd793b2020-01-31 23:52:21 -080084 }
85 continue;
86 }
Brian Silverman967e5df2020-02-09 16:43:34 -080087 if (!previous_buffer) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080088 // There were no images to read. Return an indication of that.
Brian Silverman967e5df2020-02-09 16:43:34 -080089 return false;
Brian Silverman9dd793b2020-01-31 23:52:21 -080090 }
91 // We didn't get a new one, but we already got one in a previous
92 // iteration, which means we found an image so return it.
93 saved_buffer_ = previous_buffer;
Brian Silverman967e5df2020-02-09 16:43:34 -080094 buffers_[saved_buffer_.index].PrepareMessage(rows_, cols_, ImageSize(),
95 saved_buffer_.monotonic_eof);
96 return true;
Brian Silverman9dd793b2020-01-31 23:52:21 -080097 }
98}
99
Brian Silverman967e5df2020-02-09 16:43:34 -0800100void V4L2Reader::SendLatestImage() { buffers_[saved_buffer_.index].Send(); }
101
milind-udaebe9b2022-01-09 18:25:24 -0800102void V4L2Reader::SetExposure(size_t duration) {
103 v4l2_control manual_control;
104 manual_control.id = V4L2_CID_EXPOSURE_AUTO;
105 manual_control.value = V4L2_EXPOSURE_MANUAL;
106 PCHECK(Ioctl(VIDIOC_S_CTRL, &manual_control) == 0);
107
108 v4l2_control exposure_control;
109 exposure_control.id = V4L2_CID_EXPOSURE_ABSOLUTE;
110 exposure_control.value = static_cast<int>(duration); // 100 micro s units
111 PCHECK(Ioctl(VIDIOC_S_CTRL, &exposure_control) == 0);
112}
113
114void V4L2Reader::UseAutoExposure() {
115 v4l2_control control;
116 control.id = V4L2_CID_EXPOSURE_AUTO;
117 control.value = V4L2_EXPOSURE_AUTO;
118 PCHECK(Ioctl(VIDIOC_S_CTRL, &control) == 0);
119}
120
Brian Silverman967e5df2020-02-09 16:43:34 -0800121void V4L2Reader::Buffer::InitializeMessage(size_t max_image_size) {
122 message_offset = flatbuffers::Offset<CameraImage>();
123 builder = aos::Sender<CameraImage>::Builder();
124 builder = sender.MakeBuilder();
125 // The kernel has an undocumented requirement that the buffer is aligned
126 // to 64 bytes. If you give it a nonaligned pointer, it will return EINVAL
127 // and only print something in dmesg with the relevant dynamic debug
128 // prints turned on.
129 builder.fbb()->StartIndeterminateVector(max_image_size, 1, 64, &data_pointer);
130 CHECK_EQ(reinterpret_cast<uintptr_t>(data_pointer) % 64, 0u)
131 << ": Flatbuffers failed to align things as requested";
132}
133
134void V4L2Reader::Buffer::PrepareMessage(
135 int rows, int cols, size_t image_size,
136 aos::monotonic_clock::time_point monotonic_eof) {
137 CHECK(data_pointer != nullptr);
138 data_pointer = nullptr;
139
140 const auto data_offset = builder.fbb()->EndIndeterminateVector(image_size, 1);
141 auto image_builder = builder.MakeBuilder<CameraImage>();
142 image_builder.add_data(data_offset);
143 image_builder.add_rows(rows);
144 image_builder.add_cols(cols);
145 image_builder.add_monotonic_timestamp_ns(
146 std::chrono::nanoseconds(monotonic_eof.time_since_epoch()).count());
147 message_offset = image_builder.Finish();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800148}
149
150int V4L2Reader::Ioctl(unsigned long number, void *arg) {
151 return ioctl(fd_.get(), number, arg);
152}
153
Brian Silverman967e5df2020-02-09 16:43:34 -0800154V4L2Reader::BufferInfo V4L2Reader::DequeueBuffer() {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800155 struct v4l2_buffer buffer;
156 memset(&buffer, 0, sizeof(buffer));
157 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
158 buffer.memory = V4L2_MEMORY_USERPTR;
159 const int result = Ioctl(VIDIOC_DQBUF, &buffer);
160 if (result == -1 && errno == EAGAIN) {
Brian Silverman967e5df2020-02-09 16:43:34 -0800161 return BufferInfo();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800162 }
163 PCHECK(result == 0) << ": VIDIOC_DQBUF failed";
164 CHECK_LT(buffer.index, buffers_.size());
Brian Silverman9dd793b2020-01-31 23:52:21 -0800165 CHECK_EQ(reinterpret_cast<uintptr_t>(buffers_[buffer.index].data_pointer),
166 buffer.m.userptr);
167 CHECK_EQ(ImageSize(), buffer.length);
Brian Silverman967e5df2020-02-09 16:43:34 -0800168 CHECK(buffer.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC);
Jim Ostrowski8565b402020-02-29 20:26:53 -0800169 if (!FLAGS_ignore_timestamps) {
170 // Require that we have good timestamp on images
171 CHECK_EQ(buffer.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK,
172 static_cast<uint32_t>(V4L2_BUF_FLAG_TSTAMP_SRC_EOF));
173 }
Brian Silverman967e5df2020-02-09 16:43:34 -0800174 return {static_cast<int>(buffer.index),
175 aos::time::from_timeval(buffer.timestamp)};
Brian Silverman9dd793b2020-01-31 23:52:21 -0800176}
177
178void V4L2Reader::EnqueueBuffer(int buffer_number) {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800179 CHECK_GE(buffer_number, 0);
180 CHECK_LT(buffer_number, static_cast<int>(buffers_.size()));
181 buffers_[buffer_number].InitializeMessage(ImageSize());
182 struct v4l2_buffer buffer;
183 memset(&buffer, 0, sizeof(buffer));
184 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
185 buffer.memory = V4L2_MEMORY_USERPTR;
186 buffer.index = buffer_number;
187 buffer.m.userptr =
188 reinterpret_cast<uintptr_t>(buffers_[buffer_number].data_pointer);
189 buffer.length = ImageSize();
190 PCHECK(Ioctl(VIDIOC_QBUF, &buffer) == 0);
191}
192
Brian Silverman8f24adb2020-02-02 17:15:58 -0800193void V4L2Reader::StreamOff() {
194 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
195 const int result = Ioctl(VIDIOC_STREAMOFF, &type);
196 if (result == 0) {
197 return;
198 }
Jim Ostrowski977850f2022-01-22 21:04:22 -0800199 // Some devices (like Alex's webcam) return this if streaming isn't
200 // currently on, unlike what the documentations says should happen.
Brian Silverman8f24adb2020-02-02 17:15:58 -0800201 if (errno == EBUSY) {
202 return;
203 }
204 PLOG(FATAL) << "VIDIOC_STREAMOFF failed";
205}
206
Brian Silverman9dd793b2020-01-31 23:52:21 -0800207} // namespace vision
208} // namespace frc971