summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmake/FindGObject.cmake4
-rw-r--r--cmake/FindGStreamer.cmake6
-rw-r--r--dependencies.yaml8
-rw-r--r--examples/CMakeLists.txt2
-rw-r--r--examples/multimedia/declarative-camera/VideoCaptureControls.qml3
-rw-r--r--examples/multimedia/video/mediaplayer/AudioControl.qml45
-rw-r--r--examples/multimedia/video/mediaplayer/CMakeLists.txt56
-rw-r--r--examples/multimedia/video/mediaplayer/Main.qml178
-rw-r--r--examples/multimedia/video/mediaplayer/MetadataInfo.qml69
-rw-r--r--examples/multimedia/video/mediaplayer/PlaybackControl.qml150
-rw-r--r--examples/multimedia/video/mediaplayer/PlaybackRateControl.qml33
-rw-r--r--examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml42
-rw-r--r--examples/multimedia/video/mediaplayer/PlayerMenuBar.qml124
-rw-r--r--examples/multimedia/video/mediaplayer/TracksInfo.qml75
-rw-r--r--examples/multimedia/video/mediaplayer/controls/AudioControl.qml50
-rw-r--r--examples/multimedia/video/mediaplayer/controls/MetadataInfo.qml30
-rw-r--r--examples/multimedia/video/mediaplayer/controls/PlaybackControl.qml318
-rw-r--r--examples/multimedia/video/mediaplayer/controls/PlaybackSeekControl.qml56
-rw-r--r--examples/multimedia/video/mediaplayer/controls/SettingsPopup.qml204
-rw-r--r--examples/multimedia/video/mediaplayer/controls/TracksInfo.qml25
-rw-r--r--examples/multimedia/video/mediaplayer/controls/UrlPopup.qml54
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/OqosZsDqvzQ.jpgbin181335 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/PlayerMenuBar.gifbin65177 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/architecture-overview.gifbin222405 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/audio-control.gifbin150039 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/mediaplayer.pngbin0 -> 49506 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/meta-data.pngbin66670 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/nHrBbW0H-pc.jpgbin84480 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/play-pause-stop.gifbin188432 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/playbackControlPanel.gifbin179494 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/qmlmediaplayer.jpgbin67156 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/settings.pngbin0 -> 33705 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/sf_yv01UtIw.jpgbin96819 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/images/url.pngbin68983 -> 0 bytes
-rw-r--r--examples/multimedia/video/mediaplayer/doc/qmlmediaplayer.qdocconf4
-rw-r--r--examples/multimedia/video/mediaplayer/doc/src/mediaplayer.qdoc296
-rw-r--r--examples/multimedia/video/mediaplayer/images/backward10.svg5
-rw-r--r--examples/multimedia/video/mediaplayer/images/ff.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/forward10.svg5
-rw-r--r--examples/multimedia/video/mediaplayer/images/link.svg3
-rw-r--r--examples/multimedia/video/mediaplayer/images/loop.svg3
-rw-r--r--examples/multimedia/video/mediaplayer/images/more.svg5
-rw-r--r--examples/multimedia/video/mediaplayer/images/mute.svg (renamed from examples/multimedia/video/mediaplayer/Mute_Icon.svg)0
-rw-r--r--examples/multimedia/video/mediaplayer/images/open_new.svg6
-rw-r--r--examples/multimedia/video/mediaplayer/images/pause_symbol.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/play_symbol.svg3
-rw-r--r--examples/multimedia/video/mediaplayer/images/rewind.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/settings.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/speaker.svg (renamed from examples/multimedia/video/mediaplayer/Speaker_Icon.svg)0
-rw-r--r--examples/multimedia/video/mediaplayer/images/stop_symbol.svg3
-rw-r--r--examples/multimedia/video/mediaplayer/images/url.svg6
-rw-r--r--examples/multimedia/video/mediaplayer/images/volume.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/volume_mute.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/zoom_maximize.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/images/zoom_minimize.svg4
-rw-r--r--examples/multimedia/video/mediaplayer/main.cpp6
-rw-r--r--examples/multimedia/video/mediaplayer/main.qml153
-rw-r--r--licenseRule.json2
-rw-r--r--src/3rdparty/pffft/pffft.c46
-rw-r--r--src/3rdparty/pffft/qt_attribution.json4
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java24
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java8
-rw-r--r--src/multimedia/CMakeLists.txt6
-rw-r--r--src/multimedia/audio/qwavedecoder.cpp4
-rw-r--r--src/multimedia/camera/qcamera.h2
-rw-r--r--src/multimedia/configure.cmake6
-rw-r--r--src/multimedia/doc/qtmultimedia.qdocconf1
-rw-r--r--src/multimedia/doc/src/qtmultimedia-building-from-source.qdoc94
-rw-r--r--src/multimedia/doc/src/qtmultimedia-index.qdoc3
-rw-r--r--src/multimedia/platform/qplatformmediacapture.cpp23
-rw-r--r--src/multimedia/platform/qplatformmediacapture_p.h5
-rw-r--r--src/multimedia/platform/qplatformmediaintegration.cpp41
-rw-r--r--src/multimedia/recording/qmediacapturesession.cpp43
-rw-r--r--src/multimedia/recording/qmediacapturesession.h2
-rw-r--r--src/multimedia/recording/qmediacapturesession_p.h44
-rw-r--r--src/multimedia/video/qvideoframe.cpp1
-rw-r--r--src/plugins/multimedia/CMakeLists.txt28
-rw-r--r--src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp7
-rw-r--r--src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h1
-rw-r--r--src/plugins/multimedia/ffmpeg/qandroidcamera.cpp34
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp12
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp31
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpegmediarecorder_p.h4
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpegthread.cpp19
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpegthread_p.h15
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpegvaapisymbols.cpp5
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder.cpp19
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder_p.h3
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread.cpp4
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread_p.h4
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp115
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h4
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp100
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h4
-rw-r--r--src/plugins/multimedia/gstreamer/CMakeLists.txt5
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp304
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h13
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp7
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp13
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgst.cpp16
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgst_debug.cpp99
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgst_debug_p.h9
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h3
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgst_p.h39
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp51
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h8
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamerbufferprobe.cpp14
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp222
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h28
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp522
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h14
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp17
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h3
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstutils.cpp2
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstutils_p.h2
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstvideobuffer.cpp57
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstvideobuffer_p.h6
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp58
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h5
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp127
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h13
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp12
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h2
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp27
-rw-r--r--src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h2
-rw-r--r--src/resonance-audio/CMakeLists.txt2
-rw-r--r--tests/auto/integration/multiapp/tst_multiapp.cpp28
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp72
-rw-r--r--tests/auto/integration/qaudiosink/tst_qaudiosink.cpp18
-rw-r--r--tests/auto/integration/qaudiosource/tst_qaudiosource.cpp18
-rw-r--r--tests/auto/integration/qcamerabackend/CMakeLists.txt2
-rw-r--r--tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp55
-rw-r--r--tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp126
-rw-r--r--tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp171
-rw-r--r--tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp14
-rw-r--r--tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp12
-rw-r--r--tests/auto/integration/shared/mediabackendutils.h16
-rw-r--r--tests/auto/integration/shared/mediafileselector.h7
-rw-r--r--tests/auto/unit/mockbackend/qmockaudiodecoder.cpp4
-rw-r--r--tests/auto/unit/mockbackend/qmockimagecapture.cpp2
-rw-r--r--tests/auto/unit/multimedia/CMakeLists.txt6
-rw-r--r--tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt15
-rw-r--r--tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp79
-rw-r--r--tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h31
-rw-r--r--tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp22
-rw-r--r--tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp32
-rw-r--r--tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp10
-rw-r--r--tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt14
-rw-r--r--tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp75
-rw-r--r--tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp114
-rw-r--r--tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt14
-rw-r--r--tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp78
-rw-r--r--tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp10
-rw-r--r--tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp8
-rw-r--r--tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp2
-rw-r--r--tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp2
-rw-r--r--tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp2
-rw-r--r--tests/manual/CMakeLists.txt2
-rw-r--r--tests/manual/minimal-player/minimal-player.cpp84
160 files changed, 3498 insertions, 2181 deletions
diff --git a/cmake/FindGObject.cmake b/cmake/FindGObject.cmake
index 54dd35ead..89a02435a 100644
--- a/cmake/FindGObject.cmake
+++ b/cmake/FindGObject.cmake
@@ -23,7 +23,7 @@ qt_internal_disable_find_package_global_promotion(GLIB2::GLIB2)
if(NOT TARGET GObject::GObject)
find_package(PkgConfig QUIET)
pkg_check_modules(PC_GOBJECT gobject-2.0 IMPORTED_TARGET)
- if (TARGET PkgConfig::PC_GOBJECT)
+ if(TARGET PkgConfig::PC_GOBJECT)
add_library(GObject::GObject INTERFACE IMPORTED)
target_link_libraries(GObject::GObject INTERFACE
PkgConfig::PC_GOBJECT
@@ -35,7 +35,7 @@ if(NOT TARGET GObject::GObject)
PATH_SUFFIXES glib-2.0/gobject/
)
find_library(GObject_LIBRARY NAMES gobject-2.0)
- if (GObject_LIBRARY AND GObject_INCLUDE_DIR)
+ if(GObject_LIBRARY AND GObject_INCLUDE_DIR)
add_library(GObject::GObject INTERFACE IMPORTED)
target_include_directories(GObject::GObject INTERFACE
${GObject_INCLUDE_DIR}
diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake
index dc6776606..b8891e7ed 100644
--- a/cmake/FindGStreamer.cmake
+++ b/cmake/FindGStreamer.cmake
@@ -97,17 +97,17 @@ endif()
# GStreamer optional components
foreach(component ${GStreamer_FIND_COMPONENTS})
- if (${component} STREQUAL "App")
+ if(${component} STREQUAL "App")
find_gstreamer_component(App gstreamer-app-1.0 gst/app/gstappsink.h gstapp-1.0)
if(TARGET GStreamer::App AND TARGET GStreamer::Base)
target_link_libraries(GStreamer::App INTERFACE GStreamer::Base)
endif()
- elseif (${component} STREQUAL "Photography")
+ elseif(${component} STREQUAL "Photography")
find_gstreamer_component(Photography gstreamer-photography-1.0 gst/interfaces/photography.h gstphotography-1.0)
if(TARGET GStreamer::Photography AND TARGET GStreamer::Core)
target_link_libraries(GStreamer::Photography INTERFACE GStreamer::Core)
endif()
- elseif (${component} STREQUAL "Gl")
+ elseif(${component} STREQUAL "Gl")
find_gstreamer_component(Gl gstreamer-gl-1.0 gst/gl/gl.h gstgl-1.0)
if(TARGET GStreamer::Gl AND TARGET GStreamer::Video AND TARGET GStreamer::Allocators)
target_link_libraries(GStreamer::Gl INTERFACE GStreamer::Video GStreamer::Allocators)
diff --git a/dependencies.yaml b/dependencies.yaml
index 467c64835..ec2753928 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -1,13 +1,13 @@
dependencies:
../qtbase:
- ref: a030795bc956ce4274e3562e963d59e74f2e6fd7
+ ref: f0633e823796775d2c019363ca4f1cb008851402
required: true
../qtdeclarative:
- ref: c283d4473edd1532cf1bce91ad1215e42e4fa15b
+ ref: 1635ca51f018bbb8d1ea5069a7f2ed8503be8cb9
required: false
../qtquick3d:
- ref: 6cd32d32544de86269e8a6c47a29aaffa41f8b86
+ ref: f2e9ce22f6c265f3b7aea34d2c1e6aad9c010779
required: false
../qtshadertools:
- ref: 1a3994894af3852ddde7ff0d78a7fbdf035a1a7e
+ ref: e512987ff675fee0b7aadf80a548e167a1f7d5d2
required: true
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index c34741a93..76d01581a 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -4,7 +4,7 @@
qt_examples_build_begin(EXTERNAL_BUILD)
add_subdirectory(multimedia)
-if (QT_FEATURE_spatialaudio)
+if(QT_FEATURE_spatialaudio)
add_subdirectory(spatialaudio)
endif()
diff --git a/examples/multimedia/declarative-camera/VideoCaptureControls.qml b/examples/multimedia/declarative-camera/VideoCaptureControls.qml
index a6608ad62..39acae106 100644
--- a/examples/multimedia/declarative-camera/VideoCaptureControls.qml
+++ b/examples/multimedia/declarative-camera/VideoCaptureControls.qml
@@ -58,7 +58,8 @@ FocusScope {
anchors.fill: parent
onClicked: captureControls.previewSelected()
//don't show View button during recording
- visible: captureControls.captureSession.recorder.actualLocation && !stopButton.visible
+ visible: captureControls.captureSession.recorder.actualLocation.toString() !== ""
+ && !stopButton.visible
}
}
}
diff --git a/examples/multimedia/video/mediaplayer/AudioControl.qml b/examples/multimedia/video/mediaplayer/AudioControl.qml
deleted file mode 100644
index e37ae032a..000000000
--- a/examples/multimedia/video/mediaplayer/AudioControl.qml
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
-
- required property MediaPlayer mediaPlayer
- property bool muted: false
- property real volume: volumeSlider.value/100.
-
- implicitHeight: buttons.height
-
- RowLayout {
- anchors.fill: parent
-
- Item {
- id: buttons
-
- width: muteButton.implicitWidth
- height: muteButton.implicitHeight
-
- RoundButton {
- id: muteButton
- radius: 50.0
- icon.source: muted ? "qrc:///Mute_Icon.svg" : "qrc:///Speaker_Icon.svg"
- onClicked: { muted = !muted }
- }
- }
-
- Slider {
- id: volumeSlider
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
-
- enabled: true
- to: 100.0
- value: 100.0
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/CMakeLists.txt b/examples/multimedia/video/mediaplayer/CMakeLists.txt
index 9688b0b0b..e33a20ad4 100644
--- a/examples/multimedia/video/mediaplayer/CMakeLists.txt
+++ b/examples/multimedia/video/mediaplayer/CMakeLists.txt
@@ -13,40 +13,56 @@ endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/video/mediaplayer")
-find_package(Qt6 REQUIRED COMPONENTS Core Multimedia Quick QuickControls2 Svg)
+find_package(Qt6 REQUIRED COMPONENTS Multimedia Core Quick QuickControls2 Svg)
-qt_add_executable(mediaplayer
+qt_standard_project_setup(REQUIRES 6.5)
+
+qt_add_executable(mediaplayerexample
main.cpp
)
-set(resource_files
- "main.qml"
- "PlaybackControl.qml"
- "MetadataInfo.qml"
- "AudioControl.qml"
- "PlaybackSeekControl.qml"
- "PlaybackRateControl.qml"
- "PlayerMenuBar.qml"
- "TracksInfo.qml"
- "Mute_Icon.svg"
- "Speaker_Icon.svg"
+set_target_properties(mediaplayerexample PROPERTIES
+ MACOSX_BUNDLE TRUE
)
-qt_add_resources(mediaplayer "mediaplayer"
- PREFIX
- "/"
- FILES
- ${resource_files}
+qt_add_qml_module(mediaplayerexample
+ URI mediaplayer
+ QML_FILES
+ "Main.qml"
+ "controls/PlaybackControl.qml"
+ "controls/AudioControl.qml"
+ "controls/PlaybackSeekControl.qml"
+ "controls/SettingsPopup.qml"
+ "controls/UrlPopup.qml"
+ "controls/MetadataInfo.qml"
+ "controls/TracksInfo.qml"
+ RESOURCES
+ "images/backward10.svg"
+ "images/mute.svg"
+ "images/open_new.svg"
+ "images/pause_symbol.svg"
+ "images/play_symbol.svg"
+ "images/forward10.svg"
+ "images/more.svg"
+ "images/speaker.svg"
+ "images/stop_symbol.svg"
+ "images/volume.svg"
+ "images/volume_mute.svg"
+ "images/zoom_maximize.svg"
+ "images/zoom_minimize.svg"
+ "images/link.svg"
+ "images/loop.svg"
)
-target_link_libraries(mediaplayer PRIVATE
+target_link_libraries(mediaplayerexample PRIVATE
Qt6::Core
Qt6::Multimedia
Qt6::Svg
Qt6::Quick
+ Qt6::Multimedia
)
-install(TARGETS mediaplayer
+install(TARGETS mediaplayerexample
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
diff --git a/examples/multimedia/video/mediaplayer/Main.qml b/examples/multimedia/video/mediaplayer/Main.qml
new file mode 100644
index 000000000..9ee9cf6c3
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/Main.qml
@@ -0,0 +1,178 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtMultimedia
+import "controls"
+
+//! [0]
+ApplicationWindow {
+ id: root
+ title: qsTr("Multimedia Player")
+ width: 1280
+ height: 720
+ //! [0]
+ minimumWidth: 960
+ minimumHeight: 540
+ visible: true
+ color: "black"
+
+ property alias source: mediaPlayer.source
+ property alias playbackRate: mediaPlayer.playbackRate
+ property bool fullScreen: false
+
+ MessageDialog {
+ id: mediaError
+ buttons: MessageDialog.Ok
+ }
+
+ MouseArea {
+ // an activity listener to hide the playback contols when idle
+ id: activityListener
+ anchors.fill: parent
+ z: 1
+ propagateComposedEvents: true
+ hoverEnabled: true
+
+ property bool inactiveMouse: false
+
+ Timer {
+ id: timer
+ interval: 3000 // milliseconds
+ onTriggered: activityListener.inactiveMouse = true
+ }
+
+ function activityHandler(mouse) {
+ if (activityListener.inactiveMouse)
+ activityListener.inactiveMouse = false
+ timer.restart()
+ timer.start()
+ mouse.accepted = false
+ }
+
+ onPositionChanged: mouse => activityHandler(mouse)
+ onPressed: mouse => activityHandler(mouse)
+ onDoubleClicked: mouse => mouse.accepted = false
+ }
+
+ MetadataInfo {
+ id: metadataInfo
+ }
+
+ TracksInfo {
+ id: audioTracksInfo
+ onSelectedTrackChanged: {
+ mediaPlayer.activeAudioTrack = selectedTrack
+ mediaPlayer.updateMetadata()
+ }
+ }
+
+ TracksInfo {
+ id: videoTracksInfo
+ onSelectedTrackChanged: {
+ mediaPlayer.activeVideoTrack = selectedTrack
+ mediaPlayer.updateMetadata()
+ }
+ }
+
+ TracksInfo {
+ id: subtitleTracksInfo
+ onSelectedTrackChanged: {
+ mediaPlayer.activeSubtitleTrack = selectedTrack
+ mediaPlayer.updateMetadata()
+ }
+ }
+
+ //! [1]
+ MediaPlayer {
+ id: mediaPlayer
+ //! [1]
+ function updateMetadata() {
+ metadataInfo.clear()
+ metadataInfo.read(mediaPlayer.metaData)
+ metadataInfo.read(mediaPlayer.audioTracks[mediaPlayer.activeAudioTrack])
+ metadataInfo.read(mediaPlayer.videoTracks[mediaPlayer.activeVideoTrack])
+ metadataInfo.read(mediaPlayer.subtitleTracks[mediaPlayer.activeSubtitleTrack])
+ }
+ //! [2]
+ videoOutput: videoOutput
+ audioOutput: AudioOutput {
+ id: audio
+ muted: playbackController.muted
+ volume: playbackController.volume
+ }
+ //! [2]
+ //! [4]
+ onErrorOccurred: {
+ mediaError.open()
+ mediaError.text = mediaPlayer.errorString
+ }
+ //! [4]
+ onMetaDataChanged: { updateMetadata() }
+ //! [6]
+ onTracksChanged: {
+ audioTracksInfo.read(mediaPlayer.audioTracks)
+ videoTracksInfo.read(mediaPlayer.videoTracks)
+ subtitleTracksInfo.read(mediaPlayer.subtitleTracks, 6) /* QMediaMetaData::Language = 6 */
+ updateMetadata()
+ mediaPlayer.play()
+ }
+ //! [6]
+ source: new URL("https://download.qt.io/learning/videos/media-player-example/Qt_LogoMergeEffect.mp4")
+ }
+
+ //! [3]
+ VideoOutput {
+ id: videoOutput
+ anchors.fill: parent
+ visible: mediaPlayer.mediaStatus > 0
+
+ TapHandler {
+ onDoubleTapped: {
+ root.fullScreen ? root.showNormal() : root.showFullScreen()
+ root.fullScreen = !root.fullScreen
+ }
+ }
+ }
+ //! [3]
+
+ Rectangle {
+ anchors.fill: parent
+ visible: mediaPlayer.mediaStatus === 0
+ color: "black"
+
+ TapHandler {
+ onDoubleTapped: {
+ root.fullScreen ? root.showNormal() : root.showFullScreen()
+ root.fullScreen = !root.fullScreen
+ }
+ }
+ }
+
+ //! [5]
+ PlaybackControl {
+ id: playbackController
+ //! [5]
+
+ property bool showControls: !activityListener.inactiveMouse || busy
+ opacity: showControls
+ // onOpacityChanged can't be used as it is animated and therefore not immediate
+ onShowControlsChanged: activityListener.cursorShape = showControls ?
+ Qt.ArrowCursor : Qt.BlankCursor
+
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ //! [6]
+ mediaPlayer: mediaPlayer
+ audioTracksInfo: audioTracksInfo
+ videoTracksInfo: videoTracksInfo
+ subtitleTracksInfo: subtitleTracksInfo
+ metadataInfo: metadataInfo
+ }
+ //! [6]
+}
diff --git a/examples/multimedia/video/mediaplayer/MetadataInfo.qml b/examples/multimedia/video/mediaplayer/MetadataInfo.qml
deleted file mode 100644
index 2ac4bc02d..000000000
--- a/examples/multimedia/video/mediaplayer/MetadataInfo.qml
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
- implicitWidth: 200
-
- function clear() {
- elements.clear();
- }
-
- function read(metadata) {
- if (metadata) {
- for (var key of metadata.keys()) {
- if (metadata.stringValue(key)) {
- elements.append(
- { name: metadata.metaDataKeyToString(key)
- , value: metadata.stringValue(key)
- })
- }
- }
- }
- }
-
- ListModel {
- id: elements
- }
-
- Frame {
- anchors.fill: parent
- padding: 15
-
- background: Rectangle {
- color: "lightgray"
- opacity: 0.7
- }
-
- ListView {
- id: metadataList
- visible: elements.count > 0
- anchors.fill: parent
- model: elements
- delegate: RowLayout {
- width: metadataList.width
- Text {
- Layout.preferredWidth: 90
- text: model.name + ":"
- horizontalAlignment: Text.AlignRight
- }
- Text {
- Layout.fillWidth: true
- text: model.value
- wrapMode: Text.WrapAnywhere
- }
- }
- }
-
- Text {
- id: metadataNoList
- visible: elements.count === 0
- text: qsTr("No metadata present")
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/PlaybackControl.qml b/examples/multimedia/video/mediaplayer/PlaybackControl.qml
deleted file mode 100644
index 386599bad..000000000
--- a/examples/multimedia/video/mediaplayer/PlaybackControl.qml
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
-
- required property MediaPlayer mediaPlayer
- property int mediaPlayerState: mediaPlayer.playbackState
- property alias muted: audio.muted
- property alias volume: audio.volume
-
- height: frame.height
-
- opacity: 1
-
- Behavior on opacity { NumberAnimation { duration: 300 }}
-
- function updateOpacity() {
- //hover is not usable in mobile platforms
- if (Qt.platform.os == "android" || Qt.platform.os == "ios")
- return;
-
- if (playbackControlHover.hovered || mediaPlayerState != MediaPlayer.PlayingState || !mediaPlayer.hasVideo)
- root.opacity = 1;
- else
- root.opacity = 1; // 0; TODO: enable opacity change when HoverHandle is fixed
- }
-
- Connections {
- target: mediaPlayer
- function onPlaybackStateChanged() { updateOpacity() }
- function onHasVideoChanged() { updateOpacity() }
- }
-
- HoverHandler {
- id: playbackControlHover
- margin: 50
- onHoveredChanged: updateOpacity()
- }
-
- Frame {
- id: frame
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
-
- background: Rectangle {
- color: "white"
- }
-
- ColumnLayout {
- id: playbackControlPanel
- anchors.fill: parent
- anchors.leftMargin: 10
- anchors.rightMargin: 10
-
- PlaybackSeekControl {
- Layout.fillWidth: true
- mediaPlayer: root.mediaPlayer
- }
-
- RowLayout {
- id: playerButtons
-
- Layout.fillWidth: true
-
- PlaybackRateControl {
- Layout.minimumWidth: 100
- Layout.maximumWidth: 150
- Layout.fillHeight: true
- Layout.fillWidth: true
- mediaPlayer: root.mediaPlayer
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- RowLayout {
- Layout.alignment: Qt.AlignCenter
- id: controlButtons
-
- RoundButton {
- id: pauseButton
- radius: 50.0
- text: "\u2016";
- onClicked: mediaPlayer.pause()
- }
-
- RoundButton {
- id: playButton
- radius: 50.0
- text: "\u25B6";
- onClicked: mediaPlayer.play()
- }
-
- RoundButton {
- id: stopButton
- radius: 50.0
- text: "\u25A0";
- onClicked: mediaPlayer.stop()
- }
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- AudioControl {
- id: audio
- Layout.minimumWidth: 100
- Layout.maximumWidth: 150
- Layout.fillWidth: true
- mediaPlayer: root.mediaPlayer
- }
- }
- }
- }
-
- states: [
- State {
- name: "playing"
- when: mediaPlayerState == MediaPlayer.PlayingState
- PropertyChanges { target: pauseButton; visible: true}
- PropertyChanges { target: playButton; visible: false}
- PropertyChanges { target: stopButton; visible: true}
- },
- State {
- name: "stopped"
- when: mediaPlayerState == MediaPlayer.StoppedState
- PropertyChanges { target: pauseButton; visible: false}
- PropertyChanges { target: playButton; visible: true}
- PropertyChanges { target: stopButton; visible: false}
- },
- State {
- name: "paused"
- when: mediaPlayerState == MediaPlayer.PausedState
- PropertyChanges { target: pauseButton; visible: false}
- PropertyChanges { target: playButton; visible: true}
- PropertyChanges { target: stopButton; visible: true}
- }
- ]
-
-}
-
diff --git a/examples/multimedia/video/mediaplayer/PlaybackRateControl.qml b/examples/multimedia/video/mediaplayer/PlaybackRateControl.qml
deleted file mode 100644
index 131ee17c4..000000000
--- a/examples/multimedia/video/mediaplayer/PlaybackRateControl.qml
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
-
- required property MediaPlayer mediaPlayer
-
- RowLayout {
- anchors.fill: parent
-
- Slider {
- id: slider
- Layout.fillWidth: true
- snapMode: Slider.SnapOnRelease
- enabled: true
- from: 0.5
- to: 2.5
- stepSize: 0.5
- value: 1.0
-
- onMoved: { mediaPlayer.setPlaybackRate(value) }
- }
- Text {
- text: "Rate " + mediaPlayer.playbackRate + "x"
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml b/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml
deleted file mode 100644
index ad583c79a..000000000
--- a/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
-
- required property MediaPlayer mediaPlayer
-
- implicitHeight: 20
-
-
- RowLayout {
- anchors.fill: parent
-
- Text {
- id: mediaTime
- Layout.minimumWidth: 50
- Layout.minimumHeight: 18
- horizontalAlignment: Text.AlignRight
- text: {
- var m = Math.floor(mediaPlayer.position / 60000)
- var ms = (mediaPlayer.position / 1000 - m * 60).toFixed(1)
- return `${m}:${ms.padStart(4, 0)}`
- }
- }
-
- Slider {
- id: mediaSlider
- Layout.fillWidth: true
- enabled: mediaPlayer.seekable
- to: 1.0
- value: mediaPlayer.position / mediaPlayer.duration
-
- onMoved: mediaPlayer.setPosition(value * mediaPlayer.duration)
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/PlayerMenuBar.qml b/examples/multimedia/video/mediaplayer/PlayerMenuBar.qml
deleted file mode 100644
index 822f9519b..000000000
--- a/examples/multimedia/video/mediaplayer/PlayerMenuBar.qml
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Dialogs
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
-
- required property MediaPlayer mediaPlayer
- required property VideoOutput videoOutput
- required property MetadataInfo metadataInfo
- required property TracksInfo audioTracksInfo
- required property TracksInfo videoTracksInfo
- required property TracksInfo subtitleTracksInfo
-
- height: menuBar.height
-
- signal closePlayer
-
- function loadUrl(url) {
- mediaPlayer.stop()
- mediaPlayer.source = url
- mediaPlayer.play()
- }
-
- function closeOverlays(){
- metadataInfo.visible = false;
- audioTracksInfo.visible = false;
- videoTracksInfo.visible = false;
- subtitleTracksInfo.visible = false;
- }
-
- function showOverlay(overlay){
- closeOverlays();
- overlay.visible = true;
- }
-
- Popup {
- id: urlPopup
- anchors.centerIn: Overlay.overlay
-
- RowLayout {
- id: rowOpenUrl
- Label {
- text: qsTr("URL:");
- }
-
- TextInput {
- id: urlText
- focus: true
- Layout.minimumWidth: 400
- wrapMode: TextInput.WrapAnywhere
- Keys.onReturnPressed: { loadUrl(text); urlText.text = ""; urlPopup.close() }
- }
-
- Button {
- text: "Load"
- onClicked: { loadUrl(urlText.text); urlText.text = ""; urlPopup.close() }
- }
- }
- onOpened: { urlPopup.forceActiveFocus() }
- }
-
- FileDialog {
- id: fileDialog
- title: "Please choose a file"
- onAccepted: {
- mediaPlayer.stop()
- mediaPlayer.source = fileDialog.currentFile
- mediaPlayer.play()
- }
- }
-
- MenuBar {
- id: menuBar
- anchors.left: parent.left
- anchors.right: parent.right
-
- Menu {
- title: qsTr("&File")
- Action {
- text: qsTr("&Open")
- onTriggered: fileDialog.open()
- }
- Action {
- text: qsTr("&URL");
- onTriggered: urlPopup.open()
- }
-
- Action {
- text: qsTr("&Exit");
- onTriggered: closePlayer()
- }
- }
-
- Menu {
- title: qsTr("&View")
- Action {
- text: qsTr("Metadata")
- onTriggered: showOverlay(metadataInfo)
- }
- }
-
- Menu {
- title: qsTr("&Tracks")
- Action {
- text: qsTr("Audio")
- onTriggered: showOverlay(audioTracksInfo)
- }
- Action {
- text: qsTr("Video")
- onTriggered: showOverlay(videoTracksInfo)
- }
- Action {
- text: qsTr("Subtitles")
- onTriggered: showOverlay(subtitleTracksInfo)
- }
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/TracksInfo.qml b/examples/multimedia/video/mediaplayer/TracksInfo.qml
deleted file mode 100644
index ee6273d25..000000000
--- a/examples/multimedia/video/mediaplayer/TracksInfo.qml
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-Item {
- id: root
- implicitWidth: 200
-
- property int selectedTrack: 0
-
- function read(metadataList) {
- var LanguageKey = 6;
-
- elements.clear()
-
- elements.append(
- { language: "No Selected Track"
- , trackNumber: -1
- })
-
- if (!metadataList)
- return;
-
- metadataList.forEach(function (metadata, index) {
- var language = metadata.stringValue(LanguageKey);
- var label = language ? metadata.stringValue(LanguageKey) : "track " + (index + 1)
- elements.append(
- { language: label
- , trackNumber: index
- })
- });
- }
-
- ListModel {
- id: elements
- }
-
- Frame {
- anchors.fill: parent
- padding: 15
-
- background: Rectangle {
- color: "lightgray"
- opacity: 0.7
- }
-
- ButtonGroup {id:group; }
-
- ListView {
- id: trackList
- visible: elements.count > 0
- anchors.fill: parent
- model: elements
- delegate: RowLayout {
- width: trackList.width
- RadioButton {
- checked: model.trackNumber === selectedTrack
- text: model.language
- ButtonGroup.group: group
- onClicked: selectedTrack = model.trackNumber
- }
- }
- }
-
- Text {
- id: metadataNoList
- visible: elements.count === 0
- text: qsTr("No tracks present")
- }
- }
-}
diff --git a/examples/multimedia/video/mediaplayer/controls/AudioControl.qml b/examples/multimedia/video/mediaplayer/controls/AudioControl.qml
new file mode 100644
index 000000000..81ebe7f10
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/AudioControl.qml
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ id: audioController
+
+ property alias busy: slider.pressed
+ //! [0]
+ property alias muted: muteButton.checked
+ property real volume: slider.value
+ //! [0]
+ property alias showSlider: slider.visible
+ property int iconDimension: 24
+
+ implicitHeight: 46
+ implicitWidth: mainLayout.width
+
+ RowLayout {
+ id: mainLayout
+ spacing: 10
+ anchors.verticalCenter: parent.verticalCenter
+
+ RoundButton {
+ id: muteButton
+ implicitHeight: 40
+ implicitWidth: 40
+ radius: 4
+ icon.source: audioController.muted ? "../images/volume_mute.svg" : "../images/volume.svg"
+ icon.width: audioController.iconDimension
+ icon.height: audioController.iconDimension
+ flat: true
+ checkable: true
+ }
+
+ Slider {
+ id: slider
+ visible: !audioController.showSlider
+ implicitWidth: 136
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter
+
+ enabled: !audioController.muted
+ value: 1
+ }
+ }
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/MetadataInfo.qml b/examples/multimedia/video/mediaplayer/controls/MetadataInfo.qml
new file mode 100644
index 000000000..3e68faa8e
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/MetadataInfo.qml
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ property alias metadata: listModel
+ property alias count: listModel.count
+
+ function clear() {
+ listModel.clear()
+ }
+
+ //! [0]
+ function read(metadata) {
+ if (!metadata)
+ return
+ for (const key of metadata.keys())
+ if (metadata.stringValue(key))
+ listModel.append({
+ name: metadata.metaDataKeyToString(key),
+ value: metadata.stringValue(key)
+ })
+ }
+
+ ListModel {
+ id: listModel
+ }
+ //! [0]
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/PlaybackControl.qml b/examples/multimedia/video/mediaplayer/controls/PlaybackControl.qml
new file mode 100644
index 000000000..056cf50d0
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/PlaybackControl.qml
@@ -0,0 +1,318 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtMultimedia
+import QtQuick.Dialogs
+
+//! [0]
+Item {
+ id: playbackController
+
+ required property MediaPlayer mediaPlayer
+ required property MetadataInfo metadataInfo
+ required property TracksInfo audioTracksInfo
+ required property TracksInfo videoTracksInfo
+ required property TracksInfo subtitleTracksInfo
+ //! [0]
+
+ property alias muted: audioControl.muted
+ property alias volume: audioControl.volume
+
+ property bool landscapePlaybackControls: root.width >= 668
+ property bool busy: fileDialog.visible
+ || urlPopup.visible
+ || settingsPopup.visible
+ || audioControl.busy
+ || playbackSeekControl.busy
+ || !playbackController.mediaPlayer.playing
+
+ implicitHeight: landscapePlaybackControls ? 168 : 208
+
+ Behavior on opacity { NumberAnimation { duration: 300 } }
+
+ FileDialog {
+ id: fileDialog
+ title: "Please choose a file"
+ onAccepted: {
+ playbackController.mediaPlayer.stop()
+ playbackController.mediaPlayer.source = fileDialog.currentFile
+ playbackController.mediaPlayer.play()
+ }
+ }
+
+ UrlPopup {
+ id: urlPopup
+ anchors.centerIn: Overlay.overlay
+ mediaPlayer: playbackController.mediaPlayer
+ }
+
+ SettingsPopup {
+ id: settingsPopup
+ anchors.centerIn: Overlay.overlay
+
+ metadataInfo: playbackController.metadataInfo
+ mediaPlayer: playbackController.mediaPlayer
+ audioTracksInfo: playbackController.audioTracksInfo
+ videoTracksInfo: playbackController.videoTracksInfo
+ subtitleTracksInfo: playbackController.subtitleTracksInfo
+ }
+
+ component CustomButton: RoundButton {
+ implicitWidth: 40
+ implicitHeight: 40
+ radius: 4
+ icon.width: 24
+ icon.height: 24
+ flat: true
+ }
+
+ component CustomRoundButton: RoundButton {
+ property int diameter: 40
+ Layout.preferredWidth: diameter
+ Layout.preferredHeight: diameter
+ radius: diameter / 2
+ icon.width: 24
+ icon.height: 24
+ }
+
+ //! [1]
+ CustomButton {
+ id: fileDialogButton
+ icon.source: "../images/open_new.svg"
+ flat: false
+ onClicked: fileDialog.open()
+ }
+
+ CustomButton {
+ id: openUrlButton
+ icon.source: "../images/link.svg"
+ flat: false
+ onClicked: urlPopup.open()
+ }
+ //! [1]
+
+ CustomButton {
+ id: loopButton
+ icon.source: "../images/loop.svg"
+ icon.color: playbackController.mediaPlayer.loops === MediaPlayer.Once ? palette.buttonText : palette.accent
+ onClicked: playbackController.mediaPlayer.loops = playbackController.mediaPlayer.loops === MediaPlayer.Once
+ ? MediaPlayer.Infinite
+ : MediaPlayer.Once
+ }
+
+ CustomButton {
+ id: settingsButton
+ icon.source: "../images/more.svg"
+ onClicked: settingsPopup.open()
+ }
+
+ CustomButton {
+ id: fullScreenButton
+ icon.source: root.fullScreen ? "../images/zoom_minimize.svg"
+ : "../images/zoom_maximize.svg"
+ onClicked: {
+ root.fullScreen ? root.showNormal() : root.showFullScreen()
+ root.fullScreen = !root.fullScreen
+ }
+ }
+
+ RowLayout {
+ id: controlButtons
+ spacing: 16
+
+ CustomRoundButton {
+ id: backward10Button
+ icon.source: "../images/backward10.svg"
+ onClicked: {
+ const pos = Math.max(0, playbackController.mediaPlayer.position - 10000)
+ playbackController.mediaPlayer.setPosition(pos)
+ }
+ }
+
+ //! [2]
+ CustomRoundButton {
+ id: playButton
+ visible: playbackController.mediaPlayer.playbackState !== MediaPlayer.PlayingState
+ icon.source: "../images/play_symbol.svg"
+ onClicked: playbackController.mediaPlayer.play()
+ }
+
+ CustomRoundButton {
+ id: pauseButton
+ visible: playbackController.mediaPlayer.playbackState === MediaPlayer.PlayingState
+ icon.source: "../images/pause_symbol.svg"
+ onClicked: playbackController.mediaPlayer.pause()
+ }
+ //! [2]
+
+ //! [3]
+ CustomRoundButton {
+ id: forward10Button
+ icon.source: "../images/forward10.svg"
+ onClicked: {
+ const pos = Math.min(playbackController.mediaPlayer.duration,
+ playbackController.mediaPlayer.position + 10000)
+ playbackController.mediaPlayer.setPosition(pos)
+ }
+ }
+ //! [3]
+ } // RowLayout controlButtons
+
+ AudioControl {
+ id: audioControl
+ showSlider: root.width >= 960
+ }
+
+ PlaybackSeekControl {
+ id: playbackSeekControl
+ Layout.fillWidth: true
+ mediaPlayer: playbackController.mediaPlayer
+ }
+
+ Frame {
+ id: landscapeLayout
+ anchors.fill: parent
+ padding: 32
+ topPadding: 28
+ visible: landscapePlaybackControls
+ background: Rectangle {
+ color: "#F6F6F6"
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 16
+
+ Item {
+ Layout.fillWidth: true
+ implicitHeight: 40
+
+ LayoutItemProxy {
+ id: fdbProxy
+ target: fileDialogButton
+ anchors.left: parent.left
+ }
+
+ LayoutItemProxy {
+ target: openUrlButton
+ anchors.left: fdbProxy.right
+ anchors.leftMargin: 12
+ }
+
+ LayoutItemProxy {
+ target: controlButtons
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ LayoutItemProxy {
+ target: loopButton
+ anchors.right: acProxy.left
+ anchors.rightMargin: 12
+ }
+
+ LayoutItemProxy {
+ id: acProxy
+ target: audioControl
+ anchors.right: sbProxy.left
+ anchors.rightMargin: 30
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ LayoutItemProxy {
+ id: sbProxy
+ target: settingsButton
+ anchors.right: fbProxy.left
+ anchors.rightMargin: 12
+ }
+
+ LayoutItemProxy {
+ id: fbProxy
+ target: fullScreenButton
+ anchors.right: parent.right
+ }
+ } // Item
+
+ LayoutItemProxy {
+ target: playbackSeekControl
+ Layout.topMargin: 16
+ Layout.bottomMargin: 16
+ }
+ }
+ } // Frame frame
+
+ Frame {
+ id: portraitLayout
+ anchors.fill: parent
+ padding: 32
+ topPadding: 28
+ visible: !landscapePlaybackControls
+ background: Rectangle {
+ color: "#F6F6F6"
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 16
+
+ Item {
+ Layout.fillWidth: true
+ implicitHeight: 40
+
+ LayoutItemProxy {
+ target: loopButton
+ anchors.right: cbProxy.left
+ anchors.rightMargin: 16
+ }
+
+ LayoutItemProxy {
+ id: cbProxy
+ target: controlButtons
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ LayoutItemProxy {
+ target: audioControl
+ anchors.left: cbProxy.right
+ anchors.leftMargin: 16
+ }
+ }
+ Item {
+ Layout.fillWidth: true
+ implicitHeight: 40
+
+ LayoutItemProxy {
+ id: fdbProxy_
+ target: fileDialogButton
+ anchors.left: parent.left
+ }
+
+ LayoutItemProxy {
+ target: openUrlButton
+ anchors.left: fdbProxy_.right
+ anchors.leftMargin: 12
+ }
+
+ LayoutItemProxy {
+ target: settingsButton
+ anchors.right: fbProxy_.left
+ anchors.rightMargin: 12
+ }
+
+ LayoutItemProxy {
+ id: fbProxy_
+ target: fullScreenButton
+ anchors.right: parent.right
+ }
+ }
+
+ LayoutItemProxy {
+ target: playbackSeekControl
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ }
+ }
+ }
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/PlaybackSeekControl.qml b/examples/multimedia/video/mediaplayer/controls/PlaybackSeekControl.qml
new file mode 100644
index 000000000..c94d844f8
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/PlaybackSeekControl.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtMultimedia
+
+Item {
+ id: seekController
+ required property MediaPlayer mediaPlayer
+ property alias busy: slider.pressed
+
+ implicitHeight: 20
+
+ function formatToMinutes(milliseconds) {
+ const min = Math.floor(milliseconds / 60000)
+ const sec = ((milliseconds - min * 60000) / 1000).toFixed(1)
+ return `${min}:${sec.padStart(4, 0)}`
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 22
+
+ //! [0]
+ Text {
+ id: currentTime
+ Layout.preferredWidth: 45
+ text: seekController.formatToMinutes(seekController.mediaPlayer.position)
+ horizontalAlignment: Text.AlignLeft
+ font.pixelSize: 11
+ }
+ //! [0]
+
+ Slider {
+ id: slider
+ Layout.fillWidth: true
+ //! [2]
+ enabled: seekController.mediaPlayer.seekable
+ value: seekController.mediaPlayer.position / seekController.mediaPlayer.duration
+ //! [2]
+ onMoved: seekController.mediaPlayer.setPosition(value * seekController.mediaPlayer.duration)
+ }
+
+ //! [1]
+ Text {
+ id: remainingTime
+ Layout.preferredWidth: 45
+ text: seekController.formatToMinutes(seekController.mediaPlayer.duration - seekController.mediaPlayer.position)
+ horizontalAlignment: Text.AlignRight
+ font.pixelSize: 11
+ }
+ //! [1]
+ }
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/SettingsPopup.qml b/examples/multimedia/video/mediaplayer/controls/SettingsPopup.qml
new file mode 100644
index 000000000..0f24c8503
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/SettingsPopup.qml
@@ -0,0 +1,204 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtMultimedia
+
+Popup {
+ id: settingsController
+ focus: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+
+ background: Rectangle {
+ color: "#F6F6F6"
+ }
+
+
+ required property MetadataInfo metadataInfo
+ required property MediaPlayer mediaPlayer
+ required property TracksInfo audioTracksInfo
+ required property TracksInfo videoTracksInfo
+ required property TracksInfo subtitleTracksInfo
+
+ property int vPadding: 20
+ property int hPadding: 26
+ property bool landscapeSettingsPopup: root.width >= settingsLayout.width + metadataLayout.width + 2 * hPadding + 20 + 24
+
+ padding: {
+ top: vPadding
+ bottom: vPadding
+ left: hPadding
+ right: hPadding
+ }
+
+ Flickable {
+ id: flickable
+ implicitWidth: mainLayout.width
+ implicitHeight: landscapeSettingsPopup ? 200 : 340
+ contentWidth: mainLayout.width
+ contentHeight: mainLayout.height
+ flickableDirection: Flickable.VerticalFlick
+ clip: true
+
+ GridLayout {
+ id: mainLayout
+
+ columns: landscapeSettingsPopup ? 2 : 1
+ columnSpacing: 24
+ rowSpacing: 24
+
+ ColumnLayout {
+ id: settingsLayout
+ spacing: 16
+ Layout.alignment: Qt.AlignTop
+
+ Label {
+ id: settingsLabel
+ text: qsTr("Settings")
+ font.pixelSize: 16
+ font.bold: true
+ }
+
+ GridLayout {
+ id: gridLayout
+ columns: 2
+ rowSpacing: 16
+ columnSpacing: 16
+
+ component CustomComboBox: ComboBox {
+ required property TracksInfo tracksInfo
+
+ model: tracksInfo.model
+ enabled: model.count > 0
+ textRole: "data"
+ currentIndex: model.count > 0 ? 0 : -1
+
+ onActivated: {
+ //! [1]
+ settingsController.mediaPlayer.pause()
+ tracksInfo.selectedTrack = currentIndex
+ settingsController.mediaPlayer.play()
+ //! [1]
+ }
+ }
+
+ Label {
+ text: qsTr("Playback Speed")
+ Layout.fillWidth: true
+ font.pixelSize: 14
+ }
+
+ ComboBox {
+ id: rateCb
+ model: ["0.25", "0.5", "0.75", "Normal", "1.25", "1.5", "1.75", "2"]
+ currentIndex: 3
+
+ onCurrentIndexChanged: {
+ //! [0]
+ settingsController.mediaPlayer.playbackRate = (currentIndex + 1) * 0.25
+ //! [0]
+ }
+ }
+
+ Label {
+ text: qsTr("Audio Tracks")
+ enabled: audioCb.enabled
+ Layout.fillWidth: true
+ font.pixelSize: 14
+ }
+
+ CustomComboBox {
+ id: audioCb
+ tracksInfo: settingsController.audioTracksInfo
+
+ }
+
+ Label {
+ text: qsTr("Video Tracks")
+ enabled: videoCb.enabled
+ Layout.fillWidth: true
+ font.pixelSize: 14
+ }
+
+ CustomComboBox {
+ id: videoCb
+ tracksInfo: settingsController.videoTracksInfo
+ }
+
+ Label {
+ text: qsTr("Subtitle Tracks")
+ enabled: subtitlesCb.enabled
+ font.pixelSize: 14
+ }
+
+ CustomComboBox {
+ id: subtitlesCb
+ tracksInfo: settingsController.subtitleTracksInfo
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: metadataLayout
+ spacing: 16
+ Layout.alignment: Qt.AlignTop
+
+ Label {
+ id: metadataLabel
+ text: qsTr("Metadata")
+ font.pixelSize: 16
+ font.bold: true
+ }
+
+ Rectangle {
+ id: metadataRect
+ implicitWidth: 240
+ implicitHeight: metadataList.height
+ border.color: "#8E8E93"
+ radius: 6
+
+ Column {
+ id: metadataList
+ visible: settingsController.metadataInfo.count > 0
+
+ padding: 10
+ Repeater {
+ Row {
+ spacing: metadataList.padding
+ Text {
+ text: model.name
+ font.bold: true
+ width: (metadataRect.width - 3 * metadataList.padding) / 2
+ horizontalAlignment: Text.AlignRight
+ wrapMode: Text.WordWrap
+ font.pixelSize: 12
+ }
+
+ Text {
+ text: model.value
+ width: (metadataRect.width - 3 * metadataList.padding) / 2
+ horizontalAlignment: Text.AlignLeft
+ anchors.verticalCenter: parent.verticalCenter
+ wrapMode: Text.WrapAnywhere
+ font.pixelSize: 12
+ }
+ }
+ model: settingsController.metadataInfo.metadata
+ }
+ }
+
+ Text {
+ id: metadataNoList
+ visible: settingsController.metadataInfo.count === 0
+ anchors.centerIn: parent
+ text: qsTr("No metadata present")
+ font.pixelSize: 14
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/TracksInfo.qml b/examples/multimedia/video/mediaplayer/controls/TracksInfo.qml
new file mode 100644
index 000000000..a2ae96f02
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/TracksInfo.qml
@@ -0,0 +1,25 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ property alias model: model
+ property int selectedTrack: 0
+
+ function read(tracks, key = 0) {
+ // language is the 6th index in the enum QMediaMetaData::Key
+ model.clear()
+
+ if (!tracks)
+ return
+
+ tracks.forEach((metadata, index) => {
+ const data = metadata.stringValue(key)
+ const label = data ? data : qsTr("track ") + (index + 1)
+ model.append({data: label, index: index})
+ })
+ }
+
+ ListModel { id: model }
+}
diff --git a/examples/multimedia/video/mediaplayer/controls/UrlPopup.qml b/examples/multimedia/video/mediaplayer/controls/UrlPopup.qml
new file mode 100644
index 000000000..5bc304178
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/controls/UrlPopup.qml
@@ -0,0 +1,54 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtMultimedia
+
+
+Popup {
+ id: popupController
+ width: Math.min(500, root.width - 40)
+
+ required property MediaPlayer mediaPlayer
+
+ function loadUrl(url) {
+ popupController.mediaPlayer.stop()
+ popupController.mediaPlayer.source = url
+ popupController.mediaPlayer.play()
+ }
+
+ RowLayout {
+ id: rowOpenUrl
+ anchors.fill: parent
+ Label {
+ text: qsTr("URL:");
+ }
+
+ TextField {
+ id: urlText
+ Layout.fillWidth: true
+ focus: true
+
+ placeholderText: qsTr("Enter text here...")
+ wrapMode: TextInput.WrapAnywhere
+
+ Keys.onReturnPressed: {
+ popupController.loadUrl(text)
+ urlText.text = ""
+ popupController.close()
+ }
+ }
+
+ Button {
+ text: qsTr("Load")
+ enabled: urlText.text !== ""
+ onClicked: {
+ popupController.loadUrl(urlText.text)
+ urlText.text = ""
+ popupController.close()
+ }
+ }
+ }
+ onOpened: { popupController.forceActiveFocus() }
+}
diff --git a/examples/multimedia/video/mediaplayer/doc/images/OqosZsDqvzQ.jpg b/examples/multimedia/video/mediaplayer/doc/images/OqosZsDqvzQ.jpg
deleted file mode 100644
index a53e85803..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/OqosZsDqvzQ.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/PlayerMenuBar.gif b/examples/multimedia/video/mediaplayer/doc/images/PlayerMenuBar.gif
deleted file mode 100644
index 779713332..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/PlayerMenuBar.gif
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/architecture-overview.gif b/examples/multimedia/video/mediaplayer/doc/images/architecture-overview.gif
deleted file mode 100644
index b85ce61c5..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/architecture-overview.gif
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/audio-control.gif b/examples/multimedia/video/mediaplayer/doc/images/audio-control.gif
deleted file mode 100644
index 5ac07ccc2..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/audio-control.gif
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/mediaplayer.png b/examples/multimedia/video/mediaplayer/doc/images/mediaplayer.png
new file mode 100644
index 000000000..857e70abd
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/doc/images/mediaplayer.png
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/meta-data.png b/examples/multimedia/video/mediaplayer/doc/images/meta-data.png
deleted file mode 100644
index a96b8f7e1..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/meta-data.png
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/nHrBbW0H-pc.jpg b/examples/multimedia/video/mediaplayer/doc/images/nHrBbW0H-pc.jpg
deleted file mode 100644
index 2d530a46d..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/nHrBbW0H-pc.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/play-pause-stop.gif b/examples/multimedia/video/mediaplayer/doc/images/play-pause-stop.gif
deleted file mode 100644
index 355f7af86..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/play-pause-stop.gif
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/playbackControlPanel.gif b/examples/multimedia/video/mediaplayer/doc/images/playbackControlPanel.gif
deleted file mode 100644
index c71e577d7..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/playbackControlPanel.gif
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/qmlmediaplayer.jpg b/examples/multimedia/video/mediaplayer/doc/images/qmlmediaplayer.jpg
deleted file mode 100644
index 94e9c04a0..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/qmlmediaplayer.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/settings.png b/examples/multimedia/video/mediaplayer/doc/images/settings.png
new file mode 100644
index 000000000..5cf175795
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/doc/images/settings.png
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/sf_yv01UtIw.jpg b/examples/multimedia/video/mediaplayer/doc/images/sf_yv01UtIw.jpg
deleted file mode 100644
index ab7342d5d..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/sf_yv01UtIw.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/images/url.png b/examples/multimedia/video/mediaplayer/doc/images/url.png
deleted file mode 100644
index 7bd4ff9f4..000000000
--- a/examples/multimedia/video/mediaplayer/doc/images/url.png
+++ /dev/null
Binary files differ
diff --git a/examples/multimedia/video/mediaplayer/doc/qmlmediaplayer.qdocconf b/examples/multimedia/video/mediaplayer/doc/qmlmediaplayer.qdocconf
deleted file mode 100644
index 3faf2054f..000000000
--- a/examples/multimedia/video/mediaplayer/doc/qmlmediaplayer.qdocconf
+++ /dev/null
@@ -1,4 +0,0 @@
-{HTML.extraimages,DocBook.extraFiles,qhp.QtMultimedia.extraFiles} += \
- images/OqosZsDqvzQ.jpg \
- images/sf_yv01UtIw.jpg \
- images/nHrBbW0H-pc.jpg
diff --git a/examples/multimedia/video/mediaplayer/doc/src/mediaplayer.qdoc b/examples/multimedia/video/mediaplayer/doc/src/mediaplayer.qdoc
index cec04fad8..1cc681f8f 100644
--- a/examples/multimedia/video/mediaplayer/doc/src/mediaplayer.qdoc
+++ b/examples/multimedia/video/mediaplayer/doc/src/mediaplayer.qdoc
@@ -6,176 +6,146 @@
\title QML Media Player Example
\ingroup multimedia_examples
\ingroup video_examples_qml
- \examplecategory {Multimedia}
- \brief Playing audio and video using Qt Quick.
+ \examplecategory {Graphics & Multimedia}
+ \brief Playing audio and video using the QML \c MediaPlayer type.
\meta {tag} {quick}
\meta {tag} {player}
- \image qmlmediaplayer.jpg
+ \image mediaplayer.png
This example demonstrates a simple multimedia player that can play
audio and video files using various codecs.
\include examples-run.qdocinc
- \section1 Overview
- At its core this is a QML application, see
- \l{Getting Started Programming with Qt Quick} for information specific to
- that. This documentation is focused on how this example utilizes the
- \l{Qt Multimedia QML Types}.
-
- \image architecture-overview.gif
-
- \section1 Using MediaPlayer and VideoOutput
- In \c main.qml a MediaPlayer instance is connected to a VideoOutput to
- play back the video:
-
- \quotefromfile video/mediaplayer/main.qml
- \skipto MediaPlayer
- \printuntil videoOutput: videoOutput
-
- \c videoOutput is declared like so:
-
- \skipto VideoOutput {
- \printto MetadataInfo {
-
- \section1 PlayerMenuBar
- \image PlayerMenuBar.gif
- This QML type handles media selection from a url or local file, exiting the
- application, viewing meta data, and the selection of available video, audio
- or subtitle tracks.
-
- \quotefromfile video/mediaplayer/PlayerMenuBar.qml
- Accessing the mediaPlayer object is done through properties:
- \skipto required property
- \printuntil TracksInfo subtitleTracksInfo
-
- \section2 fileDialog
- A FileDialog, \c fileDialog, is created with an \c onAccepted function that
- will stop \c mediaPlayer, load the source by setting the
- \l{MediaPlayer::source}{source} property and then play it automatically:
- \skipto FileDialog
- \printto MenuBar
-
- This is triggered in the Menu \c File, which is a child of the MenuBar:
- \skipto MenuBar
- \printto }
-
- \section2 loadUrl
- \image url.png
- While \c urlPopup handles prompting and capturing a url, it is the \c loadUrl
- function that interacts with \c mediaPlayer like so:
- \quotefromfile video/mediaplayer/PlayerMenuBar.qml
- \skipto function loadUrl
- \printto function closeOverlays(){
-
- \section2 Getting meta data
- \image meta-data.png
-
- In the declaration of \c mediaPlayer, in \c main.qml, there is the function
- \c updateMetadata():
-
- \quotefromfile video/mediaplayer/main.qml
- \skipto function updateMetadata(
- \printto }
-
- It is called in the following places:
- \skipto onMetaDataChanged:
- \printto onActiveTracksChanged: { updateMetadata() }
-
- Reading MetaData is done by the \c MetadataInfo type's \c read() function
- \quotefromfile video/mediaplayer/MetadataInfo.qml
- \skipto function read(metadata) {
- \printto ListModel
-
- The information is displayed via an \l[QML]{Overlay} item.
-
- \section2 Tracks information and control
- \youtube OqosZsDqvzQ
- This is defined in \c TracksInfo.qml and reading available tracks is done in
- a similar way to \c MetadataInfo:
- \quotefromfile video/mediaplayer/TracksInfo.qml
- \skipto function read(metadata
- \printto ListModel
-
- To set a track, the property \c selectedTrack is set like so:
- \skipto ListView
- \printto Text
-
- The \c onSelectectedTrackChanged signal, in each relevant \c TracksInfo
- instance in \c main.qml, is what makes changes to \c mediaPlayer like so:
- \quotefromfile video/mediaplayer/main.qml
- \skipto id: audioTracksInfo
- \printuntil audioTracksInfo.selectedTrack
-
- \section1 playbackControlPanel
- \image playbackControlPanel.gif
-
- This item has controls for \l{Playback control}, \l{Play Pause Stop},
- \l{Playback rate control} and \l{Playback seek control}.
-
- \section2 Playback control
- This qml type handles media playback and interacts with the MediaPlayer in
- \c main.qml.
-
- Here are the property definitions.
- \quotefromfile video/mediaplayer/PlaybackControl.qml
- \skipto required property
- \printto property alias
-
- Connections:
- \skipuntil Connections
- \printto HoverHandler
-
- \section2 Play Pause Stop
- \image play-pause-stop.gif
- \l{MediaPlayer::play()}{Play}, \l{MediaPlayer::stop()}{stop} and
- \l{MediaPlayer::pause()}{pause} interactions with the MediaPlayer object
- are done like so:
- \skipto RoundButton
- \printto Item {
-
- Playback states done using \l{MediaPlayer::playbackState}{playbackstate}
- like so:
- \skipuntil states:
- \printuntil ]
-
-
- \section2 Playback seek control
-
- \youtube sf_yv01UtIw
-
- Defined in \c PlaybackSeekControl.qml, this component comprises of an item
- with a Text, \c mediaTime, and \l[QML]{Slider}, \c mediaSlider, in a RowLayout.
-
- \c mediaTime uses MediaPlayer's \l{MediaPlayer::position}{position} property
- like so:
- \quotefromfile video/mediaplayer/PlaybackSeekControl.qml
- \skipto Text {
- \printto Slider
-
- \c mediaSlider uses the MediaPlayer \l{MediaPlayer::seekable}{seekable},
- \l{MediaPlayer::duration}{duration}, and \l{MediaPlayer::position}{position}
- properties like so:
- \skipto Slider
- \printuntil }
-
- \section2 Playback rate control
- \youtube nHrBbW0H-pc
-
- This type is defined in \c PlaybackRateControl.qml like so:
-
- \quotefromfile video/mediaplayer/PlaybackRateControl.qml
- \skipto Slider
- \printuntil text:
+ \section1 Instantiating the MediaPlayer
+ The entry point for the QML code in this example is \c Main.qml. Here
+ an \c ApplicationWindow is created and properties such as the \c id, \c title,
+ \c width and \c height are set.
- \section2 Audio control
- \image audio-control.gif
- This type is defined in \c AudioControl.qml, and utilizes the
- \l{AudioOutput::muted}{muted} and \l{AudioOutput::volume}{volume} properties
- of the AudioOutput instantiated within the MediaPlayer, which is
- instantiated in \c{main.qml}.
-
- \quotefromfile video/mediaplayer/AudioControl.qml
- \skipto required
- \printuntil value: 100.0
+ \snippet video/mediaplayer/Main.qml 0
+
+ Next the \c MediaPlayer is created and the two properties that are responsible for the video and
+ audio output are defined.
+ Firstly, \c videoOutput which renders the video viewfinder and secondly \c audioOutput which provides
+ the audio output for the player.
+
+ \snippet video/mediaplayer/Main.qml 1
+ \dots
+ \snippet video/mediaplayer/Main.qml 2
+ \dots
+ \snippet video/mediaplayer/Main.qml 3
+
+ The \c visible property of the \c VideoOutput type is set to \c true when the \c mediaStatus
+ property of the \c MediaPlayer is greater than 0. \c mediaStatus is of enumeration type
+ and is equal to 0 when \e {No media has been set}, and greater than 0 otherwise. Therefore,
+ the \c VideoOutput is visible when media has been set.
+
+ The \c MediaPlayer type has a signal property called \c onErrorOccurred that can be
+ overridden specifically to handle errors. In this case the signal opens a \c MessageDialog using
+ the method \c {open()} and sets its \c text property to a \c MediaPlayer property called
+ \c errorString.
+
+ \snippet video/mediaplayer/Main.qml 4
+
+ \section1 Playback Controls
+
+ In order to have a useable media player, there needs to be an interface to control the playback.
+ This is created in its own component file, \c PlaybackControl.qml, and instantiated in
+ \c Main.qml.
+
+ \snippet video/mediaplayer/Main.qml 5
+ \dots
+ \snippet video/mediaplayer/Main.qml 6
+
+ When created, objects are forwarded to this type such as track information,
+ metadata information and the \c MediaPlayer object itself. In \c PlaybackControl.qml,
+ each one of these objects have a \c {required property}, meaning that these properties must
+ be set when the \c PlaybackControl object is created.
+
+ \snippet video/mediaplayer/controls/PlaybackControl.qml 0
+
+ These playback controls can be broken down into sections. In the top left of the panel lies
+ a collection of buttons used to open a file, either by selecting a file from a file explorer or
+ entering a URL. The file is loaded into the \c MediaPlayer by setting the \c source property.
+ Both buttons are instantiated using a \c CustomButton \c {custom component}.
+
+ \snippet video/mediaplayer/controls/PlaybackControl.qml 1
+
+ Three buttons are created and centered on this panel, handling play, pause and seeking ten
+ seconds backwards or forwards. The media is played and paused using the methods \c {play()}
+ and \c {pause()}, respectively. To know when to draw a play or pause button, the \c playbackState
+ property is queried. For example, when it is equal to the enum value \c MediaPlayer.PlayingState
+ then the pause button is drawn.
+
+ \snippet video/mediaplayer/controls/PlaybackControl.qml 2
+
+ To navigate ten seconds forward or backwards, the \c position of the \c MediaPlayer type is
+ incremented by 10,000 milliseconds and set using the method \c {setPosition()}.
+
+ \snippet video/mediaplayer/controls/PlaybackControl.qml 3
+
+ \section1 Playback Seeking and Audio
+
+ In \c {PlaybackControl.qml}, an \c AudioControl and a \c PlaybackSeekControl type are instantiated.
+ These are both defined in their own component file and are responsible for volume control and
+ playback seeking, respectively. The \c AudioControl type defines a button to mute and a \c Slider,
+ from \c {QtQuick Controls}, to set the volume of the player. Both of these attributes are exposed by
+ defining a \c mute and \c volume property and are accessed from the \c AudioOutput definition in
+ \c {Main.qml}.
+
+ \snippet video/mediaplayer/controls/AudioControl.qml 0
+
+ The \c PlaybackSeekControl uses a \c RowLayout containing a \c Slider with a \c Text item either side.
+ The two \c Text items display the current time and the remaining time of the media being played. These
+ are both calculated using two properties of the \c MediaPlayer type, \c {position}, which gives the
+ current playback position in milliseconds, and \c {duration}, which gives the duration of the media
+ in milliseconds.
+
+ \snippet video/mediaplayer/controls/PlaybackSeekControl.qml 0
+ \dots
+ \snippet video/mediaplayer/controls/PlaybackSeekControl.qml 1
+
+ The \c Slider is only enabled when the media player is seekable and not, for example, live media.
+ The \c MediaPlayer type has a property for this called \c seekable. The \c value of the \c Slider
+ is calculated using the \c position and \c duration properties of the \c MediaPlayer.
+
+ \snippet video/mediaplayer/controls/PlaybackSeekControl.qml 2
+
+ \section1 Metadata and Track Information
+
+ The \c PlaybackControl type instantiates a \c SettingsPopup, which contains information about the
+ metadata of the currently loaded media and track selection, as well as the ability to update the
+ playback rate. This \c Popup is defined in \c SettingsPopup.qml.
+
+ \image settings.png
+
+ The metadata is contained in its own component file, \c MetadataInfo.qml. It contains a \c ListModel,
+ a function to clear it, \c {clear()}, and a function to populate it, \c {read(MediaMetadata metadata)}.
+ The \c {read(MediaMetadata metadata)} function takes as a parameter an object of type \c MediaMetaData,
+ and navigates its key-value structure to extract its data into the \c model of the \c {ListView}. The
+ methods used to do this are \c {keys()}, which returns all the keys of the \c MediaMetaData, and
+ {stringValue(Key key)}, which returns the \c value for a given \c key.
+
+ \snippet video/mediaplayer/controls/MetadataInfo.qml 0
+
+ The data is then displayed in \c {SettingsPopup.qml} in a \c ListView type. The \c delegate of this
+ \c ListView is a row of two \c Text items, corresponding to the key-value pairs abstracted from the
+ \c MediaMetaData item.
+
+ On the other side of the \c Popup there is playback rate controls and track selection for audio, video and
+ subtitles. The playback rate is chosen from a \c ComboBox and set using the property \c playbackRate.
+
+ \snippet video/mediaplayer/controls/SettingsPopup.qml 0
+
+ The type called \c TracksInfo, defined in \c {TracksInfo.qml}, contains the data about the tracks.
+ More specifically, a \c ListModel containing the titles of the tracks, or for subtitles specifically, the
+ langauges. This information is populated in \c Main.qml by calling the \c {read(MediaMetadata mediaMetadata)}
+ function defined in the \c TracksInfo type.
+
+ \snippet video/mediaplayer/Main.qml 6
+
+ The \c model defined in \c TracksInfo is then queried in the \c {ComboBox}es in the
+ \c SettingsPopup to select the current track.
+
+ \snippet video/mediaplayer/controls/SettingsPopup.qml 1
*/
diff --git a/examples/multimedia/video/mediaplayer/images/backward10.svg b/examples/multimedia/video/mediaplayer/images/backward10.svg
new file mode 100644
index 000000000..9166343bd
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/backward10.svg
@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4 13.2001C4 14.7428 4.46919 16.2508 5.34824 17.5335C6.22729 18.8162 7.47672 19.816 8.93853 20.4063C10.4003 20.9967 12.0089 21.1511 13.5607 20.8502C15.1126 20.5492 16.538 19.8063 17.6569 18.7155C18.7757 17.6247 19.5376 16.2348 19.8463 14.7218C20.155 13.2088 19.9965 11.6404 19.391 10.2152C18.7855 8.78993 17.7602 7.57175 16.4446 6.71468C15.129 5.85761 13.5822 5.40015 12 5.40015" stroke="black" stroke-width="2" stroke-linecap="round"/>
+<path d="M12.6152 3L10.1537 5.39999L12.6152 7.79997" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.16406 16V11.4766H9.09375L7.71094 12.4375V11.375L9.16797 10.3633H10.3438V16H9.16406ZM14.0469 16.1445C13.5833 16.1445 13.1836 16.0247 12.8477 15.7852C12.5143 15.543 12.2578 15.2005 12.0781 14.7578C11.8984 14.3151 11.8086 13.7904 11.8086 13.1836V13.1758C11.8086 12.5664 11.8984 12.0417 12.0781 11.6016C12.2578 11.1589 12.5143 10.8177 12.8477 10.5781C13.1836 10.3385 13.5833 10.2188 14.0469 10.2188C14.513 10.2188 14.9128 10.3385 15.2461 10.5781C15.5794 10.8177 15.8359 11.1589 16.0156 11.6016C16.1953 12.0417 16.2852 12.5664 16.2852 13.1758V13.1836C16.2852 13.7904 16.1953 14.3151 16.0156 14.7578C15.8359 15.2005 15.5794 15.543 15.2461 15.7852C14.9128 16.0247 14.513 16.1445 14.0469 16.1445ZM14.0469 15.2109C14.2708 15.2109 14.4596 15.1302 14.6133 14.9688C14.7695 14.8073 14.888 14.5755 14.9688 14.2734C15.0521 13.9714 15.0938 13.6081 15.0938 13.1836V13.1758C15.0938 12.7487 15.0521 12.3854 14.9688 12.0859C14.888 11.7839 14.7695 11.5534 14.6133 11.3945C14.4596 11.2331 14.2708 11.1523 14.0469 11.1523C13.8255 11.1523 13.6367 11.2331 13.4805 11.3945C13.3268 11.5534 13.2083 11.7839 13.125 12.0859C13.0443 12.3854 13.0039 12.7487 13.0039 13.1758V13.1836C13.0039 13.6081 13.0443 13.9714 13.125 14.2734C13.2083 14.5755 13.3268 14.8073 13.4805 14.9688C13.6367 15.1302 13.8255 15.2109 14.0469 15.2109Z" fill="black"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/ff.svg b/examples/multimedia/video/mediaplayer/images/ff.svg
new file mode 100644
index 000000000..7c907c4fa
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/ff.svg
@@ -0,0 +1,4 @@
+<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.19465 16.8817C5.5198 17.0557 5.91435 17.0366 6.2212 16.832L12.2212 12.832C12.4994 12.6466 12.6665 12.3344 12.6665 12C12.6665 11.6656 12.4994 11.3534 12.2212 11.1679L6.2212 7.16795C5.91435 6.96338 5.5198 6.9443 5.19465 7.11832C4.86949 7.29234 4.6665 7.6312 4.6665 8V16C4.6665 16.3688 4.86949 16.7077 5.19465 16.8817ZM6.6665 14.1315V9.86852L9.86373 12L6.6665 14.1315Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1946 16.8817C13.5198 17.0557 13.9143 17.0366 14.2212 16.832L20.2212 12.832C20.4994 12.6466 20.6665 12.3344 20.6665 12C20.6665 11.6656 20.4994 11.3534 20.2212 11.1679L14.2212 7.16795C13.9143 6.96338 13.5198 6.9443 13.1946 7.11832C12.8695 7.29234 12.6665 7.6312 12.6665 8V16C12.6665 16.3688 12.8695 16.7077 13.1946 16.8817ZM14.6665 14.1315V9.86852L17.8637 12L14.6665 14.1315Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/forward10.svg b/examples/multimedia/video/mediaplayer/images/forward10.svg
new file mode 100644
index 000000000..190a5aaab
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/forward10.svg
@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M20 13.2001C20 14.7428 19.5308 16.2508 18.6518 17.5335C17.7727 18.8162 16.5233 19.816 15.0615 20.4063C13.5997 20.9967 11.9911 21.1511 10.4393 20.8502C8.88743 20.5492 7.46197 19.8063 6.34315 18.7155C5.22433 17.6247 4.4624 16.2348 4.15372 14.7218C3.84504 13.2088 4.00347 11.6404 4.60897 10.2152C5.21447 8.78993 6.23985 7.57175 7.55544 6.71468C8.87103 5.85761 10.4178 5.40015 12 5.40015" stroke="black" stroke-width="2" stroke-linecap="round"/>
+<path d="M11.3838 3L13.8453 5.39999L11.3838 7.79997" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.16406 16V11.4766H9.09375L7.71094 12.4375V11.375L9.16797 10.3633H10.3438V16H9.16406ZM14.0469 16.1445C13.5833 16.1445 13.1836 16.0247 12.8477 15.7852C12.5143 15.543 12.2578 15.2005 12.0781 14.7578C11.8984 14.3151 11.8086 13.7904 11.8086 13.1836V13.1758C11.8086 12.5664 11.8984 12.0417 12.0781 11.6016C12.2578 11.1589 12.5143 10.8177 12.8477 10.5781C13.1836 10.3385 13.5833 10.2188 14.0469 10.2188C14.513 10.2188 14.9128 10.3385 15.2461 10.5781C15.5794 10.8177 15.8359 11.1589 16.0156 11.6016C16.1953 12.0417 16.2852 12.5664 16.2852 13.1758V13.1836C16.2852 13.7904 16.1953 14.3151 16.0156 14.7578C15.8359 15.2005 15.5794 15.543 15.2461 15.7852C14.9128 16.0247 14.513 16.1445 14.0469 16.1445ZM14.0469 15.2109C14.2708 15.2109 14.4596 15.1302 14.6133 14.9688C14.7695 14.8073 14.888 14.5755 14.9688 14.2734C15.0521 13.9714 15.0938 13.6081 15.0938 13.1836V13.1758C15.0938 12.7487 15.0521 12.3854 14.9688 12.0859C14.888 11.7839 14.7695 11.5534 14.6133 11.3945C14.4596 11.2331 14.2708 11.1523 14.0469 11.1523C13.8255 11.1523 13.6367 11.2331 13.4805 11.3945C13.3268 11.5534 13.2083 11.7839 13.125 12.0859C13.0443 12.3854 13.0039 12.7487 13.0039 13.1758V13.1836C13.0039 13.6081 13.0443 13.9714 13.125 14.2734C13.2083 14.5755 13.3268 14.8073 13.4805 14.9688C13.6367 15.1302 13.8255 15.2109 14.0469 15.2109Z" fill="black"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/link.svg b/examples/multimedia/video/mediaplayer/images/link.svg
new file mode 100644
index 000000000..6ce1b983d
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/link.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11 16C11 16.5523 10.5523 17 10 17H7C5.61667 17 4.4375 16.5125 3.4625 15.5375C2.4875 14.5625 2 13.3833 2 12C2 10.6167 2.4875 9.4375 3.4625 8.4625C4.4375 7.4875 5.61667 7 7 7H10C10.5523 7 11 7.44772 11 8C11 8.55228 10.5523 9 10 9H7C6.16667 9 5.45833 9.29167 4.875 9.875C4.29167 10.4583 4 11.1667 4 12C4 12.8333 4.29167 13.5417 4.875 14.125C5.45833 14.7083 6.16667 15 7 15H10C10.5523 15 11 15.4477 11 16ZM9 13C8.44772 13 8 12.5523 8 12C8 11.4477 8.44772 11 9 11H15C15.5523 11 16 11.4477 16 12C16 12.5523 15.5523 13 15 13H9ZM14 17C13.4477 17 13 16.5523 13 16C13 15.4477 13.4477 15 14 15H17C17.8333 15 18.5417 14.7083 19.125 14.125C19.7083 13.5417 20 12.8333 20 12C20 11.1667 19.7083 10.4583 19.125 9.875C18.5417 9.29167 17.8333 9 17 9H14C13.4477 9 13 8.55228 13 8C13 7.44772 13.4477 7 14 7H17C18.3833 7 19.5625 7.4875 20.5375 8.4625C21.5125 9.4375 22 10.6167 22 12C22 13.3833 21.5125 14.5625 20.5375 15.5375C19.5625 16.5125 18.3833 17 17 17H14Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/loop.svg b/examples/multimedia/video/mediaplayer/images/loop.svg
new file mode 100644
index 000000000..8161ca867
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/loop.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7 17.5C5 17.5 7 17.5 5 17.5C3 17.5 3 17.5 3 15.5C3 13.5 3 8 3 6C3 4 3 4 5 4C7 4 17 4 19 4C21 4 21 4 21 6C21 8 21 13.5 21 15.5C21 17.5 21 17.5 19 17.5C17 17.5 11 17.5 11 17.5M11 17.5L13 15M11 17.5L13 20" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/more.svg b/examples/multimedia/video/mediaplayer/images/more.svg
new file mode 100644
index 000000000..fa8bddec8
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/more.svg
@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12Z" fill="black"/>
+<path d="M22 12C22 13.1046 21.1046 14 20 14C18.8954 14 18 13.1046 18 12C18 10.8954 18.8954 10 20 10C21.1046 10 22 10.8954 22 12Z" fill="black"/>
+<path d="M6 12C6 13.1046 5.10457 14 4 14C2.89543 14 2 13.1046 2 12C2 10.8954 2.89543 10 4 10C5.10457 10 6 10.8954 6 12Z" fill="black"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/Mute_Icon.svg b/examples/multimedia/video/mediaplayer/images/mute.svg
index b66b6d311..b66b6d311 100644
--- a/examples/multimedia/video/mediaplayer/Mute_Icon.svg
+++ b/examples/multimedia/video/mediaplayer/images/mute.svg
diff --git a/examples/multimedia/video/mediaplayer/images/open_new.svg b/examples/multimedia/video/mediaplayer/images/open_new.svg
new file mode 100644
index 000000000..6c62237f1
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/open_new.svg
@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="File /open_new">
+<path id="Layer01" d="M2 5C2 3.34315 3.34315 2 5 2H11C11.5523 2 12 2.44772 12 3C12 3.55228 11.5523 4 11 4H5C4.44772 4 4 4.44772 4 5V19C4 19.5523 4.44772 20 5 20H19C19.5523 20 20 19.5523 20 19V13.125C20 12.5727 20.4477 12.125 21 12.125C21.5523 12.125 22 12.5727 22 13.125V19C22 20.6569 20.6569 22 19 22H5C3.34315 22 2 20.6569 2 19V5Z" fill="#0D0D0D"/>
+<path id="Layer02" fill-rule="evenodd" clip-rule="evenodd" d="M15 4C14.4477 4 14 3.55228 14 3C14 2.44772 14.4477 2 15 2H21C21.5523 2 22 2.44772 22 3V9C22 9.55228 21.5523 10 21 10C20.4477 10 20 9.55228 20 9V5.41421L8.76961 16.6446C8.37908 17.0351 7.74592 17.0351 7.35539 16.6446C6.96487 16.2541 6.96487 15.6209 7.35539 15.2304L18.5858 4H15Z" fill="#0D0D0D"/>
+</g>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/pause_symbol.svg b/examples/multimedia/video/mediaplayer/images/pause_symbol.svg
new file mode 100644
index 000000000..bdf6a9047
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/pause_symbol.svg
@@ -0,0 +1,4 @@
+<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.3335 7C8.3335 6.44772 8.66928 6 9.0835 6H10.5835C10.9977 6 11.3335 6.44772 11.3335 7V17C11.3335 17.5523 10.9977 18 10.5835 18H9.0835C8.66928 18 8.3335 17.5523 8.3335 17V7Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3335 7C13.3335 6.44772 13.6693 6 14.0835 6H15.5835C15.9977 6 16.3335 6.44772 16.3335 7V17C16.3335 17.5523 15.9977 18 15.5835 18H14.0835C13.6693 18 13.3335 17.5523 13.3335 17V7Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/play_symbol.svg b/examples/multimedia/video/mediaplayer/images/play_symbol.svg
new file mode 100644
index 000000000..edb567226
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/play_symbol.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.8182 12L6.89091 6.14985L6.89091 17.8501L16.8182 12ZM18.2 13.4143C19.2667 12.7857 19.2667 11.2143 18.2 10.5857L7.4 4.22123C6.33333 3.59264 5 4.37838 5 5.63555L5 18.3644C5 19.6216 6.33333 20.4074 7.4 19.7788L18.2 13.4143Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/rewind.svg b/examples/multimedia/video/mediaplayer/images/rewind.svg
new file mode 100644
index 000000000..e9b34122c
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/rewind.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.4719 16.8817C19.1467 17.0557 18.7522 17.0366 18.4453 16.832L12.4453 12.832C12.1671 12.6466 12 12.3344 12 12C12 11.6656 12.1671 11.3534 12.4453 11.1679L18.4453 7.16795C18.7522 6.96338 19.1467 6.9443 19.4719 7.11832C19.797 7.29234 20 7.6312 20 8V16C20 16.3688 19.797 16.7077 19.4719 16.8817ZM18 14.1315V9.86852L14.8028 12L18 14.1315Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4719 16.8817C11.1467 17.0557 10.7522 17.0366 10.4453 16.832L4.4453 12.832C4.1671 12.6466 4 12.3344 4 12C4 11.6656 4.1671 11.3534 4.4453 11.1679L10.4453 7.16795C10.7522 6.96338 11.1467 6.9443 11.4719 7.11832C11.797 7.29234 12 7.6312 12 8V16C12 16.3688 11.797 16.7077 11.4719 16.8817ZM10 14.1315V9.86852L6.80278 12L10 14.1315Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/settings.svg b/examples/multimedia/video/mediaplayer/images/settings.svg
new file mode 100644
index 000000000..3a13ccae1
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/settings.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.84998 4C8.84998 2.89543 9.74541 2 10.85 2H13.1222C14.2268 2 15.1222 2.89543 15.1222 4V4.59509C15.1229 4.59732 15.125 4.6023 15.1306 4.61031C15.1434 4.62838 15.1691 4.65242 15.2077 4.66935C15.852 4.95163 16.4515 5.31609 16.9931 5.74921C17.0261 5.77554 17.0594 5.787 17.0815 5.78982C17.0912 5.79106 17.0966 5.79055 17.0989 5.79008L17.9363 5.33741C18.8691 4.8332 20.0332 5.14455 20.5897 6.04706L21.6744 7.80604C22.2777 8.78435 21.9342 10.0686 20.9231 10.6152L19.9875 11.1209C19.9859 11.1226 19.9824 11.1269 19.9781 11.1358C19.9683 11.156 19.9595 11.1904 19.9636 11.2327C19.9877 11.4855 20 11.7414 20 12C20 12.2586 19.9877 12.5145 19.9636 12.7673C19.9595 12.8096 19.9683 12.844 19.9781 12.8642C19.9824 12.8732 19.9859 12.8774 19.9875 12.8791L20.923 13.3848C21.9341 13.9314 22.2776 15.2157 21.6743 16.194L20.5897 17.9529C20.0331 18.8554 18.869 19.1668 17.9362 18.6626L17.0989 18.2099C17.0966 18.2095 17.0912 18.209 17.0815 18.2102C17.0594 18.213 17.026 18.2245 16.9931 18.2508C16.4515 18.6839 15.852 19.0484 15.2077 19.3306C15.1691 19.3476 15.1434 19.3716 15.1306 19.3897C15.125 19.3977 15.1229 19.4027 15.1222 19.4049V20C15.1222 21.1046 14.2268 22 13.1222 22H10.85C9.74541 22 8.84998 21.1046 8.84998 20V19.3928C8.84931 19.3906 8.84725 19.3857 8.84163 19.3777C8.82894 19.3597 8.80351 19.3357 8.76508 19.3187C8.12675 19.0362 7.53272 18.6729 6.99586 18.242C6.96292 18.2156 6.92951 18.2041 6.90737 18.2012C6.89759 18.2 6.8922 18.2005 6.8899 18.201L6.03592 18.6626C5.10317 19.1668 3.93904 18.8554 3.38251 17.9529L2.29784 16.194C1.69457 15.2156 2.03805 13.9314 3.04914 13.3848L4.01099 12.8649C4.01265 12.8632 4.01607 12.859 4.02042 12.85C4.03023 12.8299 4.03897 12.7956 4.03503 12.7534C4.0118 12.5051 3.99995 12.2538 3.99995 12C3.99995 11.7462 4.0118 11.4949 4.03503 11.2466C4.03897 11.2045 4.03022 11.1701 4.02042 11.15C4.01607 11.1411 4.01265 11.1368 4.01098 11.1352L3.04909 10.6152C2.038 10.0686 1.69452 8.78435 2.29779 7.80604L3.38245 6.04707C3.93898 5.14456 5.10312 4.83322 6.03587 5.33742L6.88988 5.79907C6.89218 5.79953 6.89757 5.80005 6.90735 5.79879C6.92949 5.79594 6.9629 5.78441 6.99584 5.75797C7.53271 5.32709 8.12674 4.96379 8.76508 4.68128C8.80351 4.66427 8.82894 4.64029 8.84163 4.62231C8.84725 4.61434 8.84931 4.60938 8.84998 4.60716V4ZM8.85024 4.60578C8.85029 4.60578 8.85026 4.60619 8.85001 4.60703L8.85024 4.60578ZM6.88852 5.79864C6.88854 5.7986 6.88898 5.79869 6.88977 5.79904L6.88852 5.79864ZM4.00988 11.1343C4.00991 11.1342 4.01026 11.1344 4.0109 11.1351L4.00988 11.1343ZM4.00989 12.8658C4.00988 12.8657 4.01021 12.8654 4.01091 12.865L4.00989 12.8658ZM6.88854 18.2014C6.88851 18.2013 6.8889 18.2012 6.88979 18.201L6.88854 18.2014ZM19.9886 12.88C19.9885 12.88 19.9881 12.8797 19.9876 12.8792L19.9886 12.88ZM19.9886 11.12C19.9887 11.12 19.9883 11.1204 19.9876 11.1208L19.9886 11.12ZM17.1003 5.78965C17.1003 5.78968 17.0999 5.78986 17.099 5.79006L17.1003 5.78965ZM15.122 4.5937C15.122 4.59371 15.1221 4.59414 15.1222 4.59496L15.122 4.5937ZM13.1222 4L10.85 4V4.60808C10.85 5.49562 10.2774 6.19908 9.57449 6.51017C9.09654 6.7217 8.65096 6.99408 8.24769 7.31774C7.63839 7.80676 6.73261 7.98755 5.93958 7.55888L5.08481 7.09682L4.00015 8.8558L4.9628 9.37617C5.7538 9.80375 6.09888 10.6574 6.02633 11.4329C6.00889 11.6193 5.99995 11.8085 5.99995 12C5.99995 12.1915 6.00889 12.3807 6.02633 12.5671C6.09889 13.3426 5.75381 14.1963 4.9628 14.6239L4.0002 15.1442L5.08486 16.9032L5.9396 16.4411C6.73263 16.0125 7.63841 16.1933 8.2477 16.6823C8.65097 17.0059 9.09654 17.2783 9.57449 17.4898C10.2774 17.8009 10.85 18.5044 10.85 19.3919V20H13.1222V19.404C13.1222 18.5133 13.6987 17.8083 14.4051 17.4988C14.8875 17.2874 15.3372 17.0142 15.744 16.6889C16.3532 16.2017 17.2575 16.0222 18.0492 16.4501L18.8873 16.9032L19.972 15.1442L19.0357 14.6381C18.2434 14.2098 17.8986 13.3541 17.9726 12.5776C17.9907 12.3878 18 12.1951 18 12C18 11.8049 17.9907 11.6123 17.9726 11.4224C17.8986 10.6459 18.2434 9.79023 19.0357 9.36192L19.972 8.85579L18.8874 7.09681L18.0492 7.54989C17.2575 7.97786 16.3532 7.79836 15.744 7.31116C15.3372 6.98582 14.8875 6.71259 14.4051 6.50124C13.6987 6.19173 13.1222 5.48675 13.1222 4.596L13.1222 4Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/Speaker_Icon.svg b/examples/multimedia/video/mediaplayer/images/speaker.svg
index 723e48b66..723e48b66 100644
--- a/examples/multimedia/video/mediaplayer/Speaker_Icon.svg
+++ b/examples/multimedia/video/mediaplayer/images/speaker.svg
diff --git a/examples/multimedia/video/mediaplayer/images/stop_symbol.svg b/examples/multimedia/video/mediaplayer/images/stop_symbol.svg
new file mode 100644
index 000000000..99f00162f
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/stop_symbol.svg
@@ -0,0 +1,3 @@
+<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.6665 7H7.6665L7.6665 17H17.6665V7ZM7.6665 5C6.56193 5 5.6665 5.89543 5.6665 7V17C5.6665 18.1046 6.56193 19 7.6665 19H17.6665C18.7711 19 19.6665 18.1046 19.6665 17V7C19.6665 5.89543 18.7711 5 17.6665 5H7.6665Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/url.svg b/examples/multimedia/video/mediaplayer/images/url.svg
new file mode 100644
index 000000000..44942911f
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/url.svg
@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7 10C7 10.5523 6.55228 11 6 11C5.44772 11 5 10.5523 5 10C5 9.44772 5.44772 9 6 9C6.55228 9 7 9.44772 7 10Z" fill="black"/>
+<path d="M7 14C7 14.5523 6.55228 15 6 15C5.44772 15 5 14.5523 5 14C5 13.4477 5.44772 13 6 13C6.55228 13 7 13.4477 7 14Z" fill="black"/>
+<path d="M13 6L9 18" stroke="black" stroke-width="2" stroke-linecap="round"/>
+<path d="M18 6L14 18" stroke="black" stroke-width="2" stroke-linecap="round"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/volume.svg b/examples/multimedia/video/mediaplayer/images/volume.svg
new file mode 100644
index 000000000..2cdc4df6b
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/volume.svg
@@ -0,0 +1,4 @@
+<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.98759 5.1163C11.1732 3.91192 13.2226 4.75139 13.2226 6.44139V17.5587C13.2226 19.2487 11.1732 20.0882 9.98759 18.8838L6.74406 15.5888C6.72318 15.5675 6.69465 15.5556 6.66488 15.5556H4.33371C3.29051 15.5556 2.44482 14.7099 2.44482 13.6667L2.44482 10.3334C2.44482 9.29016 3.29051 8.44448 4.33371 8.44448H6.66488C6.69465 8.44448 6.72318 8.43253 6.74406 8.41131L9.98759 5.1163ZM11.3101 6.32964C11.2992 6.3316 11.2809 6.33666 11.2545 6.36344L8.011 9.65845C7.65595 10.0191 7.171 10.2223 6.66488 10.2223H4.33371C4.27235 10.2223 4.2226 10.272 4.2226 10.3334L4.2226 13.6667C4.2226 13.7281 4.27235 13.7778 4.33371 13.7778H6.66488C7.171 13.7778 7.65595 13.9809 8.011 14.3416L11.2545 17.6366C11.2809 17.6634 11.2992 17.6685 11.3101 17.6704C11.326 17.6733 11.3496 17.6723 11.3758 17.6615C11.4021 17.6507 11.4196 17.6349 11.4289 17.6218C11.4353 17.6127 11.4448 17.5963 11.4448 17.5587V6.44139C11.4448 6.40381 11.4353 6.38737 11.4289 6.37829C11.4196 6.36513 11.4021 6.34932 11.3758 6.33857C11.3496 6.32781 11.326 6.3268 11.3101 6.32964Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0329 4.37143C18.6857 4.0243 18.1229 4.0243 17.7758 4.37143C17.4287 4.71857 17.4287 5.28138 17.7758 5.62851C21.3339 9.18662 21.3339 14.9555 17.7758 18.5136C17.4287 18.8607 17.4287 19.4235 17.7758 19.7706C18.1229 20.1178 18.6857 20.1178 19.0329 19.7706C23.2852 15.5183 23.2852 8.62381 19.0329 4.37143ZM16.2044 7.37146C15.8573 7.02433 15.2945 7.02433 14.9474 7.37146C14.6002 7.71859 14.6002 8.28141 14.9474 8.62854C16.9434 10.6246 16.9434 13.8607 14.9474 15.8567C14.6002 16.2039 14.6002 16.7667 14.9474 17.1138C15.2945 17.461 15.8573 17.461 16.2044 17.1138C18.8947 14.4235 18.8947 10.0617 16.2044 7.37146Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/volume_mute.svg b/examples/multimedia/video/mediaplayer/images/volume_mute.svg
new file mode 100644
index 000000000..635c308e9
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/volume_mute.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.6541 5.1163C10.8397 3.91192 12.8891 4.75139 12.8891 6.44139V17.5587C12.8891 19.2487 10.8397 20.0882 9.6541 18.8838L6.41057 15.5888C6.38968 15.5675 6.36116 15.5556 6.33138 15.5556H4.00022C2.95701 15.5556 2.11133 14.7099 2.11133 13.6667L2.11133 10.3334C2.11133 9.29016 2.95701 8.44448 4.00022 8.44448H6.33138C6.36116 8.44448 6.38968 8.43253 6.41057 8.41131L9.6541 5.1163ZM10.9766 6.32964C10.9657 6.3316 10.9474 6.33666 10.921 6.36344L7.67751 9.65845C7.32245 10.0191 6.8375 10.2223 6.33138 10.2223H4.00022C3.93885 10.2223 3.88911 10.272 3.88911 10.3334L3.88911 13.6667C3.88911 13.7281 3.93885 13.7778 4.00022 13.7778H6.33138C6.8375 13.7778 7.32245 13.9809 7.67751 14.3416L10.921 17.6366C10.9474 17.6634 10.9657 17.6685 10.9766 17.6704C10.9925 17.6733 11.0161 17.6723 11.0423 17.6615C11.0686 17.6507 11.0861 17.6349 11.0954 17.6218C11.1018 17.6127 11.1113 17.5963 11.1113 17.5587V6.44139C11.1113 6.40381 11.1018 6.38737 11.0954 6.37829C11.0861 6.36513 11.0686 6.34932 11.0423 6.33857C11.0161 6.32781 10.9925 6.3268 10.9766 6.32964Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7071 15.7071C15.3166 16.0976 14.6834 16.0976 14.2929 15.7071C13.9024 15.3166 13.9024 14.6834 14.2929 14.2929L16.5858 12L14.2929 9.70711C13.9024 9.31658 13.9024 8.68342 14.2929 8.29289C14.6834 7.90237 15.3166 7.90237 15.7071 8.29289L18 10.5858L20.2929 8.29289C20.6834 7.90237 21.3166 7.90237 21.7071 8.29289C22.0976 8.68342 22.0976 9.31658 21.7071 9.70711L19.4142 12L21.7071 14.2929C22.0976 14.6834 22.0976 15.3166 21.7071 15.7071C21.3166 16.0976 20.6834 16.0976 20.2929 15.7071L18 13.4142L15.7071 15.7071Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/zoom_maximize.svg b/examples/multimedia/video/mediaplayer/images/zoom_maximize.svg
new file mode 100644
index 000000000..9c7fb8ebb
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/zoom_maximize.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.071 3.92892C19.6233 3.92892 20.071 4.37664 20.071 4.92892V9.17157C20.071 9.72385 19.6233 10.1716 19.071 10.1716C18.5188 10.1716 18.071 9.72385 18.071 9.17157V7.34314L16.1716 9.24263C15.781 9.63315 15.1479 9.63315 14.7573 9.24263C14.3668 8.8521 14.3668 8.21894 14.7573 7.82841L16.6568 5.92892L14.8284 5.92892C14.2761 5.92892 13.8284 5.48121 13.8284 4.92892C13.8284 4.37664 14.2761 3.92892 14.8284 3.92892H19.071ZM4.92892 13.8284C5.48121 13.8284 5.92892 14.2761 5.92892 14.8284V16.6569L8.29289 14.2929C8.68342 13.9024 9.31658 13.9024 9.70711 14.2929C10.0976 14.6834 10.0976 15.3166 9.70711 15.7071L7.34314 18.0711L9.17157 18.0711C9.72385 18.0711 10.1716 18.5188 10.1716 19.0711C10.1716 19.6234 9.72385 20.0711 9.17157 20.0711H4.92892C4.37664 20.0711 3.92892 19.6234 3.92892 19.0711V14.8284C3.92892 14.2761 4.37664 13.8284 4.92892 13.8284Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1716 4.92892C10.1716 5.48121 9.72388 5.92892 9.1716 5.92892L7.34316 5.92892L9.24265 7.82841C9.63318 8.21894 9.63318 8.8521 9.24265 9.24263C8.85213 9.63315 8.21896 9.63315 7.82844 9.24263L5.92896 7.34314V9.17157C5.92896 9.72385 5.48124 10.1716 4.92896 10.1716C4.37667 10.1716 3.92896 9.72385 3.92896 9.17157V4.92892C3.92896 4.37664 4.37667 3.92892 4.92896 3.92892H9.1716C9.72388 3.92892 10.1716 4.37664 10.1716 4.92892ZM19.0711 13.8284C19.6234 13.8284 20.0711 14.2761 20.0711 14.8284V19.0711C20.0711 19.6234 19.6234 20.0711 19.0711 20.0711H14.8284C14.2761 20.0711 13.8284 19.6234 13.8284 19.0711C13.8284 18.5188 14.2761 18.0711 14.8284 18.0711H16.6569L14.2929 15.7071C13.9024 15.3166 13.9024 14.6834 14.2929 14.2929C14.6834 13.9024 15.3166 13.9024 15.7071 14.2929L18.0711 16.6569V14.8284C18.0711 14.2761 18.5188 13.8284 19.0711 13.8284Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/images/zoom_minimize.svg b/examples/multimedia/video/mediaplayer/images/zoom_minimize.svg
new file mode 100644
index 000000000..3d6ae65aa
--- /dev/null
+++ b/examples/multimedia/video/mediaplayer/images/zoom_minimize.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1213 10C14.502 10 14 9.49797 14 8.87868L14 4.12132C14 3.50203 14.502 3 15.1213 3C15.7406 3 16.2426 3.50203 16.2426 4.12132V6.17158L18.3726 4.04163C18.8105 3.60372 19.5205 3.60372 19.9584 4.04163C20.3963 4.47953 20.3963 5.18951 19.9584 5.62741L17.8284 7.75736L19.8787 7.75736C20.498 7.75736 21 8.25939 21 8.87868C21 9.49797 20.498 10 19.8787 10L15.1213 10ZM8.87868 21C8.25939 21 7.75736 20.498 7.75736 19.8787V17.8284L5.10659 20.4792C4.66869 20.9171 3.95871 20.9171 3.52081 20.4792C3.0829 20.0413 3.0829 19.3313 3.52081 18.8934L6.17157 16.2426H4.12132C3.50203 16.2426 3 15.7406 3 15.1213C3 14.502 3.50203 14 4.12132 14L8.87868 14C9.49797 14 10 14.502 10 15.1213L10 19.8787C10 20.498 9.49797 21 8.87868 21Z" fill="#0D0D0D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.00002 8.87867C3.00002 8.25939 3.50205 7.75735 4.12134 7.75735H6.1716L4.04166 5.62741C3.60375 5.1895 3.60375 4.47952 4.04166 4.04162C4.47956 3.60372 5.18954 3.60372 5.62744 4.04162L7.75737 6.17155V4.12131C7.75737 3.50203 8.25941 2.99999 8.87869 2.99999C9.49798 2.99999 10 3.50203 10 4.12131L10 8.87867C10 9.49796 9.49798 9.99999 8.87869 9.99999L4.12134 9.99999C3.50205 9.99999 3.00002 9.49796 3.00002 8.87867ZM15.1213 21C14.502 21 14 20.498 14 19.8787V15.1213C14 14.502 14.502 14 15.1213 14L19.8787 14C20.498 14 21 14.502 21 15.1213C21 15.7406 20.498 16.2426 19.8787 16.2426H17.8284L20.4792 18.8934C20.9171 19.3313 20.9171 20.0413 20.4792 20.4792C20.0413 20.9171 19.3313 20.9171 18.8934 20.4792L16.2426 17.8284V19.8787C16.2426 20.498 15.7406 21 15.1213 21Z" fill="#0D0D0D"/>
+</svg>
diff --git a/examples/multimedia/video/mediaplayer/main.cpp b/examples/multimedia/video/mediaplayer/main.cpp
index 3a2dab71c..3df4d6027 100644
--- a/examples/multimedia/video/mediaplayer/main.cpp
+++ b/examples/multimedia/video/mediaplayer/main.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QCommandLineParser>
@@ -21,7 +21,7 @@ int main(int argc, char *argv[])
parser.process(app);
QQmlApplicationEngine engine;
- const QUrl url(QStringLiteral("qrc:/main.qml"));
+ const QUrl url(QStringLiteral("qrc:/Main.qml"));
if (!parser.positionalArguments().isEmpty()) {
QUrl source = QUrl::fromUserInput(parser.positionalArguments().at(0), QDir::currentPath());
@@ -32,7 +32,7 @@ int main(int argc, char *argv[])
});
}
- engine.load(url);
+ engine.loadFromModule("mediaplayer", "Main");
return app.exec();
}
diff --git a/examples/multimedia/video/mediaplayer/main.qml b/examples/multimedia/video/mediaplayer/main.qml
deleted file mode 100644
index d1dc1f63a..000000000
--- a/examples/multimedia/video/mediaplayer/main.qml
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Window
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtMultimedia
-
-
-Window {
- id: root
- width: 640
- height: 480
- visible: true
- title: qsTr("Multimedia Player")
- property alias source: mediaPlayer.source
-
- Popup {
- id: mediaError
- anchors.centerIn: Overlay.overlay
- Text {
- id: mediaErrorText
- }
- }
-
- MediaPlayer {
- id: mediaPlayer
-
- function updateMetadata() {
- metadataInfo.clear();
- metadataInfo.read(mediaPlayer.metaData);
- metadataInfo.read(mediaPlayer.audioTracks[mediaPlayer.activeAudioTrack]);
- metadataInfo.read(mediaPlayer.videoTracks[mediaPlayer.activeVideoTrack]);
- }
-
- videoOutput: videoOutput
- audioOutput: AudioOutput {
- id: audio
- muted: playbackControl.muted
- volume: playbackControl.volume
- }
-
- onErrorOccurred: { mediaErrorText.text = mediaPlayer.errorString; mediaError.open() }
- onMetaDataChanged: { updateMetadata() }
- onTracksChanged: {
- audioTracksInfo.read(mediaPlayer.audioTracks);
- audioTracksInfo.selectedTrack = mediaPlayer.activeAudioTrack;
- videoTracksInfo.read(mediaPlayer.videoTracks);
- videoTracksInfo.selectedTrack = mediaPlayer.activeVideoTrack;
- subtitleTracksInfo.read(mediaPlayer.subtitleTracks);
- subtitleTracksInfo.selectedTrack = mediaPlayer.activeSubtitleTrack;
- updateMetadata()
- }
- onActiveTracksChanged: { updateMetadata() }
- }
-
- PlayerMenuBar {
- id: menuBar
-
- anchors.left: parent.left
- anchors.right: parent.right
-
- visible: !videoOutput.fullScreen
-
- mediaPlayer: mediaPlayer
- videoOutput: videoOutput
- metadataInfo: metadataInfo
- audioTracksInfo: audioTracksInfo
- videoTracksInfo: videoTracksInfo
- subtitleTracksInfo: subtitleTracksInfo
-
- onClosePlayer: root.close()
- }
-
-
- VideoOutput {
- id: videoOutput
-
- property bool fullScreen: false
-
- anchors.top: fullScreen ? parent.top : menuBar.bottom
- anchors.bottom: playbackControl.top
- anchors.left: parent.left
- anchors.right: parent.right
-
- TapHandler {
- onDoubleTapped: {
- parent.fullScreen ? showNormal() : showFullScreen()
- parent.fullScreen = !parent.fullScreen
- }
- onTapped: {
- metadataInfo.visible = false
- audioTracksInfo.visible = false
- videoTracksInfo.visible = false
- subtitleTracksInfo.visible = false
- }
- }
- }
-
- MetadataInfo {
- id: metadataInfo
-
- anchors.right: parent.right
- anchors.top: videoOutput.fullScreen ? parent.top : menuBar.bottom
- anchors.bottom: playbackControl.opacity ? playbackControl.bottom : parent.bottom
-
- visible: false
- }
-
- TracksInfo {
- id: audioTracksInfo
-
- anchors.right: parent.right
- anchors.top: videoOutput.fullScreen ? parent.top : menuBar.bottom
- anchors.bottom: playbackControl.opacity ? playbackControl.bottom : parent.bottom
-
- visible: false
- onSelectedTrackChanged: mediaPlayer.activeAudioTrack = audioTracksInfo.selectedTrack
- }
-
- TracksInfo {
- id: videoTracksInfo
-
- anchors.right: parent.right
- anchors.top: videoOutput.fullScreen ? parent.top : menuBar.bottom
- anchors.bottom: playbackControl.opacity ? playbackControl.bottom : parent.bottom
-
- visible: false
- onSelectedTrackChanged: mediaPlayer.activeVideoTrack = videoTracksInfo.selectedTrack
- }
-
- TracksInfo {
- id: subtitleTracksInfo
-
- anchors.right: parent.right
- anchors.top: videoOutput.fullScreen ? parent.top : menuBar.bottom
- anchors.bottom: playbackControl.opacity ? playbackControl.bottom : parent.bottom
-
- visible: false
- onSelectedTrackChanged: mediaPlayer.activeSubtitleTrack = subtitleTracksInfo.selectedTrack
- }
-
- PlaybackControl {
- id: playbackControl
-
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
-
- mediaPlayer: mediaPlayer
- }
-}
diff --git a/licenseRule.json b/licenseRule.json
index 78253f8b3..7afb5f208 100644
--- a/licenseRule.json
+++ b/licenseRule.json
@@ -94,7 +94,7 @@
"file type" : "examples and snippets",
"spdx" : ["LicenseRef-Qt-Commercial OR BSD-3-Clause"]
},
- "config.tests/" : {
+ "config\\.tests/" : {
"comment" : "Default",
"file type" : "build system",
"spdx" : ["BSD-3-Clause"]
diff --git a/src/3rdparty/pffft/pffft.c b/src/3rdparty/pffft/pffft.c
index 7fe8c4c15..9271a9ad9 100644
--- a/src/3rdparty/pffft/pffft.c
+++ b/src/3rdparty/pffft/pffft.c
@@ -1233,27 +1233,19 @@ PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform) {
s->e = (float*)s->data;
s->twiddle = (float*)(s->data + (2*s->Ncvec*(SIMD_SZ-1))/SIMD_SZ);
- if (transform == PFFFT_REAL) {
- for (k=0; k < s->Ncvec; ++k) {
- int i = k/SIMD_SZ;
- int j = k%SIMD_SZ;
- for (m=0; m < SIMD_SZ-1; ++m) {
- float A = -2*M_PI*(m+1)*k / N;
- s->e[(2*(i*3 + m) + 0) * SIMD_SZ + j] = cos(A);
- s->e[(2*(i*3 + m) + 1) * SIMD_SZ + j] = sin(A);
- }
+ for (k=0; k < s->Ncvec; ++k) {
+ int i = k/SIMD_SZ;
+ int j = k%SIMD_SZ;
+ for (m=0; m < SIMD_SZ-1; ++m) {
+ float A = -2*M_PI*(m+1)*k / N;
+ s->e[(2*(i*3 + m) + 0) * SIMD_SZ + j] = cos(A);
+ s->e[(2*(i*3 + m) + 1) * SIMD_SZ + j] = sin(A);
}
+ }
+
+ if (transform == PFFFT_REAL) {
rffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac);
} else {
- for (k=0; k < s->Ncvec; ++k) {
- int i = k/SIMD_SZ;
- int j = k%SIMD_SZ;
- for (m=0; m < SIMD_SZ-1; ++m) {
- float A = -2*M_PI*(m+1)*k / N;
- s->e[(2*(i*3 + m) + 0)*SIMD_SZ + j] = cos(A);
- s->e[(2*(i*3 + m) + 1)*SIMD_SZ + j] = sin(A);
- }
- }
cffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac);
}
@@ -1708,19 +1700,19 @@ void pffft_zconvolve_accumulate(PFFFT_Setup *s, const float *a, const float *b,
# endif
#endif
- float ar, ai, br, bi, abr, abi;
+ float ar0, ai0, br0, bi0, abr0, abi0;
#ifndef ZCONVOLVE_USING_INLINE_ASM
v4sf vscal = LD_PS1(scaling);
int i;
#endif
assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab));
- ar = ((v4sf_union*)va)[0].f[0];
- ai = ((v4sf_union*)va)[1].f[0];
- br = ((v4sf_union*)vb)[0].f[0];
- bi = ((v4sf_union*)vb)[1].f[0];
- abr = ((v4sf_union*)vab)[0].f[0];
- abi = ((v4sf_union*)vab)[1].f[0];
+ ar0 = ((v4sf_union*)va)[0].f[0];
+ ai0 = ((v4sf_union*)va)[1].f[0];
+ br0 = ((v4sf_union*)vb)[0].f[0];
+ bi0 = ((v4sf_union*)vb)[1].f[0];
+ abr0 = ((v4sf_union*)vab)[0].f[0];
+ abi0 = ((v4sf_union*)vab)[1].f[0];
#ifdef ZCONVOLVE_USING_INLINE_ASM // inline asm version, unfortunately miscompiled by clang 3.2, at least on ubuntu.. so this will be restricted to gcc
const float *a_ = a, *b_ = b; float *ab_ = ab;
@@ -1774,8 +1766,8 @@ void pffft_zconvolve_accumulate(PFFFT_Setup *s, const float *a, const float *b,
}
#endif
if (s->transform == PFFFT_REAL) {
- ((v4sf_union*)vab)[0].f[0] = abr + ar*br*scaling;
- ((v4sf_union*)vab)[1].f[0] = abi + ai*bi*scaling;
+ ((v4sf_union*)vab)[0].f[0] = abr0 + ar0*br0*scaling;
+ ((v4sf_union*)vab)[1].f[0] = abi0 + ai0*bi0*scaling;
}
}
diff --git a/src/3rdparty/pffft/qt_attribution.json b/src/3rdparty/pffft/qt_attribution.json
index 19c6a25d2..2cf2c7264 100644
--- a/src/3rdparty/pffft/qt_attribution.json
+++ b/src/3rdparty/pffft/qt_attribution.json
@@ -7,8 +7,8 @@
"SecurityCritical": true,
"Homepage": "https://bitbucket.org/jpommier/pffft.git",
- "Version": "7641e67977cf937c22849e2ef19fffa70269e562",
- "DownloadLocation": "https://bitbucket.org/jpommier/pffft/get/38946c766c1a.zip",
+ "Version": "fbc4058602803f40dc554b8a5d2bcc694c005f2f",
+ "DownloadLocation": "https://bitbucket.org/jpommier/pffft/get/fbc405860280.zip",
"CopyrightFile": "COPYRIGHTS",
"License": "BSD 3-Clause \"New\" or \"Revised\" License",
"LicenseId": "BSD-3-Clause",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d344ae211..7d5f3b8e8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,7 +8,7 @@ add_subdirectory(multimedia)
if(ANDROID)
add_subdirectory(android)
endif()
-if (QT_FEATURE_spatialaudio)
+if(QT_FEATURE_spatialaudio)
add_subdirectory(spatialaudio)
endif()
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java
index 39feff6c7..ac8140197 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java
@@ -24,6 +24,7 @@ import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
+import android.util.Range;
import android.view.Surface;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -57,11 +58,12 @@ public class QtCamera2 {
private int mState = STATE_PREVIEW;
private Object mStartMutex = new Object();
private boolean mIsStarted = false;
- private static int MaxNumberFrames = 10;
+ private static int MaxNumberFrames = 12;
private int mFlashMode = CaptureRequest.CONTROL_AE_MODE_ON;
private int mTorchMode = CameraMetadata.FLASH_MODE_OFF;
private int mAFMode = CaptureRequest.CONTROL_AF_MODE_OFF;
private float mZoomFactor = 1.0f;
+ private Range<Integer> mFpsRange = null;
private QtExifDataHandler mExifDataHandler = null;
native void onCameraOpened(String cameraId);
@@ -261,7 +263,14 @@ public class QtCamera2 {
}
};
- public boolean addImageReader(int width, int height, int format) {
+
+ public void prepareCamera(int width, int height, int format, int minFps, int maxFps) {
+
+ addImageReader(width, height, format);
+ setFrameRate(minFps, maxFps);
+ }
+
+ private void addImageReader(int width, int height, int format) {
if (mImageReader != null)
removeSurface(mImageReader.getSurface());
@@ -276,8 +285,14 @@ public class QtCamera2 {
mCapturedPhotoReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
mCapturedPhotoReader.setOnImageAvailableListener(mOnPhotoAvailableListener, mBackgroundHandler);
addSurface(mCapturedPhotoReader.getSurface());
+ }
+
+ private void setFrameRate(int minFrameRate, int maxFrameRate) {
- return true;
+ if (minFrameRate <= 0 || maxFrameRate <= 0)
+ mFpsRange = null;
+ else
+ mFpsRange = new Range<>(minFrameRate, maxFrameRate);
}
public boolean addSurface(Surface surface) {
@@ -335,7 +350,8 @@ public class QtCamera2 {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
if (mZoomFactor != 1.0f)
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
-
+ if (mFpsRange != null)
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mFpsRange);
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
mIsStarted = true;
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
index b3ba8f3dc..3339bddc9 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
@@ -137,6 +137,7 @@ public class QtVideoDeviceManager {
return activeArraySize;
}
+ static final int maxResolution = 3840*2160; // 4k resolution
public String[] getStreamConfigurationsSizes(String cameraId, int imageFormat) {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
@@ -148,13 +149,14 @@ public class QtVideoDeviceManager {
if (sizes == null)
return new String[0];
- String[] stream = new String[sizes.length];
+ ArrayList<String> stream = new ArrayList<>();
for (int index = 0; index < sizes.length; index++) {
- stream[index] = sizes[index].toString();
+ if (sizes[index].getWidth() * sizes[index].getHeight() <= maxResolution)
+ stream.add(sizes[index].toString());
}
- return stream;
+ return stream.toArray(new String[0]);
}
public int stringToControlAEMode(String mode) {
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index 1cca4c2de..8c58545b5 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -67,7 +67,7 @@ qt_internal_add_module(Multimedia
qmaybe_p.h
qtmultimediaglobal.h qtmultimediaglobal_p.h
qerrorinfo_p.h
- recording/qmediacapturesession.cpp recording/qmediacapturesession.h
+ recording/qmediacapturesession.cpp recording/qmediacapturesession.h recording/qmediacapturesession_p.h
recording/qmediarecorder.cpp recording/qmediarecorder.h recording/qmediarecorder_p.h
recording/qscreencapture.cpp recording/qscreencapture.h
recording/qwindowcapture.cpp recording/qwindowcapture.h
@@ -167,7 +167,7 @@ qt_internal_extend_target(Multimedia CONDITION ANDROID
OpenSLES
)
-if (ANDROID)
+if(ANDROID)
set_property(TARGET Multimedia APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES
jar/Qt${QtMultimedia_VERSION_MAJOR}AndroidMultimedia.jar:org.qtproject.qt.android.multimedia.QtAudioDeviceManager
)
@@ -355,7 +355,7 @@ qt_internal_add_shaders(Multimedia "qtmultimedia_shaders_gl_macos_linear"
"shaders/rectsampler_bgra_linear.frag.qsb"
)
-if (DEFINED QT_DEFAULT_MEDIA_BACKEND)
+if(DEFINED QT_DEFAULT_MEDIA_BACKEND)
target_compile_definitions(Multimedia
PRIVATE QT_DEFAULT_MEDIA_BACKEND="${QT_DEFAULT_MEDIA_BACKEND}")
endif()
diff --git a/src/multimedia/audio/qwavedecoder.cpp b/src/multimedia/audio/qwavedecoder.cpp
index 0df50bcbf..36ac3c779 100644
--- a/src/multimedia/audio/qwavedecoder.cpp
+++ b/src/multimedia/audio/qwavedecoder.cpp
@@ -91,6 +91,10 @@ qint64 QWaveDecoder::pos() const
return device->pos();
}
+void QWaveDecoder::setIODevice(QIODevice * /* device */)
+{
+}
+
QAudioFormat QWaveDecoder::audioFormat() const
{
return format;
diff --git a/src/multimedia/camera/qcamera.h b/src/multimedia/camera/qcamera.h
index c5790e339..09d9521ff 100644
--- a/src/multimedia/camera/qcamera.h
+++ b/src/multimedia/camera/qcamera.h
@@ -34,7 +34,7 @@ class Q_MULTIMEDIA_EXPORT QCamera : public QObject
Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged)
Q_PROPERTY(QCameraFormat cameraFormat READ cameraFormat WRITE setCameraFormat NOTIFY cameraFormatChanged)
- Q_PROPERTY(FocusMode focusMode READ focusMode WRITE setFocusMode)
+ Q_PROPERTY(FocusMode focusMode READ focusMode WRITE setFocusMode NOTIFY focusModeChanged)
Q_PROPERTY(QPointF focusPoint READ focusPoint NOTIFY focusPointChanged)
Q_PROPERTY(QPointF customFocusPoint READ customFocusPoint WRITE setCustomFocusPoint NOTIFY customFocusPointChanged)
Q_PROPERTY(float focusDistance READ focusDistance WRITE setFocusDistance NOTIFY focusDistanceChanged)
diff --git a/src/multimedia/configure.cmake b/src/multimedia/configure.cmake
index 7c3092a47..5fe25f172 100644
--- a/src/multimedia/configure.cmake
+++ b/src/multimedia/configure.cmake
@@ -22,7 +22,11 @@ qt_find_package(MMRendererCore PROVIDED_TARGETS MMRendererCore::MMRendererCore M
qt_find_package(MMRenderer PROVIDED_TARGETS MMRenderer::MMRenderer MODULE_NAME multimedia QMAKE_LIB mmrndclient)
qt_find_package(WrapPulseAudio PROVIDED_TARGETS WrapPulseAudio::WrapPulseAudio MODULE_NAME multimedia QMAKE_LIB pulseaudio)
qt_find_package(WMF PROVIDED_TARGETS WMF::WMF MODULE_NAME multimedia QMAKE_LIB wmf)
-qt_find_package(EGL)
+if(TARGET EGL::EGL)
+ qt_internal_disable_find_package_global_promotion(EGL::EGL)
+endif()
+qt_find_package(EGL PROVIDED_TARGETS EGL::EGL)
+
qt_find_package(FFmpeg OPTIONAL_COMPONENTS AVCODEC AVFORMAT AVUTIL SWRESAMPLE SWSCALE PROVIDED_TARGETS FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil FFmpeg::swresample FFmpeg::swscale MODULE_NAME multimedia QMAKE_LIB ffmpeg)
qt_find_package(VAAPI COMPONENTS VA DRM PROVIDED_TARGETS VAAPI::VAAPI MODULE_NAME multimedia QMAKE_LIB vaapi)
diff --git a/src/multimedia/doc/qtmultimedia.qdocconf b/src/multimedia/doc/qtmultimedia.qdocconf
index 5a16bddec..97e6dc696 100644
--- a/src/multimedia/doc/qtmultimedia.qdocconf
+++ b/src/multimedia/doc/qtmultimedia.qdocconf
@@ -1,6 +1,5 @@
include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
include($QT_INSTALL_DOCS/config/exampleurl-qtmultimedia.qdocconf)
-include(../../../examples/multimedia/video/mediaplayer/doc/qmlmediaplayer.qdocconf)
project = QtMultimedia
description = Qt Multimedia Documentation
diff --git a/src/multimedia/doc/src/qtmultimedia-building-from-source.qdoc b/src/multimedia/doc/src/qtmultimedia-building-from-source.qdoc
new file mode 100644
index 000000000..df434c699
--- /dev/null
+++ b/src/multimedia/doc/src/qtmultimedia-building-from-source.qdoc
@@ -0,0 +1,94 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qtmultimedia-building-from-source.html
+\title Building Qt Multimedia from sources
+\brief This document describes how to build Qt Multimedia with full
+feature support from source code.
+
+This page describes the process of configuring and building \l{Qt
+Multimedia}. This description assumes familiarity with \l{Building Qt
+Sources} which specifies build requirements for your platform, as well
+as an overview of \l{Qt Configure Options}. For platform-specific
+considerations related to the Qt Multimedia module, see \l{Platform
+Notes} below.
+
+\section1 Building from source
+
+Building Qt Multimedia with full feature support depends on \l
+{https://ffmpeg.org/}{FFmpeg} headers and libraries on most platforms.
+It is possible to build Qt Multimedia without the Qt Multimedia FFmpeg
+media backend, but this is only recommended when building for platforms
+where the FFmpeg backend is not supported.
+
+FFmpeg developer libraries required to build Qt Multimedia can be built
+from sources or downloaded as binary packages. Qt Multimedia can use
+either static linking or dynamic linking to FFmpeg libraries. We
+recommend using the same major version of FFmpeg that is listed in
+\l{FFmpeg as the default backend}.
+
+To build Qt Multimedia with FFmpeg support, specify the \c{-DFFMPEG_DIR}
+CMake variable on the configure command line when building Qt. Note the
+\c{--} separator which separates ordinary configure arguments from CMake
+parameters.
+
+\badcode
+qt-source/configure -- -DFFMPEG_DIR=<FFMPEG_DIR>
+\endcode
+
+Here, \c{<FFMPEG_DIR>} is the directory containing the FFmpeg include,
+lib, and bin directories. To build Qt Multimedia without FFmpeg, omit
+the \c{<FFMPEG_DIR>} variable or specify the \c{-no-feature-ffmpeg}
+configure option.
+
+If you prefer not to build all Qt's submodules, you can reduce configure
+and build times using the \c{-submodules} configure option. This will
+configure a build that only builds Qt Multimedia and its dependencies.
+
+\badcode
+qt-source/configure -submodules qtmultimedia -- -DFFMPEG_DIR=<FFMPEG_DIR>
+\endcode
+
+If you configure Qt Multimedia against FFmpeg built with shared
+libraries (dynamic linking), the FFmpeg shared libraries must be in the
+module loader's search path to run tests or use examples.
+
+\note Qt Multimedia requires the FFmpeg avformat, avcodec, swresample,
+swscale, and avutil libraries during runtime to be able to use the
+FFmpeg media backend. If one or more of these dynamic libraries are not
+found during application startup, the FFmpeg media backend will fail to
+load, and the system will attempt to load the native backend. Qt
+Multimedia doesn't support as many features on native backends.
+
+If you don't already have these libraries in the \c{path}, specify the
+\c{-DQT_DEPLOY_FFMPEG=ON} configure option. With this option enabled,
+the necessary FFmpeg binaries will be copied to Qt's install directory
+during the build and install steps:
+
+\badcode
+qt-source/configure -submodules qtmultimedia -- -DFFMPEG_DIR=<FFMPEG_DIR> -DQT_DEPLOY_FFMPEG=ON
+\endcode
+
+After configuring Qt Multimedia, carefully review the configure summary
+(found in the config.summary file). You can verify that FFmpeg is found
+under the "Plugin" section. Then follow the regular build and install
+steps described in \l{Building Qt Sources}.
+
+\section1 Platform Notes
+
+\section2 Linux
+
+\list
+ \li When configuring Qt Multimedia with FFmpeg enabled, the
+ pulseaudio development package is required. Without this
+ package, FFmpeg will not be recognized.
+ \li When using a version of FFmpeg that is built with VAAPI support,
+ we recommend building Qt Multimedia with VAAPI support as well
+ to make hardware texture conversion possible. To configure Qt
+ Multimedia with VAAPI support, VAAPI developer libraries must be
+ installed on your system. Review the config.summary file to
+ verify that VAAPI support is enabled under the "Hardware
+ acceleration and features" section.
+\endlist
+*/
diff --git a/src/multimedia/doc/src/qtmultimedia-index.qdoc b/src/multimedia/doc/src/qtmultimedia-index.qdoc
index 7cde47add..67b6688be 100644
--- a/src/multimedia/doc/src/qtmultimedia-index.qdoc
+++ b/src/multimedia/doc/src/qtmultimedia-index.qdoc
@@ -48,6 +48,9 @@
target_link_libraries(my_project PRIVATE Qt6::Multimedia)
\endcode
+ See \l {Building Qt Multimedia from sources} for guidance on building
+ Qt Multimedia from sources.
+
\section1 Overviews and important topics
\list
diff --git a/src/multimedia/platform/qplatformmediacapture.cpp b/src/multimedia/platform/qplatformmediacapture.cpp
index 826228764..c8aded824 100644
--- a/src/multimedia/platform/qplatformmediacapture.cpp
+++ b/src/multimedia/platform/qplatformmediacapture.cpp
@@ -1,12 +1,14 @@
// Copyright (C) 2021 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 <qtmultimediaglobal_p.h>
-#include "qplatformmediacapture_p.h"
-#include "qaudiodevice.h"
-#include "qaudioinput.h"
-#include "qplatformcamera_p.h"
-#include "qplatformsurfacecapture_p.h"
+#include <QtMultimedia/qaudiodevice.h>
+#include <QtMultimedia/qaudioinput.h>
+#include <QtMultimedia/qmediacapturesession.h>
+#include <QtMultimedia/private/qplatformcamera_p.h>
+#include <QtMultimedia/private/qplatformmediacapture_p.h>
+#include <QtMultimedia/private/qmediacapturesession_p.h>
+#include <QtMultimedia/private/qplatformsurfacecapture_p.h>
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -28,6 +30,15 @@ std::vector<QPlatformVideoSource *> QPlatformMediaCaptureSession::activeVideoSou
return result;
}
+void *QPlatformMediaCaptureSession::nativePipeline(QMediaCaptureSession *session)
+{
+ auto sessionPrivate = session->d_func();
+ if (!sessionPrivate || !sessionPrivate->captureSession)
+ return nullptr;
+
+ return sessionPrivate->captureSession->nativePipeline();
+}
+
QT_END_NAMESPACE
#include "moc_qplatformmediacapture_p.cpp"
diff --git a/src/multimedia/platform/qplatformmediacapture_p.h b/src/multimedia/platform/qplatformmediacapture_p.h
index 814fa160c..981cf199b 100644
--- a/src/multimedia/platform/qplatformmediacapture_p.h
+++ b/src/multimedia/platform/qplatformmediacapture_p.h
@@ -64,6 +64,11 @@ public:
// TBD: implement ordering of the sources basing on the order of adding
std::vector<QPlatformVideoSource *> activeVideoSources();
+ virtual void *nativePipeline() { return nullptr; }
+
+ // private API, the purpose is getting GstPipeline
+ static void *nativePipeline(QMediaCaptureSession *);
+
Q_SIGNALS:
void cameraChanged();
void screenCaptureChanged();
diff --git a/src/multimedia/platform/qplatformmediaintegration.cpp b/src/multimedia/platform/qplatformmediaintegration.cpp
index c8f662963..dda00de61 100644
--- a/src/multimedia/platform/qplatformmediaintegration.cpp
+++ b/src/multimedia/platform/qplatformmediaintegration.cpp
@@ -91,48 +91,10 @@ struct InstanceHolder
qCDebug(qLcMediaPlugin) << "Released media backend";
}
- // Play nice with QtGlobalStatic::ApplicationHolder
- using QAS_Type = InstanceHolder;
- static void innerFunction(void *pointer)
- {
- new (pointer) InstanceHolder();
- }
-
std::unique_ptr<QPlatformMediaIntegration> instance;
};
-// Specialized implementation of Q_APPLICATION_STATIC which behaves as
-// an application static if a Qt application is present, otherwise as a Q_GLOBAL_STATIC.
-// By doing this, and we have a Qt application, all system resources allocated by the
-// backend is released when application lifetime ends. This is important on Windows,
-// where Windows Media Foundation instances should not be released during static destruction.
-//
-// If we don't have a Qt application available when instantiating the instance holder,
-// it will be created once, and not destroyed until static destruction. This can cause
-// abrupt termination of Windows applications during static destruction. This is not a
-// supported use case, but we keep this as a fallback to keep old applications functional.
-// See also QTBUG-120198
-struct ApplicationHolder : QtGlobalStatic::ApplicationHolder<InstanceHolder>
-{
- // Replace QtGlobalStatic::ApplicationHolder::pointer to prevent crash if
- // no application is present
- static InstanceHolder* pointer()
- {
- if (guard.loadAcquire() == QtGlobalStatic::Initialized)
- return realPointer();
-
- QMutexLocker locker(&mutex);
- if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) {
- InstanceHolder::innerFunction(&storage);
-
- if (const QCoreApplication *app = QCoreApplication::instance())
- QObject::connect(app, &QObject::destroyed, app, reset, Qt::DirectConnection);
-
- guard.storeRelease(QtGlobalStatic::Initialized);
- }
- return realPointer();
- }
-};
+Q_APPLICATION_STATIC(InstanceHolder, s_instanceHolder);
} // namespace
@@ -140,7 +102,6 @@ QT_BEGIN_NAMESPACE
QPlatformMediaIntegration *QPlatformMediaIntegration::instance()
{
- static QGlobalStatic<ApplicationHolder> s_instanceHolder;
return s_instanceHolder->instance.get();
}
diff --git a/src/multimedia/recording/qmediacapturesession.cpp b/src/multimedia/recording/qmediacapturesession.cpp
index 0ff804bf4..f175cd98e 100644
--- a/src/multimedia/recording/qmediacapturesession.cpp
+++ b/src/multimedia/recording/qmediacapturesession.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qmediacapturesession.h"
+#include "qmediacapturesession_p.h"
#include "qaudiodevice.h"
#include "qcamera.h"
#include "qmediarecorder.h"
@@ -10,8 +11,6 @@
#include "qscreencapture.h"
#include "qwindowcapture.h"
-#include <qpointer.h>
-
#include "qplatformmediaintegration_p.h"
#include "qplatformmediacapture_p.h"
#include "qaudioinput.h"
@@ -19,35 +18,19 @@
QT_BEGIN_NAMESPACE
-class QMediaCaptureSessionPrivate
+void QMediaCaptureSessionPrivate::setVideoSink(QVideoSink *sink)
{
-public:
- QMediaCaptureSession *q = nullptr;
- QPlatformMediaCaptureSession *captureSession = nullptr;
- QAudioInput *audioInput = nullptr;
- QAudioOutput *audioOutput = nullptr;
- QPointer<QCamera> camera;
- QPointer<QScreenCapture> screenCapture;
- QPointer<QWindowCapture> windowCapture;
- QPointer<QImageCapture> imageCapture;
- QPointer<QMediaRecorder> recorder;
- QPointer<QVideoSink> videoSink;
- QPointer<QObject> videoOutput;
-
- void setVideoSink(QVideoSink *sink)
- {
- if (sink == videoSink)
- return;
- if (videoSink)
- videoSink->setSource(nullptr);
- videoSink = sink;
- if (sink)
- sink->setSource(q);
- if (captureSession)
- captureSession->setVideoPreview(sink);
- emit q->videoOutputChanged();
- }
-};
+ if (sink == videoSink)
+ return;
+ if (videoSink)
+ videoSink->setSource(nullptr);
+ videoSink = sink;
+ if (sink)
+ sink->setSource(q);
+ if (captureSession)
+ captureSession->setVideoPreview(sink);
+ emit q->videoOutputChanged();
+}
/*!
\class QMediaCaptureSession
diff --git a/src/multimedia/recording/qmediacapturesession.h b/src/multimedia/recording/qmediacapturesession.h
index c613c3615..1333af7eb 100644
--- a/src/multimedia/recording/qmediacapturesession.h
+++ b/src/multimedia/recording/qmediacapturesession.h
@@ -78,6 +78,8 @@ Q_SIGNALS:
void audioOutputChanged();
private:
+ friend class QPlatformMediaCaptureSession;
+
QMediaCaptureSessionPrivate *d_ptr;
Q_DISABLE_COPY(QMediaCaptureSession)
Q_DECLARE_PRIVATE(QMediaCaptureSession)
diff --git a/src/multimedia/recording/qmediacapturesession_p.h b/src/multimedia/recording/qmediacapturesession_p.h
new file mode 100644
index 000000000..8702c8d2b
--- /dev/null
+++ b/src/multimedia/recording/qmediacapturesession_p.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 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
+
+#ifndef QMEDIACAPTURESESSION_P_H
+#define QMEDIACAPTURESESSION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qmediacapturesession.h>
+
+#include <QtCore/qpointer.h>
+
+QT_BEGIN_NAMESPACE
+
+class QMediaCaptureSessionPrivate
+{
+public:
+ QMediaCaptureSession *q = nullptr;
+ QPlatformMediaCaptureSession *captureSession = nullptr;
+ QAudioInput *audioInput = nullptr;
+ QAudioOutput *audioOutput = nullptr;
+ QPointer<QCamera> camera;
+ QPointer<QScreenCapture> screenCapture;
+ QPointer<QWindowCapture> windowCapture;
+ QPointer<QImageCapture> imageCapture;
+ QPointer<QMediaRecorder> recorder;
+ QPointer<QVideoSink> videoSink;
+ QPointer<QObject> videoOutput;
+
+ void setVideoSink(QVideoSink *sink);
+};
+
+QT_END_NAMESPACE
+
+#endif // QMEDIACAPTURESESSION_P_H
diff --git a/src/multimedia/video/qvideoframe.cpp b/src/multimedia/video/qvideoframe.cpp
index 90560f506..de981f423 100644
--- a/src/multimedia/video/qvideoframe.cpp
+++ b/src/multimedia/video/qvideoframe.cpp
@@ -112,6 +112,7 @@ QVideoFrame::QVideoFrame(const QVideoFrameFormat &format)
into the QVideoFrame's memory buffer. The resulting frame has the
same size as the QImage, but the number of bytes per line may
differ.
+ \since 6.8
If the QImage::Format matches one of the formats in
QVideoFrameFormat::PixelFormat, the QVideoFrame will use that format
diff --git a/src/plugins/multimedia/CMakeLists.txt b/src/plugins/multimedia/CMakeLists.txt
index 978710112..5bc39c1f8 100644
--- a/src/plugins/multimedia/CMakeLists.txt
+++ b/src/plugins/multimedia/CMakeLists.txt
@@ -1,24 +1,24 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-if (QT_FEATURE_ffmpeg)
+if(QT_FEATURE_ffmpeg)
add_subdirectory(ffmpeg)
-endif ()
-if (QT_FEATURE_gstreamer)
+endif()
+if(QT_FEATURE_gstreamer)
add_subdirectory(gstreamer)
-endif ()
-if (ANDROID)
+endif()
+if(ANDROID)
add_subdirectory(android)
-endif ()
-if (WASM)
+endif()
+if(WASM)
add_subdirectory(wasm)
-endif ()
-if (APPLE AND NOT WATCHOS)
+endif()
+if(APPLE AND NOT WATCHOS)
add_subdirectory(darwin)
-endif ()
-if (QT_FEATURE_wmf)
+endif()
+if(QT_FEATURE_wmf)
add_subdirectory(windows)
-endif ()
-if (QT_FEATURE_mmrenderer)
+endif()
+if(QT_FEATURE_mmrenderer)
add_subdirectory(qnx)
-endif ()
+endif()
diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp
index a99432c29..457b3603d 100644
--- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp
+++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp
@@ -18,13 +18,6 @@ Codec::Data::Data(AVCodecContextUPtr context, AVStream *stream, AVFormatContext
pixelAspectRatio = av_guess_sample_aspect_ratio(formatContext, stream, nullptr);
}
-Codec::Data::~Data()
-{
- // TODO: investigate if we can remove avcodec_close
- // FFmpeg doc says that avcodec_free_context is enough
- avcodec_close(context.get());
-}
-
QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext)
{
if (!stream)
diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h
index 5510e0e84..449fb1f65 100644
--- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h
+++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h
@@ -31,7 +31,6 @@ class Codec
{
Data(AVCodecContextUPtr context, AVStream *stream, AVFormatContext *formatContext,
std::unique_ptr<QFFmpeg::HWAccel> hwAccel);
- ~Data();
QAtomicInt ref;
AVCodecContextUPtr context;
AVStream *stream = nullptr;
diff --git a/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp b/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp
index df583ce0e..bf01a4e30 100644
--- a/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp
+++ b/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp
@@ -55,8 +55,8 @@ QCameraFormat getDefaultCameraFormat()
QCameraFormatPrivate *defaultFormat = new QCameraFormatPrivate{
.pixelFormat = QVideoFrameFormat::Format_YUV420P,
.resolution = { 1920, 1080 },
- .minFrameRate = 30,
- .maxFrameRate = 60,
+ .minFrameRate = 12,
+ .maxFrameRate = 30,
};
return defaultFormat->create();
}
@@ -273,6 +273,15 @@ void QAndroidCamera::setActive(bool active)
setState(State::WaitingOpen);
g_qcameras->insert(m_cameraDevice.id(), this);
+ // this should use the camera format.
+ // but there is only 2 fully supported formats on android - JPG and YUV420P
+ // and JPEG is not supported for encoding in FFmpeg, so it's locked for YUV for now.
+ const static int imageFormat =
+ QJniObject::getStaticField<QtJniTypes::AndroidImageFormat, jint>("YUV_420_888");
+ m_jniCamera.callMethod<void>("prepareCamera", jint(width), jint(height),
+ jint(imageFormat), jint(m_cameraFormat.minFrameRate()),
+ jint(m_cameraFormat.maxFrameRate()));
+
bool canOpen = m_jniCamera.callMethod<jboolean>(
"open", QJniObject::fromString(m_cameraDevice.id()).object<jstring>());
@@ -282,15 +291,6 @@ void QAndroidCamera::setActive(bool active)
emit error(QCamera::CameraError,
QString("Failed to start camera: ").append(m_cameraDevice.description()));
}
-
- // this should use the camera format.
- // but there is only 2 fully supported formats on android - JPG and YUV420P
- // and JPEG is not supported for encoding in FFmpeg, so it's locked for YUV for now.
- const static int imageFormat =
- QJniObject::getStaticField<QtJniTypes::AndroidImageFormat, jint>("YUV_420_888");
- m_jniCamera.callMethod<jboolean>("addImageReader", jint(width), jint(height),
- jint(imageFormat));
-
} else {
m_jniCamera.callMethod<void>("stopAndClose");
m_jniCamera.callMethod<void>("clearSurfaces");
@@ -332,10 +332,18 @@ void QAndroidCamera::setState(QAndroidCamera::State newState)
bool QAndroidCamera::setCameraFormat(const QCameraFormat &format)
{
- if (!format.isNull() && !m_cameraDevice.videoFormats().contains(format))
+ const auto chosenFormat = format.isNull() ? getDefaultCameraFormat() : format;
+
+ if (chosenFormat == m_cameraFormat || !m_cameraDevice.videoFormats().contains(chosenFormat))
return false;
- m_cameraFormat = format.isNull() ? getDefaultCameraFormat() : format;
+ m_cameraFormat = chosenFormat;
+
+ if (isActive()) {
+ // Restart the camera to set new camera format
+ setActive(false);
+ setActive(true);
+ }
return true;
}
diff --git a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp
index 81eef89ef..09ffaaf71 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp
+++ b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp
@@ -231,7 +231,7 @@ TextureSet *VAAPITextureConverter::getTextures(AVFrame *frame)
VASurfaceID vaSurface = (uintptr_t)frame->data[3];
- VADRMPRIMESurfaceDescriptor prime;
+ VADRMPRIMESurfaceDescriptor prime = {};
if (vaExportSurfaceHandle(vaDisplay, vaSurface,
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_READ_ONLY |
@@ -245,6 +245,13 @@ TextureSet *VAAPITextureConverter::getTextures(AVFrame *frame)
qWarning() << "vaExportSurfaceHandle failed";
return nullptr;
}
+
+ // Make sure all fd's in 'prime' are closed when we return from this function
+ QScopeGuard closeObjectsGuard([&prime]() {
+ for (uint32_t i = 0; i < prime.num_objects; ++i)
+ close(prime.objects[i].fd);
+ });
+
// ### Check that prime.fourcc is what we expect
vaSyncSurface(vaDisplay, vaSurface);
@@ -325,9 +332,6 @@ TextureSet *VAAPITextureConverter::getTextures(AVFrame *frame)
qWarning() << "eglImageTargetTexture2D failed with error code" << error;
}
- for (int i = 0; i < (int)prime.num_objects; ++i)
- close(prime.objects[i].fd);
-
for (int i = 0; i < nPlanes; ++i) {
functions.glActiveTexture(GL_TEXTURE0 + i);
functions.glBindTexture(GL_TEXTURE_2D, 0);
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp
index 9b1a70742..97b4570d5 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp
+++ b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp
@@ -77,13 +77,13 @@ void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings)
return;
}
- m_encoder.reset(new RecordingEngine(settings, std::move(formatContext)));
- m_encoder->setMetaData(m_metaData);
- connect(m_encoder.get(), &QFFmpeg::RecordingEngine::durationChanged, this,
+ m_recordingEngine.reset(new RecordingEngine(settings, std::move(formatContext)));
+ m_recordingEngine->setMetaData(m_metaData);
+ connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::durationChanged, this,
&QFFmpegMediaRecorder::newDuration);
- connect(m_encoder.get(), &QFFmpeg::RecordingEngine::finalizationDone, this,
+ connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::finalizationDone, this,
&QFFmpegMediaRecorder::finalizationDone);
- connect(m_encoder.get(), &QFFmpeg::RecordingEngine::error, this,
+ connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::error, this,
&QFFmpegMediaRecorder::handleSessionError);
auto *audioInput = m_session->audioInput();
@@ -91,17 +91,17 @@ void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings)
if (audioInput->device.isNull())
qWarning() << "Audio input device is null; cannot encode audio";
else
- m_encoder->addAudioInput(static_cast<QFFmpegAudioInput *>(audioInput));
+ m_recordingEngine->addAudioInput(static_cast<QFFmpegAudioInput *>(audioInput));
}
for (auto source : videoSources)
- m_encoder->addVideoSource(source);
+ m_recordingEngine->addVideoSource(source);
durationChanged(0);
stateChanged(QMediaRecorder::RecordingState);
actualLocationChanged(QUrl::fromLocalFile(actualLocation));
- m_encoder->start();
+ m_recordingEngine->start();
}
void QFFmpegMediaRecorder::pause()
@@ -109,8 +109,8 @@ void QFFmpegMediaRecorder::pause()
if (!m_session || state() != QMediaRecorder::RecordingState)
return;
- Q_ASSERT(m_encoder);
- m_encoder->setPaused(true);
+ Q_ASSERT(m_recordingEngine);
+ m_recordingEngine->setPaused(true);
stateChanged(QMediaRecorder::PausedState);
}
@@ -120,8 +120,8 @@ void QFFmpegMediaRecorder::resume()
if (!m_session || state() != QMediaRecorder::PausedState)
return;
- Q_ASSERT(m_encoder);
- m_encoder->setPaused(false);
+ Q_ASSERT(m_recordingEngine);
+ m_recordingEngine->setPaused(false);
stateChanged(QMediaRecorder::RecordingState);
}
@@ -135,7 +135,7 @@ void QFFmpegMediaRecorder::stop()
static_cast<QFFmpegAudioInput *>(input)->setRunning(false);
qCDebug(qLcMediaEncoder) << "stop";
- m_encoder.reset();
+ m_recordingEngine.reset();
}
void QFFmpegMediaRecorder::finalizationDone()
@@ -169,11 +169,12 @@ void QFFmpegMediaRecorder::setCaptureSession(QFFmpegMediaCaptureSession *session
return;
}
-void QFFmpegMediaRecorder::EncoderDeleter::operator()(RecordingEngine *encoder) const
+void QFFmpegMediaRecorder::RecordingEngineDeleter::operator()(
+ RecordingEngine *recordingEngine) const
{
// ### all of the below should be done asynchronous. finalize() should do it's work in a thread
// to avoid blocking the UI in case of slow codecs
- encoder->finalize();
+ recordingEngine->finalize();
}
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder_p.h b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder_p.h
index 3a3bbcf5c..8b73ad76d 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder_p.h
+++ b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder_p.h
@@ -55,7 +55,7 @@ private Q_SLOTS:
private:
using RecordingEngine = QFFmpeg::RecordingEngine;
- struct EncoderDeleter
+ struct RecordingEngineDeleter
{
void operator()(RecordingEngine *) const;
};
@@ -63,7 +63,7 @@ private:
QFFmpegMediaCaptureSession *m_session = nullptr;
QMediaMetaData m_metaData;
- std::unique_ptr<RecordingEngine, EncoderDeleter> m_encoder;
+ std::unique_ptr<RecordingEngine, RecordingEngineDeleter> m_recordingEngine;
};
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegthread.cpp b/src/plugins/multimedia/ffmpeg/qffmpegthread.cpp
index c098bb68c..fb14ced54 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpegthread.cpp
+++ b/src/plugins/multimedia/ffmpeg/qffmpegthread.cpp
@@ -11,8 +11,8 @@ using namespace QFFmpeg;
void ConsumerThread::stopAndDelete()
{
{
- QMutexLocker locker(&exitMutex);
- exit = true;
+ QMutexLocker locker(&m_loopDataMutex);
+ m_exit = true;
}
dataReady();
wait();
@@ -21,7 +21,7 @@ void ConsumerThread::stopAndDelete()
void ConsumerThread::dataReady()
{
- condition.wakeAll();
+ m_condition.wakeAll();
}
void ConsumerThread::run()
@@ -31,11 +31,11 @@ void ConsumerThread::run()
while (true) {
{
- QMutexLocker locker(&exitMutex);
- while (!hasData() && !exit)
- condition.wait(&exitMutex);
+ QMutexLocker locker(&m_loopDataMutex);
+ while (!hasData() && !m_exit)
+ m_condition.wait(&m_loopDataMutex);
- if (exit)
+ if (m_exit)
break;
}
@@ -45,4 +45,9 @@ void ConsumerThread::run()
cleanup();
}
+QMutexLocker<QMutex> ConsumerThread::lockLoopData() const
+{
+ return QMutexLocker(&m_loopDataMutex);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegthread_p.h b/src/plugins/multimedia/ffmpeg/qffmpegthread_p.h
index a9f80c36f..a7c5b0927 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpegthread_p.h
+++ b/src/plugins/multimedia/ffmpeg/qffmpegthread_p.h
@@ -65,7 +65,8 @@ protected:
/*!
Wake thread from sleep and process data until
- hasData() returns false.
+ hasData() returns false. The method is supposed to be invoked
+ right after the scope of QMutexLocker that lockLoopData returns.
*/
void dataReady();
@@ -74,12 +75,18 @@ protected:
*/
virtual bool hasData() const = 0;
+ /*!
+ Locks the loop data mutex. It must be used to protect loop data
+ like a queue of video frames.
+ */
+ QMutexLocker<QMutex> lockLoopData() const;
+
private:
void run() final;
- QMutex exitMutex; // Protects exit flag.
- QWaitCondition condition;
- bool exit = false;
+ mutable QMutex m_loopDataMutex;
+ QWaitCondition m_condition;
+ bool m_exit = false;
};
}
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegvaapisymbols.cpp b/src/plugins/multimedia/ffmpeg/qffmpegvaapisymbols.cpp
index 58bf4dce7..ed2532e73 100644
--- a/src/plugins/multimedia/ffmpeg/qffmpegvaapisymbols.cpp
+++ b/src/plugins/multimedia/ffmpeg/qffmpegvaapisymbols.cpp
@@ -37,7 +37,7 @@ static Libs loadLibs()
return {};
}
-constexpr size_t symbolsCount = 38
+constexpr size_t symbolsCount = 40
#if VA_CHECK_VERSION(1, 9, 0)
+ 1
#endif
@@ -114,6 +114,9 @@ DEFINE_FUNC(vaGetDisplayAttributes, 3, VA_STATUS_ERROR_OPERATION_FAILED);
DEFINE_FUNC(vaSetDriverName, 2, VA_STATUS_ERROR_OPERATION_FAILED);
+DEFINE_FUNC(vaAcquireBufferHandle, 3, VA_STATUS_ERROR_OPERATION_FAILED);
+DEFINE_FUNC(vaReleaseBufferHandle, 2, VA_STATUS_ERROR_OPERATION_FAILED);
+
#ifdef DYNAMIC_RESOLVE_VA_DRM_SYMBOLS
DEFINE_FUNC(vaGetDisplayDRM, 1); // va-drm
#endif
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder.cpp
index 57b798fed..9948952e8 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder.cpp
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder.cpp
@@ -15,16 +15,16 @@ namespace QFFmpeg {
static Q_LOGGING_CATEGORY(qLcFFmpegAudioEncoder, "qt.multimedia.ffmpeg.audioencoder");
-AudioEncoder::AudioEncoder(RecordingEngine *encoder, QFFmpegAudioInput *input,
+AudioEncoder::AudioEncoder(RecordingEngine &recordingEngine, QFFmpegAudioInput *input,
const QMediaEncoderSettings &settings)
- : EncoderThread(encoder), m_input(input), m_settings(settings)
+ : EncoderThread(recordingEngine), m_input(input), m_settings(settings)
{
setObjectName(QLatin1String("AudioEncoder"));
qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder" << settings.audioCodec();
m_format = input->device.preferredFormat();
auto codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
- Q_ASSERT(avformat_query_codec(encoder->avFormatContext()->oformat, codecID,
+ Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
FF_COMPLIANCE_NORMAL));
const AVAudioFormat requestedAudioFormat(m_format);
@@ -38,8 +38,8 @@ AudioEncoder::AudioEncoder(RecordingEngine *encoder, QFFmpegAudioInput *input,
Q_ASSERT(m_avCodec);
- m_stream = avformat_new_stream(encoder->avFormatContext(), nullptr);
- m_stream->id = encoder->avFormatContext()->nb_streams - 1;
+ m_stream = avformat_new_stream(recordingEngine.avFormatContext(), nullptr);
+ m_stream->id = recordingEngine.avFormatContext()->nb_streams - 1;
m_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
m_stream->codecpar->codec_id = codecID;
#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
@@ -96,7 +96,7 @@ void AudioEncoder::open()
void AudioEncoder::addBuffer(const QAudioBuffer &buffer)
{
- QMutexLocker locker(&m_queueMutex);
+ QMutexLocker locker = lockLoopData();
if (!m_paused.loadRelaxed()) {
m_audioBufferQueue.push(buffer);
locker.unlock();
@@ -106,7 +106,7 @@ void AudioEncoder::addBuffer(const QAudioBuffer &buffer)
QAudioBuffer AudioEncoder::takeBuffer()
{
- QMutexLocker locker(&m_queueMutex);
+ QMutexLocker locker = lockLoopData();
return dequeueIfPossible(m_audioBufferQueue);
}
@@ -130,7 +130,6 @@ void AudioEncoder::cleanup()
bool AudioEncoder::hasData() const
{
- QMutexLocker locker(&m_queueMutex);
return !m_audioBufferQueue.empty();
}
@@ -153,7 +152,7 @@ void AudioEncoder::retrievePackets()
// qCDebug(qLcFFmpegEncoder) << "writing audio packet" << packet->size << packet->pts <<
// packet->dts;
packet->stream_index = m_stream->id;
- m_encoder->getMuxer()->addPacket(std::move(packet));
+ m_recordingEngine.getMuxer()->addPacket(std::move(packet));
}
}
@@ -202,7 +201,7 @@ void AudioEncoder::processOne()
m_samplesWritten += buffer.frameCount();
qint64 time = m_format.durationForFrames(m_samplesWritten);
- m_encoder->newTimeStamp(time / 1000);
+ m_recordingEngine.newTimeStamp(time / 1000);
// qCDebug(qLcFFmpegEncoder) << "sending audio frame" << buffer.byteCount() << frame->pts <<
// ((double)buffer.frameCount()/frame->sample_rate);
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder_p.h
index c4dc20eac..16d8d81a1 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder_p.h
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoder_p.h
@@ -19,7 +19,7 @@ namespace QFFmpeg {
class AudioEncoder : public EncoderThread
{
public:
- AudioEncoder(RecordingEngine *encoder, QFFmpegAudioInput *input,
+ AudioEncoder(RecordingEngine &recordingEngine, QFFmpegAudioInput *input,
const QMediaEncoderSettings &settings);
void open();
@@ -37,7 +37,6 @@ private:
void processOne() override;
private:
- mutable QMutex m_queueMutex;
std::queue<QAudioBuffer> m_audioBufferQueue;
AVStream *m_stream = nullptr;
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread.cpp
index 97c8fb7a9..b673af450 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread.cpp
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread.cpp
@@ -6,7 +6,9 @@ QT_BEGIN_NAMESPACE
namespace QFFmpeg {
-EncoderThread::EncoderThread(RecordingEngine *encoder) : m_encoder(encoder) { }
+EncoderThread::EncoderThread(RecordingEngine &recordingEngine) : m_recordingEngine(recordingEngine)
+{
+}
void EncoderThread::setPaused(bool b)
{
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread_p.h
index 6ef5e97f6..1fe35303b 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread_p.h
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderthread_p.h
@@ -14,12 +14,12 @@ class RecordingEngine;
class EncoderThread : public ConsumerThread
{
public:
- EncoderThread(RecordingEngine *encoder);
+ EncoderThread(RecordingEngine &recordingEngine);
virtual void setPaused(bool b);
protected:
QAtomicInteger<bool> m_paused = false;
- RecordingEngine *m_encoder = nullptr;
+ RecordingEngine &m_recordingEngine;
};
} // namespace QFFmpeg
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp
index 20d38ace1..2b32af502 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp
@@ -36,7 +36,7 @@ RecordingEngine::~RecordingEngine()
void RecordingEngine::addAudioInput(QFFmpegAudioInput *input)
{
- m_audioEncoder = new AudioEncoder(this, input, m_settings);
+ m_audioEncoder = new AudioEncoder(*this, input, m_settings);
addMediaFrameHandler(input, &QFFmpegAudioInput::newAudioBuffer, m_audioEncoder,
&AudioEncoder::addBuffer);
input->setRunning(true);
@@ -63,7 +63,7 @@ void RecordingEngine::addVideoSource(QPlatformVideoSource * source)
<< "frameRate=" << frameFormat.frameRate() << "ffmpegHWPixelFormat="
<< (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
- auto veUPtr = std::make_unique<VideoEncoder>(this, m_settings, frameFormat, hwPixelFormat);
+ auto veUPtr = std::make_unique<VideoEncoder>(*this, m_settings, frameFormat, hwPixelFormat);
if (!veUPtr->isValid()) {
emit error(QMediaRecorder::FormatError, QLatin1StringView("Cannot initialize encoder"));
return;
@@ -101,36 +101,39 @@ void RecordingEngine::start()
videoEncoder->start();
}
-RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine *e) : m_encoder(e)
+RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine)
+ : m_recordingEngine(recordingEngine)
{
connect(this, &QThread::finished, this, &QObject::deleteLater);
}
void RecordingEngine::EncodingFinalizer::run()
{
- if (m_encoder->m_audioEncoder)
- m_encoder->m_audioEncoder->stopAndDelete();
- for (auto &videoEncoder : m_encoder->m_videoEncoders)
+ if (m_recordingEngine.m_audioEncoder)
+ m_recordingEngine.m_audioEncoder->stopAndDelete();
+ for (auto &videoEncoder : m_recordingEngine.m_videoEncoders)
videoEncoder->stopAndDelete();
- m_encoder->m_muxer->stopAndDelete();
+ m_recordingEngine.m_muxer->stopAndDelete();
- if (m_encoder->m_isHeaderWritten) {
- const int res = av_write_trailer(m_encoder->avFormatContext());
+ if (m_recordingEngine.m_isHeaderWritten) {
+ const int res = av_write_trailer(m_recordingEngine.avFormatContext());
if (res < 0) {
const auto errorDescription = err2str(res);
qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << errorDescription;
- emit m_encoder->error(QMediaRecorder::FormatError,
- QLatin1String("Cannot write trailer: ") + errorDescription);
+ emit m_recordingEngine.error(QMediaRecorder::FormatError,
+ QLatin1String("Cannot write trailer: ")
+ + errorDescription);
}
}
// else ffmpeg might crash
// close AVIO before emitting finalizationDone.
- m_encoder->m_formatContext->closeAVIO();
+ m_recordingEngine.m_formatContext->closeAVIO();
qCDebug(qLcFFmpegEncoder) << " done finalizing.";
- emit m_encoder->finalizationDone();
- delete m_encoder;
+ emit m_recordingEngine.finalizationDone();
+ auto recordingEnginePtr = &m_recordingEngine;
+ delete recordingEnginePtr;
}
void RecordingEngine::finalize()
@@ -140,7 +143,7 @@ void RecordingEngine::finalize()
for (auto &conn : m_connections)
disconnect(conn);
- auto *finalizer = new EncodingFinalizer(this);
+ auto *finalizer = new EncodingFinalizer(*this);
finalizer->start();
}
@@ -172,88 +175,6 @@ void RecordingEngine::addMediaFrameHandler(Args &&...args)
auto connection = connect(std::forward<Args>(args)..., Qt::DirectConnection);
m_connections.append(connection);
}
-
-struct QVideoFrameHolder
-{
- QVideoFrame f;
- QImage i;
-};
-
-static void freeQVideoFrame(void *opaque, uint8_t *)
-{
- delete reinterpret_cast<QVideoFrameHolder *>(opaque);
-}
-
-void VideoEncoder::processOne()
-{
- retrievePackets();
-
- auto frame = takeFrame();
- if (!frame.isValid())
- return;
-
- if (!isValid())
- return;
-
-// qCDebug(qLcFFmpegEncoder) << "new video buffer" << frame.startTime();
-
- AVFrameUPtr avFrame;
-
- auto *videoBuffer = dynamic_cast<QFFmpegVideoBuffer *>(frame.videoBuffer());
- if (videoBuffer) {
- // ffmpeg video buffer, let's use the native AVFrame stored in there
- auto *hwFrame = videoBuffer->getHWFrame();
- if (hwFrame && hwFrame->format == m_frameEncoder->sourceFormat())
- avFrame.reset(av_frame_clone(hwFrame));
- }
-
- if (!avFrame) {
- frame.map(QVideoFrame::ReadOnly);
- auto size = frame.size();
- avFrame = makeAVFrame();
- avFrame->format = m_frameEncoder->sourceFormat();
- avFrame->width = size.width();
- avFrame->height = size.height();
-
- for (int i = 0; i < 4; ++i) {
- avFrame->data[i] = const_cast<uint8_t *>(frame.bits(i));
- avFrame->linesize[i] = frame.bytesPerLine(i);
- }
-
- QImage img;
- if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg) {
- // the QImage is cached inside the video frame, so we can take the pointer to the image data here
- img = frame.toImage();
- avFrame->data[0] = (uint8_t *)img.bits();
- avFrame->linesize[0] = img.bytesPerLine();
- }
-
- Q_ASSERT(avFrame->data[0]);
- // ensure the video frame and it's data is alive as long as it's being used in the encoder
- avFrame->opaque_ref = av_buffer_create(nullptr, 0, freeQVideoFrame, new QVideoFrameHolder{frame, img}, 0);
- }
-
- if (m_baseTime.loadAcquire() == std::numeric_limits<qint64>::min()) {
- m_baseTime.storeRelease(frame.startTime() - m_lastFrameTime);
- qCDebug(qLcFFmpegEncoder) << ">>>> adjusting base time to" << m_baseTime.loadAcquire()
- << frame.startTime() << m_lastFrameTime;
- }
-
- qint64 time = frame.startTime() - m_baseTime.loadAcquire();
- m_lastFrameTime = frame.endTime() - m_baseTime.loadAcquire();
-
- setAVFrameTime(*avFrame, m_frameEncoder->getPts(time), m_frameEncoder->getTimeBase());
-
- m_encoder->newTimeStamp(time / 1000);
-
- qCDebug(qLcFFmpegEncoder) << ">>> sending frame" << avFrame->pts << time << m_lastFrameTime;
- int ret = m_frameEncoder->sendFrame(std::move(avFrame));
- if (ret < 0) {
- qCDebug(qLcFFmpegEncoder) << "error sending frame" << ret << err2str(ret);
- emit m_encoder->error(QMediaRecorder::ResourceError, err2str(ret));
- }
-}
-
}
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h
index 10174f9a4..b74fbba9f 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h
@@ -82,12 +82,12 @@ private:
class EncodingFinalizer : public QThread
{
public:
- EncodingFinalizer(RecordingEngine *e);
+ EncodingFinalizer(RecordingEngine &recordingEngine);
void run() override;
private:
- RecordingEngine *m_encoder = nullptr;
+ RecordingEngine &m_recordingEngine;
};
private:
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp
index 04ed5a728..a47968096 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp
@@ -13,10 +13,9 @@ namespace QFFmpeg {
static Q_LOGGING_CATEGORY(qLcFFmpegVideoEncoder, "qt.multimedia.ffmpeg.videoencoder");
-
-VideoEncoder::VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings,
+VideoEncoder::VideoEncoder(RecordingEngine &recordingEngine, const QMediaEncoderSettings &settings,
const QVideoFrameFormat &format, std::optional<AVPixelFormat> hwFormat)
- : EncoderThread(encoder)
+ : EncoderThread(recordingEngine)
{
setObjectName(QLatin1String("VideoEncoder"));
@@ -33,7 +32,7 @@ VideoEncoder::VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings
m_frameEncoder =
VideoFrameEncoder::create(settings, format.frameSize(), frameRate, ffmpegPixelFormat,
- swFormat, encoder->avFormatContext());
+ swFormat, recordingEngine.avFormatContext());
}
VideoEncoder::~VideoEncoder() = default;
@@ -45,7 +44,7 @@ bool VideoEncoder::isValid() const
void VideoEncoder::addFrame(const QVideoFrame &frame)
{
- QMutexLocker locker(&m_queueMutex);
+ QMutexLocker locker = lockLoopData();
// Drop frames if encoder can not keep up with the video source data rate
const bool queueFull = m_videoFrameQueue.size() >= m_maxQueueSize;
@@ -63,7 +62,7 @@ void VideoEncoder::addFrame(const QVideoFrame &frame)
QVideoFrame VideoEncoder::takeFrame()
{
- QMutexLocker locker(&m_queueMutex);
+ QMutexLocker locker = lockLoopData();
return dequeueIfPossible(m_videoFrameQueue);
}
@@ -72,7 +71,7 @@ void VideoEncoder::retrievePackets()
if (!m_frameEncoder)
return;
while (auto packet = m_frameEncoder->retrievePacket())
- m_encoder->getMuxer()->addPacket(std::move(packet));
+ m_recordingEngine.getMuxer()->addPacket(std::move(packet));
}
void VideoEncoder::init()
@@ -80,7 +79,7 @@ void VideoEncoder::init()
qCDebug(qLcFFmpegVideoEncoder) << "VideoEncoder::init started video device thread.";
bool ok = m_frameEncoder->open();
if (!ok)
- emit m_encoder->error(QMediaRecorder::ResourceError, "Could not initialize encoder");
+ emit m_recordingEngine.error(QMediaRecorder::ResourceError, "Could not initialize encoder");
}
void VideoEncoder::cleanup()
@@ -96,10 +95,93 @@ void VideoEncoder::cleanup()
bool VideoEncoder::hasData() const
{
- QMutexLocker locker(&m_queueMutex);
return !m_videoFrameQueue.empty();
}
+struct QVideoFrameHolder
+{
+ QVideoFrame f;
+ QImage i;
+};
+
+static void freeQVideoFrame(void *opaque, uint8_t *)
+{
+ delete reinterpret_cast<QVideoFrameHolder *>(opaque);
+}
+
+void VideoEncoder::processOne()
+{
+ retrievePackets();
+
+ auto frame = takeFrame();
+ if (!frame.isValid())
+ return;
+
+ if (!isValid())
+ return;
+
+ // qCDebug(qLcFFmpegEncoder) << "new video buffer" << frame.startTime();
+
+ AVFrameUPtr avFrame;
+
+ auto *videoBuffer = dynamic_cast<QFFmpegVideoBuffer *>(frame.videoBuffer());
+ if (videoBuffer) {
+ // ffmpeg video buffer, let's use the native AVFrame stored in there
+ auto *hwFrame = videoBuffer->getHWFrame();
+ if (hwFrame && hwFrame->format == m_frameEncoder->sourceFormat())
+ avFrame.reset(av_frame_clone(hwFrame));
+ }
+
+ if (!avFrame) {
+ frame.map(QVideoFrame::ReadOnly);
+ auto size = frame.size();
+ avFrame = makeAVFrame();
+ avFrame->format = m_frameEncoder->sourceFormat();
+ avFrame->width = size.width();
+ avFrame->height = size.height();
+
+ for (int i = 0; i < 4; ++i) {
+ avFrame->data[i] = const_cast<uint8_t *>(frame.bits(i));
+ avFrame->linesize[i] = frame.bytesPerLine(i);
+ }
+
+ QImage img;
+ if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg) {
+ // the QImage is cached inside the video frame, so we can take the pointer to the image
+ // data here
+ img = frame.toImage();
+ avFrame->data[0] = (uint8_t *)img.bits();
+ avFrame->linesize[0] = img.bytesPerLine();
+ }
+
+ Q_ASSERT(avFrame->data[0]);
+ // ensure the video frame and it's data is alive as long as it's being used in the encoder
+ avFrame->opaque_ref = av_buffer_create(nullptr, 0, freeQVideoFrame,
+ new QVideoFrameHolder{ frame, img }, 0);
+ }
+
+ if (m_baseTime.loadAcquire() == std::numeric_limits<qint64>::min()) {
+ m_baseTime.storeRelease(frame.startTime() - m_lastFrameTime);
+ qCDebug(qLcFFmpegVideoEncoder) << ">>>> adjusting base time to" << m_baseTime.loadAcquire()
+ << frame.startTime() << m_lastFrameTime;
+ }
+
+ qint64 time = frame.startTime() - m_baseTime.loadAcquire();
+ m_lastFrameTime = frame.endTime() - m_baseTime.loadAcquire();
+
+ setAVFrameTime(*avFrame, m_frameEncoder->getPts(time), m_frameEncoder->getTimeBase());
+
+ m_recordingEngine.newTimeStamp(time / 1000);
+
+ qCDebug(qLcFFmpegVideoEncoder)
+ << ">>> sending frame" << avFrame->pts << time << m_lastFrameTime;
+ int ret = m_frameEncoder->sendFrame(std::move(avFrame));
+ if (ret < 0) {
+ qCDebug(qLcFFmpegVideoEncoder) << "error sending frame" << ret << err2str(ret);
+ emit m_recordingEngine.error(QMediaRecorder::ResourceError, err2str(ret));
+ }
+}
+
} // namespace QFFmpeg
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h
index f07f146e2..8f9a943de 100644
--- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h
@@ -16,11 +16,10 @@ class QMediaEncoderSettings;
namespace QFFmpeg {
class VideoFrameEncoder;
-
class VideoEncoder : public EncoderThread
{
public:
- VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings,
+ VideoEncoder(RecordingEngine &recordingEngine, const QMediaEncoderSettings &settings,
const QVideoFrameFormat &format, std::optional<AVPixelFormat> hwFormat);
~VideoEncoder() override;
@@ -45,7 +44,6 @@ private:
void processOne() override;
private:
- mutable QMutex m_queueMutex;
std::queue<QVideoFrame> m_videoFrameQueue;
const size_t m_maxQueueSize = 10; // Arbitrarily chosen to limit memory usage (332 MB @ 4K)
diff --git a/src/plugins/multimedia/gstreamer/CMakeLists.txt b/src/plugins/multimedia/gstreamer/CMakeLists.txt
index 91fca82a3..80fc1fce0 100644
--- a/src/plugins/multimedia/gstreamer/CMakeLists.txt
+++ b/src/plugins/multimedia/gstreamer/CMakeLists.txt
@@ -52,13 +52,14 @@ qt_internal_add_module(QGstreamerMediaPluginPrivate
)
qt_internal_extend_target(QGstreamerMediaPluginPrivate CONDITION QT_FEATURE_gstreamer_photography
- LIBRARIES
+ PUBLIC_LIBRARIES
GStreamer::Photography
)
qt_internal_extend_target(QGstreamerMediaPluginPrivate CONDITION QT_FEATURE_gstreamer_gl
- LIBRARIES
+ PUBLIC_LIBRARIES
GStreamer::Gl
+ LIBRARIES
EGL::EGL
)
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp
index 240c69b5b..0cfa28169 100644
--- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp
@@ -73,8 +73,9 @@ QGstreamerAudioDecoder::QGstreamerAudioDecoder(QGstPipeline playbin, QGstElement
m_outputBin.addGhostPad(m_audioConvert, "sink");
g_object_set(m_playbin.object(), "audio-sink", m_outputBin.element(), NULL);
- g_signal_connect(m_playbin.object(), "deep-notify::source",
- (GCallback)&QGstreamerAudioDecoder::configureAppSrcElement, (gpointer)this);
+
+ m_deepNotifySourceConnection = m_playbin.connect(
+ "deep-notify::source", (GCallback)&configureAppSrcElement, (gpointer)this);
// Set volume to 100%
gdouble volume = 1.0;
@@ -115,63 +116,24 @@ void QGstreamerAudioDecoder::configureAppSrcElement([[maybe_unused]] GObject *ob
bool QGstreamerAudioDecoder::processBusMessage(const QGstreamerMessage &message)
{
- if (message.isNull())
- return false;
-
- constexpr bool extendedMessageTracing = false;
-
qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message;
GstMessage *gm = message.message();
- if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) {
+ switch (message.type()) {
+ case GST_MESSAGE_DURATION: {
updateDuration();
- } else if (GST_MESSAGE_SRC(gm) == m_playbin.object()) {
- switch (GST_MESSAGE_TYPE(gm)) {
- case GST_MESSAGE_STATE_CHANGED: {
- GstState oldState;
- GstState newState;
- GstState pending;
-
- gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
-
- if constexpr (extendedMessageTracing)
- qCDebug(qLcGstreamerAudioDecoder) << " state changed message from" << oldState
- << "to" << newState << pending;
-
- bool isDecoding = false;
- switch (newState) {
- case GST_STATE_VOID_PENDING:
- case GST_STATE_NULL:
- case GST_STATE_READY:
- break;
- case GST_STATE_PLAYING:
- isDecoding = true;
- break;
- case GST_STATE_PAUSED:
- isDecoding = true;
-
- // gstreamer doesn't give a reliable indication the duration
- // information is ready, GST_MESSAGE_DURATION is not sent by most elements
- // the duration is queried up to 5 times with increasing delay
- m_durationQueries = 5;
- updateDuration();
- break;
- }
+ return false;
+ }
- setIsDecoding(isDecoding);
- break;
- };
+ case GST_MESSAGE_ERROR: {
+ qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message);
- case GST_MESSAGE_EOS:
- m_playbin.setState(GST_STATE_NULL);
- finished();
- break;
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_error(gm, &err, &debug);
- case GST_MESSAGE_ERROR: {
- QUniqueGErrorHandle err;
- QGString debug;
- gst_message_parse_error(gm, &err, &debug);
+ if (message.source() == m_playbin) {
if (err.get()->domain == GST_STREAM_ERROR
&& err.get()->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
processInvalidMedia(QAudioDecoder::FormatError,
@@ -179,63 +141,103 @@ bool QGstreamerAudioDecoder::processBusMessage(const QGstreamerMessage &message)
else
processInvalidMedia(QAudioDecoder::ResourceError,
QString::fromUtf8(err.get()->message));
- qCWarning(qLcGstreamerAudioDecoder) << "Error:" << err;
- break;
+ } else {
+ QAudioDecoder::Error qerror = QAudioDecoder::ResourceError;
+ if (err.get()->domain == GST_STREAM_ERROR) {
+ switch (err.get()->code) {
+ case GST_STREAM_ERROR_DECRYPT:
+ case GST_STREAM_ERROR_DECRYPT_NOKEY:
+ qerror = QAudioDecoder::AccessDeniedError;
+ break;
+ case GST_STREAM_ERROR_FORMAT:
+ case GST_STREAM_ERROR_DEMUX:
+ case GST_STREAM_ERROR_DECODE:
+ case GST_STREAM_ERROR_WRONG_TYPE:
+ case GST_STREAM_ERROR_TYPE_NOT_FOUND:
+ case GST_STREAM_ERROR_CODEC_NOT_FOUND:
+ qerror = QAudioDecoder::FormatError;
+ break;
+ default:
+ break;
+ }
+ } else if (err.get()->domain == GST_CORE_ERROR) {
+ switch (err.get()->code) {
+ case GST_CORE_ERROR_MISSING_PLUGIN:
+ qerror = QAudioDecoder::FormatError;
+ break;
+ default:
+ break;
+ }
+ }
+
+ processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
}
- case GST_MESSAGE_WARNING: {
- QUniqueGErrorHandle err;
- QGString debug;
- gst_message_parse_warning(gm, &err, &debug);
- qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << err;
+ break;
+ }
+
+ default:
+ if (message.source() == m_playbin)
+ return handlePlaybinMessage(message);
+ }
+
+ return false;
+}
+
+bool QGstreamerAudioDecoder::handlePlaybinMessage(const QGstreamerMessage &message)
+{
+ GstMessage *gm = message.message();
+
+ switch (GST_MESSAGE_TYPE(gm)) {
+ case GST_MESSAGE_STATE_CHANGED: {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
+
+ gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
+
+ bool isDecoding = false;
+ switch (newState) {
+ case GST_STATE_VOID_PENDING:
+ case GST_STATE_NULL:
+ case GST_STATE_READY:
break;
- }
- case GST_MESSAGE_INFO: {
- if (qLcGstreamerAudioDecoder().isDebugEnabled()) {
- QUniqueGErrorHandle err;
- QGString debug;
- gst_message_parse_info(gm, &err, &debug);
- qDebug() << "Info:" << err;
- }
+ case GST_STATE_PLAYING:
+ isDecoding = true;
break;
- }
- default:
+ case GST_STATE_PAUSED:
+ isDecoding = true;
+
+ // gstreamer doesn't give a reliable indication the duration
+ // information is ready, GST_MESSAGE_DURATION is not sent by most elements
+ // the duration is queried up to 5 times with increasing delay
+ m_durationQueries = 5;
+ updateDuration();
break;
}
- } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) {
- QUniqueGErrorHandle err;
- QGString debug;
- gst_message_parse_error(gm, &err, &debug);
- qCDebug(qLcGstreamerAudioDecoder) << " error" << err << debug;
-
- QAudioDecoder::Error qerror = QAudioDecoder::ResourceError;
- if (err.get()->domain == GST_STREAM_ERROR) {
- switch (err.get()->code) {
- case GST_STREAM_ERROR_DECRYPT:
- case GST_STREAM_ERROR_DECRYPT_NOKEY:
- qerror = QAudioDecoder::AccessDeniedError;
- break;
- case GST_STREAM_ERROR_FORMAT:
- case GST_STREAM_ERROR_DEMUX:
- case GST_STREAM_ERROR_DECODE:
- case GST_STREAM_ERROR_WRONG_TYPE:
- case GST_STREAM_ERROR_TYPE_NOT_FOUND:
- case GST_STREAM_ERROR_CODEC_NOT_FOUND:
- qerror = QAudioDecoder::FormatError;
- break;
- default:
- break;
- }
- } else if (err.get()->domain == GST_CORE_ERROR) {
- switch (err.get()->code) {
- case GST_CORE_ERROR_MISSING_PLUGIN:
- qerror = QAudioDecoder::FormatError;
- break;
- default:
- break;
- }
- }
- processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
+ setIsDecoding(isDecoding);
+ break;
+ };
+
+ case GST_MESSAGE_EOS:
+ m_playbin.setState(GST_STATE_NULL);
+ finished();
+ break;
+
+ case GST_MESSAGE_ERROR:
+ Q_UNREACHABLE_RETURN(false); // handled in processBusMessage
+
+ case GST_MESSAGE_WARNING:
+ qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message);
+ break;
+
+ case GST_MESSAGE_INFO: {
+ if (qLcGstreamerAudioDecoder().isDebugEnabled())
+ qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message);
+ break;
+ }
+ default:
+ break;
}
return false;
@@ -325,6 +327,7 @@ void QGstreamerAudioDecoder::start()
void QGstreamerAudioDecoder::stop()
{
m_playbin.setState(GST_STATE_NULL);
+ m_currentSessionId += 1;
removeAppSink();
// GStreamer thread is stopped. Can safely access m_buffersAvailable
@@ -363,55 +366,38 @@ QAudioBuffer QGstreamerAudioDecoder::read()
{
QAudioBuffer audioBuffer;
- int buffersAvailable;
- {
- QMutexLocker locker(&m_buffersMutex);
- buffersAvailable = m_buffersAvailable;
+ if (m_buffersAvailable == 0)
+ return audioBuffer;
- // need to decrement before pulling a buffer
- // to make sure assert in QGstreamerAudioDecoderControl::new_buffer works
- m_buffersAvailable--;
- }
+ m_buffersAvailable -= 1;
+ if (m_buffersAvailable == 0)
+ bufferAvailableChanged(false);
- if (buffersAvailable) {
- if (buffersAvailable == 1)
- bufferAvailableChanged(false);
-
- const char* bufferData = nullptr;
- int bufferSize = 0;
-
- QGstSampleHandle sample = m_appSink.pullSample();
- GstBuffer *buffer = gst_sample_get_buffer(sample.get());
- GstMapInfo mapInfo;
- gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
- bufferData = (const char*)mapInfo.data;
- bufferSize = mapInfo.size;
- QAudioFormat format = QGstUtils::audioFormatForSample(sample.get());
-
- if (format.isValid()) {
- // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
- // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
- qint64 position = getPositionFromBuffer(buffer);
- audioBuffer = QAudioBuffer(QByteArray((const char*)bufferData, bufferSize), format, position);
- position /= 1000; // convert to milliseconds
- if (position != m_position) {
- m_position = position;
- positionChanged(m_position);
- }
+ QGstSampleHandle sample = m_appSink.pullSample();
+ GstBuffer *buffer = gst_sample_get_buffer(sample.get());
+ GstMapInfo mapInfo;
+ gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
+ const char *bufferData = (const char *)mapInfo.data;
+ int bufferSize = mapInfo.size;
+ QAudioFormat format = QGstUtils::audioFormatForSample(sample.get());
+
+ if (format.isValid()) {
+ // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
+ // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
+ qint64 position = getPositionFromBuffer(buffer);
+ audioBuffer = QAudioBuffer(QByteArray(bufferData, bufferSize), format, position);
+ position /= 1000; // convert to milliseconds
+ if (position != m_position) {
+ m_position = position;
+ positionChanged(m_position);
}
- gst_buffer_unmap(buffer, &mapInfo);
}
+ gst_buffer_unmap(buffer, &mapInfo);
return audioBuffer;
}
-bool QGstreamerAudioDecoder::bufferAvailable() const
-{
- QMutexLocker locker(&m_buffersMutex);
- return m_buffersAvailable > 0;
-}
-
qint64 QGstreamerAudioDecoder::position() const
{
return m_position;
@@ -428,30 +414,30 @@ void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode,
error(int(errorCode), errorString);
}
-GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *, gpointer user_data)
+GstFlowReturn QGstreamerAudioDecoder::newSample(GstAppSink *)
{
- qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
-
// "Note that the preroll buffer will also be returned as the first buffer when calling
// gst_app_sink_pull_buffer()."
- QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder*>(user_data);
-
- int buffersAvailable;
- {
- QMutexLocker locker(&decoder->m_buffersMutex);
- buffersAvailable = decoder->m_buffersAvailable;
- decoder->m_buffersAvailable++;
- Q_ASSERT(decoder->m_buffersAvailable <= MAX_BUFFERS_IN_QUEUE);
- }
- qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample" << buffersAvailable;
+ QMetaObject::invokeMethod(this, [this, sessionId = m_currentSessionId] {
+ if (sessionId != m_currentSessionId)
+ return; // stop()ed before message is executed
+
+ m_buffersAvailable += 1;
+ bufferAvailableChanged(true);
+ bufferReady();
+ });
- if (!buffersAvailable)
- decoder->bufferAvailableChanged(true);
- decoder->bufferReady();
return GST_FLOW_OK;
}
+GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *sink, gpointer user_data)
+{
+ QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder *>(user_data);
+ qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
+ return decoder->newSample(sink);
+}
+
void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio)
{
int flags = m_playbin.getInt("flags");
@@ -512,7 +498,7 @@ void QGstreamerAudioDecoder::updateDuration()
if (m_durationQueries > 0) {
//increase delay between duration requests
int delay = 25 << (5 - m_durationQueries);
- QTimer::singleShot(delay, this, SLOT(updateDuration()));
+ QTimer::singleShot(delay, this, &QGstreamerAudioDecoder::updateDuration);
m_durationQueries--;
}
}
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h
index c45e9f309..eba1025fa 100644
--- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h
@@ -57,7 +57,6 @@ public:
void setAudioFormat(const QAudioFormat &format) override;
QAudioBuffer read() override;
- bool bufferAvailable() const override;
qint64 position() const override;
qint64 duration() const override;
@@ -73,6 +72,8 @@ private:
#if QT_CONFIG(gstreamer_app)
static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data);
+ GstFlowReturn newSample(GstAppSink *sink);
+
static void configureAppSrcElement(GObject *, GObject *, GParamSpec *,
QGstreamerAudioDecoder *_this);
#endif
@@ -81,7 +82,9 @@ private:
void addAppSink();
void removeAppSink();
- void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString);
+ bool handlePlaybinMessage(const QGstreamerMessage &);
+
+ void processInvalidMedia(QAudioDecoder::Error errorCode, const QString &errorString);
static qint64 getPositionFromBuffer(GstBuffer* buffer);
QGstPipeline m_playbin;
@@ -94,13 +97,15 @@ private:
QIODevice *mDevice = nullptr;
QAudioFormat mFormat;
- mutable QMutex m_buffersMutex;
int m_buffersAvailable = 0;
-
qint64 m_position = -1;
qint64 m_duration = -1;
int m_durationQueries = 0;
+
+ qint32 m_currentSessionId{};
+
+ QGObjectHandlerScopedConnection m_deepNotifySourceConnection;
};
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp
index 5399a3e64..6fd972524 100644
--- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp
@@ -181,12 +181,7 @@ bool QGStreamerAudioSink::processBusMessage(const QGstreamerMessage &message)
break;
case GST_MESSAGE_ERROR: {
setError(QAudio::IOError);
- QUniqueGErrorHandle error;
- QGString debug;
-
- gst_message_parse_error(msg, &error, &debug);
- qDebug() << "Error:" << error;
-
+ qDebug() << "Error:" << QCompactGstMessageAdaptor(message);
break;
}
default:
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp
index ba9823d98..829d116a2 100644
--- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp
@@ -4,14 +4,16 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmath.h>
-#include <private/qaudiohelpers_p.h>
+#include <QtMultimedia/private/qaudiohelpers_p.h>
#include "qgstreameraudiosource_p.h"
#include "qgstreameraudiodevice_p.h"
+#include "common/qgst_p.h"
+#include "common/qgst_debug_p.h"
+
#include <sys/types.h>
#include <unistd.h>
-#include <gst/gst.h>
Q_DECLARE_OPAQUE_POINTER(GstSample *);
Q_DECLARE_METATYPE(GstSample *);
@@ -204,12 +206,7 @@ gboolean QGStreamerAudioSource::busMessage(GstBus *, GstMessage *msg, gpointer u
break;
case GST_MESSAGE_ERROR: {
input->setError(QAudio::IOError);
- QUniqueGErrorHandle error;
- QGString debug;
-
- gst_message_parse_error (msg, &error, &debug);
- qDebug() << "Error:" << error.get();
-
+ qDebug() << "Error:" << QCompactGstMessageAdaptor(msg);
break;
}
default:
diff --git a/src/plugins/multimedia/gstreamer/common/qgst.cpp b/src/plugins/multimedia/gstreamer/common/qgst.cpp
index 83d95a9e2..8a77533a6 100644
--- a/src/plugins/multimedia/gstreamer/common/qgst.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgst.cpp
@@ -482,6 +482,14 @@ QGstCaps QGstCaps::fromCameraFormat(const QCameraFormat &format)
return caps;
}
+QGstCaps QGstCaps::copy() const
+{
+ return QGstCaps{
+ gst_caps_copy(caps()),
+ QGstCaps::HasRef,
+ };
+}
+
QGstCaps::MemoryFormat QGstCaps::memoryFormat() const
{
auto *features = gst_caps_get_features(get(), 0);
@@ -1049,6 +1057,14 @@ void QGstBin::dumpGraph(const char *fileNamePrefix)
fileNamePrefix);
}
+QGstElement QGstBin::findByName(const char *name)
+{
+ return QGstElement{
+ gst_bin_get_by_name(bin(), name),
+ QGstElement::NeedsRef,
+ };
+}
+
// QGstBaseSink
QGstBaseSink::QGstBaseSink(GstBaseSink *element, RefMode mode)
diff --git a/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp b/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp
index ee28a5c45..ea749c817 100644
--- a/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp
@@ -172,6 +172,42 @@ QDebug operator<<(QDebug dbg, const GstMessage *msg)
break;
}
+ case GST_MESSAGE_WARNING: {
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_warning(const_cast<GstMessage *>(msg), &err, &debug);
+
+ dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg)
+ << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg) << ", Warning: " << err << " ("
+ << debug << ")";
+ break;
+ }
+
+ case GST_MESSAGE_INFO: {
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_info(const_cast<GstMessage *>(msg), &err, &debug);
+
+ dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg)
+ << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg) << ", Info: " << err << " (" << debug
+ << ")";
+ break;
+ }
+
+ case GST_MESSAGE_STATE_CHANGED: {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
+
+ gst_message_parse_state_changed(const_cast<GstMessage *>(msg), &oldState, &newState,
+ &pending);
+
+ dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg)
+ << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg) << ", OldState: " << oldState
+ << ", NewState: " << newState << "Pending State: " << pending;
+ break;
+ }
+
default: {
dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg)
<< ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg);
@@ -316,4 +352,67 @@ QDebug operator<<(QDebug dbg, const GError *error)
return dbg << error->message;
}
+QCompactGstMessageAdaptor::QCompactGstMessageAdaptor(const QGstreamerMessage &m)
+ : QCompactGstMessageAdaptor{
+ m.message(),
+ }
+{
+}
+
+QCompactGstMessageAdaptor::QCompactGstMessageAdaptor(GstMessage *m)
+ : msg{
+ m,
+ }
+{
+}
+
+QDebug operator<<(QDebug dbg, const QCompactGstMessageAdaptor &m)
+{
+ std::optional<QDebugStateSaver> saver(dbg);
+ dbg.nospace();
+
+ switch (GST_MESSAGE_TYPE(m.msg)) {
+ case GST_MESSAGE_ERROR: {
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_error(m.msg, &err, &debug);
+ dbg << err << " (" << debug << ")";
+ return dbg;
+ }
+
+ case GST_MESSAGE_WARNING: {
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_warning(m.msg, &err, &debug);
+ dbg << err << " (" << debug << ")";
+ return dbg;
+ }
+
+ case GST_MESSAGE_INFO: {
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_info(m.msg, &err, &debug);
+
+ dbg << err << " (" << debug << ")";
+ return dbg;
+ }
+
+ case GST_MESSAGE_STATE_CHANGED: {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
+
+ gst_message_parse_state_changed(m.msg, &oldState, &newState, &pending);
+
+ dbg << oldState << "->" << newState << "(pending: " << pending << ")";
+ return dbg;
+ }
+
+ default: {
+ saver.reset();
+ return dbg << m.msg;
+ }
+ }
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h b/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h
index d64c240c6..31c722a90 100644
--- a/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h
@@ -54,6 +54,15 @@ QDebug operator<<(QDebug, GstPadDirection);
QDebug operator<<(QDebug, const GValue *);
QDebug operator<<(QDebug, const GError *);
+struct QCompactGstMessageAdaptor
+{
+ explicit QCompactGstMessageAdaptor(const QGstreamerMessage &m);
+ explicit QCompactGstMessageAdaptor(GstMessage *m);
+ GstMessage *msg;
+};
+
+QDebug operator<<(QDebug, const QCompactGstMessageAdaptor &);
+
QT_END_NAMESPACE
#endif
diff --git a/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h b/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h
index b72e92db1..c37ac5971 100644
--- a/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h
@@ -46,7 +46,7 @@ struct QSharedHandle : private QUniqueHandle<HandleTraits>
}
QSharedHandle(const QSharedHandle &o)
- : QSharedHandle{
+ : BaseClass{
HandleTraits::ref(o.get()),
}
{
@@ -238,6 +238,7 @@ using QUniqueGstStructureHandle = QUniqueHandle<QGstImpl::QUniqueGstStructureHan
using QUniqueGStringHandle = QUniqueHandle<QGstImpl::QUniqueGStringHandleTraits>;
using QUniqueGErrorHandle = QUniqueHandle<QGstImpl::QUniqueGErrorHandleTraits>;
using QFileDescriptorHandle = QUniqueHandle<QGstImpl::QFileDescriptorHandleTraits>;
+using QGstBufferHandle = QGstImpl::QGstMiniObjectHandleHelper<GstBuffer>::SharedHandle;
using QGstContextHandle = QGstImpl::QGstMiniObjectHandleHelper<GstContext>::UniqueHandle;
using QGstGstDateTimeHandle = QGstImpl::QGstMiniObjectHandleHelper<GstDateTime>::SharedHandle;
using QGstPluginFeatureHandle = QGstImpl::QGstHandleHelper<GstPluginFeature>::SharedHandle;
diff --git a/src/plugins/multimedia/gstreamer/common/qgst_p.h b/src/plugins/multimedia/gstreamer/common/qgst_p.h
index ab264f552..ec41b508d 100644
--- a/src/plugins/multimedia/gstreamer/common/qgst_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgst_p.h
@@ -15,21 +15,20 @@
// We mean it.
//
-#include <common/qgst_handle_types_p.h>
-
-#include <private/qtmultimediaglobal_p.h>
-#include <private/qmultimediautils_p.h>
-
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
#include <QtCore/qsemaphore.h>
#include <QtMultimedia/qaudioformat.h>
#include <QtMultimedia/qvideoframe.h>
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include <QtMultimedia/private/qmultimediautils_p.h>
#include <gst/gst.h>
#include <gst/video/video-info.h>
+#include "qgst_handle_types_p.h"
+
#include <type_traits>
#if QT_CONFIG(gstreamer_photography)
@@ -80,6 +79,29 @@ struct GstObjectTraits
}; \
static_assert(true, "ensure semicolon")
+#define QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE(ClassName, MACRO_LABEL) \
+ template <> \
+ struct GstObjectTraits<ClassName> \
+ { \
+ using Type = ClassName; \
+ template <typename U> \
+ static bool isObjectOfType(U *arg) \
+ { \
+ return GST_IS_##MACRO_LABEL(arg); \
+ } \
+ template <typename U> \
+ static Type *cast(U *arg) \
+ { \
+ return checked_cast(arg); \
+ } \
+ template <typename U> \
+ static Type *checked_cast(U *arg) \
+ { \
+ return GST_##MACRO_LABEL(arg); \
+ } \
+ }; \
+ static_assert(true, "ensure semicolon")
+
QGST_DEFINE_CAST_TRAITS(GstBin, BIN);
QGST_DEFINE_CAST_TRAITS(GstClock, CLOCK);
QGST_DEFINE_CAST_TRAITS(GstElement, ELEMENT);
@@ -89,6 +111,8 @@ QGST_DEFINE_CAST_TRAITS(GstPipeline, PIPELINE);
QGST_DEFINE_CAST_TRAITS(GstBaseSink, BASE_SINK);
QGST_DEFINE_CAST_TRAITS(GstBaseSrc, BASE_SRC);
+QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE(GstTagSetter, TAG_SETTER);
+
#if QT_CONFIG(gstreamer_app)
QGST_DEFINE_CAST_TRAITS(GstAppSink, APP_SINK);
QGST_DEFINE_CAST_TRAITS(GstAppSrc, APP_SRC);
@@ -116,6 +140,7 @@ struct GstObjectTraits<GObject>
};
#undef QGST_DEFINE_CAST_TRAITS
+#undef QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE
} // namespace QGstImpl
@@ -333,6 +358,8 @@ public:
static QGstCaps create();
static QGstCaps fromCameraFormat(const QCameraFormat &format);
+
+ QGstCaps copy() const;
};
template <>
@@ -678,6 +705,8 @@ public:
bool syncChildrenState();
void dumpGraph(const char *fileNamePrefix);
+
+ QGstElement findByName(const char *);
};
class QGstBaseSink : public QGstElement
diff --git a/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp b/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp
index 392898245..7d507f076 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp
@@ -16,9 +16,7 @@ QT_BEGIN_NAMESPACE
class QGstPipelinePrivate : public QObject
{
- Q_OBJECT
public:
-
int m_ref = 0;
guint m_tag = 0;
GstBus *m_bus = nullptr;
@@ -46,8 +44,12 @@ public:
void installMessageFilter(QGstreamerBusMessageFilter *filter);
void removeMessageFilter(QGstreamerBusMessageFilter *filter);
+private:
static GstBusSyncReply syncGstBusFilter(GstBus* bus, GstMessage* message, QGstPipelinePrivate *d)
{
+ if (!message)
+ return GST_BUS_PASS;
+
Q_UNUSED(bus);
QMutexLocker lock(&d->filterMutex);
@@ -62,31 +64,20 @@ public:
return GST_BUS_PASS;
}
-private Q_SLOTS:
- void interval()
- {
- GstMessage* message;
- while ((message = gst_bus_poll(m_bus, GST_MESSAGE_ANY, 0)) != nullptr) {
- processMessage(message);
- gst_message_unref(message);
- }
- }
- void doProcessMessage(const QGstreamerMessage& msg)
- {
- for (QGstreamerBusMessageFilter *filter : std::as_const(busFilters)) {
- if (filter->processBusMessage(msg))
- break;
- }
- }
-
-private:
void processMessage(GstMessage *message)
{
+ if (!message)
+ return;
+
QGstreamerMessage msg{
message,
QGstreamerMessage::NeedsRef,
};
- doProcessMessage(msg);
+
+ for (QGstreamerBusMessageFilter *filter : std::as_const(busFilters)) {
+ if (filter->processBusMessage(msg))
+ break;
+ }
}
static gboolean busCallback(GstBus *, GstMessage *message, gpointer data)
@@ -106,7 +97,13 @@ QGstPipelinePrivate::QGstPipelinePrivate(GstBus* bus, QObject* parent)
if (!hasGlib) {
m_intervalTimer = new QTimer(this);
m_intervalTimer->setInterval(250);
- connect(m_intervalTimer, SIGNAL(timeout()), SLOT(interval()));
+ QObject::connect(m_intervalTimer, &QTimer::timeout, this, [this] {
+ GstMessage *message;
+ while ((message = gst_bus_poll(m_bus, GST_MESSAGE_ANY, 0)) != nullptr) {
+ processMessage(message);
+ gst_message_unref(message);
+ }
+ });
m_intervalTimer->start();
} else {
m_tag = gst_bus_add_watch_full(bus, G_PRIORITY_DEFAULT, busCallback, this, nullptr);
@@ -324,12 +321,17 @@ bool QGstPipeline::seek(qint64 pos, double rate)
return true;
}
-bool QGstPipeline::setPlaybackRate(double rate)
+bool QGstPipeline::setPlaybackRate(double rate, bool applyToPipeline)
{
QGstPipelinePrivate *d = getPrivate();
if (rate == d->m_rate)
return false;
+ if (!applyToPipeline) {
+ d->m_rate = rate;
+ return true;
+ }
+
constexpr GstSeekFlags seekFlags =
#if GST_CHECK_VERSION(1, 18, 0)
GST_SEEK_FLAG_INSTANT_RATE_CHANGE;
@@ -383,6 +385,3 @@ QGstPipelinePrivate *QGstPipeline::getPrivate() const
}
QT_END_NAMESPACE
-
-#include "qgstpipeline.moc"
-
diff --git a/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h b/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h
index 23610dd00..6914993de 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h
@@ -15,10 +15,10 @@
// We mean it.
//
-#include <private/qtmultimediaglobal_p.h>
-#include <QObject>
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include <QtCore/qobject.h>
-#include <common/qgst_p.h>
+#include "qgst_p.h"
QT_BEGIN_NAMESPACE
@@ -95,7 +95,7 @@ public:
void flush();
bool seek(qint64 pos, double rate);
- bool setPlaybackRate(double rate);
+ bool setPlaybackRate(double rate, bool applyToPipeline = true);
double playbackRate() const;
bool setPosition(qint64 pos);
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamerbufferprobe.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamerbufferprobe.cpp
index 341cb69b3..9cba810db 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamerbufferprobe.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamerbufferprobe.cpp
@@ -3,6 +3,8 @@
#include <common/qgstreamerbufferprobe_p.h>
+#include <common/qgst_p.h>
+
QT_BEGIN_NAMESPACE
QGstreamerBufferProbe::QGstreamerBufferProbe(Flags flags)
@@ -14,10 +16,14 @@ QGstreamerBufferProbe::~QGstreamerBufferProbe() = default;
void QGstreamerBufferProbe::addProbeToPad(GstPad *pad, bool downstream)
{
- if (GstCaps *caps = gst_pad_get_current_caps(pad)) {
- probeCaps(caps);
- gst_caps_unref(caps);
- }
+ QGstCaps caps{
+ gst_pad_get_current_caps(pad),
+ QGstCaps::HasRef,
+ };
+
+ if (caps)
+ probeCaps(caps.caps());
+
if (m_flags & ProbeCaps) {
m_capsProbeId = gst_pad_add_probe(
pad,
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp
index 8388ce8d2..5d5cf546c 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp
@@ -77,10 +77,13 @@ QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(Track
void QGstreamerMediaPlayer::disconnectDecoderHandlers()
{
auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{
- &padAdded, &padRemoved, &sourceSetup, &elementAdded, &unknownType,
+ &padAdded, &padRemoved, &sourceSetup, &uridecodebinElementAdded,
+ &unknownType, &elementAdded, &elementRemoved,
};
for (QGObjectHandlerScopedConnection *handler : handlers)
handler->disconnect();
+
+ decodeBinQueues = 0;
}
QMaybe<QPlatformMediaPlayer *> QGstreamerMediaPlayer::create(QMediaPlayer *parent)
@@ -139,7 +142,9 @@ QGstreamerMediaPlayer::QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput,
gst_pipeline_use_clock(playerPipeline.pipeline(), systemClock.get());
- connect(&positionUpdateTimer, &QTimer::timeout, this, &QGstreamerMediaPlayer::updatePosition);
+ connect(&positionUpdateTimer, &QTimer::timeout, this, [this] {
+ updatePosition();
+ });
}
QGstreamerMediaPlayer::~QGstreamerMediaPlayer()
@@ -180,7 +185,8 @@ qreal QGstreamerMediaPlayer::playbackRate() const
void QGstreamerMediaPlayer::setPlaybackRate(qreal rate)
{
- if (playerPipeline.setPlaybackRate(rate))
+ bool applyRateToPipeline = state() != QMediaPlayer::StoppedState;
+ if (playerPipeline.setPlaybackRate(rate, applyRateToPipeline))
playbackRateChanged(rate);
}
@@ -219,10 +225,9 @@ void QGstreamerMediaPlayer::play()
}
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
- if (mediaStatus() == QMediaPlayer::LoadedMedia)
- mediaStatusChanged(QMediaPlayer::BufferedMedia);
- emit stateChanged(QMediaPlayer::PlayingState);
+
positionUpdateTimer.start(100);
+ emit stateChanged(QMediaPlayer::PlayingState);
}
void QGstreamerMediaPlayer::pause()
@@ -241,10 +246,16 @@ void QGstreamerMediaPlayer::pause()
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
if (mediaStatus() == QMediaPlayer::EndOfMedia) {
playerPipeline.setPosition(0);
- mediaStatusChanged(QMediaPlayer::BufferedMedia);
}
updatePosition();
emit stateChanged(QMediaPlayer::PausedState);
+
+ if (m_bufferProgress > 0 || !canTrackProgress())
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
+ else
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+
+ emit bufferProgressChanged(m_bufferProgress / 100.);
}
void QGstreamerMediaPlayer::stop()
@@ -253,6 +264,7 @@ void QGstreamerMediaPlayer::stop()
if (position() != 0) {
playerPipeline.setPosition(0);
positionChanged(0);
+ mediaStatusChanged(QMediaPlayer::LoadedMedia);
}
return;
}
@@ -275,14 +287,15 @@ void QGstreamerMediaPlayer::stopOrEOS(bool eos)
playerPipeline.setPosition(0);
updatePosition();
emit stateChanged(QMediaPlayer::StoppedState);
- mediaStatusChanged(eos ? QMediaPlayer::EndOfMedia : QMediaPlayer::LoadedMedia);
+ if (eos)
+ mediaStatusChanged(QMediaPlayer::EndOfMedia);
+ else
+ mediaStatusChanged(QMediaPlayer::LoadedMedia);
+ m_initialBufferProgressSent = false;
}
bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
{
- if (message.isNull())
- return false;
-
qCDebug(qLcMediaPlayer) << "received bus message:" << message;
GstMessage* gm = message.message();
@@ -293,9 +306,17 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
gst_message_parse_tag(gm, &tagList);
qCDebug(qLcMediaPlayer) << " Got tags: " << tagList.get();
- auto metaData = QGstreamerMetaData::fromGstTagList(tagList.get());
+ auto metaData = taglistToMetaData(tagList);
+ auto keys = metaData.keys();
for (auto k : metaData.keys())
m_metaData.insert(k, metaData.value(k));
+ if (!keys.isEmpty())
+ emit metaDataChanged();
+
+ if (gstVideoOutput) {
+ QVariant rotation = m_metaData.value(QMediaMetaData::Orientation);
+ gstVideoOutput->setRotation(rotation.value<QtVideo::Rotation>());
+ }
break;
}
case GST_MESSAGE_DURATION_CHANGED: {
@@ -315,12 +336,29 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
stopOrEOS(true);
break;
case GST_MESSAGE_BUFFERING: {
- qCDebug(qLcMediaPlayer) << " buffering message";
int progress = 0;
gst_message_parse_buffering(gm, &progress);
+
+ qCDebug(qLcMediaPlayer) << " buffering message: " << progress;
+
+ if (state() != QMediaPlayer::StoppedState && !prerolling) {
+ if (!m_initialBufferProgressSent) {
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+ m_initialBufferProgressSent = true;
+ }
+
+ if (m_bufferProgress > 0 && progress == 0)
+ mediaStatusChanged(QMediaPlayer::StalledMedia);
+ else if (progress >= 50)
+ // QTBUG-124517: rethink buffering
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
+ else
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+ }
+
m_bufferProgress = progress;
- mediaStatusChanged(m_bufferProgress == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia);
- emit bufferProgressChanged(m_bufferProgress/100.);
+
+ emit bufferProgressChanged(m_bufferProgress / 100.);
break;
}
case GST_MESSAGE_STATE_CHANGED: {
@@ -332,22 +370,22 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
GstState pending;
gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
- qCDebug(qLcMediaPlayer) << " state changed message from" << oldState << "to" << newState
- << pending;
+ qCDebug(qLcMediaPlayer) << " state changed message from"
+ << QCompactGstMessageAdaptor(message);
switch (newState) {
case GST_STATE_VOID_PENDING:
case GST_STATE_NULL:
case GST_STATE_READY:
break;
- case GST_STATE_PAUSED:
- {
+ case GST_STATE_PAUSED: {
if (prerolling) {
qCDebug(qLcMediaPlayer) << "Preroll done, setting status to Loaded";
prerolling = false;
- GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "playerPipeline");
+ GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL,
+ "playerPipeline");
- qint64 d = playerPipeline.duration()/1e6;
+ qint64 d = playerPipeline.duration() / 1e6;
if (d != m_duration) {
m_duration = d;
qCDebug(qLcMediaPlayer) << " duration changed" << d;
@@ -369,41 +407,58 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
}
gst_query_unref(query);
seekableChanged(canSeek);
+
+ if (!playerPipeline.inStoppedState()) {
+ Q_ASSERT(!m_initialBufferProgressSent);
+
+ bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+ m_initialBufferProgressSent = true;
+ if (immediatelySendBuffered)
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
+ }
}
break;
}
- case GST_STATE_PLAYING:
- mediaStatusChanged(QMediaPlayer::BufferedMedia);
+ case GST_STATE_PLAYING: {
+ if (!m_initialBufferProgressSent) {
+ bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+ m_initialBufferProgressSent = true;
+ if (immediatelySendBuffered)
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
+ }
break;
}
+ }
break;
}
case GST_MESSAGE_ERROR: {
+ qCDebug(qLcMediaPlayer) << " error" << QCompactGstMessageAdaptor(message);
+
QUniqueGErrorHandle err;
QUniqueGStringHandle debug;
gst_message_parse_error(gm, &err, &debug);
- qCDebug(qLcMediaPlayer) << " error" << err << debug;
-
GQuark errorDomain = err.get()->domain;
gint errorCode = err.get()->code;
if (errorDomain == GST_STREAM_ERROR) {
if (errorCode == GST_STREAM_ERROR_CODEC_NOT_FOUND)
- emit error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
+ error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
else {
- emit error(QMediaPlayer::FormatError, QString::fromUtf8(err.get()->message));
+ error(QMediaPlayer::FormatError, QString::fromUtf8(err.get()->message));
}
} else if (errorDomain == GST_RESOURCE_ERROR) {
if (errorCode == GST_RESOURCE_ERROR_NOT_FOUND) {
if (m_resourceErrorState != ResourceErrorState::ErrorReported) {
// gstreamer seems to deliver multiple GST_RESOURCE_ERROR_NOT_FOUND events
- emit error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message));
+ error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message));
m_resourceErrorState = ResourceErrorState::ErrorReported;
m_url.clear();
}
} else {
- emit error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message));
+ error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message));
}
} else {
playerPipeline.dumpGraph("error");
@@ -411,23 +466,17 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
mediaStatusChanged(QMediaPlayer::InvalidMedia);
break;
}
- case GST_MESSAGE_WARNING: {
- QUniqueGErrorHandle err;
- QUniqueGStringHandle debug;
- gst_message_parse_warning (gm, &err, &debug);
- qCWarning(qLcMediaPlayer) << "Warning:" << err;
+
+ case GST_MESSAGE_WARNING:
+ qCWarning(qLcMediaPlayer) << "Warning:" << QCompactGstMessageAdaptor(message);
playerPipeline.dumpGraph("warning");
break;
- }
- case GST_MESSAGE_INFO: {
- if (qLcMediaPlayer().isDebugEnabled()) {
- QUniqueGErrorHandle err;
- QUniqueGStringHandle debug;
- gst_message_parse_info (gm, &err, &debug);
- qCDebug(qLcMediaPlayer) << "Info:" << err;
- }
+
+ case GST_MESSAGE_INFO:
+ if (qLcMediaPlayer().isDebugEnabled())
+ qCDebug(qLcMediaPlayer) << "Info:" << QCompactGstMessageAdaptor(message);
break;
- }
+
case GST_MESSAGE_SEGMENT_START: {
qCDebug(qLcMediaPlayer) << " segment start message, updating position";
QGstStructure structure(gst_message_get_structure(gm));
@@ -705,6 +754,43 @@ void QGstreamerMediaPlayer::unknownTypeCallback(GstElement *decodebin, GstPad *p
});
}
+static bool isQueue(const QGstElement &element)
+{
+ static const GType queueType = [] {
+ QGstElementFactoryHandle factory = QGstElementFactoryHandle{
+ gst_element_factory_find("queue"),
+ };
+ return gst_element_factory_get_element_type(factory.get());
+ }();
+
+ static const GType multiQueueType = [] {
+ QGstElementFactoryHandle factory = QGstElementFactoryHandle{
+ gst_element_factory_find("multiqueue"),
+ };
+ return gst_element_factory_get_element_type(factory.get());
+ }();
+
+ return element.type() == queueType || element.type() == multiQueueType;
+}
+
+void QGstreamerMediaPlayer::decodebinElementAddedCallback(GstBin * /*decodebin*/,
+ GstBin * /*sub_bin*/, GstElement *child,
+ QGstreamerMediaPlayer *self)
+{
+ QGstElement c(child, QGstElement::NeedsRef);
+ if (isQueue(c))
+ self->decodeBinQueues += 1;
+}
+
+void QGstreamerMediaPlayer::decodebinElementRemovedCallback(GstBin * /*decodebin*/,
+ GstBin * /*sub_bin*/, GstElement *child,
+ QGstreamerMediaPlayer *self)
+{
+ QGstElement c(child, QGstElement::NeedsRef);
+ if (isQueue(c))
+ self->decodeBinQueues -= 1;
+}
+
void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
{
qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content;
@@ -737,12 +823,14 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
stateChanged(QMediaPlayer::StoppedState);
if (position() != 0)
positionChanged(0);
- mediaStatusChanged(QMediaPlayer::NoMedia);
if (!m_metaData.isEmpty()) {
m_metaData.clear();
metaDataChanged();
}
+ if (content.isEmpty() && !stream)
+ mediaStatusChanged(QMediaPlayer::NoMedia);
+
if (content.isEmpty())
return;
@@ -752,20 +840,23 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
if (maybeAppSrc) {
m_appSrc = maybeAppSrc.value();
} else {
- emit error(QMediaPlayer::ResourceError, maybeAppSrc.error());
+ error(QMediaPlayer::ResourceError, maybeAppSrc.error());
return;
}
}
src = m_appSrc->element();
decoder = QGstElement::createFromFactory("decodebin", "decoder");
if (!decoder) {
- emit error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("decodebin"));
+ error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("decodebin"));
return;
}
decoder.set("post-stream-topology", true);
decoder.set("use-buffering", true);
- unknownType = decoder.connect("unknown-type",
- GCallback(QGstreamerMediaPlayer::unknownTypeCallback), this);
+ unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this);
+ elementAdded = decoder.connect("deep-element-added",
+ GCallback(decodebinElementAddedCallback), this);
+ elementRemoved = decoder.connect("deep-element-removed",
+ GCallback(decodebinElementAddedCallback), this);
playerPipeline.add(src, decoder);
qLinkGstElements(src, decoder);
@@ -776,7 +867,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
// use uridecodebin
decoder = QGstElement::createFromFactory("uridecodebin", "decoder");
if (!decoder) {
- emit error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("uridecodebin"));
+ error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("uridecodebin"));
return;
}
playerPipeline.add(decoder);
@@ -787,23 +878,28 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
} else {
// can't set post-stream-topology to true, as uridecodebin doesn't have the property.
// Use a hack
- elementAdded = decoder.connect(
- "element-added",
- GCallback(QGstreamerMediaPlayer::uridecodebinElementAddedCallback), this);
+ uridecodebinElementAdded = decoder.connect(
+ "element-added", GCallback(uridecodebinElementAddedCallback), this);
}
- sourceSetup = decoder.connect("source-setup",
- GCallback(QGstreamerMediaPlayer::sourceSetupCallback), this);
-
- unknownType = decoder.connect("unknown-type",
- GCallback(QGstreamerMediaPlayer::unknownTypeCallback), this);
+ sourceSetup = decoder.connect("source-setup", GCallback(sourceSetupCallback), this);
+ unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this);
decoder.set("uri", content.toEncoded().constData());
decoder.set("use-buffering", true);
+
+ constexpr int mb = 1024 * 1024;
+ decoder.set("ring-buffer-max-size", 2 * mb);
+
if (m_bufferProgress != 0) {
m_bufferProgress = 0;
emit bufferProgressChanged(0.);
}
+
+ elementAdded = decoder.connect("deep-element-added",
+ GCallback(decodebinElementAddedCallback), this);
+ elementRemoved = decoder.connect("deep-element-removed",
+ GCallback(decodebinElementAddedCallback), this);
}
padAdded = decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this);
padRemoved = decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this);
@@ -876,7 +972,7 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata()
QGstTagListHandle tagList;
gst_structure_get(topology.structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr);
- const auto metaData = QGstreamerMetaData::fromGstTagList(tagList.get());
+ const auto metaData = taglistToMetaData(tagList);
for (auto k : metaData.keys())
m_metaData.insert(k, metaData.value(k));
}
@@ -910,6 +1006,9 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata()
QSize resolution = structure.resolution();
if (resolution.isValid())
m_metaData.insert(QMediaMetaData::Resolution, resolution);
+
+ QSize nativeSize = structure.nativeSize();
+ gstVideoOutput->setNativeSize(nativeSize);
}
}
@@ -922,9 +1021,6 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata()
qCDebug(qLcMediaPlayer) << " tags=" << tagList.get();
else
qCDebug(qLcMediaPlayer) << " tags=(null)";
-
- QSize nativeSize = structure.nativeSize();
- gstVideoOutput->setNativeSize(nativeSize);
}
@@ -947,7 +1043,7 @@ QMediaMetaData QGstreamerMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackT
QGstTagListHandle tagList;
g_object_get(track.object(), "tags", &tagList, nullptr);
- return tagList ? QGstreamerMetaData::fromGstTagList(tagList.get()) : QMediaMetaData{};
+ return taglistToMetaData(tagList);
}
int QGstreamerMediaPlayer::activeTrack(TrackType type)
@@ -986,5 +1082,3 @@ void QGstreamerMediaPlayer::setActiveTrack(TrackType type, int index)
}
QT_END_NAMESPACE
-
-#include "moc_qgstreamermediaplayer_p.cpp"
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h
index 3b2129296..5c33d531f 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h
@@ -40,8 +40,6 @@ class QGstreamerMediaPlayer : public QObject,
public QGstreamerBusMessageFilter,
public QGstreamerSyncMessageFilter
{
- Q_OBJECT
-
public:
static QMaybe<QPlatformMediaPlayer *> create(QMediaPlayer *parent = nullptr);
~QGstreamerMediaPlayer();
@@ -58,7 +56,7 @@ public:
QUrl media() const override;
const QIODevice *mediaStream() const override;
- void setMedia(const QUrl&, QIODevice *) override;
+ void setMedia(const QUrl &, QIODevice *) override;
bool streamPlaybackSupported() const override { return true; }
@@ -83,7 +81,7 @@ public:
bool processBusMessage(const QGstreamerMessage& message) override;
bool processSyncMessage(const QGstreamerMessage& message) override;
-public Q_SLOTS:
+
void updatePosition() { positionChanged(position()); }
private:
@@ -91,7 +89,8 @@ private:
QGstElement audioInputSelector, QGstElement subTitleInputSelector,
QMediaPlayer *parent);
- struct TrackSelector {
+ struct TrackSelector
+ {
TrackSelector(TrackType, QGstElement selector);
QGstPad createInputPad();
void removeInputPad(QGstPad pad);
@@ -115,15 +114,23 @@ private:
void decoderPadAdded(const QGstElement &src, const QGstPad &pad);
void decoderPadRemoved(const QGstElement &src, const QGstPad &pad);
void disconnectDecoderHandlers();
- static void uridecodebinElementAddedCallback(GstElement *uridecodebin, GstElement *child, QGstreamerMediaPlayer *that);
- static void sourceSetupCallback(GstElement *uridecodebin, GstElement *source, QGstreamerMediaPlayer *that);
+ static void uridecodebinElementAddedCallback(GstElement *uridecodebin, GstElement *child,
+ QGstreamerMediaPlayer *that);
+ static void sourceSetupCallback(GstElement *uridecodebin, GstElement *source,
+ QGstreamerMediaPlayer *that);
static void unknownTypeCallback(GstElement *decodebin, GstPad *pad, GstCaps *caps,
QGstreamerMediaPlayer *self);
+ static void decodebinElementAddedCallback(GstBin *decodebin, GstBin *sub_bin,
+ GstElement *element, QGstreamerMediaPlayer *self);
+ static void decodebinElementRemovedCallback(GstBin *decodebin, GstBin *sub_bin,
+ GstElement *element, QGstreamerMediaPlayer *self);
+
void parseStreamsAndMetadata();
void connectOutput(TrackSelector &ts);
void removeOutput(TrackSelector &ts);
void removeAllOutputs();
void stopOrEOS(bool eos);
+ bool canTrackProgress() const { return decodeBinQueues > 0; }
std::array<TrackSelector, NTrackTypes> trackSelectors;
TrackSelector &trackSelector(TrackType type);
@@ -142,6 +149,7 @@ private:
bool prerolling = false;
bool m_requiresSeekOnPlay = false;
+ bool m_initialBufferProgressSent = false;
ResourceErrorState m_resourceErrorState = ResourceErrorState::NoError;
qint64 m_duration = 0;
QTimer positionUpdateTimer;
@@ -166,8 +174,12 @@ private:
QGObjectHandlerScopedConnection padAdded;
QGObjectHandlerScopedConnection padRemoved;
QGObjectHandlerScopedConnection sourceSetup;
- QGObjectHandlerScopedConnection elementAdded;
+ QGObjectHandlerScopedConnection uridecodebinElementAdded;
QGObjectHandlerScopedConnection unknownType;
+ QGObjectHandlerScopedConnection elementAdded;
+ QGObjectHandlerScopedConnection elementRemoved;
+
+ int decodeBinQueues = 0;
};
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp
index 953acb56a..3a229877d 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp
@@ -3,6 +3,7 @@
#include "qgstreamermetadata_p.h"
#include <QtMultimedia/qmediametadata.h>
+#include <QtMultimedia/qtvideo.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qlocale.h>
@@ -15,264 +16,385 @@
QT_BEGIN_NAMESPACE
-struct {
+namespace {
+
+namespace MetadataLookupImpl {
+
+#ifdef __cpp_lib_constexpr_algorithms
+# define constexpr_lookup constexpr
+#else
+# define constexpr_lookup /*constexpr*/
+#endif
+
+struct MetadataKeyValuePair
+{
const char *tag;
QMediaMetaData::Key key;
-} gstTagToMetaDataKey[] = {
- { GST_TAG_TITLE, QMediaMetaData::Title },
- { GST_TAG_COMMENT, QMediaMetaData::Comment },
- { GST_TAG_DESCRIPTION, QMediaMetaData::Description },
- { GST_TAG_GENRE, QMediaMetaData::Genre },
- { GST_TAG_DATE_TIME, QMediaMetaData::Date },
- { GST_TAG_DATE, QMediaMetaData::Date },
+};
- { GST_TAG_LANGUAGE_CODE, QMediaMetaData::Language },
+constexpr const char *toTag(const char *t)
+{
+ return t;
+}
+constexpr const char *toTag(const MetadataKeyValuePair &kv)
+{
+ return kv.tag;
+}
- { GST_TAG_ORGANIZATION, QMediaMetaData::Publisher },
- { GST_TAG_COPYRIGHT, QMediaMetaData::Copyright },
+constexpr QMediaMetaData::Key toKey(QMediaMetaData::Key k)
+{
+ return k;
+}
+constexpr QMediaMetaData::Key toKey(const MetadataKeyValuePair &kv)
+{
+ return kv.key;
+}
- // Media
- { GST_TAG_DURATION, QMediaMetaData::Duration },
+constexpr auto compareByKey = [](const auto &lhs, const auto &rhs) {
+ return toKey(lhs) < toKey(rhs);
+};
- // Audio
- { GST_TAG_BITRATE, QMediaMetaData::AudioBitRate },
- { GST_TAG_AUDIO_CODEC, QMediaMetaData::AudioCodec },
+constexpr auto compareByTag = [](const auto &lhs, const auto &rhs) {
+ return std::strcmp(toTag(lhs), toTag(rhs)) < 0;
+};
- // Music
- { GST_TAG_ALBUM, QMediaMetaData::AlbumTitle },
- { GST_TAG_ALBUM_ARTIST, QMediaMetaData::AlbumArtist },
- { GST_TAG_ARTIST, QMediaMetaData::ContributingArtist },
- { GST_TAG_TRACK_NUMBER, QMediaMetaData::TrackNumber },
+constexpr_lookup auto makeLookupTable()
+{
+ std::array<MetadataKeyValuePair, 22> lookupTable{ {
+ { GST_TAG_TITLE, QMediaMetaData::Title },
+ { GST_TAG_COMMENT, QMediaMetaData::Comment },
+ { GST_TAG_DESCRIPTION, QMediaMetaData::Description },
+ { GST_TAG_GENRE, QMediaMetaData::Genre },
+ { GST_TAG_DATE_TIME, QMediaMetaData::Date },
+ { GST_TAG_DATE, QMediaMetaData::Date },
+
+ { GST_TAG_LANGUAGE_CODE, QMediaMetaData::Language },
+
+ { GST_TAG_ORGANIZATION, QMediaMetaData::Publisher },
+ { GST_TAG_COPYRIGHT, QMediaMetaData::Copyright },
+
+ // Media
+ { GST_TAG_DURATION, QMediaMetaData::Duration },
+
+ // Audio
+ { GST_TAG_BITRATE, QMediaMetaData::AudioBitRate },
+ { GST_TAG_AUDIO_CODEC, QMediaMetaData::AudioCodec },
+
+ // Music
+ { GST_TAG_ALBUM, QMediaMetaData::AlbumTitle },
+ { GST_TAG_ALBUM_ARTIST, QMediaMetaData::AlbumArtist },
+ { GST_TAG_ARTIST, QMediaMetaData::ContributingArtist },
+ { GST_TAG_TRACK_NUMBER, QMediaMetaData::TrackNumber },
+
+ { GST_TAG_PREVIEW_IMAGE, QMediaMetaData::ThumbnailImage },
+ { GST_TAG_IMAGE, QMediaMetaData::CoverArtImage },
+
+ // Image/Video
+ { "resolution", QMediaMetaData::Resolution },
+ { GST_TAG_IMAGE_ORIENTATION, QMediaMetaData::Orientation },
+
+ // Video
+ { GST_TAG_VIDEO_CODEC, QMediaMetaData::VideoCodec },
+
+ // Movie
+ { GST_TAG_PERFORMER, QMediaMetaData::LeadPerformer },
+ } };
+
+ std::sort(lookupTable.begin(), lookupTable.end(),
+ [](const MetadataKeyValuePair &lhs, const MetadataKeyValuePair &rhs) {
+ return std::string_view(lhs.tag) < std::string_view(rhs.tag);
+ });
+ return lookupTable;
+}
- { GST_TAG_PREVIEW_IMAGE, QMediaMetaData::ThumbnailImage },
- { GST_TAG_IMAGE, QMediaMetaData::CoverArtImage },
+constexpr_lookup auto gstTagToMetaDataKey = makeLookupTable();
+constexpr_lookup auto metaDataKeyToGstTag = [] {
+ auto array = gstTagToMetaDataKey;
+ std::sort(array.begin(), array.end(), compareByKey);
+ return array;
+}();
- // Image/Video
- { "resolution", QMediaMetaData::Resolution },
- { GST_TAG_IMAGE_ORIENTATION, QMediaMetaData::Orientation },
+} // namespace MetadataLookupImpl
- // Video
- { GST_TAG_VIDEO_CODEC, QMediaMetaData::VideoCodec },
+QMediaMetaData::Key tagToKey(const char *tag)
+{
+ if (tag == nullptr)
+ return QMediaMetaData::Key(-1);
- // Movie
- { GST_TAG_PERFORMER, QMediaMetaData::LeadPerformer },
+ using namespace MetadataLookupImpl;
+ auto foundIterator = std::lower_bound(gstTagToMetaDataKey.begin(), gstTagToMetaDataKey.end(),
+ tag, compareByTag);
+ if (std::strcmp(foundIterator->tag, tag) == 0)
+ return foundIterator->key;
- { nullptr, QMediaMetaData::Title }
-};
+ return QMediaMetaData::Key(-1);
+}
+
+const char *keyToTag(QMediaMetaData::Key key)
+{
+ using namespace MetadataLookupImpl;
+ auto foundIterator = std::lower_bound(metaDataKeyToGstTag.begin(), metaDataKeyToGstTag.end(),
+ key, compareByKey);
+ if (foundIterator->key == key)
+ return foundIterator->tag;
+
+ return nullptr;
+}
+
+#undef constexpr_lookup
+
+QtVideo::Rotation parseRotationTag(const char *string)
+{
+ using namespace std::string_view_literals;
+
+ if (string == "rotate-90"sv)
+ return QtVideo::Rotation::Clockwise90;
+ if (string == "rotate-180"sv)
+ return QtVideo::Rotation::Clockwise180;
+ if (string == "rotate-270"sv)
+ return QtVideo::Rotation::Clockwise270;
+ if (string == "rotate-0"sv)
+ return QtVideo::Rotation::None;
+
+ qCritical() << "cannot parse orientation: {}" << string;
+ return QtVideo::Rotation::None;
+}
-static QMediaMetaData::Key tagToKey(const char *tag)
+QDateTime parseDate(const GValue &val)
{
- auto *map = gstTagToMetaDataKey;
- while (map->tag) {
- if (!strcmp(map->tag, tag))
- return map->key;
- ++map;
+ Q_ASSERT(G_VALUE_TYPE(&val) == G_TYPE_DATE);
+
+ const GDate *date = (const GDate *)g_value_get_boxed(&val);
+ if (!g_date_valid(date))
+ return {};
+
+ int year = g_date_get_year(date);
+ int month = g_date_get_month(date);
+ int day = g_date_get_day(date);
+ return QDateTime(QDate(year, month, day), QTime());
+}
+
+QDateTime parseDateTime(const GValue &val)
+{
+ Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_DATE_TIME);
+
+ const GstDateTime *dateTime = (const GstDateTime *)g_value_get_boxed(&val);
+ int year = gst_date_time_has_year(dateTime) ? gst_date_time_get_year(dateTime) : 0;
+ int month = gst_date_time_has_month(dateTime) ? gst_date_time_get_month(dateTime) : 0;
+ int day = gst_date_time_has_day(dateTime) ? gst_date_time_get_day(dateTime) : 0;
+ int hour = 0;
+ int minute = 0;
+ int second = 0;
+ float tz = 0;
+ if (gst_date_time_has_time(dateTime)) {
+ hour = gst_date_time_get_hour(dateTime);
+ minute = gst_date_time_get_minute(dateTime);
+ second = gst_date_time_get_second(dateTime);
+ tz = gst_date_time_get_time_zone_offset(dateTime);
}
- return QMediaMetaData::Key(-1);
+ return QDateTime{
+ QDate(year, month, day),
+ QTime(hour, minute, second),
+ QTimeZone(tz * 60 * 60),
+ };
}
-static const char *keyToTag(QMediaMetaData::Key key)
+QImage parseImage(const GValue &val)
{
- auto *map = gstTagToMetaDataKey;
- while (map->tag) {
- if (map->key == key)
- return map->tag;
- ++map;
+ Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_SAMPLE);
+
+ GstSample *sample = (GstSample *)g_value_get_boxed(&val);
+ GstCaps *caps = gst_sample_get_caps(sample);
+ if (caps && !gst_caps_is_empty(caps)) {
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+ const gchar *name = gst_structure_get_name(structure);
+ if (QByteArray(name).startsWith("image/")) {
+ GstBuffer *buffer = gst_sample_get_buffer(sample);
+ if (buffer) {
+ GstMapInfo info;
+ gst_buffer_map(buffer, &info, GST_MAP_READ);
+ QImage image = QImage::fromData(info.data, info.size, name);
+ gst_buffer_unmap(buffer, &info);
+ return image;
+ }
+ }
}
- return nullptr;
+
+ return {};
}
-//internal
-static void addTagToMap(const GstTagList *list,
- const gchar *tag,
- gpointer user_data)
+std::optional<double> parseFractionAsDouble(const GValue &val)
{
+ Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_FRACTION);
+
+ int nom = gst_value_get_fraction_numerator(&val);
+ int denom = gst_value_get_fraction_denominator(&val);
+ if (denom == 0)
+ return std::nullopt;
+ return double(nom) / double(denom);
+}
+
+// internal
+void addTagToMetaData(const GstTagList *list, const gchar *tag, void *userdata)
+{
+ QMediaMetaData &metadata = *reinterpret_cast<QMediaMetaData *>(userdata);
+
QMediaMetaData::Key key = tagToKey(tag);
if (key == QMediaMetaData::Key(-1))
return;
- auto *map = reinterpret_cast<QHash<QMediaMetaData::Key, QVariant>* >(user_data);
-
- GValue val;
- val.g_type = 0;
+ GValue val{};
gst_tag_list_copy_value(&val, list, tag);
- switch (G_VALUE_TYPE(&val)) {
- case G_TYPE_STRING: {
+ GType type = G_VALUE_TYPE(&val);
+
+ if (type == G_TYPE_STRING) {
const gchar *str_value = g_value_get_string(&val);
- if (key == QMediaMetaData::Language) {
- map->insert(key,
- QVariant::fromValue(QLocale::codeToLanguage(QString::fromUtf8(str_value),
- QLocale::ISO639Part2)));
+
+ switch (key) {
+ case QMediaMetaData::Language: {
+ metadata.insert(key,
+ QVariant::fromValue(QLocale::codeToLanguage(
+ QString::fromUtf8(str_value), QLocale::ISO639Part2)));
break;
}
- map->insert(key, QString::fromUtf8(str_value));
- break;
- }
- case G_TYPE_INT:
- map->insert(key, g_value_get_int(&val));
- break;
- case G_TYPE_UINT:
- map->insert(key, g_value_get_uint(&val));
- break;
- case G_TYPE_LONG:
- map->insert(key, qint64(g_value_get_long(&val)));
- break;
- case G_TYPE_BOOLEAN:
- map->insert(key, g_value_get_boolean(&val));
- break;
- case G_TYPE_CHAR:
- map->insert(key, g_value_get_schar(&val));
- break;
- case G_TYPE_DOUBLE:
- map->insert(key, g_value_get_double(&val));
- break;
- default:
- // GST_TYPE_DATE is a function, not a constant, so pull it out of the switch
- if (G_VALUE_TYPE(&val) == G_TYPE_DATE) {
- const GDate *date = (const GDate *)g_value_get_boxed(&val);
- if (g_date_valid(date)) {
- int year = g_date_get_year(date);
- int month = g_date_get_month(date);
- int day = g_date_get_day(date);
- // don't insert if we already have a datetime.
- if (!map->contains(key))
- map->insert(key, QDateTime(QDate(year, month, day), QTime()));
- }
- } else if (G_VALUE_TYPE(&val) == GST_TYPE_DATE_TIME) {
- const GstDateTime *dateTime = (const GstDateTime *)g_value_get_boxed(&val);
- int year = gst_date_time_has_year(dateTime) ? gst_date_time_get_year(dateTime) : 0;
- int month = gst_date_time_has_month(dateTime) ? gst_date_time_get_month(dateTime) : 0;
- int day = gst_date_time_has_day(dateTime) ? gst_date_time_get_day(dateTime) : 0;
- int hour = 0;
- int minute = 0;
- int second = 0;
- float tz = 0;
- if (gst_date_time_has_time(dateTime)) {
- hour = gst_date_time_get_hour(dateTime);
- minute = gst_date_time_get_minute(dateTime);
- second = gst_date_time_get_second(dateTime);
- tz = gst_date_time_get_time_zone_offset(dateTime);
- }
- QDateTime qDateTime(QDate(year, month, day), QTime(hour, minute, second),
- QTimeZone(tz * 60 * 60));
- map->insert(key, qDateTime);
- } else if (G_VALUE_TYPE(&val) == GST_TYPE_SAMPLE) {
- GstSample *sample = (GstSample *)g_value_get_boxed(&val);
- GstCaps *caps = gst_sample_get_caps(sample);
- if (caps && !gst_caps_is_empty(caps)) {
- GstStructure *structure = gst_caps_get_structure(caps, 0);
- const gchar *name = gst_structure_get_name(structure);
- if (QByteArray(name).startsWith("image/")) {
- GstBuffer *buffer = gst_sample_get_buffer(sample);
- if (buffer) {
- GstMapInfo info;
- gst_buffer_map(buffer, &info, GST_MAP_READ);
- map->insert(key, QImage::fromData(info.data, info.size, name));
- gst_buffer_unmap(buffer, &info);
- }
- }
- }
- } else if (G_VALUE_TYPE(&val) == GST_TYPE_FRACTION) {
- int nom = gst_value_get_fraction_numerator(&val);
- int denom = gst_value_get_fraction_denominator(&val);
-
- if (denom > 0) {
- map->insert(key, double(nom) / denom);
- }
+ case QMediaMetaData::Orientation: {
+ metadata.insert(key, QVariant::fromValue(parseRotationTag(str_value)));
+ break;
}
- break;
+ default:
+ metadata.insert(key, QString::fromUtf8(str_value));
+ break;
+ };
+ } else if (type == G_TYPE_INT) {
+ metadata.insert(key, g_value_get_int(&val));
+ } else if (type == G_TYPE_UINT) {
+ metadata.insert(key, g_value_get_uint(&val));
+ } else if (type == G_TYPE_LONG) {
+ metadata.insert(key, qint64(g_value_get_long(&val)));
+ } else if (type == G_TYPE_BOOLEAN) {
+ metadata.insert(key, g_value_get_boolean(&val));
+ } else if (type == G_TYPE_CHAR) {
+ metadata.insert(key, g_value_get_schar(&val));
+ } else if (type == G_TYPE_DOUBLE) {
+ metadata.insert(key, g_value_get_double(&val));
+ } else if (type == G_TYPE_DATE) {
+ if (!metadata.keys().contains(key)) {
+ QDateTime date = parseDate(val);
+ if (date.isValid())
+ metadata.insert(key, date);
+ }
+ } else if (type == GST_TYPE_DATE_TIME) {
+ metadata.insert(key, parseDateTime(val));
+ } else if (type == GST_TYPE_SAMPLE) {
+ QImage image = parseImage(val);
+ if (!image.isNull())
+ metadata.insert(key, image);
+ } else if (type == GST_TYPE_FRACTION) {
+ std::optional<double> fraction = parseFractionAsDouble(val);
+
+ if (fraction)
+ metadata.insert(key, *fraction);
}
g_value_unset(&val);
}
+} // namespace
-QGstreamerMetaData QGstreamerMetaData::fromGstTagList(const GstTagList *tags)
+QMediaMetaData taglistToMetaData(const GstTagList *tagList)
{
- QGstreamerMetaData m;
- gst_tag_list_foreach(tags, addTagToMap, &m.data);
+ QMediaMetaData m;
+ if (tagList)
+ gst_tag_list_foreach(tagList, reinterpret_cast<GstTagForeachFunc>(&addTagToMetaData), &m);
return m;
}
-
-void QGstreamerMetaData::setMetaData(GstElement *element) const
+QMediaMetaData taglistToMetaData(const QGstTagListHandle &handle)
{
- if (!GST_IS_TAG_SETTER(element))
- return;
+ return taglistToMetaData(handle.get());
+}
- gst_tag_setter_reset_tags(GST_TAG_SETTER(element));
+static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element)
+{
+ gst_tag_setter_reset_tags(element);
- for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) {
- const char *tagName = keyToTag(it.key());
+ for (QMediaMetaData::Key key : metadata.keys()) {
+ const char *tagName = keyToTag(key);
if (!tagName)
continue;
- const QVariant &tagValue = it.value();
+ const QVariant &tagValue = metadata.value(key);
+
+ auto setTag = [&](const auto &value) {
+ gst_tag_setter_add_tags(element, GST_TAG_MERGE_REPLACE, tagName, value, nullptr);
+ };
switch (tagValue.typeId()) {
- case QMetaType::QString:
- gst_tag_setter_add_tags(GST_TAG_SETTER(element),
- GST_TAG_MERGE_REPLACE,
- tagName,
- tagValue.toString().toUtf8().constData(),
- nullptr);
- break;
- case QMetaType::Int:
- case QMetaType::LongLong:
- gst_tag_setter_add_tags(GST_TAG_SETTER(element),
- GST_TAG_MERGE_REPLACE,
- tagName,
- tagValue.toInt(),
- nullptr);
- break;
- case QMetaType::Double:
- gst_tag_setter_add_tags(GST_TAG_SETTER(element),
- GST_TAG_MERGE_REPLACE,
- tagName,
- tagValue.toDouble(),
- nullptr);
- break;
- case QMetaType::QDate:
- case QMetaType::QDateTime: {
- QDateTime date = tagValue.toDateTime();
-
- QGstGstDateTimeHandle dateTime{
- gst_date_time_new(date.offsetFromUtc() / 60. / 60., date.date().year(),
- date.date().month(), date.date().day(), date.time().hour(),
- date.time().minute(), date.time().second()),
- QGstGstDateTimeHandle::HasRef,
- };
-
- gst_tag_setter_add_tags(GST_TAG_SETTER(element), GST_TAG_MERGE_REPLACE, tagName,
- dateTime.get(), nullptr);
- break;
- }
- default: {
- if (tagValue.typeId() == qMetaTypeId<QLocale::Language>()) {
- QByteArray language = QLocale::languageToCode(tagValue.value<QLocale::Language>(), QLocale::ISO639Part2).toUtf8();
- gst_tag_setter_add_tags(GST_TAG_SETTER(element),
- GST_TAG_MERGE_REPLACE,
- tagName,
- language.constData(),
- nullptr);
- }
-
- break;
+ case QMetaType::QString:
+ setTag(tagValue.toString().toUtf8().constData());
+ break;
+ case QMetaType::Int:
+ case QMetaType::LongLong:
+ setTag(tagValue.toInt());
+ break;
+ case QMetaType::Double:
+ setTag(tagValue.toDouble());
+ break;
+ case QMetaType::QDate:
+ case QMetaType::QDateTime: {
+ QDateTime date = tagValue.toDateTime();
+
+ QGstGstDateTimeHandle dateTime{
+ gst_date_time_new(date.offsetFromUtc() / 60. / 60., date.date().year(),
+ date.date().month(), date.date().day(), date.time().hour(),
+ date.time().minute(), date.time().second()),
+ QGstGstDateTimeHandle::HasRef,
+ };
+
+ setTag(dateTime.get());
+ break;
+ }
+ default: {
+ if (tagValue.typeId() == qMetaTypeId<QLocale::Language>()) {
+ QByteArray language = QLocale::languageToCode(tagValue.value<QLocale::Language>(),
+ QLocale::ISO639Part2)
+ .toUtf8();
+ setTag(language.constData());
}
+
+ break;
+ }
}
}
}
-void QGstreamerMetaData::setMetaData(GstBin *bin) const
+void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstElement &element)
{
- GstIterator *elements = gst_bin_iterate_all_by_interface(bin, GST_TYPE_TAG_SETTER);
- GValue item = G_VALUE_INIT;
+ GstTagSetter *tagSetter = qGstSafeCast<GstTagSetter>(element.element());
+ if (tagSetter)
+ applyMetaDataToTagSetter(metadata, tagSetter);
+ else
+ qWarning() << "applyMetaDataToTagSetter failed: element not a GstTagSetter"
+ << element.name();
+}
+
+void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstBin &bin)
+{
+ GstIterator *elements = gst_bin_iterate_all_by_interface(bin.bin(), GST_TYPE_TAG_SETTER);
+ GValue item = {};
+
while (gst_iterator_next(elements, &item) == GST_ITERATOR_OK) {
- GstElement * const element = GST_ELEMENT(g_value_get_object(&item));
- setMetaData(element);
+ GstElement *element = static_cast<GstElement *>(g_value_get_object(&item));
+ if (!element)
+ continue;
+
+ GstTagSetter *tagSetter = qGstSafeCast<GstTagSetter>(element);
+
+ if (tagSetter)
+ applyMetaDataToTagSetter(metadata, tagSetter);
}
+
gst_iterator_free(elements);
}
-
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h
index 7ff5552b2..d0d7620db 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h
@@ -16,20 +16,16 @@
//
#include <qmediametadata.h>
-#include <qvariant.h>
-#include <gst/gst.h>
+#include "qgst_p.h"
QT_BEGIN_NAMESPACE
-class QGstreamerMetaData : public QMediaMetaData
-{
-public:
- static QGstreamerMetaData fromGstTagList(const GstTagList *tags);
+QMediaMetaData taglistToMetaData(const GstTagList *);
+QMediaMetaData taglistToMetaData(const QGstTagListHandle &);
- void setMetaData(GstBin *bin) const;
- void setMetaData(GstElement *element) const;
-};
+void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstBin &);
+void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstElement &);
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
index 053dd973b..6bc65693a 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
@@ -179,6 +179,14 @@ void QGstreamerVideoOutput::doLinkSubtitleStream()
qLinkGstElements(subtitleSrc, subtitleSink);
}
+void QGstreamerVideoOutput::updateNativeSize()
+{
+ if (!m_videoSink)
+ return;
+
+ m_videoSink->setNativeSize(qRotatedFrameSize(nativeSize, rotation));
+}
+
void QGstreamerVideoOutput::setIsPreview()
{
// configures the queue to be fast and lightweight for camera preview
@@ -204,8 +212,13 @@ void QGstreamerVideoOutput::flushSubtitles()
void QGstreamerVideoOutput::setNativeSize(QSize sz)
{
nativeSize = sz;
- if (m_videoSink)
- m_videoSink->setNativeSize(nativeSize);
+ updateNativeSize();
+}
+
+void QGstreamerVideoOutput::setRotation(QtVideo::Rotation rot)
+{
+ rotation = rot;
+ updateNativeSize();
}
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h
index 62bd4b219..42acb18cc 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h
@@ -50,12 +50,14 @@ public:
void flushSubtitles();
void setNativeSize(QSize);
+ void setRotation(QtVideo::Rotation);
private:
QGstreamerVideoOutput(QGstElement videoConvert, QGstElement videoScale, QGstElement videoSink,
QObject *parent);
void doLinkSubtitleStream();
+ void updateNativeSize();
QPointer<QGstreamerVideoSink> m_videoSink;
@@ -72,6 +74,7 @@ private:
QGstElement subtitleSink;
QSize nativeSize;
+ QtVideo::Rotation rotation{};
};
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/common/qgstutils.cpp b/src/plugins/multimedia/gstreamer/common/qgstutils.cpp
index d3d5f6124..40fb4b6f7 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstutils.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstutils.cpp
@@ -115,7 +115,7 @@ QList<QAudioFormat::SampleFormat> QGValue::getSampleFormats() const
return formats;
}
-void QGstUtils::setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer)
+void QGstUtils::setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer)
{
using namespace std::chrono;
using namespace std::chrono_literals;
diff --git a/src/plugins/multimedia/gstreamer/common/qgstutils_p.h b/src/plugins/multimedia/gstreamer/common/qgstutils_p.h
index 4141ae0bf..c65fcf090 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstutils_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstutils_p.h
@@ -31,7 +31,7 @@ QAudioFormat audioFormatForSample(GstSample *sample);
QAudioFormat audioFormatForCaps(const QGstCaps &caps);
QGstCaps capsForAudioFormat(const QAudioFormat &format);
-void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer);
+void setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer);
} // namespace QGstUtils
GList *qt_gst_video_sinks();
diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideobuffer.cpp b/src/plugins/multimedia/gstreamer/common/qgstvideobuffer.cpp
index 101d56af6..6552786bb 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstvideobuffer.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstvideobuffer.cpp
@@ -51,18 +51,19 @@ QT_BEGIN_NAMESPACE
#define DRM_FORMAT_GR1616 fourcc_code('G', 'R', '3', '2') /* [31:0] G:R 16:16 little endian */
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */
-QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info, QGstreamerVideoSink *sink,
- const QVideoFrameFormat &frameFormat,
+QGstVideoBuffer::QGstVideoBuffer(QGstBufferHandle buffer, const GstVideoInfo &info,
+ QGstreamerVideoSink *sink, const QVideoFrameFormat &frameFormat,
QGstCaps::MemoryFormat format)
- : QAbstractVideoBuffer((sink && sink->rhi() && format != QGstCaps::CpuMemory) ?
- QVideoFrame::RhiTextureHandle : QVideoFrame::NoHandle, sink ? sink->rhi() : nullptr)
- , memoryFormat(format)
- , m_frameFormat(frameFormat)
- , m_rhi(sink ? sink->rhi() : nullptr)
- , m_videoInfo(info)
- , m_buffer(buffer)
+ : QAbstractVideoBuffer((sink && sink->rhi() && format != QGstCaps::CpuMemory)
+ ? QVideoFrame::RhiTextureHandle
+ : QVideoFrame::NoHandle,
+ sink ? sink->rhi() : nullptr),
+ memoryFormat(format),
+ m_frameFormat(frameFormat),
+ m_rhi(sink ? sink->rhi() : nullptr),
+ m_videoInfo(info),
+ m_buffer(std::move(buffer))
{
- gst_buffer_ref(m_buffer);
if (sink) {
eglDisplay = sink->eglDisplay();
eglImageTargetTexture2D = sink->eglImageTargetTexture2D();
@@ -76,8 +77,6 @@ QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info, QG
QGstVideoBuffer::~QGstVideoBuffer()
{
unmap();
-
- gst_buffer_unref(m_buffer);
}
@@ -96,7 +95,7 @@ QAbstractVideoBuffer::MapData QGstVideoBuffer::map(QVideoFrame::MapMode mode)
return mapData;
if (m_videoInfo.finfo->n_planes == 0) { // Encoded
- if (gst_buffer_map(m_buffer, &m_frame.map[0], flags)) {
+ if (gst_buffer_map(m_buffer.get(), &m_frame.map[0], flags)) {
mapData.nPlanes = 1;
mapData.bytesPerLine[0] = -1;
mapData.size[0] = m_frame.map[0].size;
@@ -104,7 +103,7 @@ QAbstractVideoBuffer::MapData QGstVideoBuffer::map(QVideoFrame::MapMode mode)
m_mode = mode;
}
- } else if (gst_video_frame_map(&m_frame, &m_videoInfo, m_buffer, flags)) {
+ } else if (gst_video_frame_map(&m_frame, &m_videoInfo, m_buffer.get(), flags)) {
mapData.nPlanes = GST_VIDEO_FRAME_N_PLANES(&m_frame);
for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES(&m_frame); ++i) {
@@ -122,7 +121,7 @@ void QGstVideoBuffer::unmap()
{
if (m_mode != QVideoFrame::NotMapped) {
if (m_videoInfo.finfo->n_planes == 0)
- gst_buffer_unmap(m_buffer, &m_frame.map[0]);
+ gst_buffer_unmap(m_buffer.get(), &m_frame.map[0]);
else
gst_video_frame_unmap(&m_frame);
}
@@ -258,9 +257,10 @@ private:
std::unique_ptr<QRhiTexture> m_textures[QVideoTextureHelper::TextureDescription::maxPlanes];
};
-
-static GlTextures mapFromGlTexture(GstBuffer *buffer, GstVideoFrame &frame, GstVideoInfo &videoInfo)
+static GlTextures mapFromGlTexture(const QGstBufferHandle &bufferHandle, GstVideoFrame &frame,
+ GstVideoInfo &videoInfo)
{
+ GstBuffer *buffer = bufferHandle.get();
auto *mem = GST_GL_BASE_MEMORY_CAST(gst_buffer_peek_memory(buffer, 0));
if (!mem)
return {};
@@ -293,10 +293,12 @@ static GlTextures mapFromGlTexture(GstBuffer *buffer, GstVideoFrame &frame, GstV
}
#if GST_GL_HAVE_PLATFORM_EGL && QT_CONFIG(linux_dmabuf)
-static GlTextures mapFromDmaBuffer(QRhi *rhi, GstBuffer *buffer, GstVideoFrame &frame,
- GstVideoInfo &videoInfo, Qt::HANDLE eglDisplay,
- QFunctionPointer eglImageTargetTexture2D)
+static GlTextures mapFromDmaBuffer(QRhi *rhi, const QGstBufferHandle &bufferHandle,
+ GstVideoFrame &frame, GstVideoInfo &videoInfo,
+ Qt::HANDLE eglDisplay, QFunctionPointer eglImageTargetTexture2D)
{
+ GstBuffer *buffer = bufferHandle.get();
+
Q_ASSERT(gst_is_dmabuf_memory(gst_buffer_peek_memory(buffer, 0)));
Q_ASSERT(eglDisplay);
Q_ASSERT(eglImageTargetTexture2D);
@@ -377,14 +379,15 @@ std::unique_ptr<QVideoFrameTextures> QGstVideoBuffer::mapTextures(QRhi *rhi)
#if QT_CONFIG(gstreamer_gl)
GlTextures textures = {};
- if (memoryFormat == QGstCaps::GLTexture) {
+ if (memoryFormat == QGstCaps::GLTexture)
textures = mapFromGlTexture(m_buffer, m_frame, m_videoInfo);
- }
-#if GST_GL_HAVE_PLATFORM_EGL && QT_CONFIG(linux_dmabuf)
- else if (memoryFormat == QGstCaps::DMABuf) {
- textures = mapFromDmaBuffer(m_rhi, m_buffer, m_frame, m_videoInfo, eglDisplay, eglImageTargetTexture2D);
- }
-#endif
+
+# if GST_GL_HAVE_PLATFORM_EGL && QT_CONFIG(linux_dmabuf)
+ else if (memoryFormat == QGstCaps::DMABuf)
+ textures = mapFromDmaBuffer(m_rhi, m_buffer, m_frame, m_videoInfo, eglDisplay,
+ eglImageTargetTexture2D);
+
+# endif
if (textures.count > 0)
return std::make_unique<QGstQVideoFrameTextures>(rhi, QSize{m_videoInfo.width, m_videoInfo.height},
m_frameFormat.pixelFormat(), textures);
diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideobuffer_p.h b/src/plugins/multimedia/gstreamer/common/qgstvideobuffer_p.h
index 27567c9f7..151927d3d 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstvideobuffer_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstvideobuffer_p.h
@@ -30,12 +30,10 @@ class QOpenGLContext;
class QGstVideoBuffer final : public QAbstractVideoBuffer
{
public:
-
- QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info, QGstreamerVideoSink *sink,
+ QGstVideoBuffer(QGstBufferHandle buffer, const GstVideoInfo &info, QGstreamerVideoSink *sink,
const QVideoFrameFormat &frameFormat, QGstCaps::MemoryFormat format);
~QGstVideoBuffer();
- GstBuffer *buffer() const { return m_buffer; }
QVideoFrame::MapMode mapMode() const override;
MapData map(QVideoFrame::MapMode mode) override;
@@ -49,7 +47,7 @@ private:
QRhi *m_rhi = nullptr;
mutable GstVideoInfo m_videoInfo;
mutable GstVideoFrame m_frame{};
- GstBuffer *m_buffer = nullptr;
+ const QGstBufferHandle m_buffer;
QVideoFrame::MapMode m_mode = QVideoFrame::NotMapped;
Qt::HANDLE eglDisplay = nullptr;
QFunctionPointer eglImageTargetTexture2D = nullptr;
diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp
index 0a2de1228..4e9619b11 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp
+++ b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp
@@ -177,7 +177,7 @@ void QGstVideoRenderer::flush()
QMutexLocker locker(&m_mutex);
m_flush = true;
- m_renderBuffer = nullptr;
+ m_renderBuffer = {};
m_renderCondition.wakeAll();
notify();
@@ -189,11 +189,14 @@ GstFlowReturn QGstVideoRenderer::render(GstBuffer *buffer)
qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::render";
m_renderReturn = GST_FLOW_OK;
- m_renderBuffer = buffer;
+ m_renderBuffer = QGstBufferHandle{
+ buffer,
+ QGstBufferHandle::NeedsRef,
+ };
waitForAsyncEvent(&locker, &m_renderCondition, 300);
- m_renderBuffer = nullptr;
+ m_renderBuffer = {};
return m_renderReturn;
}
@@ -224,9 +227,22 @@ bool QGstVideoRenderer::query(GstQuery *query)
void QGstVideoRenderer::gstEvent(GstEvent *event)
{
- if (GST_EVENT_TYPE(event) != GST_EVENT_TAG)
+ switch (GST_EVENT_TYPE(event)) {
+ case GST_EVENT_TAG:
+ qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: Tag";
+ return gstEventHandleTag(event);
+ case GST_EVENT_EOS:
+ qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: EOS";
+ return gstEventHandleEOS(event);
+
+ default:
+ qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: unhandled event - " << event;
return;
+ }
+}
+void QGstVideoRenderer::gstEventHandleTag(GstEvent *event)
+{
GstTagList *taglist = nullptr;
gst_event_parse_tag(event, &taglist);
if (!taglist)
@@ -256,14 +272,28 @@ void QGstVideoRenderer::gstEvent(GstEvent *event)
QMutexLocker locker(&m_mutex);
m_frameMirrored = mirrored;
switch (rotationAngle) {
- case 0: m_frameRotationAngle = QtVideo::Rotation::None; break;
- case 90: m_frameRotationAngle = QtVideo::Rotation::Clockwise90; break;
- case 180: m_frameRotationAngle = QtVideo::Rotation::Clockwise180; break;
- case 270: m_frameRotationAngle = QtVideo::Rotation::Clockwise270; break;
- default: m_frameRotationAngle = QtVideo::Rotation::None;
+ case 0:
+ m_frameRotationAngle = QtVideo::Rotation::None;
+ break;
+ case 90:
+ m_frameRotationAngle = QtVideo::Rotation::Clockwise90;
+ break;
+ case 180:
+ m_frameRotationAngle = QtVideo::Rotation::Clockwise180;
+ break;
+ case 270:
+ m_frameRotationAngle = QtVideo::Rotation::Clockwise270;
+ break;
+ default:
+ m_frameRotationAngle = QtVideo::Rotation::None;
}
}
+void QGstVideoRenderer::gstEventHandleEOS(GstEvent *)
+{
+ stop();
+}
+
bool QGstVideoRenderer::event(QEvent *event)
{
if (event->type() == QEvent::UpdateRequest) {
@@ -326,19 +356,17 @@ bool QGstVideoRenderer::handleEvent(QMutexLocker<QMutex> *locker)
}
} else if (m_renderBuffer) {
- GstBuffer *buffer = m_renderBuffer;
- m_renderBuffer = nullptr;
+ QGstBufferHandle buffer = std::move(m_renderBuffer);
m_renderReturn = GST_FLOW_ERROR;
qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::handleEvent(renderBuffer)" << m_active << m_sink;
if (m_active && m_sink) {
- gst_buffer_ref(buffer);
locker->unlock();
m_flushed = false;
- GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer);
+ GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer.get());
if (meta) {
QRect vp(meta->x, meta->y, meta->width, meta->height);
if (m_format.viewport() != vp) {
@@ -354,7 +382,7 @@ bool QGstVideoRenderer::handleEvent(QMutexLocker<QMutex> *locker)
} else {
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, m_videoInfo, m_sink, m_format, memoryFormat);
QVideoFrame frame(videoBuffer, m_format);
- QGstUtils::setFrameTimeStamps(&frame, buffer);
+ QGstUtils::setFrameTimeStampsFromBuffer(&frame, buffer.get());
frame.setMirrored(m_frameMirrored);
frame.setRotation(m_frameRotationAngle);
@@ -362,8 +390,6 @@ bool QGstVideoRenderer::handleEvent(QMutexLocker<QMutex> *locker)
m_sink->setVideoFrame(frame);
}
- gst_buffer_unref(buffer);
-
locker->relock();
m_renderReturn = GST_FLOW_OK;
diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h
index 6a923ed32..99d1b0ac8 100644
--- a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h
+++ b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h
@@ -61,6 +61,9 @@ private:
bool waitForAsyncEvent(QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time);
static QGstCaps createSurfaceCaps(QGstreamerVideoSink *);
+ void gstEventHandleTag(GstEvent *);
+ void gstEventHandleEOS(GstEvent *);
+
QPointer<QGstreamerVideoSink> m_sink;
QMutex m_mutex;
@@ -74,7 +77,7 @@ private:
const QGstCaps m_surfaceCaps;
QGstCaps m_startCaps;
- GstBuffer *m_renderBuffer = nullptr;
+ QGstBufferHandle m_renderBuffer;
bool m_notified = false;
bool m_stop = false;
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp
index 3eee3c800..bb4990603 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp
@@ -71,7 +71,7 @@ QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstEle
addProbeToPad(queue.staticPad("src").pad(), false);
sink.set("signal-handoffs", true);
- g_signal_connect(sink.object(), "handoff", G_CALLBACK(&QGstreamerImageCapture::saveImageFilter), this);
+ m_handoffConnection = sink.connect("handoff", G_CALLBACK(&saveImageFilter), this);
}
QGstreamerImageCapture::~QGstreamerImageCapture()
@@ -86,7 +86,9 @@ bool QGstreamerImageCapture::isReadyForCapture() const
int QGstreamerImageCapture::capture(const QString &fileName)
{
- QString path = QMediaStorageLocation::generateFileName(fileName, QStandardPaths::PicturesLocation, QLatin1String("jpg"));
+ using namespace Qt::Literals;
+ QString path = QMediaStorageLocation::generateFileName(
+ fileName, QStandardPaths::PicturesLocation, u"jpg"_s);
return doCapture(path);
}
@@ -98,35 +100,35 @@ int QGstreamerImageCapture::captureToBuffer()
int QGstreamerImageCapture::doCapture(const QString &fileName)
{
qCDebug(qLcImageCaptureGst) << "do capture";
+
+ // emit error in the next event loop,
+ // so application can associate it with returned request id.
+ auto invokeDeferred = [&](auto &&fn) {
+ QMetaObject::invokeMethod(this, std::forward<decltype(fn)>(fn), Qt::QueuedConnection);
+ };
+
if (!m_session) {
- //emit error in the next event loop,
- //so application can associate it with returned request id.
- QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(int, -1),
- Q_ARG(int, QImageCapture::ResourceError),
- Q_ARG(QString, QPlatformImageCapture::msgImageCaptureNotSet()));
+ invokeDeferred([this] {
+ emit error(-1, QImageCapture::ResourceError,
+ QPlatformImageCapture::msgImageCaptureNotSet());
+ });
qCDebug(qLcImageCaptureGst) << "error 1";
return -1;
}
if (!m_session->camera()) {
- //emit error in the next event loop,
- //so application can associate it with returned request id.
- QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(int, -1),
- Q_ARG(int, QImageCapture::ResourceError),
- Q_ARG(QString,tr("No camera available.")));
+ invokeDeferred([this] {
+ emit error(-1, QImageCapture::ResourceError, tr("No camera available."));
+ });
qCDebug(qLcImageCaptureGst) << "error 2";
return -1;
}
if (passImage) {
- //emit error in the next event loop,
- //so application can associate it with returned request id.
- QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(int, -1),
- Q_ARG(int, QImageCapture::NotReadyError),
- Q_ARG(QString, QPlatformImageCapture::msgCameraNotReady()));
+ invokeDeferred([this] {
+ emit error(-1, QImageCapture::NotReadyError,
+ QPlatformImageCapture::msgCameraNotReady());
+ });
qCDebug(qLcImageCaptureGst) << "error 3";
return -1;
@@ -143,15 +145,15 @@ int QGstreamerImageCapture::doCapture(const QString &fileName)
void QGstreamerImageCapture::setResolution(const QSize &resolution)
{
- auto padCaps = QGstCaps(gst_pad_get_current_caps(bin.staticPad("sink").pad()), QGstCaps::HasRef);
+ QGstCaps padCaps = bin.staticPad("sink").currentCaps();
if (padCaps.isNull()) {
qDebug() << "Camera not ready";
return;
}
- auto caps = QGstCaps(gst_caps_copy(padCaps.caps()), QGstCaps::HasRef);
- if (caps.isNull()) {
+ QGstCaps caps = padCaps.copy();
+ if (caps.isNull())
return;
- }
+
gst_caps_set_simple(caps.caps(), "width", G_TYPE_INT, resolution.width(), "height", G_TYPE_INT,
resolution.height(), nullptr);
filter.set("caps", caps);
@@ -163,12 +165,16 @@ bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer)
return false;
qCDebug(qLcImageCaptureGst) << "probe buffer";
+ QGstBufferHandle bufferHandle{
+ buffer,
+ QGstBufferHandle::NeedsRef,
+ };
+
passImage = false;
emit readyForCaptureChanged(isReadyForCapture());
- auto caps = QGstCaps(gst_pad_get_current_caps(bin.staticPad("sink").pad()), QGstCaps::HasRef);
-
+ QGstCaps caps = bin.staticPad("sink").currentCaps();
auto memoryFormat = caps.memoryFormat();
GstVideoInfo previewInfo;
@@ -178,7 +184,9 @@ bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer)
std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo);
auto *sink = m_session->gstreamerVideoSink();
- auto *gstBuffer = new QGstVideoBuffer(buffer, previewInfo, sink, fmt, memoryFormat);
+ auto *gstBuffer = new QGstVideoBuffer{
+ std::move(bufferHandle), previewInfo, sink, fmt, memoryFormat,
+ };
QVideoFrame frame(gstBuffer, fmt);
QImage img = frame.toImage();
if (img.isNull()) {
@@ -201,8 +209,7 @@ bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer)
imageData.metaData = metaData;
// ensure taginject injects this metaData
- const auto &md = static_cast<const QGstreamerMetaData &>(metaData);
- md.setMetaData(muxer.element());
+ applyMetaDataToTagSetter(metaData, muxer);
emit imageMetadataAvailable(imageData.id, metaData);
@@ -231,7 +238,8 @@ void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *ses
return;
}
- connect(m_session, &QPlatformMediaCaptureSession::cameraChanged, this, &QGstreamerImageCapture::onCameraChanged);
+ connect(m_session, &QPlatformMediaCaptureSession::cameraChanged, this,
+ &QGstreamerImageCapture::onCameraChanged);
onCameraChanged();
}
@@ -255,47 +263,42 @@ void QGstreamerImageCapture::onCameraChanged()
}
}
-gboolean QGstreamerImageCapture::saveImageFilter(GstElement *element,
- GstBuffer *buffer,
- GstPad *pad,
- void *appdata)
+gboolean QGstreamerImageCapture::saveImageFilter(GstElement *, GstBuffer *buffer, GstPad *,
+ QGstreamerImageCapture *capture)
{
- Q_UNUSED(element);
- Q_UNUSED(pad);
- QGstreamerImageCapture *capture = static_cast<QGstreamerImageCapture *>(appdata);
+ capture->saveBufferToImage(buffer);
+ return true;
+}
- capture->passImage = false;
+void QGstreamerImageCapture::saveBufferToImage(GstBuffer *buffer)
+{
+ passImage = false;
- if (capture->pendingImages.isEmpty()) {
- return true;
- }
+ if (pendingImages.isEmpty())
+ return;
- auto imageData = capture->pendingImages.dequeue();
- if (imageData.filename.isEmpty()) {
- return true;
- }
+ auto imageData = pendingImages.dequeue();
+ if (imageData.filename.isEmpty())
+ return;
qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename;
QFile f(imageData.filename);
- if (f.open(QFile::WriteOnly)) {
- GstMapInfo info;
- if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
- f.write(reinterpret_cast<const char *>(info.data), info.size);
- gst_buffer_unmap(buffer, &info);
- }
- f.close();
-
- static QMetaMethod savedSignal = QMetaMethod::fromSignal(&QGstreamerImageCapture::imageSaved);
- savedSignal.invoke(capture,
- Qt::QueuedConnection,
- Q_ARG(int, imageData.id),
- Q_ARG(QString, imageData.filename));
- } else {
+ if (!f.open(QFile::WriteOnly)) {
qCDebug(qLcImageCaptureGst) << " could not open image file for writing";
+ return;
}
- return TRUE;
+ GstMapInfo info;
+ if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
+ f.write(reinterpret_cast<const char *>(info.data), info.size);
+ gst_buffer_unmap(buffer, &info);
+ }
+ f.close();
+
+ QMetaObject::invokeMethod(this, [this, imageData = std::move(imageData)]() mutable {
+ imageSaved(imageData.id, imageData.filename);
+ });
}
QImageEncoderSettings QGstreamerImageCapture::imageSettings() const
@@ -307,9 +310,9 @@ void QGstreamerImageCapture::setImageSettings(const QImageEncoderSettings &setti
{
if (m_settings != settings) {
QSize resolution = settings.resolution();
- if (m_settings.resolution() != resolution && !resolution.isEmpty()) {
+ if (m_settings.resolution() != resolution && !resolution.isEmpty())
setResolution(resolution);
- }
+
m_settings = settings;
}
}
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h
index 3d8636cbe..79c6a02e0 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h
@@ -15,10 +15,10 @@
// We mean it.
//
-#include <private/qplatformimagecapture_p.h>
-#include <private/qmultimediautils_p.h>
+#include <QtMultimedia/private/qplatformimagecapture_p.h>
+#include <QtMultimedia/private/qmultimediautils_p.h>
-#include <qqueue.h>
+#include <QtCore/qqueue.h>
#include <common/qgst_p.h>
#include <common/qgstreamerbufferprobe_p.h>
@@ -58,7 +58,10 @@ private:
void setResolution(const QSize &resolution);
int doCapture(const QString &fileName);
- static gboolean saveImageFilter(GstElement *element, GstBuffer *buffer, GstPad *pad, void *appdata);
+ static gboolean saveImageFilter(GstElement *element, GstBuffer *buffer, GstPad *pad,
+ QGstreamerImageCapture *capture);
+
+ void saveBufferToImage(GstBuffer *buffer);
QGstreamerMediaCapture *m_session = nullptr;
int m_lastId = 0;
@@ -83,6 +86,8 @@ private:
bool passImage = false;
bool cameraActive = false;
+
+ QGObjectHandlerScopedConnection m_handoffConnection;
};
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp
index 839187a9d..720ff5603 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp
@@ -198,12 +198,12 @@ void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink)
{
gstPipeline.modifyPipelineWhileNotRunning([&] {
if (!gstVideoTee.isNull() && !videoSink.isNull()) {
- auto caps = gst_pad_get_current_caps(gstVideoTee.sink().pad());
+ QGstCaps caps = gstVideoTee.sink().currentCaps();
encoderVideoCapsFilter =
QGstElement::createFromFactory("capsfilter", "encoderVideoCapsFilter");
Q_ASSERT(encoderVideoCapsFilter);
- encoderVideoCapsFilter.set("caps", QGstCaps(caps, QGstCaps::HasRef));
+ encoderVideoCapsFilter.set("caps", caps);
gstPipeline.add(encoderVideoCapsFilter);
@@ -213,12 +213,12 @@ void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink)
}
if (!gstAudioTee.isNull() && !audioSink.isNull()) {
- auto caps = gst_pad_get_current_caps(gstAudioTee.sink().pad());
+ QGstCaps caps = gstAudioTee.sink().currentCaps();
encoderAudioCapsFilter =
QGstElement::createFromFactory("capsfilter", "encoderAudioCapsFilter");
Q_ASSERT(encoderAudioCapsFilter);
- encoderAudioCapsFilter.set("caps", QGstCaps(caps, QGstCaps::HasRef));
+ encoderAudioCapsFilter.set("caps", caps);
gstPipeline.add(encoderAudioCapsFilter);
@@ -322,6 +322,10 @@ QGstreamerVideoSink *QGstreamerMediaCapture::gstreamerVideoSink() const
return gstVideoOutput ? gstVideoOutput->gstreamerVideoSink() : nullptr;
}
+void *QGstreamerMediaCapture::nativePipeline()
+{
+ return gstPipeline.pipeline();
+}
QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h
index 6e93e8564..219773413 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h
@@ -63,6 +63,8 @@ public:
QGstreamerVideoSink *gstreamerVideoSink() const;
+ void *nativePipeline() override;
+
private:
void setCameraActive(bool activate);
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp
index 218026ede..93baa6343 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp
@@ -63,9 +63,6 @@ bool QGstreamerMediaEncoder::processBusMessage(const QGstreamerMessage &msg)
constexpr bool traceStateChange = false;
constexpr bool traceAllEvents = false;
- if (msg.isNull())
- return false;
-
if constexpr (traceAllEvents)
qCDebug(qLcMediaEncoderGst) << "received event:" << msg;
@@ -87,10 +84,12 @@ bool QGstreamerMediaEncoder::processBusMessage(const QGstreamerMessage &msg)
}
case GST_MESSAGE_ERROR: {
+ qCDebug(qLcMediaEncoderGst)
+ << "received error:" << msg.source().name() << QCompactGstMessageAdaptor(msg);
+
QUniqueGErrorHandle err;
QGString debug;
gst_message_parse_error(msg.message(), &err, &debug);
- qCDebug(qLcMediaEncoderGst) << "received error:" << msg.source().name() << err << debug;
error(QMediaRecorder::ResourceError, QString::fromUtf8(err.get()->message));
if (!m_finalizing)
stop();
@@ -99,14 +98,9 @@ bool QGstreamerMediaEncoder::processBusMessage(const QGstreamerMessage &msg)
}
case GST_MESSAGE_STATE_CHANGED: {
- if constexpr (traceStateChange) {
- GstState oldState;
- GstState newState;
- GstState pending;
- gst_message_parse_state_changed(msg.message(), &oldState, &newState, &pending);
- qCDebug(qLcMediaEncoderGst) << "received state change from" << msg.source().name()
- << oldState << newState << pending;
- }
+ if constexpr (traceStateChange)
+ qCDebug(qLcMediaEncoderGst)
+ << "received state change" << QCompactGstMessageAdaptor(msg);
return false;
}
@@ -319,7 +313,7 @@ void QGstreamerMediaEncoder::record(QMediaEncoderSettings &settings)
gstPipeline.modifyPipelineWhileNotRunning([&] {
gstPipeline.add(gstEncoder, gstFileSink);
qLinkGstElements(gstEncoder, gstFileSink);
- m_metaData.setMetaData(gstEncoder.bin());
+ applyMetaDataToTagSetter(m_metaData, gstEncoder);
m_session->linkEncoder(audioSink, videoSink);
@@ -340,6 +334,7 @@ void QGstreamerMediaEncoder::pause()
if (!m_session || m_finalizing || state() != QMediaRecorder::RecordingState)
return;
signalDurationChangedTimer.stop();
+ durationChanged(duration());
gstPipeline.dumpGraph("before-pause");
stateChanged(QMediaRecorder::PausedState);
}
@@ -357,6 +352,7 @@ void QGstreamerMediaEncoder::stop()
{
if (!m_session || m_finalizing || state() == QMediaRecorder::StoppedState)
return;
+ durationChanged(duration());
qCDebug(qLcMediaEncoderGst) << "stop";
m_finalizing = true;
m_session->unlinkEncoder();
@@ -384,7 +380,7 @@ void QGstreamerMediaEncoder::setMetaData(const QMediaMetaData &metaData)
{
if (!m_session)
return;
- m_metaData = static_cast<const QGstreamerMetaData &>(metaData);
+ m_metaData = metaData;
}
QMediaMetaData QGstreamerMediaEncoder::metaData() const
@@ -402,7 +398,8 @@ void QGstreamerMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *ses
stop();
if (m_finalizing) {
QEventLoop loop;
- loop.connect(mediaRecorder(), SIGNAL(recorderStateChanged(RecorderState)), SLOT(quit()));
+ QObject::connect(mediaRecorder(), &QMediaRecorder::recorderStateChanged, &loop,
+ &QEventLoop::quit);
loop.exec();
}
diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h
index f570f069e..637fb7264 100644
--- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h
+++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h
@@ -76,7 +76,7 @@ private:
void finalize();
QGstreamerMediaCapture *m_session = nullptr;
- QGstreamerMetaData m_metaData;
+ QMediaMetaData m_metaData;
QTimer signalDurationChangedTimer;
QGstPipeline gstPipeline;
diff --git a/src/resonance-audio/CMakeLists.txt b/src/resonance-audio/CMakeLists.txt
index 6b82e9ac5..1e967a117 100644
--- a/src/resonance-audio/CMakeLists.txt
+++ b/src/resonance-audio/CMakeLists.txt
@@ -1,7 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-if (MINGW AND CMAKE_SIZEOF_VOID_P EQUAL 4)
+if(MINGW AND CMAKE_SIZEOF_VOID_P EQUAL 4)
set(NO_SIMD_DEFINES PFFFT_SIMD_DISABLE DISABLE_SIMD)
endif()
diff --git a/tests/auto/integration/multiapp/tst_multiapp.cpp b/tests/auto/integration/multiapp/tst_multiapp.cpp
index 607b0b5e7..793a56e9d 100644
--- a/tests/auto/integration/multiapp/tst_multiapp.cpp
+++ b/tests/auto/integration/multiapp/tst_multiapp.cpp
@@ -33,37 +33,23 @@ public slots:
}
private slots:
- void mediaDevices_doesNotCrash_whenCalledWithoutApplication()
+ void mediaDevices_doesNotCrash_whenRecreatingApplication()
{
QVERIFY(executeTestOutOfProcess(
- "mediaDevices_doesNotCrash_whenCalledWithoutApplication_impl"_L1));
+ "mediaDevices_doesNotCrash_whenRecreatingApplication_impl"_L1));
}
- bool mediaDevices_doesNotCrash_whenCalledWithoutApplication_impl(int /*argc*/, char ** /*argv*/)
+ bool mediaDevices_doesNotCrash_whenRecreatingApplication_impl(int argc, char ** argv)
{
- Q_ASSERT(!qApp);
-
- QMediaDevices::defaultAudioOutput(); // Just verify that we don't crash
- return true;
- }
-
- void mediaDevices_doesNotCrash_whenCalledAfterApplicationExit()
- {
- QVERIFY(executeTestOutOfProcess(
- "mediaDevices_doesNotCrash_whenCalledAfterApplicationExit_impl"_L1));
- }
-
- bool mediaDevices_doesNotCrash_whenCalledAfterApplicationExit_impl(int argc, char **argv)
- {
- Q_ASSERT(!qApp);
-
{
QCoreApplication app{ argc, argv };
- // Create the backend bound to the lifetime of the app
+ QMediaDevices::defaultAudioOutput();
+ }
+ {
+ QCoreApplication app{ argc, argv };
QMediaDevices::defaultAudioOutput();
}
- QMediaDevices::defaultAudioOutput(); // Just verify that we don't crash
return true;
}
diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
index 0ab050ada..8b62d9811 100644
--- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
+++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
@@ -371,13 +371,13 @@ void tst_QAudioDecoderBackend::fileTest()
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.source(), *m_wavFile);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
@@ -507,8 +507,8 @@ void tst_QAudioDecoderBackend::fileTest()
buffer = d.read();
QVERIFY(buffer.isValid());
QTRY_VERIFY(!positionSpy.isEmpty());
- QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qint64(duration / 1000));
- QVERIFY(d.position() - (duration / 1000) < 20);
+ QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qlonglong(duration / 1000));
+ QCOMPARE_LT(d.position() - (duration / 1000), 20u);
duration += buffer.duration();
sampleCount += buffer.sampleCount();
@@ -521,10 +521,10 @@ void tst_QAudioDecoderBackend::fileTest()
// Resampling might end up with fewer or more samples
// so be a bit sloppy
- QVERIFY(qAbs(sampleCount - 22047) < 100);
- QVERIFY(qAbs(byteCount - 22047) < 100);
- QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
- QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
+ QCOMPARE_LT(qAbs(sampleCount - 22047), 100);
+ QCOMPARE_LT(qAbs(byteCount - 22047), 100);
+ QCOMPARE_LT(qAbs(qint64(duration) - 1000000), 20000);
+ QCOMPARE_LT(qAbs((d.position() + (buffer.duration() / 1000)) - 1000), 20);
QTRY_COMPARE(finishedSpy.size(), 1);
QVERIFY(!d.bufferAvailable());
QVERIFY(!d.isDecoding());
@@ -558,13 +558,13 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.source(), url);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
QTRY_VERIFY(!d.isDecoding());
@@ -637,13 +637,13 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.source(), url);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
QTRY_VERIFY(!d.isDecoding());
@@ -711,13 +711,13 @@ void tst_QAudioDecoderBackend::invalidSource()
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.source(), url);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
QTRY_VERIFY(!d.isDecoding());
@@ -793,13 +793,13 @@ void tst_QAudioDecoderBackend::deviceTest()
quint64 duration = 0;
int sampleCount = 0;
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
index d86044a3d..6fdfe8221 100644
--- a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
+++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
@@ -398,7 +398,7 @@ void tst_QAudioSink::stopWhileStopped()
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
audioOutput.stop();
// Check that no state transition occurred
@@ -418,7 +418,7 @@ void tst_QAudioSink::suspendWhileStopped()
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
audioOutput.suspend();
// Check that no state transition occurred
@@ -438,7 +438,7 @@ void tst_QAudioSink::resumeWhileStopped()
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
audioOutput.resume();
// Check that no state transition occurred
@@ -455,7 +455,7 @@ void tst_QAudioSink::pull()
audioOutput.setVolume(0.1f);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -514,7 +514,7 @@ void tst_QAudioSink::pullSuspendResume()
audioOutput.setVolume(0.1f);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -627,7 +627,7 @@ void tst_QAudioSink::pullResumeFromUnderrun()
AudioPullSource audioSource;
QAudioSink audioOutput(format, this);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
audioSource.open(QIODeviceBase::ReadOnly);
audioSource.available = chunkSize;
@@ -673,7 +673,7 @@ void tst_QAudioSink::push()
audioOutput.setVolume(0.1f);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -754,7 +754,7 @@ void tst_QAudioSink::pushSuspendResume()
audioOutput.setVolume(0.1f);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -922,7 +922,7 @@ void tst_QAudioSink::pushUnderrun()
audioOutput.setVolume(0.1f);
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
diff --git a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
index 3bf57e78b..ae100a08b 100644
--- a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
+++ b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
@@ -292,7 +292,7 @@ void tst_QAudioSource::stopWhileStopped()
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
audioInput.stop();
// Check that no state transition occurred
@@ -312,7 +312,7 @@ void tst_QAudioSource::suspendWhileStopped()
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
audioInput.suspend();
// Check that no state transition occurred
@@ -332,7 +332,7 @@ void tst_QAudioSource::resumeWhileStopped()
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
audioInput.resume();
// Check that no state transition occurred
@@ -347,7 +347,7 @@ void tst_QAudioSource::pull()
QAudioSource audioInput(audioFormat, this);
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -416,7 +416,7 @@ void tst_QAudioSource::pullSuspendResume()
QAudioSource audioInput(audioFormat, this);
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -518,7 +518,7 @@ void tst_QAudioSource::push()
QAudioSource audioInput(audioFormat, this);
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -609,7 +609,7 @@ void tst_QAudioSource::pushSuspendResume()
audioInput.setBufferSize(audioFormat.bytesForDuration(100000));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -735,7 +735,7 @@ void tst_QAudioSource::reset()
{
QAudioSource audioInput(audioFormat, this);
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
@@ -765,7 +765,7 @@ void tst_QAudioSource::reset()
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
// Check that we are in the default state before calling start
QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
diff --git a/tests/auto/integration/qcamerabackend/CMakeLists.txt b/tests/auto/integration/qcamerabackend/CMakeLists.txt
index f7dbd6953..07a6a4cae 100644
--- a/tests/auto/integration/qcamerabackend/CMakeLists.txt
+++ b/tests/auto/integration/qcamerabackend/CMakeLists.txt
@@ -20,7 +20,7 @@ qt_internal_add_test(tst_qcamerabackend
)
# special case begin
-if (APPLE)
+if(APPLE)
set_property(TARGET tst_qcamerabackend PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist")
set_property(TARGET tst_qcamerabackend PROPERTY PROPERTY MACOSX_BUNDLE TRUE)
endif()
diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
index 43e1a9a31..7f1cee1cc 100644
--- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
+++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
@@ -216,8 +216,8 @@ void tst_QCameraBackend::testCameraActive()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error, const QString &)));
- QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool)));
+ QSignalSpy errorSignal(&camera, &QCamera::errorOccurred);
+ QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged);
QCOMPARE(camera.isActive(), false);
@@ -280,7 +280,7 @@ void tst_QCameraBackend::testCameraFormat()
if (videoFormats.isEmpty())
QSKIP("No Camera available, skipping test.");
QCameraFormat cameraFormat = videoFormats.first();
- QSignalSpy spy(&camera, SIGNAL(cameraFormatChanged()));
+ QSignalSpy spy(&camera, &QCamera::cameraFormatChanged);
QVERIFY(spy.size() == 0);
QMediaCaptureSession session;
@@ -344,9 +344,9 @@ void tst_QCameraBackend::testCameraCapture()
QVERIFY(!imageCapture.isReadyForCapture());
- QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
- QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,const QString&)));
+ QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
+ QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred);
imageCapture.captureToFile();
QTRY_COMPARE(errorSignal.size(), 1);
@@ -403,10 +403,10 @@ void tst_QCameraBackend::testCaptureToBuffer()
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(errorOccurred(int,QImageCapture::Error,const 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());
@@ -448,8 +448,8 @@ void tst_QCameraBackend::testCameraCaptureMetadata()
camera.setFlashMode(QCamera::FlashOff);
- QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,const QMediaMetaData&)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
+ QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
camera.start();
@@ -472,9 +472,9 @@ void tst_QCameraBackend::testExposureCompensation()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy exposureCompensationSignal(&camera, SIGNAL(exposureCompensationChanged(float)));
+ QSignalSpy exposureCompensationSignal(&camera, &QCamera::exposureCompensationChanged);
- //it should be possible to set exposure parameters in Unloaded state
+ // it should be possible to set exposure parameters in Unloaded state
QCOMPARE(camera.exposureCompensation(), 0.);
if (!(camera.supportedFeatures() & QCamera::Feature::ExposureCompensation))
return;
@@ -571,10 +571,10 @@ void tst_QCameraBackend::testVideoRecording()
QMediaRecorder recorder;
session.setRecorder(&recorder);
- QSignalSpy errorSignal(camera.data(), SIGNAL(errorOccurred(QCamera::Error, const QString &)));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(QMediaRecorder::Error, const QString &)));
- QSignalSpy recorderStateChanged(&recorder, SIGNAL(recorderStateChanged(RecorderState)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy errorSignal(camera.data(), &QCamera::errorOccurred);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
recorder.setVideoResolution(320, 240);
@@ -643,10 +643,10 @@ void tst_QCameraBackend::testNativeMetadata()
QMediaRecorder recorder;
session.setRecorder(&recorder);
- QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error, const QString &)));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy recorderStateChanged(&recorder, SIGNAL(recorderStateChanged(RecorderState)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ 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()) {
@@ -687,8 +687,6 @@ void tst_QCameraBackend::testNativeMetadata()
QVERIFY(!fileName.isEmpty());
QVERIFY(QFileInfo(fileName).size() > 0);
- QSKIP_GSTREAMER("QTBUG-124182: spurious failure while retrieving the metadata");
-
// 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;
@@ -700,16 +698,17 @@ void tst_QCameraBackend::testNativeMetadata()
player.setSource(QUrl::fromLocalFile(fileName));
player.play();
- QTRY_VERIFY(metadataChangedSpy.size() > 0);
+ int metadataChangedRequiredCount = isGStreamerPlatform() ? 2 : 1;
+
+ QTRY_VERIFY(metadataChangedSpy.size() >= metadataChangedRequiredCount);
- QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), metaData.value(QMediaMetaData::Title).toString());
+ 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());
- metadataChangedSpy.clear();
-
player.stop();
player.setSource({});
QFile(fileName).remove();
diff --git a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp
index d22d0a3df..e8376d54c 100644
--- a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp
+++ b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp
@@ -104,8 +104,8 @@ void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session)
QMediaRecorder recorder;
session.setRecorder(&recorder);
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
recorder.record();
QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000);
@@ -124,7 +124,7 @@ void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session)
void tst_QMediaCaptureSession::recordFail(QMediaCaptureSession &session)
{
QMediaRecorder recorder;
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
session.setRecorder(&recorder);
recorder.record();
@@ -292,7 +292,7 @@ void tst_QMediaCaptureSession::record_video_without_preview()
session.setRecorder(&recorder);
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
camera.setActive(true);
@@ -317,8 +317,8 @@ void tst_QMediaCaptureSession::can_add_and_remove_AudioInput_with_and_without_Au
QSKIP("No audio input available");
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
- QSignalSpy audioOutputChanged(&session, SIGNAL(audioOutputChanged()));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
@@ -349,10 +349,10 @@ void tst_QMediaCaptureSession::can_change_AudioDevices_on_attached_AudioInput()
QSKIP("Two audio inputs are not available");
QAudioInput input(audioInputs[0]);
- QSignalSpy deviceChanged(&input, SIGNAL(deviceChanged()));
+ QSignalSpy deviceChanged(&input, &QAudioInput::deviceChanged);
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
@@ -384,9 +384,9 @@ void tst_QMediaCaptureSession::can_change_AudioInput_during_recording()
session.setRecorder(&recorder);
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
@@ -418,7 +418,7 @@ void tst_QMediaCaptureSession::disconnects_deleted_AudioInput()
QSKIP("No audio input available");
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
{
QAudioInput input;
session.setAudioInput(&input);
@@ -435,13 +435,13 @@ void tst_QMediaCaptureSession::can_move_AudioInput_between_sessions()
QMediaCaptureSession session0;
QMediaCaptureSession session1;
- QSignalSpy audioInputChanged0(&session0, SIGNAL(audioInputChanged()));
- QSignalSpy audioInputChanged1(&session1, SIGNAL(audioInputChanged()));
+ QSignalSpy audioInputChanged0(&session0, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy audioInputChanged1(&session1, &QMediaCaptureSession::audioInputChanged);
QAudioInput input;
{
QMediaCaptureSession session2;
- QSignalSpy audioInputChanged2(&session2, SIGNAL(audioInputChanged()));
+ QSignalSpy audioInputChanged2(&session2, &QMediaCaptureSession::audioInputChanged);
session2.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged2.size(), 1);
}
@@ -462,7 +462,7 @@ void tst_QMediaCaptureSession::disconnects_deleted_AudioOutput()
QSKIP("No audio output available");
QMediaCaptureSession session;
- QSignalSpy audioOutputChanged(&session, SIGNAL(audioOutputChanged()));
+ QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
{
QAudioOutput output;
session.setAudioOutput(&output);
@@ -482,13 +482,13 @@ void tst_QMediaCaptureSession::can_move_AudioOutput_between_sessions_and_player(
QMediaCaptureSession session0;
QMediaCaptureSession session1;
QMediaPlayer player;
- QSignalSpy audioOutputChanged0(&session0, SIGNAL(audioOutputChanged()));
- QSignalSpy audioOutputChanged1(&session1, SIGNAL(audioOutputChanged()));
- QSignalSpy audioOutputChangedPlayer(&player, SIGNAL(audioOutputChanged()));
+ QSignalSpy audioOutputChanged0(&session0, &QMediaCaptureSession::audioOutputChanged);
+ QSignalSpy audioOutputChanged1(&session1, &QMediaCaptureSession::audioOutputChanged);
+ QSignalSpy audioOutputChangedPlayer(&player, &QMediaPlayer::audioOutputChanged);
{
QMediaCaptureSession session2;
- QSignalSpy audioOutputChanged2(&session2, SIGNAL(audioOutputChanged()));
+ QSignalSpy audioOutputChanged2(&session2, &QMediaCaptureSession::audioOutputChanged);
session2.setAudioOutput(&output);
QTRY_COMPARE(audioOutputChanged2.size(), 1);
}
@@ -531,7 +531,7 @@ void tst_QMediaCaptureSession::can_add_and_remove_Camera()
session.setRecorder(&recorder);
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
camera.setActive(true);
@@ -552,13 +552,13 @@ void tst_QMediaCaptureSession::can_move_Camera_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
- QSignalSpy cameraChanged0(&session0, SIGNAL(cameraChanged()));
- QSignalSpy cameraChanged1(&session1, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged0(&session0, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy cameraChanged1(&session1, &QMediaCaptureSession::cameraChanged);
{
QCamera camera;
{
QMediaCaptureSession session2;
- QSignalSpy cameraChanged2(&session2, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged2(&session2, &QMediaCaptureSession::cameraChanged);
session2.setCamera(&camera);
QTRY_COMPARE(cameraChanged2.size(), 1);
}
@@ -592,9 +592,9 @@ void tst_QMediaCaptureSession::can_disconnect_Camera_when_recording()
session.setRecorder(&recorder);
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
camera.setActive(true);
session.setCamera(&camera);
@@ -634,7 +634,7 @@ void tst_QMediaCaptureSession::can_add_and_remove_different_Cameras()
session.setRecorder(&recorder);
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
camera.setActive(true);
session.setCamera(&camera);
@@ -667,8 +667,8 @@ void tst_QMediaCaptureSession::can_change_CameraDevice_on_attached_Camera()
session.setRecorder(&recorder);
- QSignalSpy cameraDeviceChanged(&camera, SIGNAL(cameraDeviceChanged()));
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy cameraDeviceChanged(&camera, &QCamera::cameraDeviceChanged);
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
@@ -704,8 +704,8 @@ void tst_QMediaCaptureSession::can_change_VideoOutput_with_and_without_camera()
QMediaCaptureSession session;
- QSignalSpy videoOutputChanged(&session, SIGNAL(videoOutputChanged()));
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged);
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
@@ -740,10 +740,10 @@ void tst_QMediaCaptureSession::can_change_VideoOutput_when_recording()
session.setRecorder(&recorder);
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
- QSignalSpy videoOutputChanged(&session, SIGNAL(videoOutputChanged()));
+ 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);
@@ -783,8 +783,8 @@ void tst_QMediaCaptureSession::can_add_and_remove_recorders()
QMediaRecorder recorder2;
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
- QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged()));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
@@ -806,13 +806,13 @@ void tst_QMediaCaptureSession::can_move_Recorder_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
- QSignalSpy recorderChanged0(&session0, SIGNAL(recorderChanged()));
- QSignalSpy recorderChanged1(&session1, SIGNAL(recorderChanged()));
+ QSignalSpy recorderChanged0(&session0, &QMediaCaptureSession::recorderChanged);
+ QSignalSpy recorderChanged1(&session1, &QMediaCaptureSession::recorderChanged);
{
QMediaRecorder recorder;
{
QMediaCaptureSession session2;
- QSignalSpy recorderChanged2(&session2, SIGNAL(recorderChanged()));
+ QSignalSpy recorderChanged2(&session2, &QMediaCaptureSession::recorderChanged);
session2.setRecorder(&recorder);
QTRY_COMPARE(recorderChanged2.size(), 1);
}
@@ -849,7 +849,7 @@ void tst_QMediaCaptureSession::can_record_AudioInput_with_null_AudioDevice()
QAudioInput input(nullDevice);
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
session.setAudioInput(&input);
QTRY_COMPARE(audioInputChanged.size(), 1);
@@ -867,7 +867,7 @@ void tst_QMediaCaptureSession::can_record_Camera_with_null_CameraDevice()
QCamera camera(nullDevice);
QMediaCaptureSession session;
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
session.setCamera(&camera);
QTRY_COMPARE(cameraChanged.size(), 1);
@@ -888,10 +888,10 @@ void tst_QMediaCaptureSession::recording_stops_when_recorder_removed()
QMediaRecorder recorder;
QMediaCaptureSession session;
- QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged()));
- QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged()));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ 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);
@@ -925,9 +925,9 @@ void tst_QMediaCaptureSession::can_add_and_remove_ImageCapture()
QImageCapture capture;
QMediaCaptureSession session;
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
- QSignalSpy imageCaptureChanged(&session, SIGNAL(imageCaptureChanged()));
- QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool)));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy imageCaptureChanged(&session, &QMediaCaptureSession::imageCaptureChanged);
+ QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
QVERIFY(!capture.isAvailable());
QVERIFY(!capture.isReadyForCapture());
@@ -967,13 +967,13 @@ void tst_QMediaCaptureSession::can_move_ImageCapture_between_sessions()
{
QMediaCaptureSession session0;
QMediaCaptureSession session1;
- QSignalSpy imageCaptureChanged0(&session0, SIGNAL(imageCaptureChanged()));
- QSignalSpy imageCaptureChanged1(&session1, SIGNAL(imageCaptureChanged()));
+ QSignalSpy imageCaptureChanged0(&session0, &QMediaCaptureSession::imageCaptureChanged);
+ QSignalSpy imageCaptureChanged1(&session1, &QMediaCaptureSession::imageCaptureChanged);
{
QImageCapture imageCapture;
{
QMediaCaptureSession session2;
- QSignalSpy imageCaptureChanged2(&session2, SIGNAL(imageCaptureChanged()));
+ QSignalSpy imageCaptureChanged2(&session2, &QMediaCaptureSession::imageCaptureChanged);
session2.setImageCapture(&imageCapture);
QTRY_COMPARE(imageCaptureChanged2.size(), 1);
}
@@ -1006,9 +1006,9 @@ void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null()
QImageCapture capture;
QMediaCaptureSession session;
- QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged()));
- QSignalSpy capturedSignal(&capture, SIGNAL(imageCaptured(int,QImage)));
- QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool)));
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy capturedSignal(&capture, &QImageCapture::imageCaptured);
+ QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
session.setImageCapture(&capture);
session.setCamera(&camera);
@@ -1043,12 +1043,12 @@ void tst_QMediaCaptureSession::can_add_ImageCapture_and_capture_during_recording
QMediaCaptureSession session;
QMediaRecorder recorder;
- QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged()));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &)));
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
- QSignalSpy imageCaptureChanged(&session, SIGNAL(imageCaptureChanged()));
- QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool)));
- QSignalSpy capturedSignal(&capture, SIGNAL(imageCaptured(int,QImage)));
+ 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);
@@ -1100,7 +1100,7 @@ void tst_QMediaCaptureSession::testAudioMute()
recorder.setOutputLocation(QStringLiteral("test"));
QSignalSpy spy(&audioInput, &QAudioInput::mutedChanged);
- QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
QMediaFormat format;
format.setAudioCodec(QMediaFormat::AudioCodec::Wave);
diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
index 52e89a2be..510122a94 100644
--- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
+++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
@@ -37,6 +37,8 @@
QT_USE_NAMESPACE
+using namespace Qt::Literals;
+
namespace {
static qreal colorDifference(QRgb first, QRgb second)
{
@@ -55,7 +57,7 @@ It findSimilarColor(It it, It end, QRgb color)
}
template <typename Colors>
-size_t findSimilarColorIndex(const Colors &colors, QRgb color)
+auto findSimilarColorIndex(const Colors &colors, QRgb color)
{
return std::distance(std::begin(colors),
findSimilarColor(std::begin(colors), std::end(colors), color));
@@ -117,6 +119,8 @@ private slots:
void playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl();
void play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream();
void play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames();
+ void play_startsPlayback_withAndWithoutOutputsConnected();
+ void play_startsPlayback_withAndWithoutOutputsConnected_data();
void stop_entersStoppedState_whenPlayerWasPaused();
void stop_setsPositionToZero_afterPlayingToEndOfMedia();
@@ -533,6 +537,9 @@ void tst_QMediaPlayerBackend::setSource_changesSourceAndMediaStatus_whenCalledWi
expectedState.source = *m_localVideoFile;
expectedState.mediaStatus = QMediaPlayer::LoadingMedia;
+ if (isGStreamerPlatform()) // gstreamer synchronously identifies file streams as seekable
+ expectedState.isSeekable = true;
+
MediaPlayerState actualState{ m_fixture->player };
COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
@@ -568,6 +575,9 @@ void tst_QMediaPlayerBackend::setSource_updatesExpectedAttributes_whenMediaHasLo
expectedState.isSeekable = true;
expectedState.metaData = std::nullopt; // Don't compare
+ if (isGStreamerPlatform())
+ expectedState.bufferProgress = std::nullopt; // QTBUG-124633: can change before play()
+
MediaPlayerState actualState{ m_fixture->player };
COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
@@ -603,7 +613,6 @@ void tst_QMediaPlayerBackend::setSource_stopsAndEntersErrorState_whenPlayerWasPl
QCOMPARE(m_fixture->framesCount, savedFramesCount);
}
-
void tst_QMediaPlayerBackend::setSource_loadsAudioTrack_whenCalledWithValidWavFile()
{
CHECK_SELECTED_URL(m_localWavFile);
@@ -978,14 +987,14 @@ void tst_QMediaPlayerBackend::play_setsPlaybackStateAndMediaStatus_whenValidFile
m_fixture->player.play();
QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } }));
- QTRY_COMPARE(m_fixture->mediaStatusChanged,
- SignalList({ { QMediaPlayer::LoadingMedia },
- { QMediaPlayer::LoadedMedia },
- { QMediaPlayer::BufferingMedia },
- { QMediaPlayer::BufferedMedia } }));
+ QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia } }));
QTRY_COMPARE_GT(m_fixture->bufferProgressChanged.size(), 0);
QTRY_COMPARE_NE(m_fixture->bufferProgressChanged.front().front(), 0.f);
@@ -1023,11 +1032,15 @@ void tst_QMediaPlayerBackend::play_doesNotEnterMediaLoadingState_whenResumingPla
// Assert
QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferingMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
QCOMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } }));
// Note: Should not go through Loading again when play -> stop -> play
- QCOMPARE_EQ(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::BufferingMedia } }));
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ }));
}
void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl()
@@ -1131,26 +1144,79 @@ void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLong
{
CHECK_SELECTED_URL(m_oneRedFrameVideo);
+ m_fixture->surface.setStoreFrames(true);
+
m_fixture->player.setSource(*m_oneRedFrameVideo);
m_fixture->player.play();
- auto firstFrame = m_fixture->surface.waitForFrame();
- QVERIFY(firstFrame.isValid());
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 0);
+ QVERIFY(m_fixture->surface.m_frameList.front().isValid());
QElapsedTimer timer;
timer.start();
- auto endFrame = m_fixture->surface.waitForFrame();
- QVERIFY(!endFrame.isValid());
-
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 1);
const auto elapsed = timer.elapsed();
- // 1000 is expected
- QCOMPARE_GT(elapsed, 900);
- QCOMPARE_LT(elapsed, 1400);
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer timing seems to be off
+
+ // 1000 is expected
+ QCOMPARE_GT(elapsed, 900);
+ QCOMPARE_LT(elapsed, 1400);
+ }
QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
QCOMPARE(m_fixture->surface.m_totalFrames, 2);
+ QVERIFY(!m_fixture->surface.m_frameList.back().isValid());
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected()
+{
+ QSKIP_GSTREAMER("QTBUG-124501: Fails with gstreamer");
+
+ QFETCH(const bool, audioConnected);
+ QFETCH(const bool, videoConnected);
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ if (!videoConnected && !audioConnected)
+ QSKIP_FFMPEG("FFMPEG backend playback fails when no output is connected");
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ if (!audioConnected)
+ m_fixture->player.setAudioOutput(nullptr);
+
+ if (!videoConnected)
+ m_fixture->player.setVideoOutput(nullptr);
+
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QTRY_VERIFY(!m_fixture->mediaStatusChanged.empty()
+ && m_fixture->mediaStatusChanged.back()
+ == QList<QVariant>{ QMediaPlayer::EndOfMedia });
+
+ QTRY_COMPARE_EQ(m_fixture->playbackStateChanged,
+ SignalList({
+ { QMediaPlayer::PlayingState },
+ { QMediaPlayer::StoppedState },
+ }));
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected_data()
+{
+ QTest::addColumn<bool>("videoConnected");
+ QTest::addColumn<bool>("audioConnected");
+
+ QTest::addRow("all connected") << true << true;
+ QTest::addRow("video connected") << true << false;
+ QTest::addRow("audio connected") << false << true;
+ QTest::addRow("no output connected") << false << false;
}
void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused()
@@ -1175,7 +1241,10 @@ void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused()
QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::StoppedState } }));
// it's allowed to emit statusChanged() signal async
QTRY_COMPARE(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadedMedia } }));
- QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } }));
+
+ if (!isGStreamerPlatform())
+ // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages
+ QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } }));
QTRY_COMPARE(m_fixture->player.position(), qint64(0));
QTRY_VERIFY(!m_fixture->positionChanged.empty());
@@ -1219,9 +1288,17 @@ void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal_
QTest::addRow("Increase") << 1.0f << 2.0f << 2.0f << true;
QTest::addRow("Decrease") << 1.0f << 0.5f << 0.5f << true;
QTest::addRow("Keep") << 0.5f << 0.5f << 0.5f << false;
- QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << 0.0f << true;
- QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << 0.0f << false;
+ bool backendSupportsNegativePlayback =
+ isWindowsPlatform() || isDarwinPlatform() || isGStreamerPlatform();
+
+ if (backendSupportsNegativePlayback) {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << -0.5f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << -0.6f << true;
+ } else {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << 0.0f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << 0.0f << false;
+ }
}
void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal()
@@ -1368,10 +1445,13 @@ void tst_QMediaPlayerBackend::processEOS()
QCOMPARE(m_fixture->playbackStateChanged.size(), 2);
QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
- QCOMPARE_GT(m_fixture->bufferProgressChanged.size(), 1);
- QCOMPARE(m_fixture->bufferProgressChanged.back().front(), 0.f);
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages
+ QCOMPARE_GT(m_fixture->bufferProgressChanged.size(), 1);
+ QCOMPARE(m_fixture->bufferProgressChanged.back().front(), 0.f);
+ }
- //position stays at the end of file
+ // position stays at the end of file
QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
QTRY_COMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), m_fixture->player.duration());
@@ -1462,9 +1542,9 @@ void tst_QMediaPlayerBackend::deleteLaterAtEOS()
// QTRY_VERIFY or QTest::qWait. QTest::qWait makes extra effort to process
// DeferredDelete events during the wait, which interferes with this test.
QEventLoop loop;
- QTimer::singleShot(0, &deleter, SLOT(play()));
- QTimer::singleShot(5000, &loop, SLOT(quit()));
- connect(player.data(), SIGNAL(destroyed()), &loop, SLOT(quit()));
+ QTimer::singleShot(0, &deleter, &DeleteLaterAtEos::play);
+ QTimer::singleShot(5000, &loop, &QEventLoop::quit);
+ connect(player.data(), &QObject::destroyed, &loop, &QEventLoop::quit);
loop.exec();
// Verify that the player was destroyed within the event loop.
// This check will fail without the fix for QTBUG-24927.
@@ -1571,7 +1651,7 @@ void tst_QMediaPlayerBackend::seekPauseSeek()
player.setAudioOutput(&output);
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
player.setVideoOutput(&surface);
@@ -1647,8 +1727,8 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
player.setAudioOutput(&output);
player.setVideoOutput(&surface);
- QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy stateSpy(&player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
player.setSource(*m_localVideoFile);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
@@ -1777,7 +1857,7 @@ void tst_QMediaPlayerBackend::subsequentPlayback()
player.play();
QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
- QTRY_VERIFY(player.position() > 1000);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
// make sure position does not "jump" closer to the end of the file
@@ -1787,10 +1867,10 @@ void tst_QMediaPlayerBackend::subsequentPlayback()
QTRY_COMPARE(player.position(), qint64(0));
player.play();
QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
- QTRY_VERIFY(player.position() > 1000);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
- QVERIFY(player.position() > 1000);
+ QCOMPARE_GT(player.position(), 1000);
}
void tst_QMediaPlayerBackend::multipleMediaPlayback()
@@ -1959,7 +2039,7 @@ void tst_QMediaPlayerBackend::multipleSeekStressTest()
};
auto seekAndCheck = [&](qint64 pos) {
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
player.setPosition(pos);
QTRY_VERIFY(positionSpy.size() >= 1);
@@ -2227,9 +2307,9 @@ void tst_QMediaPlayerBackend::playFromBuffer()
TestVideoSink surface(false);
QMediaPlayer player;
player.setVideoOutput(&surface);
- QFile file(m_localVideoFile->toLocalFile());
- if (!file.open(QIODevice::ReadOnly))
- QSKIP("Could not open file");
+ QFile file(u":"_s + m_localVideoFile->toEncoded(QUrl::RemoveScheme));
+ QVERIFY(file.open(QIODevice::ReadOnly));
+
player.setSourceDevice(&file, *m_localVideoFile);
player.play();
QTRY_VERIFY(player.position() >= 1000);
@@ -2243,8 +2323,8 @@ void tst_QMediaPlayerBackend::audioVideoAvailable()
TestVideoSink surface(false);
QAudioOutput output;
QMediaPlayer player;
- QSignalSpy hasVideoSpy(&player, SIGNAL(hasVideoChanged(bool)));
- QSignalSpy hasAudioSpy(&player, SIGNAL(hasAudioChanged(bool)));
+ QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged);
+ QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged);
player.setVideoOutput(&surface);
player.setAudioOutput(&output);
player.setSource(*m_localVideoFile);
@@ -2753,10 +2833,15 @@ void tst_QMediaPlayerBackend::cleanSinkAndNoMoreFramesAfterStop()
for (int i = 0; i < 8; ++i) {
player.play();
QTRY_VERIFY(framesCount > 0);
+ QVERIFY(sink.videoFrame().isValid());
player.stop();
- QVERIFY(!sink.videoFrame().isValid());
+ if (isGStreamerPlatform())
+ // QTBUG-124005: stop() is asynchronous in gstreamer
+ QTRY_VERIFY(!sink.videoFrame().isValid());
+ else
+ QVERIFY(!sink.videoFrame().isValid());
QCOMPARE_NE(framesCount, 0);
framesCount = 0;
@@ -2806,6 +2891,9 @@ void tst_QMediaPlayerBackend::lazyLoadVideo()
void tst_QMediaPlayerBackend::videoSinkSignals()
{
+ std::atomic<int> videoFrameCounter = 0;
+ std::atomic<int> videoSizeCounter = 0;
+
// TODO: come up with custom frames source,
// create the test target tst_QVideoSinkBackend,
// and move the test there
@@ -2816,9 +2904,6 @@ void tst_QMediaPlayerBackend::videoSinkSignals()
QMediaPlayer player;
player.setVideoSink(&sink);
- std::atomic<int> videoFrameCounter = 0;
- std::atomic<int> videoSizeCounter = 0;
-
player.setSource(*m_localVideoFile2);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
diff --git a/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp b/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp
index 73f9fd123..522d9bcdd 100644
--- a/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp
+++ b/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp
@@ -248,7 +248,7 @@ void tst_QScreenCaptureBackend::capture(QTestWidget &widget, const QPoint &drawi
delay / static_cast<int>(1000 / std::min(widget.screen()->refreshRate(), 60.));
const int framesCount = static_cast<int>(sink.images().size());
QCOMPARE_LE(framesCount, expectedFramesCount + 2);
- QCOMPARE_GE(framesCount, expectedFramesCount / 2);
+ QCOMPARE_GE(framesCount, 1);
for (const auto &image : sink.images()) {
auto pixelColor = [&drawingOffset, pixelRatio, &image](int x, int y) {
@@ -409,7 +409,7 @@ void tst_QScreenCaptureBackend::capture_capturesToFile_whenConnectedToMediaRecor
sc.setActive(true);
- QTest::qWait(200); // wait a bit for SC threading activating
+ QTest::qWait(1000); // wait a bit for SC threading activating
{
QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
@@ -420,9 +420,9 @@ void tst_QScreenCaptureBackend::capture_capturesToFile_whenConnectedToMediaRecor
QCOMPARE(recorder.recorderState(), QMediaRecorder::RecordingState);
}
- QTest::qWait(300);
+ QTest::qWait(1000);
widget->setColors(QColor(0, 0xFF, 0), QColor(0, 0xFF, 0)); // Change widget color
- QTest::qWait(300);
+ QTest::qWait(1000);
{
QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
@@ -440,9 +440,11 @@ void tst_QScreenCaptureBackend::capture_capturesToFile_whenConnectedToMediaRecor
TestVideoSink sink;
QMediaPlayer player;
player.setSource(fileName);
- QCOMPARE_EQ(player.metaData().value(QMediaMetaData::Resolution).toSize(), QSize(videoResolution));
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE_EQ(player.metaData().value(QMediaMetaData::Resolution).toSize(),
+ QSize(videoResolution));
QCOMPARE_GT(player.duration(), 350);
- QCOMPARE_LT(player.duration(), 650);
+ QCOMPARE_LT(player.duration(), 3000);
// Convert video frames to QImages
player.setVideoSink(&sink);
diff --git a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
index 6b2871a38..24a6365a8 100644
--- a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
+++ b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
@@ -89,7 +89,7 @@ void tst_QSoundEffect::initTestCase()
void tst_QSoundEffect::testSource()
{
- QSignalSpy readSignal(sound, SIGNAL(sourceChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::sourceChanged);
sound->setSource(url);
sound->setVolume(0.1f);
@@ -108,8 +108,8 @@ void tst_QSoundEffect::testLooping()
sound->setSource(url);
QTRY_COMPARE(sound->status(), QSoundEffect::Ready);
- QSignalSpy readSignal_Count(sound, SIGNAL(loopCountChanged()));
- QSignalSpy readSignal_Remaining(sound, SIGNAL(loopsRemainingChanged()));
+ QSignalSpy readSignal_Count(sound, &QSoundEffect::loopCountChanged);
+ QSignalSpy readSignal_Remaining(sound, &QSoundEffect::loopsRemainingChanged);
sound->setLoopCount(3);
sound->setVolume(0.1f);
@@ -195,7 +195,7 @@ void tst_QSoundEffect::testLooping()
void tst_QSoundEffect::testVolume()
{
- QSignalSpy readSignal(sound, SIGNAL(volumeChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::volumeChanged);
sound->setVolume(0.5);
QCOMPARE(sound->volume(),0.5);
@@ -205,7 +205,7 @@ void tst_QSoundEffect::testVolume()
void tst_QSoundEffect::testMuting()
{
- QSignalSpy readSignal(sound, SIGNAL(mutedChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::mutedChanged);
sound->setMuted(true);
QCOMPARE(sound->isMuted(),true);
@@ -375,7 +375,7 @@ void tst_QSoundEffect::testSupportedMimeTypes()
void tst_QSoundEffect::testCorruptFile()
{
for (int i = 0; i < 10; i++) {
- QSignalSpy statusSpy(sound, SIGNAL(statusChanged()));
+ QSignalSpy statusSpy(sound, &QSoundEffect::statusChanged);
sound->setSource(urlCorrupted);
QVERIFY(!sound->isPlaying());
QVERIFY(sound->status() == QSoundEffect::Loading || sound->status() == QSoundEffect::Error);
diff --git a/tests/auto/integration/shared/mediabackendutils.h b/tests/auto/integration/shared/mediabackendutils.h
index f146eddb4..3279f7044 100644
--- a/tests/auto/integration/shared/mediabackendutils.h
+++ b/tests/auto/integration/shared/mediabackendutils.h
@@ -22,12 +22,28 @@ inline bool isAndroidPlatform()
return QPlatformMediaIntegration::instance()->name() == "android";
}
+inline bool isFFMPEGPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "ffmpeg";
+}
+
+inline bool isWindowsPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "windows";
+}
+
#define QSKIP_GSTREAMER(message) \
do { \
if (isGStreamerPlatform()) \
QSKIP(message); \
} while (0)
+#define QSKIP_FFMPEG(message) \
+ do { \
+ if (isFFMPEGPlatform()) \
+ QSKIP(message); \
+ } while (0)
+
#define QEXPECT_FAIL_GSTREAMER(dataIndex, comment, mode) \
do { \
if (isGStreamerPlatform()) \
diff --git a/tests/auto/integration/shared/mediafileselector.h b/tests/auto/integration/shared/mediafileselector.h
index 68107747b..e36677f34 100644
--- a/tests/auto/integration/shared/mediafileselector.h
+++ b/tests/auto/integration/shared/mediafileselector.h
@@ -28,7 +28,7 @@ using MaybeUrl = QMaybe<QUrl, QString>;
class MediaFileSelector
{
public:
- quint32 failedSelectionsCount() const { return m_failedSelectionsCount; }
+ int failedSelectionsCount() const { return m_failedSelectionsCount; }
QString dumpErrors() const
{
@@ -92,6 +92,9 @@ private:
static MaybeUrl selectMediaFile(QString media)
{
+ if (qEnvironmentVariableIsSet("QTEST_SKIP_MEDIA_VALIDATION"))
+ return QUrl(media);
+
using namespace Qt::StringLiterals;
QAudioOutput audioOutput;
@@ -156,7 +159,7 @@ private:
std::unordered_map<QString, std::unique_ptr<QTemporaryFile>> m_nativeFiles;
#endif
std::unordered_map<QString, QString> m_mediaToErrors;
- quint32 m_failedSelectionsCount = 0;
+ int m_failedSelectionsCount = 0;
};
QT_END_NAMESPACE
diff --git a/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp b/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp
index 700bf4486..3c6b940a9 100644
--- a/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp
+++ b/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp
@@ -60,7 +60,7 @@ void QMockAudioDecoder::start()
setIsDecoding(true);
durationChanged(duration());
- QTimer::singleShot(50, this, SLOT(pretendDecode()));
+ QTimer::singleShot(50, this, &QMockAudioDecoder::pretendDecode);
} else {
error(QAudioDecoder::ResourceError, "No source set");
}
@@ -92,7 +92,7 @@ QAudioBuffer QMockAudioDecoder::read()
if (mBuffers.isEmpty() && mSerial >= MOCK_DECODER_MAX_BUFFERS) {
finished();
} else
- QTimer::singleShot(50, this, SLOT(pretendDecode()));
+ QTimer::singleShot(50, this, &QMockAudioDecoder::pretendDecode);
}
return a;
diff --git a/tests/auto/unit/mockbackend/qmockimagecapture.cpp b/tests/auto/unit/mockbackend/qmockimagecapture.cpp
index 74ab08e59..96e53b2f4 100644
--- a/tests/auto/unit/mockbackend/qmockimagecapture.cpp
+++ b/tests/auto/unit/mockbackend/qmockimagecapture.cpp
@@ -25,7 +25,7 @@ int QMockImageCapture::capture(const QString &fileName)
m_fileName = fileName;
m_captureRequest++;
emit readyForCaptureChanged(m_ready = false);
- QTimer::singleShot(5, this, SLOT(captured()));
+ QTimer::singleShot(5, this, &QMockImageCapture::captured);
return m_captureRequest;
} else {
emit error(-1, QImageCapture::NotReadyError,
diff --git a/tests/auto/unit/multimedia/CMakeLists.txt b/tests/auto/unit/multimedia/CMakeLists.txt
index c6663a800..598bad5ad 100644
--- a/tests/auto/unit/multimedia/CMakeLists.txt
+++ b/tests/auto/unit/multimedia/CMakeLists.txt
@@ -30,3 +30,9 @@ add_subdirectory(qmediadevices)
add_subdirectory(qerrorinfo)
add_subdirectory(qvideobuffers)
add_subdirectory(qwavedecoder)
+
+if(QT_FEATURE_gstreamer)
+ add_subdirectory(gstreamer_backend)
+ add_subdirectory(qmediacapture_gstreamer)
+ add_subdirectory(qmediaplayer_gstreamer)
+endif()
diff --git a/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt b/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt
new file mode 100644
index 000000000..6d52d09e1
--- /dev/null
+++ b/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_gstreamer_backend Test:
+#####################################################################
+
+qt_internal_add_test(tst_gstreamer_backend
+ SOURCES
+ tst_gstreamer_backend.cpp
+ tst_gstreamer_backend.h
+ LIBRARIES
+ Qt::MultimediaPrivate
+ Qt::QGstreamerMediaPluginPrivate
+)
diff --git a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp
new file mode 100644
index 000000000..d51962076
--- /dev/null
+++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tst_gstreamer_backend.h"
+
+#include <QtTest/QtTest>
+#include <QtQGstreamerMediaPlugin/private/qgstreamermetadata_p.h>
+#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h>
+
+QT_USE_NAMESPACE
+
+using namespace Qt::Literals;
+
+QGstTagListHandle tst_GStreamer::parseTagList(const char *str)
+{
+ QGstTagListHandle tagList{
+ gst_tag_list_new_from_string(str),
+ QGstTagListHandle::NeedsRef,
+ };
+ return tagList;
+}
+
+QGstTagListHandle tst_GStreamer::parseTagList(const QByteArray &ba)
+{
+ return parseTagList(ba.constData());
+}
+
+void tst_GStreamer::metadata_taglistToMetaData()
+{
+ QGstTagListHandle tagList = parseTagList(R"(taglist, title="My Video", comment="yada")");
+
+ QMediaMetaData parsed = taglistToMetaData(tagList);
+
+ QCOMPARE(parsed.stringValue(QMediaMetaData::Title), u"My Video"_s);
+ QCOMPARE(parsed.stringValue(QMediaMetaData::Comment), u"yada"_s);
+}
+
+void tst_GStreamer::metadata_taglistToMetaData_extractsOrientation()
+{
+ QFETCH(QByteArray, taglist);
+ QFETCH(QtVideo::Rotation, rotation);
+
+ QGstTagListHandle tagList = parseTagList(taglist);
+ QMediaMetaData parsed = taglistToMetaData(tagList);
+ QCOMPARE(parsed[QMediaMetaData::Orientation].value<QtVideo::Rotation>(), rotation);
+}
+
+void tst_GStreamer::metadata_taglistToMetaData_extractsOrientation_data()
+{
+ QTest::addColumn<QByteArray>("taglist");
+ QTest::addColumn<QtVideo::Rotation>("rotation");
+
+ QTest::newRow("no rotation") << R"(taglist, title="My Video", comment="yada")"_ba
+ << QtVideo::Rotation::None;
+ QTest::newRow("90 degree")
+ << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-90)"_ba
+ << QtVideo::Rotation::Clockwise90;
+ QTest::newRow("180 degree")
+ << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-180)"_ba
+ << QtVideo::Rotation::Clockwise180;
+ QTest::newRow("270 degree")
+ << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-270)"_ba
+ << QtVideo::Rotation::Clockwise270;
+}
+
+void tst_GStreamer::metadata_taglistToMetaData_extractsDuration()
+{
+ QGstTagListHandle tagList = parseTagList(
+ R"__(taglist, video-codec=(string)"On2\ VP9", container-specific-track-id=(string)1, extended-comment=(string){ "ALPHA_MODE\=1", "HANDLER_NAME\=Apple\ Video\ Media\ Handler", "VENDOR_ID\=appl", "TIMECODE\=00:00:00:00", "DURATION\=00:00:00.400000000" }, encoder=(string)"Lavc59.37.100\ libvpx-vp9")__");
+
+ QMediaMetaData parsed = taglistToMetaData(tagList.get());
+
+ QEXPECT_FAIL("", "duration in extended comment", Continue);
+ QCOMPARE(parsed[QMediaMetaData::Duration].value<int>(), 400);
+}
+
+QTEST_GUILESS_MAIN(tst_GStreamer)
+
+#include "moc_tst_gstreamer_backend.cpp"
diff --git a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h
new file mode 100644
index 000000000..7b118703f
--- /dev/null
+++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TST_GSTREAMER_BACKEND_H
+#define TST_GSTREAMER_BACKEND_H
+
+#include <QtTest/QtTest>
+
+#include <QtQGstreamerMediaPlugin/private/qgstreamerintegration_p.h>
+#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h>
+
+QT_USE_NAMESPACE
+
+class tst_GStreamer : public QObject
+{
+ Q_OBJECT
+
+ QGstTagListHandle parseTagList(const char *);
+ QGstTagListHandle parseTagList(const QByteArray &);
+
+private slots:
+ void metadata_taglistToMetaData();
+ void metadata_taglistToMetaData_extractsOrientation();
+ void metadata_taglistToMetaData_extractsOrientation_data();
+ void metadata_taglistToMetaData_extractsDuration();
+
+private:
+ QGstreamerIntegration integration;
+};
+
+#endif // TST_GSTREAMER_BACKEND_H
diff --git a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
index 1d2c39e7f..77e161fda 100644
--- a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
+++ b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
@@ -54,8 +54,8 @@ void tst_QAudioDecoder::read()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
// Starting with empty source == error
@@ -115,8 +115,8 @@ void tst_QAudioDecoder::stop()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
// Starting with empty source == error
@@ -167,8 +167,8 @@ void tst_QAudioDecoder::format()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
// Set the source to something
@@ -255,11 +255,11 @@ void tst_QAudioDecoder::readAll()
d.setSource(QUrl::fromLocalFile("Foo"));
QVERIFY(!d.isDecoding());
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
- QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy bufferAvailableSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy bufferAvailableSpy(&d, &QAudioDecoder::bufferAvailableChanged);
d.start();
int i = 0;
forever {
diff --git a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp
index 848cd05b7..1ba624eec 100644
--- a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp
+++ b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp
@@ -197,7 +197,7 @@ void tst_QCamera::testSimpleCameraCapture()
QCOMPARE(imageCapture.error(), QImageCapture::NoError);
QVERIFY(imageCapture.errorString().isEmpty());
- QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString)));
+ QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred);
imageCapture.captureToFile(QStringLiteral("/dev/null"));
QCOMPARE(errorSignal.size(), 1);
QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError);
@@ -220,8 +220,8 @@ void tst_QCamera::testCameraCapture()
QVERIFY(!imageCapture.isReadyForCapture());
- QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage)));
- QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString)));
+ QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured);
+ QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred);
imageCapture.captureToFile(QStringLiteral("/dev/null"));
QCOMPARE(capturedSignal.size(), 0);
@@ -249,8 +249,8 @@ void tst_QCamera::testCameraCaptureMetadata()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,const QMediaMetaData&)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
+ QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
camera.start();
int id = imageCapture.captureToFile(QStringLiteral("/dev/null"));
@@ -419,7 +419,7 @@ void tst_QCamera::testCameraEncodingProperyChange()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool)));
+ QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged);
camera.start();
QCOMPARE(camera.isActive(), true);
@@ -604,7 +604,7 @@ void tst_QCamera::testErrorSignal()
Q_ASSERT(service);
Q_ASSERT(service->mockCameraControl);
- QSignalSpy spyError(&camera, SIGNAL(errorOccurred(QCamera::Error,const QString&)));
+ QSignalSpy spyError(&camera, &QCamera::errorOccurred);
/* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */
service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("Camera Error"));
@@ -681,7 +681,7 @@ void tst_QCamera::testSetCameraFormat()
auto videoFormats = device.videoFormats();
QVERIFY(videoFormats.size());
QCameraFormat cameraFormat = videoFormats.first();
- QSignalSpy spy(&camera, SIGNAL(cameraFormatChanged()));
+ QSignalSpy spy(&camera, &QCamera::cameraFormatChanged);
QVERIFY(spy.size() == 0);
camera.setCameraFormat(cameraFormat);
QCOMPARE(spy.size(), 1);
@@ -733,7 +733,7 @@ void tst_QCamera::testZoomChanged()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy spy(&camera, SIGNAL(zoomFactorChanged(float)));
+ QSignalSpy spy(&camera, &QCamera::zoomFactorChanged);
QVERIFY(spy.size() == 0);
camera.setZoomFactor(2.0);
QVERIFY(spy.size() == 1);
@@ -751,7 +751,7 @@ void tst_QCamera::testMaxZoomChangedSignal()
QMockCamera *mock = QMockIntegration::instance()->lastCamera();
// ### change max zoom factor on backend, e.g. by changing camera
- QSignalSpy spy(&camera, SIGNAL(maximumZoomFactorChanged(float)));
+ QSignalSpy spy(&camera, &QCamera::maximumZoomFactorChanged);
mock->maximumZoomFactorChanged(55);
QVERIFY(spy.size() == 1);
QCOMPARE(camera.maximumZoomFactor(), 55);
@@ -763,9 +763,9 @@ void tst_QCamera::testSignalExposureCompensationChanged()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy spyExposureCompensationChanged(&camera, SIGNAL(exposureCompensationChanged(float)));
+ QSignalSpy spyExposureCompensationChanged(&camera, &QCamera::exposureCompensationChanged);
- QVERIFY(spyExposureCompensationChanged.size() ==0);
+ QVERIFY(spyExposureCompensationChanged.size() == 0);
QVERIFY(camera.exposureCompensation() != 800);
camera.setExposureCompensation(2.0);
@@ -790,7 +790,7 @@ void tst_QCamera::testSignalIsoSensitivityChanged()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy spyisoSensitivityChanged(&camera, SIGNAL(isoSensitivityChanged(int)));
+ QSignalSpy spyisoSensitivityChanged(&camera, &QCamera::isoSensitivityChanged);
QVERIFY(spyisoSensitivityChanged.size() ==0);
@@ -805,9 +805,9 @@ void tst_QCamera::testSignalShutterSpeedChanged()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy spySignalExposureTimeChanged(&camera, SIGNAL(exposureTimeChanged(float)));
+ QSignalSpy spySignalExposureTimeChanged(&camera, &QCamera::exposureTimeChanged);
- QVERIFY(spySignalExposureTimeChanged.size() ==0);
+ QVERIFY(spySignalExposureTimeChanged.size() == 0);
camera.setManualExposureTime(2.0);//set the ManualShutterSpeed to 2.0
QTest::qWait(100);
@@ -821,7 +821,7 @@ void tst_QCamera::testSignalFlashReady()
QCamera camera;
session.setCamera(&camera);
- QSignalSpy spyflashReady(&camera,SIGNAL(flashReady(bool)));
+ QSignalSpy spyflashReady(&camera, &QCamera::flashReady);
QVERIFY(spyflashReady.size() == 0);
diff --git a/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp b/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp
index c56712d14..3267b6f40 100644
--- a/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp
+++ b/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp
@@ -178,7 +178,7 @@ void tst_QImageCapture::error()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy spy(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString)));
+ QSignalSpy spy(&imageCapture, &QImageCapture::errorOccurred);
imageCapture.captureToFile();
QTest::qWait(30);
QVERIFY(spy.size() == 1);
@@ -196,7 +196,7 @@ void tst_QImageCapture::imageCaptured()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy spy(&imageCapture, SIGNAL(imageCaptured(int,QImage)));
+ QSignalSpy spy(&imageCapture, &QImageCapture::imageCaptured);
QVERIFY(imageCapture.isAvailable() == true);
QVERIFY(imageCapture.isReadyForCapture() == false);
camera.start();
@@ -219,7 +219,7 @@ void tst_QImageCapture::imageExposed()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy spy(&imageCapture, SIGNAL(imageExposed(int)));
+ QSignalSpy spy(&imageCapture, &QImageCapture::imageExposed);
QVERIFY(imageCapture.isAvailable() == true);
QVERIFY(imageCapture.isReadyForCapture() == false);
camera.start();
@@ -240,7 +240,7 @@ void tst_QImageCapture::imageSaved()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy spy(&imageCapture, SIGNAL(imageSaved(int,QString)));
+ QSignalSpy spy(&imageCapture, &QImageCapture::imageSaved);
QVERIFY(imageCapture.isAvailable() == true);
QVERIFY(imageCapture.isReadyForCapture() == false);
camera.start();
@@ -262,7 +262,7 @@ void tst_QImageCapture::readyForCaptureChanged()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy spy(&imageCapture, SIGNAL(readyForCaptureChanged(bool)));
+ QSignalSpy spy(&imageCapture, &QImageCapture::readyForCaptureChanged);
QVERIFY(imageCapture.isReadyForCapture() == false);
imageCapture.captureToFile();
QTest::qWait(100);
diff --git a/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt
new file mode 100644
index 000000000..8a4734434
--- /dev/null
+++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qmediacapture_gstreamer Test:
+#####################################################################
+
+qt_internal_add_test(tst_qmediacapture_gstreamer
+ SOURCES
+ tst_qmediacapture_gstreamer.cpp
+ LIBRARIES
+ Qt::MultimediaPrivate
+ Qt::QGstreamerMediaPluginPrivate
+)
diff --git a/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp b/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp
new file mode 100644
index 000000000..21258005c
--- /dev/null
+++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp
@@ -0,0 +1,75 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest/QtTest>
+#include <QtMultimedia/QMediaCaptureSession>
+#include <QtMultimedia/private/qplatformmediacapture_p.h>
+#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h>
+
+#include <memory>
+
+QT_USE_NAMESPACE
+
+class tst_QMediaCaptureGStreamer : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMediaCaptureGStreamer();
+
+public slots:
+ void init();
+ void cleanup();
+
+private slots:
+ void constructor_preparesGstPipeline();
+
+private:
+ std::unique_ptr<QMediaCaptureSession> session;
+
+ GstPipeline *getGstPipeline()
+ {
+ return reinterpret_cast<GstPipeline *>(
+ QPlatformMediaCaptureSession::nativePipeline(session.get()));
+ }
+
+ void dumpGraph(const char *fileNamePrefix)
+ {
+ GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(getGstPipeline()),
+ GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_VERBOSE),
+ fileNamePrefix);
+ }
+};
+
+tst_QMediaCaptureGStreamer::tst_QMediaCaptureGStreamer()
+{
+ qputenv("QT_MEDIA_BACKEND", "gstreamer");
+}
+
+void tst_QMediaCaptureGStreamer::init()
+{
+ session = std::make_unique<QMediaCaptureSession>();
+}
+
+void tst_QMediaCaptureGStreamer::cleanup()
+{
+ session.reset();
+}
+
+void tst_QMediaCaptureGStreamer::constructor_preparesGstPipeline()
+{
+ auto *rawPipeline = getGstPipeline();
+ QVERIFY(rawPipeline);
+
+ QGstPipeline pipeline{
+ rawPipeline,
+ QGstPipeline::NeedsRef,
+ };
+ QVERIFY(pipeline);
+
+ dumpGraph("constructor_preparesGstPipeline");
+}
+
+QTEST_GUILESS_MAIN(tst_QMediaCaptureGStreamer)
+
+#include "tst_qmediacapture_gstreamer.moc"
diff --git a/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp b/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp
index 2deaaf846..3fb77ca2d 100644
--- a/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp
+++ b/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp
@@ -294,32 +294,40 @@ void tst_QMediaPlayer::testPosition()
QVERIFY(player->duration() == duration);
if (seekable) {
- { QSignalSpy spy(player, SIGNAL(positionChanged(qint64)));
- player->setPosition(position);
- QCOMPARE(player->position(), position);
- QCOMPARE(spy.size(), 0); }
+ {
+ QSignalSpy spy(player, &QMediaPlayer::positionChanged);
+ player->setPosition(position);
+ QCOMPARE(player->position(), position);
+ QCOMPARE(spy.size(), 0);
+ }
mockPlayer->setPosition(position);
- { QSignalSpy spy(player, SIGNAL(positionChanged(qint64)));
- player->setPosition(0);
- QCOMPARE(player->position(), qint64(0));
- QCOMPARE(spy.size(), position == 0 ? 0 : 1); }
+ {
+ QSignalSpy spy(player, &QMediaPlayer::positionChanged);
+ player->setPosition(0);
+ QCOMPARE(player->position(), qint64(0));
+ QCOMPARE(spy.size(), position == 0 ? 0 : 1);
+ }
mockPlayer->setPosition(position);
- { QSignalSpy spy(player, SIGNAL(positionChanged(qint64)));
- player->setPosition(duration);
- QCOMPARE(player->position(), duration);
- QCOMPARE(spy.size(), position == duration ? 0 : 1); }
+ {
+ QSignalSpy spy(player, &QMediaPlayer::positionChanged);
+ player->setPosition(duration);
+ QCOMPARE(player->position(), duration);
+ QCOMPARE(spy.size(), position == duration ? 0 : 1);
+ }
mockPlayer->setPosition(position);
- { QSignalSpy spy(player, SIGNAL(positionChanged(qint64)));
- player->setPosition(-1);
- QCOMPARE(player->position(), qint64(0));
- QCOMPARE(spy.size(), position == 0 ? 0 : 1); }
+ {
+ QSignalSpy spy(player, &QMediaPlayer::positionChanged);
+ player->setPosition(-1);
+ QCOMPARE(player->position(), qint64(0));
+ QCOMPARE(spy.size(), position == 0 ? 0 : 1);
+ }
}
else {
- QSignalSpy spy(player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy spy(player, &QMediaPlayer::positionChanged);
player->setPosition(position);
QCOMPARE(player->position(), position);
@@ -342,25 +350,33 @@ void tst_QMediaPlayer::testVolume()
QVERIFY(audioOutput->volume() == vol);
if (valid) {
- { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float)));
- audioOutput->setVolume(.1f);
- QCOMPARE(audioOutput->volume(), .1f);
- QCOMPARE(spy.size(), 1); }
-
- { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float)));
- audioOutput->setVolume(-1000.f);
- QCOMPARE(audioOutput->volume(), 0.f);
- QCOMPARE(spy.size(), 1); }
-
- { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float)));
- audioOutput->setVolume(1.f);
- QCOMPARE(audioOutput->volume(), 1.f);
- QCOMPARE(spy.size(), 1); }
-
- { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float)));
- audioOutput->setVolume(1000.f);
- QCOMPARE(audioOutput->volume(), 1.f);
- QCOMPARE(spy.size(), 0); }
+ {
+ QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged);
+ audioOutput->setVolume(.1f);
+ QCOMPARE(audioOutput->volume(), .1f);
+ QCOMPARE(spy.size(), 1);
+ }
+
+ {
+ QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged);
+ audioOutput->setVolume(-1000.f);
+ QCOMPARE(audioOutput->volume(), 0.f);
+ QCOMPARE(spy.size(), 1);
+ }
+
+ {
+ QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged);
+ audioOutput->setVolume(1.f);
+ QCOMPARE(audioOutput->volume(), 1.f);
+ QCOMPARE(spy.size(), 1);
+ }
+
+ {
+ QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged);
+ audioOutput->setVolume(1000.f);
+ QCOMPARE(audioOutput->volume(), 1.f);
+ QCOMPARE(spy.size(), 0);
+ }
}
}
@@ -381,7 +397,7 @@ void tst_QMediaPlayer::testMuted()
audioOutput->setVolume(vol);
QVERIFY(audioOutput->isMuted() == muted);
- QSignalSpy spy(audioOutput, SIGNAL(mutedChanged(bool)));
+ QSignalSpy spy(audioOutput, &QAudioOutput::mutedChanged);
audioOutput->setMuted(!muted);
QCOMPARE(audioOutput->isMuted(), !muted);
QCOMPARE(audioOutput->volume(), vol);
@@ -442,7 +458,7 @@ void tst_QMediaPlayer::testPlaybackRate()
mockPlayer->setPlaybackRate(playbackRate);
QVERIFY(player->playbackRate() == playbackRate);
- QSignalSpy spy(player, SIGNAL(playbackRateChanged(qreal)));
+ QSignalSpy spy(player, &QMediaPlayer::playbackRateChanged);
player->setPlaybackRate(playbackRate + 0.5f);
QCOMPARE(player->playbackRate(), playbackRate + 0.5f);
QCOMPARE(spy.size(), 1);
@@ -512,8 +528,8 @@ void tst_QMediaPlayer::testPlay()
QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState);
QCOMPARE(player->source(), mediaContent);
- QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState)));
- QSignalSpy playingChanged(player, SIGNAL(playingChanged(bool)));
+ QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged);
player->play();
@@ -549,8 +565,8 @@ void tst_QMediaPlayer::testPause()
QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState);
QVERIFY(player->source() == mediaContent);
- QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState)));
- QSignalSpy playingChanged(player, SIGNAL(playingChanged(bool)));
+ QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged);
player->pause();
@@ -584,8 +600,8 @@ void tst_QMediaPlayer::testStop()
QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState);
QVERIFY(player->source() == mediaContent);
- QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState)));
- QSignalSpy playingChanged(player, SIGNAL(playingChanged(bool)));
+ QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged);
player->stop();
@@ -616,8 +632,8 @@ void tst_QMediaPlayer::testMediaStatus()
mockPlayer->setMediaStatus(QMediaPlayer::NoMedia);
mockPlayer->setBufferStatus(bufferProgress);
- QSignalSpy statusSpy(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy bufferSpy(player, SIGNAL(bufferProgressChanged(float)));
+ QSignalSpy statusSpy(player, &QMediaPlayer::mediaStatusChanged);
+ QSignalSpy bufferSpy(player, &QMediaPlayer::bufferProgressChanged);
QCOMPARE(player->mediaStatus(), QMediaPlayer::NoMedia);
@@ -786,9 +802,9 @@ void tst_QMediaPlayer::testQrc()
mockPlayer->setState(QMediaPlayer::PlayingState, QMediaPlayer::NoMedia);
mockPlayer->setStreamPlaybackSupported(backendHasStream);
- QSignalSpy mediaSpy(player, SIGNAL(sourceChanged(QUrl)));
- QSignalSpy statusSpy(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy errorSpy(player, SIGNAL(errorOccurred(QMediaPlayer::Error,const QString&)));
+ QSignalSpy mediaSpy(player, &QMediaPlayer::sourceChanged);
+ QSignalSpy statusSpy(player, &QMediaPlayer::mediaStatusChanged);
+ QSignalSpy errorSpy(player, &QMediaPlayer::errorOccurred);
player->setSource(mediaContent);
diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt
new file mode 100644
index 000000000..2437df00c
--- /dev/null
+++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qmediaplayer_gstreamer Test:
+#####################################################################
+
+qt_internal_add_test(tst_qmediaplayer_gstreamer
+ SOURCES
+ tst_qmediaplayer_gstreamer.cpp
+ LIBRARIES
+ Qt::MultimediaPrivate
+ Qt::QGstreamerMediaPluginPrivate
+)
diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp
new file mode 100644
index 000000000..8b0f3f073
--- /dev/null
+++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp
@@ -0,0 +1,78 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest/QtTest>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/private/qmediaplayer_p.h>
+#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h>
+
+#include <memory>
+
+QT_USE_NAMESPACE
+
+class tst_QMediaPlayerGStreamer : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMediaPlayerGStreamer();
+
+public slots:
+ void init();
+ void cleanup();
+
+private slots:
+ void constructor_preparesGstPipeline();
+
+private:
+ std::unique_ptr<QMediaPlayer> player;
+
+ GstPipeline *getGstPipeline()
+ {
+ return reinterpret_cast<GstPipeline *>(QPlatformMediaPlayer::nativePipeline(player.get()));
+ }
+
+ void dumpGraph(const char *fileNamePrefix)
+ {
+ GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(getGstPipeline()),
+ GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_VERBOSE),
+ fileNamePrefix);
+ }
+};
+
+tst_QMediaPlayerGStreamer::tst_QMediaPlayerGStreamer()
+{
+ qputenv("QT_MEDIA_BACKEND", "gstreamer");
+}
+
+void tst_QMediaPlayerGStreamer::init()
+{
+ player = std::make_unique<QMediaPlayer>();
+}
+
+void tst_QMediaPlayerGStreamer::cleanup()
+{
+ player.reset();
+}
+
+void tst_QMediaPlayerGStreamer::constructor_preparesGstPipeline()
+{
+ auto *rawPipeline = getGstPipeline();
+ QVERIFY(rawPipeline);
+
+ QGstPipeline pipeline{
+ rawPipeline,
+ QGstPipeline::NeedsRef,
+ };
+ QVERIFY(pipeline);
+
+ QVERIFY(pipeline.findByName("videoInputSelector"));
+ QVERIFY(pipeline.findByName("audioInputSelector"));
+ QVERIFY(pipeline.findByName("subTitleInputSelector"));
+
+ dumpGraph("constructor_preparesGstPipeline");
+}
+
+QTEST_GUILESS_MAIN(tst_QMediaPlayerGStreamer)
+
+#include "tst_qmediaplayer_gstreamer.moc"
diff --git a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp
index 4873c2407..e5d9d57db 100644
--- a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp
+++ b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp
@@ -125,7 +125,7 @@ void tst_QMediaRecorder::testNullControls()
QCOMPARE(recorder.mediaFormat().videoCodec(), QMediaFormat::VideoCodec::VP9);
QCOMPARE(recorder.mediaFormat().fileFormat(), QMediaFormat::MPEG4);
- QSignalSpy spy(&recorder, SIGNAL(recorderStateChanged(RecorderState)));
+ QSignalSpy spy(&recorder, &QMediaRecorder::recorderStateChanged);
recorder.record();
QCOMPARE(recorder.recorderState(), QMediaRecorder::StoppedState);
@@ -190,7 +190,7 @@ void tst_QMediaRecorder::testError()
{
const QString errorString(QLatin1String("format error"));
- QSignalSpy spy(encoder, SIGNAL(errorOccurred(Error, const QString&)));
+ QSignalSpy spy(encoder, &QMediaRecorder::errorOccurred);
QCOMPARE(encoder->error(), QMediaRecorder::NoError);
QCOMPARE(encoder->errorString(), QString());
@@ -230,8 +230,8 @@ void tst_QMediaRecorder::testSink()
void tst_QMediaRecorder::testRecord()
{
- QSignalSpy stateSignal(encoder,SIGNAL(recorderStateChanged(RecorderState)));
- QSignalSpy progressSignal(encoder, SIGNAL(durationChanged(qint64)));
+ QSignalSpy stateSignal(encoder, &QMediaRecorder::recorderStateChanged);
+ QSignalSpy progressSignal(encoder, &QMediaRecorder::durationChanged);
encoder->record();
QCOMPARE(encoder->recorderState(), QMediaRecorder::RecordingState);
QCOMPARE(encoder->error(), QMediaRecorder::NoError);
@@ -413,7 +413,7 @@ void tst_QMediaRecorder::testEnum()
{
const QString errorString(QLatin1String("resource error"));
- QSignalSpy spy(encoder, SIGNAL(errorOccurred(Error, const QString&)));
+ QSignalSpy spy(encoder, &QMediaRecorder::errorOccurred);
QCOMPARE(encoder->error(), QMediaRecorder::NoError);
QCOMPARE(encoder->errorString(), QString());
diff --git a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp
index c78e9cfb8..079f98075 100644
--- a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp
+++ b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp
@@ -176,8 +176,8 @@ void tst_QWaveDecoder::http()
QNetworkReply *reply = nam.get(QNetworkRequest(QUrl::fromLocalFile(file)));
QWaveDecoder waveDecoder(reply);
- QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown()));
- QSignalSpy parsingErrorSpy(&waveDecoder, SIGNAL(parsingError()));
+ QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown);
+ QSignalSpy parsingErrorSpy(&waveDecoder, &QWaveDecoder::parsingError);
QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly));
@@ -227,7 +227,7 @@ void tst_QWaveDecoder::readAllAtOnce()
QVERIFY(stream.isOpen());
QWaveDecoder waveDecoder(&stream);
- QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown()));
+ QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown);
QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly));
@@ -255,7 +255,7 @@ void tst_QWaveDecoder::readPerByte()
QVERIFY(stream.isOpen());
QWaveDecoder waveDecoder(&stream);
- QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown()));
+ QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown);
QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly));
diff --git a/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp b/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp
index 743eda276..6c31a4b66 100644
--- a/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp
+++ b/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp
@@ -51,7 +51,7 @@ void tst_QCameraWidgets::testCameraEncodingProperyChange()
session.setCamera(&camera);
session.setImageCapture(&imageCapture);
- QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool)));
+ QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged);
camera.start();
QCOMPARE(camera.isActive(), true);
diff --git a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
index 2fba9daa6..2a1538edc 100644
--- a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
+++ b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
@@ -214,7 +214,7 @@ void tst_QGraphicsVideoItem::nativeSize()
QCOMPARE(item.nativeSize(), QSizeF());
- QSignalSpy spy(&item, SIGNAL(nativeSizeChanged(QSizeF)));
+ QSignalSpy spy(&item, &QGraphicsVideoItem::nativeSizeChanged);
QVideoFrameFormat format(frameSize, QVideoFrameFormat::Format_ARGB8888);
format.setViewport(viewport);
diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
index de1c5a4e3..3cfe5d18e 100644
--- a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
+++ b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
@@ -184,7 +184,7 @@ void tst_QVideoWidget::fullScreen()
Qt::WindowFlags windowFlags = widget.windowFlags();
- QSignalSpy spy(&widget, SIGNAL(fullScreenChanged(bool)));
+ QSignalSpy spy(&widget, &QVideoWidget::fullScreenChanged);
// Test showing full screen with setFullScreen(true).
widget.setFullScreen(true);
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index 257ed0127..08e38e893 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -10,7 +10,7 @@ if(TARGET Qt::Quick)
add_subdirectory(qml-minimal-camera)
add_subdirectory(qml-minimal-player)
- if (QT_FEATURE_gstreamer)
+ if(QT_FEATURE_gstreamer)
add_subdirectory(qml-gstreamer-rtp)
endif()
endif()
diff --git a/tests/manual/minimal-player/minimal-player.cpp b/tests/manual/minimal-player/minimal-player.cpp
index bd75efa09..70512dff3 100644
--- a/tests/manual/minimal-player/minimal-player.cpp
+++ b/tests/manual/minimal-player/minimal-player.cpp
@@ -1,31 +1,85 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-#include <QApplication>
-
-#include <QtWidgets/QApplication>
-#include <QtWidgets/QWidget>
-#include <QtMultimedia/QMediaPlayer>
+#include <QtCore/QTimer>
+#include <QtCore/QCommandLineParser>
#include <QtMultimedia/QAudioOutput>
+#include <QtMultimedia/QMediaPlayer>
#include <QtMultimediaWidgets/QVideoWidget>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
-int main(int argc, char **argv)
+using namespace std::chrono_literals;
+using namespace Qt::Literals;
+
+int mainToggleWidgets(QString filename)
{
- QApplication app(argc, argv);
QMediaPlayer player;
- QVideoWidget widget;
+ QVideoWidget widget1;
+ QVideoWidget widget2;
QAudioOutput audioOutput;
- player.setVideoOutput(&widget);
+ player.setVideoOutput(&widget1);
player.setAudioOutput(&audioOutput);
+ player.setSource(filename);
- if (QApplication::arguments().size() > 1) {
- player.setSource(QApplication::arguments()[1]);
- } else {
+ QTimer toggleOutput;
+ bool toggled = {};
+
+ toggleOutput.callOnTimeout([&] {
+ toggled = !toggled;
+ if (toggled)
+ player.setVideoOutput(&widget2);
+ else
+ player.setVideoOutput(&widget1);
+ });
+
+ toggleOutput.setInterval(1s);
+ toggleOutput.start();
+
+ widget1.show();
+ widget2.show();
+ player.play();
+ return QApplication::exec();
+}
+
+int mainSimple(QString filename)
+{
+ QMediaPlayer player;
+ QVideoWidget widget1;
+ QAudioOutput audioOutput;
+ player.setVideoOutput(&widget1);
+ player.setAudioOutput(&audioOutput);
+ player.setSource(filename);
+
+ widget1.show();
+ player.play();
+ return QApplication::exec();
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Minimal Player");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("media", "File to play");
+
+ QCommandLineOption toggleWidgetsOption{ "toggle-widgets", "Toggle between widgets." };
+ parser.addOption(toggleWidgetsOption);
+
+ parser.process(app);
+
+ if (parser.positionalArguments().isEmpty()) {
qInfo() << "Please specify a video source";
return 0;
}
- widget.show();
- player.play();
- return QApplication::exec();
+ QString filename = parser.positionalArguments()[0];
+
+ if (parser.isSet(toggleWidgetsOption))
+ return mainToggleWidgets(filename);
+
+ return mainSimple(filename);
}