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
6
7#include <qloggingcategory.h>
8#include <qdebug.h>
9#include <sys/mman.h>
10#include <optional>
11
12QT_BEGIN_NAMESPACE
13
14Q_STATIC_LOGGING_CATEGORY(qLcV4L2MemoryTransfer, "qt.multimedia.ffmpeg.v4l2camera.memorytransfer");
15
16namespace {
17
18v4l2_buffer makeV4l2Buffer(quint32 memoryType, quint32 index = 0)
19{
20 v4l2_buffer buf = {};
21 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
22 buf.memory = memoryType;
23 buf.index = index;
24 return buf;
25}
26
27class UserPtrMemoryTransfer : public QV4L2MemoryTransfer
28{
29public:
30 static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize)
31 {
32 quint32 buffersCount = 2;
33 if (!fileDescriptor->requestBuffers(V4L2_MEMORY_USERPTR, buffersCount)) {
34 qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_USERPTR buffers";
35 return {};
36 }
37
38 std::unique_ptr<UserPtrMemoryTransfer> result(
39 new UserPtrMemoryTransfer(std::move(fileDescriptor), buffersCount, imageSize));
40
41 return result->enqueueBuffers() ? std::move(result) : nullptr;
42 }
43
44 std::optional<Buffer> dequeueBuffer() override
45 {
46 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_USERPTR);
47 if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
48 return {};
49
50 Q_ASSERT(v4l2Buffer.index < m_byteArrays.size());
51
52 auto &byteArray = m_byteArrays[v4l2Buffer.index];
53
54 Q_ASSERT(!byteArray.isEmpty());
55 Q_ASSERT(qsizetype(v4l2Buffer.bytesused) <= byteArray.size());
56
57 // truncate jpeg
58 byteArray.resize(v4l2Buffer.bytesused);
59 return Buffer{ v4l2Buffer, std::move(byteArray) };
60 }
61
62 bool enqueueBuffer(quint32 index) override
63 {
64 Q_ASSERT(index < m_byteArrays.size());
65 Q_ASSERT(m_byteArrays[index].isEmpty());
66
67 auto buf = makeV4l2Buffer(V4L2_MEMORY_USERPTR, index);
68 static_assert(sizeof(decltype(buf.m.userptr)) == sizeof(size_t), "Not compatible sizes");
69
70 m_byteArrays[index] = QByteArray(static_cast<int>(m_imageSize), Qt::Uninitialized);
71
72 buf.m.userptr = (decltype(buf.m.userptr))m_byteArrays[index].data();
73 buf.length = m_byteArrays[index].size();
74
75 if (!fileDescriptor().call(VIDIOC_QBUF, &buf)) {
76 qWarning() << "Couldn't add V4L2 buffer" << errno << strerror(errno) << index;
77 return false;
78 }
79
80 return true;
81 }
82
83 quint32 buffersCount() const override { return static_cast<quint32>(m_byteArrays.size()); }
84
85private:
86 UserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 buffersCount,
87 quint32 imageSize)
88 : QV4L2MemoryTransfer(std::move(fileDescriptor)),
89 m_imageSize(imageSize),
90 m_byteArrays(buffersCount)
91 {
92 }
93
94private:
95 quint32 m_imageSize;
96 std::vector<QByteArray> m_byteArrays;
97};
98
99class MMapMemoryTransfer : public QV4L2MemoryTransfer
100{
101public:
102 struct MemorySpan
103 {
104 void *data = nullptr;
105 size_t size = 0;
106 bool inQueue = false;
107 };
108
109 static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor)
110 {
111 quint32 buffersCount = 2;
112 if (!fileDescriptor->requestBuffers(V4L2_MEMORY_MMAP, buffersCount)) {
113 qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_MMAP buffers";
114 return {};
115 }
116
117 std::unique_ptr<MMapMemoryTransfer> result(
118 new MMapMemoryTransfer(std::move(fileDescriptor)));
119
120 return result->init(buffersCount) ? std::move(result) : nullptr;
121 }
122
123 bool init(quint32 buffersCount)
124 {
125 for (quint32 index = 0; index < buffersCount; ++index) {
126 auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
127
128 if (!fileDescriptor().call(VIDIOC_QUERYBUF, &buf)) {
129 qWarning() << "Can't map buffer" << index;
130 return false;
131 }
132
133 auto mappedData = mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
134 fileDescriptor().get(), buf.m.offset);
135
136 if (mappedData == MAP_FAILED) {
137 qWarning() << "mmap failed" << index << buf.length << buf.m.offset;
138 return false;
139 }
140
141 m_spans.push_back(MemorySpan{ mappedData, buf.length, false });
142 }
143
144 m_spans.shrink_to_fit();
145
146 return enqueueBuffers();
147 }
148
149 ~MMapMemoryTransfer() override
150 {
151 for (const auto &span : m_spans)
152 munmap(span.data, span.size);
153 }
154
155 std::optional<Buffer> dequeueBuffer() override
156 {
157 auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_MMAP);
158 if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
159 return {};
160
161 const auto index = v4l2Buffer.index;
162
163 Q_ASSERT(index < m_spans.size());
164
165 auto &span = m_spans[index];
166
167 Q_ASSERT(span.inQueue);
168 span.inQueue = false;
169
170 Q_ASSERT(v4l2Buffer.bytesused <= span.size);
171
172 // truncate jpeg
173 QByteArray byteArray(reinterpret_cast<const char *>(span.data), v4l2Buffer.bytesused);
174
175 return Buffer{ v4l2Buffer, std::move(byteArray) };
176 }
177
178 bool enqueueBuffer(quint32 index) override
179 {
180 Q_ASSERT(index < m_spans.size());
181 Q_ASSERT(!m_spans[index].inQueue);
182
183 auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
184 if (!fileDescriptor().call(VIDIOC_QBUF, &buf))
185 return false;
186
187 m_spans[index].inQueue = true;
188 return true;
189 }
190
191 quint32 buffersCount() const override { return static_cast<quint32>(m_spans.size()); }
192
193private:
194 using QV4L2MemoryTransfer::QV4L2MemoryTransfer;
195
196private:
197 std::vector<MemorySpan> m_spans;
198};
199} // namespace
200
203{
204 Q_ASSERT(m_fileDescriptor);
205 Q_ASSERT(!m_fileDescriptor->streamStarted());
206}
207
209{
210 Q_ASSERT(!m_fileDescriptor->streamStarted()); // to avoid possible corruptions
211}
212
214{
215 for (quint32 i = 0; i < buffersCount(); ++i)
216 if (!enqueueBuffer(i))
217 return false;
218
219 return true;
220}
221
223 quint32 imageSize)
224{
225 return UserPtrMemoryTransfer::create(std::move(fileDescriptor), imageSize);
226}
227
229{
230 return MMapMemoryTransfer::create(std::move(fileDescriptor));
231}
232
233QT_END_NAMESPACE
const QV4L2FileDescriptor & fileDescriptor() const
QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
#define MAP_FAILED
QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize)
std::shared_ptr< QV4L2FileDescriptor > QV4L2FileDescriptorPtr
QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)