Revision control
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qv4l2memorytransfer_p.h"
+ #include <QtMultimedia/private/qmemoryvideobuffer_p.h>
#include <QtMultimedia/private/qv4l2filedescriptor_p.h>
+ #include <QtCore/qbytearray.h>
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
#include <sys/mman.h>
#include <optional>
+ #include <vector>
QT_BEGIN_NAMESPACE
namespace QtV4L2 {
Q_STATIC_LOGGING_CATEGORY(qLcV4L2MemoryTransfer, "qt.multimedia.v4l2.memorytransfer");
namespace {
v4l2_buffer makeV4l2Buffer(v4l2_memory memoryType, quint32 index = 0)
{
v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = memoryType;
buf.index = index;
return buf;
}
class UserPtrMemoryTransfer : public QV4L2MemoryTransfer
{
public:
- static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize)
+ static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize,
+ int bytesPerLine)
{
if (auto requestResult = fileDescriptor->requestBuffers(V4L2_MEMORY_USERPTR, 2)) {
std::unique_ptr<UserPtrMemoryTransfer> result(new UserPtrMemoryTransfer{
std::move(fileDescriptor),
quint32(*requestResult),
imageSize,
+ bytesPerLine,
});
return result->enqueueBuffers() ? std::move(result) : nullptr;
} else {
qCDebug(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_USERPTR buffers";
return {};
}
}
- std::optional<Buffer> dequeueBuffer() override
+ std::optional<Frame> dequeueFrame() override
{
auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_USERPTR);
if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
return {};
Q_ASSERT(v4l2Buffer.index < m_byteArrays.size());
auto &byteArray = m_byteArrays[v4l2Buffer.index];
Q_ASSERT(!byteArray.isEmpty());
Q_ASSERT(qsizetype(v4l2Buffer.bytesused) <= byteArray.size());
// truncate jpeg
byteArray.resize(v4l2Buffer.bytesused);
- return Buffer{ v4l2Buffer, std::move(byteArray) };
+ Frame frame{
+ v4l2Buffer.timestamp,
+ std::make_unique<QMemoryVideoBuffer>(std::move(byteArray), m_bytesPerLine),
+ };
+
+ if (!enqueueBuffer(v4l2Buffer.index))
+ qCWarning(qLcV4L2MemoryTransfer) << "Cannot add buffer";
+
+ return frame;
+ }
+
+ quint32 buffersCount() const override { return static_cast<quint32>(m_byteArrays.size()); }
+
+ private:
+ UserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 buffersCount,
+ quint32 imageSize, int bytesPerLine)
+ : QV4L2MemoryTransfer(std::move(fileDescriptor)),
+ m_imageSize(imageSize),
+ m_bytesPerLine(bytesPerLine),
+ m_byteArrays(buffersCount)
+ {
}
bool enqueueBuffer(quint32 index) override
{
Q_ASSERT(index < m_byteArrays.size());
Q_ASSERT(m_byteArrays[index].isEmpty());
auto buf = makeV4l2Buffer(V4L2_MEMORY_USERPTR, index);
static_assert(sizeof(decltype(buf.m.userptr)) == sizeof(size_t), "Not compatible sizes");
m_byteArrays[index] = QByteArray(static_cast<int>(m_imageSize), Qt::Uninitialized);
buf.m.userptr = (decltype(buf.m.userptr))m_byteArrays[index].data();
buf.length = m_byteArrays[index].size();
if (!fileDescriptor().call(VIDIOC_QBUF, &buf)) {
qWarning() << "Couldn't add V4L2 buffer" << errno << qt_error_string(errno) << index;
return false;
}
return true;
}
- quint32 buffersCount() const override { return static_cast<quint32>(m_byteArrays.size()); }
-
- private:
- UserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 buffersCount,
- quint32 imageSize)
- : QV4L2MemoryTransfer(std::move(fileDescriptor)),
- m_imageSize(imageSize),
- m_byteArrays(buffersCount)
- {
- }
-
- private:
- quint32 m_imageSize;
+ const quint32 m_imageSize;
+ const int m_bytesPerLine;
std::vector<QByteArray> m_byteArrays;
};
class MMapMemoryTransfer : public QV4L2MemoryTransfer
{
public:
struct MemorySpan
{
void *data = nullptr;
size_t size = 0;
bool inQueue = false;
};
- static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor)
+ static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, int bytesPerLine)
{
auto requestResult = fileDescriptor->requestBuffers(V4L2_MEMORY_MMAP, 2);
if (!requestResult) {
qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_MMAP buffers";
return {};
}
std::unique_ptr<MMapMemoryTransfer> result(
- new MMapMemoryTransfer(std::move(fileDescriptor)));
+ new MMapMemoryTransfer(std::move(fileDescriptor), bytesPerLine));
return result->init(*requestResult) ? std::move(result) : nullptr;
}
bool init(quint32 buffersCount)
{
for (quint32 index = 0; index < buffersCount; ++index) {
auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
if (!fileDescriptor().call(VIDIOC_QUERYBUF, &buf)) {
qWarning() << "Can't map buffer" << index;
return false;
}
auto mappedData = mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
fileDescriptor().get(), buf.m.offset);
if (mappedData == MAP_FAILED) {
qWarning() << "mmap failed" << index << buf.length << buf.m.offset;
return false;
}
m_spans.push_back(MemorySpan{ mappedData, buf.length, false });
}
m_spans.shrink_to_fit();
return enqueueBuffers();
}
~MMapMemoryTransfer() override
{
for (const auto &span : m_spans)
munmap(span.data, span.size);
}
- std::optional<Buffer> dequeueBuffer() override
+ std::optional<Frame> dequeueFrame() override
{
auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_MMAP);
if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
return {};
const auto index = v4l2Buffer.index;
Q_ASSERT(index < m_spans.size());
auto &span = m_spans[index];
Q_ASSERT(span.inQueue);
span.inQueue = false;
Q_ASSERT(v4l2Buffer.bytesused <= span.size);
// truncate jpeg
QByteArray byteArray(reinterpret_cast<const char *>(span.data), v4l2Buffer.bytesused);
- return Buffer{ v4l2Buffer, std::move(byteArray) };
+ Frame frame{
+ v4l2Buffer.timestamp,
+ std::make_unique<QMemoryVideoBuffer>(std::move(byteArray), m_bytesPerLine),
+ };
+
+ if (!enqueueBuffer(index))
+ qCWarning(qLcV4L2MemoryTransfer) << "Cannot add buffer";
+
+ return frame;
+ }
+
+ quint32 buffersCount() const override { return static_cast<quint32>(m_spans.size()); }
+
+ private:
+ MMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, int bytesPerLine)
+ : QV4L2MemoryTransfer(std::move(fileDescriptor)), m_bytesPerLine(bytesPerLine)
+ {
}
bool enqueueBuffer(quint32 index) override
{
Q_ASSERT(index < m_spans.size());
Q_ASSERT(!m_spans[index].inQueue);
auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
if (!fileDescriptor().call(VIDIOC_QBUF, &buf))
return false;
m_spans[index].inQueue = true;
return true;
}
- quint32 buffersCount() const override { return static_cast<quint32>(m_spans.size()); }
-
- private:
- using QV4L2MemoryTransfer::QV4L2MemoryTransfer;
-
- private:
+ const int m_bytesPerLine;
std::vector<MemorySpan> m_spans;
};
} // namespace
QV4L2MemoryTransfer::QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
: m_fileDescriptor(std::move(fileDescriptor))
{
Q_ASSERT(m_fileDescriptor);
Q_ASSERT(!m_fileDescriptor->streamStarted());
}
QV4L2MemoryTransfer::~QV4L2MemoryTransfer()
{
Q_ASSERT(!m_fileDescriptor->streamStarted()); // to avoid possible corruptions
}
bool QV4L2MemoryTransfer::enqueueBuffers()
{
for (quint32 i = 0; i < buffersCount(); ++i)
if (!enqueueBuffer(i))
return false;
return true;
}
QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor,
- quint32 imageSize)
+ quint32 imageSize, int bytesPerLine)
{
- return UserPtrMemoryTransfer::create(std::move(fileDescriptor), imageSize);
+ return UserPtrMemoryTransfer::create(std::move(fileDescriptor), imageSize, bytesPerLine);
}
- QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
+ QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor,
+ int bytesPerLine)
{
- return MMapMemoryTransfer::create(std::move(fileDescriptor));
+ return MMapMemoryTransfer::create(std::move(fileDescriptor), bytesPerLine);
}
} // namespace QtV4L2
QT_END_NAMESPACE