// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <qcameradevice.h>

#include "qgstreamercamera_p.h"
#include "qgstreamerimagecapture_p.h"
#include <qgstreamervideodevices_p.h>
#include <qgstreamerintegration_p.h>
#include <qmediacapturesession.h>

#if QT_CONFIG(linux_v4l)
#include <linux/videodev2.h>
#include <private/qcore_unix_p.h>
#endif

#include <QtCore/qdebug.h>

QT_BEGIN_NAMESPACE

QMaybe<QPlatformCamera *> QGstreamerCamera::create(QCamera *camera)
{
    QGstElement videotestsrc("videotestsrc");
    if (!videotestsrc)
        return errorMessageCannotFindElement("videotestsrc");

    QGstElement capsFilter("capsfilter", "videoCapsFilter");
    if (!capsFilter)
        return errorMessageCannotFindElement("capsfilter");

    QGstElement videoconvert("videoconvert", "videoConvert");
    if (!videoconvert)
        return errorMessageCannotFindElement("videoconvert");

    QGstElement videoscale("videoscale", "videoScale");
    if (!videoscale)
        return errorMessageCannotFindElement("videoscale");

    return new QGstreamerCamera(videotestsrc, capsFilter, videoconvert, videoscale, camera);
}

QGstreamerCamera::QGstreamerCamera(QGstElement videotestsrc, QGstElement capsFilter,
                                   QGstElement videoconvert, QGstElement videoscale,
                                   QCamera *camera)
    : QPlatformCamera(camera),
      gstCamera(std::move(videotestsrc)),
      gstCapsFilter(std::move(capsFilter)),
      gstVideoConvert(std::move(videoconvert)),
      gstVideoScale(std::move(videoscale))
{
    gstDecode = QGstElement("identity");
    gstCameraBin = QGstBin("camerabin");
    gstCameraBin.add(gstCamera, gstCapsFilter, gstDecode, gstVideoConvert, gstVideoScale);
    gstCamera.link(gstCapsFilter, gstDecode, gstVideoConvert, gstVideoScale);
    gstCameraBin.addGhostPad(gstVideoScale, "src");
}

QGstreamerCamera::~QGstreamerCamera()
{
#if QT_CONFIG(linux_v4l)
    if (v4l2FileDescriptor >= 0)
        qt_safe_close(v4l2FileDescriptor);
    v4l2FileDescriptor = -1;
#endif
    gstCameraBin.setStateSync(GST_STATE_NULL);
}

bool QGstreamerCamera::isActive() const
{
    return m_active;
}

void QGstreamerCamera::setActive(bool active)
{
    if (m_active == active)
        return;
    if (m_cameraDevice.isNull() && active)
        return;

    m_active = active;

    emit activeChanged(active);
}

