1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qeglfsintegration_p.h"
41 #include "qlinuxmediadevice.h"
42 #include <qeglfskmshelpers.h>
43 
44 #include <QtCore/QLoggingCategory>
45 #include <QtCore/QSize>
46 #include <QtCore/QRect>
47 
48 #include <sys/ioctl.h>
49 #include <fcntl.h>
50 
51 #include <cstdlib> //this needs to go before mediactl/mediactl.h because it uses size_t without including it
52 extern "C" {
53 #include <mediactl/mediactl.h>
54 #include <mediactl/v4l2subdev.h>
55 }
56 
57 QT_BEGIN_NAMESPACE
58 
Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)59 Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
60 
61 static QString mediaBusFmtToString(uint code)
62 {
63     switch (code) {
64     case MEDIA_BUS_FMT_FIXED: return "FIXED";
65     case MEDIA_BUS_FMT_RGB444_1X12: return "RGB444_1X12";
66 //    case MEDIA_BUS_FMT_RGB444_2X8_PADHI_B: return "RGB444_2X8_PADHI_B";
67 //    case MEDIA_BUS_FMT_RGB444_2X8_PADHI_L: return "RGB444_2X8_PADHI_L";
68 //    case MEDIA_BUS_FMT_RGB555_2X8_PADHI_B: return "RGB555_2X8_PADHI_B";
69 //    case MEDIA_BUS_FMT_RGB555_2X8_PADHI_L: return "RGB555_2X8_PADHI_L";
70     case MEDIA_BUS_FMT_RGB565_1X16: return "RGB565_1X16";
71     case MEDIA_BUS_FMT_BGR565_2X8_BE: return "BGR565_2X8_BE";
72     case MEDIA_BUS_FMT_BGR565_2X8_LE: return "BGR565_2X8_LE";
73     case MEDIA_BUS_FMT_RGB565_2X8_BE: return "RGB565_2X8_BE";
74     case MEDIA_BUS_FMT_RGB565_2X8_LE: return "RGB565_2X8_LE";
75     case MEDIA_BUS_FMT_RGB666_1X18: return "RGB666_1X18";
76     case MEDIA_BUS_FMT_RBG888_1X24: return "RBG888_1X24";
77 //    case MEDIA_BUS_FMT_RGB666_1X24_CPADH: return "RGB666_1X24_CPADH";
78     case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: return "RGB666_1X7X3_SPWG";
79     case MEDIA_BUS_FMT_BGR888_1X24: return "BGR888_1X24";
80     case MEDIA_BUS_FMT_GBR888_1X24: return "GBR888_1X24";
81     case MEDIA_BUS_FMT_RGB888_1X24: return "RGB888_1X24";
82     case MEDIA_BUS_FMT_RGB888_2X12_BE: return "RGB888_2X12_BE";
83     case MEDIA_BUS_FMT_RGB888_2X12_LE: return "RGB888_2X12_LE";
84     case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: return "RGB888_1X7X4_SPWG";
85 //    case MEDIA_BUS_FMT_RGB888_1X7X4_JEID: return "RGB888_1X7X4_JEID";
86     case MEDIA_BUS_FMT_ARGB8888_1X32: return "ARGB8888_1X32";
87     case MEDIA_BUS_FMT_RGB888_1X32_PADHI: return "RGB888_1X32_PADHI";
88 //    case MEDIA_BUS_FMT_RGB101010_1X30: return "RGB101010_1X30";
89 //    case MEDIA_BUS_FMT_RGB121212_1X36: return "RGB121212_1X36";
90 //    case MEDIA_BUS_FMT_RGB161616_1X48: return "RGB161616_1X48";
91     case MEDIA_BUS_FMT_Y8_1X8: return "Y8_1X8";
92     case MEDIA_BUS_FMT_UV8_1X8: return "UV8_1X8";
93     case MEDIA_BUS_FMT_UYVY8_1_5X8: return "UYVY8_1_5X8";
94     case MEDIA_BUS_FMT_VYUY8_1_5X8: return "VYUY8_1_5X8";
95     case MEDIA_BUS_FMT_YUYV8_1_5X8: return "YUYV8_1_5X8";
96     case MEDIA_BUS_FMT_YVYU8_1_5X8: return "YVYU8_1_5X8";
97     case MEDIA_BUS_FMT_UYVY8_2X8: return "UYVY8_2X8";
98     case MEDIA_BUS_FMT_VYUY8_2X8: return "VYUY8_2X8";
99     case MEDIA_BUS_FMT_YUYV8_2X8: return "YUYV8_2X8";
100     case MEDIA_BUS_FMT_YVYU8_2X8: return "YVYU8_2X8";
101     case MEDIA_BUS_FMT_Y10_1X10: return "Y10_1X10";
102     case MEDIA_BUS_FMT_UYVY10_2X10: return "UYVY10_2X10";
103     case MEDIA_BUS_FMT_VYUY10_2X10: return "VYUY10_2X10";
104     case MEDIA_BUS_FMT_YUYV10_2X10: return "YUYV10_2X10";
105     case MEDIA_BUS_FMT_YVYU10_2X10: return "YVYU10_2X10";
106     case MEDIA_BUS_FMT_Y12_1X12: return "Y12_1X12";
107     case MEDIA_BUS_FMT_UYVY12_2X12: return "UYVY12_2X12";
108     case MEDIA_BUS_FMT_VYUY12_2X12: return "VYUY12_2X12";
109     case MEDIA_BUS_FMT_YUYV12_2X12: return "YUYV12_2X12";
110     case MEDIA_BUS_FMT_YVYU12_2X12: return "YVYU12_2X12";
111     case MEDIA_BUS_FMT_UYVY8_1X16: return "UYVY8_1X16";
112     case MEDIA_BUS_FMT_VYUY8_1X16: return "VYUY8_1X16";
113     case MEDIA_BUS_FMT_YUYV8_1X16: return "YUYV8_1X16";
114     case MEDIA_BUS_FMT_YVYU8_1X16: return "YVYU8_1X16";
115     case MEDIA_BUS_FMT_YDYUYDYV8_1X16: return "YDYUYDYV8_1X16";
116     case MEDIA_BUS_FMT_UYVY10_1X20: return "UYVY10_1X20";
117     case MEDIA_BUS_FMT_VYUY10_1X20: return "VYUY10_1X20";
118     case MEDIA_BUS_FMT_YUYV10_1X20: return "YUYV10_1X20";
119     case MEDIA_BUS_FMT_YVYU10_1X20: return "YVYU10_1X20";
120     case MEDIA_BUS_FMT_VUY8_1X24: return "VUY8_1X24";
121     case MEDIA_BUS_FMT_YUV8_1X24: return "YUV8_1X24";
122 //    case MEDIA_BUS_FMT_UYYVYY8_0_5X24: return "UYYVYY8_0_5X24";
123     case MEDIA_BUS_FMT_UYVY12_1X24: return "UYVY12_1X24";
124     case MEDIA_BUS_FMT_VYUY12_1X24: return "VYUY12_1X24";
125     case MEDIA_BUS_FMT_YUYV12_1X24: return "YUYV12_1X24";
126     case MEDIA_BUS_FMT_YVYU12_1X24: return "YVYU12_1X24";
127     case MEDIA_BUS_FMT_YUV10_1X30: return "YUV10_1X30";
128 //    case MEDIA_BUS_FMT_UYYVYY10_0_5X30: return "UYYVYY10_0_5X30";
129     case MEDIA_BUS_FMT_AYUV8_1X32: return "AYUV8_1X32";
130 //    case MEDIA_BUS_FMT_UYYVYY12_0_5X36: return "UYYVYY12_0_5X36";
131 //    case MEDIA_BUS_FMT_YUV12_1X36: return "YUV12_1X36";
132 //    case MEDIA_BUS_FMT_YUV16_1X48: return "YUV16_1X48";
133 //    case MEDIA_BUS_FMT_UYYVYY16_0_5X48: return "UYYVYY16_0_5X48";
134     case MEDIA_BUS_FMT_SBGGR8_1X8: return "SBGGR8_1X8";
135     case MEDIA_BUS_FMT_SGBRG8_1X8: return "SGBRG8_1X8";
136     case MEDIA_BUS_FMT_SGRBG8_1X8: return "SGRBG8_1X8";
137     case MEDIA_BUS_FMT_SRGGB8_1X8: return "SRGGB8_1X8";
138     case MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8: return "SBGGR10_ALAW8_1X8";
139     case MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8: return "SGBRG10_ALAW8_1X8";
140     case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8: return "SGRBG10_ALAW8_1X8";
141     case MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8: return "SRGGB10_ALAW8_1X8";
142     case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: return "SBGGR10_DPCM8_1X8";
143     case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: return "SGBRG10_DPCM8_1X8";
144     case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: return "SGRBG10_DPCM8_1X8";
145     case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: return "SRGGB10_DPCM8_1X8";
146 //    case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_B: return "SBGGR10_2X8_PADHI_B";
147 //    case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_L: return "SBGGR10_2X8_PADHI_L";
148 //    case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_B: return "SBGGR10_2X8_PADLO_B";
149 //    case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_L: return "SBGGR10_2X8_PADLO_L";
150     case MEDIA_BUS_FMT_SBGGR10_1X10: return "SBGGR10_1X10";
151     case MEDIA_BUS_FMT_SGBRG10_1X10: return "SGBRG10_1X10";
152     case MEDIA_BUS_FMT_SGRBG10_1X10: return "SGRBG10_1X10";
153     case MEDIA_BUS_FMT_SRGGB10_1X10: return "SRGGB10_1X10";
154     case MEDIA_BUS_FMT_SBGGR12_1X12: return "SBGGR12_1X12";
155     case MEDIA_BUS_FMT_SGBRG12_1X12: return "SGBRG12_1X12";
156     case MEDIA_BUS_FMT_SGRBG12_1X12: return "SGRBG12_1X12";
157     case MEDIA_BUS_FMT_SRGGB12_1X12: return "SRGGB12_1X12";
158     case MEDIA_BUS_FMT_SBGGR14_1X14: return "SBGGR14_1X14";
159     case MEDIA_BUS_FMT_SGBRG14_1X14: return "SGBRG14_1X14";
160     case MEDIA_BUS_FMT_SGRBG14_1X14: return "SGRBG14_1X14";
161     case MEDIA_BUS_FMT_SRGGB14_1X14: return "SRGGB14_1X14";
162     case MEDIA_BUS_FMT_SBGGR16_1X16: return "SBGGR16_1X16";
163     case MEDIA_BUS_FMT_SGBRG16_1X16: return "SGBRG16_1X16";
164     case MEDIA_BUS_FMT_SGRBG16_1X16: return "SGRBG16_1X16";
165     case MEDIA_BUS_FMT_SRGGB16_1X16: return "SRGGB16_1X16";
166     case MEDIA_BUS_FMT_JPEG_1X8: return "JPEG_1X8";
167     case MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8: return "S5C_UYVY_JPEG_1X8";
168     case MEDIA_BUS_FMT_AHSV8888_1X32: return "AHSV8888_1X32";
169     default: return QString(code);
170     }
171 }
172 
operator <<(QDebug debug,const struct v4l2_mbus_framefmt & format)173 static QDebug operator<<(QDebug debug, const struct v4l2_mbus_framefmt &format)
174 {
175     QDebugStateSaver saver(debug);
176     debug.nospace() << "v4l2_mbus_framefmt("
177                     << "code: " << mediaBusFmtToString(format.code) << ", "
178                     << "size: " << format.width << "x" << format.height << ")";
179     return debug;
180 }
181 
operator <<(QDebug debug,const struct v4l2_pix_format_mplane & format)182 static QDebug operator<<(QDebug debug, const struct v4l2_pix_format_mplane &format)
183 {
184     QDebugStateSaver saver(debug);
185     debug.nospace() << "v4l2_pix_format_mplane("
186                     << "pixel format: " << q_fourccToString(format.pixelformat) << ", "
187                     << "size: " << format.width << "x" << format.height << ", "
188                     << "planes: " << format.num_planes << ")";
189     return debug;
190 }
191 
QLinuxMediaDevice(const QString & devicePath)192 QLinuxMediaDevice::QLinuxMediaDevice(const QString &devicePath)
193     : m_mediaDevice(media_device_new(devicePath.toStdString().c_str()))
194 {
195     if (!m_mediaDevice)
196         qFatal("Couldn't get media device");
197 
198     if (media_device_enumerate(m_mediaDevice))
199         qFatal("Couldn't enumerate media device");
200 
201     m_info = media_get_info(m_mediaDevice);
202 
203     qCDebug(qLcEglfsKmsDebug) << "Opened linux media device:"
204                               << "\n\t Path:" << devicePath
205                               << "\n\t Model:" << model()
206                               << "\n\t Device name:" << deviceName();
207 
208     resetLinks();
209 }
210 
~QLinuxMediaDevice()211 QLinuxMediaDevice::~QLinuxMediaDevice()
212 {
213     if (m_mediaDevice)
214         media_device_unref(m_mediaDevice);
215 }
216 
model()217 QString QLinuxMediaDevice::model()
218 {
219     return QString(m_info->model);
220 }
221 
deviceName()222 QString QLinuxMediaDevice::deviceName()
223 {
224     return QString(m_info->bus_info).split(":").last();
225 }
226 
resetLinks()227 bool QLinuxMediaDevice::resetLinks()
228 {
229     if (media_reset_links(m_mediaDevice)) {
230         qWarning() << "Could not reset media controller links.";
231         return false;
232     }
233     qCDebug(qLcEglfsKmsDebug) << "Reset media links";
234     return true;
235 }
236 
parseLink(const QString & link)237 struct media_link *QLinuxMediaDevice::parseLink(const QString &link)
238 {
239     char *endp = nullptr;;
240     struct media_link *mediaLink = media_parse_link(m_mediaDevice, link.toStdString().c_str(), &endp);
241 
242     if (!mediaLink)
243         qWarning() << "Failed to parse media link:" << link;
244 
245     return mediaLink;
246 }
247 
parsePad(const QString & pad)248 struct media_pad *QLinuxMediaDevice::parsePad(const QString &pad)
249 {
250     struct media_pad *mediaPad = media_parse_pad(m_mediaDevice, pad.toStdString().c_str(), nullptr);
251 
252     if (!mediaPad)
253         qWarning() << "Failed to parse media pad:" << pad;
254 
255     return mediaPad;
256 }
257 
enableLink(struct media_link * link)258 bool QLinuxMediaDevice::enableLink(struct media_link *link)
259 {
260     if (media_setup_link(m_mediaDevice, link->source, link->sink, 1)) {
261         qWarning() << "Failed to enable media link.";
262         return false;
263     }
264     return true;
265 }
266 
disableLink(struct media_link * link)267 bool QLinuxMediaDevice::disableLink(struct media_link *link)
268 {
269     if (media_setup_link(m_mediaDevice, link->source, link->sink, 0)) {
270         qWarning() << "Failed to disable media link.";
271         return false;
272     }
273     return true;
274 }
275 
276 // Between the v4l-utils 1.10 and 1.12 releases, media_get_entity_by_name changed signature,
277 // i.e. breaking source compatibility. Unfortunately the v4l-utils version is not exposed
278 // through anything we can use through a regular ifdef so here we do a hack and create two
279 // overloads for a function based on the signature of the function pointer argument. This
280 // means that by calling safeGetEntity with media_get_entity_by_name as an argument, the
281 // compiler will pick the correct version.
282 //
283 // v4l-utils v1.12 and later
safeGetEntity(struct media_entity * (get_entity_by_name_fn)(struct media_device *,const char *),struct media_device * device,const QString & name)284 static struct media_entity *safeGetEntity(struct media_entity *(get_entity_by_name_fn)(struct media_device *, const char *),
285                                           struct media_device *device, const QString &name)
286 {
287     return get_entity_by_name_fn(device, name.toStdString().c_str());
288 }
289 // v4l-utils v1.10 and earlier
safeGetEntity(struct media_entity * (get_entity_by_name_fn)(struct media_device *,const char *,size_t),struct media_device * device,const QString & name)290 static struct media_entity *safeGetEntity(struct media_entity *(get_entity_by_name_fn)(struct media_device *, const char *, size_t),
291                                           struct media_device *device,
292                                           const QString &name)
293 {
294     return get_entity_by_name_fn(device, name.toStdString().c_str(), name.length());
295 }
296 
getEntity(const QString & name)297 struct media_entity *QLinuxMediaDevice::getEntity(const QString &name)
298 {
299     struct media_entity *entity = safeGetEntity(media_get_entity_by_name, m_mediaDevice, name);
300 
301     if (!entity)
302         qWarning() << "Failed to get media entity:" << name;
303 
304     return entity;
305 }
306 
openVideoDevice(const QString & name)307 int QLinuxMediaDevice::openVideoDevice(const QString &name)
308 {
309     struct media_entity *entity = getEntity(name);
310     const char *deviceName = media_entity_get_devname(entity);
311     int fd = open(deviceName, O_RDWR);
312     qCDebug(qLcEglfsKmsDebug) << "Opened video device:" << deviceName << "with fd" << fd;
313     return fd;
314 }
315 
CaptureSubDevice(QLinuxMediaDevice * mediaDevice,const QString & name)316 QLinuxMediaDevice::CaptureSubDevice::CaptureSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name)
317     : m_subdevFd(mediaDevice->openVideoDevice(name))
318 {
319 }
320 
setFormat(const QSize & size,uint32_t pixelFormat)321 bool QLinuxMediaDevice::CaptureSubDevice::setFormat(const QSize &size, uint32_t pixelFormat)
322 {
323     Q_ASSERT(size.isValid());
324     struct v4l2_format format;
325     memset(&format, 0, sizeof(struct v4l2_format));
326     format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
327     if (ioctl(m_subdevFd, VIDIOC_G_FMT, &format) == -1) {
328         qErrnoWarning("VIDIOC_G_FMT for capture device failed");
329         return false;
330     }
331 
332     format.fmt.pix_mp.width = static_cast<uint>(size.width());
333     format.fmt.pix_mp.height = static_cast<uint>(size.height());
334     format.fmt.pix_mp.field = V4L2_FIELD_NONE;
335     format.fmt.pix_mp.pixelformat = pixelFormat;
336     format.fmt.pix_mp.num_planes = 1;
337     format.fmt.pix_mp.flags = 0;
338     format.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
339     format.fmt.pix_mp.plane_fmt[0].sizeimage = 0;
340 
341     if (ioctl(m_subdevFd, VIDIOC_S_FMT, &format) == -1) {
342         qWarning() << "Capture device" << m_subdevFd << "VIDIOC_S_FMT with format" << format.fmt.pix_mp
343                    << "failed:" << strerror(errno);
344         return false;
345     }
346 
347     qCDebug(qLcEglfsKmsDebug) << "Capture device" << m_subdevFd << "format set:" << format.fmt.pix_mp;
348     return true;
349 }
350 
clearBuffers()351 bool QLinuxMediaDevice::CaptureSubDevice::clearBuffers()
352 {
353     struct v4l2_requestbuffers requestBuffers;
354     memset(&requestBuffers, 0, sizeof(requestBuffers));
355     requestBuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
356     requestBuffers.memory = V4L2_MEMORY_DMABUF;
357     requestBuffers.count = 0;
358     if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) {
359         qWarning("Capture device %d: VIDIOC_REQBUFS clear failed: %s", m_subdevFd, strerror(errno));
360         return false;
361     }
362     qCDebug(qLcEglfsKmsDebug, "Capture device %d: Deallocced buffers with REQBUF, now has %d buffers", m_subdevFd, requestBuffers.count);
363     Q_ASSERT(requestBuffers.count == 0);
364     return true;
365 }
366 
requestBuffer()367 bool QLinuxMediaDevice::CaptureSubDevice::requestBuffer()
368 {
369     struct v4l2_requestbuffers requestBuffers;
370     memset(&requestBuffers, 0, sizeof(requestBuffers));
371     requestBuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
372     requestBuffers.memory = V4L2_MEMORY_DMABUF;
373     requestBuffers.count = 1;
374     if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) {
375         if (errno == EINVAL)
376             qWarning("Capture device %d: Multi-planar capture or dma buffers not supported", m_subdevFd);
377         qWarning("Capture device %d: VIDIOC_REQBUFS failed: %s", m_subdevFd, strerror(errno));
378         return false;
379     }
380     Q_ASSERT(requestBuffers.count == 1);
381     return true;
382 }
383 
queueBuffer(int dmabufFd,const QSize & bufferSize)384 bool QLinuxMediaDevice::CaptureSubDevice::queueBuffer(int dmabufFd, const QSize &bufferSize)
385 {
386     const uint numPlanes = 1;
387     struct v4l2_buffer buffer;
388     memset(&buffer, 0, sizeof(buffer));
389     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
390     buffer.memory = V4L2_MEMORY_DMABUF;
391     buffer.index = 0;
392 
393     struct v4l2_plane planes[VIDEO_MAX_PLANES];
394     buffer.m.planes = planes;
395     buffer.length = numPlanes;
396     memset(planes, 0, sizeof(planes));
397     for (uint i = 0; i < numPlanes; i++) {
398         buffer.m.planes[i].m.fd = dmabufFd;
399         buffer.m.planes[i].length = static_cast<uint>(bufferSize.width() * bufferSize.height() * 4); //TODO: don't harcode bpp
400         buffer.m.planes[i].bytesused = static_cast<uint>(bufferSize.width() * bufferSize.height() * 4); //TODO: don't harcode bpp
401     }
402 
403     if (ioctl(m_subdevFd, VIDIOC_QBUF, &buffer) == -1) {
404         qWarning("Capture device %d: VIDIOC_QBUF failed for dma buffer with fd %d: %s",
405                  m_subdevFd, dmabufFd, strerror(errno));
406         return false;
407     }
408 
409     return true;
410 }
411 
dequeueBuffer()412 bool QLinuxMediaDevice::CaptureSubDevice::dequeueBuffer()
413 {
414     const int numPlanes = 1;
415     struct v4l2_buffer buffer;
416     struct v4l2_plane planes[VIDEO_MAX_PLANES];
417 
418     memset(&buffer, 0, sizeof(buffer));
419     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
420     buffer.memory = V4L2_MEMORY_DMABUF;
421     buffer.index = 0;
422     buffer.m.planes = planes;
423     buffer.length = numPlanes;
424     memset(planes, 0, sizeof(planes));
425 
426     if (ioctl(m_subdevFd, VIDIOC_DQBUF, &buffer) == -1) {
427         qWarning("Capture device %d: VIDIOC_DQBUF failed: %s", m_subdevFd, strerror(errno));
428         return false;
429     }
430 
431     return true;
432 }
433 
streamOn()434 bool QLinuxMediaDevice::CaptureSubDevice::streamOn()
435 {
436     return QLinuxMediaDevice::streamOn(m_subdevFd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
437 }
438 
streamOff()439 bool QLinuxMediaDevice::CaptureSubDevice::streamOff()
440 {
441     return QLinuxMediaDevice::streamOff(m_subdevFd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
442 }
443 
OutputSubDevice(QLinuxMediaDevice * mediaDevice,const QString & name)444 QLinuxMediaDevice::OutputSubDevice::OutputSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name)
445     : m_subdevFd(mediaDevice->openVideoDevice(name))
446 {
447 }
448 
setFormat(const QSize & size,uint32_t pixelFormat,uint32_t bytesPerLine)449 bool QLinuxMediaDevice::OutputSubDevice::setFormat(const QSize &size, uint32_t pixelFormat, uint32_t bytesPerLine)
450 {
451     Q_ASSERT(size.isValid());
452     struct v4l2_format format;
453     memset(&format, 0, sizeof(struct v4l2_format));
454     format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
455     if (ioctl(m_subdevFd, VIDIOC_G_FMT, &format) == -1) {
456         qErrnoWarning("VIDIOC_G_FMT for output device failed");
457         return false;
458     }
459 
460     format.fmt.pix_mp.width = static_cast<uint>(size.width());
461     format.fmt.pix_mp.height = static_cast<uint>(size.height());
462     format.fmt.pix_mp.field = V4L2_FIELD_NONE;
463     format.fmt.pix_mp.pixelformat = pixelFormat;
464     format.fmt.pix_mp.num_planes = 1;
465     format.fmt.pix_mp.flags = 0;
466     format.fmt.pix_mp.plane_fmt[0].bytesperline = bytesPerLine;
467     format.fmt.pix_mp.plane_fmt[0].sizeimage = 0;
468 
469     if (ioctl(m_subdevFd, VIDIOC_S_FMT, &format) == -1) {
470         qWarning() << "Output device" << m_subdevFd << "VIDIOC_S_FMT with format" << format.fmt.pix_mp
471                    << "failed:" << strerror(errno);
472         return false;
473     }
474 
475     qCDebug(qLcEglfsKmsDebug) << "Output device device" << m_subdevFd << "format set:" << format.fmt.pix_mp;
476     return true;
477 }
478 
clearBuffers()479 bool QLinuxMediaDevice::OutputSubDevice::clearBuffers()
480 {
481     struct v4l2_requestbuffers requestBuffers;
482     memset(&requestBuffers, 0, sizeof(requestBuffers));
483     requestBuffers.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
484     requestBuffers.memory = V4L2_MEMORY_DMABUF;
485     requestBuffers.count = 0;
486     if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) {
487         qWarning("Output device %d: VIDIOC_REQBUFS clear failed: %s", m_subdevFd, strerror(errno));
488         return false;
489     }
490     qCDebug(qLcEglfsKmsDebug, "Output device %d: Deallocced buffers with REQBUF, now has %d buffers", m_subdevFd, requestBuffers.count);
491     Q_ASSERT(requestBuffers.count == 0);
492     return true;
493 }
494 
requestBuffer()495 bool QLinuxMediaDevice::OutputSubDevice::requestBuffer()
496 {
497     struct v4l2_requestbuffers requestBuffers;
498     memset(&requestBuffers, 0, sizeof(requestBuffers));
499     requestBuffers.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
500     requestBuffers.memory = V4L2_MEMORY_DMABUF;
501     requestBuffers.count = 1;
502     if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) {
503         if (errno == EINVAL)
504             qWarning("Output device %d: Multi-planar output or dma buffers not supported", m_subdevFd);
505         qWarning("Output device %d: VIDIOC_REQBUFS failed: %s", m_subdevFd, strerror(errno));
506         return false;
507     }
508     qCDebug(qLcEglfsKmsDebug) << "REQBUF returned" << requestBuffers.count << "buffers for output device" << m_subdevFd;
509     return true;
510 }
511 
queueBuffer(int dmabufFd,uint bytesUsed,uint length)512 bool QLinuxMediaDevice::OutputSubDevice::queueBuffer(int dmabufFd, uint bytesUsed, uint length)
513 {
514     const int numPlanes = 1;
515     struct v4l2_buffer buffer;
516     memset(&buffer, 0, sizeof(buffer));
517     buffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
518     buffer.memory = V4L2_MEMORY_DMABUF;
519     buffer.index = 0;
520     buffer.length = numPlanes;
521     buffer.bytesused = bytesUsed;
522     buffer.field = V4L2_FIELD_NONE; //TODO: what is this?
523 
524     struct v4l2_plane planes[numPlanes];
525     memset(planes, 0, sizeof(planes));
526     buffer.m.planes = planes;
527 
528     for (int i = 0; i < numPlanes; i++) {
529         buffer.m.planes[i].m.fd = dmabufFd;
530         buffer.m.planes[i].length = length;
531         buffer.m.planes[i].bytesused = bytesUsed;
532     }
533 
534     if (ioctl(m_subdevFd, VIDIOC_QBUF, &buffer) == -1) {
535         qWarning("Output device %d: VIDIOC_QBUF failed for dmabuf %d: %s", m_subdevFd, dmabufFd, strerror(errno));
536         return false;
537     }
538 
539     if (!(buffer.flags & V4L2_BUF_FLAG_QUEUED)) {
540         qWarning() << "Queued flag not set on buffer for output device";
541         return false;
542     }
543 
544     return true;
545 }
546 
streamOn()547 bool QLinuxMediaDevice::OutputSubDevice::streamOn()
548 {
549     return QLinuxMediaDevice::streamOn(m_subdevFd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
550 }
551 
streamOff()552 bool QLinuxMediaDevice::OutputSubDevice::streamOff()
553 {
554     return QLinuxMediaDevice::streamOff(m_subdevFd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
555 }
556 
openVideoDevice(media_pad * pad)557 int QLinuxMediaDevice::openVideoDevice(media_pad *pad)
558 {
559     const char *deviceName = media_entity_get_devname(pad->entity);
560     int fd = open(deviceName, O_RDWR);
561     qCDebug(qLcEglfsKmsDebug) << "Opened video device:" << deviceName << "with fd" << fd;
562     return fd;
563 }
564 
setSubdevFormat(struct media_pad * pad,const QSize & size,uint32_t mbusFormat)565 bool QLinuxMediaDevice::setSubdevFormat(struct media_pad *pad, const QSize &size, uint32_t mbusFormat)
566 {
567     Q_ASSERT(size.isValid());
568     struct v4l2_mbus_framefmt format;
569     format.width = static_cast<uint>(size.width());
570     format.height = static_cast<uint>(size.height());
571     format.code = mbusFormat;
572 
573     if (v4l2_subdev_set_format(pad->entity, &format, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE)) {
574         qWarning() << "Setting v4l2_subdev_set_format failed for format" << format;
575         return false;
576     }
577 
578     if (format.code != mbusFormat) {
579         qWarning() << "Got" << mediaBusFmtToString(format.code) << "instead of"
580                    << mediaBusFmtToString(mbusFormat) << "when setting subdevice format";
581         return false;
582     }
583 
584     qCDebug(qLcEglfsKmsDebug) << "Set format to" << format << "for entity" << pad->entity << "index" << pad->index;
585     return true;
586 }
587 
setSubdevAlpha(int subdevFd,qreal alpha)588 bool QLinuxMediaDevice::setSubdevAlpha(int subdevFd, qreal alpha)
589 {
590     struct v4l2_control control;
591     control.id = V4L2_CID_ALPHA_COMPONENT;
592     control.value = static_cast<__s32>(alpha * 0xff);
593     if (ioctl(subdevFd, VIDIOC_S_CTRL, &control) == -1) {
594         qErrnoWarning("Setting alpha (%d) failed", control.value);
595         return false;
596     }
597     return true;
598 }
599 
setSubdevSelection(struct media_pad * pad,const QRect & geometry,uint target)600 bool QLinuxMediaDevice::setSubdevSelection(struct media_pad *pad, const QRect &geometry, uint target)
601 {
602     Q_ASSERT(geometry.isValid());
603     struct v4l2_rect rect;
604     rect.left = geometry.left();
605     rect.top = geometry.top();
606     rect.width = static_cast<uint>(geometry.width());
607     rect.height = static_cast<uint>(geometry.height());
608 
609     int ret = v4l2_subdev_set_selection(pad->entity, &rect, pad->index, target, V4L2_SUBDEV_FORMAT_ACTIVE);
610     if (ret) {
611         qWarning() << "Setting subdev selection failed.";
612         return false;
613     }
614 
615     return true;
616 }
617 
setSubdevCrop(struct media_pad * pad,const QRect & geometry)618 bool QLinuxMediaDevice::setSubdevCrop(struct media_pad *pad, const QRect &geometry)
619 {
620     return setSubdevSelection(pad, geometry, V4L2_SEL_TGT_CROP);
621 }
622 
setSubdevCompose(struct media_pad * pad,const QRect & geometry)623 bool QLinuxMediaDevice::setSubdevCompose(struct media_pad *pad, const QRect &geometry)
624 {
625     return setSubdevSelection(pad, geometry, V4L2_SEL_TGT_COMPOSE);
626 }
627 
streamOn(int subDeviceFd,v4l2_buf_type bufferType)628 bool QLinuxMediaDevice::streamOn(int subDeviceFd, v4l2_buf_type bufferType)
629 {
630     if (ioctl(subDeviceFd, VIDIOC_STREAMON, &bufferType) == -1) {
631         qWarning("VIDIOC_STREAMON failed for subdevice %d: %s", subDeviceFd, strerror(errno));
632         return false;
633     }
634 
635     return true;
636 }
637 
streamOff(int subDeviceFd,v4l2_buf_type bufferType)638 bool QLinuxMediaDevice::streamOff(int subDeviceFd, v4l2_buf_type bufferType)
639 {
640     if (ioctl(subDeviceFd, VIDIOC_STREAMOFF, &bufferType) == -1) {
641         qWarning("VIDIOC_STREAMOFF failed for subdevice %d: %s", subDeviceFd, strerror(errno));
642         return false;
643     }
644 
645     return true;
646 }
647 
648 QT_END_NAMESPACE
649