diff options
Diffstat (limited to 'tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp')
-rw-r--r-- | tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp | 889 |
1 files changed, 494 insertions, 395 deletions
diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp index c49236f43..fd6d819eb 100644 --- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp +++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp @@ -1,54 +1,32 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -//TESTED_COMPONENT=src/multimedia +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtGui/QImageReader> +#include <QtCore/qurl.h> +#include <QtCore/qlocale.h> #include <QDebug> +#include <QVideoSink> +#include <QMediaPlayer> -#include <qabstractvideosurface.h> -#include <qcameracontrol.h> -#include <qcameralockscontrol.h> -#include <qcameraexposurecontrol.h> -#include <qcameraflashcontrol.h> -#include <qcamerafocuscontrol.h> -#include <qcameraimagecapturecontrol.h> -#include <qimageencodercontrol.h> -#include <qcameraimageprocessingcontrol.h> -#include <qcameracapturebufferformatcontrol.h> -#include <qcameracapturedestinationcontrol.h> -#include <qmediaservice.h> +#include <private/qplatformcamera_p.h> +#include <private/qplatformimagecapture_p.h> +#include <private/qplatformmediaintegration_p.h> #include <qcamera.h> -#include <qcamerainfo.h> -#include <qcameraimagecapture.h> -#include <qvideorenderercontrol.h> -#include <private/qmediaserviceprovider_p.h> +#include <qcameradevice.h> +#include <qimagecapture.h> +#include <qmediacapturesession.h> +#include <qobject.h> +#include <qmediadevices.h> +#include <qmediarecorder.h> +#include <qmediaplayer.h> +#include <qaudiooutput.h> + +#ifdef Q_OS_DARWIN +#include <QtCore/private/qcore_mac_p.h> +#endif + +#include <mediabackendutils.h> QT_USE_NAMESPACE @@ -68,14 +46,13 @@ public slots: void cleanupTestCase(); private slots: - void testCameraInfo(); - void testCtorWithDevice(); - void testCtorWithCameraInfo(); + void testCameraDevice(); + void testCtorWithCameraDevice(); void testCtorWithPosition(); - void testCameraStates(); - void testCameraStartError(); - void testCaptureMode(); + void testCameraActive(); + void testCameraStartParallel(); + void testCameraFormat(); void testCameraCapture(); void testCaptureToBuffer(); void testCameraCaptureMetadata(); @@ -84,285 +61,318 @@ private slots: void testVideoRecording_data(); void testVideoRecording(); + + void testNativeMetadata(); + + void multipleCameraSet(); + private: + bool noCamera = false; +}; + +class TestVideoFormat : public QVideoSink +{ + Q_OBJECT +public: + explicit TestVideoFormat(const QCameraFormat &format) + : formatMismatch(0), + cameraFormat(format) + { + connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoFormat::checkVideoFrameFormat); + } + + void setCameraFormatToTest(const QCameraFormat &format) + { + formatMismatch = -1; + cameraFormat = format; + } + + int formatMismatch = -1; + +private: + QCameraFormat cameraFormat; + +public Q_SLOTS: + void checkVideoFrameFormat(const QVideoFrame &frame) + { + QVideoFrameFormat surfaceFormat = frame.surfaceFormat(); + if (surfaceFormat.pixelFormat() == cameraFormat.pixelFormat() + && surfaceFormat.frameSize() == cameraFormat.resolution()) { + formatMismatch = 0; +#ifdef Q_OS_ANDROID + } else if ((surfaceFormat.pixelFormat() == QVideoFrameFormat::Format_YUV420P + || surfaceFormat.pixelFormat() == QVideoFrameFormat::Format_NV12) + && (cameraFormat.pixelFormat() == QVideoFrameFormat::Format_YUV420P + || cameraFormat.pixelFormat() == QVideoFrameFormat::Format_NV12) + && surfaceFormat.frameSize() == cameraFormat.resolution()) { + formatMismatch = 0; +#endif + } else { + formatMismatch = 1; + } + } }; void tst_QCameraBackend::initTestCase() { +#ifdef Q_OS_ANDROID + QSKIP("SKIP initTestCase on CI, because of QTBUG-118571"); +#endif QCamera camera; - if (!camera.isAvailable()) - QSKIP("Camera is not available"); + noCamera = !camera.isAvailable(); } void tst_QCameraBackend::cleanupTestCase() { } -void tst_QCameraBackend::testCameraInfo() +void tst_QCameraBackend::testCameraDevice() { - int deviceCount = QMediaServiceProvider::defaultServiceProvider()->devices(QByteArray(Q_MEDIASERVICE_CAMERA)).count(); - const QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); - QCOMPARE(cameras.count(), deviceCount); + const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); if (cameras.isEmpty()) { - QVERIFY(QCameraInfo::defaultCamera().isNull()); + QVERIFY(noCamera); + QVERIFY(QMediaDevices::defaultVideoInput().isNull()); QSKIP("Camera selection is not supported"); } + QVERIFY(!noCamera); - for (const QCameraInfo &info : cameras) { - QVERIFY(!info.deviceName().isEmpty()); + for (const QCameraDevice &info : cameras) { + QVERIFY(!info.id().isEmpty()); QVERIFY(!info.description().isEmpty()); - QVERIFY(info.orientation() % 90 == 0); } } -void tst_QCameraBackend::testCtorWithDevice() +void tst_QCameraBackend::testCtorWithCameraDevice() { - const auto availableCameras = QCameraInfo::availableCameras(); - if (availableCameras.isEmpty()) - QSKIP("Camera selection not supported"); - - QCamera *camera = new QCamera(availableCameras.first().deviceName().toLatin1()); - QCOMPARE(camera->error(), QCamera::NoError); - delete camera; + if (noCamera) { + // only verify that we get an error trying to create a camera + QCamera camera; + QCOMPARE(camera.error(), QCamera::CameraError); + QVERIFY(camera.cameraDevice().isNull()); - //loading non existing camera should fail - camera = new QCamera(QUuid::createUuid().toByteArray()); - QCOMPARE(camera->error(), QCamera::ServiceMissingError); - - delete camera; -} + QSKIP("No camera available"); + } -void tst_QCameraBackend::testCtorWithCameraInfo() -{ - if (QCameraInfo::availableCameras().isEmpty()) - QSKIP("Camera selection not supported"); + QCameraDevice defaultCamera = QMediaDevices::defaultVideoInput(); { - QCameraInfo info = QCameraInfo::defaultCamera(); - QCamera camera(info); + // should use default camera + QCamera camera; QCOMPARE(camera.error(), QCamera::NoError); - QCOMPARE(QCameraInfo(camera), info); + QVERIFY(!camera.cameraDevice().isNull()); + QCOMPARE(camera.cameraDevice(), defaultCamera); } + { - QCameraInfo info = QCameraInfo::availableCameras().first(); - QCamera camera(info); + // should use default camera + QCamera camera(QCameraDevice{}); QCOMPARE(camera.error(), QCamera::NoError); - QCOMPARE(QCameraInfo(camera), info); + QVERIFY(!camera.cameraDevice().isNull()); + QCOMPARE(camera.cameraDevice(), defaultCamera); } + { - // loading an invalid CameraInfo should fail - QCamera *camera = new QCamera(QCameraInfo()); - QCOMPARE(camera->error(), QCamera::ServiceMissingError); - QVERIFY(QCameraInfo(*camera).isNull()); - delete camera; + QCamera camera(defaultCamera); + QCOMPARE(camera.error(), QCamera::NoError); + QCOMPARE(camera.cameraDevice(), defaultCamera); } { - // loading non existing camera should fail - QCamera camera(QCameraInfo(QUuid::createUuid().toByteArray())); - QCOMPARE(camera.error(), QCamera::ServiceMissingError); - QVERIFY(QCameraInfo(camera).isNull()); + QCameraDevice info = QMediaDevices::videoInputs().first(); + QCamera camera(info); + QCOMPARE(camera.error(), QCamera::NoError); + QCOMPARE(camera.cameraDevice(), info); } } void tst_QCameraBackend::testCtorWithPosition() { + if (noCamera) + QSKIP("No camera available"); + { - QCamera camera(QCamera::UnspecifiedPosition); + QCamera camera(QCameraDevice::UnspecifiedPosition); QCOMPARE(camera.error(), QCamera::NoError); } { - QCamera camera(QCamera::FrontFace); + QCamera camera(QCameraDevice::FrontFace); // even if no camera is available at this position, it should not fail // and load the default camera QCOMPARE(camera.error(), QCamera::NoError); } { - QCamera camera(QCamera::BackFace); + QCamera camera(QCameraDevice::BackFace); // even if no camera is available at this position, it should not fail // and load the default camera QCOMPARE(camera.error(), QCamera::NoError); } } -void tst_QCameraBackend::testCameraStates() +void tst_QCameraBackend::testCameraActive() { + QMediaCaptureSession session; QCamera camera; - QCameraImageCapture imageCapture(&camera); - - QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error))); - QSignalSpy stateChangedSignal(&camera, SIGNAL(stateChanged(QCamera::State))); - QSignalSpy statusChangedSignal(&camera, SIGNAL(statusChanged(QCamera::Status))); + camera.setCameraDevice(QCameraDevice()); + QImageCapture imageCapture; + session.setCamera(&camera); + session.setImageCapture(&imageCapture); - QCOMPARE(camera.state(), QCamera::UnloadedState); - QCOMPARE(camera.status(), QCamera::UnloadedStatus); + QSignalSpy errorSignal(&camera, &QCamera::errorOccurred); + QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged); - camera.load(); - QCOMPARE(camera.state(), QCamera::LoadedState); - QCOMPARE(stateChangedSignal.count(), 1); - QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::LoadedState); - QVERIFY(stateChangedSignal.count() > 0); + QCOMPARE(camera.isActive(), false); - QTRY_COMPARE(camera.status(), QCamera::LoadedStatus); - QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::LoadedStatus); - - camera.unload(); - QCOMPARE(camera.state(), QCamera::UnloadedState); - QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::UnloadedState); - QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus); - QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::UnloadedStatus); + if (noCamera) + QSKIP("No camera available"); + camera.setCameraDevice(QMediaDevices::defaultVideoInput()); + QCOMPARE(camera.error(), QCamera::NoError); camera.start(); - QCOMPARE(camera.state(), QCamera::ActiveState); - QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::ActiveState); - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::ActiveStatus); + QTRY_COMPARE(camera.isActive(), true); + QTRY_COMPARE(activeChangedSignal.size(), 1); + QCOMPARE(activeChangedSignal.last().first().value<bool>(), true); camera.stop(); - QCOMPARE(camera.state(), QCamera::LoadedState); - QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::LoadedState); - QTRY_COMPARE(camera.status(), QCamera::LoadedStatus); - QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::LoadedStatus); - - camera.unload(); - QCOMPARE(camera.state(), QCamera::UnloadedState); - QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::UnloadedState); - QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus); - QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::UnloadedStatus); + QCOMPARE(camera.isActive(), false); + QCOMPARE(activeChangedSignal.last().first().value<bool>(), false); QCOMPARE(camera.errorString(), QString()); - QCOMPARE(errorSignal.count(), 0); } -void tst_QCameraBackend::testCameraStartError() +void tst_QCameraBackend::testCameraStartParallel() { - QCamera camera1(QCameraInfo::defaultCamera()); - QCamera camera2(QCameraInfo::defaultCamera()); +#ifdef Q_OS_ANDROID + QSKIP("Multi-camera feature is currently not supported on Android. " + "Cannot open same device twice."); +#endif +#ifdef Q_OS_LINUX + QSKIP("Multi-camera feature is currently not supported on Linux. " + "Cannot open same device twice."); +#endif + if (noCamera) + QSKIP("No camera available"); + + QMediaCaptureSession session1; + QMediaCaptureSession session2; + QCamera camera1(QMediaDevices::defaultVideoInput()); + QCamera camera2(QMediaDevices::defaultVideoInput()); + session1.setCamera(&camera1); + session2.setCamera(&camera2); QSignalSpy errorSpy1(&camera1, &QCamera::errorOccurred); QSignalSpy errorSpy2(&camera2, &QCamera::errorOccurred); camera1.start(); camera2.start(); - QCOMPARE(camera1.state(), QCamera::ActiveState); - QTRY_COMPARE(camera1.status(), QCamera::ActiveStatus); + QCOMPARE(camera1.isActive(), true); QCOMPARE(camera1.error(), QCamera::NoError); - QCOMPARE(camera2.state(), QCamera::UnloadedState); - QCOMPARE(camera2.status(), QCamera::UnloadedStatus); - QCOMPARE(camera2.error(), QCamera::CameraError); + QCOMPARE(camera2.isActive(), true); + QCOMPARE(camera2.error(), QCamera::NoError); - QCOMPARE(errorSpy1.count(), 0); - QCOMPARE(errorSpy2.count(), 1); + QCOMPARE(errorSpy1.size(), 0); + QCOMPARE(errorSpy2.size(), 0); } -void tst_QCameraBackend::testCaptureMode() +void tst_QCameraBackend::testCameraFormat() { QCamera camera; + QCameraDevice device = camera.cameraDevice(); + auto videoFormats = device.videoFormats(); + if (videoFormats.isEmpty()) + QSKIP("No Camera available, skipping test."); + QCameraFormat cameraFormat = videoFormats.first(); + QSignalSpy spy(&camera, &QCamera::cameraFormatChanged); + QVERIFY(spy.size() == 0); + + QMediaCaptureSession session; + session.setCamera(&camera); + QVERIFY(videoFormats.size()); + camera.setCameraFormat(cameraFormat); + QCOMPARE(camera.cameraFormat(), cameraFormat); + QVERIFY(spy.size() == 1); + + TestVideoFormat videoFormatTester(cameraFormat); + session.setVideoOutput(&videoFormatTester); + camera.start(); + QTRY_VERIFY(videoFormatTester.formatMismatch == 0); - QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error))); - QSignalSpy stateChangedSignal(&camera, SIGNAL(stateChanged(QCamera::State))); - QSignalSpy captureModeSignal(&camera, SIGNAL(captureModeChanged(QCamera::CaptureModes))); - - QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage); - - if (!camera.isCaptureModeSupported(QCamera::CaptureVideo)) { - camera.setCaptureMode(QCamera::CaptureVideo); - QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage); - QSKIP("Video capture not supported"); + spy.clear(); + camera.stop(); + // Change camera format + if (videoFormats.size() > 1) { + QCameraFormat secondFormat = videoFormats.at(1); + camera.setCameraFormat(secondFormat); + QCOMPARE(camera.cameraFormat(), secondFormat); + QCOMPARE(spy.size(), 1); + QCOMPARE(camera.cameraFormat(), secondFormat); + videoFormatTester.setCameraFormatToTest(secondFormat); + camera.start(); + QTRY_VERIFY(videoFormatTester.formatMismatch == 0); + + // check that frame format is not same as previous camera format + videoFormatTester.setCameraFormatToTest(cameraFormat); + QTRY_VERIFY(videoFormatTester.formatMismatch == 1); } - camera.setCaptureMode(QCamera::CaptureVideo); - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - QTRY_COMPARE(captureModeSignal.size(), 1); - QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo); - captureModeSignal.clear(); - - camera.load(); - QTRY_COMPARE(camera.status(), QCamera::LoadedStatus); - //capture mode should still be video - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - - //it should be possible to switch capture mode in Loaded state - camera.setCaptureMode(QCamera::CaptureStillImage); - QTRY_COMPARE(captureModeSignal.size(), 1); - QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureStillImage); - captureModeSignal.clear(); - - camera.setCaptureMode(QCamera::CaptureVideo); - QTRY_COMPARE(captureModeSignal.size(), 1); - QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo); - captureModeSignal.clear(); - + // Set null format + spy.clear(); + camera.stop(); + camera.setCameraFormat({}); + QCOMPARE(spy.size(), 1); + videoFormatTester.setCameraFormatToTest({}); camera.start(); - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - //capture mode should still be video - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - - stateChangedSignal.clear(); - //it should be possible to switch capture mode in Active state - camera.setCaptureMode(QCamera::CaptureStillImage); - //camera may leave Active status, but should return to Active - QTest::qWait(10); //camera may leave Active status async - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage); - QVERIFY2(stateChangedSignal.isEmpty(), "camera should not change the state during capture mode changes"); - - QCOMPARE(captureModeSignal.size(), 1); - QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureStillImage); - captureModeSignal.clear(); - - camera.setCaptureMode(QCamera::CaptureVideo); - //camera may leave Active status, but should return to Active - QTest::qWait(10); //camera may leave Active status async - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - - QVERIFY2(stateChangedSignal.isEmpty(), "camera should not change the state during capture mode changes"); - - QCOMPARE(captureModeSignal.size(), 1); - QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo); - captureModeSignal.clear(); - + // In case of a null format, the backend should have picked + // a decent format to render frames + QTRY_VERIFY(videoFormatTester.formatMismatch == 1); camera.stop(); - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - camera.unload(); - QCOMPARE(camera.captureMode(), QCamera::CaptureVideo); - QVERIFY2(errorSignal.isEmpty(), QString("Camera error: %1").arg(camera.errorString()).toLocal8Bit()); + spy.clear(); + // Shouldn't change anything as it's the same device + camera.setCameraDevice(device); + QCOMPARE(spy.size(), 0); } void tst_QCameraBackend::testCameraCapture() { + QMediaCaptureSession session; QCamera camera; - QCameraImageCapture imageCapture(&camera); + QImageCapture imageCapture; + session.setCamera(&camera); + session.setImageCapture(&imageCapture); + //prevents camera to flash during the test - camera.exposure()->setFlashMode(QCameraExposure::FlashOff); + camera.setFlashMode(QCamera::FlashOff); QVERIFY(!imageCapture.isReadyForCapture()); - QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); - QSignalSpy errorSignal(&imageCapture, SIGNAL(error(int,QCameraImageCapture::Error,QString))); + QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); + QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred); - imageCapture.capture(); + imageCapture.captureToFile(); QTRY_COMPARE(errorSignal.size(), 1); - QCOMPARE(imageCapture.error(), QCameraImageCapture::NotReadyError); + QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError); QCOMPARE(capturedSignal.size(), 0); errorSignal.clear(); + if (noCamera) + QSKIP("No camera available"); + + QVideoSink sink; + session.setVideoOutput(&sink); camera.start(); QTRY_VERIFY(imageCapture.isReadyForCapture()); - QCOMPARE(camera.status(), QCamera::ActiveStatus); + QVERIFY(camera.isActive()); QCOMPARE(errorSignal.size(), 0); - int id = imageCapture.capture(); + int id = imageCapture.captureToFile(); - QTRY_VERIFY(!savedSignal.isEmpty()); + QTRY_VERIFY_WITH_TIMEOUT(!savedSignal.isEmpty(), 8s); QTRY_COMPARE(capturedSignal.size(), 1); QCOMPARE(capturedSignal.last().first().toInt(), id); QCOMPARE(errorSignal.size(), 0); - QCOMPARE(imageCapture.error(), QCameraImageCapture::NoError); + QCOMPARE(imageCapture.error(), QImageCapture::NoError); QCOMPARE(savedSignal.last().first().toInt(), id); QString location = savedSignal.last().last().toString(); @@ -378,35 +388,25 @@ void tst_QCameraBackend::testCameraCapture() void tst_QCameraBackend::testCaptureToBuffer() { - QCamera camera; - QCameraImageCapture imageCapture(&camera); - camera.exposure()->setFlashMode(QCameraExposure::FlashOff); - - camera.load(); + if (noCamera) + QSKIP("No camera available"); - if (!imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer)) - QSKIP("Buffer capture not supported"); - - QTRY_COMPARE(camera.status(), QCamera::LoadedStatus); + QMediaCaptureSession session; + QCamera camera; + QImageCapture imageCapture; + session.setCamera(&camera); + session.setImageCapture(&imageCapture); - QVERIFY(imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToFile)); - QVERIFY(imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer)); - QVERIFY(imageCapture.isCaptureDestinationSupported( - QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile)); + camera.setFlashMode(QCamera::FlashOff); - QSignalSpy destinationChangedSignal(&imageCapture, SIGNAL(captureDestinationChanged(QCameraImageCapture::CaptureDestinations))); + camera.setActive(true); - QCOMPARE(imageCapture.captureDestination(), QCameraImageCapture::CaptureToFile); - imageCapture.setCaptureDestination(QCameraImageCapture::CaptureToBuffer); - QCOMPARE(imageCapture.captureDestination(), QCameraImageCapture::CaptureToBuffer); - QCOMPARE(destinationChangedSignal.size(), 1); - QCOMPARE(destinationChangedSignal.first().first().value<QCameraImageCapture::CaptureDestinations>(), - QCameraImageCapture::CaptureToBuffer); + QTRY_VERIFY(camera.isActive()); - QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage))); - QSignalSpy imageAvailableSignal(&imageCapture, SIGNAL(imageAvailable(int,QVideoFrame))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); - QSignalSpy errorSignal(&imageCapture, SIGNAL(error(int,QCameraImageCapture::Error,QString))); + QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured); + QSignalSpy imageAvailableSignal(&imageCapture, &QImageCapture::imageAvailable); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); + QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred); camera.start(); QTRY_VERIFY(imageCapture.isReadyForCapture()); @@ -425,241 +425,340 @@ void tst_QCameraBackend::testCaptureToBuffer() QCOMPARE(imageAvailableSignal.first().first().toInt(), id); QVideoFrame frame = imageAvailableSignal.first().last().value<QVideoFrame>(); - QVERIFY(!frame.image().isNull()); + QVERIFY(!frame.toImage().isNull()); frame = QVideoFrame(); capturedSignal.clear(); imageAvailableSignal.clear(); savedSignal.clear(); - if (imageCapture.supportedBufferFormats().contains(QVideoFrame::Format_UYVY)) { - imageCapture.setBufferFormat(QVideoFrame::Format_UYVY); - QCOMPARE(imageCapture.bufferFormat(), QVideoFrame::Format_UYVY); - - id = imageCapture.capture(); - QTRY_VERIFY(!imageAvailableSignal.isEmpty()); - - QVERIFY(errorSignal.isEmpty()); - QVERIFY(!capturedSignal.isEmpty()); - QVERIFY(!imageAvailableSignal.isEmpty()); - QVERIFY(savedSignal.isEmpty()); - - QTest::qWait(2000); - QVERIFY(savedSignal.isEmpty()); - - frame = imageAvailableSignal.first().last().value<QVideoFrame>(); - QVERIFY(frame.isValid()); - - qDebug() << frame.pixelFormat(); - QCOMPARE(frame.pixelFormat(), QVideoFrame::Format_UYVY); - QVERIFY(!frame.size().isEmpty()); - frame = QVideoFrame(); - - capturedSignal.clear(); - imageAvailableSignal.clear(); - savedSignal.clear(); - - imageCapture.setBufferFormat(QVideoFrame::Format_Jpeg); - QCOMPARE(imageCapture.bufferFormat(), QVideoFrame::Format_Jpeg); - } - QTRY_VERIFY(imageCapture.isReadyForCapture()); - - //Try to capture to both buffer and file - if (imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile)) { - imageCapture.setCaptureDestination(QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile); - - int oldId = id; - id = imageCapture.capture(); - QVERIFY(id != oldId); - QTRY_VERIFY(!savedSignal.isEmpty()); - - QVERIFY(errorSignal.isEmpty()); - QVERIFY(!capturedSignal.isEmpty()); - QVERIFY(!imageAvailableSignal.isEmpty()); - QVERIFY(!savedSignal.isEmpty()); - - QCOMPARE(capturedSignal.first().first().toInt(), id); - QCOMPARE(imageAvailableSignal.first().first().toInt(), id); - - frame = imageAvailableSignal.first().last().value<QVideoFrame>(); - QVERIFY(!frame.image().isNull()); - - QString fileName = savedSignal.first().last().toString(); - QVERIFY(QFileInfo(fileName).exists()); - } } void tst_QCameraBackend::testCameraCaptureMetadata() { - QSKIP("Capture metadata is supported only on harmattan"); + if (noCamera) + QSKIP("No camera available"); + QMediaCaptureSession session; QCamera camera; - QCameraImageCapture imageCapture(&camera); - camera.exposure()->setFlashMode(QCameraExposure::FlashOff); + QImageCapture imageCapture; + session.setCamera(&camera); + session.setImageCapture(&imageCapture); + + camera.setFlashMode(QCamera::FlashOff); + + QMediaMetaData referenceMetaData; + referenceMetaData.insert(QMediaMetaData::Title, QStringLiteral("Title")); + referenceMetaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German)); + referenceMetaData.insert(QMediaMetaData::Description, QStringLiteral("Description")); + imageCapture.setMetaData(referenceMetaData); - QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,QString,QVariant))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); + QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); camera.start(); QTRY_VERIFY(imageCapture.isReadyForCapture()); - int id = imageCapture.capture(QString::fromLatin1("/dev/null")); + QTemporaryDir dir; + auto tmpFile = dir.filePath("testImage"); + int id = imageCapture.captureToFile(tmpFile); QTRY_VERIFY(!savedSignal.isEmpty()); QVERIFY(!metadataSignal.isEmpty()); + QCOMPARE(metadataSignal.first().first().toInt(), id); + QMediaMetaData receivedMetaData = metadataSignal.first()[1].value<QMediaMetaData>(); + + if (isGStreamerPlatform()) { + for (auto key : { + QMediaMetaData::Title, + QMediaMetaData::Language, + QMediaMetaData::Description, + }) + QCOMPARE(receivedMetaData[key], referenceMetaData[key]); + QVERIFY(receivedMetaData[QMediaMetaData::Resolution].isValid()); + } } void tst_QCameraBackend::testExposureCompensation() { - QSKIP("Capture exposure parameters are supported only on mobile platforms"); + if (noCamera) + QSKIP("No camera available"); + QMediaCaptureSession session; QCamera camera; - QCameraExposure *exposure = camera.exposure(); + session.setCamera(&camera); + + QSignalSpy exposureCompensationSignal(&camera, &QCamera::exposureCompensationChanged); - QSignalSpy exposureCompensationSignal(exposure, SIGNAL(exposureCompensationChanged(qreal))); + // it should be possible to set exposure parameters in Unloaded state + QCOMPARE(camera.exposureCompensation(), 0.); + if (!(camera.supportedFeatures() & QCamera::Feature::ExposureCompensation)) + return; - //it should be possible to set exposure parameters in Unloaded state - QCOMPARE(exposure->exposureCompensation()+1.0, 1.0); - exposure->setExposureCompensation(1.0); - QCOMPARE(exposure->exposureCompensation(), 1.0); - QTRY_COMPARE(exposureCompensationSignal.count(), 1); + camera.setExposureCompensation(1.0); + QCOMPARE(camera.exposureCompensation(), 1.0); + QTRY_COMPARE(exposureCompensationSignal.size(), 1); QCOMPARE(exposureCompensationSignal.last().first().toReal(), 1.0); //exposureCompensationChanged should not be emitted when value is not changed - exposure->setExposureCompensation(1.0); + camera.setExposureCompensation(1.0); QTest::qWait(50); - QCOMPARE(exposureCompensationSignal.count(), 1); - - //exposure compensation should be preserved during load/start - camera.load(); - QTRY_COMPARE(camera.status(), QCamera::LoadedStatus); - - QCOMPARE(exposure->exposureCompensation(), 1.0); - - exposureCompensationSignal.clear(); - exposure->setExposureCompensation(-1.0); - QCOMPARE(exposure->exposureCompensation(), -1.0); - QTRY_COMPARE(exposureCompensationSignal.count(), 1); - QCOMPARE(exposureCompensationSignal.last().first().toReal(), -1.0); + QCOMPARE(exposureCompensationSignal.size(), 1); + //exposure compensation should be preserved during start camera.start(); - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); + QTRY_VERIFY(camera.isActive()); - QCOMPARE(exposure->exposureCompensation(), -1.0); + QCOMPARE(camera.exposureCompensation(), 1.0); exposureCompensationSignal.clear(); - exposure->setExposureCompensation(1.0); - QCOMPARE(exposure->exposureCompensation(), 1.0); - QTRY_COMPARE(exposureCompensationSignal.count(), 1); - QCOMPARE(exposureCompensationSignal.last().first().toReal(), 1.0); + camera.setExposureCompensation(-1.0); + QCOMPARE(camera.exposureCompensation(), -1.0); + QTRY_COMPARE(exposureCompensationSignal.size(), 1); + QCOMPARE(exposureCompensationSignal.last().first().toReal(), -1.0); } void tst_QCameraBackend::testExposureMode() { - QSKIP("Capture exposure parameters are supported only on mobile platforms"); + if (noCamera) + QSKIP("No camera available"); QCamera camera; - QCameraExposure *exposure = camera.exposure(); - QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto); + QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); // Night - exposure->setExposureMode(QCameraExposure::ExposureNight); - QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureNight); - camera.start(); - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureNight); + if (camera.isExposureModeSupported(QCamera::ExposureNight)) { + camera.setExposureMode(QCamera::ExposureNight); + QCOMPARE(camera.exposureMode(), QCamera::ExposureNight); + camera.start(); + QVERIFY(camera.isActive()); + QCOMPARE(camera.exposureMode(), QCamera::ExposureNight); + } - camera.unload(); - QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus); + camera.stop(); + QTRY_VERIFY(!camera.isActive()); // Auto - exposure->setExposureMode(QCameraExposure::ExposureAuto); - QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto); + camera.setExposureMode(QCamera::ExposureAuto); + QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); camera.start(); - QTRY_COMPARE(camera.status(), QCamera::ActiveStatus); - QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto); + QTRY_VERIFY(camera.isActive()); + QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); + + // Manual + if (camera.isExposureModeSupported(QCamera::ExposureManual)) { + camera.setExposureMode(QCamera::ExposureManual); + QCOMPARE(camera.exposureMode(), QCamera::ExposureManual); + camera.start(); + QVERIFY(camera.isActive()); + QCOMPARE(camera.exposureMode(), QCamera::ExposureManual); + + camera.setManualExposureTime(.02f); // ~20ms should be supported by most cameras + QVERIFY(camera.manualExposureTime() > .01 && camera.manualExposureTime() < .04); + } + + camera.setExposureMode(QCamera::ExposureAuto); } void tst_QCameraBackend::testVideoRecording_data() { - QTest::addColumn<QByteArray>("device"); + QTest::addColumn<QCameraDevice>("device"); - const auto devices = QCameraInfo::availableCameras(); + const auto devices = QMediaDevices::videoInputs(); - for (const auto &device : devices) { - QTest::newRow(device.description().toUtf8()) - << device.deviceName().toLatin1(); - } + for (const auto &device : devices) + QTest::newRow(device.description().toUtf8()) << device; if (devices.isEmpty()) - QTest::newRow("Default device") << QByteArray(); + QTest::newRow("Null device") << QCameraDevice(); } void tst_QCameraBackend::testVideoRecording() { - QFETCH(QByteArray, device); + if (noCamera) + QSKIP("No camera available"); + QFETCH(QCameraDevice, device); - QScopedPointer<QCamera> camera(device.isEmpty() ? new QCamera : new QCamera(device)); + QMediaCaptureSession session; + QScopedPointer<QCamera> camera(new QCamera(device)); + session.setCamera(camera.data()); - QMediaRecorder recorder(camera.data()); + QMediaRecorder recorder; + session.setRecorder(&recorder); - QSignalSpy errorSignal(camera.data(), SIGNAL(errorOccurred(QCamera::Error))); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(error(QMediaRecorder::Error))); - QSignalSpy recorderStatusSignal(&recorder, SIGNAL(statusChanged(QMediaRecorder::Status))); + QSignalSpy errorSignal(camera.data(), &QCamera::errorOccurred); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); - if (!camera->isCaptureModeSupported(QCamera::CaptureVideo)) { - QSKIP("Video capture not supported"); - } + recorder.setVideoResolution(320, 240); - camera->setCaptureMode(QCamera::CaptureVideo); + // Insert metadata + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Author, QStringLiteral("Author")); + metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime()); + recorder.setMetaData(metaData); - QVideoEncoderSettings videoSettings; - videoSettings.setResolution(320, 240); - recorder.setVideoSettings(videoSettings); + camera->start(); + if (noCamera || device.isNull()) { + QVERIFY(!camera->isActive()); + return; + } + QTRY_VERIFY(camera->isActive()); - QCOMPARE(recorder.status(), QMediaRecorder::UnloadedStatus); + QTRY_VERIFY(camera->isActive()); - camera->start(); - QVERIFY(recorder.status() == QMediaRecorder::LoadingStatus || - recorder.status() == QMediaRecorder::LoadedStatus); - QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); - QTRY_COMPARE(camera->status(), QCamera::ActiveStatus); - QTRY_COMPARE(recorder.status(), QMediaRecorder::LoadedStatus); - QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); - - //record 5 seconds clip recorder.record(); - QTRY_COMPARE(recorder.status(), QMediaRecorder::RecordingStatus); - QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); - QTest::qWait(5000); - recorderStatusSignal.clear(); - recorder.stop(); - bool foundFinalizingStatus = false; - for (auto &list : recorderStatusSignal) { - if (list.contains(QVariant(QMediaRecorder::FinalizingStatus))) { - foundFinalizingStatus = true; - break; - } + if (!recorderErrorSignal.empty() || recorderErrorSignal.wait(550)) { + QEXPECT_FAIL_GSTREAMER("", "QTBUG-124148: GStreamer might return ResourceError", Continue); + + QCOMPARE(recorderErrorSignal.last().first().toInt(), QMediaRecorder::FormatError); + return; } - QVERIFY(foundFinalizingStatus); - QTRY_COMPARE(recorder.status(), QMediaRecorder::LoadedStatus); - QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); + + QTRY_VERIFY(durationChanged.size()); + + QCOMPARE(recorder.metaData(), metaData); + + recorderStateChanged.clear(); + recorder.stop(); + QTRY_VERIFY(recorderStateChanged.size() > 0); + QVERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); QVERIFY(errorSignal.isEmpty()); QVERIFY(recorderErrorSignal.isEmpty()); QString fileName = recorder.actualLocation().toLocalFile(); QVERIFY(!fileName.isEmpty()); + QVERIFY(QFileInfo(fileName).size() > 0); + + QMediaPlayer player; + player.setSource(fileName); + + QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::LoadedMedia, 8s); + QCOMPARE_EQ(player.metaData().value(QMediaMetaData::Resolution).toSize(), QSize(320, 240)); + QCOMPARE_GT(player.duration(), 350); + QCOMPARE_LT(player.duration(), 650); + + // TODO: integrate with a virtual camera and check mediaplayer output + + QFile(fileName).remove(); +} + +void tst_QCameraBackend::testNativeMetadata() +{ + if (noCamera) + QSKIP("No camera available"); + + QMediaCaptureSession session; + QCameraDevice device = QMediaDevices::defaultVideoInput(); + QCamera camera(device); + session.setCamera(&camera); + QMediaRecorder recorder; + session.setRecorder(&recorder); + + QSignalSpy errorSignal(&camera, &QCamera::errorOccurred); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); + + camera.start(); + if (device.isNull()) { + QVERIFY(!camera.isActive()); + return; + } + + QTRY_VERIFY(camera.isActive()); + + // Insert common metadata supported on all platforms + // Don't use Date, as some backends set that on their own + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Title, QStringLiteral("Title")); + metaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German)); + metaData.insert(QMediaMetaData::Description, QStringLiteral("Description")); + + recorder.setMetaData(metaData); + + recorder.record(); + QTRY_VERIFY(durationChanged.size()); + QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::RecordingState); + + QCOMPARE(recorder.metaData(), metaData); + + recorderStateChanged.clear(); + recorder.stop(); + + QTRY_VERIFY(recorderStateChanged.size() > 0); + QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::StoppedState); + + QVERIFY(errorSignal.isEmpty()); + if (!isGStreamerPlatform()) { + // https://bugreports.qt.io/browse/QTBUG-124183 + QVERIFY(recorderErrorSignal.isEmpty()); + } + + QString fileName = recorder.actualLocation().toLocalFile(); + QVERIFY(!fileName.isEmpty()); QVERIFY(QFileInfo(fileName).size() > 0); + + // QMediaRecorder::metaData() can only test that QMediaMetaData is set properly on the recorder. + // Use QMediaPlayer to test that the native metadata is properly set on the track + QAudioOutput output; + QMediaPlayer player; + player.setAudioOutput(&output); + + QSignalSpy metadataChangedSpy(&player, &QMediaPlayer::metaDataChanged); + + player.setSource(QUrl::fromLocalFile(fileName)); + player.play(); + + int metadataChangedRequiredCount = isGStreamerPlatform() ? 2 : 1; + + QTRY_VERIFY(metadataChangedSpy.size() >= metadataChangedRequiredCount); + + QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), + metaData.value(QMediaMetaData::Title).toString()); + auto lang = player.metaData().value(QMediaMetaData::Language).value<QLocale::Language>(); + if (lang != QLocale::AnyLanguage) + QCOMPARE(lang, metaData.value(QMediaMetaData::Language).value<QLocale::Language>()); + QCOMPARE(player.metaData().value(QMediaMetaData::Description).toString(), metaData.value(QMediaMetaData::Description).toString()); + QVERIFY(player.metaData().value(QMediaMetaData::Resolution).isValid()); + + if (isGStreamerPlatform()) + QVERIFY(player.metaData().value(QMediaMetaData::Date).isValid()); + + player.stop(); + player.setSource({}); QFile(fileName).remove(); +} + +void tst_QCameraBackend::multipleCameraSet() +{ + if (noCamera) + QSKIP("No camera available"); + + QMediaCaptureSession session; + QCameraDevice device = QMediaDevices::defaultVideoInput(); + + QMediaRecorder recorder; + session.setRecorder(&recorder); - camera->setCaptureMode(QCamera::CaptureStillImage); - QTRY_COMPARE(recorder.status(), QMediaRecorder::UnloadedStatus); - QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); + for (int i = 0; i < 5; ++i) { +#ifdef Q_OS_DARWIN + QMacAutoReleasePool releasePool; +#endif + + QCamera camera(device); + session.setCamera(&camera); + + camera.start(); + + QTest::qWait(100); + } } QTEST_MAIN(tst_QCameraBackend) |