Ring Daemon 16.0.0
Loading...
Searching...
No Matches
v4l2/video_device_impl.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include <algorithm>
19#include <cassert>
20#include <climits>
21#include <cstring>
22#include <map>
23#include <sstream>
24#include <stdexcept>
25#include <string>
26#include <vector>
27
28extern "C" {
29#include <linux/videodev2.h>
30#if !defined(VIDIOC_ENUM_FRAMESIZES) || !defined(VIDIOC_ENUM_FRAMEINTERVALS)
31#error You need at least Linux 2.6.19
32#endif
33
34#include <fcntl.h>
35#include <unistd.h>
36#include <sys/ioctl.h>
37}
38
39#include "logger.h"
40#include "../video_device.h"
41#include "string_utils.h"
42
43#define ZEROVAR(x) std::memset(&(x), 0, sizeof(x))
44
45namespace jami {
46namespace video {
47
49{
50public:
51 VideoV4l2Rate(unsigned rate_numerator = 0, unsigned rate_denominator = 0, unsigned format = 0)
53 , pixel_format(format)
54 {}
55
57 unsigned pixel_format;
58 std::string libAvPixelformat() const;
59};
60
62{
63public:
64 VideoV4l2Size(const unsigned width, const unsigned height)
65 : width(width)
66 , height(height)
67 , rates_()
68 {}
69
73 void readFrameRates(int fd, unsigned int pixel_format);
74
75 std::vector<FrameRate> getRateList() const;
76 VideoV4l2Rate getRate(const FrameRate& rate) const;
77
78 unsigned width;
79 unsigned height;
80
81private:
82 void addRate(VideoV4l2Rate proposed_rate);
83 std::vector<VideoV4l2Rate> rates_;
84};
85
86bool
88{
89 return a.height == b.height && a.width == b.width;
90}
91
93{
94public:
95 VideoV4l2Channel(unsigned idx, const char* s);
96
100 void readFormats(int fd);
101
105 unsigned int readSizes(int fd, unsigned int pixel_format);
106
107 std::vector<VideoSize> getSizeList() const;
108 const VideoV4l2Size& getSize(VideoSize name) const;
109
110 unsigned idx;
111 std::string name;
112
113private:
114 void putCIFFirst();
115 std::vector<VideoV4l2Size> sizes_;
116};
117
118class VideoDeviceImpl
119{
120public:
124 VideoDeviceImpl(const std::string& id, const std::string& path);
125
126 std::string unique_id;
127 std::string path;
128 std::string name;
129
130 std::vector<std::string> getChannelList() const;
131 std::vector<VideoSize> getSizeList(const std::string& channel) const;
132 std::vector<FrameRate> getRateList(const std::string& channel, VideoSize size) const;
133
136
137private:
138 std::vector<VideoV4l2Channel> channels_;
139 const VideoV4l2Channel& getChannel(const std::string& name) const;
140
141 /* Preferences */
142 VideoV4l2Channel channel_;
143 VideoV4l2Size size_;
144 VideoV4l2Rate rate_;
145};
146
147static const unsigned pixelformats_supported[] = {
148 /* pixel format depth description */
149
150 /* preferred formats, they can be fed directly to the video encoder */
151 V4L2_PIX_FMT_YUV420, /* 12 YUV 4:2:0 */
152 V4L2_PIX_FMT_YUV422P, /* 16 YVU422 planar */
153 V4L2_PIX_FMT_YUV444, /* 16 xxxxyyyy uuuuvvvv */
154
155 /* Luminance+Chrominance formats */
156 V4L2_PIX_FMT_YVU410, /* 9 YVU 4:1:0 */
157 V4L2_PIX_FMT_YVU420, /* 12 YVU 4:2:0 */
158 V4L2_PIX_FMT_YUYV, /* 16 YUV 4:2:2 */
159 V4L2_PIX_FMT_YYUV, /* 16 YUV 4:2:2 */
160 V4L2_PIX_FMT_YVYU, /* 16 YVU 4:2:2 */
161 V4L2_PIX_FMT_UYVY, /* 16 YUV 4:2:2 */
162 V4L2_PIX_FMT_VYUY, /* 16 YUV 4:2:2 */
163 V4L2_PIX_FMT_YUV411P, /* 16 YVU411 planar */
164 V4L2_PIX_FMT_Y41P, /* 12 YUV 4:1:1 */
165 V4L2_PIX_FMT_YUV555, /* 16 YUV-5-5-5 */
166 V4L2_PIX_FMT_YUV565, /* 16 YUV-5-6-5 */
167 V4L2_PIX_FMT_YUV32, /* 32 YUV-8-8-8-8 */
168 V4L2_PIX_FMT_YUV410, /* 9 YUV 4:1:0 */
169 V4L2_PIX_FMT_HI240, /* 8 8-bit color */
170 V4L2_PIX_FMT_HM12, /* 8 YUV 4:2:0 16x16 macroblocks */
171
172 /* two planes -- one Y, one Cr + Cb interleaved */
173 V4L2_PIX_FMT_NV12, /* 12 Y/CbCr 4:2:0 */
174 V4L2_PIX_FMT_NV21, /* 12 Y/CrCb 4:2:0 */
175 V4L2_PIX_FMT_NV16, /* 16 Y/CbCr 4:2:2 */
176 V4L2_PIX_FMT_NV61, /* 16 Y/CrCb 4:2:2 */
177
178 /* Compressed formats */
194
195#if 0
196 /* RGB formats */
197 V4L2_PIX_FMT_RGB332, /* 8 RGB-3-3-2 */
198 V4L2_PIX_FMT_RGB444, /* 16 xxxxrrrr ggggbbbb */
199 V4L2_PIX_FMT_RGB555, /* 16 RGB-5-5-5 */
200 V4L2_PIX_FMT_RGB565, /* 16 RGB-5-6-5 */
201 V4L2_PIX_FMT_RGB555X, /* 16 RGB-5-5-5 BE */
202 V4L2_PIX_FMT_RGB565X, /* 16 RGB-5-6-5 BE */
203 V4L2_PIX_FMT_BGR666, /* 18 BGR-6-6-6 */
204 V4L2_PIX_FMT_BGR24, /* 24 BGR-8-8-8 */
205 V4L2_PIX_FMT_RGB24, /* 24 RGB-8-8-8 */
206 V4L2_PIX_FMT_BGR32, /* 32 BGR-8-8-8-8 */
207 V4L2_PIX_FMT_RGB32, /* 32 RGB-8-8-8-8 */
208
209 /* Grey formats */
210 V4L2_PIX_FMT_GREY, /* 8 Greyscale */
211 V4L2_PIX_FMT_Y4, /* 4 Greyscale */
212 V4L2_PIX_FMT_Y6, /* 6 Greyscale */
213 V4L2_PIX_FMT_Y10, /* 10 Greyscale */
214 V4L2_PIX_FMT_Y16, /* 16 Greyscale */
215
216 /* Palette formats */
217 V4L2_PIX_FMT_PAL8, /* 8 8-bit palette */
218#endif
219};
220
221/* Returns a score for the given pixelformat
222 *
223 * Lowest score is the best, the first entries in the array are the formats
224 * supported as an input for the video encoders.
225 *
226 * See pixelformats_supported array for the support list.
227 */
228static unsigned int
230{
231 unsigned int formats_count = std::size(pixelformats_supported);
232 for (unsigned int i = 0; i < formats_count; ++i) {
234 return i;
235 }
236 return UINT_MAX - 1;
237}
238
239using std::vector;
240using std::string;
241
244{
246 rates.reserve(rates_.size());
247 for (const auto& r : rates_)
248 rates.emplace_back(r.frame_rate);
249 return rates;
250}
251
252void
253VideoV4l2Size::readFrameRates(int fd, unsigned int pixel_format)
254{
255 VideoV4l2Rate fallback_rate {1, 25, pixel_format};
256
259 frmival.pixel_format = pixel_format;
260 frmival.width = width;
261 frmival.height = height;
262
264 addRate(fallback_rate);
265 JAMI_ERR("Unable to query frame interval for size");
266 return;
267 }
268
270 addRate(fallback_rate);
271 JAMI_ERR("Continuous and stepwise Frame Intervals are not supported");
272 return;
273 }
274
275 do {
276 addRate({frmival.discrete.numerator, frmival.discrete.denominator, pixel_format});
277 ++frmival.index;
278 } while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival));
279}
280
283{
284 for (const auto& item : rates_) {
285 if (std::fabs((item.frame_rate - rate).real()) < 0.0001)
286 return item;
287 }
288 return rates_.back();
289}
290
291void
292VideoV4l2Size::addRate(VideoV4l2Rate new_rate)
293{
294 bool rate_found = false;
295 for (auto& item : rates_) {
296 if (item.frame_rate == new_rate.frame_rate) {
297 if (pixelformat_score(item.pixel_format) > pixelformat_score(new_rate.pixel_format)) {
298 // Make sure we will use the prefered pixelformat (lower score means prefered format)
299 item.pixel_format = new_rate.pixel_format;
300 }
301 rate_found = true;
302 }
303 }
304
305 if (!rate_found)
306 rates_.push_back(new_rate);
307}
308
309VideoV4l2Channel::VideoV4l2Channel(unsigned idx, const char* s)
310 : idx(idx)
311 , name(s)
312 , sizes_()
313{}
314
315std::vector<VideoSize>
317{
319 v.reserve(sizes_.size());
320 for (const auto& item : sizes_)
321 v.emplace_back(item.width, item.height);
322
323 return v;
324}
325
326unsigned int
328{
331
332 frmsize.index = 0;
333 frmsize.pixel_format = pixelformat;
334
335 if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) < 0) {
336 v4l2_format fmt;
337 ZEROVAR(fmt);
338
340 if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0)
341 throw std::runtime_error("Unable to get format");
342
343 VideoV4l2Size size(fmt.fmt.pix.width, fmt.fmt.pix.height);
344 size.readFrameRates(fd, fmt.fmt.pix.pixelformat);
345 sizes_.push_back(size);
346
347 return fmt.fmt.pix.pixelformat;
348 }
349
351 // We do not take care of V4L2_FRMSIZE_TYPE_CONTINUOUS or V4L2_FRMSIZE_TYPE_STEPWISE
352 JAMI_ERR("Continuous Frame sizes not supported");
353 return pixelformat;
354 }
355
356 // Real work starts here: attach framerates to sizes and update pixelformat information
357 do {
358 bool size_exists = false;
359 VideoV4l2Size size(frmsize.discrete.width, frmsize.discrete.height);
360
361 for (auto& item : sizes_) {
362 if (item == size) {
363 size_exists = true;
364 // If a size already exist we add frame rates since there may be some
365 // frame rates available in one format that are not availabe in another.
366 item.readFrameRates(fd, frmsize.pixel_format);
367 }
368 }
369
370 if (!size_exists) {
371 size.readFrameRates(fd, frmsize.pixel_format);
372 sizes_.push_back(size);
373 }
374
375 ++frmsize.index;
376 } while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize));
377
378 return pixelformat;
379}
380
381// Put CIF resolution (352x288) first in the list since it is more prevalent in
382// VoIP
383void
384VideoV4l2Channel::putCIFFirst()
385{
386 const auto iter = std::find_if(sizes_.begin(), sizes_.end(), [](const VideoV4l2Size& size) {
387 return size.width == 352 and size.height == 258;
388 });
389
390 if (iter != sizes_.end() and iter != sizes_.begin())
391 std::swap(*iter, *sizes_.begin());
392}
393
394void
396{
397 if (ioctl(fd, VIDIOC_S_INPUT, &idx))
398 throw std::runtime_error("VIDIOC_S_INPUT failed");
399
400 v4l2_fmtdesc fmt;
401 ZEROVAR(fmt);
402 unsigned fmt_index = 0;
403
405 while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) {
406 if (fmt_index != fmt.index)
407 break;
408 readSizes(fd, fmt.pixelformat);
409 fmt.index = ++fmt_index;
410 }
411
412 if (fmt_index == 0)
413 throw std::runtime_error("Unable to enumerate formats");
414
415 putCIFFirst();
416}
417
418const VideoV4l2Size&
420{
421 for (const auto& item : sizes_) {
422 if (item.width == s.first && item.height == s.second)
423 return item;
424 }
425
426 assert(not sizes_.empty());
427 return sizes_.front();
428}
429
430VideoDeviceImpl::VideoDeviceImpl(const string& id, const std::string& path)
431 : unique_id(id)
432 , path(path)
433 , name()
434 , channels_()
435 , channel_(-1, "")
436 , size_(-1, -1)
437 , rate_(-1, 1, 0)
438{
439 if (id == DEVICE_DESKTOP) {
441 rate_.frame_rate = 30;
442 return;
443 }
444 int fd = open(path.c_str(), O_RDWR);
445 if (fd == -1)
446 throw std::runtime_error("Unable to open device");
447
449 if (ioctl(fd, VIDIOC_QUERYCAP, &cap))
450 throw std::runtime_error("Unable to query capabilities");
451
452 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
453 throw std::runtime_error("Not a capture device");
454
455 if (cap.capabilities & V4L2_CAP_TOUCH)
456 throw std::runtime_error("Touch device, ignoring it");
457
458 name = string(reinterpret_cast<const char*>(cap.card));
459
460 v4l2_input input;
461 ZEROVAR(input);
462 unsigned idx;
463 input.index = idx = 0;
464 while (!ioctl(fd, VIDIOC_ENUMINPUT, &input)) {
465 if (idx != input.index)
466 break;
467
468 if (input.type & V4L2_INPUT_TYPE_CAMERA) {
469 VideoV4l2Channel channel(idx, (const char*) input.name);
470 channel.readFormats(fd);
471 if (not channel.getSizeList().empty())
472 channels_.push_back(channel);
473 }
474
475 input.index = ++idx;
476 }
477
478 ::close(fd);
479}
480
481string
483{
484 switch (pixel_format) {
485 // Set codec name for those pixelformats.
486 // Those names can be found in libavcodec/codec_desc.c
488 return "mjpeg";
489 case V4L2_PIX_FMT_DV:
490 return "dvvideo";
493 return "mpeg1video";
497 return "h264";
499 return "h263";
501 return "mpeg2video";
503 return "mpeg4";
506 return "vc1";
507 case V4L2_PIX_FMT_VP8:
508 return "vp8";
509 default: // Most pixel formats do not need any codec
510 return "";
511 }
512}
513
516{
518 return {"default"};
520 v.reserve(channels_.size());
521 for (const auto& itr : channels_)
522 v.push_back(itr.name);
523
524 return v;
525}
526
528VideoDeviceImpl::getSizeList(const string& channel) const
529{
530 if (unique_id == DEVICE_DESKTOP) {
531 return {VideoSize(0, 0)};
532 }
533 return getChannel(channel).getSizeList();
534}
535
537VideoDeviceImpl::getRateList(const string& channel, VideoSize size) const
538{
539 if (unique_id == DEVICE_DESKTOP) {
540 return {FrameRate(1),
541 FrameRate(5),
542 FrameRate(10),
543 FrameRate(15),
544 FrameRate(20),
545 FrameRate(25),
546 FrameRate(30),
547 FrameRate(60),
548 FrameRate(120),
549 FrameRate(144)};
550 }
551 return getChannel(channel).getSize(size).getRateList();
552}
553
554const VideoV4l2Channel&
555VideoDeviceImpl::getChannel(const string& name) const
556{
557 for (const auto& item : channels_)
558 if (item.name == name)
559 return item;
560
561 assert(not channels_.empty());
562 return channels_.front();
563}
564
565DeviceParams
567{
568 DeviceParams params;
569 params.name = name;
570 params.unique_id = unique_id;
571 params.input = path;
572 if (unique_id == DEVICE_DESKTOP) {
573 const auto* env = std::getenv("WAYLAND_DISPLAY");
574 if (!env || strlen(env) == 0) {
575 params.format = "x11grab";
576 } else {
577 params.format = "lavfi";
578 params.input = "pipewiregrab";
579 }
580 params.framerate = rate_.frame_rate;
581 return params;
582 }
583 params.format = "video4linux2";
584 params.channel_name = channel_.name;
585 params.channel = channel_.idx;
586 params.width = size_.width;
587 params.height = size_.height;
588 params.framerate = rate_.frame_rate;
589 params.pixel_format = rate_.libAvPixelformat();
590 return params;
591}
592
593void
594VideoDeviceImpl::setDeviceParams(const DeviceParams& params)
595{
596 if (unique_id == DEVICE_DESKTOP) {
597 rate_.frame_rate = params.framerate;
598 return;
599 }
600 // Set preferences or fallback to defaults.
601 channel_ = getChannel(params.channel_name);
602 size_ = channel_.getSize({params.width, params.height});
603 try {
604 rate_ = size_.getRate(params.framerate);
605 } catch (...) {
606 rate_ = {};
607 }
608}
609
610VideoDevice::VideoDevice(const std::string& id,
611 const std::vector<std::map<std::string, std::string>>& devInfo)
612 : id_(id)
613{
614 deviceImpl_ = std::make_shared<VideoDeviceImpl>(id,
615 devInfo.empty() ? id
616 : devInfo.at(0).at("devPath"));
617 name = deviceImpl_->name;
618}
619
620DeviceParams
622{
623 auto params = deviceImpl_->getDeviceParams();
624 params.orientation = orientation_;
625 return params;
626}
627
628void
629VideoDevice::setDeviceParams(const DeviceParams& params)
630{
631 return deviceImpl_->setDeviceParams(params);
632}
633
634std::vector<std::string>
636{
637 return deviceImpl_->getChannelList();
638}
639
640std::vector<VideoSize>
641VideoDevice::getSizeList(const std::string& channel) const
642{
643 return deviceImpl_->getSizeList(channel);
644}
645
646std::vector<FrameRate>
647VideoDevice::getRateList(const std::string& channel, VideoSize size) const
648{
649 return deviceImpl_->getRateList(channel, size);
650}
651
653
654} // namespace video
655} // namespace jami
VideoDeviceImpl(const std::string &id, const std::string &path)
std::vector< VideoSize > getSizeList() const
std::vector< std::string > getChannelList() const
void setDeviceParams(const DeviceParams &)
DeviceParams getDeviceParams() const
std::vector< FrameRate > getRateList() const
std::vector< std::string > getChannelList() const
VideoDevice(const std::string &path, const std::vector< std::map< std::string, std::string > > &devInfo)
DeviceParams getDeviceParams() const
Returns the parameters needed for actual use of the device.
VideoV4l2Channel(unsigned idx, const char *s)
const VideoV4l2Size & getSize(VideoSize name) const
unsigned int readSizes(int fd, unsigned int pixel_format)
std::vector< VideoSize > getSizeList() const
VideoV4l2Rate(unsigned rate_numerator=0, unsigned rate_denominator=0, unsigned format=0)
VideoV4l2Rate getRate(const FrameRate &rate) const
std::vector< FrameRate > getRateList() const
void readFrameRates(int fd, unsigned int pixel_format)
VideoV4l2Size(const unsigned width, const unsigned height)
#define JAMI_ERR(...)
Definition logger.h:218
bool operator==(VideoV4l2Size &a, VideoV4l2Size &b)
static constexpr const char DEVICE_DESKTOP[]
static const unsigned pixelformats_supported[]
static unsigned int pixelformat_score(unsigned pixelformat)
std::pair< unsigned, unsigned > VideoSize
rational< double > FrameRate
void emitSignal(Args... args)
Definition ring_signal.h:64
DeviceParams Parameters used by MediaDecoder and MediaEncoder to open a LibAV device/stream.
#define ZEROVAR(x)