void QGstreamerCamera::setCamera(const QCameraDevice &camera)
{
    if (m_cameraDevice == camera)
        return;

    m_cameraDevice = camera;

    QGstElement gstNewCamera;
    if (camera.isNull()) {
        gstNewCamera = QGstElement("videotestsrc");
    } else {
        auto *integration = static_cast<QGstreamerIntegration *>(QGstreamerIntegration::instance());
        auto *device = integration->videoDevice(camera.id());
        gstNewCamera = gst_device_create_element(device, "camerasrc");
        if (QGstStructure properties = gst_device_get_properties(device); !properties.isNull()) {
            if (properties.name() == "v4l2deviceprovider")
                m_v4l2Device = QString::fromUtf8(properties["device.path"].toString());
            properties.free();
        }
    }

    QCameraFormat f = findBestCameraFormat(camera);
    auto caps = QGstCaps::fromCameraFormat(f);
    auto gstNewDecode = QGstElement(f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec" : "identity");

    gstCamera.unlink(gstCapsFilter);
    gstCapsFilter.unlink(gstDecode);
    gstDecode.unlink(gstVideoConvert);

    gstCameraBin.remove(gstCamera);
    gstCameraBin.remove(gstDecode);

    gstCamera.setStateSync(GST_STATE_NULL);
    gstDecode.setStateSync(GST_STATE_NULL);

    gstCapsFilter.set("caps", caps);

    gstCameraBin.add(gstNewCamera, gstNewDecode);

    gstNewDecode.link(gstVideoConvert);
    gstCapsFilter.link(gstNewDecode);

    if (!gstNewCamera.link(gstCapsFilter))
        qWarning() << "linking camera failed" << gstCamera.name() << caps.toString();

    // Start sending frames once pipeline is linked
    // FIXME: put camera to READY state before linking to decoder as in the NULL state it does not know its true caps
    gstCapsFilter.syncStateWithParent();
    gstNewDecode.syncStateWithParent();
    gstNewCamera.syncStateWithParent();

    gstCamera = gstNewCamera;
    gstDecode = gstNewDecode;

    updateCameraProperties();
}

bool QGstreamerCamera::setCameraFormat(const QCameraFormat &format)
{
    if (!format.isNull() && !m_cameraDevice.videoFormats().contains(format))
        return false;

    QCameraFormat f = format;
    if (f.isNull())
        f = findBestCameraFormat(m_cameraDevice);

    auto caps = QGstCaps::fromCameraFormat(f);

    auto newGstDecode = QGstElement(f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec" : "identity");
    gstCameraBin.add(newGstDecode);
    newGstDecode.syncStateWithParent();

    gstCamera.staticPad("src").doInIdleProbe([&](){
        gstCamera.unlink(gstCapsFilter);
        gstCapsFilter.unlink(gstDecode);
        gstDecode.unlink(gstVideoConvert);

        gstCapsFilter.set("caps", caps);

        newGstDecode.link(gstVideoConvert);
        gstCapsFilter.link(newGstDecode);
        if (!gstCamera.link(gstCapsFilter))
            qWarning() << "linking filtered camera to decoder failed" << gstCamera.name() << caps.toString();
    });

    gstCameraBin.remove(gstDecode);
    gstDecode.setStateSync(GST_STATE_NULL);

    gstDecode = newGstDecode;

    return true;
}

void QGstreamerCamera::updateCameraProperties()
{
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        initV4L2Controls();
        return;
    }
#endif
#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography())
        gst_photography_set_white_balance_mode(p, GST_PHOTOGRAPHY_WB_MODE_AUTO);
    QCamera::Features f = QCamera::Feature::ColorTemperature | QCamera::Feature::ExposureCompensation |
                          QCamera::Feature::IsoSensitivity | QCamera::Feature::ManualExposureTime;
    supportedFeaturesChanged(f);
#endif

}

#if QT_CONFIG(gstreamer_photography)
GstPhotography *QGstreamerCamera::photography() const
{
    if (!gstCamera.isNull() && GST_IS_PHOTOGRAPHY(gstCamera.element()))
        return GST_PHOTOGRAPHY(gstCamera.element());
    return nullptr;
}
#endif

void QGstreamerCamera::setFocusMode(QCamera::FocusMode mode)
{
    if (mode == focusMode())
        return;

#if QT_CONFIG(gstreamer_photography)
    auto p = photography();
    if (p) {
        GstPhotographyFocusMode photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL;

        switch (mode) {
        case QCamera::FocusModeAutoNear:
            photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MACRO;
            break;
        case QCamera::FocusModeAutoFar:
            // not quite, but hey :)
            Q_FALLTHROUGH();
        case QCamera::FocusModeHyperfocal:
            photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL;
            break;
        case QCamera::FocusModeInfinity:
            photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY;
            break;
        case QCamera::FocusModeManual:
            photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MANUAL;
            break;
        default: // QCamera::FocusModeAuto:
            break;
        }

        if (gst_photography_set_focus_mode(p, photographyMode))
            focusModeChanged(mode);
    }
#endif
}

bool QGstreamerCamera::isFocusModeSupported(QCamera::FocusMode mode) const
{
#if QT_CONFIG(gstreamer_photography)
    if (photography())
        return true;
#endif
    return mode == QCamera::FocusModeAuto;
}

void QGstreamerCamera::setFlashMode(QCamera::FlashMode mode)
{
    Q_UNUSED(mode);

#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        GstPhotographyFlashMode flashMode;
        gst_photography_get_flash_mode(p, &flashMode);

        switch (mode) {
        case QCamera::FlashAuto:
            flashMode = GST_PHOTOGRAPHY_FLASH_MODE_AUTO;
            break;
        case QCamera::FlashOff:
            flashMode = GST_PHOTOGRAPHY_FLASH_MODE_OFF;
            break;
        case QCamera::FlashOn:
            flashMode = GST_PHOTOGRAPHY_FLASH_MODE_ON;
            break;
        }

        if (gst_photography_set_flash_mode(p, flashMode))
            flashModeChanged(mode);
    }
#endif
}

