Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qv4l2memorytransfer.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
7#include <QtMultimedia/private/qv4l2camera_p.h>
8#include <QtMultimedia/private/qv4l2filedescriptor_p.h>
9#include <QtMultimedia/private/qv4l2formats_p.h>
10#include <QtCore/qbytearray.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qvarlengtharray.h>
13
14#include <sys/mman.h>
15
16#include <optional>
17#include <vector>
18
19#if QT_CONFIG(linux_dmabuf)
20# include <QtMultimedia/private/qdmabufallocator_p.h>
21# include <QtMultimedia/private/qmultimedia_dmabuf_support_p.h>
22#endif
23
24QT_BEGIN_NAMESPACE
25
26namespace QtV4L2 {
27
28Q_STATIC_LOGGING_CATEGORY(qLcV4L2MemoryTransfer, "qt.multimedia.v4l2.memorytransfer");
29
30namespace {
31
32v4l2_buffer makeV4l2Buffer(v4l2_memory memoryType, quint32 index = 0)
33{
34 v4l2_buffer buf = {};
35 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
36 buf.memory = memoryType;
37 buf.index = index;
38 return buf;
39}
40
41class UserPtrMemoryTransfer : public QV4L2MemoryTransfer
42{
43public:
44 static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize,
45 int bytesPerLine)
46 {
47 if (auto requestResult = fileDescriptor->requestBuffers(V4L2_MEMORY_USERPTR, 2)) {
48 std::unique_ptr<UserPtrMemoryTransfer> result(new UserPtrMemoryTransfer{
49 std::move(fileDescriptor),
50 quint32(*requestResult),
51 imageSize,
52 bytesPerLine,
53 });
54
55 return result->enqueueBuffers() ? std::move(result) : nullptr;
56 } else {
57 qCDebug(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_USERPTR buffers";
58 return {};
59 }
60 }
61
62 ~UserPtrMemoryTransfer() override { fileDescriptor().releaseAllBuffers(V4L2_MEMORY_USERPTR); }
63
64 std::optional<Frame> dequeueFrame() override
65 {
66 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_USERPTR);
67 if (!ioctl(VIDIOC_DQBUF, &v4l2Buffer))
68 return {};
69
70 Q_ASSERT(v4l2Buffer.index < m_byteArrays.size());
71
72 auto &byteArray = m_byteArrays[v4l2Buffer.index];
73
74 Q_ASSERT(!byteArray.isEmpty());
75 Q_ASSERT(qsizetype(v4l2Buffer.bytesused) <= byteArray.size());
76
77 // truncate jpeg
78 byteArray.resize(v4l2Buffer.bytesused);
79 Frame frame{
80 v4l2Buffer.timestamp,
81 std::make_unique<QMemoryVideoBuffer>(std::move(byteArray), m_bytesPerLine),
82 };
83
84 if (!enqueueBuffer(v4l2Buffer.index))
85 qCWarning(qLcV4L2MemoryTransfer) << "Cannot add buffer";
86
87 return frame;
88 }
89
90 quint32 buffersCount() const override { return static_cast<quint32>(m_byteArrays.size()); }
91
92private:
93 UserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 buffersCount,
94 quint32 imageSize, int bytesPerLine)
95 : QV4L2MemoryTransfer(std::move(fileDescriptor)),
96 m_imageSize(imageSize),
97 m_bytesPerLine(bytesPerLine),
98 m_byteArrays(buffersCount)
99 {
100 }
101
102 bool enqueueBuffer(quint32 index) override
103 {
104 Q_ASSERT(index < m_byteArrays.size());
105 Q_ASSERT(m_byteArrays[index].isEmpty());
106
107 auto buf = makeV4l2Buffer(V4L2_MEMORY_USERPTR, index);
108 static_assert(sizeof(decltype(buf.m.userptr)) == sizeof(size_t), "Not compatible sizes");
109
110 m_byteArrays[index] = QByteArray(static_cast<int>(m_imageSize), Qt::Uninitialized);
111
112 buf.m.userptr = (decltype(buf.m.userptr))m_byteArrays[index].data();
113 buf.length = m_byteArrays[index].size();
114
115 if (!ioctl(VIDIOC_QBUF, &buf)) {
116 qWarning() << "Couldn't add V4L2 buffer" << errno << qt_error_string(errno) << index;
117 return false;
118 }
119
120 return true;
121 }
122
123 const quint32 m_imageSize;
124 const int m_bytesPerLine;
125 std::vector<QByteArray> m_byteArrays;
126};
127
128class MMapMemoryTransfer : public QV4L2MemoryTransfer
129{
130public:
131 struct MemorySpan
132 {
133 void *data = nullptr;
134 size_t size = 0;
135 bool inQueue = false;
136 };
137
138 static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, int bytesPerLine)
139 {
140 auto requestResult = fileDescriptor->requestBuffers(V4L2_MEMORY_MMAP, 2);
141 if (!requestResult) {
142 qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_MMAP buffers";
143 return {};
144 }
145
146 std::unique_ptr<MMapMemoryTransfer> result(
147 new MMapMemoryTransfer(std::move(fileDescriptor), bytesPerLine));
148
149 return result->init(*requestResult) ? std::move(result) : nullptr;
150 }
151
152 bool init(quint32 buffersCount)
153 {
154 for (quint32 index = 0; index < buffersCount; ++index) {
155 auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
156
157 if (!ioctl(VIDIOC_QUERYBUF, &buf)) {
158 qWarning() << "Can't map buffer" << index;
159 return false;
160 }
161
162 auto mappedData = mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
163 fileDescriptor().get(), buf.m.offset);
164
165 if (mappedData == MAP_FAILED) {
166 qWarning() << "mmap failed" << index << buf.length << buf.m.offset;
167 return false;
168 }
169
170 m_spans.push_back(MemorySpan{ mappedData, buf.length, false });
171 }
172
173 m_spans.shrink_to_fit();
174
175 return enqueueBuffers();
176 }
177
178 ~MMapMemoryTransfer() override
179 {
180 for (const auto &span : m_spans)
181 munmap(span.data, span.size);
182 fileDescriptor().releaseAllBuffers(V4L2_MEMORY_MMAP);
183 }
184
185 std::optional<Frame> dequeueFrame() override
186 {
187 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_MMAP);
188 if (!ioctl(VIDIOC_DQBUF, &v4l2Buffer))
189 return {};
190
191 const auto index = v4l2Buffer.index;
192
193 Q_ASSERT(index < m_spans.size());
194
195 auto &span = m_spans[index];
196
197 Q_ASSERT(span.inQueue);
198 span.inQueue = false;
199
200 Q_ASSERT(v4l2Buffer.bytesused <= span.size);
201
202 // truncate jpeg
203 QByteArray byteArray(reinterpret_cast<const char *>(span.data), v4l2Buffer.bytesused);
204
205 Frame frame{
206 v4l2Buffer.timestamp,
207 std::make_unique<QMemoryVideoBuffer>(std::move(byteArray), m_bytesPerLine),
208 };
209
210 if (!enqueueBuffer(index))
211 qCWarning(qLcV4L2MemoryTransfer) << "Cannot add buffer";
212
213 return frame;
214 }
215
216 quint32 buffersCount() const override { return static_cast<quint32>(m_spans.size()); }
217
218private:
219 MMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, int bytesPerLine)
220 : QV4L2MemoryTransfer(std::move(fileDescriptor)), m_bytesPerLine(bytesPerLine)
221 {
222 }
223
224 bool enqueueBuffer(quint32 index) override
225 {
226 Q_ASSERT(index < m_spans.size());
227 Q_ASSERT(!m_spans[index].inQueue);
228
229 auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
230 if (!ioctl(VIDIOC_QBUF, &buf))
231 return false;
232
233 m_spans[index].inQueue = true;
234 return true;
235 }
236
237 const int m_bytesPerLine;
238 std::vector<MemorySpan> m_spans;
239};
240
241#if QT_CONFIG(linux_dmabuf)
242
243constexpr quint32 kDmaBufBuffersCount = 4;
244
245class DmaBufMemoryTransfer : public QV4L2MemoryTransfer
246{
247public:
248 using QDmaBuf = QtMultimediaPrivate::QDmaBuf;
249 using QDmaBufAllocator = QtMultimediaPrivate::QDmaBufAllocator;
250 using QDmaBufVector = QVarLengthArray<std::shared_ptr<QDmaBuf>, kDmaBufBuffersCount>;
251
252 static QV4L2MemoryTransferUPtr create(const QV4L2FileDescriptorPtr &fileDescriptor,
253 quint32 imageSize, int bytesPerLine)
254 {
255 auto allocator = QDmaBufAllocator::create();
256 if (!allocator) {
257 qCDebug(qLcV4L2MemoryTransfer)
258 << "Cannot create DMA-BUF allocator:" << allocator.error();
259 return {};
260 }
261
262 auto requestResult =
263 fileDescriptor->requestBuffers(V4L2_MEMORY_DMABUF, kDmaBufBuffersCount);
264 if (!requestResult) {
265 qCDebug(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_DMABUF buffers";
266 return {};
267 }
268
269 QDmaBufVector buffers;
270
271 for (quint32 index = 0; index < quint32(*requestResult); ++index) {
272 auto buf = allocator->allocate(imageSize);
273 if (!buf) {
274 qCDebug(qLcV4L2MemoryTransfer) << "Cannot allocate DMA-BUF:" << buf.error();
275 fileDescriptor->requestBuffers(V4L2_MEMORY_DMABUF, 0);
276 return {};
277 }
278 buffers.push_back(std::move(*buf));
279 }
280
281 std::unique_ptr<DmaBufMemoryTransfer> result(
282 new DmaBufMemoryTransfer(fileDescriptor, std::move(buffers), bytesPerLine));
283
284 if (result->enqueueBuffers())
285 return result;
286
287 qCDebug(qLcV4L2MemoryTransfer) << "Cannot queue V4L2_MEMORY_DMABUF buffers";
288 fileDescriptor->requestBuffers(V4L2_MEMORY_DMABUF, 0);
289 return {};
290 }
291
292 ~DmaBufMemoryTransfer() override { fileDescriptor().releaseAllBuffers(V4L2_MEMORY_DMABUF); }
293
294 std::optional<Frame> dequeueFrame() override
295 {
296 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_DMABUF);
297 if (!ioctl(VIDIOC_DQBUF, &v4l2Buffer))
298 return {};
299
300 const quint32 index = v4l2Buffer.index;
301 Q_ASSERT(index < m_buffers.size());
302 const QDmaBuf &buf = *m_buffers[index];
303
304 // Copy out before re-queueing, so the driver cannot overwrite the frame while it is read.
305 auto data = QtMultimediaPrivate::copyDmaBufData(buf, 0, v4l2Buffer.bytesused);
306
307 if (!enqueueBuffer(index))
308 qCWarning(qLcV4L2MemoryTransfer) << "Cannot add buffer";
309
310 if (!data)
311 return {};
312
313 return Frame{
314 v4l2Buffer.timestamp,
315 std::make_unique<QMemoryVideoBuffer>(std::move(*data), m_bytesPerLine),
316 };
317 }
318
319 quint32 buffersCount() const override { return quint32(m_buffers.size()); }
320
321private:
322 DmaBufMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, QDmaBufVector buffers,
323 int bytesPerLine)
324 : QV4L2MemoryTransfer(std::move(fileDescriptor)),
325 m_bytesPerLine(bytesPerLine),
326 m_buffers(std::move(buffers))
327 {
328 }
329
330 bool enqueueBuffer(quint32 index) override
331 {
332 Q_ASSERT(index < m_buffers.size());
333
334 const auto &buf = *m_buffers[index];
335
336 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_DMABUF, index);
337 v4l2Buffer.m.fd = buf.fd();
338 v4l2Buffer.length = quint32(buf.size());
339
340 if (!ioctl(VIDIOC_QBUF, &v4l2Buffer)) {
341 qWarning() << "Couldn't add V4L2 buffer" << errno << qt_error_string(errno) << index;
342 return false;
343 }
344
345 return true;
346 }
347
348 const int m_bytesPerLine;
349 QDmaBufVector m_buffers;
350};
351
352#endif
353
354} // namespace
355
358{
359 Q_ASSERT(m_fileDescriptor);
360 Q_ASSERT(!m_fileDescriptor->streamStarted());
361}
362
364{
365 Q_ASSERT(!m_fileDescriptor->streamStarted()); // to avoid possible corruptions
366}
367
369{
370 for (quint32 i = 0; i < buffersCount(); ++i)
371 if (!enqueueBuffer(i))
372 return false;
373
374 return true;
375}
376
377q23::expected<int, std::error_code> QV4L2MemoryTransfer::ioctl(int request, void *arg)
378{
379 return m_fileDescriptor->call(request, arg);
380}
381
383 const v4l2_pix_format &format)
384{
385 return UserPtrMemoryTransfer::create(std::move(fileDescriptor), format.sizeimage,
386 int(format.bytesperline));
387}
388
390 const v4l2_pix_format &format)
391{
392 return MMapMemoryTransfer::create(std::move(fileDescriptor), int(format.bytesperline));
393}
394
395#if QT_CONFIG(linux_dmabuf)
397 const v4l2_pix_format &format)
398{
400}
401#endif
402
403} // namespace QtV4L2
404
405QT_END_NAMESPACE
QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
const QV4L2FileDescriptor & fileDescriptor() const
q23::expected< int, std::error_code > ioctl(int request, void *arg)
QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, const v4l2_pix_format &format)
Q_STATIC_LOGGING_CATEGORY(qLcV4L2Camera, "qt.multimedia.v4l2.camera")
QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, const v4l2_pix_format &format)
std::shared_ptr< QV4L2FileDescriptor > QV4L2FileDescriptorPtr
#define MAP_FAILED