/* Copyright © 2000,2001 Fabrice Bellard Copyright © 2006 Luca Abeni Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "v4l2.h" #include #include #include #include #include #include #include #include /** * Most of this file is adapted from libavdevice's v4l2.c, * which retrieves useful information but only exposes it to * stdout and is not part of the public API for some reason. */ static std::map createPixFmtToQuality() { std::map m; m[V4L2_PIX_FMT_H264] = 3; m[V4L2_PIX_FMT_MJPEG] = 2; m[V4L2_PIX_FMT_YUYV] = 1; m[V4L2_PIX_FMT_UYVY] = 1; return m; } const std::map pixFmtToQuality = createPixFmtToQuality(); static std::map createPixFmtToName() { std::map m; m[V4L2_PIX_FMT_H264] = QString("h264"); m[V4L2_PIX_FMT_MJPEG] = QString("mjpeg"); m[V4L2_PIX_FMT_YUYV] = QString("yuyv422"); m[V4L2_PIX_FMT_UYVY] = QString("uyvy422"); return m; } const std::map pixFmtToName = createPixFmtToName(); static int deviceOpen(QString devName, int* error) { struct v4l2_capability cap; int fd; const std::string devNameString = devName.toStdString(); fd = open(devNameString.c_str(), O_RDWR, 0); if (fd < 0) { *error = errno; return fd; } if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { *error = errno; goto fail; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { *error = ENODEV; goto fail; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { *error = ENOSYS; goto fail; } return fd; fail: close(fd); return -1; } static QVector getDeviceModeFramerates(int fd, unsigned w, unsigned h, uint32_t pixelFormat) { QVector rates; v4l2_frmivalenum vfve{}; vfve.pixel_format = pixelFormat; vfve.height = h; vfve.width = w; while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &vfve)) { float rate; switch (vfve.type) { case V4L2_FRMSIZE_TYPE_DISCRETE: rate = vfve.discrete.denominator / vfve.discrete.numerator; if (!rates.contains(rate)) rates.append(rate); break; case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_STEPWISE: rate = vfve.stepwise.min.denominator / vfve.stepwise.min.numerator; if (!rates.contains(rate)) rates.append(rate); } vfve.index++; } return rates; } QVector v4l2::getDeviceModes(QString devName) { QVector modes; int error = 0; int fd = deviceOpen(devName, &error); if (fd < 0 || error != 0) { return modes; } v4l2_fmtdesc vfd{}; vfd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; while (!ioctl(fd, VIDIOC_ENUM_FMT, &vfd)) { vfd.index++; v4l2_frmsizeenum vfse{}; vfse.pixel_format = vfd.pixelformat; while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse)) { VideoMode mode; mode.pixel_format = vfse.pixel_format; switch (vfse.type) { case V4L2_FRMSIZE_TYPE_DISCRETE: mode.width = vfse.discrete.width; mode.height = vfse.discrete.height; break; case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_STEPWISE: mode.width = vfse.stepwise.max_width; mode.height = vfse.stepwise.max_height; break; default: continue; } QVector rates = getDeviceModeFramerates(fd, mode.width, mode.height, vfd.pixelformat); // insert dummy FPS value to have the mode in the list even if we don't know the FPS // this fixes support for some webcams, see #5082 if (rates.isEmpty()) { rates.append(0.0f); } for (float rate : rates) { mode.FPS = rate; if (!modes.contains(mode)) { modes.append(std::move(mode)); } } vfse.index++; } } return modes; } QVector> v4l2::getDeviceList() { QVector> devices; QStringList deviceFiles; DIR* dir = opendir("/dev"); if (!dir) return devices; dirent* e; while ((e = readdir(dir))) if (!strncmp(e->d_name, "video", 5) || !strncmp(e->d_name, "vbi", 3)) deviceFiles += QString("/dev/") + e->d_name; closedir(dir); for (QString file : deviceFiles) { const std::string filePath = file.toStdString(); int fd = open(filePath.c_str(), O_RDWR); if (fd < 0) { continue; } v4l2_capability caps; ioctl(fd, VIDIOC_QUERYCAP, &caps); close(fd); devices += {file, (const char*)caps.card}; } return devices; } QString v4l2::getPixelFormatString(uint32_t pixel_format) { if (pixFmtToName.find(pixel_format) == pixFmtToName.end()) { qWarning() << "Pixel format not found"; return QString("invalid"); } return pixFmtToName.at(pixel_format); } bool v4l2::betterPixelFormat(uint32_t a, uint32_t b) { if (pixFmtToQuality.find(a) == pixFmtToQuality.end()) { return false; } else if (pixFmtToQuality.find(b) == pixFmtToQuality.end()) { return true; } return pixFmtToQuality.at(a) > pixFmtToQuality.at(b); }