bool QGstreamerCamera::isFlashModeSupported(QCamera::FlashMode mode) const
{
#if QT_CONFIG(gstreamer_photography)
    if (photography())
        return true;
#endif

    return mode == QCamera::FlashAuto;
}

bool QGstreamerCamera::isFlashReady() const
{
#if QT_CONFIG(gstreamer_photography)
    if (photography())
        return true;
#endif

    return false;
}

void QGstreamerCamera::setExposureMode(QCamera::ExposureMode mode)
{
    Q_UNUSED(mode);
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera() && v4l2AutoExposureSupported && v4l2ManualExposureSupported) {
        if (mode != QCamera::ExposureAuto && mode != QCamera::ExposureManual)
            return;
        int value = QCamera::ExposureAuto ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL;
        setV4L2Parameter(V4L2_CID_EXPOSURE_AUTO, value);
        exposureModeChanged(mode);
        return;
    }
#endif

#if QT_CONFIG(gstreamer_photography)
    auto *p = photography();
    if (!p)
        return;

    GstPhotographySceneMode sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO;

    switch (mode) {
    case QCamera::ExposureManual:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_MANUAL;
        break;
    case QCamera::ExposurePortrait:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT;
        break;
    case QCamera::ExposureSports:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SPORT;
        break;
    case QCamera::ExposureNight:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT;
        break;
    case QCamera::ExposureAuto:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO;
        break;
    case QCamera::ExposureLandscape:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE;
        break;
    case QCamera::ExposureSnow:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SNOW;
        break;
    case QCamera::ExposureBeach:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BEACH;
        break;
    case QCamera::ExposureAction:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_ACTION;
        break;
    case QCamera::ExposureNightPortrait:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT;
        break;
    case QCamera::ExposureTheatre:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_THEATRE;
        break;
    case QCamera::ExposureSunset:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SUNSET;
        break;
    case QCamera::ExposureSteadyPhoto:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO;
        break;
    case QCamera::ExposureFireworks:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS;
        break;
    case QCamera::ExposureParty:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PARTY;
        break;
    case QCamera::ExposureCandlelight:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT;
        break;
    case QCamera::ExposureBarcode:
        sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BARCODE;
        break;
    default:
        return;
    }

    if (gst_photography_set_scene_mode(p, sceneMode))
        exposureModeChanged(mode);
#endif
}

bool QGstreamerCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
{
    if (mode == QCamera::ExposureAuto)
        return true;
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera() && v4l2ManualExposureSupported && v4l2AutoExposureSupported)
        return mode == QCamera::ExposureManual;
#endif
#if QT_CONFIG(gstreamer_photography)
    if (photography())
        return true;
#endif

    return false;
}

void QGstreamerCamera::setExposureCompensation(float compensation)
{
    Q_UNUSED(compensation);
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera() && (v4l2MinExposureAdjustment != 0 || v4l2MaxExposureAdjustment != 0)) {
        int value = qBound(v4l2MinExposureAdjustment, (int)(compensation*1000), v4l2MaxExposureAdjustment);
        setV4L2Parameter(V4L2_CID_AUTO_EXPOSURE_BIAS, value);
        exposureCompensationChanged(value/1000.);
        return;
    }
#endif

#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        if (gst_photography_set_ev_compensation(p, compensation))
            exposureCompensationChanged(compensation);
    }
#endif
}

void QGstreamerCamera::setManualIsoSensitivity(int iso)
{
    Q_UNUSED(iso);
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity))
            return;
        setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY_AUTO, iso <= 0 ? V4L2_ISO_SENSITIVITY_AUTO : V4L2_ISO_SENSITIVITY_MANUAL);
        if (iso > 0) {
            iso = qBound(minIso(), iso, maxIso());
            setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY, iso);
        }
        return;
    }
