| 1 | // Copyright (C) 2016 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 |  | 
| 4 | #include <mediacapture/qgstreamercamera_p.h> | 
| 5 |  | 
| 6 | #include <QtMultimedia/qcameradevice.h> | 
| 7 | #include <QtMultimedia/qmediacapturesession.h> | 
| 8 | #include <QtMultimedia/private/qcameradevice_p.h> | 
| 9 | #include <QtCore/qdebug.h> | 
| 10 |  | 
| 11 | #include <common/qgst_debug_p.h> | 
| 12 | #include <qgstreamervideodevices_p.h> | 
| 13 | #include <qgstreamerintegration_p.h> | 
| 14 |  | 
| 15 | #if QT_CONFIG(linux_v4l) | 
| 16 | #include <linux/videodev2.h> | 
| 17 | #include <private/qcore_unix_p.h> | 
| 18 | #endif | 
| 19 |  | 
| 20 |  | 
| 21 | QT_BEGIN_NAMESPACE | 
| 22 |  | 
| 23 | QMaybe<QPlatformCamera *> QGstreamerCamera::create(QCamera *camera) | 
| 24 | { | 
| 25 |     static const auto error = qGstErrorMessageIfElementsNotAvailable( | 
| 26 |             arg: "videotestsrc" , args: "capsfilter" , args: "videoconvert" , args: "videoscale" , args: "identity" ); | 
| 27 |     if (error) | 
| 28 |         return *error; | 
| 29 |  | 
| 30 |     return new QGstreamerCamera(camera); | 
| 31 | } | 
| 32 |  | 
| 33 | QGstreamerCamera::QGstreamerCamera(QCamera *camera) | 
| 34 |     : QGstreamerCameraBase(camera), | 
| 35 |       gstCameraBin{ | 
| 36 |           QGstBin::create(name: "camerabin" ), | 
| 37 |       }, | 
| 38 |       gstCamera{ | 
| 39 |           QGstElement::createFromFactory(factory: "videotestsrc" ), | 
| 40 |       }, | 
| 41 |       gstCapsFilter{ | 
| 42 |           QGstElement::createFromFactory(factory: "capsfilter" , name: "videoCapsFilter" ), | 
| 43 |       }, | 
| 44 |       gstDecode{ | 
| 45 |           QGstElement::createFromFactory(factory: "identity" ), | 
| 46 |       }, | 
| 47 |       gstVideoConvert{ | 
| 48 |           QGstElement::createFromFactory(factory: "videoconvert" , name: "videoConvert" ), | 
| 49 |       }, | 
| 50 |       gstVideoScale{ | 
| 51 |           QGstElement::createFromFactory(factory: "videoscale" , name: "videoScale" ), | 
| 52 |       } | 
| 53 | { | 
| 54 |     gstCameraBin.add(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert, ts: gstVideoScale); | 
| 55 |     qLinkGstElements(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert, ts: gstVideoScale); | 
| 56 |     gstCameraBin.addGhostPad(child: gstVideoScale, name: "src" ); | 
| 57 | } | 
| 58 |  | 
| 59 | QGstreamerCamera::~QGstreamerCamera() | 
| 60 | { | 
| 61 |     gstCameraBin.setStateSync(state: GST_STATE_NULL); | 
| 62 | } | 
| 63 |  | 
| 64 | bool QGstreamerCamera::isActive() const | 
| 65 | { | 
| 66 |     return m_active; | 
| 67 | } | 
| 68 |  | 
| 69 | void QGstreamerCamera::setActive(bool active) | 
| 70 | { | 
| 71 |     if (m_active == active) | 
| 72 |         return; | 
| 73 |     if (m_cameraDevice.isNull() && active) | 
| 74 |         return; | 
| 75 |  | 
| 76 |     m_active = active; | 
| 77 |  | 
| 78 |     emit activeChanged(active); | 
| 79 | } | 
| 80 |  | 
| 81 | void QGstreamerCamera::setCamera(const QCameraDevice &camera) | 
| 82 | { | 
| 83 |     using namespace Qt::Literals; | 
| 84 |  | 
| 85 |     if (m_cameraDevice == camera) | 
| 86 |         return; | 
| 87 |  | 
| 88 |     m_cameraDevice = camera; | 
| 89 |  | 
| 90 |     QGstElement gstNewCamera; | 
| 91 |     if (camera.isNull()) { | 
| 92 |         gstNewCamera = QGstElement::createFromFactory(factory: "videotestsrc" ); | 
| 93 |     } else { | 
| 94 |         auto *integration = static_cast<QGstreamerIntegration *>(QGstreamerIntegration::instance()); | 
| 95 |         GstDevice *device = integration->videoDevice(id: camera.id()); | 
| 96 |  | 
| 97 |         if (!device) { | 
| 98 |             updateError(error: QCamera::Error::CameraError, | 
| 99 |                         errorString: u"Failed to create GstDevice for camera: "_s  | 
| 100 |                                 + QString::fromUtf8(ba: camera.id())); | 
| 101 |             return; | 
| 102 |         } | 
| 103 |  | 
| 104 |         gstNewCamera = QGstElement::createFromDevice(device, name: "camerasrc" ); | 
| 105 |         QUniqueGstStructureHandle properties{ | 
| 106 |             gst_device_get_properties(device), | 
| 107 |         }; | 
| 108 |  | 
| 109 |         if (properties) { | 
| 110 |             QGstStructureView propertiesView{ properties }; | 
| 111 |             if (propertiesView.name() == "v4l2deviceprovider" ) | 
| 112 |                 m_v4l2DevicePath = QString::fromUtf8(utf8: propertiesView["device.path" ].toString()); | 
| 113 |         } | 
| 114 |     } | 
| 115 |  | 
| 116 |     QCameraFormat f = findBestCameraFormat(camera); | 
| 117 |     auto caps = QGstCaps::fromCameraFormat(format: f); | 
| 118 |     auto gstNewDecode = QGstElement::createFromFactory( | 
| 119 |             factory: f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec"  : "identity" ); | 
| 120 |  | 
| 121 |     m_currentCameraFormat = f; | 
| 122 |  | 
| 123 |     updateCamera(f: [&] { | 
| 124 |         qUnlinkGstElements(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert); | 
| 125 |         gstCameraBin.stopAndRemoveElements(ts&: gstCamera, ts&: gstDecode); | 
| 126 |  | 
| 127 |         gstCapsFilter.set(property: "caps" , c: caps); | 
| 128 |  | 
| 129 |         gstCamera = std::move(gstNewCamera); | 
| 130 |         gstDecode = std::move(gstNewDecode); | 
| 131 |  | 
| 132 |         gstCameraBin.add(ts: gstCamera, ts: gstDecode); | 
| 133 |         qLinkGstElements(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert); | 
| 134 |  | 
| 135 |         gstCameraBin.syncChildrenState(); | 
| 136 |     }); | 
| 137 |  | 
| 138 |     updateCameraProperties(); | 
| 139 | } | 
| 140 |  | 
| 141 | bool QGstreamerCamera::setCameraFormat(const QCameraFormat &format) | 
| 142 | { | 
| 143 |     if (!format.isNull() && !m_cameraDevice.videoFormats().contains(t: format)) | 
| 144 |         return false; | 
| 145 |  | 
| 146 |     QCameraFormat f = format; | 
| 147 |     if (f.isNull()) | 
| 148 |         f = findBestCameraFormat(camera: m_cameraDevice); | 
| 149 |  | 
| 150 |     if (f == m_currentCameraFormat) | 
| 151 |         return true; | 
| 152 |  | 
| 153 |     m_currentCameraFormat = f; | 
| 154 |  | 
| 155 |     auto caps = QGstCaps::fromCameraFormat(format: f); | 
| 156 |  | 
| 157 |     auto newGstDecode = QGstElement::createFromFactory( | 
| 158 |             factory: f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec"  : "identity" ); | 
| 159 |  | 
| 160 |     updateCamera(f: [&] { | 
| 161 |         qUnlinkGstElements(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert); | 
| 162 |         gstCameraBin.stopAndRemoveElements(ts&: gstDecode); | 
| 163 |  | 
| 164 |         gstCapsFilter.set(property: "caps" , c: caps); | 
| 165 |  | 
| 166 |         gstDecode = std::move(newGstDecode); | 
| 167 |  | 
| 168 |         gstCameraBin.add(ts: gstDecode); | 
| 169 |         qLinkGstElements(ts: gstCamera, ts: gstCapsFilter, ts: gstDecode, ts: gstVideoConvert); | 
| 170 |         gstCameraBin.syncChildrenState(); | 
| 171 |     }); | 
| 172 |  | 
| 173 |     return true; | 
| 174 | } | 
| 175 |  | 
| 176 | void QGstreamerCamera::updateCameraProperties() | 
| 177 | { | 
| 178 | #if QT_CONFIG(linux_v4l) | 
| 179 |     if (isV4L2Camera()) { | 
| 180 |         initV4L2Controls(); | 
| 181 |         return; | 
| 182 |     } | 
| 183 | #endif | 
| 184 | #if QT_CONFIG(gstreamer_photography) | 
| 185 |     if (auto *p = photography()) | 
| 186 |         gst_photography_set_white_balance_mode(photo: p, wb_mode: GST_PHOTOGRAPHY_WB_MODE_AUTO); | 
| 187 |     QCamera::Features f = QCamera::Feature::ColorTemperature | QCamera::Feature::ExposureCompensation | | 
| 188 |                           QCamera::Feature::IsoSensitivity | QCamera::Feature::ManualExposureTime; | 
| 189 |     supportedFeaturesChanged(f); | 
| 190 | #endif | 
| 191 |  | 
| 192 | } | 
| 193 |  | 
| 194 | #if QT_CONFIG(gstreamer_photography) | 
| 195 | GstPhotography *QGstreamerCamera::photography() const | 
| 196 | { | 
| 197 |     if (gstCamera && GST_IS_PHOTOGRAPHY(gstCamera.element())) | 
| 198 |         return GST_PHOTOGRAPHY(gstCamera.element()); | 
| 199 |     return nullptr; | 
| 200 | } | 
| 201 | #endif | 
| 202 |  | 
| 203 | void QGstreamerCamera::setFocusMode(QCamera::FocusMode mode) | 
| 204 | { | 
| 205 |     if (mode == focusMode()) | 
| 206 |         return; | 
| 207 |  | 
| 208 | #if QT_CONFIG(gstreamer_photography) | 
| 209 |     auto p = photography(); | 
| 210 |     if (p) { | 
| 211 |         GstPhotographyFocusMode photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL; | 
| 212 |  | 
| 213 |         switch (mode) { | 
| 214 |         case QCamera::FocusModeAutoNear: | 
| 215 |             photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MACRO; | 
| 216 |             break; | 
| 217 |         case QCamera::FocusModeAutoFar: | 
| 218 |             // not quite, but hey :) | 
| 219 |             Q_FALLTHROUGH(); | 
| 220 |         case QCamera::FocusModeHyperfocal: | 
| 221 |             photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL; | 
| 222 |             break; | 
| 223 |         case QCamera::FocusModeInfinity: | 
| 224 |             photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY; | 
| 225 |             break; | 
| 226 |         case QCamera::FocusModeManual: | 
| 227 |             photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MANUAL; | 
| 228 |             break; | 
| 229 |         default: // QCamera::FocusModeAuto: | 
| 230 |             break; | 
| 231 |         } | 
| 232 |  | 
| 233 |         if (gst_photography_set_focus_mode(photo: p, mode: photographyMode)) | 
| 234 |             focusModeChanged(mode); | 
| 235 |     } | 
| 236 | #endif | 
| 237 | } | 
| 238 |  | 
| 239 | bool QGstreamerCamera::isFocusModeSupported(QCamera::FocusMode mode) const | 
| 240 | { | 
| 241 | #if QT_CONFIG(gstreamer_photography) | 
| 242 |     if (photography()) | 
| 243 |         return true; | 
| 244 | #endif | 
| 245 |     return mode == QCamera::FocusModeAuto; | 
| 246 | } | 
| 247 |  | 
| 248 | void QGstreamerCamera::setFlashMode(QCamera::FlashMode mode) | 
| 249 | { | 
| 250 |     Q_UNUSED(mode); | 
| 251 |  | 
| 252 | #if QT_CONFIG(gstreamer_photography) | 
| 253 |     if (auto *p = photography()) { | 
| 254 |         GstPhotographyFlashMode flashMode; | 
| 255 |         gst_photography_get_flash_mode(photo: p, flash_mode: &flashMode); | 
| 256 |  | 
| 257 |         switch (mode) { | 
| 258 |         case QCamera::FlashAuto: | 
| 259 |             flashMode = GST_PHOTOGRAPHY_FLASH_MODE_AUTO; | 
| 260 |             break; | 
| 261 |         case QCamera::FlashOff: | 
| 262 |             flashMode = GST_PHOTOGRAPHY_FLASH_MODE_OFF; | 
| 263 |             break; | 
| 264 |         case QCamera::FlashOn: | 
| 265 |             flashMode = GST_PHOTOGRAPHY_FLASH_MODE_ON; | 
| 266 |             break; | 
| 267 |         } | 
| 268 |  | 
| 269 |         if (gst_photography_set_flash_mode(photo: p, flash_mode: flashMode)) | 
| 270 |             flashModeChanged(mode); | 
| 271 |     } | 
| 272 | #endif | 
| 273 | } | 
| 274 |  | 
| 275 | bool QGstreamerCamera::isFlashModeSupported(QCamera::FlashMode mode) const | 
| 276 | { | 
| 277 | #if QT_CONFIG(gstreamer_photography) | 
| 278 |     if (photography()) | 
| 279 |         return true; | 
| 280 | #endif | 
| 281 |  | 
| 282 |     return mode == QCamera::FlashAuto; | 
| 283 | } | 
| 284 |  | 
| 285 | bool QGstreamerCamera::isFlashReady() const | 
| 286 | { | 
| 287 | #if QT_CONFIG(gstreamer_photography) | 
| 288 |     if (photography()) | 
| 289 |         return true; | 
| 290 | #endif | 
| 291 |  | 
| 292 |     return false; | 
| 293 | } | 
| 294 |  | 
| 295 | void QGstreamerCamera::setExposureMode(QCamera::ExposureMode mode) | 
| 296 | { | 
| 297 |     Q_UNUSED(mode); | 
| 298 | #if QT_CONFIG(linux_v4l) | 
| 299 |     if (isV4L2Camera() && v4l2AutoExposureSupported && v4l2ManualExposureSupported) { | 
| 300 |         if (mode != QCamera::ExposureAuto && mode != QCamera::ExposureManual) | 
| 301 |             return; | 
| 302 |         int value = QCamera::ExposureAuto ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL; | 
| 303 |         setV4L2Parameter(V4L2_CID_EXPOSURE_AUTO, value); | 
| 304 |         exposureModeChanged(mode); | 
| 305 |         return; | 
| 306 |     } | 
| 307 | #endif | 
| 308 |  | 
| 309 | #if QT_CONFIG(gstreamer_photography) | 
| 310 |     auto *p = photography(); | 
| 311 |     if (!p) | 
| 312 |         return; | 
| 313 |  | 
| 314 |     GstPhotographySceneMode sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO; | 
| 315 |  | 
| 316 |     switch (mode) { | 
| 317 |     case QCamera::ExposureManual: | 
| 318 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_MANUAL; | 
| 319 |         break; | 
| 320 |     case QCamera::ExposurePortrait: | 
| 321 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT; | 
| 322 |         break; | 
| 323 |     case QCamera::ExposureSports: | 
| 324 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SPORT; | 
| 325 |         break; | 
| 326 |     case QCamera::ExposureNight: | 
| 327 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT; | 
| 328 |         break; | 
| 329 |     case QCamera::ExposureAuto: | 
| 330 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO; | 
| 331 |         break; | 
| 332 |     case QCamera::ExposureLandscape: | 
| 333 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE; | 
| 334 |         break; | 
| 335 |     case QCamera::ExposureSnow: | 
| 336 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SNOW; | 
| 337 |         break; | 
| 338 |     case QCamera::ExposureBeach: | 
| 339 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BEACH; | 
| 340 |         break; | 
| 341 |     case QCamera::ExposureAction: | 
| 342 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_ACTION; | 
| 343 |         break; | 
| 344 |     case QCamera::ExposureNightPortrait: | 
| 345 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT; | 
| 346 |         break; | 
| 347 |     case QCamera::ExposureTheatre: | 
| 348 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_THEATRE; | 
| 349 |         break; | 
| 350 |     case QCamera::ExposureSunset: | 
| 351 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SUNSET; | 
| 352 |         break; | 
| 353 |     case QCamera::ExposureSteadyPhoto: | 
| 354 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO; | 
| 355 |         break; | 
| 356 |     case QCamera::ExposureFireworks: | 
| 357 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS; | 
| 358 |         break; | 
| 359 |     case QCamera::ExposureParty: | 
| 360 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PARTY; | 
| 361 |         break; | 
| 362 |     case QCamera::ExposureCandlelight: | 
| 363 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT; | 
| 364 |         break; | 
| 365 |     case QCamera::ExposureBarcode: | 
| 366 |         sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BARCODE; | 
| 367 |         break; | 
| 368 |     default: | 
| 369 |         return; | 
| 370 |     } | 
| 371 |  | 
| 372 |     if (gst_photography_set_scene_mode(photo: p, scene_mode: sceneMode)) | 
| 373 |         exposureModeChanged(mode); | 
| 374 | #endif | 
| 375 | } | 
| 376 |  | 
| 377 | bool QGstreamerCamera::isExposureModeSupported(QCamera::ExposureMode mode) const | 
| 378 | { | 
| 379 |     if (mode == QCamera::ExposureAuto) | 
| 380 |         return true; | 
| 381 | #if QT_CONFIG(linux_v4l) | 
| 382 |     if (isV4L2Camera() && v4l2ManualExposureSupported && v4l2AutoExposureSupported) | 
| 383 |         return mode == QCamera::ExposureManual; | 
| 384 | #endif | 
| 385 | #if QT_CONFIG(gstreamer_photography) | 
| 386 |     if (photography()) | 
| 387 |         return true; | 
| 388 | #endif | 
| 389 |  | 
| 390 |     return false; | 
| 391 | } | 
| 392 |  | 
| 393 | void QGstreamerCamera::setExposureCompensation(float compensation) | 
| 394 | { | 
| 395 |     Q_UNUSED(compensation); | 
| 396 | #if QT_CONFIG(linux_v4l) | 
| 397 |     if (isV4L2Camera() && (v4l2MinExposureAdjustment != 0 || v4l2MaxExposureAdjustment != 0)) { | 
| 398 |         int value = qBound(min: v4l2MinExposureAdjustment, val: (int)(compensation*1000), max: v4l2MaxExposureAdjustment); | 
| 399 |         setV4L2Parameter(V4L2_CID_AUTO_EXPOSURE_BIAS, value); | 
| 400 |         exposureCompensationChanged(compensation: value/1000.); | 
| 401 |         return; | 
| 402 |     } | 
| 403 | #endif | 
| 404 |  | 
| 405 | #if QT_CONFIG(gstreamer_photography) | 
| 406 |     if (auto *p = photography()) { | 
| 407 |         if (gst_photography_set_ev_compensation(photo: p, ev_comp: compensation)) | 
| 408 |             exposureCompensationChanged(compensation); | 
| 409 |     } | 
| 410 | #endif | 
| 411 | } | 
| 412 |  | 
| 413 | void QGstreamerCamera::setManualIsoSensitivity(int iso) | 
| 414 | { | 
| 415 |     Q_UNUSED(iso); | 
| 416 | #if QT_CONFIG(linux_v4l) | 
| 417 |     if (isV4L2Camera()) { | 
| 418 |         if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity)) | 
| 419 |             return; | 
| 420 |         setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY_AUTO, value: iso <= 0 ? V4L2_ISO_SENSITIVITY_AUTO : V4L2_ISO_SENSITIVITY_MANUAL); | 
| 421 |         if (iso > 0) { | 
| 422 |             iso = qBound(min: minIso(), val: iso, max: maxIso()); | 
| 423 |             setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY, value: iso); | 
| 424 |         } | 
| 425 |         return; | 
| 426 |     } | 
| 427 | #endif | 
| 428 | #if QT_CONFIG(gstreamer_photography) | 
| 429 |     if (auto *p = photography()) { | 
| 430 |         if (gst_photography_set_iso_speed(photo: p, iso_speed: iso)) | 
| 431 |             isoSensitivityChanged(iso); | 
| 432 |     } | 
| 433 | #endif | 
| 434 | } | 
| 435 |  | 
| 436 | int QGstreamerCamera::isoSensitivity() const | 
| 437 | { | 
| 438 | #if QT_CONFIG(linux_v4l) | 
| 439 |     if (isV4L2Camera()) { | 
| 440 |         if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity)) | 
| 441 |             return -1; | 
| 442 |         return getV4L2Parameter(V4L2_CID_ISO_SENSITIVITY); | 
| 443 |     } | 
| 444 | #endif | 
| 445 | #if QT_CONFIG(gstreamer_photography) | 
| 446 |     if (auto *p = photography()) { | 
| 447 |         guint speed = 0; | 
| 448 |         if (gst_photography_get_iso_speed(photo: p, iso_speed: &speed)) | 
| 449 |             return speed; | 
| 450 |     } | 
| 451 | #endif | 
| 452 |     return 100; | 
| 453 | } | 
| 454 |  | 
| 455 | void QGstreamerCamera::setManualExposureTime(float secs) | 
| 456 | { | 
| 457 |     Q_UNUSED(secs); | 
| 458 | #if QT_CONFIG(linux_v4l) | 
| 459 |     if (isV4L2Camera() && v4l2ManualExposureSupported && v4l2AutoExposureSupported) { | 
| 460 |         int exposure = qBound(min: v4l2MinExposure, val: qRound(d: secs*10000.), max: v4l2MaxExposure); | 
| 461 |         setV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE, value: exposure); | 
| 462 |         exposureTimeChanged(speed: exposure/10000.); | 
| 463 |         return; | 
| 464 |     } | 
| 465 | #endif | 
| 466 |  | 
| 467 | #if QT_CONFIG(gstreamer_photography) | 
| 468 |     if (auto *p = photography()) { | 
| 469 |         if (gst_photography_set_exposure(photo: p, exposure: guint(secs*1000000))) | 
| 470 |             exposureTimeChanged(speed: secs); | 
| 471 |     } | 
| 472 | #endif | 
| 473 | } | 
| 474 |  | 
| 475 | float QGstreamerCamera::exposureTime() const | 
| 476 | { | 
| 477 | #if QT_CONFIG(linux_v4l) | 
| 478 |     if (isV4L2Camera()) { | 
| 479 |         return getV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE)/10000.; | 
| 480 |     } | 
| 481 | #endif | 
| 482 | #if QT_CONFIG(gstreamer_photography) | 
| 483 |     if (auto *p = photography()) { | 
| 484 |         guint32 exposure = 0; | 
| 485 |         if (gst_photography_get_exposure(photo: p, exposure: &exposure)) | 
| 486 |             return exposure/1000000.; | 
| 487 |     } | 
| 488 | #endif | 
| 489 |     return -1; | 
| 490 | } | 
| 491 |  | 
| 492 | bool QGstreamerCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const | 
| 493 | { | 
| 494 |     if (mode == QCamera::WhiteBalanceAuto) | 
| 495 |         return true; | 
| 496 |  | 
| 497 | #if QT_CONFIG(linux_v4l) | 
| 498 |     if (isV4L2Camera()) { | 
| 499 |         if (v4l2AutoWhiteBalanceSupported && v4l2ColorTemperatureSupported) | 
| 500 |             return true; | 
| 501 |     } | 
| 502 | #endif | 
| 503 | #if QT_CONFIG(gstreamer_photography) | 
| 504 |     if (auto *p = photography()) { | 
| 505 |         Q_UNUSED(p); | 
| 506 |         switch (mode) { | 
| 507 |         case QCamera::WhiteBalanceAuto: | 
| 508 |         case QCamera::WhiteBalanceSunlight: | 
| 509 |         case QCamera::WhiteBalanceCloudy: | 
| 510 |         case QCamera::WhiteBalanceShade: | 
| 511 |         case QCamera::WhiteBalanceSunset: | 
| 512 |         case QCamera::WhiteBalanceTungsten: | 
| 513 |         case QCamera::WhiteBalanceFluorescent: | 
| 514 |             return true; | 
| 515 |         case QCamera::WhiteBalanceManual: { | 
| 516 |             GstPhotographyInterface *iface = GST_PHOTOGRAPHY_GET_INTERFACE(p); | 
| 517 |             if (iface->set_color_temperature && iface->get_color_temperature) | 
| 518 |                 return true; | 
| 519 |             break; | 
| 520 |         } | 
| 521 |         default: | 
| 522 |             break; | 
| 523 |         } | 
| 524 |     } | 
| 525 | #endif | 
| 526 |  | 
| 527 |     return mode == QCamera::WhiteBalanceAuto; | 
| 528 | } | 
| 529 |  | 
| 530 | void QGstreamerCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) | 
| 531 | { | 
| 532 |     Q_ASSERT(isWhiteBalanceModeSupported(mode)); | 
| 533 |  | 
| 534 | #if QT_CONFIG(linux_v4l) | 
| 535 |     if (isV4L2Camera()) { | 
| 536 |         int temperature = colorTemperatureForWhiteBalance(mode); | 
| 537 |         int t = setV4L2ColorTemperature(temperature); | 
| 538 |         if (t == 0) | 
| 539 |             mode = QCamera::WhiteBalanceAuto; | 
| 540 |         whiteBalanceModeChanged(mode); | 
| 541 |         return; | 
| 542 |     } | 
| 543 | #endif | 
| 544 |  | 
| 545 | #if QT_CONFIG(gstreamer_photography) | 
| 546 |     if (auto *p = photography()) { | 
| 547 |         GstPhotographyWhiteBalanceMode gstMode = GST_PHOTOGRAPHY_WB_MODE_AUTO; | 
| 548 |         switch (mode) { | 
| 549 |         case QCamera::WhiteBalanceSunlight: | 
| 550 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT; | 
| 551 |             break; | 
| 552 |         case QCamera::WhiteBalanceCloudy: | 
| 553 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_CLOUDY; | 
| 554 |             break; | 
| 555 |         case QCamera::WhiteBalanceShade: | 
| 556 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_SHADE; | 
| 557 |             break; | 
| 558 |         case QCamera::WhiteBalanceSunset: | 
| 559 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_SUNSET; | 
| 560 |             break; | 
| 561 |         case QCamera::WhiteBalanceTungsten: | 
| 562 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN; | 
| 563 |             break; | 
| 564 |         case QCamera::WhiteBalanceFluorescent: | 
| 565 |             gstMode = GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT; | 
| 566 |             break; | 
| 567 |         case QCamera::WhiteBalanceAuto: | 
| 568 |         default: | 
| 569 |             break; | 
| 570 |         } | 
| 571 |         if (gst_photography_set_white_balance_mode(photo: p, wb_mode: gstMode)) { | 
| 572 |             whiteBalanceModeChanged(mode); | 
| 573 |             return; | 
| 574 |         } | 
| 575 |     } | 
| 576 | #endif | 
| 577 | } | 
| 578 |  | 
| 579 | void QGstreamerCamera::setColorTemperature(int temperature) | 
| 580 | { | 
| 581 |     if (temperature == 0) { | 
| 582 |         setWhiteBalanceMode(QCamera::WhiteBalanceAuto); | 
| 583 |         return; | 
| 584 |     } | 
| 585 |  | 
| 586 |     Q_ASSERT(isWhiteBalanceModeSupported(QCamera::WhiteBalanceManual)); | 
| 587 |  | 
| 588 | #if QT_CONFIG(linux_v4l) | 
| 589 |     if (isV4L2Camera()) { | 
| 590 |         int t = setV4L2ColorTemperature(temperature); | 
| 591 |         if (t) | 
| 592 |             colorTemperatureChanged(temperature: t); | 
| 593 |         return; | 
| 594 |     } | 
| 595 | #endif | 
| 596 |  | 
| 597 | #if QT_CONFIG(gstreamer_photography) | 
| 598 |     if (auto *p = photography()) { | 
| 599 |         GstPhotographyInterface *iface = GST_PHOTOGRAPHY_GET_INTERFACE(p); | 
| 600 |         Q_ASSERT(iface->set_color_temperature); | 
| 601 |         iface->set_color_temperature(p, temperature); | 
| 602 |         return; | 
| 603 |     } | 
| 604 | #endif | 
| 605 | } | 
| 606 |  | 
| 607 | #if QT_CONFIG(linux_v4l) | 
| 608 | bool QGstreamerCamera::isV4L2Camera() const | 
| 609 | { | 
| 610 |     return !m_v4l2DevicePath.isEmpty(); | 
| 611 | } | 
| 612 |  | 
| 613 | void QGstreamerCamera::initV4L2Controls() | 
| 614 | { | 
| 615 |     v4l2AutoWhiteBalanceSupported = false; | 
| 616 |     v4l2ColorTemperatureSupported = false; | 
| 617 |     QCamera::Features features{}; | 
| 618 |  | 
| 619 |     Q_ASSERT(!m_v4l2DevicePath.isEmpty()); | 
| 620 |  | 
| 621 |  | 
| 622 |     withV4L2DeviceFileDescriptor(f: [&](int fd) { | 
| 623 |         struct v4l2_queryctrl queryControl = {}; | 
| 624 |         queryControl.id = V4L2_CID_AUTO_WHITE_BALANCE; | 
| 625 |  | 
| 626 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 627 |             v4l2AutoWhiteBalanceSupported = true; | 
| 628 |             setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, value: true); | 
| 629 |         } | 
| 630 |  | 
| 631 |         queryControl = {}; | 
| 632 |         queryControl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; | 
| 633 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 634 |             v4l2MinColorTemp = queryControl.minimum; | 
| 635 |             v4l2MaxColorTemp = queryControl.maximum; | 
| 636 |             v4l2ColorTemperatureSupported = true; | 
| 637 |             features |= QCamera::Feature::ColorTemperature; | 
| 638 |         } | 
| 639 |  | 
| 640 |         queryControl = {}; | 
| 641 |         queryControl.id = V4L2_CID_EXPOSURE_AUTO; | 
| 642 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 643 |             v4l2AutoExposureSupported = true; | 
| 644 |         } | 
| 645 |  | 
| 646 |         queryControl = {}; | 
| 647 |         queryControl.id = V4L2_CID_EXPOSURE_ABSOLUTE; | 
| 648 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 649 |             v4l2ManualExposureSupported = true; | 
| 650 |             v4l2MinExposure = queryControl.minimum; | 
| 651 |             v4l2MaxExposure = queryControl.maximum; | 
| 652 |             features |= QCamera::Feature::ManualExposureTime; | 
| 653 |         } | 
| 654 |  | 
| 655 |         queryControl = {}; | 
| 656 |         queryControl.id = V4L2_CID_AUTO_EXPOSURE_BIAS; | 
| 657 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 658 |             v4l2MinExposureAdjustment = queryControl.minimum; | 
| 659 |             v4l2MaxExposureAdjustment = queryControl.maximum; | 
| 660 |             features |= QCamera::Feature::ExposureCompensation; | 
| 661 |         } | 
| 662 |  | 
| 663 |         queryControl = {}; | 
| 664 |         queryControl.id = V4L2_CID_ISO_SENSITIVITY_AUTO; | 
| 665 |         if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 666 |             queryControl.id = V4L2_CID_ISO_SENSITIVITY; | 
| 667 |             if (::ioctl(fd: fd, VIDIOC_QUERYCTRL, &queryControl) == 0) { | 
| 668 |                 features |= QCamera::Feature::IsoSensitivity; | 
| 669 |                 minIsoChanged(iso: queryControl.minimum); | 
| 670 |                 maxIsoChanged(iso: queryControl.minimum); | 
| 671 |             } | 
| 672 |         } | 
| 673 |     }); | 
| 674 |  | 
| 675 |     supportedFeaturesChanged(features); | 
| 676 | } | 
| 677 |  | 
| 678 | int QGstreamerCamera::setV4L2ColorTemperature(int temperature) | 
| 679 | { | 
| 680 |     if (v4l2AutoWhiteBalanceSupported) { | 
| 681 |         setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, value: temperature == 0 ? true : false); | 
| 682 |     } else if (temperature == 0) { | 
| 683 |         temperature = 5600; | 
| 684 |     } | 
| 685 |  | 
| 686 |     if (temperature != 0 && v4l2ColorTemperatureSupported) { | 
| 687 |         temperature = qBound(min: v4l2MinColorTemp, val: temperature, max: v4l2MaxColorTemp); | 
| 688 |         if (!setV4L2Parameter(V4L2_CID_WHITE_BALANCE_TEMPERATURE, value: qBound(min: v4l2MinColorTemp, val: temperature, max: v4l2MaxColorTemp))) | 
| 689 |             temperature = 0; | 
| 690 |     } else { | 
| 691 |         temperature = 0; | 
| 692 |     } | 
| 693 |  | 
| 694 |     return temperature; | 
| 695 | } | 
| 696 |  | 
| 697 | bool QGstreamerCamera::setV4L2Parameter(quint32 id, qint32 value) | 
| 698 | { | 
| 699 |     return withV4L2DeviceFileDescriptor(f: [&](int fd) { | 
| 700 |         v4l2_control control{ .id: id, .value: value }; | 
| 701 |         if (::ioctl(fd: fd, VIDIOC_S_CTRL, &control) != 0) { | 
| 702 |             qWarning() << "Unable to set the V4L2 Parameter"  << Qt::hex << id << "to"  << value | 
| 703 |                        << qt_error_string(errno); | 
| 704 |             return false; | 
| 705 |         } | 
| 706 |         return true; | 
| 707 |     }); | 
| 708 | } | 
| 709 |  | 
| 710 | int QGstreamerCamera::getV4L2Parameter(quint32 id) const | 
| 711 | { | 
| 712 |     return withV4L2DeviceFileDescriptor(f: [&](int fd) { | 
| 713 |         v4l2_control control{ .id: id, .value: 0 }; | 
| 714 |         if (::ioctl(fd: fd, VIDIOC_G_CTRL, &control) != 0) { | 
| 715 |             qWarning() << "Unable to get the V4L2 Parameter"  << Qt::hex << id | 
| 716 |                        << qt_error_string(errno); | 
| 717 |             return 0; | 
| 718 |         } | 
| 719 |         return control.value; | 
| 720 |     }); | 
| 721 | } | 
| 722 |  | 
| 723 | #endif // QT_CONFIG(linux_v4l) | 
| 724 |  | 
| 725 | QGstreamerCustomCamera::QGstreamerCustomCamera(QCamera *camera) | 
| 726 |     : QGstreamerCameraBase{ | 
| 727 |           camera, | 
| 728 |       }, | 
| 729 |       m_userProvidedGstElement{ | 
| 730 |           false, | 
| 731 |       } | 
| 732 | { | 
| 733 | } | 
| 734 |  | 
| 735 | QGstreamerCustomCamera::QGstreamerCustomCamera(QCamera *camera, QGstElement element) | 
| 736 |     : QGstreamerCameraBase{ | 
| 737 |           camera, | 
| 738 |       }, | 
| 739 |       gstCamera{ | 
| 740 |           std::move(element), | 
| 741 |       }, | 
| 742 |       m_userProvidedGstElement{ | 
| 743 |           true, | 
| 744 |       } | 
| 745 | { | 
| 746 | } | 
| 747 |  | 
| 748 | void QGstreamerCustomCamera::setCamera(const QCameraDevice &device) | 
| 749 | { | 
| 750 |     if (m_userProvidedGstElement) | 
| 751 |         return; | 
| 752 |  | 
| 753 |     gstCamera = QGstBin::createFromPipelineDescription(pipelineDescription: device.id(), /*name=*/nullptr, | 
| 754 |                                                        /* ghostUnlinkedPads=*/true); | 
| 755 | } | 
| 756 |  | 
| 757 | bool QGstreamerCustomCamera::isActive() const | 
| 758 | { | 
| 759 |     return m_active; | 
| 760 | } | 
| 761 |  | 
| 762 | void QGstreamerCustomCamera::setActive(bool active) | 
| 763 | { | 
| 764 |     if (m_active == active) | 
| 765 |         return; | 
| 766 |  | 
| 767 |     m_active = active; | 
| 768 |  | 
| 769 |     emit activeChanged(active); | 
| 770 | } | 
| 771 |  | 
| 772 | QT_END_NAMESPACE | 
| 773 |  |