blob: 1813b6f12b108f2f3287f49cfaba98768e7cb2d1 [file] [log] [blame]
Austin Schuhc97d48d2022-12-26 14:09:13 -08001#include "frc971/vision/media_device.h"
2
3#include <fcntl.h>
4#include <linux/media.h>
5#include <linux/v4l2-subdev.h>
6#include <linux/videodev2.h>
7#include <sys/ioctl.h>
8
9#include <cstddef>
10#include <cstdint>
11#include <optional>
12#include <string>
13#include <string_view>
14#include <vector>
15
16#include "absl/strings/str_cat.h"
17#include "absl/strings/str_split.h"
18#include "aos/scoped/scoped_fd.h"
19#include "aos/util/file.h"
20#include "glog/logging.h"
21
22namespace frc971 {
23namespace vision {
24
25void Entity::Log() const {
26 LOG(INFO) << " { \"id\": " << id() << ",";
27 LOG(INFO) << " \"name\": \"" << name() << "\",";
28 LOG(INFO) << " \"function\": " << function() << ",";
29 LOG(INFO) << " \"interface_type\": " << interface_type() << ",";
30 LOG(INFO) << " \"major\": " << major() << ",";
31 LOG(INFO) << " \"minor\": " << minor() << ",";
32 if (has_interface_) {
33 LOG(INFO) << " \"device\": \"" << device() << "\",";
34 }
35 LOG(INFO) << " \"pads\": [";
36 for (const Pad *pad : pads_) {
37 pad->Log();
38 }
39 LOG(INFO) << " ]";
40 LOG(INFO) << " }";
41}
42
43void Entity::UpdateDevice() {
44 // There's a symlink in /sys/dev/char which gets us to the uevent file
45 // which has the DEVNAME variable set with the device name. This
46 // reliably gets us the name of the device.
47 const ::std::string contents = aos::util::ReadFileToStringOrDie(
48 absl::StrCat("/sys/dev/char/", major(), ":", minor(), "/uevent"));
49
50 // Strip it out and return it.
51 for (std::string_view line : absl::StrSplit(contents, "\n")) {
52 VLOG(1) << line;
53 if (line.size() > 8 && line.substr(0, 8) == "DEVNAME=") {
54 device_ = absl::StrCat("/dev/", line.substr(8, -1));
55 return;
56 }
57 }
58
59 LOG(FATAL) << "Failed to find DEVNAME in uevent file.";
60}
61
62std::optional<MediaDevice> MediaDevice::Initialize(int index) {
63 int fd = open(absl::StrCat("/dev/media", index).c_str(), O_RDWR);
64 std::optional<MediaDevice> result = std::nullopt;
65 if (fd >= 0) {
66 result.emplace(MediaDevice(fd));
67 }
68 return result;
69}
70
71void MediaDevice::Update() {
72 PCHECK(ioctl(fd_.get(), MEDIA_IOC_DEVICE_INFO, &device_info_) == 0);
73
74 struct media_v2_topology topology;
75 std::memset(&topology, 0, sizeof(topology));
76 PCHECK(ioctl(fd_.get(), MEDIA_IOC_G_TOPOLOGY, &topology) == 0);
77 VLOG(1) << "Got " << topology.num_entities << " entries";
78 VLOG(1) << "Got " << topology.num_interfaces << " interfaces";
79 VLOG(1) << "Got " << topology.num_pads << " pads";
80 VLOG(1) << "Got " << topology.num_links << " links";
81
82 std::vector<struct media_v2_entity> entities;
83 entities.resize(topology.num_entities);
84 topology.ptr_entities = reinterpret_cast<uint64_t>(entities.data());
85 std::vector<struct media_v2_interface> interfaces;
86 interfaces.resize(topology.num_interfaces);
87 topology.ptr_interfaces = reinterpret_cast<uint64_t>(interfaces.data());
88
89 std::vector<struct media_v2_pad> pads;
90 pads.resize(topology.num_pads);
91 topology.ptr_pads = reinterpret_cast<uint64_t>(pads.data());
92 std::vector<struct media_v2_link> links;
93 links.resize(topology.num_links);
94 topology.ptr_links = reinterpret_cast<uint64_t>(links.data());
95 PCHECK(ioctl(fd_.get(), MEDIA_IOC_G_TOPOLOGY, &topology) == 0);
96
97 entities_.reserve(entities.size());
98 for (const struct media_v2_entity &entity : entities) {
99 entities_.emplace_back();
100 entities_.back().entity_ = entity;
101 }
102
103 pads_.reserve(pads.size());
104 for (const struct media_v2_pad &pad : pads) {
105 Entity *found_entity = nullptr;
106 for (Entity &entity : entities_) {
107 if (entity.id() == pad.entity_id) {
108 found_entity = &entity;
109 break;
110 }
111 }
112 CHECK(found_entity != nullptr);
113 pads_.emplace_back();
114 pads_.back().id_ = pad.id;
115 pads_.back().flags_ = pad.flags;
116 pads_.back().entity_ = found_entity;
117
118 found_entity->pads_.emplace_back(&pads_.back());
119 pads_.back().index_ = found_entity->pads_.size() - 1u;
120 }
121
122 links_.reserve(links.size());
123
124 for (const struct media_v2_link &link : links) {
125 VLOG(1) << "Link " << link.id << " from " << link.source_id << " to "
126 << link.sink_id;
127 if ((link.flags & MEDIA_LNK_FL_LINK_TYPE) == MEDIA_LNK_FL_INTERFACE_LINK) {
128 const struct media_v2_interface *found_interface = nullptr;
129 for (const struct media_v2_interface &interface : interfaces) {
130 if (interface.id == link.source_id) {
131 found_interface = &interface;
132 break;
133 }
134 }
135 CHECK(found_interface != nullptr) << ": Failed to find interface";
136 bool found = false;
137 for (Entity &entity : entities_) {
138 if (entity.id() == link.sink_id) {
139 found = true;
140 VLOG(1) << "Added interface to " << entity.name();
141 entity.has_interface_ = true;
142 entity.interface_ = *found_interface;
143 entity.UpdateDevice();
144 break;
145 }
146 }
147 CHECK(found);
148
149 } else if ((link.flags & MEDIA_LNK_FL_LINK_TYPE) ==
150 MEDIA_LNK_FL_DATA_LINK) {
151 links_.emplace_back();
152 links_.back().flags_ = link.flags;
153 links_.back().id_ = link.id;
154
155 Pad *found_source_pad = nullptr;
156 Pad *found_sink_pad = nullptr;
157 for (Pad &pad : pads_) {
158 if (pad.id() == link.source_id) {
159 found_source_pad = &pad;
160 } else if (pad.id() == link.sink_id) {
161 found_sink_pad = &pad;
162 }
163 }
164 CHECK(found_source_pad != nullptr);
165 CHECK(found_sink_pad != nullptr);
166
167 links_.back().source_ = found_source_pad;
168 links_.back().sink_ = found_sink_pad;
169 links_.back().id_ = link.id;
170 found_source_pad->links_.push_back(&links_.back());
171 found_sink_pad->links_.push_back(&links_.back());
172 } else {
173 LOG(FATAL) << "Unknown link type " << link.flags;
174 }
175 }
176}
177
178void MediaDevice::Log() const {
179 LOG(INFO) << "{\"driver\": \"" << driver() << "\",";
180 LOG(INFO) << " \"model\": \"" << model() << "\",";
181 LOG(INFO) << " \"serial\": \"" << serial() << "\",";
182 LOG(INFO) << " \"bus_info\": \"" << bus_info() << "\",";
183 LOG(INFO) << " \"entities\": [";
184 for (const Entity &entity : entities_) {
185 entity.Log();
186 }
187 LOG(INFO) << "] }";
188}
189
190void Pad::Log() const {
191 LOG(INFO) << " {\"id\": " << id() << ",";
192 LOG(INFO) << " \"index\": " << index() << ",";
193 LOG(INFO) << " \"type\": \"" << (source() ? "source" : "sink") << "\"";
194 LOG(INFO) << " \"links\": [";
195 for (size_t i = 0; i < links_size(); ++i) {
196 LOG(INFO) << " {";
197 if (source()) {
198 LOG(INFO) << " \"sink\": \"" << links(i)->sink()->entity()->name()
199 << "\",";
200 LOG(INFO) << " \"sink_index\": \"" << links(i)->sink()->index()
201 << "\",";
202 } else {
203 LOG(INFO) << " \"source\": \""
204 << links(i)->source()->entity()->name() << "\",";
205 LOG(INFO) << " \"source_index\": \"" << links(i)->source()->index()
206 << "\",";
207 }
208 LOG(INFO) << " \"enabled\": " << links(i)->enabled() << ",";
209 LOG(INFO) << " \"immutable\": " << links(i)->immutable() << ",";
210 LOG(INFO) << " }";
211 }
212 LOG(INFO) << " ],";
213 LOG(INFO) << " }";
214}
215
216void Pad::SetSubdevCrop(uint32_t width, uint32_t height) {
217 int fd = open(entity()->device().c_str(), O_RDWR);
218 PCHECK(fd >= 0);
219
220 struct v4l2_subdev_selection selection;
221 std::memset(&selection, 0, sizeof(selection));
222 selection.which = V4L2_SUBDEV_FORMAT_ACTIVE;
223 selection.pad = index();
224
225 PCHECK(ioctl(fd, VIDIOC_SUBDEV_G_SELECTION, &selection) == 0)
226 << ": Failed to set " << entity()->device();
227
228 selection.target = V4L2_SEL_TGT_CROP;
229 selection.r.left = 0;
230 selection.r.top = 0;
231 selection.r.width = width;
232 selection.r.height = height;
233
234 PCHECK(ioctl(fd, VIDIOC_SUBDEV_S_SELECTION, &selection) == 0);
235 LOG(INFO) << "Setting " << entity()->name() << " pad " << index()
236 << " crop to (0, 0) " << width << "x" << height;
237}
238void Pad::SetSubdevFormat(uint32_t width, uint32_t height, uint32_t code) {
239 VLOG(1) << "Opening " << entity()->device();
240 int fd = open(entity()->device().c_str(), O_RDWR);
241 PCHECK(fd >= 0);
242
243 struct v4l2_subdev_format format;
244 std::memset(&format, 0, sizeof(format));
245 format.pad = index();
246 format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
247
248 PCHECK(ioctl(fd, VIDIOC_SUBDEV_G_FMT, &format) == 0);
249
250 VLOG(1) << format.format.width << ", " << format.format.height << ", "
251 << format.format.code << " field " << format.format.field
252 << " colorspace " << format.format.colorspace << " ycbcr_enc "
253 << format.format.ycbcr_enc << " quantization "
254 << format.format.quantization << " xfer_func "
255 << format.format.xfer_func;
256
257 format.format.width = width;
258 format.format.height = height;
259 format.format.code = code;
260 format.format.field = V4L2_FIELD_NONE;
261 format.format.colorspace = V4L2_COLORSPACE_SRGB;
262 format.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
263 format.format.quantization = V4L2_QUANTIZATION_DEFAULT;
264 format.format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
265
266 LOG(INFO) << "Setting " << entity()->name() << " pad " << index() << " format to "
267 << width << "x" << height << " code 0x" << std::hex << code;
268
269 PCHECK(ioctl(fd, VIDIOC_SUBDEV_S_FMT, &format) == 0);
270
271 PCHECK(close(fd) == 0);
272}
273
274void Entity::SetFormat(uint32_t width, uint32_t height, uint32_t code) {
275 VLOG(1) << "Opening " << device();
276 int fd = open(device().c_str(), O_RDWR);
277 PCHECK(fd >= 0);
278
279 struct v4l2_format format;
280 std::memset(&format, 0, sizeof(format));
281 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
282
283 PCHECK(ioctl(fd, VIDIOC_G_FMT, &format) == 0);
284
285 VLOG(1) << "width " << format.fmt.pix_mp.width;
286 VLOG(1) << "height " << format.fmt.pix_mp.height;
287 VLOG(1) << "pixelformat " << format.fmt.pix_mp.pixelformat;
288 VLOG(1) << "field " << format.fmt.pix_mp.field;
289 VLOG(1) << "colorspace " << format.fmt.pix_mp.colorspace;
290 VLOG(1) << " sizeimage " << format.fmt.pix_mp.plane_fmt[0].sizeimage;
291 VLOG(1) << " bytesperline " << format.fmt.pix_mp.plane_fmt[0].bytesperline;
292 VLOG(1) << "num_planes "
293 << static_cast<uint64_t>(format.fmt.pix_mp.num_planes);
294 VLOG(1) << "flags " << static_cast<uint64_t>(format.fmt.pix_mp.flags);
295 VLOG(1) << "ycbcr_enc " << static_cast<uint64_t>(format.fmt.pix_mp.ycbcr_enc);
296 VLOG(1) << "quantization "
297 << static_cast<uint64_t>(format.fmt.pix_mp.quantization);
298 VLOG(1) << "xfer_func " << static_cast<uint64_t>(format.fmt.pix_mp.xfer_func);
299
300 format.fmt.pix_mp.width = width;
301 format.fmt.pix_mp.height = height;
302 format.fmt.pix_mp.pixelformat = code;
303 format.fmt.pix_mp.field = V4L2_FIELD_NONE;
304 format.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
305 format.fmt.pix_mp.num_planes = 1;
306
307 // TODO(austin): This is probably V4L2_PIX_FMT_YUV422P specific... We really
308 // want to extract bytes/pixel.
309 CHECK((code == V4L2_PIX_FMT_YUV422P) || (code == V4L2_PIX_FMT_YUYV));
310 format.fmt.pix_mp.plane_fmt[0].sizeimage = width * height * 2;
311 format.fmt.pix_mp.plane_fmt[0].bytesperline = width;
312
313 format.fmt.pix_mp.flags = 0;
314 format.fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
315 format.fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT;
316 format.fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT;
317
318 LOG(INFO) << "Setting " << name() << " to " << width << "x" << height
319 << " code 0x" << std::hex << code;
320 PCHECK(ioctl(fd, VIDIOC_S_FMT, &format) == 0);
321
322 PCHECK(close(fd) == 0);
323}
324
325void MediaDevice::Reset(Link *link) {
326 LOG(INFO) << "Disabling link " << link->source()->entity()->name() << " -> "
327 << link->sink()->entity()->name();
328 struct media_link_desc link_desc;
329 link_desc.source.entity = link->source()->entity()->id();
330 link_desc.source.index = link->source()->index();
331 link_desc.source.flags = 0;
332 link_desc.sink.entity = link->sink()->entity()->id();
333 link_desc.sink.index = link->sink()->index();
334 link_desc.sink.flags = 0;
335 link_desc.flags = link->flags() & (~MEDIA_LNK_FL_ENABLED);
336 PCHECK(ioctl(fd_.get(), MEDIA_IOC_SETUP_LINK, &link_desc) == 0);
337
338 link->flags_ = link_desc.flags;
339}
340
341void MediaDevice::Enable(Link *link) {
342 LOG(INFO) << "Enabling link " << link->source()->entity()->name() << " -> "
343 << link->sink()->entity()->name();
344 struct media_link_desc link_desc;
345 link_desc.source.entity = link->source()->entity()->id();
346 link_desc.source.index = link->source()->index();
347 link_desc.source.flags = 0;
348 link_desc.sink.entity = link->sink()->entity()->id();
349 link_desc.sink.index = link->sink()->index();
350 link_desc.sink.flags = 0;
351 link_desc.flags = link->flags() | MEDIA_LNK_FL_ENABLED;
352 PCHECK(ioctl(fd_.get(), MEDIA_IOC_SETUP_LINK, &link_desc) == 0);
353
354 link->flags_ = link_desc.flags;
355}
356
357Entity *MediaDevice::FindEntity(std::string_view entity_name) {
358 for (Entity &entity : entities_) {
359 if (entity.name() == entity_name) {
360 return &entity;
361 }
362 }
363 return nullptr;
364}
365
366Link *MediaDevice::FindLink(std::string_view source, int source_pad_index,
367 std::string_view sink, int sink_pad_index) {
368 Entity *source_entity = CHECK_NOTNULL(FindEntity(source));
369 Entity *sink_entity = CHECK_NOTNULL(FindEntity(sink));
370 Pad *source_pad = source_entity->pads()[source_pad_index];
371 Pad *sink_pad = sink_entity->pads()[sink_pad_index];
372 for (size_t i = 0; i < source_pad->links_size(); ++i) {
373 if (source_pad->links(i)->sink() == sink_pad) {
374 return source_pad->links(i);
375 }
376 }
377 LOG(FATAL) << "Failed to find link between " << source << " pad "
378 << source_pad_index << " and " << sink << " pad "
379 << sink_pad_index;
380}
381
382void MediaDevice::Reset() {
383 LOG(INFO) << "Resetting " << bus_info();
384
385 for (Link &link : *links()) {
386 if (!link.immutable()) {
387 Reset(&link);
388 }
389 }
390}
391
392std::optional<MediaDevice> FindMediaDevice(std::string_view device) {
393 for (int media_device_index = 0;; ++media_device_index) {
394 std::optional<MediaDevice> media_device =
395 MediaDevice::Initialize(media_device_index);
396 if (!media_device) {
397 return std::nullopt;
398 }
399 if (media_device->bus_info() == device) {
400 return media_device;
401 }
402 }
403}
404
405} // namespace vision
406} // namespace frc971