#endif
#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        if (gst_photography_set_iso_speed(p, iso))
            isoSensitivityChanged(iso);
    }
#endif
}

int QGstreamerCamera::isoSensitivity() const
{
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity))
            return -1;
        return getV4L2Parameter(V4L2_CID_ISO_SENSITIVITY);
    }
#endif
#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        guint speed = 0;
        if (gst_photography_get_iso_speed(p, &speed))
            return speed;
    }
#endif
    return 100;
}

void QGstreamerCamera::setManualExposureTime(float secs)
{
    Q_UNUSED(secs);
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera() && v4l2ManualExposureSupported && v4l2AutoExposureSupported) {
        int exposure = qBound(v4l2MinExposure, qRound(secs*10000.), v4l2MaxExposure);
        setV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE, exposure);
        exposureTimeChanged(exposure/10000.);
        return;
    }
#endif

#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        if (gst_photography_set_exposure(p, guint(secs*1000000)))
            exposureTimeChanged(secs);
    }
#endif
}

float QGstreamerCamera::exposureTime() const
{
#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        return getV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE)/10000.;
    }
#endif
#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        guint32 exposure = 0;
        if (gst_photography_get_exposure(p, &exposure))
            return exposure/1000000.;
    }
#endif
    return -1;
}

bool QGstreamerCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
{
    if (mode == QCamera::WhiteBalanceAuto)
        return true;

#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        if (v4l2AutoWhiteBalanceSupported && v4l2ColorTemperatureSupported)
            return true;
    }
#endif
#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        Q_UNUSED(p);
        switch (mode) {
        case QCamera::WhiteBalanceAuto:
        case QCamera::WhiteBalanceSunlight:
        case QCamera::WhiteBalanceCloudy:
        case QCamera::WhiteBalanceShade:
        case QCamera::WhiteBalanceSunset:
        case QCamera::WhiteBalanceTungsten:
        case QCamera::WhiteBalanceFluorescent:
            return true;
        case QCamera::WhiteBalanceManual: {
#if GST_CHECK_VERSION(1, 18, 0)
            GstPhotographyInterface *iface = GST_PHOTOGRAPHY_GET_INTERFACE(p);
            if (iface->set_color_temperature && iface->get_color_temperature)
                return true;
#endif
            break;
        }
        default:
            break;
        }
    }
#endif

    return mode == QCamera::WhiteBalanceAuto;
}

void QGstreamerCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
{
    Q_ASSERT(isWhiteBalanceModeSupported(mode));

#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        int temperature = colorTemperatureForWhiteBalance(mode);
        int t = setV4L2ColorTemperature(temperature);
        if (t == 0)
            mode = QCamera::WhiteBalanceAuto;
        whiteBalanceModeChanged(mode);
        return;
    }
#endif

#if QT_CONFIG(gstreamer_photography)
    if (auto *p = photography()) {
        GstPhotographyWhiteBalanceMode gstMode = GST_PHOTOGRAPHY_WB_MODE_AUTO;
        switch (mode) {
        case QCamera::WhiteBalanceSunlight:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT;
            break;
        case QCamera::WhiteBalanceCloudy:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_CLOUDY;
            break;
        case QCamera::WhiteBalanceShade:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_SHADE;
            break;
        case QCamera::WhiteBalanceSunset:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_SUNSET;
            break;
        case QCamera::WhiteBalanceTungsten:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN;
            break;
        case QCamera::WhiteBalanceFluorescent:
            gstMode = GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT;
            break;
        case QCamera::WhiteBalanceAuto:
        default:
            break;
        }
        if (gst_photography_set_white_balance_mode(p, gstMode)) {
            whiteBalanceModeChanged(mode);
            return;
        }
    }
#endif
}

void QGstreamerCamera::setColorTemperature(int temperature)
{
    if (temperature == 0) {
        setWhiteBalanceMode(QCamera::WhiteBalanceAuto);
        return;
    }

    Q_ASSERT(isWhiteBalanceModeSupported(QCamera::WhiteBalanceManual));

#if QT_CONFIG(linux_v4l)
    if (isV4L2Camera()) {
        int t = setV4L2ColorTemperature(temperature);
        if (t)
            colorTemperatureChanged(t);
        return;
    }
#endif

#if QT_CONFIG(gstreamer_photography) && GST_CHECK_VERSION(1, 18, 0)
    if (auto *p = photography()) {
        GstPhotographyInterface *iface = GST_PHOTOGRAPHY_GET_INTERFACE(p);
        Q_ASSERT(iface->set_color_temperature);
        iface->set_color_temperature(p, temperature);
        return;
    }
#endif
}

