Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtMultimediaTestLib/private/mediabackendutils_p.h>
#include <QtMultimediaTestLib/private/qintegrationtestbase_p.h>
#include <QtMultimediaWidgets/qvideowidget.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/qtest.h>
#include <QtMultimedia/qaudiobuffer.h>
#include <QtMultimedia/qaudiobufferinput.h>
#include <QtMultimedia/qaudiodecoder.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudioinput.h>
#include <QtMultimedia/qaudiooutput.h>
#include <QtMultimedia/qcamera.h>
#include <QtMultimedia/qcameradevice.h>
#include <QtMultimedia/qimagecapture.h>
#include <QtMultimedia/qmediacapturesession.h>
#include <QtMultimedia/qmediadevices.h>
#include <QtMultimedia/qmediaformat.h>
#include <QtMultimedia/qmediaplayer.h>
#include <QtMultimedia/qmediarecorder.h>
#include <QtMultimedia/qscreencapture.h>
#include <QtMultimedia/qvideoframeinput.h>
#include <QtMultimedia/qvideosink.h>
#include <QtMultimedia/qwindowcapture.h>
#include <QtGui/qimagereader.h>
#include <QtCore/qdebug.h>
#include <QtCore/qrandom.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qurl.h>
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
// NOLINTBEGIN(readability-convert-member-functions-to-static)
/*
This is the backend conformance test.
Since it relies on platform media framework and sound hardware
it may be less stable.
*/
using namespace QtMultimediaTest;
class tst_QMediaCaptureSession : public QIntegrationTestBase
{
Q_OBJECT
private slots:
void initTestCase()
{
if (!initIntegrationTestCase())
return;
}
void testAudioMute();
void stress_test_setup_and_teardown();
void stress_test_setup_and_teardown_keep_session();
void stress_test_setup_and_teardown_keep_recorder();
void stress_test_setup_and_teardown_keep_camera();
void stress_test_setup_and_teardown_keep_audioinput();
void stress_test_setup_and_teardown_keep_audiooutput();
void stress_test_setup_and_teardown_keep_video();
void record_video_without_preview();
void can_add_and_remove_AudioInput_with_and_without_AudioOutput_attached();
void can_change_AudioDevices_on_attached_AudioInput();
void can_change_AudioInput_during_recording();
void disconnects_deleted_AudioInput();
void can_move_AudioInput_between_sessions();
void disconnects_deleted_AudioOutput();
void can_move_AudioOutput_between_sessions_and_player();
void disconnects_deleted_AudioBufferInput();
void can_move_AudioBufferInput_between_sessions();
void disconnects_deleted_VideoFrameInput();
void can_move_VideoFrameInput_between_sessions();
void can_add_and_remove_Camera();
void can_move_Camera_between_sessions();
void can_disconnect_Camera_when_recording();
void can_add_and_remove_different_Cameras();
void can_change_CameraDevice_on_attached_Camera();
void can_change_VideoOutput_with_and_without_camera();
void can_change_VideoOutput_when_recording();
void can_add_and_remove_recorders();
void can_move_Recorder_between_sessions();
void cannot_record_without_Camera_and_AudioInput();
void can_record_AudioInput_with_null_AudioDevice();
void can_record_Camera_with_null_CameraDevice();
void recording_stops_when_recorder_removed();
void can_add_and_remove_ImageCapture();
void can_move_ImageCapture_between_sessions();
void capture_is_not_available_when_Camera_is_null();
void can_add_ImageCapture_and_capture_during_recording();
void settingImageCaptureResolution_providesCorrectSizeVideoFrame();
void can_switch_audio_output();
void can_switch_audio_input();
void can_clear_audio_output();
void can_clear_audio_input();
private:
void recordOk(QMediaCaptureSession &session);
void recordFail(QMediaCaptureSession &session);
};
void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session)
{
QMediaRecorder recorder;
session.setRecorder(&recorder);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
recorder.record();
QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 5s);
QVERIFY(durationChanged.wait(5s));
recorder.stop();
QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::StoppedState, 5s);
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::recordFail(QMediaCaptureSession &session)
{
QMediaRecorder recorder;
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
session.setRecorder(&recorder);
recorder.record();
QTRY_VERIFY_WITH_TIMEOUT(recorderErrorSignal.size() == 1, 5s);
QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::StoppedState, 5s);
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
for (int i = 0; i < 50; i++) {
QMediaCaptureSession session;
QMediaRecorder recorder;
QCamera camera;
QAudioInput input;
QAudioOutput output;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_session()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QMediaCaptureSession session;
for (int i = 0; i < 50; i++) {
QMediaRecorder recorder;
QCamera camera;
QAudioInput input;
QAudioOutput output;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_recorder()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QMediaCaptureSession session;
QMediaRecorder recorder;
for (int i = 0; i < 50; i++) {
QCamera camera;
QAudioInput input;
QAudioOutput output;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_camera()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QCamera camera;
for (int i = 0; i < 50; i++) {
QMediaCaptureSession session;
QMediaRecorder recorder;
QAudioInput input;
QAudioOutput output;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audioinput()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QAudioInput input;
for (int i = 0; i < 50; i++) {
QMediaCaptureSession session;
QMediaRecorder recorder;
QCamera camera;
QAudioOutput output;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audiooutput()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QAudioOutput output;
for (int i = 0; i < 50; i++) {
QMediaCaptureSession session;
QMediaRecorder recorder;
QCamera camera;
QAudioInput input;
QVideoWidget video;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_video()
{
QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI");
QVideoWidget video;
for (int i = 0; i < 50; i++) {
QMediaCaptureSession session;
QMediaRecorder recorder;
QCamera camera;
QAudioInput input;
QAudioOutput output;
if (!input.device().isNull())
session.setAudioInput(&input);
if (!output.device().isNull())
session.setAudioOutput(&output);
session.setRecorder(&recorder);
session.setCamera(&camera);
session.setVideoOutput(&video);
QRandomGenerator rng;
QTest::qWait(rng.bounded(200));
}
}
void tst_QMediaCaptureSession::record_video_without_preview()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input is available");
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
camera.setActive(true);
QTRY_COMPARE(cameraChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
session.setCamera(nullptr);
QTRY_COMPARE(cameraChanged.size(), 2);
// can't record without audio and video
recordFail(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_add_and_remove_AudioInput_with_and_without_AudioOutput_attached()
{
QAudioInput input;
if (input.device().isNull())
QSKIP("No audio input available");
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
session.setAudioInput(nullptr);
QTRY_COMPARE(audioInputChanged.size(), 2);
QAudioOutput output;
if (output.device().isNull())
return;
session.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged.size(), 1);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 3);
session.setAudioOutput(nullptr);
QTRY_COMPARE(audioOutputChanged.size(), 2);
session.setAudioInput(nullptr);
QTRY_COMPARE(audioInputChanged.size(), 4);
}
void tst_QMediaCaptureSession::can_change_AudioDevices_on_attached_AudioInput()
{
auto audioInputs = QMediaDevices::audioInputs();
if (audioInputs.size() < 2)
QSKIP("Two audio inputs are not available");
QAudioInput input(audioInputs[0]);
QSignalSpy deviceChanged(&input, &QAudioInput::deviceChanged);
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
input.setDevice(audioInputs[1]);
QTRY_COMPARE(deviceChanged.size(), 1);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
input.setDevice(audioInputs[0]);
QTRY_COMPARE(deviceChanged.size(), 2);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_change_AudioInput_during_recording()
{
QSKIP_WMF("QTBUG-151355: fails/crashes with audio device present on Windows backend");
QAudioInput input;
if (input.device().isNull())
QSKIP("No audio input available");
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
recorder.record();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
QVERIFY(durationChanged.wait(2000));
session.setAudioInput(nullptr);
QTRY_COMPARE(audioInputChanged.size(), 2);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 3);
recorder.stop();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(recorderErrorSignal.isEmpty());
session.setAudioInput(nullptr);
QTRY_COMPARE(audioInputChanged.size(), 4);
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::disconnects_deleted_AudioInput()
{
if (QMediaDevices::audioInputs().isEmpty())
QSKIP("No audio input available");
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
{
QAudioInput input;
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
}
QVERIFY(session.audioInput() == nullptr);
QTRY_COMPARE(audioInputChanged.size(), 2);
}
void tst_QMediaCaptureSession::can_move_AudioInput_between_sessions()
{
if (QMediaDevices::audioInputs().isEmpty())
QSKIP("No audio input available");
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy audioInputChanged0(&session0, &QMediaCaptureSession::audioInputChanged);
QSignalSpy audioInputChanged1(&session1, &QMediaCaptureSession::audioInputChanged);
QAudioInput input;
{
QMediaCaptureSession session2;
QSignalSpy audioInputChanged2(&session2, &QMediaCaptureSession::audioInputChanged);
session2.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged2.size(), 1);
}
session0.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged0.size(), 1);
QVERIFY(session0.audioInput() != nullptr);
session1.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged0.size(), 2);
QVERIFY(session0.audioInput() == nullptr);
QTRY_COMPARE(audioInputChanged1.size(), 1);
QVERIFY(session1.audioInput() != nullptr);
}
void tst_QMediaCaptureSession::disconnects_deleted_AudioOutput()
{
if (QMediaDevices::audioOutputs().isEmpty())
QSKIP("No audio output available");
QMediaCaptureSession session;
QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
{
QAudioOutput output;
session.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged.size(), 1);
}
QVERIFY(session.audioOutput() == nullptr);
QTRY_COMPARE(audioOutputChanged.size(), 2);
}
void tst_QMediaCaptureSession::can_move_AudioOutput_between_sessions_and_player()
{
if (QMediaDevices::audioOutputs().isEmpty())
QSKIP("No audio output available");
QAudioOutput output;
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QMediaPlayer player;
QSignalSpy audioOutputChanged0(&session0, &QMediaCaptureSession::audioOutputChanged);
QSignalSpy audioOutputChanged1(&session1, &QMediaCaptureSession::audioOutputChanged);
QSignalSpy audioOutputChangedPlayer(&player, &QMediaPlayer::audioOutputChanged);
{
QMediaCaptureSession session2;
QSignalSpy audioOutputChanged2(&session2, &QMediaCaptureSession::audioOutputChanged);
session2.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged2.size(), 1);
}
session0.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged0.size(), 1);
QVERIFY(session0.audioOutput() != nullptr);
session1.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged0.size(), 2);
QVERIFY(session0.audioOutput() == nullptr);
QTRY_COMPARE(audioOutputChanged1.size(), 1);
QVERIFY(session1.audioOutput() != nullptr);
player.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged0.size(), 2);
QVERIFY(session0.audioOutput() == nullptr);
QTRY_COMPARE(audioOutputChanged1.size(), 2);
QVERIFY(session1.audioOutput() == nullptr);
QTRY_COMPARE(audioOutputChangedPlayer.size(), 1);
QVERIFY(player.audioOutput() != nullptr);
session0.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged0.size(), 3);
QVERIFY(session0.audioOutput() != nullptr);
QTRY_COMPARE(audioOutputChangedPlayer.size(), 2);
QVERIFY(player.audioOutput() == nullptr);
}
void tst_QMediaCaptureSession::disconnects_deleted_AudioBufferInput()
{
QMediaCaptureSession session;
QSignalSpy audioBufferInputChanged(&session, &QMediaCaptureSession::audioBufferInputChanged);
{
QAudioBufferInput input;
session.setAudioBufferInput(&input);
QTRY_COMPARE(audioBufferInputChanged.size(), 1);
}
QCOMPARE(session.audioBufferInput(), nullptr);
QCOMPARE(audioBufferInputChanged.size(), 2);
}
void tst_QMediaCaptureSession::can_move_AudioBufferInput_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy audioBufferInputChanged0(&session0, &QMediaCaptureSession::audioBufferInputChanged);
QSignalSpy audioBufferInputChanged1(&session1, &QMediaCaptureSession::audioBufferInputChanged);
QAudioBufferInput input;
{
QMediaCaptureSession session2;
QSignalSpy audioBufferInputChanged2(&session2,
&QMediaCaptureSession::audioBufferInputChanged);
session2.setAudioBufferInput(&input);
QCOMPARE(audioBufferInputChanged2.size(), 1);
}
session0.setAudioBufferInput(&input);
QCOMPARE(audioBufferInputChanged0.size(), 1);
QCOMPARE(session0.audioBufferInput(), &input);
QCOMPARE(input.captureSession(), &session0);
session1.setAudioBufferInput(&input);
QCOMPARE(audioBufferInputChanged0.size(), 2);
QCOMPARE(session0.audioBufferInput(), nullptr);
QCOMPARE(audioBufferInputChanged1.size(), 1);
QCOMPARE(session1.audioBufferInput(), &input);
QCOMPARE(input.captureSession(), &session1);
}
void tst_QMediaCaptureSession::disconnects_deleted_VideoFrameInput()
{
QMediaCaptureSession session;
QSignalSpy videoFrameInputChanged(&session, &QMediaCaptureSession::videoFrameInputChanged);
{
QVideoFrameInput input;
session.setVideoFrameInput(&input);
QTRY_COMPARE(videoFrameInputChanged.size(), 1);
}
QCOMPARE(session.videoFrameInput(), nullptr);
QCOMPARE(videoFrameInputChanged.size(), 2);
}
void tst_QMediaCaptureSession::can_move_VideoFrameInput_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy videoFrameInputChanged0(&session0, &QMediaCaptureSession::videoFrameInputChanged);
QSignalSpy videoFrameInputChanged1(&session1, &QMediaCaptureSession::videoFrameInputChanged);
QVideoFrameInput input;
{
QMediaCaptureSession session2;
QSignalSpy videoFrameInputChanged2(&session2,
&QMediaCaptureSession::videoFrameInputChanged);
session2.setVideoFrameInput(&input);
QCOMPARE(videoFrameInputChanged2.size(), 1);
}
session0.setVideoFrameInput(&input);
QCOMPARE(videoFrameInputChanged0.size(), 1);
QCOMPARE(session0.videoFrameInput(), &input);
QCOMPARE(input.captureSession(), &session0);
session1.setVideoFrameInput(&input);
QCOMPARE(videoFrameInputChanged0.size(), 2);
QCOMPARE(session0.videoFrameInput(), nullptr);
QCOMPARE(videoFrameInputChanged1.size(), 1);
QCOMPARE(session1.videoFrameInput(), &input);
QCOMPARE(input.captureSession(), &session1);
}
void tst_QMediaCaptureSession::can_add_and_remove_Camera()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input is available");
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
camera.setActive(true);
QTRY_COMPARE(cameraChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
session.setCamera(nullptr);
QTRY_COMPARE(cameraChanged.size(), 2);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 3);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_move_Camera_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy cameraChanged0(&session0, &QMediaCaptureSession::cameraChanged);
QSignalSpy cameraChanged1(&session1, &QMediaCaptureSession::cameraChanged);
{
QCamera camera;
{
QMediaCaptureSession session2;
QSignalSpy cameraChanged2(&session2, &QMediaCaptureSession::cameraChanged);
session2.setCamera(&camera);
QTRY_COMPARE(cameraChanged2.size(), 1);
}
QVERIFY(camera.captureSession() == nullptr);
session0.setCamera(&camera);
QTRY_COMPARE(cameraChanged0.size(), 1);
QVERIFY(session0.camera() == &camera);
QVERIFY(camera.captureSession() == &session0);
session1.setCamera(&camera);
QTRY_COMPARE(cameraChanged0.size(), 2);
QVERIFY(session0.camera() == nullptr);
QTRY_COMPARE(cameraChanged1.size(), 1);
QVERIFY(session1.camera() == &camera);
QVERIFY(camera.captureSession() == &session1);
}
QTRY_COMPARE(cameraChanged1.size(), 2);
QVERIFY(session1.camera() == nullptr);
}
void tst_QMediaCaptureSession::can_disconnect_Camera_when_recording()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input is available");
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
camera.setActive(true);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
durationChanged.clear();
recorder.record();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
QTRY_VERIFY(durationChanged.size() > 0);
session.setCamera(nullptr);
QTRY_COMPARE(cameraChanged.size(), 2);
recorder.stop();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::can_add_and_remove_different_Cameras()
{
auto cameraDevices = QMediaDevices().videoInputs();
if (cameraDevices.size() < 2)
QSKIP("Two video input are not available");
QCamera camera(cameraDevices[0]);
QCamera camera2(cameraDevices[1]);
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
camera.setActive(true);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
session.setCamera(nullptr);
QTRY_COMPARE(cameraChanged.size(), 2);
session.setCamera(&camera2);
camera2.setActive(true);
QTRY_COMPARE(cameraChanged.size(), 3);
QTRY_COMPARE(camera2.isActive(), true);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_change_CameraDevice_on_attached_Camera()
{
auto cameraDevices = QMediaDevices().videoInputs();
if (cameraDevices.size() < 2)
QSKIP("Two video input are not available");
QCamera camera(cameraDevices[0]);
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
QSignalSpy cameraDeviceChanged(&camera, &QCamera::cameraDeviceChanged);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
recordFail(session);
QVERIFY(!QTest::currentTestFailed());
camera.setActive(true);
QTRY_COMPARE(camera.isActive(), true);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
camera.setCameraDevice(cameraDevices[1]);
camera.setActive(true);
QTRY_COMPARE(cameraDeviceChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_change_VideoOutput_with_and_without_camera()
{
#ifdef Q_OS_HARMONY
QSKIP("Phone form factor only allows one top-level window; this test shows two QVideoWidgets simultaneously");
#endif
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input is available");
QVideoWidget videoOutput;
QVideoWidget videoOutput2;
videoOutput.show();
videoOutput2.show();
QMediaCaptureSession session;
QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
session.setVideoOutput(&videoOutput);
QTRY_COMPARE(videoOutputChanged.size(), 1);
session.setVideoOutput(nullptr);
QTRY_COMPARE(videoOutputChanged.size(), 2);
session.setVideoOutput(&videoOutput2);
QTRY_COMPARE(videoOutputChanged.size(), 3);
session.setCamera(nullptr);
QTRY_COMPARE(cameraChanged.size(), 2);
session.setVideoOutput(nullptr);
QTRY_COMPARE(videoOutputChanged.size(), 4);
}
void tst_QMediaCaptureSession::can_change_VideoOutput_when_recording()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input is available");
QVideoWidget videoOutput;
videoOutput.show();
QMediaCaptureSession session;
QMediaRecorder recorder;
session.setRecorder(&recorder);
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged);
camera.setActive(true);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
QTRY_COMPARE(camera.isActive(), true);
recorder.record();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
QVERIFY(durationChanged.wait(2000));
session.setVideoOutput(&videoOutput);
QTRY_COMPARE(videoOutputChanged.size(), 1);
session.setVideoOutput(nullptr);
QTRY_COMPARE(videoOutputChanged.size(), 2);
session.setVideoOutput(&videoOutput);
QTRY_COMPARE(videoOutputChanged.size(), 3);
recorder.stop();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::can_add_and_remove_recorders()
{
QSKIP_WMF("QTBUG-151355: fails/crashes with audio device present on Windows backend");
QAudioInput input;
if (input.device().isNull())
QSKIP("Recording source not available");
QMediaRecorder recorder;
QMediaRecorder recorder2;
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
session.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged.size(), 1);
session.setRecorder(&recorder2);
QTRY_COMPARE(recorderChanged.size(), 2);
session.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged.size(), 3);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_move_Recorder_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy recorderChanged0(&session0, &QMediaCaptureSession::recorderChanged);
QSignalSpy recorderChanged1(&session1, &QMediaCaptureSession::recorderChanged);
{
QMediaRecorder recorder;
{
QMediaCaptureSession session2;
QSignalSpy recorderChanged2(&session2, &QMediaCaptureSession::recorderChanged);
session2.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged2.size(), 1);
}
QVERIFY(recorder.captureSession() == nullptr);
session0.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged0.size(), 1);
QVERIFY(session0.recorder() == &recorder);
QVERIFY(recorder.captureSession() == &session0);
session1.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged0.size(), 2);
QVERIFY(session0.recorder() == nullptr);
QTRY_COMPARE(recorderChanged1.size(), 1);
QVERIFY(session1.recorder() == &recorder);
QVERIFY(recorder.captureSession() == &session1);
}
QTRY_COMPARE(recorderChanged1.size(), 2);
QVERIFY(session1.recorder() == nullptr);
}
void tst_QMediaCaptureSession::cannot_record_without_Camera_and_AudioInput()
{
QMediaCaptureSession session;
recordFail(session);
}
void tst_QMediaCaptureSession::can_record_AudioInput_with_null_AudioDevice()
{
QSKIP_WMF("QTBUG-151355: fails/crashes with audio device present on Windows backend");
if (QMediaDevices().audioInputs().size() == 0)
QSKIP("No audio input is not available");
QAudioDevice nullDevice;
QAudioInput input(nullDevice);
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::can_record_Camera_with_null_CameraDevice()
{
if (QMediaDevices().videoInputs().size() == 0)
QSKIP("No video input is not available");
QCameraDevice nullDevice;
QCamera camera(nullDevice);
QMediaCaptureSession session;
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
camera.setActive(true);
QTRY_COMPARE(camera.isActive(), true);
recordOk(session);
QVERIFY(!QTest::currentTestFailed());
}
void tst_QMediaCaptureSession::recording_stops_when_recorder_removed()
{
QSKIP_WMF("QTBUG-151355: fails/crashes with audio device present on Windows backend");
QAudioInput input;
if (input.device().isNull())
QSKIP("Recording source not available");
QSKIP_GSTREAMER("spurious failures on gstreamer");
QMediaRecorder recorder;
QMediaCaptureSession session;
QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
session.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged.size(), 1);
recorder.record();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
QVERIFY(durationChanged.wait(2000));
session.setRecorder(nullptr);
QTRY_COMPARE(recorderChanged.size(), 2);
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::can_add_and_remove_ImageCapture()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input available");
QImageCapture capture;
QMediaCaptureSession session;
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
QSignalSpy imageCaptureChanged(&session, &QMediaCaptureSession::imageCaptureChanged);
QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
QVERIFY(!capture.isAvailable());
QVERIFY(!capture.isReadyForCapture());
session.setImageCapture(&capture);
QTRY_COMPARE(imageCaptureChanged.size(), 1);
QVERIFY(!capture.isAvailable());
QVERIFY(!capture.isReadyForCapture());
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
QVERIFY(capture.isAvailable());
QVERIFY(!capture.isReadyForCapture());
camera.setActive(true);
QTRY_COMPARE(camera.isActive(), true);
QTRY_COMPARE(readyForCaptureChanged.size(), 1);
QVERIFY(capture.isReadyForCapture());
session.setImageCapture(nullptr);
QTRY_COMPARE(imageCaptureChanged.size(), 2);
QTRY_COMPARE(readyForCaptureChanged.size(), 2);
QVERIFY(!capture.isAvailable());
QVERIFY(!capture.isReadyForCapture());
session.setImageCapture(&capture);
QTRY_COMPARE(imageCaptureChanged.size(), 3);
QTRY_COMPARE(readyForCaptureChanged.size(), 3);
QVERIFY(capture.isAvailable());
QVERIFY(capture.isReadyForCapture());
}
void tst_QMediaCaptureSession::can_move_ImageCapture_between_sessions()
{
QSKIP_GSTREAMER("QTBUG-124005: Spurious failure on CI");
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QSignalSpy imageCaptureChanged0(&session0, &QMediaCaptureSession::imageCaptureChanged);
QSignalSpy imageCaptureChanged1(&session1, &QMediaCaptureSession::imageCaptureChanged);
{
QImageCapture imageCapture;
{
QMediaCaptureSession session2;
QSignalSpy imageCaptureChanged2(&session2, &QMediaCaptureSession::imageCaptureChanged);
session2.setImageCapture(&imageCapture);
QTRY_COMPARE(imageCaptureChanged2.size(), 1);
}
QVERIFY(imageCapture.captureSession() == nullptr);
session0.setImageCapture(&imageCapture);
QTRY_COMPARE(imageCaptureChanged0.size(), 1);
QVERIFY(session0.imageCapture() == &imageCapture);
QVERIFY(imageCapture.captureSession() == &session0);
session1.setImageCapture(&imageCapture);
QTRY_COMPARE(imageCaptureChanged0.size(), 2);
QVERIFY(session0.imageCapture() == nullptr);
QTRY_COMPARE(imageCaptureChanged1.size(), 1);
QVERIFY(session1.imageCapture() == &imageCapture);
QVERIFY(imageCapture.captureSession() == &session1);
}
QTRY_COMPARE(imageCaptureChanged1.size(), 2);
QVERIFY(session1.imageCapture() == nullptr);
}
void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input available");
QSKIP_GSTREAMER("Spurious failures");
QImageCapture capture;
QMediaCaptureSession session;
QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
QSignalSpy capturedSignal(&capture, &QImageCapture::imageCaptured);
QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
session.setImageCapture(&capture);
session.setCamera(&camera);
camera.setActive(true);
QTRY_COMPARE(camera.isActive(), true);
QTRY_COMPARE(readyForCaptureChanged.size(), 1);
QVERIFY(capture.isReadyForCapture());
QVERIFY(capture.capture() >= 0);
QTRY_COMPARE(capturedSignal.size(), 1);
QVERIFY(capture.isReadyForCapture());
int readyCount = readyForCaptureChanged.size();
session.setCamera(nullptr);
QTRY_COMPARE(readyForCaptureChanged.size(), readyCount + 1);
QVERIFY(!capture.isReadyForCapture());
QVERIFY(!capture.isAvailable());
QVERIFY(capture.capture() < 0);
}
void tst_QMediaCaptureSession::can_add_ImageCapture_and_capture_during_recording()
{
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input available");
QSKIP_GSTREAMER("spurious failures with gstreamer");
QImageCapture capture;
QMediaCaptureSession session;
QMediaRecorder recorder;
QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
QSignalSpy imageCaptureChanged(&session, &QMediaCaptureSession::imageCaptureChanged);
QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
QSignalSpy capturedSignal(&capture, &QImageCapture::imageCaptured);
session.setCamera(&camera);
camera.setActive(true);
QTRY_COMPARE(camera.isActive(), true);
session.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged.size(), 1);
recorder.record();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
QVERIFY(durationChanged.wait(2000));
session.setImageCapture(&capture);
QTRY_COMPARE(imageCaptureChanged.size(), 1);
QTRY_COMPARE(readyForCaptureChanged.size(), 1);
QVERIFY(capture.isReadyForCapture());
QVERIFY(capture.capture() >= 0);
QTRY_COMPARE(capturedSignal.size(), 1);
session.setImageCapture(nullptr);
QVERIFY(readyForCaptureChanged.size() >= 2);
QVERIFY(!capture.isReadyForCapture());
recorder.stop();
QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
QTRY_VERIFY(QFileInfo(fileName).size() > 0);
QFile(fileName).remove();
}
void tst_QMediaCaptureSession::settingImageCaptureResolution_providesCorrectSizeVideoFrame()
{
QSKIP_IF_NOT_FFMPEG("Test only for FFmpeg backend");
#if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX) || defined(Q_OS_WIN)
QSKIP("Camera doesn't respect QImageCapture resolution");
#endif
QCamera camera;
if (!camera.isAvailable())
QSKIP("No video input available");
auto photoResolutions = camera.cameraDevice().photoResolutions();
if (photoResolutions.isEmpty())
QSKIP("No photo resolutions provided by camera device");
auto it = std::find_if(
photoResolutions.begin(), photoResolutions.end(), [&](const QSize &resolution) {
return resolution != camera.cameraFormat().resolution();
});
if (it == photoResolutions.end())
QSKIP("No photo resolutions that differ from default for camera format");
const QSize requestedResolution = *it;
QImageCapture capture;
QMediaCaptureSession session;
QSignalSpy imageAvailableSignal(&capture, &QImageCapture::imageAvailable);
session.setCamera(&camera);
camera.setActive(true);
session.setImageCapture(&capture);
// Act
capture.setResolution(requestedResolution);
QTest::qWait(1000); // Some platforms asynchronously restart camera when capture resolution changes
QTRY_VERIFY(capture.isReadyForCapture());
capture.capture();
// Assert
QTRY_COMPARE(imageAvailableSignal.size(), 1);
const QList<QVariant> arguments = imageAvailableSignal.front();
const QVideoFrame frame = arguments.at(1).value<QVideoFrame>();
QVERIFY(frame.isValid());
QCOMPARE(frame.size(), requestedResolution);
}
void tst_QMediaCaptureSession::testAudioMute()
{
#ifdef Q_OS_HARMONY
// OHOS records the Wave file via OH_AudioCapturer (OH_AVRecorder has no
// PCM codec), but the OHOS backend does not yet implement a
// QPlatformAudioDecoder, so this test cannot decode the recorded file for
// raw sample inspection.
QSKIP("OHOS backend has no QAudioDecoder implementation yet");
#endif
QAudioInput audioInput;
if (audioInput.device().isNull())
QSKIP("No audio input available");
QMediaRecorder recorder;
QMediaCaptureSession session;
session.setRecorder(&recorder);
session.setAudioInput(&audioInput);
session.setCamera(nullptr);
recorder.setOutputLocation(QUrl{u"test"_s});
QSignalSpy spy(&audioInput, &QAudioInput::mutedChanged);
QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
QMediaFormat format;
format.setAudioCodec(QMediaFormat::AudioCodec::Wave);
recorder.setMediaFormat(format);
audioInput.setMuted(true);
recorder.record();
QCOMPARE(spy.size(), 1);
QCOMPARE(spy.last()[0], true);
QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000);
QVERIFY(durationChanged.wait(2000));
recorder.stop();
QTRY_COMPARE(recorder.recorderState(), QMediaRecorder::StoppedState);
QString actualLocation = recorder.actualLocation().toLocalFile();
QVERIFY2(!actualLocation.isEmpty(), "Recorder did not save a file");
QTRY_VERIFY2(QFileInfo(actualLocation).size() > 0, "Recorded file is empty (zero bytes)");
QAudioDecoder decoder;
QAudioBuffer buffer;
decoder.setSource(QUrl::fromLocalFile(actualLocation));
decoder.start();
// Wait a while
QTRY_VERIFY(decoder.bufferAvailable());
while (decoder.bufferAvailable()) {
buffer = decoder.read();
QVERIFY(buffer.isValid());
const void *data = buffer.constData<void *>();
QVERIFY(data != nullptr);
const unsigned int *idata = reinterpret_cast<const unsigned int *>(data);
QCOMPARE(*idata, 0U);
}
decoder.stop();
QFile(actualLocation).remove();
audioInput.setMuted(false);
QCOMPARE(spy.size(), 2);
QCOMPARE(spy.last()[0], false);
}
void tst_QMediaCaptureSession::can_switch_audio_input()
{
// prepare
QMediaCaptureSession session;
QAudioInput in1;
session.setAudioInput(&in1);
QVERIFY(session.audioInput() != nullptr);
QAudioOutput out;
session.setAudioOutput(&out);
QVERIFY(session.audioOutput() != nullptr);
// exercise
QAudioInput in2;
QSignalSpy changeSpy(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(&in2);
// validate
QVERIFY(session.audioInput() != nullptr);
QCOMPARE(changeSpy.count(), 1);
}
void tst_QMediaCaptureSession::can_switch_audio_output()
{
// prepare
QMediaCaptureSession session;
QAudioInput in;
session.setAudioInput(&in);
QVERIFY(session.audioInput() != nullptr);
QAudioOutput out1;
session.setAudioOutput(&out1);
QVERIFY(session.audioOutput() != nullptr);
// exercise
QSignalSpy changeSpy(&session, &QMediaCaptureSession::audioOutputChanged);
QAudioOutput out2;
session.setAudioOutput(&out2);
// validate
QVERIFY(session.audioOutput() != nullptr);
QCOMPARE(changeSpy.count(), 1);
}
void tst_QMediaCaptureSession::can_clear_audio_input()
{
// prepare
QMediaCaptureSession session;
QAudioInput in1;
session.setAudioInput(&in1);
QVERIFY(session.audioInput() != nullptr);
QAudioOutput out;
session.setAudioOutput(&out);
QVERIFY(session.audioOutput() != nullptr);
// exercise
QSignalSpy changeSpy(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(nullptr);
// validate
QVERIFY(session.audioInput() == nullptr);
QCOMPARE(changeSpy.count(), 1);
}
void tst_QMediaCaptureSession::can_clear_audio_output()
{
// prepare
QMediaCaptureSession session;
QAudioInput in;
session.setAudioInput(&in);
QVERIFY(session.audioInput() != nullptr);
QAudioOutput out1;
session.setAudioOutput(&out1);
QVERIFY(session.audioOutput() != nullptr);
// exercise
QSignalSpy changeSpy(&session, &QMediaCaptureSession::audioOutputChanged);
session.setAudioOutput(nullptr);
// validate
QVERIFY(session.audioOutput() == nullptr);
QCOMPARE(changeSpy.count(), 1);
}
QTEST_MAIN(tst_QMediaCaptureSession)
#include "tst_qmediacapturesession.moc"