1 /*
2     Copyright © 2000,2001 Fabrice Bellard
3     Copyright © 2006 Luca Abeni
4     Copyright © 2015-2019 by The qTox Project Contributors
5 
6     This file is part of qTox, a Qt-based graphical interface for Tox.
7 
8     qTox is libre software: you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12 
13     qTox is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 
23 #include "v4l2.h"
24 
25 #include <QDebug>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <linux/videodev2.h>
30 #include <map>
31 #include <sys/ioctl.h>
32 #include <unistd.h>
33 
34 /**
35  * Most of this file is adapted from libavdevice's v4l2.c,
36  * which retrieves useful information but only exposes it to
37  * stdout and is not part of the public API for some reason.
38  */
39 
createPixFmtToQuality()40 static std::map<uint32_t, uint8_t> createPixFmtToQuality()
41 {
42     std::map<uint32_t, uint8_t> m;
43     m[V4L2_PIX_FMT_H264] = 3;
44     m[V4L2_PIX_FMT_MJPEG] = 2;
45     m[V4L2_PIX_FMT_YUYV] = 1;
46     m[V4L2_PIX_FMT_UYVY] = 1;
47     return m;
48 }
49 const std::map<uint32_t, uint8_t> pixFmtToQuality = createPixFmtToQuality();
50 
createPixFmtToName()51 static std::map<uint32_t, QString> createPixFmtToName()
52 {
53     std::map<uint32_t, QString> m;
54     m[V4L2_PIX_FMT_H264] = QString("h264");
55     m[V4L2_PIX_FMT_MJPEG] = QString("mjpeg");
56     m[V4L2_PIX_FMT_YUYV] = QString("yuyv422");
57     m[V4L2_PIX_FMT_UYVY] = QString("uyvy422");
58     return m;
59 }
60 const std::map<uint32_t, QString> pixFmtToName = createPixFmtToName();
61 
deviceOpen(QString devName,int * error)62 static int deviceOpen(QString devName, int* error)
63 {
64     struct v4l2_capability cap;
65     int fd;
66 
67     const std::string devNameString = devName.toStdString();
68     fd = open(devNameString.c_str(), O_RDWR, 0);
69     if (fd < 0) {
70         *error = errno;
71         return fd;
72     }
73 
74     if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
75         *error = errno;
76         goto fail;
77     }
78 
79     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
80         *error = ENODEV;
81         goto fail;
82     }
83 
84     if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
85         *error = ENOSYS;
86         goto fail;
87     }
88 
89     return fd;
90 
91 fail:
92     close(fd);
93     return -1;
94 }
95 
getDeviceModeFramerates(int fd,unsigned w,unsigned h,uint32_t pixelFormat)96 static QVector<float> getDeviceModeFramerates(int fd, unsigned w, unsigned h,
97                                                        uint32_t pixelFormat)
98 {
99     QVector<float> rates;
100     v4l2_frmivalenum vfve{};
101     vfve.pixel_format = pixelFormat;
102     vfve.height = h;
103     vfve.width = w;
104 
105     while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &vfve)) {
106         float rate;
107         switch (vfve.type) {
108         case V4L2_FRMSIZE_TYPE_DISCRETE:
109             rate = vfve.discrete.denominator / vfve.discrete.numerator;
110             if (!rates.contains(rate))
111                 rates.append(rate);
112             break;
113         case V4L2_FRMSIZE_TYPE_CONTINUOUS:
114         case V4L2_FRMSIZE_TYPE_STEPWISE:
115             rate = vfve.stepwise.min.denominator / vfve.stepwise.min.numerator;
116             if (!rates.contains(rate))
117                 rates.append(rate);
118         }
119         vfve.index++;
120     }
121 
122     return rates;
123 }
124 
getDeviceModes(QString devName)125 QVector<VideoMode> v4l2::getDeviceModes(QString devName)
126 {
127     QVector<VideoMode> modes;
128 
129     int error = 0;
130     int fd = deviceOpen(devName, &error);
131     if (fd < 0 || error != 0) {
132         return modes;
133     }
134 
135     v4l2_fmtdesc vfd{};
136     vfd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
137 
138     while (!ioctl(fd, VIDIOC_ENUM_FMT, &vfd)) {
139         vfd.index++;
140 
141         v4l2_frmsizeenum vfse{};
142         vfse.pixel_format = vfd.pixelformat;
143 
144         while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse)) {
145             VideoMode mode;
146             mode.pixel_format = vfse.pixel_format;
147             switch (vfse.type) {
148             case V4L2_FRMSIZE_TYPE_DISCRETE:
149                 mode.width = vfse.discrete.width;
150                 mode.height = vfse.discrete.height;
151                 break;
152             case V4L2_FRMSIZE_TYPE_CONTINUOUS:
153             case V4L2_FRMSIZE_TYPE_STEPWISE:
154                 mode.width = vfse.stepwise.max_width;
155                 mode.height = vfse.stepwise.max_height;
156                 break;
157             default:
158                 continue;
159             }
160 
161             QVector<float> rates =
162                 getDeviceModeFramerates(fd, mode.width, mode.height, vfd.pixelformat);
163 
164             // insert dummy FPS value to have the mode in the list even if we don't know the FPS
165             // this fixes support for some webcams, see #5082
166             if (rates.isEmpty()) {
167                 rates.append(0.0f);
168             }
169 
170             for (float rate : rates) {
171                 mode.FPS = rate;
172                 if (!modes.contains(mode)) {
173                     modes.append(std::move(mode));
174                 }
175             }
176             vfse.index++;
177         }
178     }
179 
180     return modes;
181 }
182 
getDeviceList()183 QVector<QPair<QString, QString>> v4l2::getDeviceList()
184 {
185     QVector<QPair<QString, QString>> devices;
186     QStringList deviceFiles;
187 
188     DIR* dir = opendir("/dev");
189     if (!dir)
190         return devices;
191 
192     dirent* e;
193     while ((e = readdir(dir)))
194         if (!strncmp(e->d_name, "video", 5) || !strncmp(e->d_name, "vbi", 3))
195             deviceFiles += QString("/dev/") + e->d_name;
196     closedir(dir);
197 
198     for (QString file : deviceFiles) {
199         const std::string filePath = file.toStdString();
200         int fd = open(filePath.c_str(), O_RDWR);
201         if (fd < 0) {
202             continue;
203         }
204 
205         v4l2_capability caps;
206         ioctl(fd, VIDIOC_QUERYCAP, &caps);
207         close(fd);
208 
209         devices += {file, (const char*)caps.card};
210     }
211     return devices;
212 }
213 
getPixelFormatString(uint32_t pixel_format)214 QString v4l2::getPixelFormatString(uint32_t pixel_format)
215 {
216     if (pixFmtToName.find(pixel_format) == pixFmtToName.end()) {
217         qWarning() << "Pixel format not found";
218         return QString("invalid");
219     }
220     return pixFmtToName.at(pixel_format);
221 }
222 
betterPixelFormat(uint32_t a,uint32_t b)223 bool v4l2::betterPixelFormat(uint32_t a, uint32_t b)
224 {
225     if (pixFmtToQuality.find(a) == pixFmtToQuality.end()) {
226         return false;
227     } else if (pixFmtToQuality.find(b) == pixFmtToQuality.end()) {
228         return true;
229     }
230     return pixFmtToQuality.at(a) > pixFmtToQuality.at(b);
231 }
232