#if QT_CONFIG(linux_v4l)
void QGstreamerCamera::initV4L2Controls()
{
    v4l2AutoWhiteBalanceSupported = false;
    v4l2ColorTemperatureSupported = false;
    QCamera::Features features;


    const QString deviceName = v4l2Device();
    Q_ASSERT(!deviceName.isEmpty());

    v4l2FileDescriptor = qt_safe_open(deviceName.toLocal8Bit().constData(), O_RDONLY);
    if (v4l2FileDescriptor == -1) {
        qWarning() << "Unable to open the camera" << deviceName
                   << "for read to query the parameter info:" << qt_error_string(errno);
        return;
    }

    struct v4l2_queryctrl queryControl;
    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_AUTO_WHITE_BALANCE;

    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        v4l2AutoWhiteBalanceSupported = true;
        setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, true);
    }

    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE;
    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        v4l2MinColorTemp = queryControl.minimum;
        v4l2MaxColorTemp = queryControl.maximum;
        v4l2ColorTemperatureSupported = true;
        features |= QCamera::Feature::ColorTemperature;
    }

    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_EXPOSURE_AUTO;
    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        v4l2AutoExposureSupported = true;
    }

    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        v4l2ManualExposureSupported = true;
        v4l2MinExposure = queryControl.minimum;
        v4l2MaxExposure = queryControl.maximum;
        features |= QCamera::Feature::ManualExposureTime;
    }

    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_AUTO_EXPOSURE_BIAS;
    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        v4l2MinExposureAdjustment = queryControl.minimum;
        v4l2MaxExposureAdjustment = queryControl.maximum;
        features |= QCamera::Feature::ExposureCompensation;
    }

    ::memset(&queryControl, 0, sizeof(queryControl));
    queryControl.id = V4L2_CID_ISO_SENSITIVITY_AUTO;
    if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
        queryControl.id = V4L2_CID_ISO_SENSITIVITY;
        if (::ioctl(v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
            features |= QCamera::Feature::IsoSensitivity;
            minIsoChanged(queryControl.minimum);
            maxIsoChanged(queryControl.minimum);
        }
    }

    supportedFeaturesChanged(features);
}

int QGstreamerCamera::setV4L2ColorTemperature(int temperature)
{
    struct v4l2_control control;
    ::memset(&control, 0, sizeof(control));

    if (v4l2AutoWhiteBalanceSupported) {
        setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, temperature == 0 ? true : false);
    } else if (temperature == 0) {
        temperature = 5600;
    }

    if (temperature != 0 && v4l2ColorTemperatureSupported) {
        temperature = qBound(v4l2MinColorTemp, temperature, v4l2MaxColorTemp);
        if (!setV4L2Parameter(V4L2_CID_WHITE_BALANCE_TEMPERATURE, qBound(v4l2MinColorTemp, temperature, v4l2MaxColorTemp)))
            temperature = 0;
    } else {
        temperature = 0;
    }

    return temperature;
}

bool QGstreamerCamera::setV4L2Parameter(quint32 id, qint32 value)
{
    struct v4l2_control control{id, value};
    if (::ioctl(v4l2FileDescriptor, VIDIOC_S_CTRL, &control) != 0) {
        qWarning() << "Unable to set the V4L2 Parameter" << Qt::hex << id << "to" << value << qt_error_string(errno);
        return false;
    }
    return true;
}

int QGstreamerCamera::getV4L2Parameter(quint32 id) const
{
    struct v4l2_control control{id, 0};
    if (::ioctl(v4l2FileDescriptor, VIDIOC_G_CTRL, &control) != 0) {
        qWarning() << "Unable to get the V4L2 Parameter" << Qt::hex << id << qt_error_string(errno);
        return 0;
    }
    return control.value;
}

#endif

QT_END_NAMESPACE

#include "moc_qgstreamercamera_p.cpp"
