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