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_camera.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
10
11#include <QtMultimedia/private/qcameradevice_p.h>
12#include <QtMultimedia/private/qmultimedia_ranges_p.h>
13#include <QtMultimedia/qcameradevice.h>
14
15#include <QtCore/qscopeguard.h>
16#include <QtCore/private/qsystemerror_p.h>
17
18#include <libcamera/camera.h>
19#include <libcamera/control_ids.h>
20#include <libcamera/controls.h>
21#include <libcamera/framebuffer.h>
22#include <libcamera/framebuffer_allocator.h>
23#include <libcamera/geometry.h>
24#include <libcamera/pixel_format.h>
25#include <libcamera/request.h>
26#include <libcamera/stream.h>
27
28#include <algorithm>
29#include <cerrno>
30#include <chrono>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36namespace QLibCamera {
37
38using namespace libcamera;
39
40namespace {
41
42auto makeCameraError(const QString &errorString)
43{
44 return q23::unexpected(std::tuple{
45 QCamera::CameraError,
46 errorString,
47 });
48}
49
50} // namespace
51
52QLibCameraCamera::QLibCameraCamera(QCamera *parent, std::shared_ptr<QLibCameraInstance> instance)
53 : QPlatformCamera(parent), m_instance(std::move(instance))
54{
55}
56
57QLibCameraCamera::~QLibCameraCamera()
58{
59 stopCapturing();
60}
61
62int QLibCameraCamera::cameraPixelFormatScore(QVideoFrameFormat::PixelFormat format,
63 QVideoFrameFormat::ColorRange /*colorRange*/) const
64{
65 switch (format) {
66 case QVideoFrameFormat::Format_NV12:
67 case QVideoFrameFormat::Format_YUV420P:
68 return 3;
69 case QVideoFrameFormat::Format_YUYV:
70 case QVideoFrameFormat::Format_UYVY:
71 return 2;
72 case QVideoFrameFormat::Format_Jpeg:
73 return 0; // compressed; needs decoding before it can be turned into a QVideoFrame
74 default:
75 return 1;
76 }
77}
78
79void QLibCameraCamera::setActive(bool active)
80{
81 if (m_active == active)
82 return;
83 if (m_cameraDevice.isNull() && active)
84 return;
85
86 if (m_cameraFormat.isNull())
87 m_cameraFormat = findBestCameraFormat(m_cameraDevice);
88
89 m_active = active;
90 if (m_active)
91 startCapturingReportingErrors();
92 else
93 stopCapturing();
94
95 Q_EMIT newVideoFrame({});
96 Q_EMIT activeChanged(active);
97}
98
99void QLibCameraCamera::setCamera(const QCameraDevice &camera)
100{
101 if (m_cameraDevice == camera)
102 return;
103
104 const bool wasActive = m_active;
105 stopCapturing();
106
107 m_cameraDevice = camera;
108 m_cameraFormat = {};
109 m_camera.reset();
110
111 if (!camera.isNull() && m_instance)
112 m_camera = m_instance->get(QString::fromUtf8(camera.id()));
113
114 if (m_camera)
115 supportedFeaturesChanged(QLibCameraControls::detectSupportedFeatures(m_camera->controls()));
116 else
117 supportedFeaturesChanged({});
118
119 if (wasActive) {
120 m_active = false; // startCapturing() below re-flips this
121 setActive(true);
122 }
123}
124
125bool QLibCameraCamera::setCameraFormat(const QCameraFormat &format)
126{
127 if (!format.isNull() && !m_cameraDevice.videoFormats().contains(format))
128 return false;
129
130 QCameraFormat newFormat = format.isNull() ? findBestCameraFormat(m_cameraDevice) : format;
131 if (newFormat == m_cameraFormat)
132 return true;
133
134 m_cameraFormat = newFormat;
135
136 if (m_active) {
137 stopCapturing();
138 startCapturingReportingErrors();
139 }
140 return true;
141}
142
143QLibCameraCamera::StartCapturingResult QLibCameraCamera::startCapturing()
144{
145 using namespace QLibCameraFormatSupport;
146 namespace ranges = QtMultimediaPrivate::ranges;
147
148 if (!m_camera || m_cameraFormat.isNull())
149 return {};
150
151 auto abortGuard = qScopeGuard([this] {
152 stopCapturing();
153 });
154
155 const int acquireResult = m_camera->acquire();
156 switch (acquireResult) {
157 case 0:
158 break;
159 case -EBUSY:
160 return makeCameraError(u"Camera is in use"_s);
161 case -ENODEV:
162 return makeCameraError(u"Camera has been disconnected"_s);
163 default:
164 return makeCameraError(u"Failed to acquire camera: "_s
165 + QSystemError::stdString(-acquireResult));
166 }
167
168 m_streamState = StreamState::Acquired;
169
170 m_config = m_camera->generateConfiguration({ StreamRole::VideoRecording });
171 if (!m_config || m_config->empty())
172 return makeCameraError(u"Camera does not support video capture"_s);
173
174 StreamConfiguration &streamConfig = m_config->at(0);
175 // Prefer the currently-requested QCameraFormat's pixel format if libcamera can still produce
176 // it for this size; otherwise fall through to whatever generateConfiguration() proposed.
177 const std::vector<PixelFormat> pixelFormats = streamConfig.formats().pixelformats();
178 const auto pixelFormatIt = ranges::find_if(pixelFormats, [&](const PixelFormat &pf) {
179 return pixelFormatFromLibcamera(pf) == m_cameraFormat.pixelFormat();
180 });
181 if (pixelFormatIt != pixelFormats.end())
182 streamConfig.pixelFormat = *pixelFormatIt;
183
184 streamConfig.size = toSize(m_cameraFormat.resolution());
185 streamConfig.bufferCount = 4;
186
187 switch (m_config->validate()) {
188 case CameraConfiguration::Valid:
189 case CameraConfiguration::Adjusted:
190 break;
191 case CameraConfiguration::Invalid:
192 return makeCameraError(u"Invalid camera configuration"_s);
193 }
194
195 const int configureResult = m_camera->configure(m_config.get());
196 switch (configureResult) {
197 case 0:
198 break;
199 case -ENODEV:
200 return makeCameraError(u"Camera has been disconnected"_s);
201 case -EACCES:
202 return makeCameraError(u"Camera is not in a state where it can be configured"_s);
203 case -EINVAL:
204 return makeCameraError(u"Invalid camera configuration"_s);
205 default:
206 return makeCameraError(u"Failed to configure camera: "_s
207 + QSystemError::stdString(-configureResult));
208 }
209
210 // validate()/configure() may adjust the requested configuration; the *adjusted* values are
211 // the ones actually in effect, and frameFormat() must describe those, not what was asked for.
212 const StreamConfiguration &adjusted = m_config->at(0);
213 m_stride = int(adjusted.stride);
214 std::tie(m_colorSpace, m_colorRange) = colorSpaceFromLibcamera(adjusted.colorSpace);
215 if (const std::optional adjustedFormat = pixelFormatFromLibcamera(adjusted.pixelFormat))
216 m_framePixelFormat = *adjustedFormat;
217
218 Stream *stream = adjusted.stream();
219
220 m_allocator = std::make_unique<FrameBufferAllocator>(m_camera);
221 if (const int allocated = m_allocator->allocate(stream); allocated < 0) {
222 switch (allocated) {
223 case -EACCES:
224 return makeCameraError(u"Camera is not in a state where buffers can be allocated"_s);
225 case -EINVAL:
226 return makeCameraError(u"Camera stream is not part of the active configuration"_s);
227 case -EBUSY:
228 return makeCameraError(u"Capture buffers are already allocated"_s);
229 default:
230 return makeCameraError(u"Failed to allocate capture buffers: "_s
231 + QSystemError::stdString(-allocated));
232 }
233 }
234
235 m_stream = std::make_unique<QLibCameraCameraStream>(m_camera, stream, m_stride, frameFormat(),
236 m_cameraFormat.maxFrameRate());
237
238 connect(m_stream.get(), &QLibCameraCameraStream::newVideoFrame, this,
239 &QLibCameraCamera::newVideoFrame);
240
241 m_camera->requestCompleted.connect(this, [stream = m_stream.get()](Request *request) {
242 // libcamera thread
243 stream->handleRequestCompleted(request);
244 });
245
246 m_requests.clear();
247 for (const std::unique_ptr<FrameBuffer> &buffer : m_allocator->buffers(stream)) {
248 std::unique_ptr<Request> request = m_camera->createRequest();
249 if (!request)
250 return makeCameraError(u"Failed to create capture request"_s);
251
252 const int addBufferResult = request->addBuffer(stream, buffer.get());
253 switch (addBufferResult) {
254 case 0:
255 break;
256 case -EEXIST:
257 return makeCameraError(u"Capture request already has a buffer for the stream"_s);
258 case -EINVAL:
259 return makeCameraError(u"Frame buffer does not reference a valid stream"_s);
260 default:
261 return makeCameraError(u"Failed to add buffer to capture request: "_s
262 + QSystemError::stdString(-addBufferResult));
263 }
264 m_requests.push_back(std::move(request));
265 }
266
267 using namespace std::chrono;
268 ControlList startControls(m_camera->controls());
269 if (m_cameraFormat.maxFrameRate() > 0) {
270 const auto frameDuration =
271 duration_cast<microseconds>(duration<double>(1.0 / m_cameraFormat.maxFrameRate()));
272 startControls.set(libcamera::controls::FrameDurationLimits,
273 { frameDuration.count(), frameDuration.count() });
274 }
275
276 const int startResult = m_camera->start(&startControls);
277 switch (startResult) {
278 case 0:
279 break;
280 case -ENODEV:
281 return makeCameraError(u"Camera has been disconnected"_s);
282 case -EACCES:
283 return makeCameraError(u"Camera is not in a state where it can be started"_s);
284 default:
285 return makeCameraError(u"Failed to start camera: "_s
286 + QSystemError::stdString(-startResult));
287 }
288 m_streamState = StreamState::Running;
289
290 for (const std::unique_ptr<Request> &request : m_requests) {
291 const int queueResult = m_camera->queueRequest(request.get());
292 switch (queueResult) {
293 case 0:
294 break;
295 case -ENODEV:
296 return makeCameraError(u"Camera has been disconnected"_s);
297 case -EACCES:
298 return makeCameraError(u"Camera is not running so requests can't be queued"_s);
299 case -EXDEV:
300 return makeCameraError(u"Capture request does not belong to this camera"_s);
301 case -EINVAL:
302 return makeCameraError(u"Capture request is invalid"_s);
303 case -ENOMEM:
304 return makeCameraError(u"No buffer memory available to queue capture request"_s);
305 default:
306 return makeCameraError(u"Failed to queue capture request: "_s
307 + QSystemError::stdString(-queueResult));
308 }
309 }
310
311 abortGuard.dismiss();
312 return {};
313}
314
315void QLibCameraCamera::startCapturingReportingErrors()
316{
317 if (StartCapturingResult result = startCapturing(); !result)
318 updateError(std::get<0>(result.error()), std::get<1>(result.error()));
319}
320
321void QLibCameraCamera::stopCapturing()
322{
323 if (m_camera) {
324 m_camera->requestCompleted.disconnect(this);
325 if (m_streamState == StreamState::Running) {
326 const int stopResult = m_camera->stop();
327 switch (stopResult) {
328 case 0:
329 case -EACCES: // the camera is not running
330 break;
331 case -ENODEV: // the camera has been disconnected
332 qCDebug(lcLibCamera) << "Camera disconnected while stopping capture";
333 break;
334 default:
335 qCWarning(lcLibCamera)
336 << "Failed to stop camera:" << QSystemError::stdString(-stopResult);
337 break;
338 }
339 }
340 }
341
342 Stream *stream = m_stream ? m_stream->stream() : nullptr;
343 m_stream.reset();
344
345 m_requests.clear();
346 if (m_allocator && stream) {
347 const int freeResult = m_allocator->free(stream);
348 switch (freeResult) {
349 case 0:
350 case -EACCES: // the camera is not in a state where buffers can be freed
351 case -EINVAL: // the allocator does not handle this stream
352 break;
353 default:
354 qCWarning(lcLibCamera)
355 << "Failed to free capture buffers:" << QSystemError::stdString(-freeResult);
356 break;
357 }
358 }
359 m_allocator.reset();
360 m_config.reset();
361
362 if (m_camera && m_streamState != StreamState::Idle) {
363 const int releaseResult = m_camera->release();
364 switch (releaseResult) {
365 case 0:
366 break;
367 case -EBUSY: // the camera is still running
368 qCWarning(lcLibCamera) << "Camera still running while releasing it";
369 break;
370 default:
371 qCWarning(lcLibCamera)
372 << "Failed to release camera:" << QSystemError::stdString(-releaseResult);
373 break;
374 }
375 }
376 m_streamState = StreamState::Idle;
377}
378
379void QLibCameraCamera::setExposureMode(QCamera::ExposureMode mode)
380{
381 if (mode != QCamera::ExposureAuto && mode != QCamera::ExposureManual)
382 return;
383 withPendingControls([&](ControlList &pending) {
384 QLibCameraControls::setAeEnable(pending, mode == QCamera::ExposureAuto);
385 });
386 exposureModeChanged(mode);
387}
388
389bool QLibCameraCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
390{
391 return mode == QCamera::ExposureAuto || mode == QCamera::ExposureManual;
392}
393
394void QLibCameraCamera::setExposureCompensation(float compensation)
395{
396 withPendingControls([&](ControlList &pending) {
398 });
399 exposureCompensationChanged(compensation);
400}
401
402void QLibCameraCamera::setManualIsoSensitivity(int iso)
403{
404 withPendingControls([&](ControlList &pending) {
406 });
407 isoSensitivityChanged(iso);
408}
409
410void QLibCameraCamera::setManualExposureTime(float seconds)
411{
412 withPendingControls([&](ControlList &pending) {
414 });
415 exposureTimeChanged(seconds);
416}
417
418void QLibCameraCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
419{
420 withPendingControls([&](ControlList &pending) {
421 QLibCameraControls::setAwbEnable(pending, mode == QCamera::WhiteBalanceAuto);
422 });
423 if (mode != QCamera::WhiteBalanceAuto)
424 setColorTemperature(colorTemperatureForWhiteBalance(mode));
425 whiteBalanceModeChanged(mode);
426}
427
428void QLibCameraCamera::setColorTemperature(int temperature)
429{
430 withPendingControls([&](ControlList &pending) {
432 });
433 colorTemperatureChanged(temperature);
434}
435
436void QLibCameraCamera::zoomTo(float newZoomFactor, float /*rate*/)
437{
438 if (!m_camera)
439 return;
440 newZoomFactor = std::clamp(newZoomFactor, minZoomFactor(), maxZoomFactor());
441 withPendingControls([&](ControlList &pending) {
442 QLibCameraControls::setZoomFactor(pending, m_camera->controls(), newZoomFactor);
443 });
444 zoomFactorChanged(newZoomFactor);
445}
446
447QVideoFrameFormat QLibCameraCamera::frameFormat() const
448{
449 QVideoFrameFormat result = QPlatformCamera::frameFormat();
450 result.setColorSpace(m_colorSpace);
451 result.setColorRange(m_colorRange);
452 return result;
453}
454
455} // namespace QLibCamera
456
457QT_END_NAMESPACE
458
459#include "moc_qlibcamera_camera_p.cpp"
void setExposureCompensation(float compensation) override
void setManualIsoSensitivity(int iso) override
void setActive(bool active) override
void setCamera(const QCameraDevice &) override
void setManualExposureTime(float seconds) override
bool setCameraFormat(const QCameraFormat &) override
void zoomTo(float newZoomFactor, float rate=-1.) override
void setExposureMode(QCamera::ExposureMode) override
QVideoFrameFormat frameFormat() const override
int cameraPixelFormatScore(QVideoFrameFormat::PixelFormat, QVideoFrameFormat::ColorRange) const override
void setColorTemperature(int temperature) override
void setWhiteBalanceMode(QCamera::WhiteBalanceMode) override
bool isExposureModeSupported(QCamera::ExposureMode mode) const override
void setExposureCompensation(ControlList &pending, float stops)
void setAnalogueGainForIso(ControlList &pending, int iso)
void setAwbEnable(ControlList &pending, bool enable)
void setZoomFactor(ControlList &pending, const ControlInfoMap &controls, float zoomFactor)
void setColorTemperature(ControlList &pending, int kelvin)
void setManualExposureTime(ControlList &pending, float seconds)
void setAeEnable(ControlList &pending, bool enable)