| 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 | q23::expected<QPlatformCamera *, QString> 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 q23::unexpected{ *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 | |