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
qlibcamera_stream.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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
7
8#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
9#include <QtMultimedia/private/qvideoframe_p.h>
10
11#include <QtCore/qmetaobject.h>
12#include <QtCore/private/qsystemerror_p.h>
13
14#include <libcamera/base/span.h>
15#include <libcamera/camera.h>
16#include <libcamera/framebuffer.h>
17#include <libcamera/request.h>
18
19#include <sys/mman.h>
20#include <unistd.h>
21
22#include <cerrno>
23#include <chrono>
24#include <cstring>
25
26QT_BEGIN_NAMESPACE
27
28using namespace Qt::StringLiterals;
29
30namespace QLibCamera {
31
32using namespace libcamera;
33
34namespace {
35
36q23::expected<QVideoFrame, QString> createVideoFrameFromLibcameraBuffer(const FrameBuffer &buffer,
37 QVideoFrameFormat format,
38 int bytesPerLine)
39{
40 Span<const FrameBuffer::Plane> planes = buffer.planes();
41 if (planes.empty())
42 return q23::unexpected(u"Captured frame buffer has no planes"_s);
43
44 // All libcamera planes are commonly packed into a single fd with per-plane offsets (see
45 // FrameBuffer::Plane::offset); map that one fd once and copy the whole region.
46 const int fd = planes[0].fd.get();
47 size_t totalLength = 0;
48 for (const FrameBuffer::Plane &plane : planes)
49 totalLength = std::max<size_t>(totalLength, plane.offset + plane.length);
50
51 void *mapped = ::mmap(nullptr, totalLength, PROT_READ, MAP_SHARED, fd, 0);
52 if (mapped == MAP_FAILED) {
53 return q23::unexpected(u"mmap fallback failed for captured frame: %1"_s.arg(
54 QString::fromLocal8Bit(strerror(errno))));
55 }
56
57 QByteArray data(static_cast<const char *>(mapped), qsizetype(totalLength));
58 ::munmap(mapped, totalLength);
59
60 auto videoBuffer = std::make_unique<QMemoryVideoBuffer>(std::move(data), bytesPerLine);
61 return QVideoFramePrivate::createFrame(std::move(videoBuffer), std::move(format));
62}
63
64} // namespace
65
66QLibCameraCameraStream::QLibCameraCameraStream(std::shared_ptr<Camera> libcameraCamera,
67 Stream *stream, int bytesPerLine,
68 QVideoFrameFormat frameFormat, double maxFrameRate)
69 : m_libcameraCamera(std::move(libcameraCamera)),
70 m_stream(stream),
71 m_bytesPerLine(bytesPerLine),
72 m_frameFormat(std::move(frameFormat)),
73 m_maxFrameRate(maxFrameRate)
74{
75}
76
78{
79 // Runs on libcamera's CameraManager thread
80 using namespace std::chrono;
81
82 switch (request->status()) {
83 case Request::RequestComplete:
84 break;
85 case Request::RequestCancelled:
86 return; // e.g. during stop()
87 case Request::RequestPending:
88 qCWarning(lcLibCamera) << "Completed request is still pending";
89 return;
90 }
91
92 FrameBuffer *buffer = request->findBuffer(m_stream);
93 if (!buffer) {
94 qCWarning(lcLibCamera) << "Completed request has no buffer for our stream";
95 return;
96 }
97
98 const auto timestamp = nanoseconds(int64_t(buffer->metadata().timestamp));
99 if (!m_firstFrameTimestamp)
100 m_firstFrameTimestamp = timestamp;
101
102 q23::expected<QVideoFrame, QString> frame =
103 createVideoFrameFromLibcameraBuffer(*buffer, m_frameFormat, m_bytesPerLine);
104
105 // we have created a deep copy, so recycle the buffer now
106 request->reuse(Request::ReuseFlag::ReuseBuffers);
107 withPendingControls([&](ControlList &pendingControls) {
108 request->controls().merge(pendingControls, ControlList::MergePolicy::OverwriteExisting);
109 });
110 const int queueResult = m_libcameraCamera->queueRequest(request);
111 switch (queueResult) {
112 case 0:
113 case -EACCES: // the camera is stopping
114 break;
115 case -ENODEV: // the camera has been disconnected
116 qCDebug(lcLibCamera) << "Camera disconnected, not requeueing request";
117 break;
118 default:
119 qCWarning(lcLibCamera) << "Failed to requeue request:"
120 << QSystemError::stdString(-queueResult);
121 break;
122 }
123
124 if (!frame) {
125 qCWarning(lcLibCamera) << "Failed to build video frame:" << frame.error();
126 return;
127 }
128
129 const auto elapsed = duration_cast<microseconds>(timestamp - *m_firstFrameTimestamp);
130 frame->setStartTime(elapsed.count());
131 if (m_maxFrameRate > 0) {
132 const auto frameDuration =
133 duration_cast<microseconds>(duration<double>(1.0 / m_maxFrameRate));
134 frame->setEndTime((elapsed + frameDuration).count());
135 }
136
137 QMetaObject::invokeMethod(this, [this, frame = std::move(*frame)] {
138 // emit on app thread: destructor will cancel in-flight events
139 Q_EMIT newVideoFrame(frame);
140 }, Qt::QueuedConnection);
141}
142
143} // namespace QLibCamera
144
145QT_END_NAMESPACE
146
147#include "moc_qlibcamera_stream_p.cpp"
void handleRequestCompleted(libcamera::Request *)
QLibCameraCameraStream(std::shared_ptr< libcamera::Camera >, libcamera::Stream *, int bytesPerLine, QVideoFrameFormat, double maxFrameRate)
#define MAP_FAILED