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->updateVideoElementGeometry(initialRect);
117
118 const auto cameras = QMediaDevices::videoInputs();
119
120 if (ranges::contains(cameras, camera)) {
121 m_cameraDev = camera;
122 createCamera(m_cameraDev);
123 emit cameraIsReady();
124 if (wasActive)
125 m_cameraOutput->start();
126 return;
127 }
128
129 if (cameras.count() > 0) {
130 m_cameraDev = camera;
131 createCamera(m_cameraDev);
132 emit cameraIsReady();
133 if (wasActive)
134 m_cameraOutput->start();
135 } else {
136 updateError(QCamera::CameraError, QStringLiteral("Failed to find a camera"));
137 }
138}
139
140bool QWasmCamera::setCameraFormat(const QCameraFormat &format)
141{
142 m_cameraFormat = format;
143 m_cameraOutput->setVideoConstraints(format.resolution(), format.minFrameRate(), format.maxFrameRate());
144 return true;
145}
146
147void QWasmCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
148{
149 QWasmMediaCaptureSession *captureSession = static_cast<QWasmMediaCaptureSession *>(session);
150 if (m_CaptureSession == captureSession)
151 return;
152
153 m_CaptureSession = captureSession;
154
155 if (m_shouldBeActive)
156 setActive(true);
157}
158
159void QWasmCamera::setFocusMode(QCamera::FocusMode mode)
160{
161 if (!isFocusModeSupported(mode))
162 return;
163
164 static constexpr std::string_view focusModeString = "focusMode";
165 if (mode == QCamera::FocusModeManual)
166 m_cameraOutput->setDeviceSetting(focusModeString.data(), emscripten::val("manual"));
167 if (mode == QCamera::FocusModeAuto)
168 m_cameraOutput->setDeviceSetting(focusModeString.data(), emscripten::val("continuous"));
169 focusModeChanged(mode);
170}
171
172bool QWasmCamera::isFocusModeSupported(QCamera::FocusMode mode) const
173{
174 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
175 if (caps.isUndefined())
176 return false;
177
178 emscripten::val focusMode = caps["focusMode"];
179 if (focusMode.isUndefined())
180 return false;
181
182 std::vector<std::string> focalModes;
183
184 for (int i = 0; i < focusMode["length"].as<int>(); i++)
185 focalModes.push_back(focusMode[i].as<std::string>());
186
187 // Do we need to take into account focusDistance
188 // it is not always available, and what distance
189 // would be far/near
190
191 bool found = false;
192 switch (mode) {
193 case QCamera::FocusModeAuto:
194 return ranges::contains(focalModes, "continuous")
195 || ranges::contains(focalModes, "single-shot");
196 case QCamera::FocusModeAutoNear:
197 case QCamera::FocusModeAutoFar:
198 case QCamera::FocusModeHyperfocal:
199 case QCamera::FocusModeInfinity:
200 break;
201 case QCamera::FocusModeManual:
202 found = ranges::contains(focalModes, "manual");
203 };
204 return found;
205}
206
207void QWasmCamera::setTorchMode(QCamera::TorchMode mode)
208{
209 if (!isTorchModeSupported(mode))
210 return;
211
212 if (m_wasmTorchMode == mode)
213 return;
214
215 static constexpr std::string_view torchModeString = "torchMode";
216 bool hasChanged = false;
217 switch (mode) {
218 case QCamera::TorchOff:
219 m_cameraOutput->setDeviceSetting(torchModeString.data(), emscripten::val(false));
220 hasChanged = true;
221 break;
222 case QCamera::TorchOn:
223 m_cameraOutput->setDeviceSetting(torchModeString.data(), emscripten::val(true));
224 hasChanged = true;
225 break;
226 case QCamera::TorchAuto:
227 break;
228 };
229 m_wasmTorchMode = mode;
230 if (hasChanged)
231 torchModeChanged(m_wasmTorchMode);
232}
233
234bool QWasmCamera::isTorchModeSupported(QCamera::TorchMode mode) const
235{
236 if (!m_cameraIsReady)
237 return false;
238
239 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
240 if (caps.isUndefined())
241 return false;
242
243 emscripten::val exposureMode = caps["torch"];
244 if (exposureMode.isUndefined())
245 return false;
246
247 return (mode != QCamera::TorchAuto);
248}
249
250void QWasmCamera::setExposureMode(QCamera::ExposureMode mode)
251{
252 // TODO manually come up with exposureTime values ?
253 if (!isExposureModeSupported(mode))
254 return;
255
256 if (m_wasmExposureMode == mode)
257 return;
258
259 bool hasChanged = false;
260 static constexpr std::string_view exposureModeString = "exposureMode";
261 switch (mode) {
262 case QCamera::ExposureManual:
263 m_cameraOutput->setDeviceSetting(exposureModeString.data(), emscripten::val("manual"));
264 hasChanged = true;
265 break;
266 case QCamera::ExposureAuto:
267 m_cameraOutput->setDeviceSetting(exposureModeString.data(), emscripten::val("continuous"));
268 hasChanged = true;
269 break;
270 default:
271 break;
272 };
273
274 if (hasChanged) {
275 m_wasmExposureMode = mode;
276 exposureModeChanged(m_wasmExposureMode);
277 }
278}
279
280bool QWasmCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
281{
282 if (!m_cameraIsReady)
283 return false;
284
285 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
286 if (caps.isUndefined())
287 return false;
288
289 emscripten::val exposureMode = caps["exposureMode"];
290 if (exposureMode.isUndefined())
291 return false;
292
293 std::vector<std::string> exposureModes;
294
295 for (int i = 0; i < exposureMode["length"].as<int>(); i++)
296 exposureModes.push_back(exposureMode[i].as<std::string>());
297
298 bool found = false;
299 switch (mode) {
300 case QCamera::ExposureAuto:
301 found = ranges::contains(exposureModes, "continuous");
302 break;
303 case QCamera::ExposureManual:
304 found = ranges::contains(exposureModes, "manual");
305 break;
306 default:
307 break;
308 };
309
310 return found;
311}
312
314{
315 if (!m_cameraIsReady)
316 return;
317
318 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
319 if (caps.isUndefined())
320 return;
321
322 emscripten::val exposureComp = caps["exposureCompensation"];
323 if (exposureComp.isUndefined())
324 return;
325 if (m_wasmExposureCompensation == bias)
326 return;
327
328 static constexpr std::string_view exposureCompensationModeString = "exposureCompensation";
329 m_cameraOutput->setDeviceSetting(exposureCompensationModeString.data(), emscripten::val(bias));
330 m_wasmExposureCompensation = bias;
331 emit exposureCompensationChanged(m_wasmExposureCompensation);
332}
333
335{
336 if (m_wasmExposureTime == secs)
337 return;
338
339 if (!m_cameraIsReady)
340 return;
341
342 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
343 emscripten::val exposureTime = caps["exposureTime"];
344 if (exposureTime.isUndefined())
345 return;
346 static constexpr std::string_view exposureTimeString = "exposureTime";
347 m_cameraOutput->setDeviceSetting(exposureTimeString.data(), emscripten::val(secs));
348 m_wasmExposureTime = secs;
349 emit exposureTimeChanged(m_wasmExposureTime);
350}
351
353{
354 if (!m_cameraIsReady)
355 return 0;
356
357 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
358 if (caps.isUndefined())
359 return false;
360
361 emscripten::val isoSpeed = caps["iso"];
362 if (isoSpeed.isUndefined())
363 return 0;
364
365 return isoSpeed.as<double>();
366}
367
369{
370 if (!m_cameraIsReady)
371 return;
372
373 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
374 if (caps.isUndefined())
375 return;
376
377 emscripten::val isoSpeed = caps["iso"];
378 if (isoSpeed.isUndefined())
379 return;
380 if (m_wasmIsoSensitivity == sens)
381 return;
382 static constexpr std::string_view isoString = "iso";
383 m_cameraOutput->setDeviceSetting(isoString.data(), emscripten::val(sens));
384 m_wasmIsoSensitivity = sens;
385 emit isoSensitivityChanged(m_wasmIsoSensitivity);
386}
387
388bool QWasmCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
389{
390 if (!m_cameraIsReady)
391 return false;
392
393 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
394 if (caps.isUndefined())
395 return false;
396
397 emscripten::val whiteBalanceMode = caps["whiteBalanceMode"];
398 if (whiteBalanceMode.isUndefined())
399 return false;
400
401 if (mode == QCamera::WhiteBalanceAuto || mode == QCamera::WhiteBalanceManual)
402 return true;
403
404 return false;
405}
406
407void QWasmCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
408{
409 if (!isWhiteBalanceModeSupported(mode))
410 return;
411
412 if (m_wasmWhiteBalanceMode == mode)
413 return;
414
415 bool hasChanged = false;
416 static constexpr std::string_view whiteBalanceModeString = "whiteBalanceMode";
417 switch (mode) {
418 case QCamera::WhiteBalanceAuto:
419 m_cameraOutput->setDeviceSetting(whiteBalanceModeString.data(), emscripten::val("auto"));
420 hasChanged = true;
421 break;
422 case QCamera::WhiteBalanceManual:
423 m_cameraOutput->setDeviceSetting(whiteBalanceModeString.data(), emscripten::val("manual"));
424 hasChanged = true;
425 break;
426 default:
427 break;
428 };
429
430 if (hasChanged) {
431 m_wasmWhiteBalanceMode = mode;
432 emit whiteBalanceModeChanged(m_wasmWhiteBalanceMode);
433 }
434}
435
436void QWasmCamera::setColorTemperature(int temperature)
437{
438 if (!m_cameraIsReady)
439 return;
440
441 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
442 if (caps.isUndefined())
443 return;
444
445 emscripten::val whiteBalanceMode = caps["colorTemperature"];
446 if (whiteBalanceMode.isUndefined())
447 return;
448 if(m_wasmColorTemperature == temperature)
449 return;
450
451 static constexpr std::string_view colorBalanceString = "colorTemperature";
452 m_cameraOutput->setDeviceSetting(colorBalanceString.data(), emscripten::val(temperature));
453 m_wasmColorTemperature = temperature;
454 colorTemperatureChanged(m_wasmColorTemperature);
455}
456
457void QWasmCamera::createCamera(const QCameraDevice &camera)
458{
459 m_cameraOutput->addCameraSourceElement(camera.id().toStdString());
460}
461
462void QWasmCamera::updateCameraFeatures()
463{
464 if (!m_cameraIsReady)
465 return;
466
467 emscripten::val caps = m_cameraOutput->getDeviceCapabilities();
468 if (caps.isUndefined())
469 return;
470
471 QCamera::Features cameraFeatures;
472
473 if (!caps["colorTemperature"].isUndefined())
474 cameraFeatures |= QCamera::Feature::ColorTemperature;
475
476 if (!caps["exposureCompensation"].isUndefined())
477 cameraFeatures |= QCamera::Feature::ExposureCompensation;
478
479 if (!caps["iso"].isUndefined())
480 cameraFeatures |= QCamera::Feature::IsoSensitivity;
481
482 if (!caps["exposureTime"].isUndefined())
483 cameraFeatures |= QCamera::Feature::ManualExposureTime;
484
485 if (!caps["focusDistance"].isUndefined())
486 cameraFeatures |= QCamera::Feature::FocusDistance;
487
488 supportedFeaturesChanged(cameraFeatures);
489 updateVideoFormats(caps);
490}
491
492void QWasmCamera::updateVideoFormats(const emscripten::val &cababilities)
493{
494 // Use synthetic standard res and framerates. Webaudio
495 // only allows real camera format from a live camera stream
496 // camera constraints are only a suggestion anyway
497 emscripten::val widthCapabilities = cababilities["width"];
498 emscripten::val heightCapabilities = cababilities["height"];
499 emscripten::val frameRateCapabilities = cababilities["frameRate"];
500
501 if (widthCapabilities.isUndefined() || heightCapabilities.isUndefined()
502 || frameRateCapabilities.isUndefined())
503 return;
504
505 const int maxWidth = widthCapabilities["max"].as<int>();
506 const int maxHeight = heightCapabilities["max"].as<int>();
507 const float minFrameRate = frameRateCapabilities["min"].as<double>();
508 const float maxFrameRate = frameRateCapabilities["max"].as<double>();
509
510 static const QSize standardResolutions[] = {
511 { 3840, 2160 }, // 4k
512 { 2560, 1440 }, // 1440p
513 { 2048, 1080 }, // 2k
514 { 1920, 1080 }, // FHD
515 { 1280, 720 }, // HD
516 { 640, 480 }, // SD
517 };
518 static const float standardFrameRates[] = { 15.f, 24.f, 25.f, 29.97f, 30.f,
519 60.f, 100.f, 120.f };
520
521 QList<QCameraFormat> formats;
522 for (const QSize &resolution : standardResolutions) {
523 if (resolution.width() > maxWidth || resolution.height() > maxHeight)
524 continue;
525 for (float rate : standardFrameRates) {
526 if (rate < minFrameRate || rate > maxFrameRate + 0.5f)
527 continue;
528 auto *oneFormat = new QCameraFormatPrivate{
529 QSharedData(),
530 QVideoFrameFormat::Format_RGBA8888,
531 resolution,
532 rate, rate
533 };
534 formats << oneFormat->create();
535 }
536 }
537
538 if (formats.isEmpty())
539 return;
540
541 QCameraDevicePrivate *devicePrivate = QCameraDevicePrivate::handle(m_cameraDev);
542 devicePrivate->videoFormats = formats;
543}
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")