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