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
qwasmcamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include <qcameradevice.h>
7#include <private/qplatformvideosink_p.h>
8#include <private/qplatformvideodevices_p.h>
9#include <private/qmemoryvideobuffer_p.h>
10#include <private/qcameradevice_p.h>
11#include <private/qvideotexturehelper_p.h>
12#include <private/qwasmmediadevices_p.h>
13#include <QtMultimedia/private/qmultimedia_ranges_p.h>
14
16#include <common/qwasmvideooutput_p.h>
17
18#include <emscripten/val.h>
19#include <emscripten/bind.h>
20#include <emscripten/html5.h>
21#include <QUuid>
22#include <QTimer>
23
24#include <private/qstdweb_p.h>
25
26Q_LOGGING_CATEGORY(qWasmCamera, "qt.multimedia.wasm.camera")
27
28namespace ranges = QtMultimediaPrivate::ranges;
29
30QWasmCamera::QWasmCamera(QCamera *camera)
31 : QPlatformCamera(camera),
32 m_cameraOutput(new QWasmVideoOutput),
33 m_cameraIsReady(false)
34{
35 connect(this, &QWasmCamera::cameraIsReady, this, [this]() {
36 m_cameraIsReady = true;
37 if (m_cameraShouldStartActive) {
38 QTimer::singleShot(50, this, [this]() {
39 setActive(true);
40 });
41 }
42 });
43}
44
45QWasmCamera::~QWasmCamera() = default;
46
47bool QWasmCamera::isActive() const
48{
49 return m_cameraActive;
50}
51
52void QWasmCamera::setActive(bool active)
53{
54 if (m_cameraActive == active)
55 return;
56 if (!m_CaptureSession) {
57 updateError(QCamera::CameraError, QStringLiteral("video surface error"));
58 m_shouldBeActive = true;
59 return;
60 }
61 if (m_cameraActive && !active)
62 m_cameraOutput->stop();
63
64 m_shouldBeActive = active;
65
66 if (!m_cameraIsReady) {
67 m_cameraShouldStartActive = true;
68 return;
69 }
70
71 QVideoSink *sink = m_CaptureSession->videoSink();
72 if (!sink) {
73 qWarning() << Q_FUNC_INFO << "sink not ready";
74 return;
75 }
76
77 m_cameraOutput->setSurface(m_CaptureSession->videoSink());
78 m_cameraActive = active;
79 m_shouldBeActive = false;
80
81 if (active)
82 updateCameraFeatures();
83 emit activeChanged(active);
84 if (m_CaptureSession->imageCapture()) {
85 if (active) {
86 m_readyChangedConnection = connect(cameraOutput(), &QWasmVideoOutput::readyChanged, this, [this] () {
87 m_CaptureSession->setReadyForCapture(true);
88 });
89 }
90 }
91 if (!active && m_readyChangedConnection) {
92 QObject::disconnect(m_readyChangedConnection);
93 }
94
95 if (m_cameraActive)
96 m_cameraOutput->start();
97}
98
99void QWasmCamera::setCamera(const QCameraDevice &camera)
100{
101 if (camera.id().isEmpty() || (m_cameraDev.id() == camera.id())) {
102 return;
103 }
104
105 const bool wasActive = m_cameraActive;
106 if (wasActive)
107 m_cameraOutput->stop();
108
109 m_cameraOutput->setVideoMode(QWasmVideoOutput::Camera);
110
111 constexpr QSize initialSize(0, 0);
112 constexpr QRect initialRect(QPoint(0, 0), initialSize);
113 m_cameraOutput->createVideoElement(
114 QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString()); // videoElementId
115 m_cameraOutput->doElementCallbacks();
116 m_cameraOutput->createOffscreenElement(initialSize);
117 m_cameraOutput->updateVideoElementGeometry(initialRect);
118
119 const auto cameras = QMediaDevices::videoInputs();
120
121 if (ranges::contains(cameras, camera)) {
122 m_cameraDev = camera;
123 createCamera(m_cameraDev);
124 emit cameraIsReady();
125 if (wasActive)
126 m_cameraOutput->start();
127 return;
128 }
129
130 if (cameras.count() > 0) {
131 m_cameraDev = camera;
132 createCamera(m_cameraDev);
133 emit cameraIsReady();
134 if (wasActive)
135 m_cameraOutput->start();
136 } else {
137 updateError(QCamera::CameraError, QStringLiteral("Failed to find a camera"));
138 }
139}
140
141bool QWasmCamera::setCameraFormat(const QCameraFormat &format)
142{
143 m_cameraFormat = format;
144 m_cameraOutput->setVideoConstraints(format.resolution(), format.minFrameRate(), format.maxFrameRate());
145 return true;
146}
147
148void QWasmCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
149{
150 QWasmMediaCaptureSession *captureSession = static_cast<QWasmMediaCaptureSession *>(session);
151 if (m_CaptureSession == captureSession)
152 return;
153
154 m_CaptureSession = captureSession;
155
156 if (m_shouldBeActive)
157 setActive(true);
158}
159
160void QWasmCamera::setFocusMode(QCamera::FocusMode mode)
161{
162 if (!isFocusModeSupported(mode))
163 return;
164
165 static constexpr std::string_view focusModeString = "focusMode";
166 if (mode == QCamera::FocusModeManual)
167 m_cameraOutput->setDeviceSetting(focusModeString.data(), emscripten::val("manual"));
168 if (mode == QCamera::FocusModeAuto)
169 m_cameraOutput->setDeviceSetting(focusModeString.data(), emscripten::val("continuous"));
170 focusModeChanged(mode);
171}
172
173bool QWasmCamera::isFocusModeSupported(QCamera::FocusMode mode) const
174{
175 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
176 if (caps.isUndefined())
177 return false;
178
179 emscripten::val focusMode = caps["focusMode"];
180 if (focusMode.isUndefined())
181 return false;
182
183 std::vector<std::string> focalModes;
184
185 for (int i = 0; i < focusMode["length"].as<int>(); i++)
186 focalModes.push_back(focusMode[i].as<std::string>());
187
188 // Do we need to take into account focusDistance
189 // it is not always available, and what distance
190 // would be far/near
191
192 bool found = false;
193 switch (mode) {
194 case QCamera::FocusModeAuto:
195 return ranges::contains(focalModes, "continuous")
196 || ranges::contains(focalModes, "single-shot");
197 case QCamera::FocusModeAutoNear:
198 case QCamera::FocusModeAutoFar:
199 case QCamera::FocusModeHyperfocal:
200 case QCamera::FocusModeInfinity:
201 break;
202 case QCamera::FocusModeManual:
203 found = ranges::contains(focalModes, "manual");
204 };
205 return found;
206}
207
208void QWasmCamera::setTorchMode(QCamera::TorchMode mode)
209{
210 if (!isTorchModeSupported(mode))
211 return;
212
213 if (m_wasmTorchMode == mode)
214 return;
215
216 static constexpr std::string_view torchModeString = "torchMode";
217 bool hasChanged = false;
218 switch (mode) {
219 case QCamera::TorchOff:
220 m_cameraOutput->setDeviceSetting(torchModeString.data(), emscripten::val(false));
221 hasChanged = true;
222 break;
223 case QCamera::TorchOn:
224 m_cameraOutput->setDeviceSetting(torchModeString.data(), emscripten::val(true));
225 hasChanged = true;
226 break;
227 case QCamera::TorchAuto:
228 break;
229 };
230 m_wasmTorchMode = mode;
231 if (hasChanged)
232 torchModeChanged(m_wasmTorchMode);
233}
234
235bool QWasmCamera::isTorchModeSupported(QCamera::TorchMode mode) const
236{
237 if (!m_cameraIsReady)
238 return false;
239
240 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
241 if (caps.isUndefined())
242 return false;
243
244 emscripten::val exposureMode = caps["torch"];
245 if (exposureMode.isUndefined())
246 return false;
247
248 return (mode != QCamera::TorchAuto);
249}
250
251void QWasmCamera::setExposureMode(QCamera::ExposureMode mode)
252{
253 // TODO manually come up with exposureTime values ?
254 if (!isExposureModeSupported(mode))
255 return;
256
257 if (m_wasmExposureMode == mode)
258 return;
259
260 bool hasChanged = false;
261 static constexpr std::string_view exposureModeString = "exposureMode";
262 switch (mode) {
263 case QCamera::ExposureManual:
264 m_cameraOutput->setDeviceSetting(exposureModeString.data(), emscripten::val("manual"));
265 hasChanged = true;
266 break;
267 case QCamera::ExposureAuto:
268 m_cameraOutput->setDeviceSetting(exposureModeString.data(), emscripten::val("continuous"));
269 hasChanged = true;
270 break;
271 default:
272 break;
273 };
274
275 if (hasChanged) {
276 m_wasmExposureMode = mode;
277 exposureModeChanged(m_wasmExposureMode);
278 }
279}
280
281bool QWasmCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
282{
283 if (!m_cameraIsReady)
284 return false;
285
286 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
287 if (caps.isUndefined())
288 return false;
289
290 emscripten::val exposureMode = caps["exposureMode"];
291 if (exposureMode.isUndefined())
292 return false;
293
294 std::vector<std::string> exposureModes;
295
296 for (int i = 0; i < exposureMode["length"].as<int>(); i++)
297 exposureModes.push_back(exposureMode[i].as<std::string>());
298
299 bool found = false;
300 switch (mode) {
301 case QCamera::ExposureAuto:
302 found = ranges::contains(exposureModes, "continuous");
303 break;
304 case QCamera::ExposureManual:
305 found = ranges::contains(exposureModes, "manual");
306 break;
307 default:
308 break;
309 };
310
311 return found;
312}
313
315{
316 if (!m_cameraIsReady)
317 return;
318
319 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
320 if (caps.isUndefined())
321 return;
322
323 emscripten::val exposureComp = caps["exposureCompensation"];
324 if (exposureComp.isUndefined())
325 return;
326 if (m_wasmExposureCompensation == bias)
327 return;
328
329 static constexpr std::string_view exposureCompensationModeString = "exposureCompensation";
330 m_cameraOutput->setDeviceSetting(exposureCompensationModeString.data(), emscripten::val(bias));
331 m_wasmExposureCompensation = bias;
332 emit exposureCompensationChanged(m_wasmExposureCompensation);
333}
334
336{
337 if (m_wasmExposureTime == secs)
338 return;
339
340 if (!m_cameraIsReady)
341 return;
342
343 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
344 emscripten::val exposureTime = caps["exposureTime"];
345 if (exposureTime.isUndefined())
346 return;
347 static constexpr std::string_view exposureTimeString = "exposureTime";
348 m_cameraOutput->setDeviceSetting(exposureTimeString.data(), emscripten::val(secs));
349 m_wasmExposureTime = secs;
350 emit exposureTimeChanged(m_wasmExposureTime);
351}
352
354{
355 if (!m_cameraIsReady)
356 return 0;
357
358 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
359 if (caps.isUndefined())
360 return false;
361
362 emscripten::val isoSpeed = caps["iso"];
363 if (isoSpeed.isUndefined())
364 return 0;
365
366 return isoSpeed.as<double>();
367}
368
370{
371 if (!m_cameraIsReady)
372 return;
373
374 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
375 if (caps.isUndefined())
376 return;
377
378 emscripten::val isoSpeed = caps["iso"];
379 if (isoSpeed.isUndefined())
380 return;
381 if (m_wasmIsoSensitivity == sens)
382 return;
383 static constexpr std::string_view isoString = "iso";
384 m_cameraOutput->setDeviceSetting(isoString.data(), emscripten::val(sens));
385 m_wasmIsoSensitivity = sens;
386 emit isoSensitivityChanged(m_wasmIsoSensitivity);
387}
388
389bool QWasmCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
390{
391 if (!m_cameraIsReady)
392 return false;
393
394 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
395 if (caps.isUndefined())
396 return false;
397
398 emscripten::val whiteBalanceMode = caps["whiteBalanceMode"];
399 if (whiteBalanceMode.isUndefined())
400 return false;
401
402 if (mode == QCamera::WhiteBalanceAuto || mode == QCamera::WhiteBalanceManual)
403 return true;
404
405 return false;
406}
407
408void QWasmCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
409{
410 if (!isWhiteBalanceModeSupported(mode))
411 return;
412
413 if (m_wasmWhiteBalanceMode == mode)
414 return;
415
416 bool hasChanged = false;
417 static constexpr std::string_view whiteBalanceModeString = "whiteBalanceMode";
418 switch (mode) {
419 case QCamera::WhiteBalanceAuto:
420 m_cameraOutput->setDeviceSetting(whiteBalanceModeString.data(), emscripten::val("auto"));
421 hasChanged = true;
422 break;
423 case QCamera::WhiteBalanceManual:
424 m_cameraOutput->setDeviceSetting(whiteBalanceModeString.data(), emscripten::val("manual"));
425 hasChanged = true;
426 break;
427 default:
428 break;
429 };
430
431 if (hasChanged) {
432 m_wasmWhiteBalanceMode = mode;
433 emit whiteBalanceModeChanged(m_wasmWhiteBalanceMode);
434 }
435}
436
437void QWasmCamera::setColorTemperature(int temperature)
438{
439 if (!m_cameraIsReady)
440 return;
441
442 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
443 if (caps.isUndefined())
444 return;
445
446 emscripten::val whiteBalanceMode = caps["colorTemperature"];
447 if (whiteBalanceMode.isUndefined())
448 return;
449 if(m_wasmColorTemperature == temperature)
450 return;
451
452 static constexpr std::string_view colorBalanceString = "colorTemperature";
453 m_cameraOutput->setDeviceSetting(colorBalanceString.data(), emscripten::val(temperature));
454 m_wasmColorTemperature = temperature;
455 colorTemperatureChanged(m_wasmColorTemperature);
456}
457
458void QWasmCamera::createCamera(const QCameraDevice &camera)
459{
460 m_cameraOutput->addCameraSourceElement(camera.id().toStdString());
461}
462
463void QWasmCamera::updateCameraFeatures()
464{
465 if (!m_cameraIsReady)
466 return;
467
468 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
469 if (caps.isUndefined())
470 return;
471
472 QCamera::Features cameraFeatures;
473
474 if (!caps["colorTemperature"].isUndefined())
475 cameraFeatures |= QCamera::Feature::ColorTemperature;
476
477 if (!caps["exposureCompensation"].isUndefined())
478 cameraFeatures |= QCamera::Feature::ExposureCompensation;
479
480 if (!caps["iso"].isUndefined())
481 cameraFeatures |= QCamera::Feature::IsoSensitivity;
482
483 if (!caps["exposureTime"].isUndefined())
484 cameraFeatures |= QCamera::Feature::ManualExposureTime;
485
486 if (!caps["focusDistance"].isUndefined())
487 cameraFeatures |= QCamera::Feature::FocusDistance;
488
489 supportedFeaturesChanged(cameraFeatures);
490 updateVideoFormats(caps);
491}
492
493void QWasmCamera::updateVideoFormats(const emscripten::val &cababilities)
494{
495 // Use synthetic standard res and framerates. Webaudio
496 // only allows real camera format from a live camera stream
497 // camera constraints are only a suggestion anyway
498 emscripten::val widthCapabilities = cababilities["width"];
499 emscripten::val heightCapabilities = cababilities["height"];
500 emscripten::val frameRateCapabilities = cababilities["frameRate"];
501
502 if (widthCapabilities.isUndefined() || heightCapabilities.isUndefined()
503 || frameRateCapabilities.isUndefined())
504 return;
505
506 const int maxWidth = widthCapabilities["max"].as<int>();
507 const int maxHeight = heightCapabilities["max"].as<int>();
508 const float minFrameRate = frameRateCapabilities["min"].as<double>();
509 const float maxFrameRate = frameRateCapabilities["max"].as<double>();
510
511 static const QSize standardResolutions[] = {
512 { 3840, 2160 }, // 4k
513 { 2560, 1440 }, // 1440p
514 { 2048, 1080 }, // 2k
515 { 1920, 1080 }, // FHD
516 { 1280, 720 }, // HD
517 { 640, 480 }, // SD
518 };
519 static const float standardFrameRates[] = { 15.f, 24.f, 25.f, 29.97f, 30.f,
520 60.f, 100.f, 120.f };
521
522 QList<QCameraFormat> formats;
523 for (const QSize &resolution : standardResolutions) {
524 if (resolution.width() > maxWidth || resolution.height() > maxHeight)
525 continue;
526 for (float rate : standardFrameRates) {
527 if (rate < minFrameRate || rate > maxFrameRate + 0.5f)
528 continue;
529 auto *oneFormat = new QCameraFormatPrivate{
530 QSharedData(),
531 QVideoFrameFormat::Format_RGBA8888,
532 resolution,
533 rate, rate
534 };
535 formats << oneFormat->create();
536 }
537 }
538
539 if (formats.isEmpty())
540 return;
541
542 auto *devicePrivate =
543 const_cast<QCameraDevicePrivate *>(QCameraDevicePrivate::handle(m_cameraDev));
544 devicePrivate->videoFormats = formats;
545}
bool isTorchModeSupported(QCamera::TorchMode mode) const override
void setManualExposureTime(float) override
void setExposureCompensation(float bias) override
void setColorTemperature(int temperature) override
int isoSensitivity() const override
void setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) override
void setTorchMode(QCamera::TorchMode mode) override
bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const override
void setExposureMode(QCamera::ExposureMode mode) override
bool setCameraFormat(const QCameraFormat &format) override
void setManualIsoSensitivity(int) override
void setFocusMode(QCamera::FocusMode mode) override
void setActive(bool active) override
bool isFocusModeSupported(QCamera::FocusMode mode) const override
bool isActive() const override
void setCaptureSession(QPlatformMediaCaptureSession *session) override
bool isExposureModeSupported(QCamera::ExposureMode mode) const override
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")