blob: f1944c1d92af10c5c99d7697e096fcd744e7ec36 [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
9namespace frc971 {
10namespace vision {
11
12V4L2Reader::V4L2Reader(aos::EventLoop *event_loop,
13 const std::string &device_name)
14 : fd_(open(device_name.c_str(), O_RDWR | O_NONBLOCK)) {
15 PCHECK(fd_.get() != -1);
16
17 // First, clean up after anybody else who left the device streaming.
Brian Silverman8f24adb2020-02-02 17:15:58 -080018 StreamOff();
Brian Silverman9dd793b2020-01-31 23:52:21 -080019
20 {
21 struct v4l2_format format;
22 memset(&format, 0, sizeof(format));
23 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
24 format.fmt.pix.width = cols_;
25 format.fmt.pix.height = rows_;
26 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
27 // This means we want to capture from a progressive (non-interlaced) source.
28 format.fmt.pix.field = V4L2_FIELD_NONE;
29 PCHECK(Ioctl(VIDIOC_S_FMT, &format) == 0);
30 CHECK_EQ(static_cast<int>(format.fmt.pix.width), cols_);
31 CHECK_EQ(static_cast<int>(format.fmt.pix.height), rows_);
32 CHECK_EQ(static_cast<int>(format.fmt.pix.bytesperline),
33 cols_ * 2 /* bytes per pixel */);
34 CHECK_EQ(format.fmt.pix.sizeimage, ImageSize());
35 }
36
37 {
38 struct v4l2_requestbuffers request;
39 memset(&request, 0, sizeof(request));
40 request.count = buffers_.size();
41 request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
42 request.memory = V4L2_MEMORY_USERPTR;
43 PCHECK(Ioctl(VIDIOC_REQBUFS, &request) == 0);
44 CHECK_EQ(request.count, buffers_.size())
45 << ": Kernel refused to give us the number of buffers we asked for";
46 }
47
48 for (size_t i = 0; i < buffers_.size(); ++i) {
49 buffers_[i].sender = event_loop->MakeSender<CameraImage>("/camera");
50 EnqueueBuffer(i);
51 }
52
53 {
54 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
55 PCHECK(Ioctl(VIDIOC_STREAMON, &type) == 0);
56 }
57}
58
Brian Silverman967e5df2020-02-09 16:43:34 -080059bool V4L2Reader::ReadLatestImage() {
Brian Silverman9dd793b2020-01-31 23:52:21 -080060 // First, enqueue any old buffer we already have. This is the one which may
61 // have been sent.
Brian Silverman967e5df2020-02-09 16:43:34 -080062 if (saved_buffer_) {
63 EnqueueBuffer(saved_buffer_.index);
64 saved_buffer_.Clear();
Brian Silverman9dd793b2020-01-31 23:52:21 -080065 }
66 while (true) {
Brian Silverman967e5df2020-02-09 16:43:34 -080067 const BufferInfo previous_buffer = saved_buffer_;
Brian Silverman9dd793b2020-01-31 23:52:21 -080068 saved_buffer_ = DequeueBuffer();
Brian Silverman967e5df2020-02-09 16:43:34 -080069 if (saved_buffer_) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080070 // We got a new buffer. Return the previous one (if relevant) and keep
71 // going.
Brian Silverman967e5df2020-02-09 16:43:34 -080072 if (previous_buffer) {
73 EnqueueBuffer(previous_buffer.index);
Brian Silverman9dd793b2020-01-31 23:52:21 -080074 }
75 continue;
76 }
Brian Silverman967e5df2020-02-09 16:43:34 -080077 if (!previous_buffer) {
Brian Silverman9dd793b2020-01-31 23:52:21 -080078 // There were no images to read. Return an indication of that.
Brian Silverman967e5df2020-02-09 16:43:34 -080079 return false;
Brian Silverman9dd793b2020-01-31 23:52:21 -080080 }
81 // We didn't get a new one, but we already got one in a previous
82 // iteration, which means we found an image so return it.
83 saved_buffer_ = previous_buffer;
Brian Silverman967e5df2020-02-09 16:43:34 -080084 buffers_[saved_buffer_.index].PrepareMessage(rows_, cols_, ImageSize(),
85 saved_buffer_.monotonic_eof);
86 return true;
Brian Silverman9dd793b2020-01-31 23:52:21 -080087 }
88}
89
Brian Silverman967e5df2020-02-09 16:43:34 -080090void V4L2Reader::SendLatestImage() { buffers_[saved_buffer_.index].Send(); }
91
92void V4L2Reader::Buffer::InitializeMessage(size_t max_image_size) {
93 message_offset = flatbuffers::Offset<CameraImage>();
94 builder = aos::Sender<CameraImage>::Builder();
95 builder = sender.MakeBuilder();
96 // The kernel has an undocumented requirement that the buffer is aligned
97 // to 64 bytes. If you give it a nonaligned pointer, it will return EINVAL
98 // and only print something in dmesg with the relevant dynamic debug
99 // prints turned on.
100 builder.fbb()->StartIndeterminateVector(max_image_size, 1, 64, &data_pointer);
101 CHECK_EQ(reinterpret_cast<uintptr_t>(data_pointer) % 64, 0u)
102 << ": Flatbuffers failed to align things as requested";
103}
104
105void V4L2Reader::Buffer::PrepareMessage(
106 int rows, int cols, size_t image_size,
107 aos::monotonic_clock::time_point monotonic_eof) {
108 CHECK(data_pointer != nullptr);
109 data_pointer = nullptr;
110
111 const auto data_offset = builder.fbb()->EndIndeterminateVector(image_size, 1);
112 auto image_builder = builder.MakeBuilder<CameraImage>();
113 image_builder.add_data(data_offset);
114 image_builder.add_rows(rows);
115 image_builder.add_cols(cols);
116 image_builder.add_monotonic_timestamp_ns(
117 std::chrono::nanoseconds(monotonic_eof.time_since_epoch()).count());
118 message_offset = image_builder.Finish();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800119}
120
121int V4L2Reader::Ioctl(unsigned long number, void *arg) {
122 return ioctl(fd_.get(), number, arg);
123}
124
Brian Silverman967e5df2020-02-09 16:43:34 -0800125V4L2Reader::BufferInfo V4L2Reader::DequeueBuffer() {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800126 struct v4l2_buffer buffer;
127 memset(&buffer, 0, sizeof(buffer));
128 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
129 buffer.memory = V4L2_MEMORY_USERPTR;
130 const int result = Ioctl(VIDIOC_DQBUF, &buffer);
131 if (result == -1 && errno == EAGAIN) {
Brian Silverman967e5df2020-02-09 16:43:34 -0800132 return BufferInfo();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800133 }
134 PCHECK(result == 0) << ": VIDIOC_DQBUF failed";
135 CHECK_LT(buffer.index, buffers_.size());
Brian Silverman9dd793b2020-01-31 23:52:21 -0800136 CHECK_EQ(reinterpret_cast<uintptr_t>(buffers_[buffer.index].data_pointer),
137 buffer.m.userptr);
138 CHECK_EQ(ImageSize(), buffer.length);
Brian Silverman967e5df2020-02-09 16:43:34 -0800139 CHECK(buffer.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC);
140 CHECK_EQ(buffer.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK,
141 static_cast<uint32_t>(V4L2_BUF_FLAG_TSTAMP_SRC_EOF));
142 return {static_cast<int>(buffer.index),
143 aos::time::from_timeval(buffer.timestamp)};
Brian Silverman9dd793b2020-01-31 23:52:21 -0800144}
145
146void V4L2Reader::EnqueueBuffer(int buffer_number) {
Brian Silverman9dd793b2020-01-31 23:52:21 -0800147 CHECK_GE(buffer_number, 0);
148 CHECK_LT(buffer_number, static_cast<int>(buffers_.size()));
149 buffers_[buffer_number].InitializeMessage(ImageSize());
150 struct v4l2_buffer buffer;
151 memset(&buffer, 0, sizeof(buffer));
152 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
153 buffer.memory = V4L2_MEMORY_USERPTR;
154 buffer.index = buffer_number;
155 buffer.m.userptr =
156 reinterpret_cast<uintptr_t>(buffers_[buffer_number].data_pointer);
157 buffer.length = ImageSize();
158 PCHECK(Ioctl(VIDIOC_QBUF, &buffer) == 0);
159}
160
Brian Silverman8f24adb2020-02-02 17:15:58 -0800161void V4L2Reader::StreamOff() {
162 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
163 const int result = Ioctl(VIDIOC_STREAMOFF, &type);
164 if (result == 0) {
165 return;
166 }
167 // Some devices (like Alex's webcam) return this if streaming isn't currently
168 // on, unlike what the documentations says should happen.
169 if (errno == EBUSY) {
170 return;
171 }
172 PLOG(FATAL) << "VIDIOC_STREAMOFF failed";
173}
174
Brian Silverman9dd793b2020-01-31 23:52:21 -0800175} // namespace vision
176} // namespace frc971