diff options
| author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-10-11 08:25:44 +0300 |
|---|---|---|
| committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-10-11 08:38:33 +0300 |
| commit | 3cc3a6bd7e32fddac5ec2d51fe68b02fb17a7af3 (patch) | |
| tree | 7907b5067ae3a6c400271b4e0803a94c0f59e369 | |
| parent | 0247b19277274736124239029f4cd9e4cce7a4c6 (diff) | |
| parent | 5c245118182649ecbd1571a81f6d73a2c513246a (diff) | |
Merge tag 'v6.5.7-lts' into tqtc/lts-6.5-opensourcev6.5.7-lts-lgpl6.5
Qt 6.5.7-lts release
Conflicts solved:
dependencies.yaml
src/multimedia/recording/qscreencapture-limitations.qdocinc
src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp
src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink_p.h
src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp
src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource_p.h
Conflicted files under src/plugins/... were removed in
17b73aa155dff25980911f6c2570a6414663a1e5.
The SPDX license identifier in .qdocinc was updated in
faf7a48d7e31f4d0f268a9a918423da55ab0f87a.
Change-Id: I590b854ef1508a161e3edd2f0916b409539c3e65
801 files changed, 11500 insertions, 5894 deletions
diff --git a/.cmake.conf b/.cmake.conf index 6e7ce70b3..65b76e20c 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,4 +1,4 @@ -set(QT_REPO_MODULE_VERSION "6.5.6") +set(QT_REPO_MODULE_VERSION "6.5.7") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1") list(APPEND QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_CONTEXTLESS_CONNECT=1") diff --git a/CMakeLists.txt b/CMakeLists.txt index 70710b1b6..802bafcb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,4 +26,49 @@ if(NOT TARGET Qt::Network) return() endif() +set(QT_BUILD_EXTRA_IDE_FILE_PATTERNS + src/multimedia/doc/snippets/multimedia-snippets/images/qt-logo.png + src/multimedia/doc/snippets/multimedia-snippets/devices.cpp + src/multimedia/doc/snippets/multimedia-snippets/qsound.cpp + src/multimedia/doc/snippets/multimedia-snippets/audio.cpp + src/multimedia/doc/snippets/multimedia-snippets/camera.cpp + src/multimedia/doc/snippets/multimedia-snippets/media.cpp + src/multimedia/doc/snippets/multimedia-snippets/multiple-videooutputs.qml + src/multimedia/doc/snippets/multimedia-snippets/qtvideosink.qml + src/multimedia/doc/snippets/multimedia-snippets/soundeffect.qml + src/multimedia/doc/snippets/multimedia-snippets/video.cpp + src/multimedia/doc/snippets/CMakeLists.txt + src/multimedia/doc/src/classic.css + src/multimedia/doc/src/examples/video-qml-paint-rate.qdocinc + src/multimedia/doc/src/images/annotatedurl.png + src/multimedia/doc/src/images/codeless.png + src/multimedia/doc/src/images/qmlcamera-menu.png + src/multimedia/doc/src/images/radio-example.png + src/multimedia/doc/src/images/slideshow-img1.png + src/multimedia/doc/src/images/video-graphics-memory.png + src/multimedia/doc/src/images/video-qml-paint-rate.png + src/multimedia/doc/src/images/Zoom.gif + src/multimedia/doc/src/images/how-focus-works.gif + src/multimedia/doc/src/images/image_processing.png + src/multimedia/doc/src/images/noun_Media_166644.svg + src/multimedia/doc/src/images/qS1FmgPVL.jpg + src/multimedia/doc/src/images/sound-wave-small.jpg + src/multimedia/doc/src/images/camera_correctionAngle_90.png + src/multimedia/doc/src/qt6-changes.qdoc + src/multimedia/doc/src/qtmultimedia-cpp.qdoc + src/multimedia/doc/src/qtmultimedia-examples.qdoc + src/multimedia/doc/src/qtmultimedia-qml-types.qdoc + src/multimedia/doc/src/videooverview.qdoc + src/multimedia/doc/src/audiooverview.qdoc + src/multimedia/doc/src/cameraoverview.qdoc + src/multimedia/doc/src/multimedia-overview.qdoc + src/multimedia/doc/src/platform-notes-apple.qdoc + src/multimedia/doc/src/platform-notes-wasm.qdoc + src/multimedia/doc/src/qm-external-pages.qdoc + src/multimedia/doc/src/qtmultimedia-building-from-source.qdoc + src/multimedia/doc/src/qtmultimedia-index.qdoc + src/multimedia/doc/QtMultimediaDoc + src/multimedia/doc/qtmultimedia.qdocconf +) + qt_build_repo() diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake index b8891e7ed..bab077efd 100644 --- a/cmake/FindGStreamer.cmake +++ b/cmake/FindGStreamer.cmake @@ -26,14 +26,29 @@ include(CMakeFindDependencyMacro) find_dependency(GObject) find_package(PkgConfig QUIET) -function(find_gstreamer_component component prefix header library) - if(NOT TARGET GStreamer::${component}) +function(find_gstreamer_component component) + cmake_parse_arguments(PARSE_ARGV 1 ARGS "" "PC_NAME;HEADER;LIBRARY" "DEPENDENCIES") + + set(pkgconfig_name ${ARGS_PC_NAME}) + set(header ${ARGS_HEADER}) + set(library ${ARGS_LIBRARY}) + + set(target GStreamer::${component}) + + if(NOT TARGET ${target}) string(TOUPPER ${component} upper) - pkg_check_modules(PC_GSTREAMER_${upper} ${prefix} IMPORTED_TARGET) + pkg_check_modules(PC_GSTREAMER_${upper} IMPORTED_TARGET ${pkgconfig_name} ) if(TARGET PkgConfig::PC_GSTREAMER_${upper}) add_library(GStreamer::${component} INTERFACE IMPORTED) target_link_libraries(GStreamer::${component} INTERFACE PkgConfig::PC_GSTREAMER_${upper}) else() + foreach(dependency IN LISTS ARGS_DEPENDENCIES) + if (NOT TARGET ${dependency}) + set(GStreamer_${component}_FOUND FALSE PARENT_SCOPE) + return() + endif() + endforeach() + find_path(GStreamer_${component}_INCLUDE_DIR NAMES ${header} PATH_SUFFIXES gstreamer-1.0 @@ -58,64 +73,75 @@ function(find_gstreamer_component component prefix header library) add_library(GStreamer::${component} INTERFACE IMPORTED) target_include_directories(GStreamer::${component} INTERFACE ${GStreamer_${component}_INCLUDE_DIR}) target_link_libraries(GStreamer::${component} INTERFACE ${GStreamer_${component}_LIBRARY}) + if(ARGS_DEPENDENCIES) + target_link_libraries(GStreamer::${component} INTERFACE ${ARGS_DEPENDENCIES}) + endif() endif() mark_as_advanced(GStreamer_${component}_INCLUDE_DIR GStreamer_${component}_LIBRARY) + endif() endif() - if(TARGET GStreamer::${component}) + if(TARGET ${target}) set(GStreamer_${component}_FOUND TRUE PARENT_SCOPE) endif() endfunction() # GStreamer required dependencies -find_gstreamer_component(Core gstreamer-1.0 gst/gst.h gstreamer-1.0) -find_gstreamer_component(Base gstreamer-base-1.0 gst/gst.h gstbase-1.0) -find_gstreamer_component(Audio gstreamer-audio-1.0 gst/audio/audio.h gstaudio-1.0) -find_gstreamer_component(Video gstreamer-video-1.0 gst/video/video.h gstvideo-1.0) -find_gstreamer_component(Pbutils gstreamer-pbutils-1.0 gst/pbutils/pbutils.h gstpbutils-1.0) -find_gstreamer_component(Allocators gstreamer-allocators-1.0 gst/allocators/allocators.h gstallocators-1.0) - -if(TARGET GStreamer::Core) - target_link_libraries(GStreamer::Core INTERFACE GObject::GObject) -endif() -if(TARGET GStreamer::Base AND TARGET GStreamer::Core) - target_link_libraries(GStreamer::Base INTERFACE GStreamer::Core) -endif() -if(TARGET GStreamer::Audio AND TARGET GStreamer::Base) - target_link_libraries(GStreamer::Audio INTERFACE GStreamer::Base) -endif() -if(TARGET GStreamer::Video AND TARGET GStreamer::Base) - target_link_libraries(GStreamer::Video INTERFACE GStreamer::Base) -endif() -if(TARGET GStreamer::Pbutils AND TARGET GStreamer::Audio AND TARGET GStreamer::Video) - target_link_libraries(GStreamer::Pbutils INTERFACE GStreamer::Audio GStreamer::Video) +find_gstreamer_component(Core + PC_NAME gstreamer-1.0 + HEADER gst/gst.h + LIBRARY gstreamer-1.0 + DEPENDENCIES GObject::GObject) +find_gstreamer_component(Base + PC_NAME gstreamer-base-1.0 + HEADER gst/gst.h + LIBRARY gstbase-1.0 + DEPENDENCIES GStreamer::Core) +find_gstreamer_component(Audio + PC_NAME gstreamer-audio-1.0 + HEADER gst/audio/audio.h + LIBRARY gstaudio-1.0 + DEPENDENCIES GStreamer::Base) +find_gstreamer_component(Video + PC_NAME gstreamer-video-1.0 + HEADER gst/video/video.h + LIBRARY gstvideo-1.0 + DEPENDENCIES GStreamer::Base) +find_gstreamer_component(Pbutils + PC_NAME gstreamer-pbutils-1.0 + HEADER gst/pbutils/pbutils.h + LIBRARY gstpbutils-1.0 + DEPENDENCIES GStreamer::Audio GStreamer::Video) +find_gstreamer_component(Allocators + PC_NAME gstreamer-allocators-1.0 + HEADER gst/allocators/allocators.h + LIBRARY gstallocators-1.0 + DEPENDENCIES GStreamer::Core) + +if(App IN_LIST GStreamer_FIND_COMPONENTS) + find_gstreamer_component(App + PC_NAME gstreamer-app-1.0 + HEADER gst/app/gstappsink.h + LIBRARY gstapp-1.0 + DEPENDENCIES GStreamer::Base) endif() -if(TARGET GStreamer::Allocators AND TARGET GStreamer::Core) - target_link_libraries(GStreamer::Allocators INTERFACE GStreamer::Core) + +if(Photography IN_LIST GStreamer_FIND_COMPONENTS) + find_gstreamer_component(Photography + PC_NAME gstreamer-photography-1.0 + HEADER gst/interfaces/photography.h + LIBRARY gstphotography-1.0 + DEPENDENCIES GStreamer::Core) endif() -# GStreamer optional components -foreach(component ${GStreamer_FIND_COMPONENTS}) - 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") - 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") - 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) - endif() - else() - message(WARNING "FindGStreamer.cmake: Invalid Gstreamer component \"${component}\" requested") - endif() -endforeach() +if(Gl IN_LIST GStreamer_FIND_COMPONENTS) + find_gstreamer_component(Gl + PC_NAME gstreamer-gl-1.0 + HEADER gst/gl/gl.h + LIBRARY gstgl-1.0 + DEPENDENCIES GStreamer::Core) +endif() # Create target GStreamer::GStreamer include(FindPackageHandleStandardArgs) diff --git a/cmake/FindVAAPI.cmake b/cmake/FindVAAPI.cmake index a3ea6cd58..b1170dc8e 100644 --- a/cmake/FindVAAPI.cmake +++ b/cmake/FindVAAPI.cmake @@ -4,13 +4,40 @@ find_package(PkgConfig QUIET) +function(qt_internal_multimedia_set_va_outputs component include_dir lib_path) + if ("${component}" STREQUAL "VA") + set(VAAPI_INCLUDE_DIR "${include_dir}" CACHE INTERNAL "") + get_filename_component(lib_realpath "${lib_path}" REALPATH) + + string(REGEX MATCH "[0-9]+(\\.[0-9]+)*$" VAAPI_SUFFIX "${lib_realpath}") + set(VAAPI_SUFFIX "${VAAPI_SUFFIX}" CACHE INTERNAL "") + + mark_as_advanced(VAAPI_SUFFIX VAAPI_INCLUDE_DIR) + endif() +endfunction() + function(find_component component prefix header library) if(NOT TARGET VAAPI::${component}) string(TOUPPER ${component} upper) - pkg_check_modules(PC_VAAPI_${upper} ${prefix} IMPORTED_TARGET) + pkg_search_module(PC_VAAPI_${upper} ${prefix} IMPORTED_TARGET) if(TARGET PkgConfig::PC_VAAPI_${upper}) add_library(VAAPI::${component} INTERFACE IMPORTED) target_link_libraries(VAAPI::${component} INTERFACE PkgConfig::PC_VAAPI_${upper}) + + if (NOT PC_VAAPI_${upper}_LINK_LIBRARIES) + get_target_property(PC_VAAPI_${upper}_LINK_LIBRARIES PkgConfig::PC_VAAPI_${upper} INTERFACE_LINK_LIBRARIES) + message(STATUS "PC_VAAPI_${upper}_LINK_LIBRARIES is not defined by PkgConfig; " + "Get the value from target properties: ${PC_VAAPI_${upper}_LINK_LIBRARIES}") + endif() + + foreach (lib_path ${PC_VAAPI_${upper}_LINK_LIBRARIES}) + get_filename_component(lib_name "${lib_path}" NAME_WLE) + if (${lib_name} STREQUAL ${prefix}) + qt_internal_multimedia_set_va_outputs(${component} + "${PC_VAAPI_${upper}_INCLUDEDIR}" "${lib_path}") + break() + endif() + endforeach() else() find_path(VAAPI_${component}_INCLUDE_DIR NAMES ${header} @@ -25,6 +52,9 @@ function(find_component component prefix header library) target_link_libraries(VAAPI::${component} INTERFACE ${VAAPI_${component}_LIBRARY}) endif() mark_as_advanced(VAAPI_${component}_INCLUDE_DIR VAAPI_${component}_LIBRARY) + + qt_internal_multimedia_set_va_outputs(${component} + "${VAAPI_${component}_INCLUDE_DIR}" "${VAAPI_${component}_LIBRARY}") endif() endif() @@ -48,6 +78,8 @@ find_package_handle_standard_args(VAAPI REQUIRED_VARS VAAPI_VA_FOUND VAAPI_DRM_FOUND + VAAPI_INCLUDE_DIR + VAAPI_SUFFIX HANDLE_COMPONENTS ) diff --git a/coin/instructions/run_ffmpeg_backend_tests.yaml b/coin/instructions/run_ffmpeg_backend_tests.yaml index 92096bd33..7d2bf2ba2 100644 --- a/coin/instructions/run_ffmpeg_backend_tests.yaml +++ b/coin/instructions/run_ffmpeg_backend_tests.yaml @@ -18,21 +18,11 @@ enable_if: env_var: TARGET_OSVERSION_COIN contains_value: android_any - condition: runtime + env_var: TARGET_OSVERSION_COIN + contains_value: windows + - condition: runtime env_var: NON_QTBASE_CMAKE_ARGS contains_value: "-DFFMPEG_DIR=/" - - condition: and - conditions: - - condition: runtime - env_var: TARGET_OS_COIN - equals_value: windows - - condition: or - conditions: - - condition: property - property: target.arch - not_equals_value: arm64 - - condition: runtime - env_var: NON_QTBASE_CMAKE_ARGS - contains_value: "-DFFMPEG_DIR=C:" instructions: - type: EnvironmentVariable variableName: QT_MEDIA_BACKEND diff --git a/coin/instructions/run_gstreamer_backend_tests.yaml b/coin/instructions/run_gstreamer_backend_tests.yaml new file mode 100644 index 000000000..797b9480c --- /dev/null +++ b/coin/instructions/run_gstreamer_backend_tests.yaml @@ -0,0 +1,25 @@ +type: Group +enable_if: + condition: or + conditions: + - condition: runtime + env_var: TARGET_OSVERSION_COIN + contains_value: ubuntu + # Disabling on rhel/opensuse for now due to missing h264 codecs on CI + # - condition: runtime + # env_var: TARGET_OSVERSION_COIN + # contains_value: rhel + # - condition: runtime + # env_var: TARGET_OSVERSION_COIN + # contains_value: opensuse + +instructions: + - type: EnvironmentVariable + variableName: QT_MEDIA_BACKEND + variableValue: gstreamer + + # QTBUG-127927 + # - type: EnvironmentVariable + # variableName: G_DEBUG + # variableValue: fatal_criticals + - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml" diff --git a/coin/module_config.yaml b/coin/module_config.yaml index e731cefd8..6a1fa65a2 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -11,4 +11,5 @@ instructions: Test: - !include "{{qt/qtmultimedia}}/run_ffmpeg_backend_tests.yaml" + - !include "{{qt/qtmultimedia}}/run_gstreamer_backend_tests.yaml" - !include "{{qt/qtbase}}/coin_module_test_docs.yaml" diff --git a/config.tests/alsa/CMakeLists.txt b/config.tests/alsa/CMakeLists.txt deleted file mode 100644 index 8b434e11a..000000000 --- a/config.tests/alsa/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from alsa.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_alsa LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - alsatest.cpp -) diff --git a/config.tests/alsa/alsatest.cpp b/config.tests/alsa/alsatest.cpp deleted file mode 100644 index cd744df54..000000000 --- a/config.tests/alsa/alsatest.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include <alsa/asoundlib.h> -#if SND_LIB_VERSION < 0x1000a // 1.0.10 -#error "Alsa version found too old, require >= 1.0.10" -#endif - -int main(int argc,char **argv) -{ -} - diff --git a/config.tests/avfoundation/CMakeLists.txt b/config.tests/avfoundation/CMakeLists.txt deleted file mode 100644 index 4d71200f1..000000000 --- a/config.tests/avfoundation/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from avfoundation.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_avfoundation LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - main.mm -) diff --git a/config.tests/avfoundation/main.mm b/config.tests/avfoundation/main.mm deleted file mode 100644 index c15ced3c4..000000000 --- a/config.tests/avfoundation/main.mm +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#import <Foundation/Foundation.h> -#import <AVFoundation/AVFoundation.h> - -int main(int argc, char** argv) -{ - AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:@"http://doesnotmatter.com"]]; - return 0; -} diff --git a/config.tests/gstreamer/CMakeLists.txt b/config.tests/gstreamer/CMakeLists.txt deleted file mode 100644 index 314362293..000000000 --- a/config.tests/gstreamer/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from gstreamer.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_gstreamer LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - main.cpp -) diff --git a/config.tests/gstreamer/main.cpp b/config.tests/gstreamer/main.cpp deleted file mode 100644 index 445bb1894..000000000 --- a/config.tests/gstreamer/main.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#define GST_USE_UNSTABLE_API - -#include <gst/gst.h> - -int main(int argc, char** argv) -{ - return 0; -} diff --git a/config.tests/gstreamer_appsrc/CMakeLists.txt b/config.tests/gstreamer_appsrc/CMakeLists.txt deleted file mode 100644 index 3cff11eb9..000000000 --- a/config.tests/gstreamer_appsrc/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from gstreamer_appsrc.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_gstreamer_appsrc LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - main.cpp -) diff --git a/config.tests/gstreamer_appsrc/main.cpp b/config.tests/gstreamer_appsrc/main.cpp deleted file mode 100644 index f8da0a48e..000000000 --- a/config.tests/gstreamer_appsrc/main.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#define GST_USE_UNSTABLE_API - -#include <gst/app/gstappsrc.h> - -int main(int argc, char** argv) -{ - return 0; -} diff --git a/config.tests/gstreamer_photography/CMakeLists.txt b/config.tests/gstreamer_photography/CMakeLists.txt deleted file mode 100644 index 235182c33..000000000 --- a/config.tests/gstreamer_photography/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from gstreamer_photography.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_gstreamer_photography LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - main.cpp -) diff --git a/config.tests/gstreamer_photography/main.cpp b/config.tests/gstreamer_photography/main.cpp deleted file mode 100644 index add336fae..000000000 --- a/config.tests/gstreamer_photography/main.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#define GST_USE_UNSTABLE_API - -#include <gst/interfaces/photography.h> -#include <gst/interfaces/photography-enumtypes.h> -#include <gst/pbutils/pbutils.h> -#include <gst/pbutils/encoding-profile.h> - -int main(int argc, char** argv) -{ - return 0; -} diff --git a/config.tests/mmrenderer/CMakeLists.txt b/config.tests/mmrenderer/CMakeLists.txt deleted file mode 100644 index 062a7eca5..000000000 --- a/config.tests/mmrenderer/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from mmrenderer.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_mmrenderer LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - mmrenderertest.cpp -) diff --git a/config.tests/mmrenderer/mmrenderertest.cpp b/config.tests/mmrenderer/mmrenderertest.cpp deleted file mode 100644 index 86e6c5d7d..000000000 --- a/config.tests/mmrenderer/mmrenderertest.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include <mm/renderer.h> - -int main(int argc,char **argv) -{ - mmr_connect(0); - - return 0; -} - diff --git a/config.tests/pulseaudio/CMakeLists.txt b/config.tests/pulseaudio/CMakeLists.txt deleted file mode 100644 index bf47d800b..000000000 --- a/config.tests/pulseaudio/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from pulseaudio.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_pulseaudio LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - pulseaudio.cpp -) diff --git a/config.tests/pulseaudio/pulseaudio.cpp b/config.tests/pulseaudio/pulseaudio.cpp deleted file mode 100644 index 8585abbdb..000000000 --- a/config.tests/pulseaudio/pulseaudio.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include <pulse/pulseaudio.h> -#include <pulse/glib-mainloop.h> - -#if !defined(PA_API_VERSION) || PA_API_VERSION-0 != 12 -# error "Incompatible PulseAudio API version" -#endif - -int main(int, char **) -{ - const char *headers = pa_get_headers_version(); - const char *library = pa_get_library_version(); - pa_glib_mainloop_new(0); - return (headers - library) * 0; -} diff --git a/config.tests/wmf/CMakeLists.txt b/config.tests/wmf/CMakeLists.txt deleted file mode 100644 index 907c7f600..000000000 --- a/config.tests/wmf/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -# Generated from wmf.pro. - -cmake_minimum_required(VERSION 3.16) -project(config_test_wmf LANGUAGES C CXX) - -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH) - set(CMAKE_SYSTEM_PREFIX_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH}") -endif() -if(DEFINED QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH) - set(CMAKE_SYSTEM_FRAMEWORK_PATH "${QT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH}") -endif() - -foreach(p ${QT_CONFIG_COMPILE_TEST_PACKAGES}) - find_package(${p}) -endforeach() - -if(QT_CONFIG_COMPILE_TEST_LIBRARIES) - link_libraries(${QT_CONFIG_COMPILE_TEST_LIBRARIES}) -endif() -if(QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS) - foreach(lib ${QT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS}) - if(TARGET ${lib}) - link_libraries(${lib}) - endif() - endforeach() -endif() - -add_executable(${PROJECT_NAME} - main.cpp -) diff --git a/config.tests/wmf/main.cpp b/config.tests/wmf/main.cpp deleted file mode 100644 index dfbeb852d..000000000 --- a/config.tests/wmf/main.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include <mfapi.h> -#include <mfidl.h> -#include <mferror.h> -#include <d3d9.h> -#include <evr9.h> -#include <mmdeviceapi.h> - -int main(int, char**) -{ - HRESULT hr = MENonFatalError; - if (SUCCEEDED(hr)) { - return 1; - } - return 0; -} diff --git a/dependencies.yaml b/dependencies.yaml index 7a0500145..94be4cb31 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,13 +1,13 @@ dependencies: ../tqtc-qtbase: - ref: 5d8e9a8415562ba004b38508d91e1fa0254c17d3 + ref: fc0e66eefe3a08428ca4a6e92c66f37ac126d3c4 required: true ../tqtc-qtdeclarative: - ref: ff0a47c8f267e905113b82c53af2742027f0eca6 + ref: 844f9b9b376838bcb44324984876f8bf99d85d38 required: false ../tqtc-qtquick3d: - ref: 730898b245931c55ff61ec3a892d600dfa37e175 + ref: 889447b1f96230f9043422a8ad1a6202854235d8 required: false ../tqtc-qtshadertools: - ref: ac330781f44d174045e7a6770ed81c1dd29691f8 + ref: 4c6749e750764297ee4237d9a1f2657b8313c4f7 required: true diff --git a/examples/multimedia/audiodevices/CMakeLists.txt b/examples/multimedia/audiodevices/CMakeLists.txt index 6d7907a84..1ceb158af 100644 --- a/examples/multimedia/audiodevices/CMakeLists.txt +++ b/examples/multimedia/audiodevices/CMakeLists.txt @@ -22,12 +22,9 @@ qt_add_executable(audiodevices ) set_target_properties(audiodevices PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_include_directories(audiodevices PUBLIC - ../shared + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in ) target_link_libraries(audiodevices PUBLIC diff --git a/examples/multimedia/audiodevices/Info.plist.in b/examples/multimedia/audiodevices/Info.plist.in new file mode 100644 index 000000000..7083ca46f --- /dev/null +++ b/examples/multimedia/audiodevices/Info.plist.in @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleLongVersionString</key> + <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> + + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + + <key>CFBundleGetInfoString</key> + <string>${MACOSX_BUNDLE_INFO_STRING}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + + <key>NSMicrophoneUsageDescription</key> + <string>Qt Multimedia Example</string> + + <key>NSCameraUsageDescription</key> + <string>Qt Multimedia Example</string> + + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> +</dict> +</plist> diff --git a/examples/multimedia/audiodevices/audiodevices.cpp b/examples/multimedia/audiodevices/audiodevices.cpp index 8fceba4fb..714613538 100644 --- a/examples/multimedia/audiodevices/audiodevices.cpp +++ b/examples/multimedia/audiodevices/audiodevices.cpp @@ -35,7 +35,6 @@ AudioDevicesBase::~AudioDevicesBase() = default; AudioTest::AudioTest(QWidget *parent) : AudioDevicesBase(parent), m_devices(new QMediaDevices(this)) { m_devices->videoInputs(); - qDebug() << "<<<<<<<<<<<<<<<<<<"; QMediaFormat().supportedFileFormats(QMediaFormat::Encode); connect(testButton, &QPushButton::clicked, this, &AudioTest::test); connect(modeBox, QOverload<int>::of(&QComboBox::activated), this, &AudioTest::modeChanged); @@ -137,19 +136,19 @@ void AudioTest::populateTable() allFormatsTable->setRowCount(row + 1); QTableWidgetItem *sampleTypeItem = new QTableWidgetItem(toString(sampleFormat)); - allFormatsTable->setItem(row, 2, sampleTypeItem); + allFormatsTable->setItem(row, 0, sampleTypeItem); QTableWidgetItem *sampleRateItem = new QTableWidgetItem(QStringLiteral("%1 - %2") .arg(m_deviceInfo.minimumSampleRate()) .arg(m_deviceInfo.maximumSampleRate())); - allFormatsTable->setItem(row, 0, sampleRateItem); + allFormatsTable->setItem(row, 1, sampleRateItem); QTableWidgetItem *channelsItem = new QTableWidgetItem(QStringLiteral("%1 - %2") .arg(m_deviceInfo.minimumChannelCount()) .arg(m_deviceInfo.maximumChannelCount())); - allFormatsTable->setItem(row, 1, channelsItem); + allFormatsTable->setItem(row, 2, channelsItem); ++row; } diff --git a/examples/multimedia/audiooutput/audiooutput.cpp b/examples/multimedia/audiooutput/audiooutput.cpp index 809b79293..bf45e2808 100644 --- a/examples/multimedia/audiooutput/audiooutput.cpp +++ b/examples/multimedia/audiooutput/audiooutput.cpp @@ -192,7 +192,8 @@ void AudioTest::updateAudioDevices() void AudioTest::toggleMode() { m_pushTimer->stop(); - m_audioOutput->stop(); + // Reset audiosink + m_audioOutput->reset(); toggleSuspendResume(); if (m_pullMode) { diff --git a/examples/multimedia/audiorecorder/CMakeLists.txt b/examples/multimedia/audiorecorder/CMakeLists.txt index fc067aeaf..0c8505819 100644 --- a/examples/multimedia/audiorecorder/CMakeLists.txt +++ b/examples/multimedia/audiorecorder/CMakeLists.txt @@ -27,10 +27,6 @@ set_target_properties(audiorecorder PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in ) -target_include_directories(audiorecorder PUBLIC - ../shared -) - target_link_libraries(audiorecorder PUBLIC Qt::Core Qt::Gui @@ -38,12 +34,6 @@ target_link_libraries(audiorecorder PUBLIC Qt::Widgets ) -if(WIN32) - target_include_directories(audiorecorder PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ) -endif() - install(TARGETS audiorecorder RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" diff --git a/examples/multimedia/audiosource/CMakeLists.txt b/examples/multimedia/audiosource/CMakeLists.txt index 730743f42..07dda1bd1 100644 --- a/examples/multimedia/audiosource/CMakeLists.txt +++ b/examples/multimedia/audiosource/CMakeLists.txt @@ -25,10 +25,6 @@ set_target_properties(audiosource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in ) -target_include_directories(audiosource PUBLIC - ../shared -) - target_link_libraries(audiosource PUBLIC Qt::Core Qt::Gui diff --git a/examples/multimedia/declarative-camera/CMakeLists.txt b/examples/multimedia/declarative-camera/CMakeLists.txt index 7e55b22a4..677db567d 100644 --- a/examples/multimedia/declarative-camera/CMakeLists.txt +++ b/examples/multimedia/declarative-camera/CMakeLists.txt @@ -24,10 +24,6 @@ set_target_properties(declarative-camera PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in ) -target_include_directories(declarative-camera PUBLIC - ../shared -) - target_link_libraries(declarative-camera PUBLIC Qt::Core Qt::Gui diff --git a/examples/multimedia/declarative-camera/PhotoCaptureControls.qml b/examples/multimedia/declarative-camera/PhotoCaptureControls.qml index 60191dd38..823bcc747 100644 --- a/examples/multimedia/declarative-camera/PhotoCaptureControls.qml +++ b/examples/multimedia/declarative-camera/PhotoCaptureControls.qml @@ -117,6 +117,7 @@ FocusScope { currentZoom: captureControls.captureSession.camera.zoomFactor maximumZoom: captureControls.captureSession.camera.maximumZoomFactor + minimumZoom: captureControls.captureSession.camera.minimumZoomFactor onZoomTo: (target) => captureControls.captureSession.camera.zoomFactor = target } diff --git a/examples/multimedia/declarative-camera/VideoCaptureControls.qml b/examples/multimedia/declarative-camera/VideoCaptureControls.qml index bbd832dd0..6adea2fb1 100644 --- a/examples/multimedia/declarative-camera/VideoCaptureControls.qml +++ b/examples/multimedia/declarative-camera/VideoCaptureControls.qml @@ -99,6 +99,7 @@ FocusScope { currentZoom: captureControls.captureSession.camera.zoomFactor maximumZoom: captureControls.captureSession.camera.maximumZoomFactor + minimumZoom: captureControls.captureSession.camera.minimumZoomFactor onZoomTo: (target) => captureControls.captureSession.camera.zoomFactor = target } diff --git a/examples/multimedia/declarative-camera/ZoomControl.qml b/examples/multimedia/declarative-camera/ZoomControl.qml index c60495fd0..8f6353b93 100644 --- a/examples/multimedia/declarative-camera/ZoomControl.qml +++ b/examples/multimedia/declarative-camera/ZoomControl.qml @@ -7,9 +7,10 @@ Item { id : zoomControl property real currentZoom : 1 property real maximumZoom : 1 + property real minimumZoom : 1 signal zoomTo(real target) - visible: zoomControl.maximumZoom > 1 + visible: zoomControl.maximumZoom > zoomControl.minimumZoom MouseArea { id : mouseArea @@ -26,7 +27,7 @@ Item { onPositionChanged: { if (pressed) { var target = initialZoom * Math.pow(5, (initialPos-mouseY)/zoomControl.height); - target = Math.max(1, Math.min(target, zoomControl.maximumZoom)) + target = Math.max(zoomControl.minimumZoom, Math.min(target, zoomControl.maximumZoom)) zoomControl.zoomTo(target) } } @@ -53,7 +54,7 @@ Item { Rectangle { id: groove x : 0 - y : parent.height * (1.0 - (zoomControl.currentZoom-1.0) / (zoomControl.maximumZoom-1.0)) + y : parent.height * (1.0 - (zoomControl.currentZoom-zoomControl.minimumZoom) / (zoomControl.maximumZoom-zoomControl.minimumZoom)) width: parent.width height: parent.height - y smooth: true diff --git a/examples/multimedia/player/player.cpp b/examples/multimedia/player/player.cpp index c674554e8..2c0459e58 100644 --- a/examples/multimedia/player/player.cpp +++ b/examples/multimedia/player/player.cpp @@ -108,7 +108,10 @@ Player::Player(QWidget *parent) : QWidget(parent) connect(controls, &PlayerControls::changeRate, m_player, &QMediaPlayer::setPlaybackRate); connect(controls, &PlayerControls::stop, m_videoWidget, QOverload<>::of(&QVideoWidget::update)); - connect(m_player, &QMediaPlayer::playbackStateChanged, controls, &PlayerControls::setState); + connect(m_player, &QMediaPlayer::playbackStateChanged, controls, + [controls](QMediaPlayer::PlaybackState arg) { + controls->setState(arg); + }); connect(m_audioOutput, &QAudioOutput::volumeChanged, controls, &PlayerControls::setVolume); connect(m_audioOutput, &QAudioOutput::mutedChanged, controls, &PlayerControls::setMuted); @@ -121,12 +124,16 @@ Player::Player(QWidget *parent) : QWidget(parent) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_audioOutputCombo = new QComboBox(this); - m_audioOutputCombo->addItem(QStringLiteral("Default"), QVariant::fromValue(QAudioDevice())); - for (auto &deviceInfo : QMediaDevices::audioOutputs()) - m_audioOutputCombo->addItem(deviceInfo.description(), QVariant::fromValue(deviceInfo)); + controlLayout->addWidget(m_audioOutputCombo); + + updateAudioDevices(); + connect(m_audioOutputCombo, QOverload<int>::of(&QComboBox::activated), this, &Player::audioOutputChanged); - controlLayout->addWidget(m_audioOutputCombo); + + QObject::connect(&m_mediaDevices, &QMediaDevices::audioOutputsChanged, this, [this] { + updateAudioDevices(); + }); #endif layout->addLayout(controlLayout); @@ -518,6 +525,15 @@ void Player::updateDurationInfo(qint64 currentInfo) m_labelDuration->setText(tStr); } +void Player::updateAudioDevices() +{ + m_audioOutputCombo->clear(); + + m_audioOutputCombo->addItem(QStringLiteral("Default"), QVariant::fromValue(QAudioDevice())); + for (auto &deviceInfo : QMediaDevices::audioOutputs()) + m_audioOutputCombo->addItem(deviceInfo.description(), QVariant::fromValue(deviceInfo)); +} + void Player::audioOutputChanged(int index) { auto device = m_audioOutputCombo->itemData(index).value<QAudioDevice>(); diff --git a/examples/multimedia/player/player.h b/examples/multimedia/player/player.h index 66b1b8fab..f20995fcf 100644 --- a/examples/multimedia/player/player.h +++ b/examples/multimedia/player/player.h @@ -8,6 +8,7 @@ #include <QMediaMetaData> #include <QMediaPlayer> +#include <QMediaDevices> #include <QWidget> QT_BEGIN_NAMESPACE @@ -69,6 +70,9 @@ private: void setStatusInfo(const QString &info); void handleCursor(QMediaPlayer::MediaStatus status); void updateDurationInfo(qint64 currentInfo); + + void updateAudioDevices(); + QString trackName(const QMediaMetaData &metaData, int index); QMediaPlayer *m_player = nullptr; @@ -92,6 +96,8 @@ private: QString m_statusInfo; qint64 m_duration; + QMediaDevices m_mediaDevices; + QWidget *m_metaDataFields[QMediaMetaData::NumMetaData] = {}; QLabel *m_metaDataLabels[QMediaMetaData::NumMetaData] = {}; }; diff --git a/examples/multimedia/player/playercontrols.cpp b/examples/multimedia/player/playercontrols.cpp index 4933bf8cf..9a6c81dd2 100644 --- a/examples/multimedia/player/playercontrols.cpp +++ b/examples/multimedia/player/playercontrols.cpp @@ -17,10 +17,12 @@ PlayerControls::PlayerControls(QWidget *parent) : QWidget(parent) connect(m_playButton, &QAbstractButton::clicked, this, &PlayerControls::playClicked); + m_pauseButton = new QToolButton(this); + m_pauseButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + connect(m_pauseButton, &QAbstractButton::clicked, this, &PlayerControls::pauseClicked); + m_stopButton = new QToolButton(this); m_stopButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); - m_stopButton->setEnabled(false); - connect(m_stopButton, &QAbstractButton::clicked, this, &PlayerControls::stop); m_nextButton = new QToolButton(this); @@ -56,10 +58,13 @@ PlayerControls::PlayerControls(QWidget *parent) : QWidget(parent) connect(m_rateBox, QOverload<int>::of(&QComboBox::activated), this, &PlayerControls::updateRate); + setState(QMediaPlayer::StoppedState, /*force=*/true); + QBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_stopButton); layout->addWidget(m_previousButton); + layout->addWidget(m_pauseButton); layout->addWidget(m_playButton); layout->addWidget(m_nextButton); layout->addWidget(m_muteButton); @@ -73,23 +78,30 @@ QMediaPlayer::PlaybackState PlayerControls::state() const return m_playerState; } -void PlayerControls::setState(QMediaPlayer::PlaybackState state) +void PlayerControls::setState(QMediaPlayer::PlaybackState state, bool force) { - if (state != m_playerState) { + if (state != m_playerState || force) { m_playerState = state; + QColor baseColor = palette().color(QPalette::Base); + QString inactiveStyleSheet = QStringLiteral("background-color: %1").arg(baseColor.name()); + QString defaultStyleSheet = QStringLiteral(""); + switch (state) { case QMediaPlayer::StoppedState: - m_stopButton->setEnabled(false); - m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + m_stopButton->setStyleSheet(inactiveStyleSheet); + m_playButton->setStyleSheet(defaultStyleSheet); + m_pauseButton->setStyleSheet(defaultStyleSheet); break; case QMediaPlayer::PlayingState: - m_stopButton->setEnabled(true); - m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + m_stopButton->setStyleSheet(defaultStyleSheet); + m_playButton->setStyleSheet(inactiveStyleSheet); + m_pauseButton->setStyleSheet(defaultStyleSheet); break; case QMediaPlayer::PausedState: - m_stopButton->setEnabled(true); - m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + m_stopButton->setStyleSheet(defaultStyleSheet); + m_playButton->setStyleSheet(defaultStyleSheet); + m_pauseButton->setStyleSheet(inactiveStyleSheet); break; } } @@ -129,15 +141,12 @@ void PlayerControls::setMuted(bool muted) void PlayerControls::playClicked() { - switch (m_playerState) { - case QMediaPlayer::StoppedState: - case QMediaPlayer::PausedState: - emit play(); - break; - case QMediaPlayer::PlayingState: - emit pause(); - break; - } + emit play(); +} + +void PlayerControls::pauseClicked() +{ + emit pause(); } void PlayerControls::muteClicked() diff --git a/examples/multimedia/player/playercontrols.h b/examples/multimedia/player/playercontrols.h index 72dddd68f..322b4ca24 100644 --- a/examples/multimedia/player/playercontrols.h +++ b/examples/multimedia/player/playercontrols.h @@ -26,7 +26,7 @@ public: qreal playbackRate() const; public slots: - void setState(QMediaPlayer::PlaybackState state); + void setState(QMediaPlayer::PlaybackState state, bool force = false); void setVolume(float volume); void setMuted(bool muted); void setPlaybackRate(float rate); @@ -43,6 +43,7 @@ signals: private slots: void playClicked(); + void pauseClicked(); void muteClicked(); void updateRate(); void onVolumeSliderValueChanged(); @@ -51,6 +52,7 @@ private: QMediaPlayer::PlaybackState m_playerState = QMediaPlayer::StoppedState; bool m_playerMuted = false; QAbstractButton *m_playButton = nullptr; + QAbstractButton *m_pauseButton = nullptr; QAbstractButton *m_stopButton = nullptr; QAbstractButton *m_nextButton = nullptr; QAbstractButton *m_previousButton = nullptr; diff --git a/src/3rdparty/ffmpeg/qt_attribution.json b/src/3rdparty/ffmpeg/qt_attribution.json index af4a7c743..dfb7728b2 100644 --- a/src/3rdparty/ffmpeg/qt_attribution.json +++ b/src/3rdparty/ffmpeg/qt_attribution.json @@ -6,9 +6,9 @@ "Description": "FFmpeg is a collection of libraries and tools to process multimedia content such as audio, video, subtitles, and related metadata.", "QtUsage": "The FFmpeg media backend uses the \\l {https://ffmpeg.org}{FFmpeg framework}. FFmpeg is licensed under LGPLv2.1 or later versions of the licenses. Note that, while FFmpeg also features some optional components available under GPL or LGPLv3, the binaries that ship with Qt in the online installer do not contain these components. See the \\l {https://ffmpeg.org/legal.html}{FFmpeg licensing page} for further details.", "Homepage": "https://ffmpeg.org/", - "DownloadLocation": "https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n6.1.zip", + "DownloadLocation": "https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.2.tar.gz", "SecurityCritical": true, - "Version": "n6.1", + "Version": "n7.0.2", "LicenseId": "LGPL-2.1-or-later AND BSD-3-Clause AND BSD-2-Clause AND BSD-Source-Code AND ISC AND MIT AND MPL-2.0", "License": "GNU Lesser General Public License v2.1 or later and BSD 3-Clause \"New\" or \"Revised\" License and BSD 2-Clause \"Simplified\" License and BSD Source Code Attribution and ISC License and MIT License and Mozilla Public License 2.0", "Copyright": "Copyright (c) 2000-2023 the FFmpeg developers" diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java index 1a1e2f995..7544facad 100644 --- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java +++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java @@ -4,20 +4,16 @@ package org.qtproject.qt.android.multimedia; import java.util.ArrayList; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; +import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; +import android.os.Handler; +import android.os.Looper; import android.util.Log; public class QtAudioDeviceManager @@ -25,10 +21,12 @@ public class QtAudioDeviceManager private static final String TAG = "QtAudioDeviceManager"; static private AudioManager m_audioManager = null; static private final AudioDevicesReceiver m_audioDevicesReceiver = new AudioDevicesReceiver(); + static private Handler handler = new Handler(Looper.getMainLooper()); static private AudioRecord m_recorder = null; static private AudioTrack m_streamPlayer = null; static private Thread m_streamingThread = null; static private boolean m_isStreaming = false; + static private boolean m_useSpeaker = false; static private final int m_sampleRate = 8000; static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO; static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT; @@ -37,36 +35,37 @@ public class QtAudioDeviceManager public static native void onAudioInputDevicesUpdated(); public static native void onAudioOutputDevicesUpdated(); - static private class AudioDevicesReceiver extends BroadcastReceiver - { + static private void updateDeviceList() { + onAudioInputDevicesUpdated(); + onAudioOutputDevicesUpdated(); + if (m_useSpeaker) { + final AudioDeviceInfo[] audioDevices = + m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + setAudioOutput(getModeForSpeaker(audioDevices), false, true); + } + } + + private static class AudioDevicesReceiver extends AudioDeviceCallback { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + updateDeviceList(); + } + @Override - public void onReceive(Context context, Intent intent) { - onAudioInputDevicesUpdated(); - onAudioOutputDevicesUpdated(); + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + updateDeviceList(); } } - public static void registerAudioHeadsetStateReceiver(Context context) + + public static void registerAudioHeadsetStateReceiver() { - IntentFilter audioDevicesFilter = new IntentFilter(); - audioDevicesFilter.addAction(AudioManager.ACTION_HEADSET_PLUG); - audioDevicesFilter.addAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); - audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); - audioDevicesFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - audioDevicesFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - audioDevicesFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - audioDevicesFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - audioDevicesFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - audioDevicesFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); - - context.registerReceiver(m_audioDevicesReceiver, audioDevicesFilter); + m_audioManager.registerAudioDeviceCallback(m_audioDevicesReceiver, handler); } - public static void unregisterAudioHeadsetStateReceiver(Context context) + public static void unregisterAudioHeadsetStateReceiver() { - context.unregisterReceiver(m_audioDevicesReceiver); + m_audioManager.unregisterAudioDeviceCallback(m_audioDevicesReceiver); } static public void setContext(Context context) @@ -220,8 +219,27 @@ public class QtAudioDeviceManager return ret; } + private static int getModeForSpeaker(AudioDeviceInfo[] audioDevices) + { + // If we want to force device to use speaker when Bluetooth or Wiread headset is connected, + // we need to use MODE_IN_COMMUNICATION. Otherwise the MODE_NORMAL can be used. + for (AudioDeviceInfo deviceInfo : audioDevices) { + switch (deviceInfo.getType()) { + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return AudioManager.MODE_IN_COMMUNICATION; + default: break; + } + } + return AudioManager.MODE_NORMAL; + } + + private static boolean setAudioOutput(int id) { + m_useSpeaker = false; final AudioDeviceInfo[] audioDevices = m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); for (AudioDeviceInfo deviceInfo : audioDevices) { @@ -233,7 +251,8 @@ public class QtAudioDeviceManager setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, true, false); return true; case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: - setAudioOutput(AudioManager.STREAM_MUSIC, false, true); + m_useSpeaker = true; + setAudioOutput(getModeForSpeaker(audioDevices), false, true); return true; case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: 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..d56708616 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 @@ -2,9 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only package org.qtproject.qt.android.multimedia; -import org.qtproject.qt.android.multimedia.QtVideoDeviceManager; -import org.qtproject.qt.android.multimedia.QtExifDataHandler; - import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; @@ -20,14 +17,11 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; -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; -import android.media.MediaFormat; import java.lang.Thread; import java.util.ArrayList; import java.util.List; @@ -57,11 +51,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 +256,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 +278,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) { @@ -334,8 +342,9 @@ public class QtCamera2 { mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mAFMode); mPreviewRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); if (mZoomFactor != 1.0f) - mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion()); - + updateZoom(mPreviewRequestBuilder); + if (mFpsRange != null) + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mFpsRange); mPreviewRequest = mPreviewRequestBuilder.build(); mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler); mIsStarted = true; @@ -375,7 +384,7 @@ public class QtCamera2 { captureBuilder.addTarget(mCapturedPhotoReader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode); if (mZoomFactor != 1.0f) - captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion()); + updateZoom(captureBuilder); CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @@ -440,6 +449,15 @@ public class QtCamera2 { activePixels.height() - croppedHeight/2); } + private void updateZoom(CaptureRequest.Builder requBuilder) + { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) { + requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion()); + } else { + requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, mZoomFactor); + } + } + public void zoomTo(float factor) { synchronized (mStartMutex) { @@ -450,7 +468,7 @@ public class QtCamera2 { return; } - mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion()); + updateZoom(mPreviewRequestBuilder); mPreviewRequest = mPreviewRequestBuilder.build(); try { diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java index bfac6670f..3c40d32a6 100644 --- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java +++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java @@ -66,7 +66,7 @@ public class QtMultimediaUtils static int getDeviceOrientation() { - return m_orientationListener.deviceOrientation; + return OrientationListener.deviceOrientation; } static String getDefaultMediaDirectory(int type) @@ -125,24 +125,25 @@ public class QtMultimediaUtils return codecs; } -public static String getMimeType(Context context, String url) -{ - Uri parsedUri = Uri.parse(url); - String type = null; - - try { - String scheme = parsedUri.getScheme(); - if (scheme != null && scheme.contains("content")) { - ContentResolver cR = context.getContentResolver(); - type = cR.getType(parsedUri); - } else { - String extension = MimeTypeMap.getFileExtensionFromUrl(url); - if (extension != null) + public static String getMimeType(Context context, String url) + { + Uri parsedUri = Uri.parse(url); + String type = null; + + try { + String scheme = parsedUri.getScheme(); + if (scheme != null && scheme.contains("content")) { + ContentResolver cR = context.getContentResolver(); + type = cR.getType(parsedUri); + } else { + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - } - } catch (Exception e) { - Log.e(QtTAG, "getMimeType(): " + e.toString()); + } + } catch (Exception e) { + Log.e(QtTAG, "getMimeType(): " + e.toString()); + } + return type; } - return type; -} } + 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..0394f8f0d 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 @@ -13,6 +13,7 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.MediaCodecList; import android.media.MediaCodecInfo; +import android.os.Build; import android.util.Range; import android.util.Size; import android.util.Log; @@ -120,13 +121,25 @@ public class QtVideoDeviceManager { return fps; } - public float getMaxZoom(String cameraId) { + public float[] getZoomRange(String cameraId) { - float maxZoom = 1.0f; + float[] zoomRange = { 1.0f, 1.0f }; final CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); - if (characteristics != null) - maxZoom = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); - return maxZoom; + if (characteristics == null) + return zoomRange; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + final Range<Float> range = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE); + if (range != null) { + zoomRange[0] = range.getLower(); + zoomRange[1] = range.getUpper(); + } + } + + if (zoomRange[1] == 1.0f) + zoomRange[1] = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + + return zoomRange; } public Rect getActiveArraySize(String cameraId) { @@ -137,6 +150,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 +162,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) { @@ -217,6 +232,23 @@ public class QtVideoDeviceManager { return supportedFlashModesList.toArray(ret); } + static public boolean isEmulator() + { + return ((Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.HARDWARE.contains("goldfish") + || Build.HARDWARE.contains("ranchu") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || Build.PRODUCT.contains("sdk") + || Build.PRODUCT.contains("vbox86p") + || Build.PRODUCT.contains("emulator") + || Build.PRODUCT.contains("simulator")); + } + public boolean isTorchModeSupported(String cameraId) { boolean ret = false; final CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt index 26bd22fc7..2d228bbfd 100644 --- a/src/multimedia/CMakeLists.txt +++ b/src/multimedia/CMakeLists.txt @@ -37,23 +37,24 @@ qt_internal_add_module(Multimedia camera/qcamera.cpp camera/qcamera.h camera/qcamera_p.h camera/qcameradevice.cpp camera/qcameradevice.h camera/qcameradevice_p.h camera/qimagecapture.cpp camera/qimagecapture.h + platform/qgstreamer_platformspecificinterface.cpp platform/qgstreamer_platformspecificinterface_p.h platform/qplatformaudiodecoder.cpp platform/qplatformaudiodecoder_p.h platform/qplatformaudioinput_p.h platform/qplatformaudiooutput_p.h platform/qplatformaudioresampler_p.h platform/qplatformcamera.cpp platform/qplatformcamera_p.h - platform/qplatformvideosource.cpp platform/qplatformvideosource_p.h - platform/qplatformsurfacecapture.cpp platform/qplatformsurfacecapture_p.h platform/qplatformimagecapture.cpp platform/qplatformimagecapture_p.h platform/qplatformmediacapture.cpp platform/qplatformmediacapture_p.h platform/qplatformmediadevices.cpp platform/qplatformmediadevices_p.h - platform/qplatformmediarecorder.cpp platform/qplatformmediarecorder_p.h platform/qplatformmediaformatinfo.cpp platform/qplatformmediaformatinfo_p.h platform/qplatformmediaintegration.cpp platform/qplatformmediaintegration_p.h platform/qplatformmediaplayer.cpp platform/qplatformmediaplayer_p.h platform/qplatformmediaplugin.cpp platform/qplatformmediaplugin_p.h + platform/qplatformmediarecorder.cpp platform/qplatformmediarecorder_p.h + platform/qplatformsurfacecapture.cpp platform/qplatformsurfacecapture_p.h platform/qplatformvideodevices.cpp platform/qplatformvideodevices_p.h platform/qplatformvideosink.cpp platform/qplatformvideosink_p.h + platform/qplatformvideosource.cpp platform/qplatformvideosource_p.h playback/qmediaplayer.cpp playback/qmediaplayer.h playback/qmediaplayer_p.h qmediadevices.cpp qmediadevices.h qmediaenumdebug.h @@ -65,7 +66,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 video/qabstractvideobuffer.cpp video/qabstractvideobuffer_p.h diff --git a/src/multimedia/alsa/qalsaaudiodevice.cpp b/src/multimedia/alsa/qalsaaudiodevice.cpp index f5d4a2209..893375270 100644 --- a/src/multimedia/alsa/qalsaaudiodevice.cpp +++ b/src/multimedia/alsa/qalsaaudiodevice.cpp @@ -37,55 +37,35 @@ QAlsaAudioDeviceInfo::QAlsaAudioDeviceInfo(const QByteArray &dev, const QString minimumSampleRate = 8000; maximumSampleRate = 48000; - supportedSampleFormats << QAudioFormat::UInt8 << QAudioFormat::Int16 << QAudioFormat::Int32 << QAudioFormat::Float; + supportedSampleFormats = { + QAudioFormat::UInt8, + QAudioFormat::Int16, + QAudioFormat::Int32, + QAudioFormat::Float, + }; preferredFormat.setChannelCount(mode == QAudioDevice::Input ? 1 : 2); preferredFormat.setSampleFormat(QAudioFormat::Float); preferredFormat.setSampleRate(48000); } -QAlsaAudioDeviceInfo::~QAlsaAudioDeviceInfo() -{ -} +QAlsaAudioDeviceInfo::~QAlsaAudioDeviceInfo() = default; void QAlsaAudioDeviceInfo::checkSurround() { + if (mode != QAudioDevice::Output) + return; + surround40 = false; surround51 = false; surround71 = false; - void **hints, **n; - char *name, *descr, *io; - - if(snd_device_name_hint(-1, "pcm", &hints) < 0) - return; - - n = hints; - - while (*n != NULL) { - name = snd_device_name_get_hint(*n, "NAME"); - descr = snd_device_name_get_hint(*n, "DESC"); - io = snd_device_name_get_hint(*n, "IOID"); - if((name != NULL) && (descr != NULL)) { - QString deviceName = QLatin1String(name); - if (mode == QAudioDevice::Output) { - if(deviceName.contains(QLatin1String("surround40"))) - surround40 = true; - if(deviceName.contains(QLatin1String("surround51"))) - surround51 = true; - if(deviceName.contains(QLatin1String("surround71"))) - surround71 = true; - } - } - if(name != NULL) - free(name); - if(descr != NULL) - free(descr); - if(io != NULL) - free(io); - ++n; - } - snd_device_name_free_hint(hints); + if (id.startsWith(QLatin1String("surround40"))) + surround40 = true; + if (id.startsWith(QLatin1String("surround51"))) + surround51 = true; + if (id.startsWith(QLatin1String("surround71"))) + surround71 = true; } QT_END_NAMESPACE diff --git a/src/multimedia/alsa/qalsaaudiodevice_p.h b/src/multimedia/alsa/qalsaaudiodevice_p.h index f82ea4f5a..dcbc9e692 100644 --- a/src/multimedia/alsa/qalsaaudiodevice_p.h +++ b/src/multimedia/alsa/qalsaaudiodevice_p.h @@ -38,9 +38,9 @@ public: private: void checkSurround(); - bool surround40; - bool surround51; - bool surround71; + bool surround40{}; + bool surround51{}; + bool surround71{}; }; QT_END_NAMESPACE diff --git a/src/multimedia/alsa/qalsaaudiosink.cpp b/src/multimedia/alsa/qalsaaudiosink.cpp index 98a68861f..e515219a2 100644 --- a/src/multimedia/alsa/qalsaaudiosink.cpp +++ b/src/multimedia/alsa/qalsaaudiosink.cpp @@ -30,13 +30,13 @@ QAlsaAudioSink::QAlsaAudioSink(const QByteArray &device, QObject *parent) m_device = device; timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(userFeed())); + connect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed); } QAlsaAudioSink::~QAlsaAudioSink() { close(); - disconnect(timer, SIGNAL(timeout())); + disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed); QCoreApplication::processEvents(); delete timer; } @@ -130,6 +130,7 @@ int QAlsaAudioSink::setFormat() pcmformat = SND_PCM_FORMAT_FLOAT_BE; else pcmformat = SND_PCM_FORMAT_FLOAT_LE; + break; default: break; } diff --git a/src/multimedia/alsa/qalsaaudiosink_p.h b/src/multimedia/alsa/qalsaaudiosink_p.h index 7e8836f96..0f5a5aa5a 100644 --- a/src/multimedia/alsa/qalsaaudiosink_p.h +++ b/src/multimedia/alsa/qalsaaudiosink_p.h @@ -96,7 +96,6 @@ private: char* audioBuffer = nullptr; snd_pcm_t* handle = nullptr; snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; - snd_pcm_format_t pcmformat = SND_PCM_FORMAT_S16; snd_pcm_hw_params_t *hwparams = nullptr; qreal m_volume = 1.0f; }; diff --git a/src/multimedia/alsa/qalsaaudiosource.cpp b/src/multimedia/alsa/qalsaaudiosource.cpp index ce099463d..ebf6e24e2 100644 --- a/src/multimedia/alsa/qalsaaudiosource.cpp +++ b/src/multimedia/alsa/qalsaaudiosource.cpp @@ -16,7 +16,6 @@ #include <QtCore/qvarlengtharray.h> #include <QtMultimedia/private/qaudiohelpers_p.h> #include "qalsaaudiosource_p.h" -#include "qalsaaudiodevice_p.h" QT_BEGIN_NAMESPACE @@ -45,13 +44,13 @@ QAlsaAudioSource::QAlsaAudioSource(const QByteArray &device, QObject *parent) m_device = device; timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(userFeed())); + connect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed); } QAlsaAudioSource::~QAlsaAudioSource() { close(); - disconnect(timer, SIGNAL(timeout())); + disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed); QCoreApplication::processEvents(); delete timer; } @@ -143,21 +142,22 @@ int QAlsaAudioSource::setFormat() break; case QAudioFormat::Int16: if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian) - pcmformat = SND_PCM_FORMAT_S16_LE; - else pcmformat = SND_PCM_FORMAT_S16_BE; + else + pcmformat = SND_PCM_FORMAT_S16_LE; break; case QAudioFormat::Int32: if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian) - pcmformat = SND_PCM_FORMAT_S32_LE; - else pcmformat = SND_PCM_FORMAT_S32_BE; + else + pcmformat = SND_PCM_FORMAT_S32_LE; break; case QAudioFormat::Float: if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian) - pcmformat = SND_PCM_FORMAT_FLOAT_LE; - else pcmformat = SND_PCM_FORMAT_FLOAT_BE; + else + pcmformat = SND_PCM_FORMAT_FLOAT_LE; + break; default: break; } @@ -370,7 +370,7 @@ bool QAlsaAudioSource::open() bytesAvailable = checkBytesReady(); if(pullMode) - connect(audioSource,SIGNAL(readyRead()),this,SLOT(userFeed())); + connect(audioSource, &QIODevice::readyRead, this, &QAlsaAudioSource::userFeed); // Step 6: Start audio processing chunks = buffer_size/period_size; diff --git a/src/multimedia/alsa/qalsamediadevices.cpp b/src/multimedia/alsa/qalsamediadevices.cpp index 5a133e9d1..9466fa0cd 100644 --- a/src/multimedia/alsa/qalsamediadevices.cpp +++ b/src/multimedia/alsa/qalsamediadevices.cpp @@ -13,6 +13,26 @@ QT_BEGIN_NAMESPACE +namespace { + +struct free_char +{ + void operator()(char *c) const { ::free(c); } +}; + +using unique_str = std::unique_ptr<char, free_char>; + +bool operator==(const unique_str &str, std::string_view sv) +{ + return std::string_view{ str.get() } == sv; +} +bool operator!=(const unique_str &str, std::string_view sv) +{ + return !(str == sv); +} + +} // namespace + QAlsaMediaDevices::QAlsaMediaDevices() : QPlatformMediaDevices() { @@ -22,52 +42,50 @@ static QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode) { QList<QAudioDevice> devices; - QByteArray filter; - // Create a list of all current audio devices that support mode - void **hints, **n; - char *name, *descr, *io; - bool hasDefault = false; - - if(snd_device_name_hint(-1, "pcm", &hints) < 0) { + void **hints; + if (snd_device_name_hint(-1, "pcm", &hints) < 0) { qWarning() << "no alsa devices available"; return devices; } - n = hints; - if(mode == QAudioDevice::Input) { - filter = "Input"; - } else { - filter = "Output"; - } + std::string_view filter = (mode == QAudioDevice::Input) ? "Input" : "Output"; - QAlsaAudioDeviceInfo* sysdefault = nullptr; + QAlsaAudioDeviceInfo *sysdefault = nullptr; - while (*n != NULL) { - name = snd_device_name_get_hint(*n, "NAME"); - if (name != 0 && qstrcmp(name, "null") != 0) { - descr = snd_device_name_get_hint(*n, "DESC"); - io = snd_device_name_get_hint(*n, "IOID"); - - if ((descr != NULL) && ((io == NULL) || (io == filter))) { - auto *infop = new QAlsaAudioDeviceInfo(name, QString::fromUtf8(descr), mode); - devices.append(infop->create()); - if (!hasDefault && strcmp(name, "default") == 0) { - infop->isDefault = true; - hasDefault = true; - } - else if (!sysdefault && !hasDefault && strcmp(name, "sysdefault") == 0) { - sysdefault = infop; - } + auto makeDeviceInfo = [&filter, mode](void *entry) -> QAlsaAudioDeviceInfo * { + unique_str name{ snd_device_name_get_hint(entry, "NAME") }; + if (name && name != "null") { + unique_str descr{ snd_device_name_get_hint(entry, "DESC") }; + unique_str io{ snd_device_name_get_hint(entry, "IOID") }; + + if (descr && (!io || (io == filter))) { + auto *infop = new QAlsaAudioDeviceInfo{ + name.get(), + QString::fromUtf8(descr.get()), + mode, + }; + return infop; } + } + return nullptr; + }; + + bool hasDefault = false; + void **n = hints; + while (*n != NULL) { + QAlsaAudioDeviceInfo *infop = makeDeviceInfo(*n++); - free(descr); - free(io); + if (infop) { + devices.append(infop->create()); + if (!hasDefault && infop->id.startsWith("default")) { + infop->isDefault = true; + hasDefault = true; + } + if (!sysdefault && infop->id.startsWith("sysdefault")) + sysdefault = infop; } - free(name); - ++n; } - snd_device_name_free_hint(hints); if (!hasDefault && sysdefault) { // Make "sysdefault" the default device if there is no "default" device exists @@ -75,11 +93,15 @@ static QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode) hasDefault = true; } if (!hasDefault && devices.size() > 0) { - auto infop = new QAlsaAudioDeviceInfo("default", QString(), QAudioDevice::Output); - infop->isDefault = true; - devices.prepend(infop->create()); + // forcefully declare the first device as "default" + QAlsaAudioDeviceInfo *infop = makeDeviceInfo(hints[0]); + if (infop) { + infop->isDefault = true; + devices.prepend(infop->create()); + } } + snd_device_name_free_hint(hints); return devices; } diff --git a/src/multimedia/android/qandroidmediadevices.cpp b/src/multimedia/android/qandroidmediadevices.cpp index 36da88280..9dd52ecd5 100644 --- a/src/multimedia/android/qandroidmediadevices.cpp +++ b/src/multimedia/android/qandroidmediadevices.cpp @@ -24,8 +24,7 @@ Q_DECLARE_JNI_CLASS(QtAudioDeviceManager, QAndroidMediaDevices::QAndroidMediaDevices() : QPlatformMediaDevices() { QJniObject::callStaticMethod<void>(QtJniTypes::className<QtJniTypes::QtAudioDeviceManager>(), - "registerAudioHeadsetStateReceiver", - QNativeInterface::QAndroidApplication::context()); + "registerAudioHeadsetStateReceiver"); } QAndroidMediaDevices::~QAndroidMediaDevices() @@ -34,8 +33,7 @@ QAndroidMediaDevices::~QAndroidMediaDevices() // the application. In such case it is probably not needed, but let's leave it for // compatibility with Android documentation QJniObject::callStaticMethod<void>(QtJniTypes::className<QtJniTypes::QtAudioDeviceManager>(), - "unregisterAudioHeadsetStateReceiver", - QNativeInterface::QAndroidApplication::context()); + "unregisterAudioHeadsetStateReceiver"); } QList<QAudioDevice> QAndroidMediaDevices::audioInputs() const diff --git a/src/multimedia/android/qopenslesengine.cpp b/src/multimedia/android/qopenslesengine.cpp index 90fa86a6f..33c69c058 100644 --- a/src/multimedia/android/qopenslesengine.cpp +++ b/src/multimedia/android/qopenslesengine.cpp @@ -116,7 +116,9 @@ QList<QAudioDevice> QOpenSLESEngine::availableDevices(QAudioDevice::Mode mode) jobjectArray devsArray = static_cast<jobjectArray>(devs.object()); const jint size = env->GetArrayLength(devsArray); for (int i = 0; i < size; ++i) { - QString val = QJniObject(env->GetObjectArrayElement(devsArray, i)).toString(); + auto devElement = env->GetObjectArrayElement(devsArray, i); + QString val = QJniObject(devElement).toString(); + env->DeleteLocalRef(devElement); int pos = val.indexOf(QStringLiteral(":")); devices << (new QOpenSLESDeviceInfo( val.left(pos).toUtf8(), val.mid(pos+1), mode))->create(); diff --git a/src/multimedia/audio/qaudiosink.cpp b/src/multimedia/audio/qaudiosink.cpp index d72efbf5d..b04508529 100644 --- a/src/multimedia/audio/qaudiosink.cpp +++ b/src/multimedia/audio/qaudiosink.cpp @@ -177,6 +177,12 @@ QIODevice* QAudioSink::start() Sets error() to QAudio::NoError, state() to QAudio::StoppedState and emit stateChanged() signal. + + \note On Linux, and Darwin, this operation synchronously drains the + underlying audio buffer, which may cause delays accordingly to the + buffer payload. To reset all the buffers immediately, use the method + \l reset instead. + \sa reset() */ void QAudioSink::stop() { diff --git a/src/multimedia/audio/qsamplecache_p.cpp b/src/multimedia/audio/qsamplecache_p.cpp index 825c79685..8406a8628 100644 --- a/src/multimedia/audio/qsamplecache_p.cpp +++ b/src/multimedia/audio/qsamplecache_p.cpp @@ -151,6 +151,10 @@ QSample* QSampleCache::requestSample(const QUrl& url) #endif } else { sample = *it; + if (sample->state() == QSample::Error && needsThreadStart) { + m_loadingThread.wait(); + m_loadingThread.start(); + } } sample->addRef(); @@ -219,7 +223,7 @@ void QSampleCache::refresh(qint64 usageChange) << "new usage =" << m_usage; if (m_usage > m_capacity) - qWarning() << "QSampleCache: usage[" << m_usage << " out of limit[" << m_capacity << "]"; + qWarning() << "QSampleCache: usage" << m_usage << "out of limit" << m_capacity; } // Called in both threads @@ -247,9 +251,9 @@ void QSample::loadIfNecessary() QMutexLocker locker(&m_mutex); if (m_state == QSample::Error || m_state == QSample::Creating) { m_state = QSample::Loading; - QMetaObject::invokeMethod(this, "load", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &QSample::load, Qt::QueuedConnection); } else { - qobject_cast<QSampleCache*>(m_parent)->loadingRelease(); + m_parent->loadingRelease(); } } @@ -357,12 +361,13 @@ void QSample::load() Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); #endif qCDebug(qLcSampleCache) << "QSample: load [" << m_url << "]"; - m_stream = m_parent->networkAccessManager().get(QNetworkRequest(m_url)); - connect(m_stream, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(loadingError(QNetworkReply::NetworkError))); + QNetworkReply *reply = m_parent->networkAccessManager().get(QNetworkRequest(m_url)); + m_stream = reply; + connect(reply, &QNetworkReply::errorOccurred, this, &QSample::loadingError); m_waveDecoder = new QWaveDecoder(m_stream); - connect(m_waveDecoder, SIGNAL(formatKnown()), SLOT(decoderReady())); - connect(m_waveDecoder, SIGNAL(parsingError()), SLOT(decoderError())); - connect(m_waveDecoder, SIGNAL(readyRead()), SLOT(readSample())); + connect(m_waveDecoder, &QWaveDecoder::formatKnown, this, &QSample::decoderReady); + connect(m_waveDecoder, &QWaveDecoder::parsingError, this, &QSample::decoderError); + connect(m_waveDecoder, &QIODevice::readyRead, this, &QSample::readSample); m_waveDecoder->open(QIODevice::ReadOnly); } @@ -376,8 +381,8 @@ void QSample::loadingError(QNetworkReply::NetworkError errorCode) qCDebug(qLcSampleCache) << "QSample: loading error" << errorCode; cleanup(); m_state = QSample::Error; - qobject_cast<QSampleCache*>(m_parent)->loadingRelease(); - emit error(); + m_parent->loadingRelease(); + emit error(this); } // Called in loading thread @@ -390,8 +395,8 @@ void QSample::decoderError() qCDebug(qLcSampleCache) << "QSample: decoder error"; cleanup(); m_state = QSample::Error; - qobject_cast<QSampleCache*>(m_parent)->loadingRelease(); - emit error(); + m_parent->loadingRelease(); + emit error(this); } // Called in loading thread from decoder when sample is done. Locked already. @@ -404,8 +409,8 @@ void QSample::onReady() qCDebug(qLcSampleCache) << "QSample: load ready format:" << m_audioFormat; cleanup(); m_state = QSample::Ready; - qobject_cast<QSampleCache*>(m_parent)->loadingRelease(); - emit ready(); + m_parent->loadingRelease(); + emit ready(this); } // Called in application thread, then moved to loader thread diff --git a/src/multimedia/audio/qsamplecache_p.h b/src/multimedia/audio/qsamplecache_p.h index 3ba0c420c..22f6baa7b 100644 --- a/src/multimedia/audio/qsamplecache_p.h +++ b/src/multimedia/audio/qsamplecache_p.h @@ -15,15 +15,16 @@ // We mean it. // +#include <QtCore/qmap.h> +#include <QtCore/qmutex.h> #include <QtCore/qobject.h> +#include <QtCore/qpointer.h> +#include <QtCore/qset.h> #include <QtCore/qthread.h> #include <QtCore/qurl.h> -#include <QtCore/qmutex.h> -#include <QtCore/qmap.h> -#include <QtCore/qset.h> -#include <qaudioformat.h> -#include <qnetworkreply.h> -#include <private/qglobal_p.h> +#include <QtCore/private/qglobal_p.h> +#include <QtMultimedia/qaudioformat.h> +#include <QtNetwork/qnetworkreply.h> QT_BEGIN_NAMESPACE @@ -32,7 +33,6 @@ class QNetworkAccessManager; class QSampleCache; class QWaveDecoder; -// Lives in application thread class Q_MULTIMEDIA_EXPORT QSample : public QObject { Q_OBJECT @@ -54,8 +54,8 @@ public: void release(); Q_SIGNALS: - void error(); - void ready(); + void error(QPointer<QSample> self); + void ready(QPointer<QSample> self); protected: QSample(const QUrl& url, QSampleCache *parent); diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp index fda704c97..72894d7ee 100644 --- a/src/multimedia/audio/qsoundeffect.cpp +++ b/src/multimedia/audio/qsoundeffect.cpp @@ -72,8 +72,8 @@ public: void setPlaying(bool playing); public Q_SLOTS: - void sampleReady(); - void decoderError(); + void sampleReady(QSample *); + void decoderError(QSample *); void stateChanged(QAudio::State); public: @@ -103,8 +103,11 @@ QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &au QPlatformMediaIntegration::instance()->mediaDevices()->prepareAudio(); } -void QSoundEffectPrivate::sampleReady() +void QSoundEffectPrivate::sampleReady(QSample *sample) { + if (sample && sample != m_sample.get()) + return; + if (m_status == QSoundEffect::Error) return; @@ -165,8 +168,11 @@ void QSoundEffectPrivate::sampleReady() } } -void QSoundEffectPrivate::decoderError() +void QSoundEffectPrivate::decoderError(QSample *sample) { + if (sample && sample != m_sample.get()) + return; + qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString())); disconnect(m_sample.get(), &QSample::ready, this, &QSoundEffectPrivate::sampleReady); disconnect(m_sample.get(), &QSample::error, this, &QSoundEffectPrivate::decoderError); @@ -421,8 +427,7 @@ void QSoundEffect::setSource(const QUrl &url) disconnect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError); disconnect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady); } - d->m_sample->release(); - d->m_sample = nullptr; + d->m_sample.reset(); } if (d->m_audioSink) { @@ -437,10 +442,10 @@ void QSoundEffect::setSource(const QUrl &url) switch (d->m_sample->state()) { case QSample::Ready: - d->sampleReady(); + d->sampleReady(d->m_sample.get()); break; case QSample::Error: - d->decoderError(); + d->decoderError(d->m_sample.get()); break; default: break; diff --git a/src/multimedia/audio/qwavedecoder.cpp b/src/multimedia/audio/qwavedecoder.cpp index 36ac3c779..62c2268bb 100644 --- a/src/multimedia/audio/qwavedecoder.cpp +++ b/src/multimedia/audio/qwavedecoder.cpp @@ -10,29 +10,6 @@ QT_BEGIN_NAMESPACE -namespace { - -void bswap2(char *data, qsizetype count) noexcept -{ - for (qsizetype i = 0; i < count; ++i) { - qSwap(data[0], data[1]); - ++count; - data += 2; - } -} - -void bswap4(char *data, qsizetype count) noexcept -{ - for (qsizetype i = 0; i < count; ++i) { - qSwap(data[0], data[3]); - qSwap(data[1], data[2]); - ++count; - data += 4; - } -} - -} - QWaveDecoder::QWaveDecoder(QIODevice *device, QObject *parent) : QIODevice(parent), device(device) @@ -56,7 +33,7 @@ bool QWaveDecoder::open(QIODevice::OpenMode mode) if (canOpen && enoughDataAvailable()) handleData(); else - connect(device, SIGNAL(readyRead()), SLOT(handleData())); + connect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData); return canOpen; } @@ -178,10 +155,10 @@ qint64 QWaveDecoder::readData(char *data, qint64 maxlen) nSamples = read / bytesPerSample; switch (bytesPerSample) { case 2: - bswap2(data, nSamples); + qbswap<2>(data, nSamples, data); break; case 4: - bswap4(data, nSamples); + qbswap<4>(data, nSamples, data); break; default: Q_UNREACHABLE(); @@ -274,7 +251,7 @@ bool QWaveDecoder::writeDataLength() void QWaveDecoder::parsingFailed() { Q_ASSERT(device); - device->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData); emit parsingError(); } @@ -318,7 +295,8 @@ void QWaveDecoder::handleData() if (state == QWaveDecoder::WaitingForFormatState) { if (findChunk("fmt ")) { chunk descriptor; - peekChunk(&descriptor); + const bool peekSuccess = peekChunk(&descriptor); + Q_ASSERT(peekSuccess); quint32 rawChunkSize = descriptor.size + sizeof(chunk); if (device->bytesAvailable() < qint64(rawChunkSize)) @@ -386,7 +364,7 @@ void QWaveDecoder::handleData() if (state == QWaveDecoder::WaitingForDataState) { if (findChunk("data")) { - device->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData); chunk descriptor; device->read(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); @@ -400,7 +378,7 @@ void QWaveDecoder::handleData() dataSize = device->size() - headerLength(); haveFormat = true; - connect(device, SIGNAL(readyRead()), SIGNAL(readyRead())); + connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead); emit formatKnown(); return; @@ -426,7 +404,7 @@ bool QWaveDecoder::enoughDataAvailable() if (qstrncmp(descriptor.id, "RIFF", 4) == 0) descriptor.size = qFromLittleEndian<quint32>(descriptor.size); - if (device->bytesAvailable() < qint64(sizeof(chunk) + descriptor.size)) + if (device->bytesAvailable() < qint64(sizeof(chunk)) + descriptor.size) return false; return true; diff --git a/src/multimedia/camera/qcamera.cpp b/src/multimedia/camera/qcamera.cpp index 527b14c25..9cfbcc01d 100644 --- a/src/multimedia/camera/qcamera.cpp +++ b/src/multimedia/camera/qcamera.cpp @@ -152,14 +152,6 @@ QT_BEGIN_NAMESPACE See the \l{Camera Overview}{camera overview} for more information. */ - -void QCameraPrivate::_q_error(int error, const QString &errorString) -{ - Q_Q(QCamera); - - this->error.setAndNotify(QCamera::Error(error), errorString, *q); -} - void QCameraPrivate::init(const QCameraDevice &device) { Q_Q(QCamera); @@ -167,16 +159,16 @@ void QCameraPrivate::init(const QCameraDevice &device) auto maybeControl = QPlatformMediaIntegration::instance()->createCamera(q); if (!maybeControl) { qWarning() << "Failed to initialize QCamera" << maybeControl.error(); - error = { QCamera::CameraError, maybeControl.error() }; return; } control = maybeControl.value(); cameraDevice = !device.isNull() ? device : QMediaDevices::defaultVideoInput(); if (cameraDevice.isNull()) - _q_error(QCamera::CameraError, QStringLiteral("No camera detected")); + control->updateError(QCamera::CameraError, QStringLiteral("No camera detected")); control->setCamera(cameraDevice); - q->connect(control, SIGNAL(activeChanged(bool)), q, SIGNAL(activeChanged(bool))); - q->connect(control, SIGNAL(error(int,QString)), q, SLOT(_q_error(int,QString))); + q->connect(control, &QPlatformVideoSource::activeChanged, q, &QCamera::activeChanged); + q->connect(control, &QPlatformCamera::errorChanged, q, &QCamera::errorChanged); + q->connect(control, &QPlatformCamera::errorOccurred, q, &QCamera::errorOccurred); } /*! @@ -296,7 +288,9 @@ void QCamera::setActive(bool active) QCamera::Error QCamera::error() const { - return d_func()->error.code(); + Q_D(const QCamera); + + return d->control ? d->control->error() : QCamera::CameraError; } /*! @@ -312,7 +306,10 @@ QCamera::Error QCamera::error() const */ QString QCamera::errorString() const { - return d_func()->error.description(); + Q_D(const QCamera); + + return d->control ? d->control->errorString() + : QStringLiteral("Camera is not supported on the platform"); } /*! \enum QCamera::Feature diff --git a/src/multimedia/camera/qcamera.h b/src/multimedia/camera/qcamera.h index 4daa6a116..ec9c27d5e 100644 --- a/src/multimedia/camera/qcamera.h +++ b/src/multimedia/camera/qcamera.h @@ -262,7 +262,6 @@ private: friend class QMediaCaptureSession; Q_DISABLE_COPY(QCamera) Q_DECLARE_PRIVATE(QCamera) - Q_PRIVATE_SLOT(d_func(), void _q_error(int, const QString &)) friend class QCameraDevice; }; diff --git a/src/multimedia/camera/qcamera_p.h b/src/multimedia/camera/qcamera_p.h index c0477c242..ae1299435 100644 --- a/src/multimedia/camera/qcamera_p.h +++ b/src/multimedia/camera/qcamera_p.h @@ -16,7 +16,6 @@ // #include "private/qobject_p.h" -#include "private/qerrorinfo_p.h" #include "qcamera.h" #include "qcameradevice.h" @@ -34,13 +33,8 @@ public: QMediaCaptureSession *captureSession = nullptr; QPlatformCamera *control = nullptr; - QErrorInfo<QCamera::Error> error; - QCameraDevice cameraDevice; QCameraFormat cameraFormat; - - void _q_error(int error, const QString &errorString); - void unsetError() { error = {}; } }; QT_END_NAMESPACE diff --git a/src/multimedia/camera/qcameradevice.cpp b/src/multimedia/camera/qcameradevice.cpp index 037afc73b..5c8dd93bf 100644 --- a/src/multimedia/camera/qcameradevice.cpp +++ b/src/multimedia/camera/qcameradevice.cpp @@ -140,7 +140,7 @@ float QCameraFormat::minFrameRate() const noexcept Returns the highest frame rate defined by this format. - In 6.2, the camera will always try to use the maximum frame rate supported by a + The camera will always try to use the maximum frame rate supported by a certain video format. */ @@ -149,7 +149,7 @@ float QCameraFormat::minFrameRate() const noexcept Returns the highest frame rate defined by this format. - In 6.2, the camera will always try to use the highest frame rate supported by a + The camera will always try to use the highest frame rate supported by a certain video format. */ float QCameraFormat::maxFrameRate() const noexcept @@ -423,10 +423,12 @@ QCameraDevice& QCameraDevice::operator=(const QCameraDevice& other) = default; #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const QCameraDevice &camera) { - d.maybeSpace() << QStringLiteral("QCameraDevice(name=%1, position=%2, orientation=%3)") - .arg(camera.description()) - .arg(QString::fromLatin1(QCamera::staticMetaObject.enumerator(QCamera::staticMetaObject.indexOfEnumerator("Position")) - .valueToKey(camera.position()))); + d.maybeSpace() << QStringLiteral("QCameraDevice(name=%1, id=%2, position=%3)") + .arg(camera.description()) + .arg(QLatin1StringView(camera.id())) + .arg(QLatin1StringView( + QMetaEnum::fromType<QCameraDevice::Position>().valueToKey( + camera.position()))); return d.space(); } #endif diff --git a/src/multimedia/camera/qimagecapture.cpp b/src/multimedia/camera/qimagecapture.cpp index 9b92ce743..df3ddae3f 100644 --- a/src/multimedia/camera/qimagecapture.cpp +++ b/src/multimedia/camera/qimagecapture.cpp @@ -92,18 +92,15 @@ QImageCapture::QImageCapture(QObject *parent) } d->control = maybeControl.value(); - connect(d->control, SIGNAL(imageExposed(int)), - this, SIGNAL(imageExposed(int))); - connect(d->control, SIGNAL(imageCaptured(int,QImage)), - this, SIGNAL(imageCaptured(int,QImage))); - connect(d->control, SIGNAL(imageMetadataAvailable(int,QMediaMetaData)), - this, SIGNAL(imageMetadataAvailable(int,QMediaMetaData))); - connect(d->control, SIGNAL(imageAvailable(int,QVideoFrame)), - this, SIGNAL(imageAvailable(int,QVideoFrame))); - connect(d->control, SIGNAL(imageSaved(int,QString)), - this, SIGNAL(imageSaved(int,QString))); - connect(d->control, SIGNAL(readyForCaptureChanged(bool)), - this, SIGNAL(readyForCaptureChanged(bool))); + connect(d->control, &QPlatformImageCapture::imageExposed, this, &QImageCapture::imageExposed); + connect(d->control, &QPlatformImageCapture::imageCaptured, this, &QImageCapture::imageCaptured); + connect(d->control, &QPlatformImageCapture::imageMetadataAvailable, this, + &QImageCapture::imageMetadataAvailable); + connect(d->control, &QPlatformImageCapture::imageAvailable, this, + &QImageCapture::imageAvailable); + connect(d->control, &QPlatformImageCapture::imageSaved, this, &QImageCapture::imageSaved); + connect(d->control, &QPlatformImageCapture::readyForCaptureChanged, this, + &QImageCapture::readyForCaptureChanged); connect(d->control, SIGNAL(error(int,int,QString)), this, SLOT(_q_error(int,int,QString))); } diff --git a/src/multimedia/configure.cmake b/src/multimedia/configure.cmake index 5fe25f172..1b06c1df9 100644 --- a/src/multimedia/configure.cmake +++ b/src/multimedia/configure.cmake @@ -103,29 +103,20 @@ qt_feature("evr" PUBLIC PRIVATE LABEL "evr.h" CONDITION WIN32 AND TEST_evr ) -qt_feature("gstreamer_1_0" PRIVATE - LABEL "GStreamer 1.0" - CONDITION GStreamer_FOUND -) -qt_feature("gstreamer_app" PRIVATE - LABEL "GStreamer App" - CONDITION ( QT_FEATURE_gstreamer_1_0 AND GStreamer_App_FOUND ) +qt_feature("gstreamer" PRIVATE + LABEL "QtMM GStreamer plugin" + CONDITION GStreamer_FOUND AND GStreamer_App_FOUND + ENABLE INPUT_gstreamer STREQUAL 'yes' + DISABLE INPUT_gstreamer STREQUAL 'no' ) qt_feature("gstreamer_photography" PRIVATE LABEL "GStreamer Photography" - CONDITION ( QT_FEATURE_gstreamer_1_0 AND GStreamer_Photography_FOUND ) + CONDITION QT_FEATURE_gstreamer AND GStreamer_Photography_FOUND ) qt_feature("gstreamer_gl" PRIVATE LABEL "GStreamer OpenGL" - CONDITION QT_FEATURE_opengl AND QT_FEATURE_gstreamer_1_0 AND GStreamer_Gl_FOUND AND EGL_FOUND + CONDITION QT_FEATURE_opengl AND QT_FEATURE_gstreamer AND GStreamer_Gl_FOUND AND EGL_FOUND ) -qt_feature("gstreamer" PRIVATE - LABEL "QtMM GStreamer plugin" - CONDITION (QT_FEATURE_gstreamer_1_0 AND QT_FEATURE_gstreamer_app) - ENABLE INPUT_gstreamer STREQUAL 'yes' - DISABLE INPUT_gstreamer STREQUAL 'no' -) - qt_feature("gpu_vivante" PRIVATE LABEL "Vivante GPU" CONDITION QT_FEATURE_gui AND QT_FEATURE_opengles2 AND TEST_gpu_vivante @@ -191,7 +182,7 @@ qt_configure_add_summary_entry(ARGS "opensles") qt_configure_add_summary_entry(ARGS "wasm") qt_configure_end_summary_section() qt_configure_add_summary_section(NAME "Plugin") -qt_configure_add_summary_entry(ARGS "gstreamer_1_0") +qt_configure_add_summary_entry(ARGS "gstreamer") qt_configure_add_summary_entry(ARGS "ffmpeg") qt_configure_add_summary_entry(ARGS "mmrenderer") qt_configure_add_summary_entry(ARGS "avfoundation") @@ -210,3 +201,9 @@ qt_configure_add_report_entry( MESSAGE "No backend for low level audio found." CONDITION NOT QT_FEATURE_alsa AND NOT QT_FEATURE_pulseaudio AND NOT QT_FEATURE_mmrenderer AND NOT QT_FEATURE_coreaudio AND NOT QT_FEATURE_wmsdk AND NOT ANDROID AND NOT WASM ) + +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "No media backend found" + CONDITION LINUX AND NOT (QT_FEATURE_gstreamer OR QT_FEATURE_ffmpeg) +) diff --git a/src/multimedia/darwin/qdarwinaudiosource.mm b/src/multimedia/darwin/qdarwinaudiosource.mm index 4c1345fb8..b7a24b232 100644 --- a/src/multimedia/darwin/qdarwinaudiosource.mm +++ b/src/multimedia/darwin/qdarwinaudiosource.mm @@ -418,6 +418,7 @@ QDarwinAudioSource::QDarwinAudioSource(const QAudioDevice &device, QObject *pare , m_isOpen(false) , m_internalBufferSize(DEFAULT_BUFFER_SIZE) , m_totalFrames(0) + , m_audioIO(nullptr) , m_audioUnit(0) , m_clockFrequency(CoreAudioUtils::frequency() / 1000) , m_errorCode(QAudio::NoError) diff --git a/src/multimedia/doc/src/qtmultimedia-index.qdoc b/src/multimedia/doc/src/qtmultimedia-index.qdoc index db779818b..7dce70542 100644 --- a/src/multimedia/doc/src/qtmultimedia-index.qdoc +++ b/src/multimedia/doc/src/qtmultimedia-index.qdoc @@ -187,18 +187,18 @@ The version shipped with Qt binary packages is \b{FFmpeg 6.1.1} and is tested by the maintainers. - \note On the Windows platform, Qt's FFmpeg media backend uses - dynamic linking to the FFmpeg libraries. Windows applications must - therefore bundle FFmpeg binaries in their installer, and make them - visible to the application according to Windows dll loading rules. - We recommend to store the FFmpeg dlls in the same directory as the - application's executable file, because this guarantees that the - correct build of FFmpeg is being used if multiple versions are - available on the system. All necessary FFmpeg dlls are shipped with - the Qt Online Installer and are automatically deployed if the - windeployqt tool is used to create the deployment. Applications can - also deploy their own build of FFmpeg, as long as the FFmpeg major - version matches the version used by Qt. + \note On the Windows and macOS platforms, Qt's FFmpeg media backend + uses dynamic linking to the FFmpeg libraries. Windows and macOS + applications must therefore bundle FFmpeg binaries in their + installer, and make them visible to the application at runtime. On + Windows, we recommend to store the FFmpeg dlls in the same directory + as the application's executable file, because this guarantees that + the correct build of FFmpeg is being used if multiple versions are + available on the system. All necessary FFmpeg libraries are shipped + with the Qt Online Installer and are automatically deployed if the + windeployqt or macdeployqt tools are used to create the deployment. + Applications can also deploy their own build of FFmpeg, as long as + the FFmpeg major version matches the version used by Qt. \note See \l{Licenses and Attributions} regarding what components are removed in the package shipped by Qt. @@ -253,7 +253,7 @@ \section2 Target platform notes The following pages list issues for specific target platforms that are not - related to the multimedia backed. + related to the multimedia backend. \list \li \l{Qt Multimedia on macOS and iOS}{macOS and iOS} diff --git a/src/multimedia/platform/qgstreamer_platformspecificinterface.cpp b/src/multimedia/platform/qgstreamer_platformspecificinterface.cpp new file mode 100644 index 000000000..06ce46e3c --- /dev/null +++ b/src/multimedia/platform/qgstreamer_platformspecificinterface.cpp @@ -0,0 +1,27 @@ +// 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 + +// +// 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/private/qgstreamer_platformspecificinterface_p.h> + +QT_BEGIN_NAMESPACE + +QGStreamerPlatformSpecificInterface::~QGStreamerPlatformSpecificInterface() = default; + +QGStreamerPlatformSpecificInterface *QGStreamerPlatformSpecificInterface::instance() +{ + return dynamic_cast<QGStreamerPlatformSpecificInterface *>( + QPlatformMediaIntegration::instance()->platformSpecificInterface()); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qgstreamer_platformspecificinterface_p.h b/src/multimedia/platform/qgstreamer_platformspecificinterface_p.h new file mode 100644 index 000000000..1a086f5a4 --- /dev/null +++ b/src/multimedia/platform/qgstreamer_platformspecificinterface_p.h @@ -0,0 +1,46 @@ +// 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 GSTREAMER_PLATFORMSPECIFICINTERFACE_P_H +#define GSTREAMER_PLATFORMSPECIFICINTERFACE_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/private/qplatformmediaintegration_p.h> + +typedef struct _GstPipeline GstPipeline; // NOLINT (bugprone-reserved-identifier) +typedef struct _GstElement GstElement; // NOLINT (bugprone-reserved-identifier) + +QT_BEGIN_NAMESPACE + +class Q_MULTIMEDIA_EXPORT QGStreamerPlatformSpecificInterface + : public QAbstractPlatformSpecificInterface +{ +public: + ~QGStreamerPlatformSpecificInterface() override; + + static QGStreamerPlatformSpecificInterface *instance(); + + virtual QAudioDevice makeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline) = 0; + virtual QAudioDevice makeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline) = 0; + virtual QCamera *makeCustomGStreamerCamera(const QByteArray &gstreamerPipeline, + QObject *parent) = 0; + + // Note: ownership of GstElement is not transferred + virtual QCamera *makeCustomGStreamerCamera(GstElement *, QObject *parent) = 0; + + virtual GstPipeline *gstPipeline(QMediaPlayer *) = 0; + virtual GstPipeline *gstPipeline(QMediaCaptureSession *) = 0; +}; + +QT_END_NAMESPACE + +#endif // GSTREAMER_PLATFORMSPECIFICINTERFACE_P_H diff --git a/src/multimedia/platform/qplatformaudiodecoder_p.h b/src/multimedia/platform/qplatformaudiodecoder_p.h index 1159a37ca..e736ddef4 100644 --- a/src/multimedia/platform/qplatformaudiodecoder_p.h +++ b/src/multimedia/platform/qplatformaudiodecoder_p.h @@ -71,6 +71,8 @@ public: QAudioDecoder::Error error() const { return m_error; } QString errorString() const { return m_errorString; } + virtual bool canReadQrc() const { return false; } + virtual ~QPlatformAudioDecoder(); protected: diff --git a/src/multimedia/platform/qplatformcamera.cpp b/src/multimedia/platform/qplatformcamera.cpp index 0d3975550..35f6fd579 100644 --- a/src/multimedia/platform/qplatformcamera.cpp +++ b/src/multimedia/platform/qplatformcamera.cpp @@ -221,6 +221,13 @@ int QPlatformCamera::colorTemperatureForWhiteBalance(QCamera::WhiteBalanceMode m return 0; } +void QPlatformCamera::updateError(QCamera::Error error, const QString &errorString) +{ + QMetaObject::invokeMethod(this, [this, error, errorString]() { + m_error.setAndNotify(error, errorString, *this); + }); +} + QT_END_NAMESPACE #include "moc_qplatformcamera_p.cpp" diff --git a/src/multimedia/platform/qplatformcamera_p.h b/src/multimedia/platform/qplatformcamera_p.h index 85624c0ce..341bf9121 100644 --- a/src/multimedia/platform/qplatformcamera_p.h +++ b/src/multimedia/platform/qplatformcamera_p.h @@ -16,7 +16,7 @@ // #include "qplatformvideosource_p.h" - +#include "private/qerrorinfo_p.h" #include <QtMultimedia/qcamera.h> QT_BEGIN_NAMESPACE @@ -110,8 +110,13 @@ public: static int colorTemperatureForWhiteBalance(QCamera::WhiteBalanceMode mode); + QCamera::Error error() const { return m_error.code(); } + QString errorString() const final { return m_error.description(); } + + void updateError(QCamera::Error error, const QString &errorString); + Q_SIGNALS: - void error(int error, const QString &errorString); + void errorOccurred(QCamera::Error error, const QString &errorString); protected: explicit QPlatformCamera(QCamera *parent); @@ -150,6 +155,7 @@ private: float m_maxExposureTime = -1.; QCamera::WhiteBalanceMode m_whiteBalance = QCamera::WhiteBalanceAuto; int m_colorTemperature = 0; + QErrorInfo<QCamera::Error> m_error; }; QT_END_NAMESPACE diff --git a/src/multimedia/platform/qplatformmediacapture.cpp b/src/multimedia/platform/qplatformmediacapture.cpp index 0c8983ed1..31de10d90 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 diff --git a/src/multimedia/platform/qplatformmediaintegration.cpp b/src/multimedia/platform/qplatformmediaintegration.cpp index 3781d0924..ee5dfce93 100644 --- a/src/multimedia/platform/qplatformmediaintegration.cpp +++ b/src/multimedia/platform/qplatformmediaintegration.cpp @@ -224,6 +224,12 @@ QLatin1String QPlatformMediaIntegration::name() return m_backendName; } +QVideoFrame QPlatformMediaIntegration::convertVideoFrame(QVideoFrame &, + const QVideoFrameFormat &) +{ + return {}; +} + QPlatformMediaIntegration::QPlatformMediaIntegration(QLatin1String name) : m_backendName(name) { } QPlatformMediaIntegration::~QPlatformMediaIntegration() = default; diff --git a/src/multimedia/platform/qplatformmediaintegration_p.h b/src/multimedia/platform/qplatformmediaintegration_p.h index 1f9ea5542..f1b2712dd 100644 --- a/src/multimedia/platform/qplatformmediaintegration_p.h +++ b/src/multimedia/platform/qplatformmediaintegration_p.h @@ -49,6 +49,13 @@ class QAudioOutput; class QPlatformAudioInput; class QPlatformAudioOutput; class QPlatformVideoDevices; +class QVideoFrame; + +class Q_MULTIMEDIA_EXPORT QAbstractPlatformSpecificInterface +{ +public: + virtual ~QAbstractPlatformSpecificInterface() = default; +}; class Q_MULTIMEDIA_EXPORT QPlatformMediaIntegration : public QObject { @@ -86,6 +93,11 @@ public: static QStringList availableBackends(); QLatin1String name(); // for unit tests + // Convert a QVideoFrame to the destination format + virtual QVideoFrame convertVideoFrame(QVideoFrame &, const QVideoFrameFormat &); + + virtual QAbstractPlatformSpecificInterface *platformSpecificInterface() { return nullptr; } + protected: virtual QPlatformMediaFormatInfo *createFormatInfo(); diff --git a/src/multimedia/platform/qplatformmediaplayer.cpp b/src/multimedia/platform/qplatformmediaplayer.cpp index ea22f94df..00840f074 100644 --- a/src/multimedia/platform/qplatformmediaplayer.cpp +++ b/src/multimedia/platform/qplatformmediaplayer.cpp @@ -14,9 +14,7 @@ QPlatformMediaPlayer::QPlatformMediaPlayer(QMediaPlayer *parent) : player(parent QPlatformMediaIntegration::instance()->mediaDevices()->prepareAudio(); } -QPlatformMediaPlayer::~QPlatformMediaPlayer() -{ -} +QPlatformMediaPlayer::~QPlatformMediaPlayer() = default; void QPlatformMediaPlayer::stateChanged(QMediaPlayer::PlaybackState newState) { @@ -39,16 +37,4 @@ void QPlatformMediaPlayer::error(int error, const QString &errorString) player->d_func()->setError(QMediaPlayer::Error(error), errorString); } -void *QPlatformMediaPlayer::nativePipeline(QMediaPlayer *player) -{ - if (!player) - return nullptr; - - auto playerPrivate = player->d_func(); - if (!playerPrivate || !playerPrivate->control) - return nullptr; - - return playerPrivate->control->nativePipeline(); -} - QT_END_NAMESPACE diff --git a/src/multimedia/platform/qplatformmediaplayer_p.h b/src/multimedia/platform/qplatformmediaplayer_p.h index f1d922a16..6c5c86396 100644 --- a/src/multimedia/platform/qplatformmediaplayer_p.h +++ b/src/multimedia/platform/qplatformmediaplayer_p.h @@ -20,6 +20,7 @@ #include <QtMultimedia/qaudiodevice.h> #include <QtMultimedia/qmediametadata.h> +#include <QtCore/qobject.h> #include <QtCore/qpair.h> #include <QtCore/private/qglobal_p.h> #include <private/qtvideo_p.h> @@ -69,15 +70,19 @@ public: virtual void setVideoSink(QVideoSink * /*sink*/) = 0; + virtual bool canPlayQrc() const { return false; } + // media streams - enum TrackType { VideoStream, AudioStream, SubtitleStream, NTrackTypes }; + enum TrackType : uint8_t { VideoStream, AudioStream, SubtitleStream, NTrackTypes }; virtual int trackCount(TrackType) { return 0; }; virtual QMediaMetaData trackMetaData(TrackType /*type*/, int /*streamNumber*/) { return QMediaMetaData(); } virtual int activeTrack(TrackType) { return -1; } virtual void setActiveTrack(TrackType, int /*streamNumber*/) {} + void durationChanged(std::chrono::milliseconds ms) { durationChanged(ms.count()); } void durationChanged(qint64 duration) { emit player->durationChanged(duration); } + void positionChanged(std::chrono::milliseconds ms) { positionChanged(ms.count()); } void positionChanged(qint64 position) { if (m_position == position) return; @@ -116,7 +121,7 @@ public: bool doLoop() { return isSeekable() && (m_loops < 0 || ++m_currentLoop < m_loops); } - int loops() { return m_loops; } + int loops() const { return m_loops; } virtual void setLoops(int loops) { if (m_loops == loops) @@ -125,11 +130,6 @@ public: Q_EMIT player->loopsChanged(); } - virtual void *nativePipeline() { return nullptr; } - - // private API, the purpose is getting GstPipeline - static void *nativePipeline(QMediaPlayer *player); - protected: explicit QPlatformMediaPlayer(QMediaPlayer *parent = nullptr); @@ -145,6 +145,25 @@ private: qint64 m_position = 0; }; +#ifndef QT_NO_DEBUG_STREAM +inline QDebug operator<<(QDebug dbg, QPlatformMediaPlayer::TrackType type) +{ + QDebugStateSaver save(dbg); + dbg.nospace(); + + switch (type) { + case QPlatformMediaPlayer::TrackType::AudioStream: + return dbg << "AudioStream"; + case QPlatformMediaPlayer::TrackType::VideoStream: + return dbg << "VideoStream"; + case QPlatformMediaPlayer::TrackType::SubtitleStream: + return dbg << "SubtitleStream"; + default: + Q_UNREACHABLE_RETURN(dbg); + } +} +#endif + QT_END_NAMESPACE diff --git a/src/multimedia/platform/qplatformmediarecorder.cpp b/src/multimedia/platform/qplatformmediarecorder.cpp index ba9ea0165..505eb799a 100644 --- a/src/multimedia/platform/qplatformmediarecorder.cpp +++ b/src/multimedia/platform/qplatformmediarecorder.cpp @@ -15,12 +15,12 @@ QPlatformMediaRecorder::QPlatformMediaRecorder(QMediaRecorder *parent) void QPlatformMediaRecorder::pause() { - error(QMediaRecorder::FormatError, QMediaRecorder::tr("Pause not supported")); + updateError(QMediaRecorder::FormatError, QMediaRecorder::tr("Pause not supported")); } void QPlatformMediaRecorder::resume() { - error(QMediaRecorder::FormatError, QMediaRecorder::tr("Resume not supported")); + updateError(QMediaRecorder::FormatError, QMediaRecorder::tr("Resume not supported")); } void QPlatformMediaRecorder::stateChanged(QMediaRecorder::RecorderState state) @@ -47,7 +47,7 @@ void QPlatformMediaRecorder::actualLocationChanged(const QUrl &location) emit q->actualLocationChanged(location); } -void QPlatformMediaRecorder::error(QMediaRecorder::Error error, const QString &errorString) +void QPlatformMediaRecorder::updateError(QMediaRecorder::Error error, const QString &errorString) { m_error.setAndNotify(error, errorString, *q); } @@ -64,7 +64,7 @@ QString QPlatformMediaRecorder::findActualLocation(const QMediaEncoderSettings & const auto primaryLocation = audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation; const QString suffix = settings.mimeType().preferredSuffix(); - const QString location = QMediaStorageLocation::generateFileName( + QString location = QMediaStorageLocation::generateFileName( outputLocation().toString(QUrl::PreferLocalFile), primaryLocation, suffix); Q_ASSERT(!location.isEmpty()); diff --git a/src/multimedia/platform/qplatformmediarecorder_p.h b/src/multimedia/platform/qplatformmediarecorder_p.h index 214510a29..18420a7b4 100644 --- a/src/multimedia/platform/qplatformmediarecorder_p.h +++ b/src/multimedia/platform/qplatformmediarecorder_p.h @@ -123,7 +123,7 @@ public: virtual void setOutputLocation(const QUrl &location) { m_outputLocation = location; } QUrl actualLocation() const { return m_actualLocation; } void clearActualLocation() { m_actualLocation.clear(); } - void clearError() { error(QMediaRecorder::NoError, QString()); } + void clearError() { updateError(QMediaRecorder::NoError, QString()); } protected: explicit QPlatformMediaRecorder(QMediaRecorder *parent); @@ -131,7 +131,7 @@ protected: void stateChanged(QMediaRecorder::RecorderState state); void durationChanged(qint64 position); void actualLocationChanged(const QUrl &location); - void error(QMediaRecorder::Error error, const QString &errorString); + void updateError(QMediaRecorder::Error error, const QString &errorString); void metaDataChanged(); QMediaRecorder *mediaRecorder() { return q; } diff --git a/src/multimedia/platform/qplatformsurfacecapture_p.h b/src/multimedia/platform/qplatformsurfacecapture_p.h index 9bd7cddff..fcec91ad3 100644 --- a/src/multimedia/platform/qplatformsurfacecapture_p.h +++ b/src/multimedia/platform/qplatformsurfacecapture_p.h @@ -59,7 +59,7 @@ public: Source source() const { return m_source; } Error error() const; - QString errorString() const; + QString errorString() const final; protected: virtual bool setActiveInternal(bool) = 0; @@ -71,7 +71,6 @@ public Q_SLOTS: Q_SIGNALS: void sourceChanged(ScreenSource); - void errorChanged(); void errorOccurred(Error error, QString errorString); private: diff --git a/src/multimedia/platform/qplatformvideosource_p.h b/src/multimedia/platform/qplatformvideosource_p.h index 3ed76d3e2..b11524226 100644 --- a/src/multimedia/platform/qplatformvideosource_p.h +++ b/src/multimedia/platform/qplatformvideosource_p.h @@ -43,9 +43,14 @@ public: virtual void setCaptureSession(QPlatformMediaCaptureSession *) { } + virtual QString errorString() const = 0; + + bool hasError() const { return !errorString().isEmpty(); } + Q_SIGNALS: void newVideoFrame(const QVideoFrame &); void activeChanged(bool); + void errorChanged(); }; QT_END_NAMESPACE diff --git a/src/multimedia/playback/qmediaplayer.cpp b/src/multimedia/playback/qmediaplayer.cpp index dc8e3dab8..951880f60 100644 --- a/src/multimedia/playback/qmediaplayer.cpp +++ b/src/multimedia/playback/qmediaplayer.cpp @@ -134,7 +134,8 @@ void QMediaPlayerPrivate::setMedia(const QUrl &media, QIODevice *stream) // Back ends can't play qrc files directly. // If the back end supports StreamPlayback, we pass a QFile for that resource. // If it doesn't, we copy the data to a temporary file and pass its path. - if (!media.isEmpty() && !stream && media.scheme() == QLatin1String("qrc")) { + if (!media.isEmpty() && !stream && media.scheme() == QLatin1String("qrc") + && !control->canPlayQrc()) { qrcMedia = media; file.reset(new QFile(QLatin1Char(':') + media.path())); @@ -596,6 +597,12 @@ void QMediaPlayer::setPlaybackRate(qreal rate) It does not wait for the media to finish loading and does not check for errors. Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading. + + \note FFmpeg, used by the FFmpeg media backend, restricts use of nested protocols for + security reasons. In controlled environments where all inputs are trusted, the list of + approved protocols can be overridden using the QT_FFMPEG_PROTOCOL_WHITELIST environment + variable. This environment variable is Qt's private API and can change between patch + releases without notice. */ void QMediaPlayer::setSource(const QUrl &source) diff --git a/src/multimedia/playback/qmediaplayer_p.h b/src/multimedia/playback/qmediaplayer_p.h index ece086d06..a8a4653e3 100644 --- a/src/multimedia/playback/qmediaplayer_p.h +++ b/src/multimedia/playback/qmediaplayer_p.h @@ -40,6 +40,11 @@ class QMediaPlayerPrivate : public QObjectPrivate Q_DECLARE_PUBLIC(QMediaPlayer) public: + static QMediaPlayerPrivate *get(QMediaPlayer *session) + { + return reinterpret_cast<QMediaPlayerPrivate *>(QObjectPrivate::get(session)); + } + QMediaPlayerPrivate() = default; QPlatformMediaPlayer *control = nullptr; diff --git a/src/multimedia/pulseaudio/qaudioengine_pulse.cpp b/src/multimedia/pulseaudio/qaudioengine_pulse.cpp index efd5b5623..fa7fc77e3 100644 --- a/src/multimedia/pulseaudio/qaudioengine_pulse.cpp +++ b/src/multimedia/pulseaudio/qaudioengine_pulse.cpp @@ -70,27 +70,28 @@ static void serverInfoCallback(pa_context *context, const pa_server_info *info, return; } -#ifdef DEBUG_PULSE - char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - - pa_sample_spec_snprint(ss, sizeof(ss), &info->sample_spec); - pa_channel_map_snprint(cm, sizeof(cm), &info->channel_map); - - qDebug() << QStringLiteral("User name: %1\n" - "Host Name: %2\n" - "Server Name: %3\n" - "Server Version: %4\n" - "Default Sample Specification: %5\n" - "Default Channel Map: %6\n" - "Default Sink: %7\n" - "Default Source: %8\n") - .arg(QString::fromUtf8(info->user_name), - QString::fromUtf8(info->host_name), - QString::fromUtf8(info->server_name), - QLatin1StringView(info->server_version), QLatin1StringView(ss), - QLatin1StringView(cm), QString::fromUtf8(info->default_sink_name), - QString::fromUtf8(info->default_source_name)); -#endif + if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_sample_spec_snprint(ss, sizeof(ss), &info->sample_spec); + pa_channel_map_snprint(cm, sizeof(cm), &info->channel_map); + + qCDebug(qLcPulseAudioEngine) + << QStringLiteral("User name: %1\n" + "Host Name: %2\n" + "Server Name: %3\n" + "Server Version: %4\n" + "Default Sample Specification: %5\n" + "Default Channel Map: %6\n" + "Default Sink: %7\n" + "Default Source: %8\n") + .arg(QString::fromUtf8(info->user_name), + QString::fromUtf8(info->host_name), + QString::fromUtf8(info->server_name), + QLatin1StringView(info->server_version), QLatin1StringView(ss), + QLatin1StringView(cm), QString::fromUtf8(info->default_sink_name), + QString::fromUtf8(info->default_source_name)); + } QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata); @@ -143,34 +144,22 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int Q_ASSERT(info); -#ifdef DEBUG_PULSE - static const QMap<pa_sink_state, QString> stateMap{ - { PA_SINK_INVALID_STATE, "n/a" }, { PA_SINK_RUNNING, "RUNNING" }, - { PA_SINK_IDLE, "IDLE" }, { PA_SINK_SUSPENDED, "SUSPENDED" }, - { PA_SINK_UNLINKED, "UNLINKED" }, - }; - - qDebug() << QStringLiteral("Sink #%1\n" - "\tState: %2\n" - "\tName: %3\n" - "\tDescription: %4\n" - ).arg(QString::number(info->index), - stateMap.value(info->state), - info->name, - info->description); - static const QMap<pa_sink_state, QString> stateMap{ - { PA_SINK_INVALID_STATE, u"n/a"_s }, { PA_SINK_RUNNING, u"RUNNING"_s }, - { PA_SINK_IDLE, u"IDLE"_s }, { PA_SINK_SUSPENDED, u"SUSPENDED"_s }, - { PA_SINK_UNLINKED, u"UNLINKED"_s }, - }; - - qDebug() << QStringLiteral("Sink #%1\n" - "\tState: %2\n" - "\tName: %3\n" - "\tDescription: %4\n") - .arg(QString::number(info->index), stateMap.value(info->state), - QString::fromUtf8(info->name), -#endif + if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) { + static const QMap<pa_sink_state, QString> stateMap{ + { PA_SINK_INVALID_STATE, "n/a" }, { PA_SINK_RUNNING, "RUNNING" }, + { PA_SINK_IDLE, "IDLE" }, { PA_SINK_SUSPENDED, "SUSPENDED" }, + { PA_SINK_UNLINKED, "UNLINKED" }, + }; + + qCDebug(qLcPulseAudioEngine) + << QStringLiteral("Sink #%1\n" + "\tState: %2\n" + "\tName: %3\n" + "\tDescription: %4\n") + .arg(QString::number(info->index), stateMap.value(info->state), + QString::fromUtf8(info->name), + QString::fromUtf8(info->description)); + } if (updateDevicesMap(pulseEngine->m_sinkLock, pulseEngine->m_defaultSink, pulseEngine->m_sinks, QAudioDevice::Output, *info)) @@ -191,21 +180,24 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, Q_ASSERT(info); -#ifdef DEBUG_PULSE + if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) { static const QMap<pa_source_state, QString> stateMap{ - { PA_SOURCE_INVALID_STATE, u"n/a"_s }, { PA_SOURCE_RUNNING, u"RUNNING"_s }, - { PA_SOURCE_IDLE, u"IDLE"_s }, { PA_SOURCE_SUSPENDED, u"SUSPENDED"_s }, - { PA_SOURCE_UNLINKED, u"UNLINKED"_s }, + { PA_SOURCE_INVALID_STATE, u"n/a"_s }, + { PA_SOURCE_RUNNING, u"RUNNING"_s }, + { PA_SOURCE_IDLE, u"IDLE"_s }, + { PA_SOURCE_SUSPENDED, u"SUSPENDED"_s }, + { PA_SOURCE_UNLINKED, u"UNLINKED"_s } }; - qCDebug() << QStringLiteral("Source #%1\n" - "\tState: %2\n" - "\tName: %3\n" - "\tDescription: %4\n") + qCDebug(qLcPulseAudioEngine) + << QStringLiteral("Source #%1\n" + "\tState: %2\n" + "\tName: %3\n" + "\tDescription: %4\n") .arg(QString::number(info->index), stateMap.value(info->state), QString::fromUtf8(info->name), QString::fromUtf8(info->description)); -#endif + } // skip monitor channels if (info->monitor_of_sink != PA_INVALID_INDEX) @@ -230,21 +222,21 @@ static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32 case PA_SUBSCRIPTION_EVENT_SERVER: { PAOperationUPtr op(pa_context_get_server_info(context, serverInfoCallback, userdata)); if (!op) - qWarning("PulseAudioService: failed to get server info"); + qWarning() << "PulseAudioService: failed to get server info"; break; } case PA_SUBSCRIPTION_EVENT_SINK: { PAOperationUPtr op( pa_context_get_sink_info_by_index(context, index, sinkInfoCallback, userdata)); if (!op) - qWarning("PulseAudioService: failed to get sink info"); + qWarning() << "PulseAudioService: failed to get sink info"; break; } case PA_SUBSCRIPTION_EVENT_SOURCE: { PAOperationUPtr op(pa_context_get_source_info_by_index(context, index, sourceInfoCallback, userdata)); if (!op) - qWarning("PulseAudioService: failed to get source info"); + qWarning() << "PulseAudioService: failed to get source info"; break; } default: @@ -275,9 +267,10 @@ static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32 static void contextStateCallbackInit(pa_context *context, void *userdata) { Q_UNUSED(context); -#ifdef DEBUG_PULSE - qDebug() << pa_context_get_state(context); -#endif + + if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) + qCDebug(qLcPulseAudioEngine) << pa_context_get_state(context); + QPulseAudioEngine *pulseEngine = reinterpret_cast<QPulseAudioEngine*>(userdata); pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); } @@ -287,9 +280,8 @@ static void contextStateCallback(pa_context *c, void *userdata) QPulseAudioEngine *self = reinterpret_cast<QPulseAudioEngine*>(userdata); pa_context_state_t state = pa_context_get_state(c); -#ifdef DEBUG_PULSE - qDebug() << state; -#endif + if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) + qCDebug(qLcPulseAudioEngine) << state; if (state == PA_CONTEXT_FAILED) QMetaObject::invokeMethod(self, "onContextFailed", Qt::QueuedConnection); @@ -320,12 +312,14 @@ void QPulseAudioEngine::prepare() m_mainLoop = pa_threaded_mainloop_new(); if (m_mainLoop == nullptr) { - qWarning("PulseAudioService: unable to create pulseaudio mainloop"); + qWarning() << "PulseAudioService: unable to create pulseaudio mainloop"; return; } + pa_threaded_mainloop_set_name(m_mainLoop, "QPulseAudioEngi"); // thread names are limited to 15 chars on linux + if (pa_threaded_mainloop_start(m_mainLoop) != 0) { - qWarning("PulseAudioService: unable to start pulseaudio mainloop"); + qWarning() << "PulseAudioService: unable to start pulseaudio mainloop"; pa_threaded_mainloop_free(m_mainLoop); m_mainLoop = nullptr; return; @@ -347,7 +341,7 @@ void QPulseAudioEngine::prepare() pa_proplist_free(proplist); if (m_context == nullptr) { - qWarning("PulseAudioService: Unable to create new pulseaudio context"); + qWarning() << "PulseAudioService: Unable to create new pulseaudio context"; pa_threaded_mainloop_unlock(m_mainLoop); pa_threaded_mainloop_free(m_mainLoop); m_mainLoop = nullptr; @@ -357,8 +351,8 @@ void QPulseAudioEngine::prepare() pa_context_set_state_callback(m_context, contextStateCallbackInit, this); - if (pa_context_connect(m_context, nullptr, (pa_context_flags_t)0, nullptr) < 0) { - qWarning("PulseAudioService: pa_context_connect() failed"); + if (pa_context_connect(m_context, nullptr, static_cast<pa_context_flags_t>(0), nullptr) < 0) { + qWarning() << "PulseAudioService: pa_context_connect() failed"; pa_context_unref(m_context); pa_threaded_mainloop_unlock(m_mainLoop); pa_threaded_mainloop_free(m_mainLoop); @@ -377,9 +371,7 @@ void QPulseAudioEngine::prepare() break; case PA_CONTEXT_READY: -#ifdef DEBUG_PULSE - qDebug("Connection established."); -#endif + qCDebug(qLcPulseAudioEngine) << "Connection established."; keepGoing = false; break; @@ -411,7 +403,7 @@ void QPulseAudioEngine::prepare() | PA_SUBSCRIPTION_MASK_SERVER), nullptr, nullptr)); if (!op) - qWarning("PulseAudioService: failed to subscribe to context notifications"); + qWarning() << "PulseAudioService: failed to subscribe to context notifications"; } else { pa_context_unref(m_context); m_context = nullptr; @@ -435,9 +427,13 @@ void QPulseAudioEngine::release() return; if (m_context) { + lock(); + pa_context_disconnect(m_context); pa_context_unref(m_context); m_context = nullptr; + + unlock(); } if (m_mainLoop) { @@ -459,7 +455,7 @@ void QPulseAudioEngine::updateDevices() while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_mainLoop); } else { - qWarning("PulseAudioService: failed to get server info"); + qWarning() << "PulseAudioService: failed to get server info"; } // Get output devices @@ -468,7 +464,7 @@ void QPulseAudioEngine::updateDevices() while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_mainLoop); } else { - qWarning("PulseAudioService: failed to get sink info"); + qWarning() << "PulseAudioService: failed to get sink info"; } // Get input devices @@ -477,7 +473,7 @@ void QPulseAudioEngine::updateDevices() while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_mainLoop); } else { - qWarning("PulseAudioService: failed to get source info"); + qWarning() << "PulseAudioService: failed to get source info"; } } @@ -489,7 +485,7 @@ void QPulseAudioEngine::onContextFailed() release(); // Try to reconnect later - QTimer::singleShot(3000, this, SLOT(prepare())); + QTimer::singleShot(3000, this, &QPulseAudioEngine::prepare); } QPulseAudioEngine *QPulseAudioEngine::instance() diff --git a/src/multimedia/pulseaudio/qpulseaudiosource.cpp b/src/multimedia/pulseaudio/qpulseaudiosource.cpp index cae01c3e0..ba21ae37f 100644 --- a/src/multimedia/pulseaudio/qpulseaudiosource.cpp +++ b/src/multimedia/pulseaudio/qpulseaudiosource.cpp @@ -33,35 +33,34 @@ static void inputStreamStateCallback(pa_stream *stream, void *userdata) Q_UNUSED(userdata); pa_stream_state_t state = pa_stream_get_state(stream); -#ifdef DEBUG_PULSE - qDebug() << "Stream state: " << state; -#endif + qCDebug(qLcPulseAudioIn) << "Stream state: " << state; switch (state) { - case PA_STREAM_CREATING: + case PA_STREAM_CREATING: break; - case PA_STREAM_READY: { -#ifdef DEBUG_PULSE - QPulseAudioSource *audioInput = static_cast<QPulseAudioSource*>(userdata); + case PA_STREAM_READY: + if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) { + QPulseAudioSource *audioInput = static_cast<QPulseAudioSource *>(userdata); const pa_buffer_attr *buffer_attr = pa_stream_get_buffer_attr(stream); - qDebug() << "*** maxlength: " << buffer_attr->maxlength; - qDebug() << "*** prebuf: " << buffer_attr->prebuf; - qDebug() << "*** fragsize: " << buffer_attr->fragsize; - qDebug() << "*** minreq: " << buffer_attr->minreq; - qDebug() << "*** tlength: " << buffer_attr->tlength; - - pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format()); - qDebug() << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec); -#endif - } - break; - case PA_STREAM_TERMINATED: - break; - case PA_STREAM_FAILED: - default: - qWarning() << "Stream error: " << currentError(stream); - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); - break; + qCDebug(qLcPulseAudioIn) << "*** maxlength: " << buffer_attr->maxlength; + qCDebug(qLcPulseAudioIn) << "*** prebuf: " << buffer_attr->prebuf; + qCDebug(qLcPulseAudioIn) << "*** fragsize: " << buffer_attr->fragsize; + qCDebug(qLcPulseAudioIn) << "*** minreq: " << buffer_attr->minreq; + qCDebug(qLcPulseAudioIn) << "*** tlength: " << buffer_attr->tlength; + + pa_sample_spec spec = + QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format()); + qCDebug(qLcPulseAudioIn) + << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec); + } + break; + case PA_STREAM_TERMINATED: + break; + case PA_STREAM_FAILED: + default: + qWarning() << "Stream error:" << currentError(stream); + QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); + pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); + break; } } @@ -85,32 +84,31 @@ static void inputStreamSuccessCallback(pa_stream *stream, int success, void *use Q_UNUSED(userdata); Q_UNUSED(success); - //if (!success) - //TODO: Is cork success? i->operation_success = success; + // if (!success) + // TODO: Is cork success? i->operation_success = success; QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); } QPulseAudioSource::QPulseAudioSource(const QByteArray &device, QObject *parent) - : QPlatformAudioSource(parent) - , m_totalTimeValue(0) - , m_audioSource(nullptr) - , m_volume(qreal(1.0f)) - , m_pullMode(true) - , m_opened(false) - , m_bufferSize(0) - , m_periodSize(0) - , m_periodTime(SourcePeriodTimeMs) - , m_stream(nullptr) - , m_device(device) - , m_stateMachine(*this) + : QPlatformAudioSource(parent), + m_totalTimeValue(0), + m_audioSource(nullptr), + m_volume(qreal(1.0f)), + m_pullMode(true), + m_opened(false), + m_bufferSize(0), + m_periodSize(0), + m_periodTime(SourcePeriodTimeMs), + m_stream(nullptr), + m_device(device), + m_stateMachine(*this) { } QPulseAudioSource::~QPulseAudioSource() { - qDebug() << Q_FUNC_INFO; // TODO: Investigate draining the stream if (auto notifier = m_stateMachine.stop()) close(); @@ -179,7 +177,8 @@ bool QPulseAudioSource::open() QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) { + if (!pulseEngine->context() + || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) { m_stateMachine.stopOrUpdateError(QAudio::FatalError); return false; } @@ -195,21 +194,21 @@ bool QPulseAudioSource::open() m_spec = spec; -#ifdef DEBUG_PULSE -// QTime now(QTime::currentTime()); -// qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()"; -#endif + //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) { + // QTime now(QTime::currentTime()); + // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :open()"; + //} if (m_streamName.isNull()) m_streamName = QStringLiteral("QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(this)).toUtf8(); -#ifdef DEBUG_PULSE - qDebug() << "Format: " << spec.format; - qDebug() << "Rate: " << spec.rate; - qDebug() << "Channels: " << spec.channels; - qDebug() << "Frame size: " << pa_frame_size(&spec); -#endif + if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) { + qCDebug(qLcPulseAudioIn) << "Format: " << spec.format; + qCDebug(qLcPulseAudioIn) << "Rate: " << spec.rate; + qCDebug(qLcPulseAudioIn) << "Channels: " << spec.channels; + qCDebug(qLcPulseAudioIn) << "Frame size: " << pa_frame_size(&spec); + } pulseEngine->lock(); @@ -221,23 +220,26 @@ bool QPulseAudioSource::open() pa_stream_set_underflow_callback(m_stream, inputStreamUnderflowCallback, this); pa_stream_set_overflow_callback(m_stream, inputStreamOverflowCallback, this); - m_periodSize = pa_usec_to_bytes(SourcePeriodTimeMs*1000, &spec); + m_periodSize = pa_usec_to_bytes(SourcePeriodTimeMs * 1000, &spec); int flags = 0; pa_buffer_attr buffer_attr; - buffer_attr.maxlength = (uint32_t) -1; - buffer_attr.prebuf = (uint32_t) -1; - buffer_attr.tlength = (uint32_t) -1; - buffer_attr.minreq = (uint32_t) -1; + buffer_attr.maxlength = static_cast<uint32_t>(-1); + buffer_attr.prebuf = static_cast<uint32_t>(-1); + buffer_attr.tlength = static_cast<uint32_t>(-1); + buffer_attr.minreq = static_cast<uint32_t>(-1); flags |= PA_STREAM_ADJUST_LATENCY; if (m_bufferSize > 0) - buffer_attr.fragsize = (uint32_t) m_bufferSize; + buffer_attr.fragsize = static_cast<uint32_t>(m_bufferSize); else - buffer_attr.fragsize = (uint32_t) m_periodSize; + buffer_attr.fragsize = static_cast<uint32_t>(m_periodSize); - flags |= PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING; - if (pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, (pa_stream_flags_t)flags) < 0) { + flags |= PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING; + + int connectionResult = pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, + static_cast<pa_stream_flags_t>(flags)); + if (connectionResult < 0) { qWarning() << "pa_stream_connect_record() failed!"; pa_stream_unref(m_stream); m_stream = nullptr; @@ -246,11 +248,13 @@ bool QPulseAudioSource::open() return false; } -// auto *ss = pa_stream_get_sample_spec(m_stream); -// qDebug() << "connected stream:"; -// qDebug() << " channels" << ss->channels << spec.channels; -// qDebug() << " format" << ss->format << spec.format; -// qDebug() << " rate" << ss->rate << spec.rate; + //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) { + // auto *ss = pa_stream_get_sample_spec(m_stream); + // qCDebug(qLcPulseAudioIn) << "connected stream:"; + // qCDebug(qLcPulseAudioIn) << " channels" << ss->channels << spec.channels; + // qCDebug(qLcPulseAudioIn) << " format" << ss->format << spec.format; + // qCDebug(qLcPulseAudioIn) << " rate" << ss->rate << spec.rate; + //} while (pa_stream_get_state(m_stream) != PA_STREAM_READY) pa_threaded_mainloop_wait(pulseEngine->mainloop()); @@ -258,12 +262,13 @@ bool QPulseAudioSource::open() const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(m_stream); m_periodSize = actualBufferAttr->fragsize; m_periodTime = pa_bytes_to_usec(m_periodSize, &spec) / 1000; - if (actualBufferAttr->tlength != (uint32_t)-1) + if (actualBufferAttr->tlength != static_cast<uint32_t>(-1)) m_bufferSize = actualBufferAttr->tlength; pulseEngine->unlock(); - connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed); + connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, + &QPulseAudioSource::onPulseContextFailed); m_opened = true; m_timer.start(m_periodTime, this); @@ -296,7 +301,8 @@ void QPulseAudioSource::close() m_stream = nullptr; } - disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed); + disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, + &QPulseAudioSource::onPulseContextFailed); if (!m_pullMode && m_audioSource) { delete m_audioSource; @@ -349,18 +355,20 @@ qint64 QPulseAudioSource::read(char *data, qint64 len) while (pa_stream_readable_size(m_stream) > 0) { size_t readLength = 0; -#ifdef DEBUG_PULSE - qDebug() << "QPulseAudioSource::read -- " << pa_stream_readable_size(m_stream) << " bytes available from pulse audio"; -#endif + if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) { + auto readableSize = pa_stream_readable_size(m_stream); + qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- " << readableSize + << " bytes available from pulse audio"; + } QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); pulseEngine->lock(); const void *audioBuffer; - // Second and third parameters (audioBuffer and length) to pa_stream_peek are output parameters, - // the audioBuffer pointer is set to point to the actual pulse audio data, - // and the length is set to the length of this data. + // Second and third parameters (audioBuffer and length) to pa_stream_peek are output + // parameters, the audioBuffer pointer is set to point to the actual pulse audio data, and + // the length is set to the length of this data. if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) { qWarning() << "pa_stream_peek() failed:" << currentError(m_stream); pulseEngine->unlock(); @@ -383,18 +391,19 @@ qint64 QPulseAudioSource::read(char *data, qint64 len) applyVolume(audioBuffer, data + readBytes, actualLength); } -#ifdef DEBUG_PULSE - qDebug() << "QPulseAudioSource::read -- wrote " << actualLength << " to client"; -#endif + qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- wrote " << actualLength + << " to client"; if (actualLength < qint64(readLength)) { -#ifdef DEBUG_PULSE - qDebug() << "QPulseAudioSource::read -- appending " << readLength - actualLength << " bytes of data to temp buffer"; -#endif int diff = readLength - actualLength; int oldSize = m_tempBuffer.size(); + + qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- appending " << diff + << " bytes of data to temp buffer"; + m_tempBuffer.resize(m_tempBuffer.size() + diff); - applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff); + applyVolume(static_cast<const char *>(audioBuffer) + actualLength, + m_tempBuffer.data() + oldSize, diff); QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection); } @@ -408,9 +417,8 @@ qint64 QPulseAudioSource::read(char *data, qint64 len) break; } -#ifdef DEBUG_PULSE - qDebug() << "QPulseAudioSource::read -- returning after reading " << readBytes << " bytes"; -#endif + qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- returning after reading " << readBytes + << " bytes"; return readBytes; } @@ -471,8 +479,8 @@ qint64 QPulseAudioSource::processedUSecs() const pa_usec_t usecs = 0; int result = pa_stream_get_time(m_stream, &usecs); Q_UNUSED(result); -// if (result != 0) -// qWarning() << "no timing info from pulse"; + //if (result != 0) + // qWarning() << "no timing info from pulse"; return usecs; } @@ -503,16 +511,18 @@ void QPulseAudioSource::userFeed() { if (!m_stateMachine.isActiveOrIdle()) return; -#ifdef DEBUG_PULSE -// QTime now(QTime::currentTime()); -// qDebug()<< now.second() << "s " << now.msec() << "ms :userFeed() IN"; -#endif - if (m_pullMode) { + + //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) { + // QTime now(QTime::currentTime()); + // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :userFeed() IN"; + //} + + if (m_pullMode) { // reads some audio data and writes it to QIODevice - read(nullptr,0); + read(nullptr, 0); } else if (m_audioSource != nullptr) { // emits readyRead() so user will call read() on QIODevice to get some audio data - PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource); + PulseInputPrivate *a = qobject_cast<PulseInputPrivate *>(m_audioSource); a->trigger(); } } @@ -531,7 +541,7 @@ void QPulseAudioSource::onPulseContextFailed() PulseInputPrivate::PulseInputPrivate(QPulseAudioSource *audio) { - m_audioDevice = qobject_cast<QPulseAudioSource*>(audio); + m_audioDevice = qobject_cast<QPulseAudioSource *>(audio); } qint64 PulseInputPrivate::readData(char *data, qint64 len) diff --git a/src/multimedia/pulseaudio/qpulsehelpers.cpp b/src/multimedia/pulseaudio/qpulsehelpers.cpp index 66a08a459..bc03e133f 100644 --- a/src/multimedia/pulseaudio/qpulsehelpers.cpp +++ b/src/multimedia/pulseaudio/qpulsehelpers.cpp @@ -6,6 +6,8 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcPulseAudioOut, "qt.multimedia.pulseaudio.output") +Q_LOGGING_CATEGORY(qLcPulseAudioIn, "qt.multimedia.pulseaudio.input") +Q_LOGGING_CATEGORY(qLcPulseAudioEngine, "qt.multimedia.pulseaudio.engine") namespace QPulseAudioInternal { diff --git a/src/multimedia/pulseaudio/qpulsehelpers_p.h b/src/multimedia/pulseaudio/qpulsehelpers_p.h index 787edfb39..d271fde48 100644 --- a/src/multimedia/pulseaudio/qpulsehelpers_p.h +++ b/src/multimedia/pulseaudio/qpulsehelpers_p.h @@ -24,6 +24,8 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioOut) +Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioIn) +Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioEngine) struct PAOperationDeleter { diff --git a/src/multimedia/qmediametadata.cpp b/src/multimedia/qmediametadata.cpp index dc238721f..6a9b016a9 100644 --- a/src/multimedia/qmediametadata.cpp +++ b/src/multimedia/qmediametadata.cpp @@ -2,14 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qmediametadata.h" + #include <QtCore/qcoreapplication.h> -#include <qvariant.h> -#include <qobject.h> -#include <qdatetime.h> -#include <qmediaformat.h> -#include <qsize.h> -#include <qurl.h> -#include <qimage.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qobject.h> +#include <QtCore/qsize.h> +#include <QtCore/qurl.h> +#include <QtCore/qvariant.h> +#include <QtGui/qimage.h> +#include <QtMultimedia/qmediaformat.h> QT_BEGIN_NAMESPACE diff --git a/src/multimedia/qmultimediautils.cpp b/src/multimedia/qmultimediautils.cpp index ce64add39..ae3cb03eb 100644 --- a/src/multimedia/qmultimediautils.cpp +++ b/src/multimedia/qmultimediautils.cpp @@ -64,10 +64,7 @@ QSize qRotatedFrameSize(const QVideoFrame &frame) QUrl qMediaFromUserInput(QUrl url) { - using namespace Qt::Literals; - if (url.scheme().isEmpty() || url.scheme() == "file"_L1) - url = QUrl::fromUserInput(url.toString(), QDir::currentPath(), QUrl::AssumeLocalFile); - return url; + return QUrl::fromUserInput(url.toString(), QDir::currentPath(), QUrl::AssumeLocalFile); } QT_END_NAMESPACE diff --git a/src/multimedia/recording/qmediacapturesession.cpp b/src/multimedia/recording/qmediacapturesession.cpp index df031c612..814332191 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" @@ -9,8 +10,6 @@ #include "qvideosink.h" #include "qscreencapture.h" -#include <qpointer.h> - #include "qplatformmediaintegration_p.h" #include "qplatformmediacapture_p.h" #include "qaudioinput.h" @@ -18,34 +17,20 @@ 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<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(); - } + Q_Q(QMediaCaptureSession); + if (sink == videoSink) + return; + if (videoSink) + videoSink->setSource(nullptr); + videoSink = sink; + if (sink) + sink->setSource(q); + if (captureSession) + captureSession->setVideoPreview(sink); + emit q->videoOutputChanged(); }; /*! @@ -125,14 +110,16 @@ public: Creates a session for media capture from the \a parent object. */ QMediaCaptureSession::QMediaCaptureSession(QObject *parent) - : QObject(parent), - d_ptr(new QMediaCaptureSessionPrivate) + : QObject{ *new QMediaCaptureSessionPrivate, parent } { - d_ptr->q = this; + QT6_ONLY(Q_UNUSED(unused)) + + Q_D(QMediaCaptureSession); + auto maybeCaptureSession = QPlatformMediaIntegration::instance()->createCaptureSession(); if (maybeCaptureSession) { - d_ptr->captureSession = maybeCaptureSession.value(); - d_ptr->captureSession->setCaptureSession(this); + d->captureSession.reset(maybeCaptureSession.value()); + d->captureSession->setCaptureSession(this); } else { qWarning() << "Failed to initialize QMediaCaptureSession" << maybeCaptureSession.error(); } @@ -143,15 +130,16 @@ QMediaCaptureSession::QMediaCaptureSession(QObject *parent) */ QMediaCaptureSession::~QMediaCaptureSession() { + Q_D(QMediaCaptureSession); + setCamera(nullptr); setRecorder(nullptr); setImageCapture(nullptr); setScreenCapture(nullptr); setAudioInput(nullptr); setAudioOutput(nullptr); - d_ptr->setVideoSink(nullptr); - delete d_ptr->captureSession; - delete d_ptr; + d->setVideoSink(nullptr); + d->captureSession.reset(); } /*! \qmlproperty AudioInput QtMultimedia::CaptureSession::audioInput @@ -166,7 +154,8 @@ QMediaCaptureSession::~QMediaCaptureSession() */ QAudioInput *QMediaCaptureSession::audioInput() const { - return d_ptr->audioInput; + Q_D(const QMediaCaptureSession); + return d->audioInput; } /*! @@ -176,24 +165,26 @@ QAudioInput *QMediaCaptureSession::audioInput() const */ void QMediaCaptureSession::setAudioInput(QAudioInput *input) { - QAudioInput *oldInput = d_ptr->audioInput; + Q_D(QMediaCaptureSession); + + QAudioInput *oldInput = d->audioInput; if (oldInput == input) return; // To avoid double emit of audioInputChanged // from recursive setAudioInput(nullptr) call. - d_ptr->audioInput = nullptr; + d->audioInput = nullptr; - if (d_ptr->captureSession) - d_ptr->captureSession->setAudioInput(nullptr); + if (d->captureSession) + d->captureSession->setAudioInput(nullptr); if (oldInput) oldInput->setDisconnectFunction({}); if (input) { input->setDisconnectFunction([this](){ setAudioInput(nullptr); }); - if (d_ptr->captureSession) - d_ptr->captureSession->setAudioInput(input->handle()); + if (d->captureSession) + d->captureSession->setAudioInput(input->handle()); } - d_ptr->audioInput = input; + d->audioInput = input; emit audioInputChanged(); } @@ -216,18 +207,22 @@ void QMediaCaptureSession::setAudioInput(QAudioInput *input) */ QCamera *QMediaCaptureSession::camera() const { - return d_ptr->camera; + Q_D(const QMediaCaptureSession); + + return d->camera; } void QMediaCaptureSession::setCamera(QCamera *camera) { + Q_D(QMediaCaptureSession); + // TODO: come up with an unification of the captures setup - QCamera *oldCamera = d_ptr->camera; + QCamera *oldCamera = d->camera; if (oldCamera == camera) return; - d_ptr->camera = camera; - if (d_ptr->captureSession) - d_ptr->captureSession->setCamera(nullptr); + d->camera = camera; + if (d->captureSession) + d->captureSession->setCamera(nullptr); if (oldCamera) { if (oldCamera->captureSession() && oldCamera->captureSession() != this) oldCamera->captureSession()->setCamera(nullptr); @@ -236,8 +231,8 @@ void QMediaCaptureSession::setCamera(QCamera *camera) if (camera) { if (camera->captureSession()) camera->captureSession()->setCamera(nullptr); - if (d_ptr->captureSession) - d_ptr->captureSession->setCamera(camera->platformCamera()); + if (d->captureSession) + d->captureSession->setCamera(camera->platformCamera()); camera->setCaptureSession(this); } emit cameraChanged(); @@ -264,18 +259,22 @@ void QMediaCaptureSession::setCamera(QCamera *camera) */ QScreenCapture *QMediaCaptureSession::screenCapture() { - return d_ptr ? d_ptr->screenCapture : nullptr; + Q_D(QMediaCaptureSession); + + return d->screenCapture; } void QMediaCaptureSession::setScreenCapture(QScreenCapture *screenCapture) { + Q_D(QMediaCaptureSession); + // TODO: come up with an unification of the captures setup - QScreenCapture *oldScreenCapture = d_ptr->screenCapture; + QScreenCapture *oldScreenCapture = d->screenCapture; if (oldScreenCapture == screenCapture) return; - d_ptr->screenCapture = screenCapture; - if (d_ptr->captureSession) - d_ptr->captureSession->setScreenCapture(nullptr); + d->screenCapture = screenCapture; + if (d->captureSession) + d->captureSession->setScreenCapture(nullptr); if (oldScreenCapture) { if (oldScreenCapture->captureSession() && oldScreenCapture->captureSession() != this) oldScreenCapture->captureSession()->setScreenCapture(nullptr); @@ -284,8 +283,8 @@ void QMediaCaptureSession::setScreenCapture(QScreenCapture *screenCapture) if (screenCapture) { if (screenCapture->captureSession()) screenCapture->captureSession()->setScreenCapture(nullptr); - if (d_ptr->captureSession) - d_ptr->captureSession->setScreenCapture(screenCapture->platformScreenCapture()); + if (d->captureSession) + d->captureSession->setScreenCapture(screenCapture->platformScreenCapture()); screenCapture->setCaptureSession(this); } emit screenCaptureChanged(); @@ -309,18 +308,22 @@ void QMediaCaptureSession::setScreenCapture(QScreenCapture *screenCapture) */ QImageCapture *QMediaCaptureSession::imageCapture() { - return d_ptr->imageCapture; + Q_D(QMediaCaptureSession); + + return d->imageCapture; } void QMediaCaptureSession::setImageCapture(QImageCapture *imageCapture) { + Q_D(QMediaCaptureSession); + // TODO: come up with an unification of the captures setup - QImageCapture *oldImageCapture = d_ptr->imageCapture; + QImageCapture *oldImageCapture = d->imageCapture; if (oldImageCapture == imageCapture) return; - d_ptr->imageCapture = imageCapture; - if (d_ptr->captureSession) - d_ptr->captureSession->setImageCapture(nullptr); + d->imageCapture = imageCapture; + if (d->captureSession) + d->captureSession->setImageCapture(nullptr); if (oldImageCapture) { if (oldImageCapture->captureSession() && oldImageCapture->captureSession() != this) oldImageCapture->captureSession()->setImageCapture(nullptr); @@ -329,8 +332,8 @@ void QMediaCaptureSession::setImageCapture(QImageCapture *imageCapture) if (imageCapture) { if (imageCapture->captureSession()) imageCapture->captureSession()->setImageCapture(nullptr); - if (d_ptr->captureSession) - d_ptr->captureSession->setImageCapture(imageCapture->platformImageCapture()); + if (d->captureSession) + d->captureSession->setImageCapture(imageCapture->platformImageCapture()); imageCapture->setCaptureSession(this); } emit imageCaptureChanged(); @@ -354,17 +357,19 @@ void QMediaCaptureSession::setImageCapture(QImageCapture *imageCapture) QMediaRecorder *QMediaCaptureSession::recorder() { - return d_ptr->recorder; + Q_D(QMediaCaptureSession); + return d->recorder; } void QMediaCaptureSession::setRecorder(QMediaRecorder *recorder) { - QMediaRecorder *oldRecorder = d_ptr->recorder; + Q_D(QMediaCaptureSession); + QMediaRecorder *oldRecorder = d->recorder; if (oldRecorder == recorder) return; - d_ptr->recorder = recorder; - if (d_ptr->captureSession) - d_ptr->captureSession->setMediaRecorder(nullptr); + d->recorder = recorder; + if (d->captureSession) + d->captureSession->setMediaRecorder(nullptr); if (oldRecorder) { if (oldRecorder->captureSession() && oldRecorder->captureSession() != this) oldRecorder->captureSession()->setRecorder(nullptr); @@ -373,8 +378,8 @@ void QMediaCaptureSession::setRecorder(QMediaRecorder *recorder) if (recorder) { if (recorder->captureSession()) recorder->captureSession()->setRecorder(nullptr); - if (d_ptr->captureSession) - d_ptr->captureSession->setMediaRecorder(recorder->platformRecoder()); + if (d->captureSession) + d->captureSession->setMediaRecorder(recorder->platformRecoder()); recorder->setCaptureSession(this); } emit recorderChanged(); @@ -452,25 +457,27 @@ QVideoSink *QMediaCaptureSession::videoSink() const */ void QMediaCaptureSession::setAudioOutput(QAudioOutput *output) { - QAudioOutput *oldOutput = d_ptr->audioOutput; + Q_D(QMediaCaptureSession); + + QAudioOutput *oldOutput = d->audioOutput; if (oldOutput == output) return; // We don't want to end up with signal emitted // twice (from recursive call setAudioInput(nullptr) // from oldOutput->setDisconnectFunction(): - d_ptr->audioOutput = nullptr; + d->audioOutput = nullptr; - if (d_ptr->captureSession) - d_ptr->captureSession->setAudioOutput(nullptr); + if (d->captureSession) + d->captureSession->setAudioOutput(nullptr); if (oldOutput) oldOutput->setDisconnectFunction({}); if (output) { output->setDisconnectFunction([this](){ setAudioOutput(nullptr); }); - if (d_ptr->captureSession) - d_ptr->captureSession->setAudioOutput(output->handle()); + if (d->captureSession) + d->captureSession->setAudioOutput(output->handle()); } - d_ptr->audioOutput = output; + d->audioOutput = output; emit audioOutputChanged(); } /*! @@ -496,7 +503,8 @@ QAudioOutput *QMediaCaptureSession::audioOutput() const */ QPlatformMediaCaptureSession *QMediaCaptureSession::platformSession() const { - return d_ptr->captureSession; + Q_D(const QMediaCaptureSession); + return d->captureSession.get(); } /*! \qmlsignal QtMultimedia::CaptureSession::audioInputChanged() diff --git a/src/multimedia/recording/qmediacapturesession.h b/src/multimedia/recording/qmediacapturesession.h index daef2a3ab..abff47f52 100644 --- a/src/multimedia/recording/qmediacapturesession.h +++ b/src/multimedia/recording/qmediacapturesession.h @@ -71,7 +71,11 @@ Q_SIGNALS: void audioOutputChanged(); private: - QMediaCaptureSessionPrivate *d_ptr; + friend class QPlatformMediaCaptureSession; + + // ### Qt7: remove unused member + QT6_ONLY(void *unused = nullptr;) // for ABI compatibility + 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..aafec92c0 --- /dev/null +++ b/src/multimedia/recording/qmediacapturesession_p.h @@ -0,0 +1,50 @@ +// 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> +#include <QtCore/private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QMediaCaptureSessionPrivate : public QObjectPrivate +{ +public: + static QMediaCaptureSessionPrivate *get(QMediaCaptureSession *session) + { + return reinterpret_cast<QMediaCaptureSessionPrivate *>(QObjectPrivate::get(session)); + } + + Q_DECLARE_PUBLIC(QMediaCaptureSession) + + std::unique_ptr<QPlatformMediaCaptureSession> captureSession; + QAudioInput *audioInput = nullptr; + QAudioOutput *audioOutput = nullptr; + QPointer<QCamera> camera; + QPointer<QScreenCapture> screenCapture; + 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/recording/qscreencapture-limitations.qdocinc b/src/multimedia/recording/qscreencapture-limitations.qdocinc index a50525f7e..9dd2e043c 100644 --- a/src/multimedia/recording/qscreencapture-limitations.qdocinc +++ b/src/multimedia/recording/qscreencapture-limitations.qdocinc @@ -1,22 +1,26 @@ -// Copyright (C) 2023 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 +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! //! [content] \section1 Screen Capture Limitations - On Qt 6.5.2 and 6.5.3, the following limitations apply to using \l ScreenCapture: + On Qt 6.5.2 and above, the following limitations apply to using \1ScreenCapture: \list \li It is only supported with the FFmpeg backend. - \li It is supported on all desktop platforms, except Linux with Wayland - compositor, due to Wayland protocol restrictions and limitations. + \li It is unsupported on Linux with Wayland compositor, due to Wayland + protocol restrictions and limitations. \li It is not supported on mobile operating systems, except on Android. There, you might run into performance issues as the class is currently implemented via QScreen::grabWindow, which is not optimal for the use case. - \li On Linux, it works with X11, but it has not been tested on embedded. + \li On embedded with EGLFS, it has limited functionality. Capturing Widget-based + applications works from Qt 6.5.5, and capturring Quick-based applications + works from Qt 6.5.6. For Qt Quick applications, the class is currently + implemented via QQuickWindow::grabWindow, which can cause performance issues. \li In most cases, we set a screen capture frame rate that equals the screen refresh rate, except on Windows, where the rate might be flexible. Such a frame rate (75/120 FPS) might cause performance issues on weak - CPUs if the captured screen is of 4K resolution. + CPUs if the captured screen is of 4K resolution. On EGLFS, the capture + frame rate is currently locked to 30 FPS. \endlist //! [content] */ diff --git a/src/multimedia/shaders/uyvy.frag b/src/multimedia/shaders/uyvy.frag index 26a83da3f..1fad19dda 100644 --- a/src/multimedia/shaders/uyvy.frag +++ b/src/multimedia/shaders/uyvy.frag @@ -11,10 +11,31 @@ layout(binding = 1) uniform sampler2D plane1Texture; void main() { - int x = int(floor(texCoord.x * ubuf.width)); - bool rightSubPixel = (x/2*2 != x); - float Y = rightSubPixel ? texture(plane1Texture, texCoord).a : texture(plane1Texture, texCoord).g; - vec2 UV = texture(plane1Texture, texCoord).rb; + // UYVY input texture is half the width of output texture + // + // | x | y | z | w | + // | U0 | Y0 | V0 | Y1 | + // \ \_________________ + // RGB \ \ + // Output \ \ + // | r | g | b | a | | r | g | b | a | + // x is even x is odd + + // When converting to RGBA, we should sample lumen Y0 + // when output column is even, and Y1 when output column + // is odd + + float colIndex = floor(texCoord.x * ubuf.width); + float oddOutputCol = mod(colIndex, 2); + + // dxInput is the pixel width in the half-width input texture + vec2 dxInput = 0.5 * vec2(1 / ubuf.width, 0); + + float oddY = texture(plane1Texture, texCoord - dxInput).w; + float evenY = texture(plane1Texture, texCoord + dxInput).y; + float Y = mix(evenY, oddY, oddOutputCol); + + vec2 UV = texture(plane1Texture, texCoord).xz; fragColor = ubuf.colorMatrix * vec4(Y, UV, 1.0) * ubuf.opacity; #ifdef QMM_OUTPUTSURFACE_LINEAR diff --git a/src/multimedia/shaders/yuyv.frag b/src/multimedia/shaders/yuyv.frag index 0f6e65b6d..180051ef6 100644 --- a/src/multimedia/shaders/yuyv.frag +++ b/src/multimedia/shaders/yuyv.frag @@ -11,10 +11,31 @@ layout(binding = 1) uniform sampler2D plane1Texture; void main() { - int x = int(floor(texCoord.x * ubuf.width)); - bool rightSubPixel = (x/2*2 != x); - float Y = rightSubPixel ? texture(plane1Texture, texCoord).b : texture(plane1Texture, texCoord).r; - vec2 UV = texture(plane1Texture, texCoord).ga; + // YUYV input texture is half the width of output texture + // + // | x | y | z | w | + // | Y0 | U0 | Y1 | V0 | + // \ \_________________ + // RGB \ \ + // Output \ \ + // | r | g | b | a | | r | g | b | a | + // x is even x is odd + + // When converting to RGBA, we should sample lumen Y0 + // when output column is even, and Y1 when output column + // is odd + + float colIndex = floor(texCoord.x * ubuf.width); + float oddOutputCol = mod(colIndex, 2); + + // dxInput is the pixel width in the half-width input texture + vec2 dxInput = 0.5 * vec2(1 / ubuf.width, 0); + + float oddY = texture(plane1Texture, texCoord - dxInput).z; + float evenY = texture(plane1Texture, texCoord + dxInput).x; + float Y = mix(evenY, oddY, oddOutputCol); + + vec2 UV = texture(plane1Texture, texCoord).yw; fragColor = ubuf.colorMatrix * vec4(Y, UV, 1.0) * ubuf.opacity; #ifdef QMM_OUTPUTSURFACE_LINEAR diff --git a/src/multimedia/video/qabstractvideobuffer_p.h b/src/multimedia/video/qabstractvideobuffer_p.h index 2004e25f7..5945c449f 100644 --- a/src/multimedia/video/qabstractvideobuffer_p.h +++ b/src/multimedia/video/qabstractvideobuffer_p.h @@ -64,7 +64,6 @@ public: virtual QMatrix4x4 externalTextureMatrix() const { return {}; } - virtual QByteArray underlyingByteArray(int /*plane*/) const { return {}; } protected: QVideoFrame::HandleType m_type; QRhi *m_rhi = nullptr; diff --git a/src/multimedia/video/qmemoryvideobuffer.cpp b/src/multimedia/video/qmemoryvideobuffer.cpp index bcbbe7e59..a004f4425 100644 --- a/src/multimedia/video/qmemoryvideobuffer.cpp +++ b/src/multimedia/video/qmemoryvideobuffer.cpp @@ -68,12 +68,4 @@ void QMemoryVideoBuffer::unmap() m_mapMode = QVideoFrame::NotMapped; } -/*! - \reimp -*/ -QByteArray QMemoryVideoBuffer::underlyingByteArray(int plane) const -{ - return plane == 0 ? m_data : QByteArray{}; -} - QT_END_NAMESPACE diff --git a/src/multimedia/video/qmemoryvideobuffer_p.h b/src/multimedia/video/qmemoryvideobuffer_p.h index ec97abd4f..d15e91ad9 100644 --- a/src/multimedia/video/qmemoryvideobuffer_p.h +++ b/src/multimedia/video/qmemoryvideobuffer_p.h @@ -30,7 +30,6 @@ public: MapData map(QVideoFrame::MapMode mode) override; void unmap() override; - QByteArray underlyingByteArray(int plane) const override; private: int m_bytesPerLine = 0; QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped; diff --git a/src/multimedia/video/qvideoframe.cpp b/src/multimedia/video/qvideoframe.cpp index 73c8e7a71..b6d3fdac1 100644 --- a/src/multimedia/video/qvideoframe.cpp +++ b/src/multimedia/video/qvideoframe.cpp @@ -632,12 +632,13 @@ QVideoFrame::RotationAngle QVideoFrame::rotationAngle() const } /*! - Sets the \a mirrored flag for the frame. + Sets the \a mirrored flag for the frame and + sets the flag to the underlying \l surfaceFormat. */ void QVideoFrame::setMirrored(bool mirrored) { if (d) - d->mirrored = mirrored; + d->format.setMirrored(mirrored); } /*! @@ -645,7 +646,7 @@ void QVideoFrame::setMirrored(bool mirrored) */ bool QVideoFrame::mirrored() const { - return d && d->mirrored; + return d && d->format.isMirrored(); } /*! diff --git a/src/multimedia/video/qvideoframe_p.h b/src/multimedia/video/qvideoframe_p.h index ba541814b..204e3ef08 100644 --- a/src/multimedia/video/qvideoframe_p.h +++ b/src/multimedia/video/qvideoframe_p.h @@ -48,7 +48,6 @@ public: QMutex mapMutex; QString subtitleText; QtVideo::Rotation rotation = QtVideo::Rotation::None; - bool mirrored = false; QImage image; QMutex imageMutex; diff --git a/src/multimedia/video/qvideoframeconversionhelper.cpp b/src/multimedia/video/qvideoframeconversionhelper.cpp index 1b570b74f..d3f2b0403 100644 --- a/src/multimedia/video/qvideoframeconversionhelper.cpp +++ b/src/multimedia/video/qvideoframeconversionhelper.cpp @@ -34,31 +34,30 @@ static inline void planarYUV420_to_ARGB32(const uchar *y, int yStride, int width, int height) { height &= ~1; - quint32 *rgb0 = rgb; - quint32 *rgb1 = rgb + width; - for (int j = 0; j < height; j += 2) { + for (int j = 0; j + 1 < height; j += 2) { const uchar *lineY0 = y; const uchar *lineY1 = y + yStride; const uchar *lineU = u; const uchar *lineV = v; - for (int i = 0; i < width; i += 2) { + quint32 *rgb0 = rgb; + quint32 *rgb1 = rgb + width; + for (int i = 0; i + 1 < width; i += 2) { EXPAND_UV(*lineU, *lineV); lineU += uvPixelStride; lineV += uvPixelStride; - *rgb0++ = qYUVToARGB32(*lineY0++, rv, guv, bu); - *rgb0++ = qYUVToARGB32(*lineY0++, rv, guv, bu); - *rgb1++ = qYUVToARGB32(*lineY1++, rv, guv, bu); - *rgb1++ = qYUVToARGB32(*lineY1++, rv, guv, bu); + rgb0[i] = qYUVToARGB32(*lineY0++, rv, guv, bu); + rgb0[i + 1] = qYUVToARGB32(*lineY0++, rv, guv, bu); + rgb1[i] = qYUVToARGB32(*lineY1++, rv, guv, bu); + rgb1[i + 1] = qYUVToARGB32(*lineY1++, rv, guv, bu); } y += yStride << 1; // stride * 2 u += uStride; v += vStride; - rgb0 += width; - rgb1 += width; + rgb += width << 1; // width * 2 } } @@ -69,31 +68,27 @@ static inline void planarYUV422_to_ARGB32(const uchar *y, int yStride, quint32 *rgb, int width, int height) { - quint32 *rgb0 = rgb; - for (int j = 0; j < height; ++j) { const uchar *lineY0 = y; const uchar *lineU = u; const uchar *lineV = v; - for (int i = 0; i < width; i += 2) { + for (int i = 0; i + 1 < width; i += 2) { EXPAND_UV(*lineU, *lineV); lineU += uvPixelStride; lineV += uvPixelStride; - *rgb0++ = qYUVToARGB32(*lineY0++, rv, guv, bu); - *rgb0++ = qYUVToARGB32(*lineY0++, rv, guv, bu); + rgb[i] = qYUVToARGB32(*lineY0++, rv, guv, bu); + rgb[i+1] = qYUVToARGB32(*lineY0++, rv, guv, bu); } y += yStride; // stride * 2 u += uStride; v += vStride; - rgb0 += width; + rgb += width; } } - - static void QT_FASTCALL qt_convert_YUV420P_to_ARGB32(const QVideoFrame &frame, uchar *output) { FETCH_INFO_TRIPLANAR(frame) @@ -187,8 +182,7 @@ static void QT_FASTCALL qt_convert_UYVY_to_ARGB32(const QVideoFrame &frame, ucha for (int i = 0; i < height; ++i) { const uchar *lineSrc = src; - - for (int j = 0; j < width; j += 2) { + for (int j = 0; j + 1 < width; j += 2) { int u = *lineSrc++; int y0 = *lineSrc++; int v = *lineSrc++; @@ -196,11 +190,12 @@ static void QT_FASTCALL qt_convert_UYVY_to_ARGB32(const QVideoFrame &frame, ucha EXPAND_UV(u, v); - *rgb++ = qYUVToARGB32(y0, rv, guv, bu); - *rgb++ = qYUVToARGB32(y1, rv, guv, bu); + rgb[j] = qYUVToARGB32(y0, rv, guv, bu); + rgb[j+1] = qYUVToARGB32(y1, rv, guv, bu); } src += stride; + rgb += width; } } @@ -213,8 +208,7 @@ static void QT_FASTCALL qt_convert_YUYV_to_ARGB32(const QVideoFrame &frame, ucha for (int i = 0; i < height; ++i) { const uchar *lineSrc = src; - - for (int j = 0; j < width; j += 2) { + for (int j = 0; j + 1 < width; j += 2) { int y0 = *lineSrc++; int u = *lineSrc++; int y1 = *lineSrc++; @@ -222,11 +216,12 @@ static void QT_FASTCALL qt_convert_YUYV_to_ARGB32(const QVideoFrame &frame, ucha EXPAND_UV(u, v); - *rgb++ = qYUVToARGB32(y0, rv, guv, bu); - *rgb++ = qYUVToARGB32(y1, rv, guv, bu); + rgb[j] = qYUVToARGB32(y0, rv, guv, bu); + rgb[j+1] = qYUVToARGB32(y1, rv, guv, bu); } src += stride; + rgb += width; } } @@ -376,23 +371,24 @@ static void QT_FASTCALL qt_convert_premultiplied_to_ARGB32(const QVideoFrame &fr } static inline void planarYUV420_16bit_to_ARGB32(const uchar *y, int yStride, - const uchar *u, int uStride, - const uchar *v, int vStride, - int uvPixelStride, - quint32 *rgb, - int width, int height) + const uchar *u, int uStride, + const uchar *v, int vStride, + int uvPixelStride, + quint32 *rgb, + int width, int height) { height &= ~1; - quint32 *rgb0 = rgb; - quint32 *rgb1 = rgb + width; - for (int j = 0; j < height; j += 2) { + for (int j = 0; j + 1 < height; j += 2) { const uchar *lineY0 = y; const uchar *lineY1 = y + yStride; const uchar *lineU = u; const uchar *lineV = v; - for (int i = 0; i < width; i += 2) { + quint32 *rgb0 = rgb; + quint32 *rgb1 = rgb + width; + + for (int i = 0; i + 1 < width; i += 2) { EXPAND_UV(*lineU, *lineV); lineU += uvPixelStride; lineV += uvPixelStride; @@ -410,11 +406,11 @@ static inline void planarYUV420_16bit_to_ARGB32(const uchar *y, int yStride, y += yStride << 1; // stride * 2 u += uStride; v += vStride; - rgb0 += width; - rgb1 += width; + rgb += width * 2; } } + static void QT_FASTCALL qt_convert_P016_to_ARGB32(const QVideoFrame &frame, uchar *output) { FETCH_INFO_BIPLANAR(frame) diff --git a/src/multimedia/video/qvideoframeconverter.cpp b/src/multimedia/video/qvideoframeconverter.cpp index b6de38eca..46c88f25a 100644 --- a/src/multimedia/video/qvideoframeconverter.cpp +++ b/src/multimedia/video/qvideoframeconverter.cpp @@ -300,7 +300,8 @@ static QImage convertCPU(const QVideoFrame &frame, QtVideo::Rotation rotation, b } } -QImage qImageFromVideoFrame(const QVideoFrame &frame, QtVideo::Rotation rotation, bool mirrorX, bool mirrorY) +QImage qImageFromVideoFrame(const QVideoFrame &frame, QtVideo::Rotation rotation, bool mirrorX, + bool mirrorY, bool forceCpu) { #ifdef Q_OS_DARWIN QMacAutoReleasePool releasePool; @@ -324,6 +325,9 @@ QImage qImageFromVideoFrame(const QVideoFrame &frame, QtVideo::Rotation rotation if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg) return convertJPEG(frame, rotation, mirrorX, mirrorY); + if (forceCpu) // For test purposes + return convertCPU(frame, rotation, mirrorX, mirrorY); + QRhi *rhi = nullptr; if (frame.videoBuffer()) diff --git a/src/multimedia/video/qvideoframeconverter_p.h b/src/multimedia/video/qvideoframeconverter_p.h index 5619b4014..bf6facf23 100644 --- a/src/multimedia/video/qvideoframeconverter_p.h +++ b/src/multimedia/video/qvideoframeconverter_p.h @@ -20,7 +20,9 @@ QT_BEGIN_NAMESPACE -Q_MULTIMEDIA_EXPORT QImage qImageFromVideoFrame(const QVideoFrame &frame, QtVideo::Rotation rotation = QtVideo::Rotation::None, bool mirrorX = false, bool mirrorY = false); +Q_MULTIMEDIA_EXPORT QImage +qImageFromVideoFrame(const QVideoFrame &frame, QtVideo::Rotation rotation = QtVideo::Rotation::None, + bool mirrorX = false, bool mirrorY = false, bool forceCpu = false); /** * @brief Maps the video frame and returns an image having a shared ownership for the video frame diff --git a/src/multimedia/video/qvideooutputorientationhandler.cpp b/src/multimedia/video/qvideooutputorientationhandler.cpp index c34e9e92a..ff91bd7fb 100644 --- a/src/multimedia/video/qvideooutputorientationhandler.cpp +++ b/src/multimedia/video/qvideooutputorientationhandler.cpp @@ -18,8 +18,8 @@ QVideoOutputOrientationHandler::QVideoOutputOrientationHandler(QObject *parent) if (!screen) return; - connect(screen, SIGNAL(orientationChanged(Qt::ScreenOrientation)), - this, SLOT(screenOrientationChanged(Qt::ScreenOrientation))); + connect(screen, &QScreen::orientationChanged, this, + &QVideoOutputOrientationHandler::screenOrientationChanged); screenOrientationChanged(screen->orientation()); } diff --git a/src/multimedia/video/qvideotexturehelper.cpp b/src/multimedia/video/qvideotexturehelper.cpp index 937ff33cb..e9c26044c 100644 --- a/src/multimedia/video/qvideotexturehelper.cpp +++ b/src/multimedia/video/qvideotexturehelper.cpp @@ -213,7 +213,7 @@ static const TextureDescription descriptions[QVideoFrameFormat::NPixelFormats] = { { 1, 1 }, { 1, 1 }, { 1, 1 } } }, // Format_YUV420P10 - { 3, 1, + { 3, 2, [](int stride, int height) { return stride * ((height * 3 / 2 + 1) & ~1); }, { QRhiTexture::R16, QRhiTexture::R16, QRhiTexture::R16 }, { { 1, 1 }, { 2, 2 }, { 2, 2 } } diff --git a/src/multimedia/windows/qwindowsaudiosource.cpp b/src/multimedia/windows/qwindowsaudiosource.cpp index 99ff86f6e..d9e26f637 100644 --- a/src/multimedia/windows/qwindowsaudiosource.cpp +++ b/src/multimedia/windows/qwindowsaudiosource.cpp @@ -128,6 +128,9 @@ void QWindowsAudioSource::deviceStateChange(QAudio::State state, QAudio::Error e QByteArray QWindowsAudioSource::readCaptureClientBuffer() { + if (m_deviceState == QAudio::StoppedState) + return {}; + UINT32 actualFrames = 0; BYTE *data = nullptr; DWORD flags = 0; @@ -200,7 +203,8 @@ void QWindowsAudioSource::pullCaptureClient() } } - schedulePull(); + if (m_deviceState != QAudio::StoppedState) + schedulePull(); } void QWindowsAudioSource::start(QIODevice* device) diff --git a/src/multimediaquick/qquickimagecapture.cpp b/src/multimediaquick/qquickimagecapture.cpp index 303f3c412..b646cc0b4 100644 --- a/src/multimediaquick/qquickimagecapture.cpp +++ b/src/multimediaquick/qquickimagecapture.cpp @@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE QQuickImageCapture::QQuickImageCapture(QObject *parent) : QImageCapture(parent) { - connect(this, SIGNAL(imageCaptured(int,QImage)), this, SLOT(_q_imageCaptured(int,QImage))); + connect(this, &QImageCapture::imageCaptured, this, &QQuickImageCapture::_q_imageCaptured); } QQuickImageCapture::~QQuickImageCapture() = default; diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp index a8ceb192f..3b005e4a5 100644 --- a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp +++ b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp @@ -133,7 +133,7 @@ void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl & return; if (!m_cameraSession && !m_audioInput) { - emit error(QMediaRecorder::ResourceError, QLatin1String("No devices are set")); + updateError(QMediaRecorder::ResourceError, QLatin1String("No devices are set")); return; } @@ -141,14 +141,14 @@ void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl & const bool validCameraSession = m_cameraSession && m_cameraSession->camera(); - if (validCameraSession && !qt_androidRequestCameraPermission()) { - emit error(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied.")); + if (validCameraSession && !qt_androidCheckCameraPermission()) { + updateError(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied.")); setKeepAlive(false); return; } - if (m_audioInput && !qt_androidRequestRecordingPermission()) { - emit error(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied.")); + if (m_audioInput && !qt_androidCheckMicrophonePermission()) { + updateError(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied.")); setKeepAlive(false); return; } @@ -221,15 +221,15 @@ void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl & } if (!m_mediaRecorder->prepare()) { - emit error(QMediaRecorder::FormatError, QLatin1String("Unable to prepare the media recorder.")); + updateError(QMediaRecorder::FormatError, + QLatin1String("Unable to prepare the media recorder.")); restartViewfinder(); return; } if (!m_mediaRecorder->start()) { - emit error(QMediaRecorder::FormatError, - QMediaRecorderPrivate::msgFailedStartRecording()); + updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording()); restartViewfinder(); return; @@ -451,7 +451,7 @@ void QAndroidCaptureSession::onError(int what, int extra) Q_UNUSED(what); Q_UNUSED(extra); stop(true); - emit error(QMediaRecorder::ResourceError, QLatin1String("Unknown error.")); + updateError(QMediaRecorder::ResourceError, QLatin1String("Unknown error.")); } void QAndroidCaptureSession::onInfo(int what, int extra) @@ -460,11 +460,11 @@ void QAndroidCaptureSession::onInfo(int what, int extra) if (what == 800) { // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED stop(); - emit error(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached.")); + updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached.")); } else if (what == 801) { // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED stop(); - emit error(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached.")); + updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached.")); } } diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h index ab91fc3ef..161d47994 100644 --- a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h +++ b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h @@ -67,10 +67,10 @@ public: if (m_mediaEncoder) m_mediaEncoder->actualLocationChanged(location); } - void error(int error, const QString &errorString) + void updateError(int error, const QString &errorString) { if (m_mediaEncoder) - m_mediaEncoder->error(QMediaRecorder::Error(error), errorString); + m_mediaEncoder->updateError(QMediaRecorder::Error(error), errorString); } private Q_SLOTS: diff --git a/src/plugins/multimedia/darwin/camera/avfcamerarenderer.mm b/src/plugins/multimedia/darwin/camera/avfcamerarenderer.mm index 1b461da7a..f29eb2c2e 100644 --- a/src/plugins/multimedia/darwin/camera/avfcamerarenderer.mm +++ b/src/plugins/multimedia/darwin/camera/avfcamerarenderer.mm @@ -240,11 +240,7 @@ void AVFCameraRenderer::handleViewfinderFrame() } if (m_sink && frame.isValid()) { - // ### pass format to surface - QVideoFrameFormat format = frame.surfaceFormat(); - if (m_needsHorizontalMirroring) - format.setMirrored(true); - + // frame.setMirroed(m_needsHorizontalMirroring) ? m_sink->setVideoFrame(frame); } } diff --git a/src/plugins/multimedia/darwin/camera/avfcamerautility.mm b/src/plugins/multimedia/darwin/camera/avfcamerautility.mm index 704fbdc11..1864eb0e8 100644 --- a/src/plugins/multimedia/darwin/camera/avfcamerautility.mm +++ b/src/plugins/multimedia/darwin/camera/avfcamerautility.mm @@ -112,25 +112,31 @@ qt_convert_to_capture_device_format(AVCaptureDevice *captureDevice, return nil; AVCaptureDeviceFormat *newFormat = nil; + Float64 newFormatMaxFrameRate = {}; NSArray<AVCaptureDeviceFormat *> *formats = captureDevice.formats; for (AVCaptureDeviceFormat *format in formats) { CMFormatDescriptionRef formatDesc = format.formatDescription; CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc); FourCharCode cvPixFormat = CMVideoFormatDescriptionGetCodecType(formatDesc); - if (requiredCvPixFormat == cvPixFormat - && cameraFormatPrivate->resolution == QSize(dim.width, dim.height) - && (!cvFormatValidator || cvFormatValidator(cvPixFormat))) { - for (AVFrameRateRange *frameRateRange in format.videoSupportedFrameRateRanges) { - if (frameRateRange.minFrameRate >= cameraFormatPrivate->minFrameRate - && frameRateRange.maxFrameRate <= cameraFormatPrivate->maxFrameRate) { - newFormat = format; - break; - } + if (cvPixFormat != requiredCvPixFormat) + continue; + + if (cameraFormatPrivate->resolution != QSize(dim.width, dim.height)) + continue; + + if (cvFormatValidator && !cvFormatValidator(cvPixFormat)) + continue; + + const float epsilon = 0.001f; + for (AVFrameRateRange *frameRateRange in format.videoSupportedFrameRateRanges) { + if (frameRateRange.minFrameRate >= cameraFormatPrivate->minFrameRate - epsilon + && frameRateRange.maxFrameRate <= cameraFormatPrivate->maxFrameRate + epsilon + && newFormatMaxFrameRate < frameRateRange.maxFrameRate) { + newFormat = format; + newFormatMaxFrameRate = frameRateRange.maxFrameRate; } } - if (newFormat) - break; } return newFormat; } diff --git a/src/plugins/multimedia/darwin/camera/avfmediaencoder.mm b/src/plugins/multimedia/darwin/camera/avfmediaencoder.mm index 4a138d4e9..bd04d6b47 100644 --- a/src/plugins/multimedia/darwin/camera/avfmediaencoder.mm +++ b/src/plugins/multimedia/darwin/camera/avfmediaencoder.mm @@ -210,7 +210,9 @@ static NSDictionary *avfAudioSettings(const QMediaEncoderSettings &encoderSettin UInt32 size = 0; if (format.isValid()) { auto layout = CoreAudioUtils::toAudioChannelLayout(format, &size); - [settings setObject:[NSData dataWithBytes:layout.get() length:sizeof(AudioChannelLayout)] forKey:AVChannelLayoutKey]; + UInt32 layoutSize = offsetof(AudioChannelLayout, mChannelDescriptions) + + layout->mNumberChannelDescriptions * sizeof(AudioChannelDescription); + [settings setObject:[NSData dataWithBytes:layout.get() length:layoutSize] forKey:AVChannelLayoutKey]; } else { // finally default to setting channel count to 1 [settings setObject:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey]; @@ -479,7 +481,7 @@ void AVFMediaEncoder::record(QMediaEncoderSettings &settings) if (!cameraControl && !audioInput) { qWarning() << Q_FUNC_INFO << "Cannot record without any inputs"; - Q_EMIT error(QMediaRecorder::ResourceError, tr("No inputs specified")); + updateError(QMediaRecorder::ResourceError, tr("No inputs specified")); return; } @@ -491,8 +493,8 @@ void AVFMediaEncoder::record(QMediaEncoderSettings &settings) if (!audioOnly) { if (!cameraControl || !cameraControl->isActive()) { qCDebug(qLcCamera) << Q_FUNC_INFO << "can not start record while camera is not active"; - Q_EMIT error(QMediaRecorder::ResourceError, - QMediaRecorderPrivate::msgFailedStartRecording()); + updateError(QMediaRecorder::ResourceError, + QMediaRecorderPrivate::msgFailedStartRecording()); return; } } @@ -506,13 +508,13 @@ void AVFMediaEncoder::record(QMediaEncoderSettings &settings) NSURL *nsFileURL = fileURL.toNSURL(); if (!nsFileURL) { qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL; - Q_EMIT error(QMediaRecorder::ResourceError, tr("Invalid output file URL")); + updateError(QMediaRecorder::ResourceError, tr("Invalid output file URL")); return; } if (!qt_is_writable_file_URL(nsFileURL)) { qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL << "(the location is not writable)"; - Q_EMIT error(QMediaRecorder::ResourceError, tr("Non-writeable file location")); + updateError(QMediaRecorder::ResourceError, tr("Non-writeable file location")); return; } if (qt_file_exists(nsFileURL)) { @@ -520,7 +522,7 @@ void AVFMediaEncoder::record(QMediaEncoderSettings &settings) // Objective-C exception, which is not good at all. qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL << "(file already exists)"; - Q_EMIT error(QMediaRecorder::ResourceError, tr("File already exists")); + updateError(QMediaRecorder::ResourceError, tr("File already exists")); return; } @@ -555,8 +557,7 @@ void AVFMediaEncoder::record(QMediaEncoderSettings &settings) [m_writer start]; } else { [session startRunning]; - Q_EMIT error(QMediaRecorder::FormatError, - QMediaRecorderPrivate::msgFailedStartRecording()); + updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording()); } } @@ -632,7 +633,7 @@ void AVFMediaEncoder::assetWriterFinished() void AVFMediaEncoder::assetWriterError(QString err) { - Q_EMIT error(QMediaRecorder::FormatError, err); + updateError(QMediaRecorder::FormatError, err); if (m_state != QMediaRecorder::StoppedState) stopWriter(); } diff --git a/src/plugins/multimedia/darwin/camera/qavfcamerabase.mm b/src/plugins/multimedia/darwin/camera/qavfcamerabase.mm index 9d99de0b9..f5ef0df87 100644 --- a/src/plugins/multimedia/darwin/camera/qavfcamerabase.mm +++ b/src/plugins/multimedia/darwin/camera/qavfcamerabase.mm @@ -93,9 +93,25 @@ bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCamera::ExposureM #endif // defined(Q_OS_IOS) +// Helper function to translate AVCaptureDevicePosition enum to QCameraDevice::Position enum. +QCameraDevice::Position qt_AVCaptureDevicePosition_to_QCameraDevicePosition(AVCaptureDevicePosition input) +{ + switch (input) { + case AVCaptureDevicePositionFront: + return QCameraDevice::Position::FrontFace; + case AVCaptureDevicePositionBack: + return QCameraDevice::Position::BackFace; + case AVCaptureDevicePositionUnspecified: + return QCameraDevice::Position::UnspecifiedPosition; + default: + return QCameraDevice::Position::UnspecifiedPosition; + } +} + } // Unnamed namespace. + QAVFVideoDevices::QAVFVideoDevices(QPlatformMediaIntegration *integration) : QPlatformVideoDevices(integration) { @@ -192,6 +208,7 @@ void QAVFVideoDevices::updateCameraDevices() info->isDefault = true; info->id = QByteArray([[device uniqueID] UTF8String]); info->description = QString::fromNSString([device localizedName]); + info->position = qt_AVCaptureDevicePosition_to_QCameraDevicePosition([device position]); qCDebug(qLcCamera) << "Handling camera info" << info->description << (info->isDefault ? "(default)" : ""); @@ -453,7 +470,7 @@ void QAVFCameraBase::setFocusDistance(float d) if (!captureDevice) return; - if (captureDevice.lockingFocusWithCustomLensPositionSupported) { + if (!captureDevice.lockingFocusWithCustomLensPositionSupported) { qCDebug(qLcCamera) << Q_FUNC_INFO << "Setting custom focus distance not supported\n"; return; } diff --git a/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer.mm b/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer.mm index 4e30b1a83..ac034822b 100644 --- a/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer.mm +++ b/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer.mm @@ -12,6 +12,7 @@ #include <qpointer.h> #include <QFileInfo> #include <QtCore/qmath.h> +#include <QtCore/qmutex.h> #import <AVFoundation/AVFoundation.h> @@ -59,6 +60,12 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM - (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest; @end +#ifdef Q_OS_IOS +// Alas, no such thing as 'class variable', hence globals: +static unsigned sessionActivationCount; +static QMutex sessionMutex; +#endif // Q_OS_IOS + @implementation AVFMediaPlayerObserver { @private @@ -70,10 +77,39 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM BOOL m_bufferIsLikelyToKeepUp; NSData *m_data; NSString *m_mimeType; +#ifdef Q_OS_IOS + BOOL m_activated; +#endif } @synthesize m_player, m_playerItem, m_playerLayer, m_session; +#ifdef Q_OS_IOS +- (void)setSessionActive:(BOOL)active +{ + const QMutexLocker lock(&sessionMutex); + if (active) { + // Don't count the same player twice if already activated, + // unless it tried to deactivate first: + if (m_activated) + return; + if (!sessionActivationCount) + [AVAudioSession.sharedInstance setActive:YES error:nil]; + ++sessionActivationCount; + m_activated = YES; + } else { + if (!sessionActivationCount || !m_activated) { + qWarning("Unbalanced audio session deactivation, ignoring."); + return; + } + --sessionActivationCount; + m_activated = NO; + if (!sessionActivationCount) + [AVAudioSession.sharedInstance setActive:NO error:nil]; + } +} +#endif // Q_OS_IOS + - (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session { if (!(self = [super init])) @@ -152,7 +188,7 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM if (m_playerLayer) m_playerLayer.player = nil; #if defined(Q_OS_IOS) - [[AVAudioSession sharedInstance] setActive:NO error:nil]; + [self setSessionActive:NO]; #endif } @@ -241,11 +277,12 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM m_player = [AVPlayer playerWithPlayerItem:m_playerItem]; [m_player retain]; - //Set the initial volume on new player object + //Set the initial audio ouptut settings on new player object if (self.session) { auto *audioOutput = m_session->m_audioOutput; m_player.volume = (audioOutput ? audioOutput->volume : 1.); m_player.muted = (audioOutput ? audioOutput->muted : true); + m_session->updateAudioOutputDevice(); } //Assign the output layer to the new player @@ -272,7 +309,7 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM context:AVFMediaPlayerObserverCurrentItemDurationObservationContext]; #if defined(Q_OS_IOS) [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; - [[AVAudioSession sharedInstance] setActive:YES error:nil]; + [self setSessionActive:YES]; #endif } @@ -707,12 +744,12 @@ void AVFMediaPlayer::setAudioOutput(QPlatformAudioOutput *output) m_audioOutput->q->disconnect(this); m_audioOutput = output; if (m_audioOutput) { - connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &AVFMediaPlayer::audioOutputChanged); + connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &AVFMediaPlayer::updateAudioOutputDevice); connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &AVFMediaPlayer::setVolume); connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &AVFMediaPlayer::setMuted); //connect(m_audioOutput->q, &QAudioOutput::audioRoleChanged, this, &AVFMediaPlayer::setAudioRole); } - audioOutputChanged(); + updateAudioOutputDevice(); setMuted(m_audioOutput ? m_audioOutput->muted : true); setVolume(m_audioOutput ? m_audioOutput->volume : 1.); } @@ -893,7 +930,7 @@ void AVFMediaPlayer::setMuted(bool muted) player.muted = muted; } -void AVFMediaPlayer::audioOutputChanged() +void AVFMediaPlayer::updateAudioOutputDevice() { #ifdef Q_OS_MACOS AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player]; diff --git a/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer_p.h b/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer_p.h index d04ab0818..6ac3aef46 100644 --- a/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer_p.h +++ b/src/plugins/multimedia/darwin/mediaplayer/avfmediaplayer_p.h @@ -89,7 +89,7 @@ public Q_SLOTS: void setVolume(float volume); void setMuted(bool muted); - void audioOutputChanged(); + void updateAudioOutputDevice(); void processEOS(); void processLoadStateChange(QMediaPlayer::PlaybackState newState); diff --git a/src/plugins/multimedia/ffmpeg/CMakeLists.txt b/src/plugins/multimedia/ffmpeg/CMakeLists.txt index 8274b1fd4..2ffac7426 100644 --- a/src/plugins/multimedia/ffmpeg/CMakeLists.txt +++ b/src/plugins/multimedia/ffmpeg/CMakeLists.txt @@ -21,6 +21,7 @@ qt_internal_add_plugin(QFFmpegMediaPlugin qffmpegavaudioformat.cpp qffmpegavaudioformat_p.h qffmpegaudiodecoder.cpp qffmpegaudiodecoder_p.h qffmpegaudioinput.cpp qffmpegaudioinput_p.h + qffmpegconverter.cpp qffmpegconverter_p.h qffmpeghwaccel.cpp qffmpeghwaccel_p.h qffmpegmediametadata.cpp qffmpegmediametadata_p.h qffmpegmediaplayer.cpp qffmpegmediaplayer_p.h @@ -66,6 +67,8 @@ qt_internal_add_plugin(QFFmpegMediaPlugin recordingengine/qffmpegmuxer.cpp recordingengine/qffmpegrecordingengine_p.h recordingengine/qffmpegrecordingengine.cpp + recordingengine/qffmpegencodinginitializer_p.h + recordingengine/qffmpegencodinginitializer.cpp recordingengine/qffmpegvideoencoder_p.h recordingengine/qffmpegvideoencoder.cpp recordingengine/qffmpegvideoencoderutils_p.h diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp index 457b3603d..fb4f041b9 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp @@ -23,17 +23,46 @@ QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext) if (!stream) return { "Invalid stream" }; + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + auto hwCodec = create(stream, formatContext, Hw); + if (hwCodec) + return hwCodec; + + qCInfo(qLcPlaybackEngineCodec) << hwCodec.error(); + } + + auto codec = create(stream, formatContext, Sw); + if (!codec) + qCWarning(qLcPlaybackEngineCodec) << codec.error(); + + return codec; +} + +AVRational Codec::pixelAspectRatio(AVFrame *frame) const +{ + // does the same as av_guess_sample_aspect_ratio, but more efficient + return d->pixelAspectRatio.num && d->pixelAspectRatio.den ? d->pixelAspectRatio + : frame->sample_aspect_ratio; +} + +QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext, + VideoCodecCreationPolicy videoCodecPolicy) +{ + Q_ASSERT(stream); + + if (videoCodecPolicy == Hw && stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + Q_ASSERT(!"Codec::create has been called with Hw policy on a non-video stream"); + const AVCodec *decoder = nullptr; std::unique_ptr<QFFmpeg::HWAccel> hwAccel; - if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + if (videoCodecPolicy == Hw) std::tie(decoder, hwAccel) = HWAccel::findDecoderWithHwAccel(stream->codecpar->codec_id); - - if (!decoder) + else decoder = QFFmpeg::findAVDecoder(stream->codecpar->codec_id); if (!decoder) - return { "Failed to find a valid FFmpeg decoder" }; + return { QString("No %1 decoder found").arg(videoCodecPolicy == Hw ? "HW" : "SW") }; qCDebug(qLcPlaybackEngineCodec) << "found decoder" << decoder->name << "for id" << decoder->id; @@ -41,6 +70,18 @@ QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext) if (!context) return { "Failed to allocate a FFmpeg codec context" }; + // Use HW decoding even if the codec level doesn't match the reported capabilities + // of the hardware. FFmpeg documentation recommendeds setting this flag by default. + context->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL; + + static const bool allowProfileMismatch = static_cast<bool>( + qEnvironmentVariableIntValue("QT_FFMPEG_HW_ALLOW_PROFILE_MISMATCH")); + if (allowProfileMismatch) { + // Use HW decoding even if the codec profile doesn't match the reported capabilities + // of the hardware. + context->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH; + } + if (hwAccel) context->hw_device_ctx = av_buffer_ref(hwAccel->hwDeviceContextAsBuffer()); @@ -51,7 +92,7 @@ QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext) int ret = avcodec_parameters_to_context(context.get(), stream->codecpar); if (ret < 0) - return { "Failed to set FFmpeg codec parameters" }; + return QStringLiteral("Failed to set FFmpeg codec parameters: %1").arg(err2str(ret)); // ### This still gives errors about wrong HW formats (as we accept all of them) // But it would be good to get so we can filter out pixel format we don't support natively @@ -64,19 +105,13 @@ QMaybe<Codec> Codec::create(AVStream *stream, AVFormatContext *formatContext) applyExperimentalCodecOptions(decoder, opts); ret = avcodec_open2(context.get(), decoder, opts); + if (ret < 0) - return QString("Failed to open FFmpeg codec context " + err2str(ret)); + return QStringLiteral("Failed to open FFmpeg codec context: %1").arg(err2str(ret)); return Codec(new Data(std::move(context), stream, formatContext, std::move(hwAccel))); } -AVRational Codec::pixelAspectRatio(AVFrame *frame) const -{ - // does the same as av_guess_sample_aspect_ratio, but more efficient - return d->pixelAspectRatio.num && d->pixelAspectRatio.den ? d->pixelAspectRatio - : frame->sample_aspect_ratio; -} - QT_END_NAMESPACE } // namespace QFFmpeg diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h index 449fb1f65..b6866ed6b 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h @@ -51,6 +51,13 @@ public: qint64 toUs(qint64 ts) const { return timeStampUs(ts, d->stream->time_base).value_or(0); } private: + enum VideoCodecCreationPolicy { + Hw, + Sw, + }; + + static QMaybe<Codec> create(AVStream *stream, AVFormatContext *formatContext, + VideoCodecCreationPolicy videoCodecPolicy); Codec(Data *data) : d(data) { } QExplicitlySharedDataPointer<Data> d; }; diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h index 84fe2fead..6a123b18e 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h @@ -41,6 +41,9 @@ struct Frame else pts = codec.toUs(frame->best_effort_timestamp); + if (frame->sample_rate && codec.context()->codec_type == AVMEDIA_TYPE_AUDIO) + duration = qint64(1000000) * frame->nb_samples / frame->sample_rate; + if (auto frameDuration = getAVFrameDuration(*frame)) { duration = codec.toUs(frameDuration); } else { diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp index fbb75dd44..011a99391 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp @@ -151,6 +151,10 @@ loadMedia(const QUrl &mediaUrl, QIODevice *stream, const std::shared_ptr<ICancel constexpr auto NetworkTimeoutUs = "5000000"; av_dict_set(dict, "timeout", NetworkTimeoutUs, 0); + const QByteArray protocolWhitelist = qgetenv("QT_FFMPEG_PROTOCOL_WHITELIST"); + if (!protocolWhitelist.isNull()) + av_dict_set(dict, "protocol_whitelist", protocolWhitelist.data(), 0); + context->interrupt_callback.opaque = cancelToken.get(); context->interrupt_callback.callback = [](void *opaque) { const auto *cancelToken = static_cast<const ICancelToken *>(opaque); @@ -170,7 +174,7 @@ loadMedia(const QUrl &mediaUrl, QIODevice *stream, const std::shared_ptr<ICancel auto code = QMediaPlayer::ResourceError; if (ret == AVERROR(EACCES)) code = QMediaPlayer::AccessDeniedError; - else if (ret == AVERROR(EINVAL)) + else if (ret == AVERROR(EINVAL) || ret == AVERROR_INVALIDDATA) code = QMediaPlayer::FormatError; return MediaDataHolder::ContextError{ code, QMediaPlayer::tr("Could not open file") }; diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp index 2f40c53aa..58f763f3c 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp @@ -140,7 +140,7 @@ void StreamDecoder::decodeMedia(Packet packet) } if (sendPacketResult == 0) - receiveAVFrames(); + receiveAVFrames(!packet.isValid()); } int StreamDecoder::sendAVPacket(Packet packet) @@ -148,15 +148,29 @@ int StreamDecoder::sendAVPacket(Packet packet) return avcodec_send_packet(m_codec.context(), packet.isValid() ? packet.avPacket() : nullptr); } -void StreamDecoder::receiveAVFrames() +void StreamDecoder::receiveAVFrames(bool flushPacket) { while (true) { auto avFrame = makeAVFrame(); const auto receiveFrameResult = avcodec_receive_frame(m_codec.context(), avFrame.get()); - if (receiveFrameResult == AVERROR_EOF || receiveFrameResult == AVERROR(EAGAIN)) + if (receiveFrameResult == AVERROR_EOF || receiveFrameResult == AVERROR(EAGAIN)) { + if (flushPacket && receiveFrameResult == AVERROR(EAGAIN)) { + // The documentation says that in the EAGAIN state output is not available. The new + // input must be sent. It does not say that this state can also be returned for + // Android MediaCodec when the ff_AMediaCodec_dequeueOutputBuffer call times out. + // The flush packet means it is the end of the stream. No more packets are available, + // so getting EAGAIN is unexpected here. At this point, the EAGAIN status was probably + // caused by a timeout in the ffmpeg implementation, not by too few packets. That is + // why there will be another try of calling avcodec_receive_frame + qWarning() << "Unexpected FFmpeg behavior: EAGAIN state for avcodec_receive_frame " + << "at end of the stream"; + flushPacket = false; + continue; + } break; + } if (receiveFrameResult < 0) { emit error(QMediaPlayer::FormatError, err2str(receiveFrameResult)); diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h index 1acc07983..d3a7d98f3 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h @@ -66,7 +66,7 @@ private: int sendAVPacket(Packet); - void receiveAVFrames(); + void receiveAVFrames(bool flushPacket = false); private: Codec m_codec; diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp index 789c9b53b..d44ad9578 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp @@ -17,8 +17,14 @@ SubtitleRenderer::SubtitleRenderer(const TimeController &tc, QVideoSink *sink) void SubtitleRenderer::setOutput(QVideoSink *sink, bool cleanPrevSink) { - setOutputInternal(m_sink, sink, [cleanPrevSink](QVideoSink *prev) { - if (prev && cleanPrevSink) + setOutputInternal(m_sink, sink, [=](QVideoSink *prev) { + if (!prev) + return; + + if (sink) + sink->setSubtitleText(prev->subtitleText()); + + if (cleanPrevSink) prev->setSubtitleText({}); }); } diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp index 0052e6ee6..bd37147af 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp @@ -16,8 +16,14 @@ VideoRenderer::VideoRenderer(const TimeController &tc, QVideoSink *sink, QtVideo void VideoRenderer::setOutput(QVideoSink *sink, bool cleanPrevSink) { - setOutputInternal(m_sink, sink, [cleanPrevSink](QVideoSink *prev) { - if (prev && cleanPrevSink) + setOutputInternal(m_sink, sink, [=](QVideoSink *prev) { + if (!prev) + return; + + if (sink) + sink->setVideoFrame(prev->videoFrame()); + + if (cleanPrevSink) prev->setVideoFrame({}); }); } diff --git a/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp b/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp index 176b55d8a..402d088c3 100644 --- a/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp +++ b/src/plugins/multimedia/ffmpeg/qandroidcamera.cpp @@ -25,7 +25,6 @@ extern "C" { #include "libavutil/hwcontext.h" -#include "libavutil/pixfmt.h" } Q_DECLARE_JNI_CLASS(QtCamera2, "org/qtproject/qt/android/multimedia/QtCamera2"); @@ -48,16 +47,21 @@ Q_GLOBAL_STATIC(QReadWriteLock, rwLock) namespace { -QCameraFormat getDefaultCameraFormat() +QCameraFormat getDefaultCameraFormat(const QCameraDevice & cameraDevice) { // default settings QCameraFormatPrivate *defaultFormat = new QCameraFormatPrivate{ .pixelFormat = QVideoFrameFormat::Format_YUV420P, .resolution = { 1920, 1080 }, - .minFrameRate = 30, - .maxFrameRate = 60, + .minFrameRate = 12, + .maxFrameRate = 30, }; - return defaultFormat->create(); + QCameraFormat format = defaultFormat->create(); + + if (!cameraDevice.videoFormats().empty() && !cameraDevice.videoFormats().contains(format)) + return cameraDevice.videoFormats().first(); + + return format; } bool checkAndRequestCameraPermission() @@ -101,7 +105,7 @@ QAndroidCamera::QAndroidCamera(QCamera *camera) : QPlatformCamera(camera) if (camera) { m_cameraDevice = camera->cameraDevice(); m_cameraFormat = !camera->cameraFormat().isNull() ? camera->cameraFormat() - : getDefaultCameraFormat(); + : getDefaultCameraFormat(m_cameraDevice); updateCameraCharacteristics(); } @@ -113,23 +117,29 @@ QAndroidCamera::QAndroidCamera(QCamera *camera) : QPlatformCamera(camera) QAndroidCamera::~QAndroidCamera() { - QWriteLocker locker(rwLock); - g_qcameras->remove(m_cameraDevice.id()); + { + QWriteLocker locker(rwLock); + g_qcameras->remove(m_cameraDevice.id()); + + m_jniCamera.callMethod<void>("stopAndClose"); + setState(State::Closed); + } - m_jniCamera.callMethod<void>("stopAndClose"); m_jniCamera.callMethod<void>("stopBackgroundThread"); - setState(State::Closed); } void QAndroidCamera::setCamera(const QCameraDevice &camera) { - setActive(false); + const bool active = isActive(); + if (active) + setActive(false); m_cameraDevice = camera; updateCameraCharacteristics(); - m_cameraFormat = getDefaultCameraFormat(); + m_cameraFormat = getDefaultCameraFormat(camera); - setActive(true); + if (active) + setActive(true); } std::optional<int> QAndroidCamera::ffmpegHWPixelFormat() const @@ -249,7 +259,7 @@ void QAndroidCamera::setActive(bool active) return; if (!m_jniCamera.isValid()) { - emit error(QCamera::CameraError, "No connection to Android Camera2 API"); + updateError(QCamera::CameraError, QStringLiteral("No connection to Android Camera2 API")); return; } @@ -259,7 +269,7 @@ void QAndroidCamera::setActive(bool active) int height = m_cameraFormat.resolution().height(); if (width < 0 || height < 0) { - m_cameraFormat = getDefaultCameraFormat(); + m_cameraFormat = getDefaultCameraFormat(m_cameraDevice); width = m_cameraFormat.resolution().width(); height = m_cameraFormat.resolution().height(); } @@ -270,24 +280,24 @@ 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>()); if (!canOpen) { g_qcameras->remove(m_cameraDevice.id()); setState(State::Closed); - emit error(QCamera::CameraError, - QString("Failed to start camera: ").append(m_cameraDevice.description())); + updateError(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"); @@ -313,8 +323,8 @@ void QAndroidCamera::setState(QAndroidCamera::State newState) m_state = State::Closed; - emit error(QCamera::CameraError, - QString("Failed to start Camera %1").arg(m_cameraDevice.description())); + updateError(QCamera::CameraError, + QString("Failed to start Camera %1").arg(m_cameraDevice.description())); } if (m_state == State::Closed && newState == State::WaitingOpen) @@ -329,9 +339,11 @@ void QAndroidCamera::setState(QAndroidCamera::State newState) bool QAndroidCamera::setCameraFormat(const QCameraFormat &format) { - const auto chosenFormat = format.isNull() ? getDefaultCameraFormat() : format; + const auto chosenFormat = format.isNull() ? getDefaultCameraFormat(m_cameraDevice) : format; - if (chosenFormat == m_cameraFormat || !m_cameraDevice.videoFormats().contains(chosenFormat)) + if (chosenFormat == m_cameraFormat) + return true; + if (!m_cameraDevice.videoFormats().contains(chosenFormat)) return false; m_cameraFormat = chosenFormat; @@ -361,9 +373,22 @@ void QAndroidCamera::updateCameraCharacteristics() return; } - const float maxZoom = deviceManager.callMethod<jfloat>( - "getMaxZoom", QJniObject::fromString(m_cameraDevice.id()).object<jstring>()); + QJniEnvironment jniEnv; + float maxZoom = 1.0; + float minZoom = 1.0; + QJniObject zoomRangeObj = deviceManager.callMethod<jfloatArray>( + "getZoomRange", QJniObject::fromString(m_cameraDevice.id()).object<jstring>()); + jfloatArray zoomRange = zoomRangeObj.object<jfloatArray>(); + + if (jniEnv->GetArrayLength(zoomRange) == 2) { + jfloat jfloatZoomRange[2]; + jniEnv->GetFloatArrayRegion(zoomRange, 0, 2, jfloatZoomRange); + minZoom = jfloatZoomRange[0]; + maxZoom = jfloatZoomRange[1]; + } + maximumZoomFactorChanged(maxZoom); + minimumZoomFactorChanged(minZoom); if (maxZoom < zoomFactor()) { zoomTo(1.0, -1.0); } @@ -376,7 +401,6 @@ void QAndroidCamera::updateCameraCharacteristics() QJniObject flashModesObj = deviceManager.callMethod<QtJniTypes::StringArray>( "getSupportedFlashModes", QJniObject::fromString(m_cameraDevice.id()).object<jstring>()); - QJniEnvironment jniEnv; jobjectArray flashModes = flashModesObj.object<jobjectArray>(); int size = jniEnv->GetArrayLength(flashModes); for (int i = 0; i < size; ++i) { @@ -518,10 +542,10 @@ void QAndroidCamera::onCameraDisconnect() void QAndroidCamera::onCameraError(int reason) { - emit error(QCamera::CameraError, - QString("Capture error with Camera %1. Camera2 Api error code: %2") - .arg(m_cameraDevice.description()) - .arg(reason)); + updateError(QCamera::CameraError, + QString("Capture error with Camera %1. Camera2 Api error code: %2") + .arg(m_cameraDevice.description()) + .arg(reason)); } void QAndroidCamera::onSessionActive() @@ -549,10 +573,10 @@ void QAndroidCamera::onCaptureSessionFailed(int reason, long frameNumber) { Q_UNUSED(frameNumber); - emit error(QCamera::CameraError, - QString("Capture session failure with Camera %1. Camera2 Api error code: %2") - .arg(m_cameraDevice.description()) - .arg(reason)); + updateError(QCamera::CameraError, + QStringLiteral("Capture session failure with Camera %1. Camera2 Api error code: %2") + .arg(m_cameraDevice.description()) + .arg(reason)); } // JNI logic diff --git a/src/plugins/multimedia/ffmpeg/qandroidcameraframe.cpp b/src/plugins/multimedia/ffmpeg/qandroidcameraframe.cpp index 82621a2a3..ce527d6a8 100644 --- a/src/plugins/multimedia/ffmpeg/qandroidcameraframe.cpp +++ b/src/plugins/multimedia/ffmpeg/qandroidcameraframe.cpp @@ -11,10 +11,21 @@ Q_DECLARE_JNI_CLASS(AndroidImageFormat, "android/graphics/ImageFormat"); Q_DECLARE_JNI_TYPE(AndroidImage, "Landroid/media/Image;") Q_DECLARE_JNI_TYPE(AndroidImagePlaneArray, "[Landroid/media/Image$Plane;") Q_DECLARE_JNI_TYPE(JavaByteBuffer, "Ljava/nio/ByteBuffer;") +Q_DECLARE_JNI_CLASS(QtVideoDeviceManager, + "org/qtproject/qt/android/multimedia/QtVideoDeviceManager"); QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(qLCAndroidCameraFrame, "qt.multimedia.ffmpeg.android.camera.frame"); +namespace { +bool isWorkaroundForEmulatorNeeded() { + const static bool workaroundForEmulator + = QJniObject::callStaticMethod<jboolean>( + QtJniTypes::className<QtJniTypes::QtVideoDeviceManager>(), "isEmulator"); + return workaroundForEmulator; +} +} + bool QAndroidCameraFrame::parse(const QJniObject &frame) { QJniEnvironment jniEnv; @@ -85,9 +96,12 @@ bool QAndroidCameraFrame::parse(const QJniObject &frame) } if (pixelStrides[1] == 1) calculedPixelFormat = QVideoFrameFormat::Format_YUV420P; - else if (pixelStrides[1] == 2 && abs(buffer[1] - buffer[2]) == 1) - // this can be NV21, but it will converted below - calculedPixelFormat = QVideoFrameFormat::Format_NV12; + else if (pixelStrides[1] == 2) { + if (buffer[1] - buffer[2] == -1) // Interleaved UVUV -> NV12 + calculedPixelFormat = QVideoFrameFormat::Format_NV12; + else if (buffer[1] - buffer[2] == 1) // Interleaved VUVU -> NV21 + calculedPixelFormat = QVideoFrameFormat::Format_NV21; + } break; case AndroidImageFormat::HEIC: // QImage cannot parse HEIC @@ -129,19 +143,44 @@ bool QAndroidCameraFrame::parse(const QJniObject &frame) m_planes[mapIndex].data = buffer[arrayIndex]; }; + int width = frame.callMethod<jint>("getWidth"); + int height = frame.callMethod<jint>("getHeight"); + m_size = QSize(width, height); + switch (calculedPixelFormat) { case QVideoFrameFormat::Format_YUV420P: m_numberPlanes = 3; copyPlane(0, 0); copyPlane(1, 1); copyPlane(2, 2); + + if (isWorkaroundForEmulatorNeeded()) { + for (int i = 0; i < 3; ++i) { + const int dataSize = (i == 0) ? width * height : width * height / 4; + m_planes[i].data = new uint8_t[dataSize]; + memcpy(m_planes[i].data, buffer[i], dataSize); + } + } + m_pixelFormat = QVideoFrameFormat::Format_YUV420P; break; case QVideoFrameFormat::Format_NV12: + case QVideoFrameFormat::Format_NV21: + // Y-plane and combined interleaved UV-plane m_numberPlanes = 2; copyPlane(0, 0); - copyPlane(1, 1); - m_pixelFormat = QVideoFrameFormat::Format_NV12; + + // Android reports U and V planes as planes[1] and planes[2] respectively, regardless of the + // order of interleaved samples. We point to whichever is first in memory. + copyPlane(1, calculedPixelFormat == QVideoFrameFormat::Format_NV21 ? 2 : 1); + + // With interleaved UV plane, Android reports the size of each plane as the smallest size + // that includes all samples of that plane. For example, if the UV plane is [u, v, u, v], + // the size of the U-plane is 3, not 4. With FFmpeg we need to count the total number of + // bytes in the UV-plane, which is 1 more than what Android reports. + m_planes[1].size++; + + m_pixelFormat = calculedPixelFormat; break; case QVideoFrameFormat::Format_Jpeg: qCWarning(qLCAndroidCameraFrame) @@ -160,10 +199,6 @@ bool QAndroidCameraFrame::parse(const QJniObject &frame) long timestamp = frame.callMethod<jlong>("getTimestamp"); m_timestamp = timestamp / 1000; - int width = frame.callMethod<jint>("getWidth"); - int height = frame.callMethod<jint>("getHeight"); - m_size = QSize(width, height); - return true; } @@ -192,6 +227,13 @@ QAndroidCameraFrame::~QAndroidCameraFrame() QJniEnvironment jniEnv; if (m_frame) jniEnv->DeleteGlobalRef(m_frame); + + if (isWorkaroundForEmulatorNeeded()) { + if (m_pixelFormat == QVideoFrameFormat::Format_YUV420P) { + for (int i = 0; i < 3; ++i) + delete[] m_planes[i].data; + } + } } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/qandroidcameraframe_p.h b/src/plugins/multimedia/ffmpeg/qandroidcameraframe_p.h index 23a737f7d..fdbefbca8 100644 --- a/src/plugins/multimedia/ffmpeg/qandroidcameraframe_p.h +++ b/src/plugins/multimedia/ffmpeg/qandroidcameraframe_p.h @@ -36,7 +36,7 @@ public: int numberPlanes() const { return m_numberPlanes; } Plane plane(int index) const { - if (index < 0 || index > numberPlanes()) + if (index < 0 || index >= numberPlanes()) return {}; return m_planes[index]; diff --git a/src/plugins/multimedia/ffmpeg/qavfcamera.mm b/src/plugins/multimedia/ffmpeg/qavfcamera.mm index b441af024..652971a1a 100644 --- a/src/plugins/multimedia/ffmpeg/qavfcamera.mm +++ b/src/plugins/multimedia/ffmpeg/qavfcamera.mm @@ -342,6 +342,9 @@ uint32_t QAVFCamera::setPixelFormat(QVideoFrameFormat::PixelFormat cameraPixelFo QSize QAVFCamera::adjustedResolution() const { +#ifdef Q_OS_MACOS + return m_cameraFormat.resolution(); +#else // Check, that we have matching dimesnions. QSize resolution = m_cameraFormat.resolution(); AVCaptureConnection *connection = [m_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; @@ -357,6 +360,7 @@ QSize QAVFCamera::adjustedResolution() const resolution.transpose(); return resolution; +#endif // Q_OS_MACOS } void QAVFCamera::syncHandleFrame(const QVideoFrame &frame) diff --git a/src/plugins/multimedia/ffmpeg/qeglfsscreencapture.cpp b/src/plugins/multimedia/ffmpeg/qeglfsscreencapture.cpp index 8c60be60e..4fb32d233 100644 --- a/src/plugins/multimedia/ffmpeg/qeglfsscreencapture.cpp +++ b/src/plugins/multimedia/ffmpeg/qeglfsscreencapture.cpp @@ -26,7 +26,7 @@ public: connect(this, &Grabber::errorUpdated, &screenCapture, &QEglfsScreenCapture::updateError); // Limit frame rate to 30 fps for performance reasons, // to be reviewed at the next optimization round - setFrameRate(std::min(screen->refreshRate(), 30.0)); + setFrameRate(std::min(screen->refreshRate(), qreal(30.0))); } ~Grabber() override { stop(); } diff --git a/src/plugins/multimedia/ffmpeg/qffmpeg.cpp b/src/plugins/multimedia/ffmpeg/qffmpeg.cpp index 0a15e44f6..6b8543f00 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeg.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpeg.cpp @@ -188,9 +188,7 @@ bool isCodecValid(const AVCodec *codec, const std::vector<AVHWDeviceType> &avail if (codec->type != AVMEDIA_TYPE_VIDEO) return true; - const auto pixFmts = codec->pix_fmts; - - if (!pixFmts) { + if (!codec->pix_fmts) { #if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) // Disable V4L2 M2M codecs for encoding for now, // TODO: Investigate on how to get them working @@ -209,14 +207,14 @@ bool isCodecValid(const AVCodec *codec, const std::vector<AVHWDeviceType> &avail // and with v4l2m2m codecs, that is suspicious. } - if (findAVFormat(pixFmts, &isHwPixelFormat) == AV_PIX_FMT_NONE) + if (findAVPixelFormat(codec, &isHwPixelFormat) == AV_PIX_FMT_NONE) return true; if ((codec->capabilities & AV_CODEC_CAP_HARDWARE) == 0) return true; - auto checkDeviceType = [pixFmts](AVHWDeviceType type) { - return hasAVFormat(pixFmts, pixelFormatForHwDevice(type)); + auto checkDeviceType = [codec](AVHWDeviceType type) { + return isAVFormatSupported(codec, pixelFormatForHwDevice(type)); }; if (codecAvailableOnDevice && codecAvailableOnDevice->count(codec->id) == 0) @@ -338,6 +336,9 @@ const char *preferredHwCodecNameSuffix(bool isEncoder, AVHWDeviceType deviceType return "_videotoolbox"; case AV_HWDEVICE_TYPE_D3D11VA: case AV_HWDEVICE_TYPE_DXVA2: +#if QT_FFMPEG_HAS_D3D12VA + case AV_HWDEVICE_TYPE_D3D12VA: +#endif return "_mf"; case AV_HWDEVICE_TYPE_CUDA: case AV_HWDEVICE_TYPE_VDPAU: @@ -386,6 +387,8 @@ const AVCodec *findAVCodec(CodecStorageType codecsType, AVCodecID codecId, const std::optional<AVHWDeviceType> &deviceType, const std::optional<PixelOrSampleFormat> &format) { + // TODO: remove deviceType and use only isAVFormatSupported to check the format + return findAVCodec(codecsType, codecId, [&](const AVCodec *codec) { if (format && !isAVFormatSupported(codec, *format)) return NotSuitableAVScore; @@ -411,6 +414,7 @@ const AVCodec *findAVCodec(CodecStorageType codecsType, AVCodecID codecId, // The situation happens mostly with encoders // Probably, it's ffmpeg bug: avcodec_get_hw_config returns null even though // hw acceleration is supported + // To be removed: only isAVFormatSupported should be used. if (hasAVFormat(codec->pix_fmts, pixelFormatForHwDevice(*deviceType))) return hwCodecNameScores(codec, *deviceType); } @@ -441,8 +445,10 @@ const AVCodec *findAVEncoder(AVCodecID codecId, bool isAVFormatSupported(const AVCodec *codec, PixelOrSampleFormat format) { - if (codec->type == AVMEDIA_TYPE_VIDEO) - return hasAVFormat(codec->pix_fmts, AVPixelFormat(format)); + if (codec->type == AVMEDIA_TYPE_VIDEO) { + auto checkFormat = [format](AVPixelFormat f) { return f == format; }; + return findAVPixelFormat(codec, checkFormat) != AV_PIX_FMT_NONE; + } if (codec->type == AVMEDIA_TYPE_AUDIO) return hasAVFormat(codec->sample_fmts, AVSampleFormat(format)); @@ -489,6 +495,10 @@ AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType) return AV_PIX_FMT_QSV; case AV_HWDEVICE_TYPE_D3D11VA: return AV_PIX_FMT_D3D11; +#if QT_FFMPEG_HAS_D3D12VA + case AV_HWDEVICE_TYPE_D3D12VA: + return AV_PIX_FMT_D3D12; +#endif case AV_HWDEVICE_TYPE_DXVA2: return AV_PIX_FMT_DXVA2_VLD; case AV_HWDEVICE_TYPE_DRM: diff --git a/src/plugins/multimedia/ffmpeg/qffmpeg_p.h b/src/plugins/multimedia/ffmpeg/qffmpeg_p.h index 601b44ccb..52f0fcd1f 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeg_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpeg_p.h @@ -188,6 +188,34 @@ Format findAVFormat(const Format *fmts, const Predicate &predicate) return findBestAVFormat(fmts, scoresGetter).first; } +template <typename Predicate> +const AVCodecHWConfig *findHwConfig(const AVCodec *codec, const Predicate &predicate) +{ + for (int i = 0; const auto hwConfig = avcodec_get_hw_config(codec, i); ++i) { + if (predicate(hwConfig)) + return hwConfig; + } + + return nullptr; +} + +template <typename Predicate> +AVPixelFormat findAVPixelFormat(const AVCodec *codec, const Predicate &predicate) +{ + const AVPixelFormat format = findAVFormat(codec->pix_fmts, predicate); + if (format != AV_PIX_FMT_NONE) + return format; + + auto checkHwConfig = [&predicate](const AVCodecHWConfig *config) { + return config->pix_fmt != AV_PIX_FMT_NONE && predicate(config->pix_fmt); + }; + + if (auto hwConfig = findHwConfig(codec, checkHwConfig)) + return hwConfig->pix_fmt; + + return AV_PIX_FMT_NONE; +} + template <typename Value, typename CalculateScore> auto findBestAVValue(const Value *values, const CalculateScore &calculateScore, Value invalidValue = {}) diff --git a/src/plugins/multimedia/ffmpeg/qffmpegconverter.cpp b/src/plugins/multimedia/ffmpeg/qffmpegconverter.cpp new file mode 100644 index 000000000..c3d30c1df --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/qffmpegconverter.cpp @@ -0,0 +1,272 @@ +// 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 + +#include "qffmpegconverter_p.h" +#include <QtMultimedia/qvideoframeformat.h> +#include <QtMultimedia/qvideoframe.h> +#include <QtCore/qloggingcategory.h> +#include <private/qvideotexturehelper_p.h> + +extern "C" { +#include <libswscale/swscale.h> +} + +QT_BEGIN_NAMESPACE + +namespace { + +Q_LOGGING_CATEGORY(lc, "qt.multimedia.ffmpeg.converter"); + + +// Converts to FFmpeg pixel format. This function differs from +// QFFmpegVideoBuffer::toAVPixelFormat which only covers the subset +// of pixel formats required for encoding. Here we need to cover more +// pixel formats to be able to generate test images for decoding/display +AVPixelFormat toAVPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat) +{ + switch (pixelFormat) { + default: + case QVideoFrameFormat::Format_Invalid: + return AV_PIX_FMT_NONE; + case QVideoFrameFormat::Format_AYUV: + case QVideoFrameFormat::Format_AYUV_Premultiplied: + return AV_PIX_FMT_NONE; // TODO: Fixme (No corresponding FFmpeg format available) + case QVideoFrameFormat::Format_YV12: + case QVideoFrameFormat::Format_IMC1: + case QVideoFrameFormat::Format_IMC3: + case QVideoFrameFormat::Format_IMC2: + case QVideoFrameFormat::Format_IMC4: + return AV_PIX_FMT_YUV420P; + case QVideoFrameFormat::Format_Jpeg: + return AV_PIX_FMT_BGRA; + case QVideoFrameFormat::Format_ARGB8888: + return AV_PIX_FMT_ARGB; + case QVideoFrameFormat::Format_ARGB8888_Premultiplied: + case QVideoFrameFormat::Format_XRGB8888: + return AV_PIX_FMT_0RGB; + case QVideoFrameFormat::Format_BGRA8888: + return AV_PIX_FMT_BGRA; + case QVideoFrameFormat::Format_BGRA8888_Premultiplied: + case QVideoFrameFormat::Format_BGRX8888: + return AV_PIX_FMT_BGR0; + case QVideoFrameFormat::Format_ABGR8888: + return AV_PIX_FMT_ABGR; + case QVideoFrameFormat::Format_XBGR8888: + return AV_PIX_FMT_0BGR; + case QVideoFrameFormat::Format_RGBA8888: + return AV_PIX_FMT_RGBA; + case QVideoFrameFormat::Format_RGBX8888: + return AV_PIX_FMT_RGB0; + case QVideoFrameFormat::Format_YUV422P: + return AV_PIX_FMT_YUV422P; + case QVideoFrameFormat::Format_YUV420P: + return AV_PIX_FMT_YUV420P; + case QVideoFrameFormat::Format_YUV420P10: + return AV_PIX_FMT_YUV420P10; + case QVideoFrameFormat::Format_UYVY: + return AV_PIX_FMT_UYVY422; + case QVideoFrameFormat::Format_YUYV: + return AV_PIX_FMT_YUYV422; + case QVideoFrameFormat::Format_NV12: + return AV_PIX_FMT_NV12; + case QVideoFrameFormat::Format_NV21: + return AV_PIX_FMT_NV21; + case QVideoFrameFormat::Format_Y8: + return AV_PIX_FMT_GRAY8; + case QVideoFrameFormat::Format_Y16: + return AV_PIX_FMT_GRAY16; + case QVideoFrameFormat::Format_P010: + return AV_PIX_FMT_P010; + case QVideoFrameFormat::Format_P016: + return AV_PIX_FMT_P016; + case QVideoFrameFormat::Format_SamplerExternalOES: + return AV_PIX_FMT_MEDIACODEC; + } +} + +struct SwsFrameData +{ + static constexpr int arraySize = 4; // Array size required by sws_scale + std::array<uchar *, arraySize> bits; + std::array<int, arraySize> stride; +}; + +SwsFrameData getSwsData(QVideoFrame &dst) +{ + switch (dst.pixelFormat()) { + case QVideoFrameFormat::Format_YV12: + case QVideoFrameFormat::Format_IMC1: + return { { dst.bits(0), dst.bits(2), dst.bits(1), nullptr }, + { dst.bytesPerLine(0), dst.bytesPerLine(2), dst.bytesPerLine(1), 0 } }; + + case QVideoFrameFormat::Format_IMC2: + return { { dst.bits(0), dst.bits(1) + dst.bytesPerLine(1) / 2, dst.bits(1), nullptr }, + { dst.bytesPerLine(0), dst.bytesPerLine(1), dst.bytesPerLine(1), 0 } }; + + case QVideoFrameFormat::Format_IMC4: + return { { dst.bits(0), dst.bits(1), dst.bits(1) + dst.bytesPerLine(1) / 2, nullptr }, + { dst.bytesPerLine(0), dst.bytesPerLine(1), dst.bytesPerLine(1), 0 } }; + default: + return { { dst.bits(0), dst.bits(1), dst.bits(2), nullptr }, + { dst.bytesPerLine(0), dst.bytesPerLine(1), dst.bytesPerLine(2), 0 } }; + } +} + +struct SwsColorSpace +{ + int colorSpace; + int colorRange; // 0 - mpeg/video, 1 - jpeg/full +}; + +// Qt heuristics for determining color space requires checking +// both frame color space and range. This function mimics logic +// used elsewhere in Qt Multimedia. +SwsColorSpace toSwsColorSpace(QVideoFrameFormat::ColorRange colorRange, + QVideoFrameFormat::ColorSpace colorSpace) +{ + const int avRange = colorRange == QVideoFrameFormat::ColorRange_Video ? 0 : 1; + + switch (colorSpace) { + case QVideoFrameFormat::ColorSpace_BT601: + if (colorRange == QVideoFrameFormat::ColorRange_Full) + return { SWS_CS_ITU709, 1 }; // TODO: FIXME - Not exact match + return { SWS_CS_ITU601, 0 }; + case QVideoFrameFormat::ColorSpace_BT709: + return { SWS_CS_ITU709, avRange }; + case QVideoFrameFormat::ColorSpace_AdobeRgb: + return { SWS_CS_ITU601, 1 }; // TODO: Why do ITU601 and Adobe RGB match well? + case QVideoFrameFormat::ColorSpace_BT2020: + return { SWS_CS_BT2020, avRange }; + case QVideoFrameFormat::ColorSpace_Undefined: + default: + return { SWS_CS_DEFAULT, avRange }; + } +} + +using SwsContextUPtr = std::unique_ptr<SwsContext, decltype(&sws_freeContext)>; +using PixelFormat = QVideoFrameFormat::PixelFormat; + +// clang-format off + +SwsContextUPtr createConverter(const QSize &srcSize, PixelFormat srcPixFmt, + const QSize &dstSize, PixelFormat dstPixFmt) +{ + SwsContext* context = sws_getContext( + srcSize.width(), srcSize.height(), toAVPixelFormat(srcPixFmt), + dstSize.width(), dstSize.height(), toAVPixelFormat(dstPixFmt), + SWS_BILINEAR, nullptr, nullptr, nullptr); + + return { context, &sws_freeContext }; +} + +bool setColorSpaceDetails(SwsContext *context, + const QVideoFrameFormat &srcFormat, + const QVideoFrameFormat &dstFormat) +{ + const SwsColorSpace src = toSwsColorSpace(srcFormat.colorRange(), srcFormat.colorSpace()); + const SwsColorSpace dst = toSwsColorSpace(dstFormat.colorRange(), dstFormat.colorSpace()); + + constexpr int brightness = 0; + constexpr int contrast = 0; + constexpr int saturation = 0; + const int status = sws_setColorspaceDetails(context, + sws_getCoefficients(src.colorSpace), src.colorRange, + sws_getCoefficients(dst.colorSpace), dst.colorRange, + brightness, contrast, saturation); + + return status == 0; +} + +bool convert(SwsContext *context, QVideoFrame &src, int srcHeight, QVideoFrame &dst) +{ + if (!src.map(QVideoFrame::ReadOnly)) + return false; + + QScopeGuard unmapSrc{[&] { + src.unmap(); + }}; + + if (!dst.map(QVideoFrame::WriteOnly)) + return false; + + QScopeGuard unmapDst{[&] { + dst.unmap(); + }}; + + const SwsFrameData srcData = getSwsData(src); + const SwsFrameData dstData = getSwsData(dst); + + constexpr int firstSrcSliceRow = 0; + const int scaledHeight = sws_scale(context, + srcData.bits.data(), srcData.stride.data(), + firstSrcSliceRow, srcHeight, + dstData.bits.data(), dstData.stride.data()); + + if (scaledHeight != srcHeight) + return false; + + return true; +} + +// Ensure even size if using planar format with chroma subsampling +QSize adjustSize(const QSize& size, PixelFormat srcFmt, PixelFormat dstFmt) +{ + const auto* srcDesc = QVideoTextureHelper::textureDescription(srcFmt); + const auto* dstDesc = QVideoTextureHelper::textureDescription(dstFmt); + + QSize output = size; + for (const auto desc : { srcDesc, dstDesc }) { + for (int i = 0; i < desc->nplanes; ++i) { + // TODO: Assumes that max subsampling is 2 + if (desc->sizeScale[i].x != 1) + output.setWidth(output.width() & ~1); // Make even + + if (desc->sizeScale[i].y != 1) + output.setHeight(output.height() & ~1); // Make even + } + } + + return output; +} + +} // namespace + +// Converts a video frame to the dstFormat video frame format. +QVideoFrame convertFrame(QVideoFrame &src, const QVideoFrameFormat &dstFormat) +{ + if (src.size() != dstFormat.frameSize()) { + qCCritical(lc) << "Resizing is not supported"; + return {}; + } + + // Adjust size to even width/height if we have chroma subsampling + const QSize size = adjustSize(src.size(), src.pixelFormat(), dstFormat.pixelFormat()); + if (size != src.size()) + qCWarning(lc) << "Input truncated to even width/height"; + + const SwsContextUPtr conv = createConverter( + size, src.pixelFormat(), size, dstFormat.pixelFormat()); + + if (!conv) { + qCCritical(lc) << "Failed to create SW converter"; + return {}; + } + + if (!setColorSpaceDetails(conv.get(), src.surfaceFormat(), dstFormat)) { + qCCritical(lc) << "Failed to set color space details"; + return {}; + } + + QVideoFrame dst{ dstFormat }; + + if (!convert(conv.get(), src, size.height(), dst)) { + qCCritical(lc) << "Frame conversion failed"; + return {}; + } + + return dst; +} + +// clang-format on + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/qffmpegconverter_p.h b/src/plugins/multimedia/ffmpeg/qffmpegconverter_p.h new file mode 100644 index 000000000..57ee3135f --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/qffmpegconverter_p.h @@ -0,0 +1,30 @@ +// 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 QFFMPEGCONVERTER_P_H +#define QFFMPEGCONVERTER_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 <QtCore/qtconfigmacros.h> +#include <private/qtmultimediaglobal_p.h> + +QT_BEGIN_NAMESPACE + +class QVideoFrameFormat; +class QVideoFrame; + +QVideoFrame convertFrame(QVideoFrame &src, const QVideoFrameFormat &dstFormat); + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/multimedia/ffmpeg/qffmpegdefs_p.h b/src/plugins/multimedia/ffmpeg/qffmpegdefs_p.h index f3860377e..239d8ff0c 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegdefs_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegdefs_p.h @@ -32,6 +32,8 @@ extern "C" { (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 3, 100)) // since ffmpeg n6.0 #define QT_FFMPEG_STREAM_SIDE_DATA_DEPRECATED \ (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100)) // since ffmpeg n6.1 +#define QT_FFMPEG_HAS_D3D12VA \ + (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 8, 100)) // since ffmpeg n7.0 #define QT_FFMPEG_SWR_CONST_CH_LAYOUT (LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 9, 100)) #define QT_FFMPEG_AVIO_WRITE_CONST \ (LIBAVFORMAT_VERSION_MAJOR >= 61) diff --git a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel.cpp b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel.cpp index c85a2f585..dcc228101 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel.cpp @@ -93,6 +93,11 @@ static bool precheckDriver(AVHWDeviceType type) if (type == AV_HWDEVICE_TYPE_D3D11VA) return QSystemLibrary(QLatin1String("d3d11.dll")).load(); +#if QT_FFMPEG_HAS_D3D12VA + if (type == AV_HWDEVICE_TYPE_D3D12VA) + return QSystemLibrary(QLatin1String("d3d12.dll")).load(); +#endif + if (type == AV_HWDEVICE_TYPE_DXVA2) return QSystemLibrary(QLatin1String("d3d9.dll")).load(); @@ -122,6 +127,9 @@ static bool checkHwType(AVHWDeviceType type) if (type == AV_HWDEVICE_TYPE_MEDIACODEC || type == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || type == AV_HWDEVICE_TYPE_D3D11VA || +#if QT_FFMPEG_HAS_D3D12VA + type == AV_HWDEVICE_TYPE_D3D12VA || +#endif type == AV_HWDEVICE_TYPE_DXVA2) return true; // Don't waste time; it's expected to work fine of the precheck is OK @@ -143,10 +151,11 @@ static const std::vector<AVHWDeviceType> &deviceTypes() std::unordered_set<AVPixelFormat> hwPixFormats; void *opaque = nullptr; while (auto codec = av_codec_iterate(&opaque)) { - if (auto pixFmt = codec->pix_fmts) - for (; *pixFmt != AV_PIX_FMT_NONE; ++pixFmt) - if (isHwPixelFormat(*pixFmt)) - hwPixFormats.insert(*pixFmt); + findAVPixelFormat(codec, [&](AVPixelFormat format) { + if (isHwPixelFormat(format)) + hwPixFormats.insert(format); + return false; + }); } // create a device types list @@ -291,7 +300,9 @@ AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *sugge const bool shouldCheckCodecFormats = config->pix_fmt == AV_PIX_FMT_NONE; auto scoresGettor = [&](AVPixelFormat format) { - if (shouldCheckCodecFormats && !isAVFormatSupported(codecContext->codec, format)) + // check in supported codec->pix_fmts; + // no reason to use findAVPixelFormat as we're already in the hw_config loop + if (shouldCheckCodecFormats && !hasAVFormat(codecContext->codec->pix_fmts, format)) return NotSuitableAVScore; if (!shouldCheckCodecFormats && config->pix_fmt != format) diff --git a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp index 0a4e7fe44..c709bd1f1 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_vaapi.cpp @@ -185,7 +185,7 @@ VAAPITextureConverter::VAAPITextureConverter(QRhi *rhi) } const QString platform = QGuiApplication::platformName(); QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface(); - eglDisplay = pni->nativeResourceForIntegration("egldisplay"); + eglDisplay = pni->nativeResourceForIntegration(QByteArrayLiteral("egldisplay")); qCDebug(qLHWAccelVAAPI) << " platform is" << platform << eglDisplay; if (!eglDisplay) { diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediaformatinfo.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediaformatinfo.cpp index 6389b4eed..4e1d67ce2 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediaformatinfo.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediaformatinfo.cpp @@ -178,7 +178,8 @@ QFFmpegMediaFormatInfo::QFFmpegMediaFormatInfo() for (auto codec : audioEncoders) { auto id = codecId(codec); // only add the codec if it can be used with this container - if (avformat_query_codec(outputFormat, id, FF_COMPLIANCE_NORMAL) == 1) { + int result = avformat_query_codec(outputFormat, id, FF_COMPLIANCE_NORMAL); + if (result == 1 || (result < 0 && id == outputFormat->audio_codec)) { // add codec for container // qCDebug(qLcMediaFormatInfo) << " " << codec << Qt::hex << av_codec_get_tag(outputFormat->codec_tag, id); encoder.audio.append(codec); @@ -187,7 +188,8 @@ QFFmpegMediaFormatInfo::QFFmpegMediaFormatInfo() for (auto codec : videoEncoders) { auto id = codecId(codec); // only add the codec if it can be used with this container - if (avformat_query_codec(outputFormat, id, FF_COMPLIANCE_NORMAL) == 1) { + int result = avformat_query_codec(outputFormat, id, FF_COMPLIANCE_NORMAL); + if (result == 1 || (result < 0 && id == outputFormat->video_codec)) { // add codec for container // qCDebug(qLcMediaFormatInfo) << " " << codec << Qt::hex << av_codec_get_tag(outputFormat->codec_tag, id); encoder.video.append(codec); diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration.cpp index 166d33cdf..c2e769563 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration.cpp @@ -15,6 +15,7 @@ #include "qffmpegresampler_p.h" #include "qffmpegsymbolsresolve_p.h" #include "qgrabwindowsurfacecapture_p.h" +#include "qffmpegconverter_p.h" #ifdef Q_OS_MACOS #include <VideoToolbox/VideoToolbox.h> @@ -246,6 +247,12 @@ QMaybe<QPlatformAudioInput *> QFFmpegMediaIntegration::createAudioInput(QAudioIn return new QFFmpegAudioInput(input); } +QVideoFrame QFFmpegMediaIntegration::convertVideoFrame(QVideoFrame &srcFrame, + const QVideoFrameFormat &destFormat) +{ + return convertFrame(srcFrame, destFormat); +} + QPlatformMediaFormatInfo *QFFmpegMediaIntegration::createFormatInfo() { return new QFFmpegMediaFormatInfo; diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration_p.h b/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration_p.h index 4c0e8a058..c0df78f03 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediaintegration_p.h @@ -40,6 +40,9 @@ public: QMaybe<QPlatformAudioInput *> createAudioInput(QAudioInput *input) override; // QPlatformAudioOutput *createAudioOutput(QAudioOutput *) override; + QVideoFrame convertVideoFrame(QVideoFrame &srcFrame, + const QVideoFrameFormat &destFormat) override; + protected: QPlatformMediaFormatInfo *createFormatInfo() override; diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp index 6a950a6ad..26ffe74fc 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp @@ -146,7 +146,7 @@ void QFFmpegMediaPlayer::setPlaybackRate(qreal rate) if (m_playbackEngine) m_playbackEngine->setPlaybackRate(effectiveRate); - emit playbackRateChanged(effectiveRate); + playbackRateChanged(effectiveRate); } QUrl QFFmpegMediaPlayer::media() const @@ -209,8 +209,6 @@ void QFFmpegMediaPlayer::setMedia(const QUrl &media, QIODevice *stream) void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaDataHolder, const std::shared_ptr<QFFmpeg::CancelToken> &cancelToken) { - Q_ASSERT(mediaStatus() == QMediaPlayer::LoadingMedia); - // If loading was cancelled, we do not emit any signals about failing // to load media (or any other events). The rationale is that cancellation // either happens during destruction, where the signals are no longer @@ -221,6 +219,8 @@ void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaData return; } + Q_ASSERT(mediaStatus() == QMediaPlayer::LoadingMedia); + if (!mediaDataHolder) { const auto [code, description] = mediaDataHolder.error(); error(code, description); diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp index 5accd9581..3ce2e9578 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp @@ -32,7 +32,7 @@ bool QFFmpegMediaRecorder::isLocationWritable(const QUrl &) const void QFFmpegMediaRecorder::handleSessionError(QMediaRecorder::Error code, const QString &description) { - error(code, description); + updateError(code, description); stop(); } @@ -46,7 +46,7 @@ void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings) const auto hasAudio = m_session->audioInput() != nullptr; if (!hasVideo && !hasAudio) { - error(QMediaRecorder::ResourceError, QMediaRecorder::tr("No video or audio input")); + updateError(QMediaRecorder::ResourceError, QMediaRecorder::tr("No video or audio input")); return; } @@ -61,8 +61,8 @@ void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings) formatContext->openAVIO(actualLocation); if (!formatContext->isAVIOOpen()) { - error(QMediaRecorder::LocationNotWritable, - QMediaRecorder::tr("Cannot open the output location for writing")); + updateError(QMediaRecorder::LocationNotWritable, + QMediaRecorder::tr("Cannot open the output location for writing")); return; } @@ -72,25 +72,24 @@ void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings) &QFFmpegMediaRecorder::newDuration); connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::finalizationDone, this, &QFFmpegMediaRecorder::finalizationDone); - connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::error, this, + connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::sessionError, this, &QFFmpegMediaRecorder::handleSessionError); - auto *audioInput = m_session->audioInput(); - if (audioInput) { - if (audioInput->device.isNull()) - qWarning() << "Audio input device is null; cannot encode audio"; - else - m_recordingEngine->addAudioInput(static_cast<QFFmpegAudioInput *>(audioInput)); - } + auto handleStreamInitializationError = [this](QMediaRecorder::Error code, + const QString &description) { + qCWarning(qLcMediaEncoder) << "Stream initialization error:" << description; + updateError(code, description); + }; - for (auto source : videoSources) - m_recordingEngine->addVideoSource(source); + connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::streamInitializationError, this, + handleStreamInitializationError); durationChanged(0); stateChanged(QMediaRecorder::RecordingState); actualLocationChanged(QUrl::fromLocalFile(actualLocation)); - m_recordingEngine->start(); + m_recordingEngine->initialize(static_cast<QFFmpegAudioInput *>(m_session->audioInput()), + videoSources); } void QFFmpegMediaRecorder::pause() diff --git a/src/plugins/multimedia/ffmpeg/qffmpegvideobuffer.cpp b/src/plugins/multimedia/ffmpeg/qffmpegvideobuffer.cpp index 5b79af5b3..834b0dbdf 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegvideobuffer.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegvideobuffer.cpp @@ -160,7 +160,7 @@ float QFFmpegVideoBuffer::maxNits() // TODO: Longer term we might want to also support HDR10+ dynamic metadata if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) { auto *data = reinterpret_cast<AVMasteringDisplayMetadata *>(sd->data); - auto maybeLum = QFFmpeg::mul(10'000., data->max_luminance); + auto maybeLum = QFFmpeg::mul(qreal(10'000.), data->max_luminance); if (maybeLum) maxNits = float(maybeLum.value()); } diff --git a/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp index 2086af10d..1ba05364d 100644 --- a/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp +++ b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp @@ -394,7 +394,7 @@ void QV4L2Camera::readFrame() void QV4L2Camera::setCameraBusy() { m_cameraBusy = true; - emit error(QCamera::CameraError, QLatin1String("Camera is in use")); + updateError(QCamera::CameraError, QLatin1String("Camera is in use")); } void QV4L2Camera::initV4L2Controls() @@ -412,7 +412,7 @@ void QV4L2Camera::initV4L2Controls() qCWarning(qLcV4L2Camera) << "Unable to open the camera" << deviceName << "for read to query the parameter info:" << qt_error_string(errno); - emit error(QCamera::CameraError, QLatin1String("Cannot open camera")); + updateError(QCamera::CameraError, QLatin1String("Cannot open camera")); return; } @@ -651,7 +651,7 @@ void QV4L2Camera::initV4L2MemoryTransfer() if (!m_memoryTransfer) { qCWarning(qLcV4L2Camera) << "Cannot init v4l2 memory transfer," << qt_error_string(errno); - emit error(QCamera::CameraError, QLatin1String("Cannot init V4L2 memory transfer")); + updateError(QCamera::CameraError, QLatin1String("Cannot init V4L2 memory transfer")); } } diff --git a/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp b/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp index ff94422d6..2d178bec7 100644 --- a/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp +++ b/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp @@ -186,7 +186,7 @@ public: { if (FAILED(status)) { const std::string msg{ std::system_category().message(status) }; - emit m_windowsCamera.error(QCamera::CameraError, QString::fromStdString(msg)); + m_windowsCamera.updateError(QCamera::CameraError, QString::fromStdString(msg)); return; } diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoderutils.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoderutils.cpp index ea36a8138..a56b9947b 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoderutils.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegaudioencoderutils.cpp @@ -11,9 +11,19 @@ namespace QFFmpeg { AVSampleFormat adjustSampleFormat(const AVSampleFormat *supportedFormats, AVSampleFormat requested) { auto calcScore = [requested](AVSampleFormat format) { - return format == requested ? BestAVScore - : format == av_get_planar_sample_fmt(requested) ? BestAVScore - 1 - : 0; + if (format == requested) + return BestAVScore; + if (format == av_get_planar_sample_fmt(requested)) + return BestAVScore - 1; + + const int bps = av_get_bytes_per_sample(format); + const int bpsRequested = av_get_bytes_per_sample(requested); + // choose the closest one with higher bps + if (bps >= bpsRequested) + return DefaultAVScore - (bps - bpsRequested); + + // choose the closest one with lower bps, considering a priority penalty + return DefaultAVScore - (bpsRequested - bps) - 1000000; }; const auto result = findBestAVFormat(supportedFormats, calcScore).first; @@ -23,9 +33,15 @@ AVSampleFormat adjustSampleFormat(const AVSampleFormat *supportedFormats, AVSamp int adjustSampleRate(const int *supportedRates, int requested) { auto calcScore = [requested](int rate) { - return requested == rate ? BestAVScore - : requested <= rate ? rate - requested - : requested - rate - 1000000; + if (requested == rate) + return BestAVScore; + + // choose the closest one with higher rate + if (rate >= requested) + return DefaultAVScore - (rate - requested); + + // choose the closest one with lower rate, considering a priority penalty + return DefaultAVScore - (requested - rate) - 1000000; }; const auto result = findBestAVValue(supportedRates, calcScore).first; @@ -35,9 +51,11 @@ int adjustSampleRate(const int *supportedRates, int requested) static AVScore calculateScoreByChannelsCount(int supportedChannelsNumber, int requestedChannelsNumber) { + // choose the closest one with higher channels number if (supportedChannelsNumber >= requestedChannelsNumber) return requestedChannelsNumber - supportedChannelsNumber; + // choose the closest one with lower channels number, considering a priority penalty return supportedChannelsNumber - requestedChannelsNumber - 10000; } diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer.cpp new file mode 100644 index 000000000..e5b88a7d5 --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer.cpp @@ -0,0 +1,101 @@ +// 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 + +#include "qffmpegencodinginitializer_p.h" +#include "qffmpegrecordingengine_p.h" +#include "qvideoframe.h" + +#include "private/qplatformvideosource_p.h" + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +EncodingInitializer::EncodingInitializer(RecordingEngine &engine) : m_recordingEngine(engine) { } + +void EncodingInitializer::start(QFFmpegAudioInput *audioInput, + const std::vector<QPlatformVideoSource *> &videoSources) +{ + if (audioInput) + m_recordingEngine.addAudioInput(audioInput); + + for (auto source : videoSources) + addVideoSource(source); + + tryStartRecordingEngine(); +} + +void EncodingInitializer::addVideoSource(QPlatformVideoSource *source) +{ + Q_ASSERT(source); + Q_ASSERT(source->isActive()); + + if (source->frameFormat().isValid()) + m_recordingEngine.addVideoSource(source, {}); + else if (source->hasError()) + emitStreamInitializationError(QStringLiteral("Source error: ") + source->errorString()); + else + addPendingVideoSource(source); +} + +void EncodingInitializer::addPendingVideoSource(QPlatformVideoSource *source) +{ + Q_ASSERT(m_pendingSources.count(source) == 0); + + m_pendingSources.insert(source); + + connect(source, &QPlatformVideoSource::errorChanged, this, [this, source]() { + if (source->hasError()) + erasePendingSource(source, QStringLiteral("Source error: ") + source->errorString()); + }); + + connect(source, &QPlatformVideoSource::destroyed, this, + [this, source]() { erasePendingSource(source, QStringLiteral("Source deleted")); }); + + connect(source, &QPlatformVideoSource::activeChanged, this, [this, source]() { + if (!source->isActive()) + erasePendingSource(source, QStringLiteral("Source deactivated")); + }); + + connect(source, &QPlatformVideoSource::newVideoFrame, this, + [this, source](const QVideoFrame &frame) { + if (frame.isValid()) + erasePendingSource(source, + [&]() { m_recordingEngine.addVideoSource(source, frame); }); + else + erasePendingSource(source, QStringLiteral("Source has sent the end frame")); + }); +} + +void EncodingInitializer::tryStartRecordingEngine() +{ + if (m_pendingSources.empty()) + m_recordingEngine.start(); +} + +void EncodingInitializer::emitStreamInitializationError(QString error) +{ + emit m_recordingEngine.streamInitializationError( + QMediaRecorder::ResourceError, + QStringLiteral("Video steam initialization error. ") + error); +} + +template <typename F> +void EncodingInitializer::erasePendingSource(QObject *source, F &&functionOrError) +{ + const auto erasedCount = m_pendingSources.erase(source); + if (erasedCount == 0) + return; // got a queued event, just ignore it. + + if constexpr (std::is_invocable_v<F>) + functionOrError(); + else + emitStreamInitializationError(functionOrError); + + disconnect(source, nullptr, this, nullptr); + tryStartRecordingEngine(); +} + +} // namespace QFFmpeg + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer_p.h new file mode 100644 index 000000000..8d0539e1b --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer_p.h @@ -0,0 +1,63 @@ +// 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 QENCODINGINITIALIZER_P_H +#define QENCODINGINITIALIZER_P_H + +#include "qobject.h" +#include <unordered_set> +#include <vector> + +// +// 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. +// + +QT_BEGIN_NAMESPACE + +class QFFmpegAudioInput; +class QPlatformVideoSource; + +namespace QFFmpeg { + +class RecordingEngine; + +// Initializes RecordingEngine with audio and video sources, potentially lazily +// upon first frame arrival if video frame format is not pre-determined. +class EncodingInitializer : public QObject +{ +public: + EncodingInitializer(RecordingEngine &engine); + + void start(QFFmpegAudioInput *audioInput, + const std::vector<QPlatformVideoSource *> &videoSources); + +private: + void addVideoSource(QPlatformVideoSource *source); + + void addPendingVideoSource(QPlatformVideoSource *source); + + void tryStartRecordingEngine(); + +private: + void emitStreamInitializationError(QString error); + + template <typename F> + void erasePendingSource(QObject *source, F &&functionOrError); + +private: + RecordingEngine &m_recordingEngine; + std::unordered_set<QObject *> m_pendingSources; +}; + +} // namespace QFFmpeg + +QT_END_NAMESPACE + +#endif // QENCODINGINITIALIZER_P_H diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer.cpp index 6367dde3b..2df594017 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer.cpp @@ -18,7 +18,7 @@ Muxer::Muxer(RecordingEngine *encoder) : m_encoder(encoder) void Muxer::addPacket(AVPacketUPtr packet) { { - QMutexLocker locker(&m_queueMutex); + QMutexLocker locker = lockLoopData(); m_packetQueue.push(std::move(packet)); } @@ -28,7 +28,7 @@ void Muxer::addPacket(AVPacketUPtr packet) AVPacketUPtr Muxer::takePacket() { - QMutexLocker locker(&m_queueMutex); + QMutexLocker locker = lockLoopData(); return dequeueIfPossible(m_packetQueue); } @@ -37,11 +37,14 @@ void Muxer::init() qCDebug(qLcFFmpegMuxer) << "Muxer::init started thread."; } -void Muxer::cleanup() { } +void Muxer::cleanup() +{ + while (!m_packetQueue.empty()) + processOne(); +} bool QFFmpeg::Muxer::hasData() const { - QMutexLocker locker(&m_queueMutex); return !m_packetQueue.empty(); } diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer_p.h index 8cdf73c6f..4f8f4d27a 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer_p.h +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegmuxer_p.h @@ -29,7 +29,6 @@ private: void processOne() override; private: - mutable QMutex m_queueMutex; std::queue<AVPacketUPtr> m_packetQueue; RecordingEngine *m_encoder; diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp index 2b32af502..14b2750fd 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp @@ -3,6 +3,7 @@ #include "qffmpegrecordingengine_p.h" #include "qffmpegmediaformatinfo_p.h" #include "qffmpegvideoframeencoder_p.h" +#include "qffmpegencodinginitializer_p.h" #include "private/qmultimediautils_p.h" #include <qdebug.h> @@ -36,22 +37,33 @@ RecordingEngine::~RecordingEngine() void RecordingEngine::addAudioInput(QFFmpegAudioInput *input) { + Q_ASSERT(input); + + if (input->device.isNull()) { + emit streamInitializationError(QMediaRecorder::ResourceError, + QLatin1StringView("Audio device is null")); + return; + } + + if (!input->device.preferredFormat().isValid()) { + emit streamInitializationError( + QMediaRecorder::FormatError, + QLatin1StringView("Audio device has invalid preferred format")); + return; + } + m_audioEncoder = new AudioEncoder(*this, input, m_settings); addMediaFrameHandler(input, &QFFmpegAudioInput::newAudioBuffer, m_audioEncoder, &AudioEncoder::addBuffer); input->setRunning(true); } -void RecordingEngine::addVideoSource(QPlatformVideoSource * source) +void RecordingEngine::addVideoSource(QPlatformVideoSource *source, const QVideoFrame &firstFrame) { - auto frameFormat = source->frameFormat(); + QVideoFrameFormat frameFormat = + firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat(); - if (!frameFormat.isValid()) { - qCWarning(qLcFFmpegEncoder) << "Cannot add source; invalid vide frame format"; - emit error(QMediaRecorder::ResourceError, - QLatin1StringView("Cannot get video source format")); - return; - } + Q_ASSERT(frameFormat.isValid()); std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat() ? AVPixelFormat(*source->ffmpegHWPixelFormat()) @@ -65,17 +77,30 @@ void RecordingEngine::addVideoSource(QPlatformVideoSource * source) auto veUPtr = std::make_unique<VideoEncoder>(*this, m_settings, frameFormat, hwPixelFormat); if (!veUPtr->isValid()) { - emit error(QMediaRecorder::FormatError, QLatin1StringView("Cannot initialize encoder")); + emit streamInitializationError(QMediaRecorder::FormatError, + QLatin1StringView("Cannot initialize encoder")); return; } auto ve = veUPtr.release(); addMediaFrameHandler(source, &QPlatformVideoSource::newVideoFrame, ve, &VideoEncoder::addFrame); m_videoEncoders.append(ve); + + if (firstFrame.isValid()) + ve->addFrame(firstFrame); } void RecordingEngine::start() { + Q_ASSERT(m_initializer); + m_initializer.reset(); + + if (!m_audioEncoder && m_videoEncoders.empty()) { + emit sessionError(QMediaRecorder::ResourceError, + QLatin1StringView("No valid stream found for encoding")); + return; + } + qCDebug(qLcFFmpegEncoder) << "RecordingEngine::start!"; avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData); @@ -85,7 +110,8 @@ void RecordingEngine::start() int res = avformat_write_header(avFormatContext(), nullptr); if (res < 0) { qWarning() << "could not write header, error:" << res << err2str(res); - emit error(QMediaRecorder::ResourceError, "Cannot start writing the stream"); + emit sessionError(QMediaRecorder::ResourceError, + QLatin1StringView("Cannot start writing the stream")); return; } @@ -101,6 +127,15 @@ void RecordingEngine::start() videoEncoder->start(); } +void RecordingEngine::initialize(QFFmpegAudioInput *audioInput, + const std::vector<QPlatformVideoSource *> &videoSources) +{ + qCDebug(qLcFFmpegEncoder) << ">>>>>>>>>>>>>>> initialize"; + + m_initializer = std::make_unique<EncodingInitializer>(*this); + m_initializer->start(audioInput, videoSources); +} + RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine) : m_recordingEngine(recordingEngine) { @@ -120,9 +155,9 @@ void RecordingEngine::EncodingFinalizer::run() if (res < 0) { const auto errorDescription = err2str(res); qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << errorDescription; - emit m_recordingEngine.error(QMediaRecorder::FormatError, - QLatin1String("Cannot write trailer: ") - + errorDescription); + emit m_recordingEngine.sessionError(QMediaRecorder::FormatError, + QLatin1String("Cannot write trailer: ") + + errorDescription); } } // else ffmpeg might crash @@ -140,6 +175,8 @@ void RecordingEngine::finalize() { qCDebug(qLcFFmpegEncoder) << ">>>>>>>>>>>>>>> finalize"; + m_initializer.reset(); + for (auto &conn : m_connections) disconnect(conn); diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h index b74fbba9f..8a7f4824b 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h @@ -36,6 +36,7 @@ class Muxer; class AudioEncoder; class VideoEncoder; class VideoFrameEncoder; +class EncodingInitializer; template <typename T> T dequeueIfPossible(std::queue<T> &queue) @@ -55,10 +56,8 @@ public: RecordingEngine(const QMediaEncoderSettings &settings, std::unique_ptr<EncodingFormatContext> context); ~RecordingEngine(); - void addAudioInput(QFFmpegAudioInput *input); - void addVideoSource(QPlatformVideoSource *source); - - void start(); + void initialize(QFFmpegAudioInput *audioInput, + const std::vector<QPlatformVideoSource *> &videoSources); void finalize(); void setPaused(bool p); @@ -72,7 +71,8 @@ public Q_SLOTS: Q_SIGNALS: void durationChanged(qint64 duration); - void error(QMediaRecorder::Error code, const QString &description); + void sessionError(QMediaRecorder::Error code, const QString &description); + void streamInitializationError(QMediaRecorder::Error code, const QString &description); void finalizationDone(); private: @@ -90,6 +90,12 @@ private: RecordingEngine &m_recordingEngine; }; + friend class EncodingInitializer; + void addAudioInput(QFFmpegAudioInput *input); + void addVideoSource(QPlatformVideoSource *source, const QVideoFrame &firstFrame); + + void start(); + private: QMediaEncoderSettings m_settings; QMediaMetaData m_metaData; @@ -99,6 +105,7 @@ private: AudioEncoder *m_audioEncoder = nullptr; QList<VideoEncoder *> m_videoEncoders; QList<QMetaObject::Connection> m_connections; + std::unique_ptr<EncodingInitializer> m_initializer; QMutex m_timeMutex; qint64 m_timeRecorded = 0; diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp index a47968096..20a6520d8 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp @@ -79,7 +79,8 @@ void VideoEncoder::init() qCDebug(qLcFFmpegVideoEncoder) << "VideoEncoder::init started video device thread."; bool ok = m_frameEncoder->open(); if (!ok) - emit m_recordingEngine.error(QMediaRecorder::ResourceError, "Could not initialize encoder"); + emit m_recordingEngine.sessionError(QMediaRecorder::ResourceError, + "Could not initialize encoder"); } void VideoEncoder::cleanup() @@ -178,7 +179,7 @@ void VideoEncoder::processOne() 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)); + emit m_recordingEngine.sessionError(QMediaRecorder::ResourceError, err2str(ret)); } } diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoderutils.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoderutils.cpp index 83b9575b4..eef2a64bf 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoderutils.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoderutils.cpp @@ -108,8 +108,9 @@ AVPixelFormat findTargetFormat(AVPixelFormat sourceFormat, AVPixelFormat sourceS if (constraints && hasAVFormat(constraints->valid_hw_formats, hwFormat)) return hwFormat; - // Some codecs, don't expose constraints, let's find the format in codec->pix_fmts - if (hasAVFormat(codec->pix_fmts, hwFormat)) + // Some codecs, don't expose constraints, + // let's find the format in codec->pix_fmts and hw_config + if (isAVFormatSupported(codec, hwFormat)) return hwFormat; } diff --git a/src/plugins/multimedia/gstreamer/CMakeLists.txt b/src/plugins/multimedia/gstreamer/CMakeLists.txt index 80fc1fce0..ae3c54bbd 100644 --- a/src/plugins/multimedia/gstreamer/CMakeLists.txt +++ b/src/plugins/multimedia/gstreamer/CMakeLists.txt @@ -3,16 +3,15 @@ qt_find_package(EGL) -qt_internal_add_module(QGstreamerMediaPluginPrivate +qt_internal_add_module(QGstreamerMediaPluginImplPrivate STATIC INTERNAL_MODULE SOURCES audio/qgstreameraudiodevice.cpp audio/qgstreameraudiodevice_p.h - audio/qgstreameraudiosource.cpp audio/qgstreameraudiosource_p.h - audio/qgstreameraudiosink.cpp audio/qgstreameraudiosink_p.h audio/qgstreameraudiodecoder.cpp audio/qgstreameraudiodecoder_p.h common/qglist_helper_p.h common/qgst.cpp common/qgst_p.h + common/qgst_bus.cpp common/qgst_bus_p.h common/qgst_debug.cpp common/qgst_debug_p.h common/qgst_handle_types_p.h common/qgstappsource.cpp common/qgstappsource_p.h @@ -37,10 +36,15 @@ qt_internal_add_module(QGstreamerMediaPluginPrivate mediacapture/qgstreamerimagecapture.cpp mediacapture/qgstreamerimagecapture_p.h mediacapture/qgstreamermediacapture.cpp mediacapture/qgstreamermediacapture_p.h mediacapture/qgstreamermediaencoder.cpp mediacapture/qgstreamermediaencoder_p.h + uri_handler/qgstreamer_qrc_handler.cpp uri_handler/qgstreamer_qrc_handler_p.h + NO_UNITY_BUILD_SOURCES # Conflicts with macros defined in X11.h, and Xlib.h common/qgstvideobuffer.cpp common/qgstreamervideosink.cpp + + # preprocessor / internal linkage + uri_handler/qgstreamer_qrc_handler.cpp NO_GENERATE_CPP_EXPORTS DEFINES GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26 @@ -51,12 +55,12 @@ qt_internal_add_module(QGstreamerMediaPluginPrivate GStreamer::App ) -qt_internal_extend_target(QGstreamerMediaPluginPrivate CONDITION QT_FEATURE_gstreamer_photography +qt_internal_extend_target(QGstreamerMediaPluginImplPrivate CONDITION QT_FEATURE_gstreamer_photography PUBLIC_LIBRARIES GStreamer::Photography ) -qt_internal_extend_target(QGstreamerMediaPluginPrivate CONDITION QT_FEATURE_gstreamer_gl +qt_internal_extend_target(QGstreamerMediaPluginImplPrivate CONDITION QT_FEATURE_gstreamer_gl PUBLIC_LIBRARIES GStreamer::Gl LIBRARIES @@ -70,6 +74,6 @@ qt_internal_add_plugin(QGstreamerMediaPlugin qgstreamerplugin.cpp gstreamer.json LIBRARIES - Qt::QGstreamerMediaPluginPrivate + Qt::QGstreamerMediaPluginImplPrivate Qt::MultimediaPrivate ) diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp index 0cfa28169..5bc2115e5 100644 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp +++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp @@ -4,8 +4,9 @@ #include <audio/qgstreameraudiodecoder_p.h> -#include <common/qgstreamermessage_p.h> #include <common/qgst_debug_p.h> +#include <common/qgstappsource_p.h> +#include <common/qgstreamermessage_p.h> #include <common/qgstutils_p.h> #include <gst/gstvalue.h> @@ -21,8 +22,6 @@ #include <QtCore/qurl.h> #include <QtCore/qloggingcategory.h> -#define MAX_BUFFERS_IN_QUEUE 4 - QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(qLcGstreamerAudioDecoder, "qt.multimedia.gstreameraudiodecoder"); @@ -42,23 +41,21 @@ typedef enum { QMaybe<QPlatformAudioDecoder *> QGstreamerAudioDecoder::create(QAudioDecoder *parent) { - QGstElement audioconvert = QGstElement::createFromFactory("audioconvert", "audioconvert"); - if (!audioconvert) - return errorMessageCannotFindElement("audioconvert"); + static const auto error = qGstErrorMessageIfElementsNotAvailable("audioconvert", "playbin"); + if (error) + return *error; - QGstPipeline playbin = QGstPipeline::adopt( - GST_PIPELINE_CAST(QGstElement::createFromFactory("playbin", "playbin").element())); - if (!playbin) - return errorMessageCannotFindElement("playbin"); - - return new QGstreamerAudioDecoder(playbin, audioconvert, parent); + return new QGstreamerAudioDecoder(parent); } -QGstreamerAudioDecoder::QGstreamerAudioDecoder(QGstPipeline playbin, QGstElement audioconvert, - QAudioDecoder *parent) +QGstreamerAudioDecoder::QGstreamerAudioDecoder(QAudioDecoder *parent) : QPlatformAudioDecoder(parent), - m_playbin(std::move(playbin)), - m_audioConvert(std::move(audioconvert)) + m_playbin{ + QGstPipeline::createFromFactory("playbin3", "playbin"), + }, + m_audioConvert{ + QGstElement::createFromFactory("audioconvert", "audioconvert"), + } { // Sort out messages m_playbin.installMessageFilter(this); @@ -87,159 +84,185 @@ QGstreamerAudioDecoder::~QGstreamerAudioDecoder() stop(); m_playbin.removeMessageFilter(this); - -#if QT_CONFIG(gstreamer_app) - delete m_appSrc; -#endif } -#if QT_CONFIG(gstreamer_app) void QGstreamerAudioDecoder::configureAppSrcElement([[maybe_unused]] GObject *object, GObject *orig, [[maybe_unused]] GParamSpec *pspec, QGstreamerAudioDecoder *self) { - // In case we switch from appsrc to not - if (!self->m_appSrc) - return; - QGstElementHandle appsrc; g_object_get(orig, "source", &appsrc, NULL); - auto *qAppSrc = self->m_appSrc; - qAppSrc->setExternalAppSrc(QGstAppSrc{ - qGstSafeCast<GstAppSrc>(appsrc.get()), - QGstAppSrc::NeedsRef, // CHECK: can we `release()`? - }); - qAppSrc->setup(self->mDevice); + GstAppSrc *gstAppSrc = qGstSafeCast<GstAppSrc>(appsrc.release()); + if (gstAppSrc) + QGstAppSource::attachQIODeviceToGstAppSrc(gstAppSrc, self->mDevice); } -#endif bool QGstreamerAudioDecoder::processBusMessage(const QGstreamerMessage &message) { qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message; - GstMessage *gm = message.message(); - switch (message.type()) { - case GST_MESSAGE_DURATION: { - updateDuration(); + case GST_MESSAGE_DURATION: + return processBusMessageDuration(message); + + case GST_MESSAGE_ERROR: + return processBusMessageError(message); + + case GST_MESSAGE_WARNING: + return processBusMessageWarning(message); + + case GST_MESSAGE_INFO: + return processBusMessageInfo(message); + + case GST_MESSAGE_EOS: + return processBusMessageEOS(message); + + case GST_MESSAGE_STATE_CHANGED: + return processBusMessageStateChanged(message); + + case GST_MESSAGE_STREAMS_SELECTED: + return processBusMessageStreamsSelected(message); + + default: return false; } +} - case GST_MESSAGE_ERROR: { - qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message); - - 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, - tr("Cannot play stream of type: <unknown>")); - else - processInvalidMedia(QAudioDecoder::ResourceError, - QString::fromUtf8(err.get()->message)); - } 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; - } - } +bool QGstreamerAudioDecoder::canReadQrc() const +{ + return true; +} - processInvalidMedia(qerror, QString::fromUtf8(err.get()->message)); +bool QGstreamerAudioDecoder::processBusMessageError(const QGstreamerMessage &message) +{ + qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message); + + QUniqueGErrorHandle err; + QGString debug; + gst_message_parse_error(message.message(), &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, + tr("Cannot play stream of type: <unknown>")); + else + processInvalidMedia(QAudioDecoder::ResourceError, + QString::fromUtf8(err.get()->message)); + } 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; + } } - break; - } - default: - if (message.source() == m_playbin) - return handlePlaybinMessage(message); + processInvalidMedia(qerror, QString::fromUtf8(err.get()->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_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; - } +bool QGstreamerAudioDecoder::processBusMessageDuration(const QGstreamerMessage &) +{ + updateDuration(); + return false; +} - setIsDecoding(isDecoding); - break; - }; +bool QGstreamerAudioDecoder::processBusMessageWarning(const QGstreamerMessage &message) +{ + qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message); + return false; +} - case GST_MESSAGE_EOS: - m_playbin.setState(GST_STATE_NULL); - finished(); - break; +bool QGstreamerAudioDecoder::processBusMessageInfo(const QGstreamerMessage &message) +{ + if (qLcGstreamerAudioDecoder().isDebugEnabled()) + qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message); + return false; +} - case GST_MESSAGE_ERROR: - Q_UNREACHABLE_RETURN(false); // handled in processBusMessage +bool QGstreamerAudioDecoder::processBusMessageEOS(const QGstreamerMessage &) +{ + m_playbin.setState(GST_STATE_NULL); + finished(); + return false; +} - case GST_MESSAGE_WARNING: - qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message); - break; +bool QGstreamerAudioDecoder::processBusMessageStateChanged(const QGstreamerMessage &message) +{ + if (message.source() != m_playbin) + return false; - case GST_MESSAGE_INFO: { - if (qLcGstreamerAudioDecoder().isDebugEnabled()) - qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message); + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(message.message(), &oldState, &newState, &pending); + + bool isDecoding = false; + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + case GST_STATE_READY: break; - } - default: + 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; } + setIsDecoding(isDecoding); + return false; +} + +bool QGstreamerAudioDecoder::processBusMessageStreamsSelected(const QGstreamerMessage &message) +{ + using namespace Qt::StringLiterals; + + QGstStreamCollectionHandle collection; + gst_message_parse_streams_selected(const_cast<GstMessage *>(message.message()), &collection); + + bool hasAudio = false; + qForeachStreamInCollection(collection, [&](GstStream *stream) { + GstStreamType type = gst_stream_get_stream_type(stream); + if (type == GstStreamType::GST_STREAM_TYPE_AUDIO) + hasAudio = true; + }); + + if (!hasAudio) + processInvalidMedia(QAudioDecoder::FormatError, u"No audio track in media"_s); + return false; } @@ -252,8 +275,6 @@ void QGstreamerAudioDecoder::setSource(const QUrl &fileName) { stop(); mDevice = nullptr; - delete m_appSrc; - m_appSrc = nullptr; bool isSignalRequired = (mSource != fileName); mSource = fileName; @@ -289,16 +310,6 @@ void QGstreamerAudioDecoder::start() return; } - if (!m_appSrc) { - auto maybeAppSrc = QGstAppSource::create(this); - if (maybeAppSrc) { - m_appSrc = maybeAppSrc.value(); - } else { - processInvalidMedia(QAudioDecoder::ResourceError, maybeAppSrc.error()); - return; - } - } - m_playbin.set("uri", "appsrc://"); } else { return; @@ -336,14 +347,14 @@ void QGstreamerAudioDecoder::stop() bufferAvailableChanged(false); } - if (m_position != -1) { - m_position = -1; - positionChanged(m_position); + if (m_position != invalidPosition) { + m_position = invalidPosition; + positionChanged(m_position.count()); } - if (m_duration != -1) { - m_duration = -1; - durationChanged(m_duration); + if (m_duration != invalidDuration) { + m_duration = invalidDuration; + durationChanged(m_duration.count()); } setIsDecoding(false); @@ -364,6 +375,8 @@ void QGstreamerAudioDecoder::setAudioFormat(const QAudioFormat &format) QAudioBuffer QGstreamerAudioDecoder::read() { + using namespace std::chrono; + QAudioBuffer audioBuffer; if (m_buffersAvailable == 0) @@ -385,12 +398,16 @@ QAudioBuffer QGstreamerAudioDecoder::read() 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 + nanoseconds position = getPositionFromBuffer(buffer); + audioBuffer = QAudioBuffer{ + QByteArray(bufferData, bufferSize), + format, + round<microseconds>(position).count(), + }; + milliseconds positionInMs = round<milliseconds>(position); if (position != m_position) { - m_position = position; - positionChanged(m_position); + m_position = positionInMs; + positionChanged(m_position.count()); } } gst_buffer_unmap(buffer, &mapInfo); @@ -400,12 +417,12 @@ QAudioBuffer QGstreamerAudioDecoder::read() qint64 QGstreamerAudioDecoder::position() const { - return m_position; + return m_position.count(); } qint64 QGstreamerAudioDecoder::duration() const { - return m_duration; + return m_duration.count(); } void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString) @@ -452,6 +469,8 @@ void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio) void QGstreamerAudioDecoder::addAppSink() { + using namespace std::chrono_literals; + if (m_appSink) return; @@ -460,10 +479,19 @@ void QGstreamerAudioDecoder::addAppSink() GstAppSinkCallbacks callbacks{}; callbacks.new_sample = new_sample; m_appSink.setCallbacks(callbacks, this, nullptr); - gst_app_sink_set_max_buffers(m_appSink.appSink(), MAX_BUFFERS_IN_QUEUE); - gst_base_sink_set_sync(m_appSink.baseSink(), FALSE); - QGstPipeline::modifyPipelineWhileNotRunning(m_playbin.getPipeline(), [&] { +#if GST_CHECK_VERSION(1, 24, 0) + static constexpr auto maxBufferTime = 500ms; + m_appSink.setMaxBufferTime(maxBufferTime); +#else + static constexpr int maxBuffers = 16; + m_appSink.setMaxBuffers(maxBuffers); +#endif + + static constexpr bool sync = false; + m_appSink.setSync(sync); + + m_audioConvert.src().modifyPipelineInIdleProbe([&] { m_outputBin.add(m_appSink); qLinkGstElements(m_audioConvert, m_appSink); }); @@ -476,23 +504,26 @@ void QGstreamerAudioDecoder::removeAppSink() qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::removeAppSink"; - QGstPipeline::modifyPipelineWhileNotRunning(m_playbin.getPipeline(), [&] { + m_audioConvert.src().modifyPipelineInIdleProbe([&] { qUnlinkGstElements(m_audioConvert, m_appSink); m_outputBin.stopAndRemoveElements(m_appSink); }); + m_appSink = {}; } void QGstreamerAudioDecoder::updateDuration() { - int duration = m_playbin.duration() / 1000000; + std::optional<std::chrono::milliseconds> duration = m_playbin.durationInMs(); + if (!duration) + duration = invalidDuration; if (m_duration != duration) { - m_duration = duration; - durationChanged(m_duration); + m_duration = *duration; + durationChanged(m_duration.count()); } - if (m_duration > 0) + if (m_duration.count() > 0) m_durationQueries = 0; if (m_durationQueries > 0) { @@ -503,14 +534,15 @@ void QGstreamerAudioDecoder::updateDuration() } } -qint64 QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer* buffer) +std::chrono::nanoseconds QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer *buffer) { - qint64 position = GST_BUFFER_TIMESTAMP(buffer); - if (position >= 0) - position = position / G_GINT64_CONSTANT(1000); // microseconds + using namespace std::chrono; + using namespace std::chrono_literals; + nanoseconds position{ GST_BUFFER_TIMESTAMP(buffer) }; + if (position >= 0ns) + return position; else - position = -1; - return position; + return invalidPosition; } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h index eba1025fa..f7bacd941 100644 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h +++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h @@ -23,12 +23,9 @@ #include <QtCore/qmutex.h> #include <QtCore/qurl.h> -#include <common/qgstpipeline_p.h> #include <common/qgst_p.h> - -#if QT_CONFIG(gstreamer_app) -# include <common/qgstappsource_p.h> -#endif +#include <common/qgst_bus_p.h> +#include <common/qgstpipeline_p.h> #include <gst/app/gstappsink.h> @@ -64,42 +61,50 @@ public: // GStreamerBusMessageFilter interface bool processBusMessage(const QGstreamerMessage &message) override; + bool canReadQrc() const override; + private slots: void updateDuration(); private: - QGstreamerAudioDecoder(QGstPipeline playbin, QGstElement audioconvert, QAudioDecoder *parent); + explicit QGstreamerAudioDecoder(QAudioDecoder *parent); -#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 void setAudioFlags(bool wantNativeAudio); void addAppSink(); void removeAppSink(); - bool handlePlaybinMessage(const QGstreamerMessage &); - void processInvalidMedia(QAudioDecoder::Error errorCode, const QString &errorString); - static qint64 getPositionFromBuffer(GstBuffer* buffer); + static std::chrono::nanoseconds getPositionFromBuffer(GstBuffer *buffer); + + bool processBusMessageError(const QGstreamerMessage &); + bool processBusMessageDuration(const QGstreamerMessage &); + bool processBusMessageWarning(const QGstreamerMessage &); + bool processBusMessageInfo(const QGstreamerMessage &); + bool processBusMessageEOS(const QGstreamerMessage &); + bool processBusMessageStateChanged(const QGstreamerMessage &); + bool processBusMessageStreamsSelected(const QGstreamerMessage &); QGstPipeline m_playbin; QGstBin m_outputBin; QGstElement m_audioConvert; QGstAppSink m_appSink; - QGstAppSource *m_appSrc = nullptr; QUrl mSource; QIODevice *mDevice = nullptr; QAudioFormat mFormat; int m_buffersAvailable = 0; - qint64 m_position = -1; - qint64 m_duration = -1; + + static constexpr auto invalidDuration = std::chrono::milliseconds{ -1 }; + static constexpr auto invalidPosition = std::chrono::milliseconds{ -1 }; + std::chrono::milliseconds m_position{ invalidPosition }; + std::chrono::milliseconds m_duration{ invalidDuration }; int m_durationQueries = 0; diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp index 2c6b57e55..b22e40118 100644 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp +++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp @@ -49,6 +49,39 @@ QGStreamerAudioDeviceInfo::QGStreamerAudioDeviceInfo(GstDevice *d, const QByteAr preferredFormat.setSampleFormat(f); } -QGStreamerAudioDeviceInfo::~QGStreamerAudioDeviceInfo() = default; +QGStreamerCustomAudioDeviceInfo::QGStreamerCustomAudioDeviceInfo( + const QByteArray &gstreamerPipeline, QAudioDevice::Mode mode) + : QAudioDevicePrivate{ + gstreamerPipeline, + mode, + } +{ +} + +QAudioDevice qMakeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline) +{ + auto deviceInfo = std::make_unique<QGStreamerCustomAudioDeviceInfo>(gstreamerPipeline, + QAudioDevice::Mode::Input); + + return deviceInfo.release()->create(); +} + +QAudioDevice qMakeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline) +{ + auto deviceInfo = std::make_unique<QGStreamerCustomAudioDeviceInfo>(gstreamerPipeline, + QAudioDevice::Mode::Output); + + return deviceInfo.release()->create(); +} + +bool isCustomAudioDevice(const QAudioDevicePrivate *device) +{ + return dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(device); +} + +bool isCustomAudioDevice(const QAudioDevice &device) +{ + return isCustomAudioDevice(device.handle()); +} QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h index dee0c40bc..62dd896dd 100644 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h +++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h @@ -19,11 +19,11 @@ #include <QtCore/qstringlist.h> #include <QtCore/qlist.h> -#include "qaudio.h" -#include "qaudiodevice.h" -#include <private/qaudiodevice_p.h> +#include <QtMultimedia/qaudio.h> +#include <QtMultimedia/qaudiodevice.h> +#include <QtMultimedia/private/qaudiodevice_p.h> -#include <common/qgst_handle_types_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgst_handle_types_p.h> #include <gst/gst.h> @@ -33,11 +33,22 @@ class QGStreamerAudioDeviceInfo : public QAudioDevicePrivate { public: QGStreamerAudioDeviceInfo(GstDevice *gstDevice, const QByteArray &device, QAudioDevice::Mode mode); - ~QGStreamerAudioDeviceInfo(); QGstDeviceHandle gstDevice; }; +class QGStreamerCustomAudioDeviceInfo : public QAudioDevicePrivate +{ +public: + QGStreamerCustomAudioDeviceInfo(const QByteArray &gstreamerPipeline, QAudioDevice::Mode mode); +}; + +bool isCustomAudioDevice(const QAudioDevicePrivate *device); +bool isCustomAudioDevice(const QAudioDevice &device); + +QAudioDevice qMakeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline); +QAudioDevice qMakeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline); + QT_END_NAMESPACE #endif diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp deleted file mode 100644 index 6fd972524..000000000 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include <QtCore/qcoreapplication.h> -#include <QtCore/qdebug.h> -#include <QtCore/qmath.h> -#include <QtMultimedia/private/qaudiohelpers_p.h> - -#include <sys/types.h> -#include <unistd.h> - -#include <audio/qgstreameraudiosink_p.h> -#include <audio/qgstreameraudiodevice_p.h> -#include <common/qgst_debug_p.h> -#include <common/qgstappsource_p.h> -#include <common/qgstpipeline_p.h> -#include <common/qgstreamermessage_p.h> -#include <common/qgstutils_p.h> - -#include <utility> - -QT_BEGIN_NAMESPACE - -QMaybe<QPlatformAudioSink *> QGStreamerAudioSink::create(const QAudioDevice &device, QObject *parent) -{ - auto maybeAppSrc = QGstAppSource::create(); - if (!maybeAppSrc) - return maybeAppSrc.error(); - - QGstElement audioconvert = QGstElement::createFromFactory("audioconvert", "conv"); - if (!audioconvert) - return errorMessageCannotFindElement("audioconvert"); - - QGstElement volume = QGstElement::createFromFactory("volume", "volume"); - if (!volume) - return errorMessageCannotFindElement("volume"); - - return new QGStreamerAudioSink(device, maybeAppSrc.value(), audioconvert, volume, parent); -} - -QGStreamerAudioSink::QGStreamerAudioSink(const QAudioDevice &device, QGstAppSource *appsrc, - QGstElement audioconvert, QGstElement volume, - QObject *parent) - : QPlatformAudioSink(parent), - m_device(device.id()), - gstPipeline(QGstPipeline::create("audioSinkPipeline")), - gstVolume(std::move(volume)), - m_appSrc(appsrc) -{ - gstPipeline.installMessageFilter(this); - - connect(m_appSrc, &QGstAppSource::bytesProcessed, this, &QGStreamerAudioSink::bytesProcessedByAppSrc); - connect(m_appSrc, &QGstAppSource::noMoreData, this, &QGStreamerAudioSink::needData); - gstAppSrc = m_appSrc->element(); - - QGstElement queue = QGstElement::createFromFactory("queue", "audioSinkQueue"); - - if (m_volume != 1.) - gstVolume.set("volume", m_volume); - - // link decodeBin to audioconvert in a callback once we get a pad from the decoder - // g_signal_connect (gstDecodeBin, "pad-added", (GCallback) padAdded, conv); - - const auto *audioInfo = static_cast<const QGStreamerAudioDeviceInfo *>(device.handle()); - gstOutput = QGstElement::createFromDevice(audioInfo->gstDevice, nullptr); - - gstPipeline.add(gstAppSrc, queue, /*gstDecodeBin, */ audioconvert, gstVolume, gstOutput); - qLinkGstElements(gstAppSrc, queue, audioconvert, gstVolume, gstOutput); -} - -QGStreamerAudioSink::~QGStreamerAudioSink() -{ - close(); - gstPipeline.removeMessageFilter(this); - - gstPipeline = {}; - gstVolume = {}; - gstAppSrc = {}; - delete m_appSrc; - m_appSrc = nullptr; -} - -void QGStreamerAudioSink::setError(QAudio::Error error) -{ - if (m_errorState == error) - return; - - m_errorState = error; - emit errorChanged(error); -} - -QAudio::Error QGStreamerAudioSink::error() const -{ - return m_errorState; -} - -void QGStreamerAudioSink::setState(QAudio::State state) -{ - if (m_deviceState == state) - return; - - m_deviceState = state; - emit stateChanged(state); -} - -QAudio::State QGStreamerAudioSink::state() const -{ - return m_deviceState; -} - -void QGStreamerAudioSink::start(QIODevice *device) -{ - setState(QAudio::StoppedState); - setError(QAudio::NoError); - - close(); - - if (!m_format.isValid()) { - setError(QAudio::OpenError); - return; - } - - m_pullMode = true; - m_audioSource = device; - - if (!open()) { - m_audioSource = nullptr; - setError(QAudio::OpenError); - return; - } - - setState(QAudio::ActiveState); -} - -QIODevice *QGStreamerAudioSink::start() -{ - setState(QAudio::StoppedState); - setError(QAudio::NoError); - - close(); - - if (!m_format.isValid()) { - setError(QAudio::OpenError); - return nullptr; - } - - m_pullMode = false; - - if (!open()) - return nullptr; - - m_audioSource = new GStreamerOutputPrivate(this); - m_audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered); - - setState(QAudio::IdleState); - - return m_audioSource; -} - -#if 0 -static void padAdded(GstElement *element, GstPad *pad, gpointer data) -{ - GstElement *other = static_cast<GstElement *>(data); - - QGString name { gst_pad_get_name(pad)}; - qDebug("A new pad %s was created for %s\n", name, gst_element_get_name(element)); - - qDebug("element %s will be linked to %s\n", - gst_element_get_name(element), - gst_element_get_name(other)); - gst_element_link(element, other); -} -#endif - -bool QGStreamerAudioSink::processBusMessage(const QGstreamerMessage &message) -{ - auto *msg = message.message(); - switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_EOS: - setState(QAudio::IdleState); - break; - case GST_MESSAGE_ERROR: { - setError(QAudio::IOError); - qDebug() << "Error:" << QCompactGstMessageAdaptor(message); - break; - } - default: - break; - } - - return true; -} - -bool QGStreamerAudioSink::open() -{ - if (m_opened) - return true; - - if (gstOutput.isNull()) { - setError(QAudio::OpenError); - setState(QAudio::StoppedState); - return false; - } - -// qDebug() << "GST caps:" << gst_caps_to_string(caps); - m_appSrc->setup(m_audioSource, m_audioSource ? m_audioSource->pos() : 0); - m_appSrc->setAudioFormat(m_format); - - /* run */ - gstPipeline.setState(GST_STATE_PLAYING); - - m_opened = true; - - m_timeStamp.restart(); - m_bytesProcessed = 0; - - return true; -} - -void QGStreamerAudioSink::close() -{ - if (!m_opened) - return; - - if (!gstPipeline.setStateSync(GST_STATE_NULL)) - qWarning() << "failed to close the audio output stream"; - - if (!m_pullMode && m_audioSource) - delete m_audioSource; - m_audioSource = nullptr; - m_opened = false; -} - -qint64 QGStreamerAudioSink::write(const char *data, qint64 len) -{ - if (!len) - return 0; - if (m_errorState == QAudio::UnderrunError) - m_errorState = QAudio::NoError; - - m_appSrc->write(data, len); - return len; -} - -void QGStreamerAudioSink::stop() -{ - if (m_deviceState == QAudio::StoppedState) - return; - - close(); - - setError(QAudio::NoError); - setState(QAudio::StoppedState); -} - -qsizetype QGStreamerAudioSink::bytesFree() const -{ - if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) - return 0; - - return m_appSrc->canAcceptMoreData() ? 4096*4 : 0; -} - -void QGStreamerAudioSink::setBufferSize(qsizetype value) -{ - m_bufferSize = value; - if (!gstAppSrc.isNull()) - gst_app_src_set_max_bytes(GST_APP_SRC(gstAppSrc.element()), value); -} - -qsizetype QGStreamerAudioSink::bufferSize() const -{ - return m_bufferSize; -} - -qint64 QGStreamerAudioSink::processedUSecs() const -{ - qint64 result = qint64(1000000) * m_bytesProcessed / - m_format.bytesPerFrame() / - m_format.sampleRate(); - - return result; -} - -void QGStreamerAudioSink::resume() -{ - if (m_deviceState == QAudio::SuspendedState) { - m_appSrc->resume(); - gstPipeline.setState(GST_STATE_PLAYING); - - setState(m_suspendedInState); - setError(QAudio::NoError); - } -} - -void QGStreamerAudioSink::setFormat(const QAudioFormat &format) -{ - m_format = format; -} - -QAudioFormat QGStreamerAudioSink::format() const -{ - return m_format; -} - -void QGStreamerAudioSink::suspend() -{ - if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) { - m_suspendedInState = m_deviceState; - setError(QAudio::NoError); - setState(QAudio::SuspendedState); - - gstPipeline.setState(GST_STATE_PAUSED); - m_appSrc->suspend(); - // ### elapsed time - } -} - -void QGStreamerAudioSink::reset() -{ - stop(); -} - -GStreamerOutputPrivate::GStreamerOutputPrivate(QGStreamerAudioSink *audio) -{ - m_audioDevice = audio; -} - -qint64 GStreamerOutputPrivate::readData(char *data, qint64 len) -{ - Q_UNUSED(data); - Q_UNUSED(len); - - return 0; -} - -qint64 GStreamerOutputPrivate::writeData(const char *data, qint64 len) -{ - if (m_audioDevice->state() == QAudio::IdleState) - m_audioDevice->setState(QAudio::ActiveState); - return m_audioDevice->write(data, len); -} - -void QGStreamerAudioSink::setVolume(qreal vol) -{ - if (m_volume == vol) - return; - - m_volume = vol; - if (!gstVolume.isNull()) - gstVolume.set("volume", vol); -} - -qreal QGStreamerAudioSink::volume() const -{ - return m_volume; -} - -void QGStreamerAudioSink::bytesProcessedByAppSrc(int bytes) -{ - m_bytesProcessed += bytes; - setState(QAudio::ActiveState); - setError(QAudio::NoError); -} - -void QGStreamerAudioSink::needData() -{ - if (state() != QAudio::StoppedState && state() != QAudio::IdleState) { - setState(QAudio::IdleState); - setError(m_audioSource && m_audioSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError); - } -} - -QT_END_NAMESPACE - -#include "moc_qgstreameraudiosink_p.cpp" diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink_p.h deleted file mode 100644 index 1aadb2290..000000000 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink_p.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef QAUDIOOUTPUTGSTREAMER_H -#define QAUDIOOUTPUTGSTREAMER_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 <QtCore/qfile.h> -#include <QtCore/qtimer.h> -#include <QtCore/qstring.h> -#include <QtCore/qstringlist.h> -#include <QtCore/qelapsedtimer.h> -#include <QtCore/qiodevice.h> -#include <QtCore/private/qringbuffer_p.h> - -#include "qaudio.h" -#include "qaudiodevice.h" -#include <private/qaudiosystem_p.h> -#include <private/qmultimediautils_p.h> - -#include <common/qgst_p.h> -#include <common/qgstpipeline_p.h> - -QT_BEGIN_NAMESPACE - -class QGstAppSource; - -class QGStreamerAudioSink - : public QPlatformAudioSink, - public QGstreamerBusMessageFilter -{ - friend class GStreamerOutputPrivate; - Q_OBJECT - -public: - static QMaybe<QPlatformAudioSink *> create(const QAudioDevice &device, QObject *parent); - ~QGStreamerAudioSink(); - - void start(QIODevice *device) override; - QIODevice *start() override; - void stop() override; - void reset() override; - void suspend() override; - void resume() override; - qsizetype bytesFree() const override; - void setBufferSize(qsizetype value) override; - qsizetype bufferSize() const override; - qint64 processedUSecs() const override; - QAudio::Error error() const override; - QAudio::State state() const override; - void setFormat(const QAudioFormat &format) override; - QAudioFormat format() const override; - - void setVolume(qreal volume) override; - qreal volume() const override; - -private Q_SLOTS: - void bytesProcessedByAppSrc(int bytes); - void needData(); - -private: - QGStreamerAudioSink(const QAudioDevice &device, QGstAppSource *appsrc, QGstElement audioconvert, - QGstElement volume, QObject *parent); - - void setState(QAudio::State state); - void setError(QAudio::Error error); - - bool processBusMessage(const QGstreamerMessage &message) override; - - bool open(); - void close(); - qint64 write(const char *data, qint64 len); - -private: - QByteArray m_device; - QAudioFormat m_format; - QAudio::Error m_errorState = QAudio::NoError; - QAudio::State m_deviceState = QAudio::StoppedState; - QAudio::State m_suspendedInState = QAudio::SuspendedState; - bool m_pullMode = true; - bool m_opened = false; - QIODevice *m_audioSource = nullptr; - int m_bufferSize = 0; - qint64 m_bytesProcessed = 0; - QElapsedTimer m_timeStamp; - qreal m_volume = 1.; - QByteArray pushData; - - QGstPipeline gstPipeline; - QGstElement gstOutput; - QGstElement gstVolume; - QGstElement gstAppSrc; - QGstAppSource *m_appSrc = nullptr; -}; - -class GStreamerOutputPrivate : public QIODevice -{ - friend class QGStreamerAudioSink; - Q_OBJECT - -public: - explicit GStreamerOutputPrivate(QGStreamerAudioSink *audio); - -protected: - qint64 readData(char *data, qint64 len) override; - qint64 writeData(const char *data, qint64 len) override; - -private: - QGStreamerAudioSink *m_audioDevice; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp deleted file mode 100644 index 829d116a2..000000000 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource.cpp +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include <QtCore/qcoreapplication.h> -#include <QtCore/qdebug.h> -#include <QtCore/qmath.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> - -Q_DECLARE_OPAQUE_POINTER(GstSample *); -Q_DECLARE_METATYPE(GstSample *); - -QT_BEGIN_NAMESPACE - -QGStreamerAudioSource::QGStreamerAudioSource(const QAudioDevice &device, QObject *parent) - : QPlatformAudioSource(parent), - m_info(device), - m_device(device.id()) -{ - qRegisterMetaType<GstSample *>(); -} - -QGStreamerAudioSource::~QGStreamerAudioSource() -{ - close(); -} - -void QGStreamerAudioSource::setError(QAudio::Error error) -{ - if (m_errorState == error) - return; - - m_errorState = error; - emit errorChanged(error); -} - -QAudio::Error QGStreamerAudioSource::error() const -{ - return m_errorState; -} - -void QGStreamerAudioSource::setState(QAudio::State state) -{ - if (m_deviceState == state) - return; - - m_deviceState = state; - emit stateChanged(state); -} - -QAudio::State QGStreamerAudioSource::state() const -{ - return m_deviceState; -} - -void QGStreamerAudioSource::setFormat(const QAudioFormat &format) -{ - if (m_deviceState == QAudio::StoppedState) - m_format = format; -} - -QAudioFormat QGStreamerAudioSource::format() const -{ - return m_format; -} - -void QGStreamerAudioSource::start(QIODevice *device) -{ - setState(QAudio::StoppedState); - setError(QAudio::NoError); - - close(); - - if (!open()) - return; - - m_pullMode = true; - m_audioSink = device; - - setState(QAudio::ActiveState); -} - -QIODevice *QGStreamerAudioSource::start() -{ - setState(QAudio::StoppedState); - setError(QAudio::NoError); - - close(); - - if (!open()) - return nullptr; - - m_pullMode = false; - m_audioSink = new GStreamerInputPrivate(this); - m_audioSink->open(QIODevice::ReadOnly | QIODevice::Unbuffered); - - setState(QAudio::IdleState); - - return m_audioSink; -} - -void QGStreamerAudioSource::stop() -{ - if (m_deviceState == QAudio::StoppedState) - return; - - close(); - - setError(QAudio::NoError); - setState(QAudio::StoppedState); -} - -bool QGStreamerAudioSource::open() -{ - if (m_opened) - return true; - - const auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_info.handle()); - if (!deviceInfo->gstDevice) { - setError(QAudio::OpenError); - setState(QAudio::StoppedState); - return false; - } - - gstInput = QGstElement::createFromDevice(deviceInfo->gstDevice); - if (gstInput.isNull()) { - setError(QAudio::OpenError); - setState(QAudio::StoppedState); - return false; - } - - auto gstCaps = QGstUtils::capsForAudioFormat(m_format); - - if (gstCaps.isNull()) { - setError(QAudio::OpenError); - setState(QAudio::StoppedState); - return false; - } - - -#ifdef DEBUG_AUDIO - qDebug() << "Opening input" << QTime::currentTime(); - qDebug() << "Caps: " << gst_caps_to_string(gstCaps); -#endif - - gstPipeline = QGstPipeline::create("audioSourcePipeline"); - - auto *gstBus = gst_pipeline_get_bus(gstPipeline.pipeline()); - gst_bus_add_watch(gstBus, &QGStreamerAudioSource::busMessage, this); - gst_object_unref (gstBus); - - gstAppSink = createAppSink(); - gstAppSink.set("caps", gstCaps); - - QGstElement conv = QGstElement::createFromFactory("audioconvert", "conv"); - gstVolume = QGstElement::createFromFactory("volume", "volume"); - Q_ASSERT(gstVolume); - if (m_volume != 1.) - gstVolume.set("volume", m_volume); - - gstPipeline.add(gstInput, gstVolume, conv, gstAppSink); - qLinkGstElements(gstInput, gstVolume, conv, gstAppSink); - - gstPipeline.setState(GST_STATE_PLAYING); - - m_opened = true; - - m_timeStamp.restart(); - m_elapsedTimeOffset = 0; - m_bytesWritten = 0; - - return true; -} - -void QGStreamerAudioSource::close() -{ - if (!m_opened) - return; - - gstPipeline.setState(GST_STATE_NULL); - gstPipeline = {}; - gstVolume = {}; - gstAppSink = {}; - gstInput = {}; - - if (!m_pullMode && m_audioSink) { - delete m_audioSink; - } - m_audioSink = nullptr; - m_opened = false; -} - -gboolean QGStreamerAudioSource::busMessage(GstBus *, GstMessage *msg, gpointer user_data) -{ - QGStreamerAudioSource *input = static_cast<QGStreamerAudioSource *>(user_data); - switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_EOS: - input->stop(); - break; - case GST_MESSAGE_ERROR: { - input->setError(QAudio::IOError); - qDebug() << "Error:" << QCompactGstMessageAdaptor(msg); - break; - } - default: - break; - } - return false; -} - -qsizetype QGStreamerAudioSource::bytesReady() const -{ - return m_buffer.size(); -} - -void QGStreamerAudioSource::resume() -{ - if (m_deviceState == QAudio::SuspendedState || m_deviceState == QAudio::IdleState) { - gstPipeline.setState(GST_STATE_PLAYING); - setState(QAudio::ActiveState); - setError(QAudio::NoError); - } -} - -void QGStreamerAudioSource::setVolume(qreal vol) -{ - if (m_volume == vol) - return; - - m_volume = vol; - if (!gstVolume.isNull()) - gstVolume.set("volume", vol); -} - -qreal QGStreamerAudioSource::volume() const -{ - return m_volume; -} - -void QGStreamerAudioSource::setBufferSize(qsizetype value) -{ - m_bufferSize = value; -} - -qsizetype QGStreamerAudioSource::bufferSize() const -{ - return m_bufferSize; -} - -qint64 QGStreamerAudioSource::processedUSecs() const -{ - return m_format.durationForBytes(m_bytesWritten); -} - -void QGStreamerAudioSource::suspend() -{ - if (m_deviceState == QAudio::ActiveState) { - setError(QAudio::NoError); - setState(QAudio::SuspendedState); - - gstPipeline.setState(GST_STATE_PAUSED); - } -} - -void QGStreamerAudioSource::reset() -{ - stop(); - m_buffer.clear(); -} - -//#define MAX_BUFFERS_IN_QUEUE 4 - -QGstAppSink QGStreamerAudioSource::createAppSink() -{ - QGstAppSink sink = QGstAppSink::create("appsink"); - - GstAppSinkCallbacks callbacks{}; - callbacks.eos = eos; - callbacks.new_sample = new_sample; - sink.setCallbacks(callbacks, this, nullptr); - // gst_app_sink_set_max_buffers(sink.appSink(), MAX_BUFFERS_IN_QUEUE); - gst_base_sink_set_sync(sink.baseSink(), FALSE); - - return sink; -} - -void QGStreamerAudioSource::newDataAvailable(QGstSampleHandle sample) -{ - if (m_audioSink) { - 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; - gsize bufferSize = mapInfo.size; - - if (!m_pullMode) { - // need to store that data in the QBuffer - m_buffer.append(bufferData, bufferSize); - m_audioSink->readyRead(); - } else { - m_bytesWritten += bufferSize; - m_audioSink->write(bufferData, bufferSize); - } - - gst_buffer_unmap(buffer, &mapInfo); - } -} - -GstFlowReturn QGStreamerAudioSource::new_sample(GstAppSink *sink, gpointer user_data) -{ - // "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()." - QGStreamerAudioSource *control = static_cast<QGStreamerAudioSource*>(user_data); - - QGstSampleHandle sample{ - gst_app_sink_pull_sample(sink), - QGstSampleHandle::HasRef, - }; - - QMetaObject::invokeMethod(control, [control, sample = std::move(sample)]() mutable { - control->newDataAvailable(std::move(sample)); - }); - - return GST_FLOW_OK; -} - -void QGStreamerAudioSource::eos(GstAppSink *, gpointer user_data) -{ - QGStreamerAudioSource *control = static_cast<QGStreamerAudioSource*>(user_data); - control->setState(QAudio::StoppedState); -} - -GStreamerInputPrivate::GStreamerInputPrivate(QGStreamerAudioSource *audio) -{ - m_audioDevice = audio; -} - -qint64 GStreamerInputPrivate::readData(char *data, qint64 len) -{ - if (m_audioDevice->state() == QAudio::IdleState) - m_audioDevice->setState(QAudio::ActiveState); - qint64 bytes = m_audioDevice->m_buffer.read(data, len); - m_audioDevice->m_bytesWritten += bytes; - return bytes; -} - -qint64 GStreamerInputPrivate::writeData(const char *data, qint64 len) -{ - Q_UNUSED(data); - Q_UNUSED(len); - return 0; -} - -qint64 GStreamerInputPrivate::bytesAvailable() const -{ - return m_audioDevice->m_buffer.size(); -} - - -QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource_p.h deleted file mode 100644 index 9021f1ddd..000000000 --- a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosource_p.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QAUDIOINPUTGSTREAMER_H -#define QAUDIOINPUTGSTREAMER_H - -#include <QtCore/qfile.h> -#include <QtCore/qtimer.h> -#include <QtCore/qstring.h> -#include <QtCore/qstringlist.h> -#include <QtCore/qelapsedtimer.h> -#include <QtCore/qiodevice.h> -#include <QtCore/qmutex.h> -#include <QtCore/qatomic.h> -#include <QtCore/private/qringbuffer_p.h> - -#include "qaudio.h" -#include "qaudiodevice.h" -#include <private/qaudiosystem_p.h> - -#include <common/qgstutils_p.h> -#include <common/qgstpipeline_p.h> - -#include <gst/app/gstappsink.h> - -QT_BEGIN_NAMESPACE - -class GStreamerInputPrivate; - -class QGStreamerAudioSource - : public QPlatformAudioSource -{ - friend class GStreamerInputPrivate; -public: - QGStreamerAudioSource(const QAudioDevice &device, QObject *parent); - ~QGStreamerAudioSource(); - - void start(QIODevice *device) override; - QIODevice *start() override; - void stop() override; - void reset() override; - void suspend() override; - void resume() override; - qsizetype bytesReady() const override; - void setBufferSize(qsizetype value) override; - qsizetype bufferSize() const override; - qint64 processedUSecs() const override; - QAudio::Error error() const override; - QAudio::State state() const override; - void setFormat(const QAudioFormat &format) override; - QAudioFormat format() const override; - - void setVolume(qreal volume) override; - qreal volume() const override; - -private: - void setState(QAudio::State state); - void setError(QAudio::Error error); - - QGstAppSink createAppSink(); - static GstFlowReturn new_sample(GstAppSink *, gpointer user_data); - static void eos(GstAppSink *, gpointer user_data); - - bool open(); - void close(); - - static gboolean busMessage(GstBus *bus, GstMessage *msg, gpointer user_data); - - void newDataAvailable(QGstSampleHandle sample); - - QAudioDevice m_info; - qint64 m_bytesWritten = 0; - QIODevice *m_audioSink = nullptr; - QAudioFormat m_format; - QAudio::Error m_errorState = QAudio::NoError; - QAudio::State m_deviceState = QAudio::StoppedState; - qreal m_volume = 1.; - - QRingBuffer m_buffer; - QAtomicInteger<bool> m_pullMode = true; - bool m_opened = false; - int m_bufferSize = 0; - qint64 m_elapsedTimeOffset = 0; - QElapsedTimer m_timeStamp; - QByteArray m_device; - QByteArray m_tempBuffer; - - QGstElement gstInput; - QGstPipeline gstPipeline; - QGstElement gstVolume; - QGstAppSink gstAppSink; -}; - -class GStreamerInputPrivate : public QIODevice -{ -public: - explicit GStreamerInputPrivate(QGStreamerAudioSource *audio); - - qint64 readData(char *data, qint64 len) override; - qint64 writeData(const char *data, qint64 len) override; - qint64 bytesAvailable() const override; - bool isSequential() const override { return true; } -private: - QGStreamerAudioSource *m_audioDevice; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/multimedia/gstreamer/common/qglist_helper_p.h b/src/plugins/multimedia/gstreamer/common/qglist_helper_p.h index 54108e1c3..f5bf4b3aa 100644 --- a/src/plugins/multimedia/gstreamer/common/qglist_helper_p.h +++ b/src/plugins/multimedia/gstreamer/common/qglist_helper_p.h @@ -24,10 +24,12 @@ QT_BEGIN_NAMESPACE namespace QGstUtils { -template <typename ListType> +template <typename ListType, bool IsConst> struct GListIterator { - explicit GListIterator(const GList *element = nullptr) : element(element) { } + using GListType = std::conditional_t<IsConst, const GList *, GList *>; + + explicit GListIterator(GListType element = nullptr) : element(element) { } const ListType &operator*() const noexcept { return *operator->(); } const ListType *operator->() const noexcept @@ -59,22 +61,29 @@ struct GListIterator using reference = value_type &; using iterator_category = std::input_iterator_tag; - const GList *element = nullptr; + GListType element = nullptr; }; -template <typename ListType> -struct GListRangeAdaptor +template <typename ListType, bool IsConst> +struct GListRangeAdaptorImpl { static_assert(std::is_pointer_v<ListType>); - explicit GListRangeAdaptor(const GList *list) : head(list) { } + using GListType = std::conditional_t<IsConst, const GList *, GList *>; + + explicit GListRangeAdaptorImpl(GListType list) : head(list) { } - auto begin() { return GListIterator<ListType>(head); } - auto end() { return GListIterator<ListType>(nullptr); } + auto begin() { return GListIterator<ListType, IsConst>(head); } + auto end() { return GListIterator<ListType, IsConst>(nullptr); } - const GList *head; + GListType head; }; +template <typename ListType> +using GListRangeAdaptor = GListRangeAdaptorImpl<ListType, false>; +template <typename ListType> +using GListConstRangeAdaptor = GListRangeAdaptorImpl<ListType, true>; + } // namespace QGstUtils QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgst.cpp b/src/plugins/multimedia/gstreamer/common/qgst.cpp index 10268e495..8a041629d 100644 --- a/src/plugins/multimedia/gstreamer/common/qgst.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgst.cpp @@ -10,6 +10,7 @@ #include <QtMultimedia/qcameradevice.h> #include <array> +#include <thread> QT_BEGIN_NAMESPACE @@ -127,11 +128,11 @@ std::optional<QGRange<int>> QGValue::toIntRange() const return QGRange<int>{ gst_value_get_int_range_min(value), gst_value_get_int_range_max(value) }; } -QGstStructure QGValue::toStructure() const +QGstStructureView QGValue::toStructure() const { if (!value || !GST_VALUE_HOLDS_STRUCTURE(value)) - return QGstStructure(); - return QGstStructure(gst_value_get_structure(value)); + return QGstStructureView(nullptr); + return QGstStructureView(gst_value_get_structure(value)); } QGstCaps QGValue::toCaps() const @@ -156,38 +157,52 @@ QGValue QGValue::at(int index) const return QGValue{ gst_value_list_get_value(value, index) }; } -// QGstStructure +// QGstStructureView -QGstStructure::QGstStructure(const GstStructure *s) : structure(s) { } +QGstStructureView::QGstStructureView(const GstStructure *s) : structure(s) { } -void QGstStructure::free() +QGstStructureView::QGstStructureView(const QUniqueGstStructureHandle &handle) + : QGstStructureView{ handle.get() } { - if (structure) - gst_structure_free(const_cast<GstStructure *>(structure)); - structure = nullptr; } -bool QGstStructure::isNull() const +QUniqueGstStructureHandle QGstStructureView::clone() const +{ + return QUniqueGstStructureHandle{ gst_structure_copy(structure) }; +} + +bool QGstStructureView::isNull() const { return !structure; } -QByteArrayView QGstStructure::name() const +QByteArrayView QGstStructureView::name() const { return gst_structure_get_name(structure); } -QGValue QGstStructure::operator[](const char *name) const +QGValue QGstStructureView::operator[](const char *fieldname) const +{ + return QGValue{ gst_structure_get_value(structure, fieldname) }; +} + +QGstCaps QGstStructureView::caps() const { - return QGValue{ gst_structure_get_value(structure, name) }; + return operator[]("caps").toCaps(); } -QGstStructure QGstStructure::copy() const +QGstTagListHandle QGstStructureView::tags() const { - return gst_structure_copy(structure); + QGValue tags = operator[]("tags"); + if (tags.isNull()) + return {}; + + QGstTagListHandle tagList; + gst_structure_get(structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr); + return tagList; } -QSize QGstStructure::resolution() const +QSize QGstStructureView::resolution() const { QSize size; @@ -201,7 +216,7 @@ QSize QGstStructure::resolution() const return size; } -QVideoFrameFormat::PixelFormat QGstStructure::pixelFormat() const +QVideoFrameFormat::PixelFormat QGstStructureView::pixelFormat() const { QVideoFrameFormat::PixelFormat pixelFormat = QVideoFrameFormat::Format_Invalid; @@ -224,23 +239,23 @@ QVideoFrameFormat::PixelFormat QGstStructure::pixelFormat() const return pixelFormat; } -QGRange<float> QGstStructure::frameRateRange() const +QGRange<float> QGstStructureView::frameRateRange() const { - float minRate = 0.; - float maxRate = 0.; - if (!structure) return { 0.f, 0.f }; + std::optional<float> minRate; + std::optional<float> maxRate; + auto extractFraction = [](const GValue *v) -> float { return (float)gst_value_get_fraction_numerator(v) / (float)gst_value_get_fraction_denominator(v); }; auto extractFrameRate = [&](const GValue *v) { auto insert = [&](float min, float max) { - if (max > maxRate) + if (!maxRate || max > maxRate) maxRate = max; - if (min < minRate) + if (!minRate || min < minRate) minRate = min; }; @@ -248,8 +263,8 @@ QGRange<float> QGstStructure::frameRateRange() const float rate = extractFraction(v); insert(rate, rate); } else if (GST_VALUE_HOLDS_FRACTION_RANGE(v)) { - auto *min = gst_value_get_fraction_range_max(v); - auto *max = gst_value_get_fraction_range_max(v); + const GValue *min = gst_value_get_fraction_range_min(v); + const GValue *max = gst_value_get_fraction_range_max(v); insert(extractFraction(min), extractFraction(max)); } }; @@ -273,17 +288,49 @@ QGRange<float> QGstStructure::frameRateRange() const } } - return { minRate, maxRate }; + if (!minRate || !maxRate) + return { 0.f, 0.f }; + + return { + minRate.value_or(*maxRate), + maxRate.value_or(*minRate), + }; } -QGstreamerMessage QGstStructure::getMessage() +std::optional<QGRange<QSize>> QGstStructureView::resolutionRange() const +{ + if (!structure) + return std::nullopt; + + const GValue *width = gst_structure_get_value(structure, "width"); + const GValue *height = gst_structure_get_value(structure, "height"); + + if (!width || !height) + return std::nullopt; + + for (const GValue *v : { width, height }) + if (!GST_VALUE_HOLDS_INT_RANGE(v)) + return std::nullopt; + + int minWidth = gst_value_get_int_range_min(width); + int maxWidth = gst_value_get_int_range_max(width); + int minHeight = gst_value_get_int_range_min(height); + int maxHeight = gst_value_get_int_range_max(height); + + return QGRange<QSize>{ + QSize(minWidth, minHeight), + QSize(maxWidth, maxHeight), + }; +} + +QGstreamerMessage QGstStructureView::getMessage() { GstMessage *message = nullptr; gst_structure_get(structure, "message", GST_TYPE_MESSAGE, &message, nullptr); return QGstreamerMessage(message, QGstreamerMessage::HasRef); } -std::optional<Fraction> QGstStructure::pixelAspectRatio() const +std::optional<Fraction> QGstStructureView::pixelAspectRatio() const { gint numerator; gint denominator; @@ -297,7 +344,20 @@ std::optional<Fraction> QGstStructure::pixelAspectRatio() const return std::nullopt; } -QSize QGstStructure::nativeSize() const +// QTBUG-125249: gstreamer tries "to keep the input height (because of interlacing)". Can we align +// the behavior between gstreamer and ffmpeg? +static QSize qCalculateFrameSizeGStreamer(QSize resolution, Fraction par) +{ + if (par.numerator == par.denominator || par.numerator < 1 || par.denominator < 1) + return resolution; + + return QSize{ + resolution.width() * par.numerator / par.denominator, + resolution.height(), + }; +} + +QSize QGstStructureView::nativeSize() const { QSize size = resolution(); if (!size.isValid()) { @@ -307,7 +367,7 @@ QSize QGstStructure::nativeSize() const std::optional<Fraction> par = pixelAspectRatio(); if (par) - size = qCalculateFrameSize(size, *par); + size = qCalculateFrameSizeGStreamer(size, *par); return size; } @@ -505,9 +565,11 @@ int QGstCaps::size() const return int(gst_caps_get_size(get())); } -QGstStructure QGstCaps::at(int index) const +QGstStructureView QGstCaps::at(int index) const { - return gst_caps_get_structure(get(), index); + return QGstStructureView{ + gst_caps_get_structure(get(), index), + }; } GstCaps *QGstCaps::caps() const @@ -567,6 +629,11 @@ void QGstObject::set(const char *property, const QGstCaps &c) g_object_set(get(), property, c.caps(), nullptr); } +void QGstObject::set(const char *property, void *object, GDestroyNotify destroyFunction) +{ + g_object_set_data_full(qGstCheckedCast<GObject>(get()), property, object, destroyFunction); +} + QGString QGstObject::getString(const char *property) const { char *s = nullptr; @@ -574,11 +641,11 @@ QGString QGstObject::getString(const char *property) const return QGString(s); } -QGstStructure QGstObject::getStructure(const char *property) const +QGstStructureView QGstObject::getStructure(const char *property) const { GstStructure *s = nullptr; g_object_get(get(), property, &s, nullptr); - return QGstStructure(s); + return QGstStructureView(s); } bool QGstObject::getBool(const char *property) const @@ -630,13 +697,18 @@ double QGstObject::getDouble(const char *property) const return d; } -QGstObject QGstObject::getObject(const char *property) const +QGstObject QGstObject::getGstObject(const char *property) const { GstObject *o = nullptr; g_object_get(get(), property, &o, nullptr); return QGstObject(o, HasRef); } +void *QGstObject::getObject(const char *property) const +{ + return g_object_get_data(qGstCheckedCast<GObject>(get()), property); +} + QGObjectHandlerConnection QGstObject::connect(const char *name, GCallback callback, gpointer userData) { @@ -656,14 +728,23 @@ GType QGstObject::type() const return G_OBJECT_TYPE(get()); } +QLatin1StringView QGstObject::typeName() const +{ + return QLatin1StringView{ + g_type_name(type()), + }; +} + GstObject *QGstObject::object() const { return get(); } -const char *QGstObject::name() const +QLatin1StringView QGstObject::name() const { - return get() ? GST_OBJECT_NAME(get()) : "(null)"; + using namespace Qt::StringLiterals; + + return get() ? QLatin1StringView{ GST_OBJECT_NAME(get()) } : "(null)"_L1; } // QGObjectHandlerConnection @@ -731,6 +812,35 @@ QGstCaps QGstPad::queryCaps() const return QGstCaps(gst_pad_query_caps(pad(), nullptr), QGstCaps::HasRef); } +QGstTagListHandle QGstPad::tags() const +{ + QGstTagListHandle tagList; + g_object_get(object(), "tags", &tagList, nullptr); + return tagList; +} + +QGString QGstPad::streamId() const +{ + return QGString{ + gst_pad_get_stream_id(pad()), + }; +} + +std::optional<QPlatformMediaPlayer::TrackType> QGstPad::inferTrackTypeFromName() const +{ + using namespace Qt::Literals; + QLatin1StringView padName = name(); + + if (padName.startsWith("video_"_L1)) + return QPlatformMediaPlayer::TrackType::VideoStream; + if (padName.startsWith("audio_"_L1)) + return QPlatformMediaPlayer::TrackType::AudioStream; + if (padName.startsWith("text_"_L1)) + return QPlatformMediaPlayer::TrackType::SubtitleStream; + + return std::nullopt; +} + bool QGstPad::isLinked() const { return gst_pad_is_linked(pad()); @@ -776,6 +886,33 @@ bool QGstPad::sendEvent(GstEvent *event) return gst_pad_send_event(pad(), event); } +void QGstPad::sendFlushStartStop(bool resetTime) +{ + GstEvent *flushStart = gst_event_new_flush_start(); + gboolean ret = sendEvent(flushStart); + if (!ret) { + qWarning("failed to send flush-start event"); + return; + } + + GstEvent *flushStop = gst_event_new_flush_stop(resetTime); + ret = sendEvent(flushStop); + if (!ret) + qWarning("failed to send flush-stop event"); +} + +void QGstPad::sendFlushIfPaused() +{ + using namespace std::chrono_literals; + + GstState state = parent().state(1s); + + if (state != GST_STATE_PAUSED) + return; + + sendFlushStartStop(/*resetTime=*/true); +} + // QGstClock QGstClock::QGstClock(const QGstObject &o) @@ -831,6 +968,20 @@ QGstElement QGstElement::createFromFactory(const char *factory, const char *name }; } +QGstElement QGstElement::createFromFactory(GstElementFactory *factory, const char *name) +{ + return QGstElement{ + gst_element_factory_create(factory, name), + NeedsRef, + }; +} + +QGstElement QGstElement::createFromFactory(const QGstElementFactoryHandle &factory, + const char *name) +{ + return createFromFactory(factory.get(), name); +} + QGstElement QGstElement::createFromDevice(const QGstDeviceHandle &device, const char *name) { return createFromDevice(device.get(), name); @@ -844,6 +995,38 @@ QGstElement QGstElement::createFromDevice(GstDevice *device, const char *name) }; } +QGstElement QGstElement::createFromPipelineDescription(const char *str) +{ + QUniqueGErrorHandle error; + QGstElement element{ + gst_parse_launch(str, &error), + QGstElement::NeedsRef, + }; + + if (error) // error does not mean that the element could not be constructed + qWarning() << "gst_parse_launch error:" << error; + + return element; +} + +QGstElement QGstElement::createFromPipelineDescription(const QByteArray &str) +{ + return createFromPipelineDescription(str.constData()); +} + +QGstElementFactoryHandle QGstElement::findFactory(const char *name) +{ + return QGstElementFactoryHandle{ + gst_element_factory_find(name), + QGstElementFactoryHandle::HasRef, + }; +} + +QGstElementFactoryHandle QGstElement::findFactory(const QByteArray &name) +{ + return findFactory(name.constData()); +} + QGstPad QGstElement::staticPad(const char *name) const { return QGstPad(gst_element_get_static_pad(element(), name), HasRef); @@ -895,15 +1078,24 @@ GstStateChangeReturn QGstElement::setState(GstState state) bool QGstElement::setStateSync(GstState state, std::chrono::nanoseconds timeout) { + if (state == GST_STATE_NULL) { + // QTBUG-125251: when changing pipeline state too quickly between NULL->PAUSED->NULL there + // may be a pending task to activate pads while we try to switch to NULL. This can cause an + // assertion failure in gstreamer. we therefore finish the state change when called on a bin + // or pipeline. + if (qIsGstObjectOfType<GstBin>(element())) + finishStateChange(); + } + GstStateChangeReturn change = gst_element_set_state(element(), state); - if (change == GST_STATE_CHANGE_ASYNC) { + if (change == GST_STATE_CHANGE_ASYNC) change = gst_element_get_state(element(), nullptr, &state, timeout.count()); - } -#ifndef QT_NO_DEBUG - if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) + + if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) { qWarning() << "Could not change state of" << name() << "to" << state << change; -#endif - return change == GST_STATE_CHANGE_SUCCESS; + dumpPipelineGraph("setStateSyncFailure"); + } + return change == GST_STATE_CHANGE_SUCCESS || change == GST_STATE_CHANGE_NO_PREROLL; } bool QGstElement::syncStateWithParent() @@ -918,13 +1110,34 @@ bool QGstElement::finishStateChange(std::chrono::nanoseconds timeout) GstStateChangeReturn change = gst_element_get_state(element(), &state, &pending, timeout.count()); -#ifndef QT_NO_DEBUG - if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) + if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) { qWarning() << "Could not finish change state of" << name() << change << state << pending; -#endif + dumpPipelineGraph("finishStateChangeFailure"); + } return change == GST_STATE_CHANGE_SUCCESS; } +bool QGstElement::hasAsyncStateChange(std::chrono::nanoseconds timeout) const +{ + GstState state; + GstStateChangeReturn change = + gst_element_get_state(element(), &state, nullptr, timeout.count()); + return change == GST_STATE_CHANGE_ASYNC; +} + +bool QGstElement::waitForAsyncStateChangeComplete(std::chrono::nanoseconds timeout) const +{ + using namespace std::chrono_literals; + for (;;) { + if (!hasAsyncStateChange()) + return true; + timeout -= 10ms; + if (timeout < 0ms) + return false; + std::this_thread::sleep_for(10ms); + } +} + void QGstElement::lockState(bool locked) { gst_element_set_locked_state(element(), locked); @@ -945,6 +1158,64 @@ void QGstElement::sendEos() const sendEvent(gst_event_new_eos()); } +std::optional<std::chrono::nanoseconds> QGstElement::duration() const +{ + gint64 d; + if (!gst_element_query_duration(element(), GST_FORMAT_TIME, &d)) { + qDebug() << "QGstElement: failed to query duration"; + return std::nullopt; + } + return std::chrono::nanoseconds{ d }; +} + +std::optional<std::chrono::milliseconds> QGstElement::durationInMs() const +{ + using namespace std::chrono; + auto dur = duration(); + if (dur) + return round<milliseconds>(*dur); + return std::nullopt; +} + +std::optional<std::chrono::nanoseconds> QGstElement::position() const +{ + QGstQueryHandle &query = positionQuery(); + + gint64 pos; + if (gst_element_query(element(), query.get())) { + gst_query_parse_position(query.get(), nullptr, &pos); + return std::chrono::nanoseconds{ pos }; + } + + qDebug() << "QGstElement: failed to query position"; + return std::nullopt; +} + +std::optional<std::chrono::milliseconds> QGstElement::positionInMs() const +{ + using namespace std::chrono; + auto pos = position(); + if (pos) + return round<milliseconds>(*pos); + return std::nullopt; +} + +std::optional<bool> QGstElement::canSeek() const +{ + QGstQueryHandle query{ + gst_query_new_seeking(GST_FORMAT_TIME), + QGstQueryHandle::HasRef, + }; + gboolean canSeek = false; + gst_query_parse_seeking(query.get(), nullptr, &canSeek, nullptr, nullptr); + + if (gst_element_query(element(), query.get())) { + gst_query_parse_seeking(query.get(), nullptr, &canSeek, nullptr, nullptr); + return canSeek; + } + return std::nullopt; +} + GstClockTime QGstElement::baseTime() const { return gst_element_get_base_time(element()); @@ -985,6 +1256,27 @@ QGstPipeline QGstElement::getPipeline() const } } +void QGstElement::dumpPipelineGraph(const char *filename) const +{ + static const bool dumpEnabled = qEnvironmentVariableIsSet("GST_DEBUG_DUMP_DOT_DIR"); + if (dumpEnabled) { + QGstPipeline pipeline = getPipeline(); + if (pipeline) + pipeline.dumpGraph(filename); + } +} + +QGstQueryHandle &QGstElement::positionQuery() const +{ + if (Q_UNLIKELY(!m_positionQuery)) + m_positionQuery = QGstQueryHandle{ + gst_query_new_position(GST_FORMAT_TIME), + QGstQueryHandle::HasRef, + }; + + return m_positionQuery; +} + // QGstBin QGstBin QGstBin::create(const char *name) @@ -1002,6 +1294,36 @@ QGstBin QGstBin::createFromFactory(const char *factory, const char *name) }; } +QGstBin QGstBin::createFromPipelineDescription(const QByteArray &pipelineDescription, + const char *name, bool ghostUnlinkedPads) +{ + return createFromPipelineDescription(pipelineDescription.constData(), name, ghostUnlinkedPads); +} + +QGstBin QGstBin::createFromPipelineDescription(const char *pipelineDescription, const char *name, + bool ghostUnlinkedPads) +{ + QUniqueGErrorHandle error; + + GstElement *element = + gst_parse_bin_from_description_full(pipelineDescription, ghostUnlinkedPads, + /*context=*/nullptr, GST_PARSE_FLAG_NONE, &error); + + if (!element) { + qWarning() << "Failed to make element from pipeline description" << pipelineDescription + << error; + return QGstBin{}; + } + + if (name) + gst_element_set_name(element, name); + + return QGstBin{ + element, + NeedsRef, + }; +} + QGstBin::QGstBin(GstBin *bin, RefMode mode) : QGstElement{ qGstCheckedCast<GstElement>(bin), @@ -1030,17 +1352,12 @@ bool QGstBin::syncChildrenState() return gst_bin_sync_children_states(bin()); } -void QGstBin::dumpGraph(const char *fileNamePrefix) +void QGstBin::dumpGraph(const char *fileNamePrefix) const { if (isNull()) return; - GST_DEBUG_BIN_TO_DOT_FILE(bin(), - GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL - | GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE - | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS - | GST_DEBUG_GRAPH_SHOW_STATES), - fileNamePrefix); + GST_DEBUG_BIN_TO_DOT_FILE(bin(), GST_DEBUG_GRAPH_SHOW_VERBOSE, fileNamePrefix); } QGstElement QGstBin::findByName(const char *name) @@ -1051,6 +1368,11 @@ QGstElement QGstBin::findByName(const char *name) }; } +void QGstBin::recalculateLatency() +{ + gst_bin_recalculate_latency(bin()); +} + // QGstBaseSink QGstBaseSink::QGstBaseSink(GstBaseSink *element, RefMode mode) @@ -1061,6 +1383,11 @@ QGstBaseSink::QGstBaseSink(GstBaseSink *element, RefMode mode) { } +void QGstBaseSink::setSync(bool arg) +{ + gst_base_sink_set_sync(baseSink(), arg ? TRUE : FALSE); +} + GstBaseSink *QGstBaseSink::baseSink() const { return qGstCheckedCast<GstBaseSink>(element()); @@ -1081,8 +1408,6 @@ GstBaseSrc *QGstBaseSrc::baseSrc() const return qGstCheckedCast<GstBaseSrc>(element()); } -#if QT_CONFIG(gstreamer_app) - // QGstAppSink QGstAppSink::QGstAppSink(GstAppSink *element, RefMode mode) @@ -1107,6 +1432,18 @@ GstAppSink *QGstAppSink::appSink() const return qGstCheckedCast<GstAppSink>(element()); } +# if GST_CHECK_VERSION(1, 24, 0) +void QGstAppSink::setMaxBufferTime(std::chrono::nanoseconds ns) +{ + gst_app_sink_set_max_time(appSink(), qGstClockTimeFromChrono(ns)); +} +# endif + +void QGstAppSink::setMaxBuffers(int n) +{ + gst_app_sink_set_max_buffers(appSink(), n); +} + void QGstAppSink::setCaps(const QGstCaps &caps) { gst_app_sink_set_caps(appSink(), caps.caps()); @@ -1161,6 +1498,10 @@ GstFlowReturn QGstAppSrc::pushBuffer(GstBuffer *buffer) return gst_app_src_push_buffer(appSrc(), buffer); } -#endif +QString qGstErrorMessageCannotFindElement(std::string_view element) +{ + return QStringLiteral("Could not find the %1 GStreamer element") + .arg(QLatin1StringView(element)); +} QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgst_bus.cpp b/src/plugins/multimedia/gstreamer/common/qgst_bus.cpp new file mode 100644 index 000000000..537bf5c76 --- /dev/null +++ b/src/plugins/multimedia/gstreamer/common/qgst_bus.cpp @@ -0,0 +1,149 @@ +// 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 + +#include "qgst_bus_p.h" + +QT_BEGIN_NAMESPACE + +QGstBus::QGstBus(QGstBusHandle bus) + : QGstBusHandle{ + std::move(bus), + } +{ + if (!get()) + return; + + GPollFD pollFd{}; + gst_bus_get_pollfd(get(), &pollFd); + Q_ASSERT(pollFd.fd); + +#ifndef Q_OS_WIN + m_socketNotifier.setSocket(pollFd.fd); + + QObject::connect(&m_socketNotifier, &QSocketNotifier::activated, &m_socketNotifier, + [this](QSocketDescriptor, QSocketNotifier::Type) { + this->processAllPendingMessages(); + }); + + m_socketNotifier.setEnabled(true); +#else + m_socketNotifier.setHandle(reinterpret_cast<Qt::HANDLE>(pollFd.fd)); + + QObject::connect(&m_socketNotifier, &QWinEventNotifier::activated, &m_socketNotifier, + [this](QWinEventNotifier::HANDLE) { + this->processAllPendingMessages(); + }); + m_socketNotifier.setEnabled(true); +#endif + + gst_bus_set_sync_handler(get(), (GstBusSyncHandler)syncGstBusFilter, this, nullptr); +} + +QGstBus::QGstBus(GstBus *bus, QGstBusHandle::RefMode refmode) + : QGstBus{ + QGstBusHandle{ + bus, + refmode, + }, + } +{ +} + +QGstBus::~QGstBus() +{ + close(); +} + +void QGstBus::close() +{ + if (!get()) + return; + + gst_bus_set_sync_handler(get(), nullptr, nullptr, nullptr); + QGstBusHandle::close(); +} + +void QGstBus::installMessageFilter(QGstreamerSyncMessageFilter *filter) +{ + Q_ASSERT(filter); + QMutexLocker lock(&filterMutex); + if (!syncFilters.contains(filter)) + syncFilters.append(filter); +} + +void QGstBus::removeMessageFilter(QGstreamerSyncMessageFilter *filter) +{ + Q_ASSERT(filter); + QMutexLocker lock(&filterMutex); + syncFilters.removeAll(filter); +} + +void QGstBus::installMessageFilter(QGstreamerBusMessageFilter *filter) +{ + Q_ASSERT(filter); + if (!busFilters.contains(filter)) + busFilters.append(filter); +} + +void QGstBus::removeMessageFilter(QGstreamerBusMessageFilter *filter) +{ + Q_ASSERT(filter); + busFilters.removeAll(filter); +} + +bool QGstBus::processNextPendingMessage(GstMessageType type, + std::optional<std::chrono::nanoseconds> timeout) +{ + if (!get()) + return false; + + GstClockTime gstTimeout = [&]() -> GstClockTime { + if (!timeout) + return GST_CLOCK_TIME_NONE; // block forever + return timeout->count(); + }(); + + QGstreamerMessage message{ + gst_bus_timed_pop_filtered(get(), gstTimeout, type), + QGstreamerMessage::HasRef, + }; + if (!message) + return false; + + for (QGstreamerBusMessageFilter *filter : std::as_const(busFilters)) { + if (filter->processBusMessage(message)) + break; + } + + return true; +} + +void QGstBus::processAllPendingMessages() +{ + for (;;) { + bool messageHandled = processNextPendingMessage(GST_MESSAGE_ANY, std::chrono::nanoseconds{ 0 }); + + if (!messageHandled) + return; + } +} + +GstBusSyncReply QGstBus::syncGstBusFilter(GstBus *bus, GstMessage *message, QGstBus *self) +{ + if (!message) + return GST_BUS_PASS; + + QMutexLocker lock(&self->filterMutex); + Q_ASSERT(bus == self->get()); + + for (QGstreamerSyncMessageFilter *filter : std::as_const(self->syncFilters)) { + if (filter->processSyncMessage(QGstreamerMessage{ message, QGstreamerMessage::NeedsRef })) { + gst_message_unref(message); + return GST_BUS_DROP; + } + } + + return GST_BUS_PASS; +} + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgst_bus_p.h b/src/plugins/multimedia/gstreamer/common/qgst_bus_p.h new file mode 100644 index 000000000..a3137d9bf --- /dev/null +++ b/src/plugins/multimedia/gstreamer/common/qgst_bus_p.h @@ -0,0 +1,84 @@ +// 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 QGST_BUS_P_H +#define QGST_BUS_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 <QtCore/qsocketnotifier.h> +#include <QtCore/qwineventnotifier.h> +#include <QtCore/qmutex.h> + +#include "qgst_p.h" +#include "qgstreamermessage_p.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerSyncMessageFilter +{ +public: + // returns true if message was processed and should be dropped, false otherwise + virtual bool processSyncMessage(const QGstreamerMessage &message) = 0; +}; + +class QGstreamerBusMessageFilter +{ +public: + // returns true if message was processed and should be dropped, false otherwise + virtual bool processBusMessage(const QGstreamerMessage &message) = 0; +}; + +class QGstBus : private QGstBusHandle +{ +public: + using QGstBusHandle::get; + using QGstBusHandle::HasRef; + using QGstBusHandle::RefMode; + + explicit QGstBus(QGstBusHandle); + QGstBus(GstBus *, QGstBusHandle::RefMode); + + ~QGstBus(); + QGstBus(const QGstBus &) = delete; + QGstBus(QGstBus &&) = delete; + QGstBus &operator=(const QGstBus &) = delete; + QGstBus &operator=(QGstBus &&) = delete; + + void close(); + + void installMessageFilter(QGstreamerSyncMessageFilter *); + void removeMessageFilter(QGstreamerSyncMessageFilter *); + void installMessageFilter(QGstreamerBusMessageFilter *); + void removeMessageFilter(QGstreamerBusMessageFilter *); + + bool processNextPendingMessage(GstMessageType type = GST_MESSAGE_ANY, + std::optional<std::chrono::nanoseconds> timeout = {}); + +private: + void processAllPendingMessages(); + + static GstBusSyncReply syncGstBusFilter(GstBus *, GstMessage *, QGstBus *); + +#ifndef Q_OS_WIN + QSocketNotifier m_socketNotifier{ QSocketNotifier::Read }; +#else + QWinEventNotifier m_socketNotifier{}; +#endif + QMutex filterMutex; + QList<QGstreamerSyncMessageFilter *> syncFilters; + QList<QGstreamerBusMessageFilter *> busFilters; +}; + +QT_END_NAMESPACE + +#endif // QGST_BUS_P_H diff --git a/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp b/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp index ea749c817..da9ac3a74 100644 --- a/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgst_debug.cpp @@ -4,6 +4,8 @@ #include "qgst_debug_p.h" #include "qgstreamermessage_p.h" +#include <gst/gstclock.h> + QT_BEGIN_NAMESPACE // NOLINTBEGIN(performance-unnecessary-value-param) @@ -18,7 +20,7 @@ QDebug operator<<(QDebug dbg, const QGstCaps &caps) return dbg << caps.caps(); } -QDebug operator<<(QDebug dbg, const QGstStructure &structure) +QDebug operator<<(QDebug dbg, const QGstStructureView &structure) { return dbg << structure.structure; } @@ -43,6 +45,21 @@ QDebug operator<<(QDebug dbg, const QUniqueGStringHandle &handle) return dbg << handle.get(); } +QDebug operator<<(QDebug dbg, const QGstStreamCollectionHandle &handle) +{ + return dbg << handle.get(); +} + +QDebug operator<<(QDebug dbg, const QGstStreamHandle &handle) +{ + return dbg << handle.get(); +} + +QDebug operator<<(QDebug dbg, const QGstTagListHandle &handle) +{ + return dbg << handle.get(); +} + QDebug operator<<(QDebug dbg, const QGstElement &element) { return dbg << element.element(); @@ -155,20 +172,40 @@ QDebug operator<<(QDebug dbg, const GstDevice *device) return dbg; } +namespace { + +struct Timepoint +{ + explicit Timepoint(guint64 us) : ts{ us } { } + guint64 ts; +}; + +QDebug operator<<(QDebug dbg, Timepoint ts) +{ + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%" GST_TIME_FORMAT, GST_TIME_ARGS(ts.ts)); + dbg << buffer; + return dbg; +} + +} // namespace + QDebug operator<<(QDebug dbg, const GstMessage *msg) { QDebugStateSaver saver(dbg); dbg.nospace(); + dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg); + if (GST_MESSAGE_TIMESTAMP(msg) != 0xFFFFFFFFFFFFFFFF) + dbg << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg); + switch (msg->type) { case GST_MESSAGE_ERROR: { QUniqueGErrorHandle err; QGString debug; gst_message_parse_error(const_cast<GstMessage *>(msg), &err, &debug); - dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg) - << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg) << ", Error: " << err << " (" << debug - << ")"; + dbg << ", Error: " << err << " (" << debug << ")"; break; } @@ -177,9 +214,7 @@ QDebug operator<<(QDebug dbg, const GstMessage *msg) 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 << ")"; + dbg << ", Warning: " << err << " (" << debug << ")"; break; } @@ -188,9 +223,31 @@ QDebug operator<<(QDebug dbg, const GstMessage *msg) 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 - << ")"; + dbg << ", Info: " << err << " (" << debug << ")"; + break; + } + + case GST_MESSAGE_TAG: { + QGstTagListHandle tagList; + gst_message_parse_tag(const_cast<GstMessage *>(msg), &tagList); + + dbg << ", Tags: " << tagList; + break; + } + + case GST_MESSAGE_QOS: { + gboolean live; + guint64 running_time; + guint64 stream_time; + guint64 timestamp; + guint64 duration; + + gst_message_parse_qos(const_cast<GstMessage *>(msg), &live, &running_time, &stream_time, + ×tamp, &duration); + + dbg << ", Live: " << bool(live) << ", Running time: " << Timepoint{ running_time } + << ", Stream time: " << Timepoint{ stream_time } + << ", Timestamp: " << Timepoint{ timestamp } << ", Duration: " << Timepoint{ duration }; break; } @@ -202,23 +259,81 @@ QDebug operator<<(QDebug dbg, const GstMessage *msg) 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; + dbg << ", Transition: " << oldState << "->" << newState; + + if (pending != GST_STATE_VOID_PENDING) + dbg << ", Pending State: " << pending; break; } - default: { - dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg) - << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg); + case GST_MESSAGE_STREAM_COLLECTION: { + QGstStreamCollectionHandle collection; + gst_message_parse_stream_collection(const_cast<GstMessage *>(msg), &collection); + + dbg << ", " << collection; + break; } + + case GST_MESSAGE_STREAMS_SELECTED: { + QGstStreamCollectionHandle collection; + gst_message_parse_streams_selected(const_cast<GstMessage *>(msg), &collection); + + dbg << ", " << collection; + break; + } + + case GST_MESSAGE_STREAM_STATUS: { + GstStreamStatusType streamStatus; + gst_message_parse_stream_status(const_cast<GstMessage *>(msg), &streamStatus, nullptr); + + dbg << ", Stream Status: " << streamStatus; + break; + } + + case GST_MESSAGE_BUFFERING: { + int progress = 0; + gst_message_parse_buffering(const_cast<GstMessage *>(msg), &progress); + + dbg << ", Buffering: " << progress << "%"; + break; + } + + case GST_MESSAGE_SEGMENT_START: { + gint64 pos; + GstFormat fmt{}; + gst_message_parse_segment_start(const_cast<GstMessage *>(msg), &fmt, &pos); + + switch (fmt) { + case GST_FORMAT_TIME: { + dbg << ", Position: " << std::chrono::nanoseconds{ pos }.count(); + break; + } + case GST_FORMAT_BYTES: { + dbg << ", Position: " << pos << "Bytes"; + break; + } + default: { + dbg << ", Position: " << pos; + break; + } + } + + break; + } + + default: + break; } return dbg; } QDebug operator<<(QDebug dbg, const GstTagList *tagList) { - dbg << QGString{ gst_tag_list_to_string(tagList) }; + if (tagList) + dbg << QGString{ gst_tag_list_to_string(tagList) }; + else + dbg << "NULL"; + return dbg; } @@ -244,6 +359,34 @@ QDebug operator<<(QDebug dbg, const GstPadTemplate *padTemplate) return dbg; } +QDebug operator<<(QDebug dbg, const GstStreamCollection *streamCollection) +{ + QDebugStateSaver saver(dbg); + dbg.nospace(); + + GstStreamCollection *collection = const_cast<GstStreamCollection *>(streamCollection); + dbg << "Stream Collection: {"; + + qForeachStreamInCollection(collection, [&](GstStream *stream) { + dbg << stream << ", "; + }); + + dbg << "}"; + return dbg; +} + +QDebug operator<<(QDebug dbg, const GstStream *cstream) +{ + GstStream *stream = const_cast<GstStream *>(cstream); + + QDebugStateSaver saver(dbg); + dbg.nospace(); + + dbg << gst_stream_get_stream_id(stream) << " (" << gst_stream_get_stream_type(stream) << ")"; + + return dbg; +} + QDebug operator<<(QDebug dbg, GstState state) { return dbg << gst_element_state_get_name(state); @@ -264,19 +407,46 @@ QDebug operator<<(QDebug dbg, GstMessageType type) return dbg << gst_message_type_get_name(type); } +#define ADD_ENUM_SWITCH(value) \ + case value: \ + return dbg << #value; \ + static_assert(true, "enforce semicolon") + QDebug operator<<(QDebug dbg, GstPadDirection direction) { switch (direction) { - case GST_PAD_UNKNOWN: - return dbg << "GST_PAD_UNKNOWN"; - case GST_PAD_SRC: - return dbg << "GST_PAD_SRC"; - case GST_PAD_SINK: - return dbg << "GST_PAD_SINK"; + ADD_ENUM_SWITCH(GST_PAD_UNKNOWN); + ADD_ENUM_SWITCH(GST_PAD_SRC); + ADD_ENUM_SWITCH(GST_PAD_SINK); + default: + Q_UNREACHABLE_RETURN(dbg); + } +} + +QDebug operator<<(QDebug dbg, GstStreamStatusType type) +{ + switch (type) { + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_CREATE); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_ENTER); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_LEAVE); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_DESTROY); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_START); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_PAUSE); + ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_STOP); + default: + Q_UNREACHABLE_RETURN(dbg); } return dbg; } +#undef ADD_ENUM_SWITCH + +QDebug operator<<(QDebug dbg, GstStreamType streamType) +{ + dbg << gst_stream_type_get_name(streamType); + return dbg; +} + QDebug operator<<(QDebug dbg, const GValue *value) { switch (G_VALUE_TYPE(value)) { @@ -404,7 +574,9 @@ QDebug operator<<(QDebug dbg, const QCompactGstMessageAdaptor &m) gst_message_parse_state_changed(m.msg, &oldState, &newState, &pending); - dbg << oldState << "->" << newState << "(pending: " << pending << ")"; + dbg << oldState << " -> " << newState; + if (pending != GST_STATE_VOID_PENDING) + dbg << " (pending: " << pending << ")"; return dbg; } diff --git a/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h b/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h index 31c722a90..222b2ef04 100644 --- a/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgst_debug_p.h @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE class QGstreamerMessage; QDebug operator<<(QDebug, const QGstCaps &); -QDebug operator<<(QDebug, const QGstStructure &); +QDebug operator<<(QDebug, const QGstStructureView &); QDebug operator<<(QDebug, const QGstElement &); QDebug operator<<(QDebug, const QGstPad &); QDebug operator<<(QDebug, const QGString &); @@ -31,6 +31,9 @@ QDebug operator<<(QDebug, const QGValue &); QDebug operator<<(QDebug, const QGstreamerMessage &); QDebug operator<<(QDebug, const QUniqueGErrorHandle &); QDebug operator<<(QDebug, const QUniqueGStringHandle &); +QDebug operator<<(QDebug, const QGstStreamCollectionHandle &); +QDebug operator<<(QDebug, const QGstStreamHandle &); +QDebug operator<<(QDebug, const QGstTagListHandle &); QDebug operator<<(QDebug, const GstCaps *); QDebug operator<<(QDebug, const GstVideoInfo *); @@ -44,12 +47,16 @@ QDebug operator<<(QDebug, const GstTagList *); QDebug operator<<(QDebug, const GstQuery *); QDebug operator<<(QDebug, const GstEvent *); QDebug operator<<(QDebug, const GstPadTemplate *); +QDebug operator<<(QDebug, const GstStreamCollection *); +QDebug operator<<(QDebug, const GstStream *); QDebug operator<<(QDebug, GstState); QDebug operator<<(QDebug, GstStateChange); QDebug operator<<(QDebug, GstStateChangeReturn); QDebug operator<<(QDebug, GstMessageType); QDebug operator<<(QDebug, GstPadDirection); +QDebug operator<<(QDebug, GstStreamStatusType); +QDebug operator<<(QDebug, GstStreamType); QDebug operator<<(QDebug, const GValue *); QDebug operator<<(QDebug, const GError *); 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 6628f7378..0e0d47dad 100644 --- a/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgst_handle_types_p.h @@ -163,6 +163,28 @@ struct QUniqueGErrorHandleTraits } }; +struct QUniqueGDateHandleTraits +{ + using Type = GDate *; + static constexpr Type invalidValue() noexcept { return nullptr; } + static bool close(Type handle) noexcept + { + g_date_free(handle); + return true; + } +}; + +struct QUniqueGstDateTimeHandleTraits +{ + using Type = GstDateTime *; + static constexpr Type invalidValue() noexcept { return nullptr; } + static bool close(Type handle) noexcept + { + gst_date_time_unref(handle); + return true; + } +}; + struct QFileDescriptorHandleTraits { using Type = int; @@ -213,7 +235,8 @@ struct QGstMiniObjectHandleHelper static Type ref(Type handle) noexcept { - gst_mini_object_ref(GST_MINI_OBJECT_CAST(handle)); + if (GST_MINI_OBJECT_CAST(handle)) + gst_mini_object_ref(GST_MINI_OBJECT_CAST(handle)); return handle; } }; @@ -226,10 +249,12 @@ struct QGstMiniObjectHandleHelper using QGstClockHandle = QGstImpl::QGstHandleHelper<GstClock>::UniqueHandle; using QGstElementHandle = QGstImpl::QGstHandleHelper<GstElement>::UniqueHandle; -using QGstElementFactoryHandle = QGstImpl::QGstHandleHelper<GstElementFactory>::UniqueHandle; +using QGstElementFactoryHandle = QGstImpl::QGstHandleHelper<GstElementFactory>::SharedHandle; using QGstDeviceHandle = QGstImpl::QGstHandleHelper<GstDevice>::SharedHandle; using QGstDeviceMonitorHandle = QGstImpl::QGstHandleHelper<GstDeviceMonitor>::UniqueHandle; -using QGstBusHandle = QGstImpl::QGstHandleHelper<GstBus>::UniqueHandle; +using QGstBusHandle = QGstImpl::QGstHandleHelper<GstBus>::SharedHandle; +using QGstStreamCollectionHandle = QGstImpl::QGstHandleHelper<GstStreamCollection>::SharedHandle; +using QGstStreamHandle = QGstImpl::QGstHandleHelper<GstStream>::SharedHandle; using QGstTagListHandle = QGstImpl::QSharedHandle<QGstImpl::QGstTagListHandleTraits>; using QGstSampleHandle = QGstImpl::QSharedHandle<QGstImpl::QGstSampleHandleTraits>; @@ -237,10 +262,14 @@ using QGstSampleHandle = QGstImpl::QSharedHandle<QGstImpl::QGstSampleHandleTrait using QUniqueGstStructureHandle = QUniqueHandle<QGstImpl::QUniqueGstStructureHandleTraits>; using QUniqueGStringHandle = QUniqueHandle<QGstImpl::QUniqueGStringHandleTraits>; using QUniqueGErrorHandle = QUniqueHandle<QGstImpl::QUniqueGErrorHandleTraits>; +using QUniqueGDateHandle = QUniqueHandle<QGstImpl::QUniqueGDateHandleTraits>; +using QUniqueGstDateTimeHandle = QUniqueHandle<QGstImpl::QUniqueGstDateTimeHandleTraits>; 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; +using QGstQueryHandle = QGstImpl::QGstMiniObjectHandleHelper<GstQuery>::SharedHandle; #if QT_CONFIG(gstreamer_gl) using QGstGLContextHandle = QGstImpl::QGstHandleHelper<GstGLContext>::UniqueHandle; diff --git a/src/plugins/multimedia/gstreamer/common/qgst_p.h b/src/plugins/multimedia/gstreamer/common/qgst_p.h index e52dcb58f..73a6fb556 100644 --- a/src/plugins/multimedia/gstreamer/common/qgst_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgst_p.h @@ -23,8 +23,11 @@ #include <QtMultimedia/qvideoframe.h> #include <QtMultimedia/private/qtmultimediaglobal_p.h> #include <QtMultimedia/private/qmultimediautils_p.h> +#include <QtMultimedia/private/qplatformmediaplayer_p.h> #include <gst/gst.h> +#include <gst/app/gstappsink.h> +#include <gst/app/gstappsrc.h> #include <gst/video/video-info.h> #include "qgst_handle_types_p.h" @@ -37,10 +40,6 @@ # undef GST_USE_UNSTABLE_API #endif -#if QT_CONFIG(gstreamer_app) -# include <gst/app/gstappsink.h> -# include <gst/app/gstappsrc.h> -#endif QT_BEGIN_NAMESPACE @@ -79,6 +78,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); @@ -87,11 +109,11 @@ QGST_DEFINE_CAST_TRAITS(GstPad, PAD); QGST_DEFINE_CAST_TRAITS(GstPipeline, PIPELINE); QGST_DEFINE_CAST_TRAITS(GstBaseSink, BASE_SINK); QGST_DEFINE_CAST_TRAITS(GstBaseSrc, BASE_SRC); - -#if QT_CONFIG(gstreamer_app) QGST_DEFINE_CAST_TRAITS(GstAppSink, APP_SINK); QGST_DEFINE_CAST_TRAITS(GstAppSrc, APP_SRC); -#endif + +QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE(GstTagSetter, TAG_SETTER); + template <> struct GstObjectTraits<GObject> @@ -115,10 +137,18 @@ struct GstObjectTraits<GObject> }; #undef QGST_DEFINE_CAST_TRAITS +#undef QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE } // namespace QGstImpl template <typename DestinationType, typename SourceType> +bool qIsGstObjectOfType(SourceType *arg) +{ + using Traits = QGstImpl::GstObjectTraits<DestinationType>; + return arg && Traits::isObjectOfType(arg); +} + +template <typename DestinationType, typename SourceType> DestinationType *qGstSafeCast(SourceType *arg) { using Traits = QGstImpl::GstObjectTraits<DestinationType>; @@ -137,7 +167,7 @@ DestinationType *qGstCheckedCast(SourceType *arg) } class QSize; -class QGstStructure; +class QGstStructureView; class QGstCaps; class QGstPipelinePrivate; class QCameraFormat; @@ -146,6 +176,15 @@ template <typename T> struct QGRange { T min; T max; + +#ifdef __cpp_impl_three_way_comparison + auto operator<=> (const QGRange &) const = default; +#else + bool operator==(const QGRange &rhs) const + { + return std::tie(min, max) == std::tie(rhs.min, rhs.max); + } +#endif }; struct QGString : QUniqueGStringHandle @@ -153,7 +192,47 @@ struct QGString : QUniqueGStringHandle using QUniqueGStringHandle::QUniqueGStringHandle; QLatin1StringView asStringView() const { return QLatin1StringView{ get() }; } + QByteArrayView asByteArrayView() const { return QByteArrayView{ get() }; } QString toQString() const { return QString::fromUtf8(get()); } + + bool operator==(const QGString &str) const { return asStringView() == str.asStringView(); } + bool operator==(const QLatin1StringView str) const { return asStringView() == str; } + bool operator==(const QByteArrayView str) const { return asByteArrayView() == str; } + + bool operator!=(const QGString &str) const { return asStringView() != str.asStringView(); } + bool operator!=(const QLatin1StringView str) const { return asStringView() != str; } + bool operator!=(const QByteArrayView str) const { return asByteArrayView() != str; } + + friend bool operator<(const QGString &lhs, const QGString &rhs) + { + return lhs.asStringView() < rhs.asStringView(); + } + friend bool operator<(const QGString &lhs, const QLatin1StringView rhs) + { + return lhs.asStringView() < rhs; + } + friend bool operator<(const QGString &lhs, const QByteArrayView rhs) + { + return lhs.asByteArrayView() < rhs; + } + friend bool operator<(const QLatin1StringView lhs, const QGString &rhs) + { + return lhs < rhs.asStringView(); + } + friend bool operator<(const QByteArrayView lhs, const QGString &rhs) + { + return lhs < rhs.asByteArrayView(); + } + + explicit operator QByteArrayView() const { return asByteArrayView(); } + explicit operator QByteArray() const + { + QByteArrayView view{ asByteArrayView() }; + return QByteArray{ + view.data(), + view.size(), + }; + } }; class QGValue @@ -178,7 +257,7 @@ public: std::optional<QGRange<float>> getFractionRange() const; std::optional<QGRange<int>> toIntRange() const; - QGstStructure toStructure() const; + QGstStructureView toStructure() const; QGstCaps toCaps() const; bool isList() const; @@ -276,27 +355,29 @@ protected: class QGstreamerMessage; -class QGstStructure +class QGstStructureView { public: const GstStructure *structure = nullptr; - QGstStructure() = default; - QGstStructure(const GstStructure *s); - void free(); + explicit QGstStructureView(const GstStructure *); + explicit QGstStructureView(const QUniqueGstStructureHandle &); - bool isNull() const; + QUniqueGstStructureHandle clone() const; + bool isNull() const; QByteArrayView name() const; - QGValue operator[](const char *name) const; + QGValue operator[](const char *fieldname) const; + + QGstCaps caps() const; + QGstTagListHandle tags() const; QSize resolution() const; QVideoFrameFormat::PixelFormat pixelFormat() const; QGRange<float> frameRateRange() const; + std::optional<QGRange<QSize>> resolutionRange() const; QGstreamerMessage getMessage(); std::optional<Fraction> pixelAspectRatio() const; QSize nativeSize() const; - - QGstStructure copy() const; }; template <> @@ -320,7 +401,7 @@ public: enum MemoryFormat { CpuMemory, GLTexture, DMABuf }; int size() const; - QGstStructure at(int index) const; + QGstStructureView at(int index) const; GstCaps *caps() const; MemoryFormat memoryFormat() const; @@ -366,9 +447,28 @@ public: void set(const char *property, double d); void set(const char *property, const QGstObject &o); void set(const char *property, const QGstCaps &c); + void set(const char *property, void *object, GDestroyNotify destroyFunction); + + template <typename Object> + void set(const char *property, Object *object, GDestroyNotify destroyFunction) + { + set(property, static_cast<void *>(object), destroyFunction); + } + + template <typename Object> + void set(const char *property, std::unique_ptr<Object> object) + { + set(property, static_cast<void *>(object.release()), qDeleteFromVoidPointer<Object>); + } + + template <typename T> + static void qDeleteFromVoidPointer(void *ptr) + { + delete reinterpret_cast<T *>(ptr); + } QGString getString(const char *property) const; - QGstStructure getStructure(const char *property) const; + QGstStructureView getStructure(const char *property) const; bool getBool(const char *property) const; uint getUInt(const char *property) const; int getInt(const char *property) const; @@ -376,14 +476,23 @@ public: qint64 getInt64(const char *property) const; float getFloat(const char *property) const; double getDouble(const char *property) const; - QGstObject getObject(const char *property) const; + QGstObject getGstObject(const char *property) const; + void *getObject(const char *property) const; + + template <typename T> + T *getObject(const char *property) const + { + void *rawObject = getObject(property); + return reinterpret_cast<T *>(rawObject); + } QGObjectHandlerConnection connect(const char *name, GCallback callback, gpointer userData); void disconnect(gulong handlerId); GType type() const; + QLatin1StringView typeName() const; GstObject *object() const; - const char *name() const; + QLatin1StringView name() const; }; class QGObjectHandlerConnection @@ -444,6 +553,12 @@ public: QGstCaps currentCaps() const; QGstCaps queryCaps() const; + QGstTagListHandle tags() const; + QGString streamId() const; + + std::optional<QPlatformMediaPlayer::TrackType> + inferTrackTypeFromName() const; // for decodebin3 etc + bool isLinked() const; bool link(const QGstPad &sink) const; bool unlink(const QGstPad &sink) const; @@ -455,6 +570,7 @@ public: GstEvent *stickyEvent(GstEventType type); bool sendEvent(GstEvent *event); + void sendFlushStartStop(bool resetTime); template<auto Member, typename T> void addProbe(T *instance, GstPadProbeType type) { @@ -500,6 +616,11 @@ public: gst_pad_add_probe(pad(), GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, callback, instance, nullptr); } + + template <typename Functor> + void modifyPipelineInIdleProbe(Functor &&f); + + void sendFlushIfPaused(); }; class QGstClock : public QGstObject @@ -527,8 +648,16 @@ public: explicit QGstElement(GstElement *element, RefMode mode); static QGstElement createFromFactory(const char *factory, const char *name = nullptr); + static QGstElement createFromFactory(GstElementFactory *, const char *name = nullptr); + static QGstElement createFromFactory(const QGstElementFactoryHandle &, + const char *name = nullptr); static QGstElement createFromDevice(const QGstDeviceHandle &, const char *name = nullptr); static QGstElement createFromDevice(GstDevice *, const char *name = nullptr); + static QGstElement createFromPipelineDescription(const char *); + static QGstElement createFromPipelineDescription(const QByteArray &); + + static QGstElementFactoryHandle findFactory(const char *); + static QGstElementFactoryHandle findFactory(const QByteArray &name); QGstPad staticPad(const char *name) const; QGstPad src() const; @@ -541,6 +670,9 @@ public: bool setStateSync(GstState state, std::chrono::nanoseconds timeout = std::chrono::seconds(1)); bool syncStateWithParent(); bool finishStateChange(std::chrono::nanoseconds timeout = std::chrono::seconds(5)); + bool hasAsyncStateChange(std::chrono::nanoseconds timeout = std::chrono::seconds(0)) const; + bool waitForAsyncStateChangeComplete( + std::chrono::nanoseconds timeout = std::chrono::seconds(5)) const; void lockState(bool locked); bool isStateLocked() const; @@ -548,6 +680,12 @@ public: void sendEvent(GstEvent *event) const; void sendEos() const; + std::optional<std::chrono::nanoseconds> duration() const; + std::optional<std::chrono::milliseconds> durationInMs() const; + std::optional<std::chrono::nanoseconds> position() const; + std::optional<std::chrono::milliseconds> positionInMs() const; + std::optional<bool> canSeek() const; + template <auto Member, typename T> QGObjectHandlerConnection onPadAdded(T *instance) { @@ -597,8 +735,42 @@ public: QGstElement getParent() const; QGstPipeline getPipeline() const; + void dumpPipelineGraph(const char *filename) const; + +private: + QGstQueryHandle &positionQuery() const; + mutable QGstQueryHandle m_positionQuery; }; +template <typename Functor> +void QGstPad::modifyPipelineInIdleProbe(Functor &&f) +{ + using namespace std::chrono_literals; + + GstPadDirection direction = gst_pad_get_direction(pad()); + + switch (direction) { + case GstPadDirection::GST_PAD_SINK: { + // modifying a source: we need to flush the sink pad before we can modify downstream + // elements + sendFlushIfPaused(); + doInIdleProbe(f); + return; + } + case GstPadDirection::GST_PAD_SRC: { + // modifying a sink: we need to use the idle probes iff the pipeline is playing + if (parent().state(1s) == GstState::GST_STATE_PLAYING) + doInIdleProbe(f); + else + f(); + return; + } + + default: + Q_UNREACHABLE(); + } +} + template <typename... Ts> std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> qLinkGstElements(const Ts &...ts) @@ -640,6 +812,12 @@ public: explicit QGstBin(GstBin *bin, RefMode mode = NeedsRef); static QGstBin create(const char *name); static QGstBin createFromFactory(const char *factory, const char *name); + static QGstBin createFromPipelineDescription(const QByteArray &pipelineDescription, + const char *name = nullptr, + bool ghostUnlinkedPads = false); + static QGstBin createFromPipelineDescription(const char *pipelineDescription, + const char *name = nullptr, + bool ghostUnlinkedPads = false); template <typename... Ts> std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> add(const Ts &...ts) @@ -675,9 +853,11 @@ public: bool syncChildrenState(); - void dumpGraph(const char *fileNamePrefix); + void dumpGraph(const char *fileNamePrefix) const; QGstElement findByName(const char *); + + void recalculateLatency(); }; class QGstBaseSink : public QGstElement @@ -692,6 +872,8 @@ public: QGstBaseSink &operator=(const QGstBaseSink &) = default; QGstBaseSink &operator=(QGstBaseSink &&) noexcept = default; + void setSync(bool); + GstBaseSink *baseSink() const; }; @@ -710,7 +892,6 @@ public: GstBaseSrc *baseSrc() const; }; -#if QT_CONFIG(gstreamer_app) class QGstAppSink : public QGstBaseSink { public: @@ -727,6 +908,11 @@ public: GstAppSink *appSink() const; + void setMaxBuffers(int); +# if GST_CHECK_VERSION(1, 24, 0) + void setMaxBufferTime(std::chrono::nanoseconds); +# endif + void setCaps(const QGstCaps &caps); void setCallbacks(GstAppSinkCallbacks &callbacks, gpointer user_data, GDestroyNotify notify); @@ -754,14 +940,54 @@ public: GstFlowReturn pushBuffer(GstBuffer *); // take ownership }; -#endif +inline GstClockTime qGstClockTimeFromChrono(std::chrono::nanoseconds ns) +{ + return ns.count(); +} + +QString qGstErrorMessageCannotFindElement(std::string_view element); + +template <typename Arg, typename... Args> +std::optional<QString> qGstErrorMessageIfElementsNotAvailable(const Arg &arg, Args... args) +{ + QGstElementFactoryHandle factory = QGstElement::findFactory(arg); + if (!factory) + return qGstErrorMessageCannotFindElement(arg); + + if constexpr (sizeof...(args) != 0) + return qGstErrorMessageIfElementsNotAvailable(args...); + else + return std::nullopt; +} + +template <typename Functor> +void qForeachStreamInCollection(GstStreamCollection *collection, Functor &&f) +{ + guint size = gst_stream_collection_get_size(collection); + for (guint index = 0; index != size; ++index) + f(gst_stream_collection_get_stream(collection, index)); +} -inline QString errorMessageCannotFindElement(std::string_view element) +template <typename Functor> +void qForeachStreamInCollection(const QGstStreamCollectionHandle &collection, Functor &&f) { - return QStringLiteral("Could not find the %1 GStreamer element") - .arg(QLatin1StringView(element)); + qForeachStreamInCollection(collection.get(), std::forward<Functor>(f)); } QT_END_NAMESPACE +namespace std { + +template <> +struct hash<QT_PREPEND_NAMESPACE(QGstElement)> +{ + using argument_type = QT_PREPEND_NAMESPACE(QGstElement); + using result_type = size_t; + result_type operator()(const argument_type &e) const noexcept + { + return std::hash<void *>{}(e.element()); + } +}; +} // namespace std + #endif diff --git a/src/plugins/multimedia/gstreamer/common/qgstappsource.cpp b/src/plugins/multimedia/gstreamer/common/qgstappsource.cpp index 99af8443c..4111c70b6 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstappsource.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstappsource.cpp @@ -1,46 +1,51 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include <QDebug> - #include "qgstappsource_p.h" -#include <common/qgstutils_p.h> -#include "qnetworkreply.h" -#include "qloggingcategory.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> static Q_LOGGING_CATEGORY(qLcAppSrc, "qt.multimedia.appsrc") QT_BEGIN_NAMESPACE -QMaybe<QGstAppSource *> QGstAppSource::create(QObject *parent) +QGstAppSource::QGstAppSource(GstAppSrc *owner, QIODevice *stream, qint64 offset) + : m_owningAppSrc(owner) { - QGstAppSrc appsrc = QGstAppSrc::create("appsrc"); - if (!appsrc) - return errorMessageCannotFindElement("appsrc"); + Q_ASSERT(owner); - return new QGstAppSource(appsrc, parent); -} + QGstAppSrc ownerObject{ + m_owningAppSrc, + QGstAppSrc::NeedsRef, + }; -QGstAppSource::QGstAppSource(QGstAppSrc appsrc, QObject *parent) - : QObject(parent), m_appSrc(std::move(appsrc)) -{ - m_appSrc.set("emit-signals", false); + ownerObject.set("qgst-app-src", this, QGstObject::qDeleteFromVoidPointer<QGstAppSource>); + + setup(stream, offset); } QGstAppSource::~QGstAppSource() { - m_appSrc.setStateSync(GST_STATE_NULL); streamDestroyed(); qCDebug(qLcAppSrc) << "~QGstAppSrc"; } +void QGstAppSource::attachQIODeviceToGstAppSrc(GstAppSrc *source, QIODevice *ioDevice) +{ + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) + // GstAppSrc takes ownership of QGstAppSource + + QGstAppSource *appSource = new QGstAppSource(source, ioDevice); + Q_ASSERT(appSource); + + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) +} + bool QGstAppSource::setup(QIODevice *stream, qint64 offset) { QMutexLocker locker(&m_mutex); - if (m_appSrc.isNull()) - return false; - if (!setStream(stream, offset)) return false; @@ -49,44 +54,21 @@ bool QGstAppSource::setup(QIODevice *stream, qint64 offset) callbacks.enough_data = QGstAppSource::on_enough_data; callbacks.seek_data = QGstAppSource::on_seek_data; - m_appSrc.setCallbacks(callbacks, this, nullptr); + QGstAppSrc{ m_owningAppSrc, QGstAppSrc::NeedsRef }.setCallbacks(callbacks, this, nullptr); - GstAppSrc *appSrc = m_appSrc.appSrc(); - m_maxBytes = gst_app_src_get_max_bytes(appSrc); - m_suspended = false; + gst_app_src_set_max_bytes(m_owningAppSrc, maxBytes); - if (m_sequential) - m_streamType = GST_APP_STREAM_TYPE_STREAM; - else - m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; - gst_app_src_set_stream_type(appSrc, m_streamType); - gst_app_src_set_size(appSrc, m_sequential ? -1 : m_stream->size() - m_offset); - - m_noMoreData = true; + if (m_sequential) { + gst_app_src_set_stream_type(m_owningAppSrc, GST_APP_STREAM_TYPE_STREAM); + gst_app_src_set_size(m_owningAppSrc, -1); + } else { + gst_app_src_set_stream_type(m_owningAppSrc, GST_APP_STREAM_TYPE_RANDOM_ACCESS); + gst_app_src_set_size(m_owningAppSrc, m_stream->size() - m_offset); + } return true; } -void QGstAppSource::setAudioFormat(const QAudioFormat &f) -{ - QMutexLocker locker(&m_mutex); - - m_format = f; - if (!m_format.isValid()) - return; - - auto caps = QGstUtils::capsForAudioFormat(m_format); - Q_ASSERT(!caps.isNull()); - m_appSrc.set("caps", caps); - m_appSrc.set("format", GST_FORMAT_TIME); -} - -void QGstAppSource::setExternalAppSrc(QGstAppSrc appsrc) -{ - QMutexLocker locker(&m_mutex); - m_appSrc = std::move(appsrc); -} - bool QGstAppSource::setStream(QIODevice *stream, qint64 offset) { if (m_stream) { @@ -95,10 +77,7 @@ bool QGstAppSource::setStream(QIODevice *stream, qint64 offset) m_stream = nullptr; } - m_dataRequestSize = 0; m_sequential = true; - m_maxBytes = 0; - streamedSamples = 0; if (stream) { if (!stream->isOpen() && !stream->open(QIODevice::ReadOnly)) @@ -116,144 +95,97 @@ bool QGstAppSource::isStreamValid() const { return m_stream != nullptr && m_stream->isOpen(); } - -QGstElement QGstAppSource::element() const -{ - return m_appSrc; -} - -void QGstAppSource::write(const char *data, qsizetype size) -{ - QMutexLocker locker(&m_mutex); - - qCDebug(qLcAppSrc) << "write" << size << m_noMoreData << m_dataRequestSize; - if (!size) - return; - Q_ASSERT(!m_stream); - m_buffer.append(data, size); - m_noMoreData = false; - pushData(); -} - -bool QGstAppSource::canAcceptMoreData() const -{ - QMutexLocker locker(&m_mutex); - return m_noMoreData || m_dataRequestSize != 0; -} - -void QGstAppSource::suspend() -{ - QMutexLocker locker(&m_mutex); - m_suspended = true; -} - -void QGstAppSource::resume() -{ - QMutexLocker locker(&m_mutex); - m_suspended = false; - m_noMoreData = true; -} - void QGstAppSource::onDataReady() { + QMutexLocker locker(&m_mutex); qCDebug(qLcAppSrc) << "onDataReady" << m_stream->bytesAvailable() << m_stream->size(); - pushData(); + pushData(m_stream->bytesAvailable()); } void QGstAppSource::streamDestroyed() { + QMutexLocker locker(&m_mutex); qCDebug(qLcAppSrc) << "stream destroyed"; m_stream = nullptr; - m_dataRequestSize = 0; - streamedSamples = 0; sendEOS(); } -void QGstAppSource::pushData() +void QGstAppSource::pushData(qint64 bytesToRead) { - if (m_appSrc.isNull() || !m_dataRequestSize || m_suspended) { - qCDebug(qLcAppSrc) << "push data: return immediately" << m_appSrc.isNull() << m_dataRequestSize << m_suspended; + if (!m_stream) { + qCDebug(qLcAppSrc) << "push data: return immediately - stream was destroyed"; return; } - qCDebug(qLcAppSrc) << "pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); + if (!m_dataNeeded) { + qCDebug(qLcAppSrc) << "push data: return immediately - no data needed"; + return; + } + + Q_ASSERT(m_stream); + + qCDebug(qLcAppSrc) << "pushData" << m_stream; if ((m_stream && m_stream->atEnd())) { - eosOrIdle(); - qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); + sendEOS(); + qCDebug(qLcAppSrc) << "end pushData" << m_stream; return; } - qint64 size; - if (m_stream) - size = m_stream->bytesAvailable(); - else - size = m_buffer.size(); + qint64 size = m_stream->bytesAvailable(); - if (!m_dataRequestSize) - m_dataRequestSize = m_maxBytes; - size = qMin(size, (qint64)m_dataRequestSize); - qCDebug(qLcAppSrc) << " reading" << size << "bytes" << size << m_dataRequestSize; + size = qMin(size, bytesToRead); + qCDebug(qLcAppSrc) << " reading" << size << "bytes of requested" << bytesToRead; - GstBuffer* buffer = gst_buffer_new_and_alloc(size); + GstBuffer *buffer = gst_buffer_new_and_alloc(size); - if (m_sequential || !m_stream) + if (m_sequential) buffer->offset = bytesReadSoFar; else buffer->offset = m_stream->pos(); - if (m_format.isValid()) { - // timestamp raw audio data - uint nSamples = size/m_format.bytesPerFrame(); - - GST_BUFFER_TIMESTAMP(buffer) = gst_util_uint64_scale(streamedSamples, GST_SECOND, m_format.sampleRate()); - GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale(nSamples, GST_SECOND, m_format.sampleRate()); - streamedSamples += nSamples; - } - GstMapInfo mapInfo; gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE); void* bufferData = mapInfo.data; qint64 bytesRead; - if (m_stream) - bytesRead = m_stream->read((char*)bufferData, size); - else - bytesRead = m_buffer.read((char*)bufferData, size); - buffer->offset_end = buffer->offset + bytesRead - 1; + bytesRead = m_stream->read((char *)bufferData, size); + + buffer->offset_end = buffer->offset + bytesRead - 1; bytesReadSoFar += bytesRead; gst_buffer_unmap(buffer, &mapInfo); qCDebug(qLcAppSrc) << "pushing bytes into gstreamer" << buffer->offset << bytesRead; if (bytesRead == 0) { gst_buffer_unref(buffer); - eosOrIdle(); - qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); + sendEOS(); + qCDebug(qLcAppSrc) << "end pushData" << m_stream; return; } - m_noMoreData = false; - emit bytesProcessed(bytesRead); - - GstFlowReturn ret = m_appSrc.pushBuffer(buffer); - if (ret == GST_FLOW_ERROR) { - qWarning() << "QGstAppSrc: push buffer error"; - } else if (ret == GST_FLOW_FLUSHING) { - qWarning() << "QGstAppSrc: push buffer wrong state"; + + GstFlowReturn ret = gst_app_src_push_buffer(m_owningAppSrc, buffer); + switch (ret) { + case GST_FLOW_OK: + break; + + default: + qWarning() << "QGstAppSrc: push buffer error -" << gst_flow_get_name(ret); + break; } - qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); + qCDebug(qLcAppSrc) << "end pushData" << m_stream; } -bool QGstAppSource::doSeek(qint64 value) +bool QGstAppSource::doSeek(qint64 streamPosition) { if (isStreamValid()) - return m_stream->seek(value + m_offset); + return m_stream->seek(streamPosition + m_offset); return false; } -gboolean QGstAppSource::on_seek_data(GstAppSrc *, guint64 arg0, gpointer userdata) +gboolean QGstAppSource::on_seek_data(GstAppSrc *, guint64 streamPosition, gpointer userdata) { // we do get some spurious seeks to INT_MAX, ignore those - if (arg0 == std::numeric_limits<quint64>::max()) + if (streamPosition == std::numeric_limits<quint64>::max()) return true; QGstAppSource *self = reinterpret_cast<QGstAppSource *>(userdata); @@ -264,54 +196,36 @@ gboolean QGstAppSource::on_seek_data(GstAppSrc *, guint64 arg0, gpointer userdat if (self->m_sequential) return false; - self->doSeek(arg0); + self->doSeek(streamPosition); return true; } void QGstAppSource::on_enough_data(GstAppSrc *, gpointer userdata) { - qCDebug(qLcAppSrc) << "on_enough_data"; - QGstAppSource *self = static_cast<QGstAppSource *>(userdata); + qCWarning(qLcAppSrc) << "on_enough_data"; + + QGstAppSource *self = reinterpret_cast<QGstAppSource *>(userdata); Q_ASSERT(self); + QMutexLocker locker(&self->m_mutex); - self->m_dataRequestSize = 0; + self->m_dataNeeded = false; } -void QGstAppSource::on_need_data(GstAppSrc *, guint arg0, gpointer userdata) +void QGstAppSource::on_need_data(GstAppSrc *, guint numberOfBytes, gpointer userdata) { - qCDebug(qLcAppSrc) << "on_need_data requesting bytes" << arg0; + qCDebug(qLcAppSrc) << "on_need_data requesting bytes" << numberOfBytes; QGstAppSource *self = static_cast<QGstAppSource *>(userdata); Q_ASSERT(self); QMutexLocker locker(&self->m_mutex); - self->m_dataRequestSize = arg0; - self->pushData(); + self->m_dataNeeded = true; + self->pushData(numberOfBytes); qCDebug(qLcAppSrc) << "done on_need_data"; } void QGstAppSource::sendEOS() { qCDebug(qLcAppSrc) << "sending EOS"; - if (m_appSrc.isNull()) - return; - - gst_app_src_end_of_stream(GST_APP_SRC(m_appSrc.element())); -} - -void QGstAppSource::eosOrIdle() -{ - qCDebug(qLcAppSrc) << "eosOrIdle"; - if (m_appSrc.isNull()) - return; - - if (!m_sequential) { - sendEOS(); - return; - } - if (m_noMoreData) - return; - qCDebug(qLcAppSrc) << " idle!"; - m_noMoreData = true; - emit noMoreData(); + gst_app_src_end_of_stream(m_owningAppSrc); } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstappsource_p.h b/src/plugins/multimedia/gstreamer/common/qgstappsource_p.h index 59ced00dc..f6bd94bfa 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstappsource_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstappsource_p.h @@ -15,16 +15,12 @@ // We mean it. // -#include <private/qtmultimediaglobal_p.h> -#include <private/qmultimediautils_p.h> -#include <qaudioformat.h> - #include <QtCore/qobject.h> #include <QtCore/qiodevice.h> -#include <QtCore/private/qringbuffer_p.h> -#include <QtCore/qatomic.h> #include <QtCore/qmutex.h> +#include <QtMultimedia/private/qtmultimediaglobal_p.h> + #include <common/qgst_p.h> #include <gst/app/gstappsrc.h> @@ -34,34 +30,20 @@ class QGstAppSource : public QObject { Q_OBJECT public: - static QMaybe<QGstAppSource *> create(QObject *parent = nullptr); + explicit QGstAppSource(GstAppSrc *owner, QIODevice *, qint64 offset = 0); ~QGstAppSource(); - bool setup(QIODevice *stream = nullptr, qint64 offset = 0); - void setAudioFormat(const QAudioFormat &f); - - void setExternalAppSrc(QGstAppSrc); - QGstElement element() const; - - void write(const char *data, qsizetype size); - - bool canAcceptMoreData() const; - - void suspend(); - void resume(); - -Q_SIGNALS: - void bytesProcessed(int bytes); - void noMoreData(); + static void attachQIODeviceToGstAppSrc(GstAppSrc *, QIODevice *); private Q_SLOTS: - void pushData(); - bool doSeek(qint64); void onDataReady(); - void streamDestroyed(); + private: - QGstAppSource(QGstAppSrc appsrc, QObject *parent); + bool setup(QIODevice *stream = nullptr, qint64 offset = 0); + + bool doSeek(qint64 streamPosition); + void pushData(qint64 bytesToRead); bool setStream(QIODevice *, qint64 offset); bool isStreamValid() const; @@ -71,24 +53,18 @@ private: static void on_need_data(GstAppSrc *element, uint arg0, gpointer userdata); void sendEOS(); - void eosOrIdle(); mutable QMutex m_mutex; QIODevice *m_stream = nullptr; - QRingBuffer m_buffer; - QAudioFormat m_format; - QGstAppSrc m_appSrc; + GstAppSrc *m_owningAppSrc = nullptr; // QGstAppSource is owned by this GstAppSrc. bool m_sequential = true; - bool m_suspended = false; - bool m_noMoreData = false; - GstAppStreamType m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; qint64 m_offset = 0; - qint64 m_maxBytes = 0; qint64 bytesReadSoFar = 0; - QAtomicInteger<unsigned int> m_dataRequestSize = 0; - int streamedSamples = 0; + bool m_dataNeeded = false; + + static constexpr qint64 maxBytes = 64 * 1024; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp b/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp index 7d507f076..5b556c583 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstpipeline.cpp @@ -1,201 +1,82 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include <QtCore/qmap.h> -#include <QtCore/qtimer.h> -#include <QtCore/qmutex.h> -#include <QtCore/qlist.h> -#include <QtCore/qabstracteventdispatcher.h> -#include <QtCore/qcoreapplication.h> -#include <QtCore/qproperty.h> +#include <QtCore/qloggingcategory.h> #include "qgstpipeline_p.h" -#include "qgstreamermessage_p.h" +#include "qgst_bus_p.h" + +#include <thread> QT_BEGIN_NAMESPACE -class QGstPipelinePrivate : public QObject +static Q_LOGGING_CATEGORY(qLcGstPipeline, "qt.multimedia.gstpipeline"); + +static constexpr GstSeekFlags rateChangeSeekFlags = +#if GST_CHECK_VERSION(1, 18, 0) + GST_SEEK_FLAG_INSTANT_RATE_CHANGE; +#else + GST_SEEK_FLAG_FLUSH; +#endif + +class QGstPipelinePrivate : public QGstBus { public: - int m_ref = 0; - guint m_tag = 0; - GstBus *m_bus = nullptr; - QTimer *m_intervalTimer = nullptr; - QMutex filterMutex; - QList<QGstreamerSyncMessageFilter*> syncFilters; - QList<QGstreamerBusMessageFilter*> busFilters; - bool inStoppedState = true; - mutable qint64 m_position = 0; - double m_rate = 1.; - bool m_flushOnConfigChanges = false; - bool m_pendingFlush = false; + mutable std::chrono::nanoseconds m_position{}; + double m_rate = 1.; int m_configCounter = 0; GstState m_savedState = GST_STATE_NULL; - explicit QGstPipelinePrivate(GstBus *bus, QObject *parent = nullptr); - ~QGstPipelinePrivate(); - - void ref() { ++ m_ref; } - void deref() { if (!--m_ref) delete this; } - - void installMessageFilter(QGstreamerSyncMessageFilter *filter); - void removeMessageFilter(QGstreamerSyncMessageFilter *filter); - 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); - - for (QGstreamerSyncMessageFilter *filter : std::as_const(d->syncFilters)) { - if (filter->processSyncMessage( - QGstreamerMessage{ message, QGstreamerMessage::NeedsRef })) { - gst_message_unref(message); - return GST_BUS_DROP; - } - } - - return GST_BUS_PASS; - } - - void processMessage(GstMessage *message) - { - if (!message) - return; - - QGstreamerMessage msg{ - message, - QGstreamerMessage::NeedsRef, - }; - - for (QGstreamerBusMessageFilter *filter : std::as_const(busFilters)) { - if (filter->processBusMessage(msg)) - break; - } - } - - static gboolean busCallback(GstBus *, GstMessage *message, gpointer data) - { - static_cast<QGstPipelinePrivate *>(data)->processMessage(message); - return TRUE; - } + explicit QGstPipelinePrivate(QGstBusHandle); }; -QGstPipelinePrivate::QGstPipelinePrivate(GstBus* bus, QObject* parent) - : QObject(parent), - m_bus(bus) +QGstPipelinePrivate::QGstPipelinePrivate(QGstBusHandle bus) + : QGstBus{ + std::move(bus), + } { - // glib event loop can be disabled either by env variable or QT_NO_GLIB define, so check the dispacher - QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher(); - const bool hasGlib = dispatcher && dispatcher->inherits("QEventDispatcherGlib"); - if (!hasGlib) { - m_intervalTimer = new QTimer(this); - m_intervalTimer->setInterval(250); - 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); - } - - gst_bus_set_sync_handler(bus, (GstBusSyncHandler)syncGstBusFilter, this, nullptr); } -QGstPipelinePrivate::~QGstPipelinePrivate() -{ - delete m_intervalTimer; - - if (m_tag) - gst_bus_remove_watch(m_bus); +// QGstPipeline - gst_bus_set_sync_handler(m_bus, nullptr, nullptr, nullptr); - gst_object_unref(GST_OBJECT(m_bus)); -} - -void QGstPipelinePrivate::installMessageFilter(QGstreamerSyncMessageFilter *filter) -{ - if (filter) { - QMutexLocker lock(&filterMutex); - if (!syncFilters.contains(filter)) - syncFilters.append(filter); - } -} - -void QGstPipelinePrivate::removeMessageFilter(QGstreamerSyncMessageFilter *filter) -{ - if (filter) { - QMutexLocker lock(&filterMutex); - syncFilters.removeAll(filter); - } -} - -void QGstPipelinePrivate::installMessageFilter(QGstreamerBusMessageFilter *filter) +QGstPipeline QGstPipeline::create(const char *name) { - if (filter && !busFilters.contains(filter)) - busFilters.append(filter); + GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(gst_pipeline_new(name)); + return adopt(pipeline); } -void QGstPipelinePrivate::removeMessageFilter(QGstreamerBusMessageFilter *filter) +QGstPipeline QGstPipeline::createFromFactory(const char *factory, const char *name) { - if (filter) - busFilters.removeAll(filter); -} + QGstElement playbin3 = QGstElement::createFromFactory(factory, name); + GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(playbin3.element()); -QGstPipeline QGstPipeline::create(const char *name) -{ - GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(gst_pipeline_new(name)); - return adopt(pipeline); + return QGstPipeline::adopt(pipeline); } QGstPipeline QGstPipeline::adopt(GstPipeline *pipeline) { - QGstPipelinePrivate *d = new QGstPipelinePrivate(gst_pipeline_get_bus(pipeline)); - g_object_set_data_full(qGstCheckedCast<GObject>(pipeline), "pipeline-private", d, - [](gpointer ptr) { - delete reinterpret_cast<QGstPipelinePrivate *>(ptr); - return; - }); - - return QGstPipeline{ + QGstPipeline wrappedObject{ pipeline, QGstPipeline::NeedsRef, }; -} -QGstPipeline::QGstPipeline(GstPipeline *p, RefMode mode) : QGstBin(qGstCheckedCast<GstBin>(p), mode) -{ -} + QGstBusHandle bus{ + gst_pipeline_get_bus(pipeline), + QGstBusHandle::HasRef, + }; -QGstPipeline::~QGstPipeline() = default; + auto d = std::make_unique<QGstPipelinePrivate>(std::move(bus)); + wrappedObject.set("pipeline-private", std::move(d)); -bool QGstPipeline::inStoppedState() const -{ - QGstPipelinePrivate *d = getPrivate(); - return d->inStoppedState; + return wrappedObject; } -void QGstPipeline::setInStoppedState(bool stopped) +QGstPipeline::QGstPipeline(GstPipeline *p, RefMode mode) : QGstBin(qGstCheckedCast<GstBin>(p), mode) { - QGstPipelinePrivate *d = getPrivate(); - d->inStoppedState = stopped; } -void QGstPipeline::setFlushOnConfigChanges(bool flush) -{ - QGstPipelinePrivate *d = getPrivate(); - d->m_flushOnConfigChanges = flush; -} +QGstPipeline::~QGstPipeline() = default; void QGstPipeline::installMessageFilter(QGstreamerSyncMessageFilter *filter) { @@ -223,21 +104,18 @@ void QGstPipeline::removeMessageFilter(QGstreamerBusMessageFilter *filter) GstStateChangeReturn QGstPipeline::setState(GstState state) { - QGstPipelinePrivate *d = getPrivate(); - auto retval = gst_element_set_state(element(), state); - if (d->m_pendingFlush) { - d->m_pendingFlush = false; - flush(); - } - return retval; + return gst_element_set_state(element(), state); } -void QGstPipeline::dumpGraph(const char *fileName) +bool QGstPipeline::processNextPendingMessage(GstMessageType types, std::chrono::nanoseconds timeout) { - if (isNull()) - return; + QGstPipelinePrivate *d = getPrivate(); + return d->processNextPendingMessage(types, timeout); +} - QGstBin{ bin(), QGstBin::NeedsRef }.dumpGraph(fileName); +bool QGstPipeline::processNextPendingMessage(std::chrono::nanoseconds timeout) +{ + return processNextPendingMessage(GST_MESSAGE_ANY, timeout); } void QGstPipeline::beginConfig() @@ -265,8 +143,8 @@ void QGstPipeline::beginConfig() break; } case GST_STATE_CHANGE_FAILURE: { - // should not happen - qCritical() << "QGstPipeline::beginConfig: state change failure"; + qDebug() << "QGstPipeline::beginConfig: state change failure"; + dumpGraph("beginConfigFailure"); break; } @@ -289,8 +167,6 @@ void QGstPipeline::endConfig() if (d->m_configCounter) return; - if (d->m_flushOnConfigChanges) - d->m_pendingFlush = true; if (d->m_savedState == GST_STATE_PLAYING) setState(GST_STATE_PLAYING); d->m_savedState = GST_STATE_NULL; @@ -298,53 +174,61 @@ void QGstPipeline::endConfig() void QGstPipeline::flush() { - QGstPipelinePrivate *d = getPrivate(); - seek(position(), d->m_rate); + seek(position()); } -bool QGstPipeline::seek(qint64 pos, double rate) +void QGstPipeline::seek(std::chrono::nanoseconds pos, double rate) { + using namespace std::chrono_literals; + + // CAVEAT: we need to ensure that no async operation is currently in-flight, i.e + // gst_element_get_state does not return GST_STATE_CHANGE_ASYNC. + // Apparently this can happen when (a) the pipeline is changed due to new sinks being added or + // the like and (b) during pending seeks. + bool asyncChangeSuccess = waitForAsyncStateChangeComplete(); + if (!asyncChangeSuccess) { + qWarning() << "QGstPipeline::seek: async pipeline change in progress. Seeking impossible"; + return; + } + QGstPipelinePrivate *d = getPrivate(); - // always adjust the rate, so it can be set before playback starts + // always adjust the rate, so it can be set before playback starts // setting position needs a loaded media file that's seekable - d->m_rate = rate; - qint64 from = rate > 0 ? pos : 0; - qint64 to = rate > 0 ? duration() : pos; - bool success = gst_element_seek(element(), rate, GST_FORMAT_TIME, - GstSeekFlags(GST_SEEK_FLAG_FLUSH), - GST_SEEK_TYPE_SET, from, - GST_SEEK_TYPE_SET, to); - if (!success) - return false; + + qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos.count() << "rate:" << rate; + + bool success = (rate > 0) + ? gst_element_seek(element(), rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, pos.count(), GST_SEEK_TYPE_END, 0) + : gst_element_seek(element(), rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, pos.count()); + + if (!success) { + qDebug() << "seek: gst_element_seek failed" << pos.count(); + dumpGraph("seekSeekFailed"); + return; + } d->m_position = pos; - return true; } -bool QGstPipeline::setPlaybackRate(double rate, bool applyToPipeline) +void QGstPipeline::seek(std::chrono::nanoseconds pos) +{ + qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos.count(); + seek(pos, getPrivate()->m_rate); +} + +void QGstPipeline::setPlaybackRate(double rate, bool forceFlushingSeek) { QGstPipelinePrivate *d = getPrivate(); if (rate == d->m_rate) - return false; - - if (!applyToPipeline) { - d->m_rate = rate; - return true; - } + return; - constexpr GstSeekFlags seekFlags = -#if GST_CHECK_VERSION(1, 18, 0) - GST_SEEK_FLAG_INSTANT_RATE_CHANGE; -#else - GST_SEEK_FLAG_FLUSH; -#endif + d->m_rate = rate; - bool success = gst_element_seek(element(), rate, GST_FORMAT_TIME, seekFlags, GST_SEEK_TYPE_NONE, - GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); - if (success) - d->m_rate = rate; + qCDebug(qLcGstPipeline) << "QGstPipeline::setPlaybackRate to" << rate; - return success; + applyPlaybackRate(forceFlushingSeek); } double QGstPipeline::playbackRate() const @@ -353,35 +237,141 @@ double QGstPipeline::playbackRate() const return d->m_rate; } -bool QGstPipeline::setPosition(qint64 pos) +void QGstPipeline::applyPlaybackRate(bool forceFlushingSeek) { QGstPipelinePrivate *d = getPrivate(); - return seek(pos, d->m_rate); + + // do not GST_SEEK_FLAG_FLUSH with GST_SEEK_TYPE_NONE + // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3604 + if (!forceFlushingSeek && GST_CHECK_VERSION(1, 18, 0)) { + bool asyncChangeSuccess = waitForAsyncStateChangeComplete(); + if (!asyncChangeSuccess) { + qWarning() + << "QGstPipeline::seek: async pipeline change in progress. Seeking impossible"; + return; + } + + qCDebug(qLcGstPipeline) << "QGstPipeline::applyPlaybackRate instantly"; + bool success = gst_element_seek( + element(), d->m_rate, GST_FORMAT_UNDEFINED, rateChangeSeekFlags, GST_SEEK_TYPE_NONE, + GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (!success) { + qDebug() << "setPlaybackRate: gst_element_seek failed"; + dumpGraph("applyPlaybackRateSeekFailed"); + } + } else { + seek(position(), d->m_rate); + } } -qint64 QGstPipeline::position() const +void QGstPipeline::setPosition(std::chrono::nanoseconds pos) +{ + seek(pos); +} + +std::chrono::nanoseconds QGstPipeline::position() const { - gint64 pos; QGstPipelinePrivate *d = getPrivate(); - if (gst_element_query_position(element(), GST_FORMAT_TIME, &pos)) - d->m_position = pos; + std::optional<std::chrono::nanoseconds> pos = QGstElement::position(); + if (pos) { + d->m_position = *pos; + qCDebug(qLcGstPipeline) << "QGstPipeline::position:" + << std::chrono::round<std::chrono::milliseconds>(*pos).count(); + } else { + qDebug() << "QGstPipeline: failed to query position, using previous position"; + dumpGraph("positionQueryFailed"); + } + return d->m_position; } -qint64 QGstPipeline::duration() const +std::chrono::milliseconds QGstPipeline::positionInMs() const +{ + using namespace std::chrono; + return round<milliseconds>(position()); +} + +void QGstPipeline::setPositionAndRate(std::chrono::nanoseconds pos, double rate) +{ + QGstPipelinePrivate *d = getPrivate(); + d->m_rate = rate; + seek(pos, rate); +} + +std::optional<std::chrono::nanoseconds> +QGstPipeline::queryPosition(std::chrono::nanoseconds timeout) const +{ + using namespace std::chrono_literals; + using namespace std::chrono; + + std::chrono::nanoseconds totalSleepTime{}; + + for (;;) { + std::optional<nanoseconds> dur = QGstElement::duration(); + if (dur) + return dur; + + if (totalSleepTime >= timeout) + return std::nullopt; + std::this_thread::sleep_for(20ms); + totalSleepTime += 20ms; + } +} + +std::optional<std::chrono::nanoseconds> +QGstPipeline::queryDuration(std::chrono::nanoseconds timeout) const +{ + using namespace std::chrono_literals; + using namespace std::chrono; + + std::chrono::nanoseconds totalSleepTime{}; + + for (;;) { + std::optional<nanoseconds> dur = QGstElement::duration(); + if (dur) + return dur; + + if (totalSleepTime >= timeout) + return std::nullopt; + + std::this_thread::sleep_for(20ms); + totalSleepTime += 20ms; + } +} + +std::optional<std::pair<std::chrono::nanoseconds, std::chrono::nanoseconds>> +QGstPipeline::queryPositionAndDuration(std::chrono::nanoseconds timeout) const { - gint64 d; - if (!gst_element_query_duration(element(), GST_FORMAT_TIME, &d)) - return 0.; - return d; + using namespace std::chrono_literals; + using namespace std::chrono; + + std::chrono::nanoseconds totalSleepTime{}; + + std::optional<nanoseconds> dur; + std::optional<nanoseconds> pos; + + for (;;) { + if (!dur) + dur = QGstElement::duration(); + if (!pos) + pos = QGstElement::position(); + + if (dur && pos) + return std::pair{ *dur, *pos }; + + if (totalSleepTime >= timeout) + return std::nullopt; + + std::this_thread::sleep_for(20ms); + totalSleepTime += 20ms; + } } QGstPipelinePrivate *QGstPipeline::getPrivate() const { - gpointer p = g_object_get_data(qGstCheckedCast<GObject>(object()), "pipeline-private"); - auto *d = reinterpret_cast<QGstPipelinePrivate *>(p); - Q_ASSERT(d); - return d; + QGstPipelinePrivate *ret = getObject<QGstPipelinePrivate>("pipeline-private"); + Q_ASSERT(ret); + return ret; } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h b/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h index 6914993de..e8a0ad400 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstpipeline_p.h @@ -23,24 +23,14 @@ QT_BEGIN_NAMESPACE class QGstreamerMessage; - -class QGstreamerSyncMessageFilter { -public: - //returns true if message was processed and should be dropped, false otherwise - virtual bool processSyncMessage(const QGstreamerMessage &message) = 0; -}; - - -class QGstreamerBusMessageFilter { -public: - //returns true if message was processed and should be dropped, false otherwise - virtual bool processBusMessage(const QGstreamerMessage &message) = 0; -}; - +class QGstreamerSyncMessageFilter; +class QGstreamerBusMessageFilter; class QGstPipelinePrivate; class QGstPipeline : public QGstBin { + static constexpr auto defaultQueryTimeout = std::chrono::seconds{ 5 }; + public: constexpr QGstPipeline() = default; QGstPipeline(const QGstPipeline &) = default; @@ -50,19 +40,8 @@ public: QGstPipeline(GstPipeline *, RefMode mode); ~QGstPipeline(); - // installs QGstPipelinePrivate as "pipeline-private" gobject property + static QGstPipeline createFromFactory(const char *factory, const char *name); static QGstPipeline create(const char *name); - static QGstPipeline adopt(GstPipeline *); - - // This is needed to help us avoid sending QVideoFrames or audio buffers to the - // application while we're prerolling the pipeline. - // QMediaPlayer is still in a stopped state, while we put the gstreamer pipeline into a - // Paused state so that we can get the required metadata of the stream and also have a fast - // transition to play. - bool inStoppedState() const; - void setInStoppedState(bool stopped); - - void setFlushOnConfigChanges(bool flush); void installMessageFilter(QGstreamerSyncMessageFilter *filter); void removeMessageFilter(QGstreamerSyncMessageFilter *filter); @@ -73,7 +52,9 @@ public: GstPipeline *pipeline() const { return GST_PIPELINE_CAST(get()); } - void dumpGraph(const char *fileName); + bool processNextPendingMessage(GstMessageType = GST_MESSAGE_ANY, + std::chrono::nanoseconds timeout = {}); + bool processNextPendingMessage(std::chrono::nanoseconds timeout); template <typename Functor> void modifyPipelineWhileNotRunning(Functor &&fn) @@ -94,16 +75,30 @@ public: void flush(); - bool seek(qint64 pos, double rate); - bool setPlaybackRate(double rate, bool applyToPipeline = true); + void setPlaybackRate(double rate, bool forceFlushingSeek = false); double playbackRate() const; + void applyPlaybackRate(bool forceFlushingSeek = false); + + void setPosition(std::chrono::nanoseconds pos); + std::chrono::nanoseconds position() const; + std::chrono::milliseconds positionInMs() const; - bool setPosition(qint64 pos); - qint64 position() const; + void setPositionAndRate(std::chrono::nanoseconds pos, double rate); - qint64 duration() const; + std::optional<std::chrono::nanoseconds> + queryPosition(std::chrono::nanoseconds timeout = defaultQueryTimeout) const; + std::optional<std::chrono::nanoseconds> + queryDuration(std::chrono::nanoseconds timeout = defaultQueryTimeout) const; + std::optional<std::pair<std::chrono::nanoseconds, std::chrono::nanoseconds>> + queryPositionAndDuration(std::chrono::nanoseconds timeout = defaultQueryTimeout) const; private: + // installs QGstPipelinePrivate as "pipeline-private" gobject property + static QGstPipeline adopt(GstPipeline *); + + void seek(std::chrono::nanoseconds pos, double rate); + void seek(std::chrono::nanoseconds pos); + QGstPipelinePrivate *getPrivate() const; void beginConfig(); diff --git a/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput.cpp b/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput.cpp index 0381b921e..8930afcec 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput.cpp @@ -1,107 +1,156 @@ // 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 <QtMultimedia/qaudiodevice.h> -#include <QtMultimedia/qaudioinput.h> +#include <common/qgstreameraudioinput_p.h> #include <QtCore/qloggingcategory.h> +#include <QtMultimedia/qaudiodevice.h> +#include <QtMultimedia/qaudioinput.h> #include <audio/qgstreameraudiodevice_p.h> -#include <common/qgstreameraudioinput_p.h> +#include <common/qgstpipeline_p.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <utility> +QT_BEGIN_NAMESPACE -static Q_LOGGING_CATEGORY(qLcMediaAudioInput, "qt.multimedia.audioInput") +namespace { -QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(qLcMediaAudioInput, "qt.multimedia.audioinput") -QMaybe<QPlatformAudioInput *> QGstreamerAudioInput::create(QAudioInput *parent) +constexpr QLatin1String defaultSrcName = [] { + using namespace Qt::Literals; + + if constexpr (QT_CONFIG(pulseaudio)) + return "pulsesrc"_L1; + else if constexpr (QT_CONFIG(alsa)) + return "alsasrc"_L1; + else + return "autoaudiosrc"_L1; +}(); + +bool srcHasDeviceProperty(const QGstElement &element) { - QGstElement autoaudiosrc = QGstElement::createFromFactory("autoaudiosrc", "autoaudiosrc"); - if (!autoaudiosrc) - return errorMessageCannotFindElement("autoaudiosrc"); + using namespace Qt::Literals; + QLatin1String elementType = element.typeName(); + + if constexpr (QT_CONFIG(pulseaudio)) + return elementType == "GstPulseSrc"_L1; - QGstElement volume = QGstElement::createFromFactory("volume", "volume"); - if (!volume) - return errorMessageCannotFindElement("volume"); + if constexpr (0 && QT_CONFIG(alsa)) // alsasrc has a "device" property, but it cannot be changed + // during playback + return elementType == "GstAlsaSrc"_L1; - return new QGstreamerAudioInput(autoaudiosrc, volume, parent); + return false; } -QGstreamerAudioInput::QGstreamerAudioInput(QGstElement autoaudiosrc, QGstElement volume, - QAudioInput *parent) +} // namespace + +QMaybe<QPlatformAudioInput *> QGstreamerAudioInput::create(QAudioInput *parent) +{ + static const auto error = qGstErrorMessageIfElementsNotAvailable("autoaudiosrc", "volume"); + if (error) + return *error; + + return new QGstreamerAudioInput(parent); +} + +QGstreamerAudioInput::QGstreamerAudioInput(QAudioInput *parent) : QObject(parent), QPlatformAudioInput(parent), - gstAudioInput(QGstBin::create("audioInput")), - audioSrc(std::move(autoaudiosrc)), - audioVolume(std::move(volume)) + m_audioInputBin(QGstBin::create("audioInput")), + m_audioSrc{ + QGstElement::createFromFactory(defaultSrcName.constData(), "autoaudiosrc"), + }, + m_audioVolume{ + QGstElement::createFromFactory("volume", "volume"), + } +{ + m_audioInputBin.add(m_audioSrc, m_audioVolume); + qLinkGstElements(m_audioSrc, m_audioVolume); + + m_audioInputBin.addGhostPad(m_audioVolume, "src"); +} + +QGstElement QGstreamerAudioInput::createGstElement() { - gstAudioInput.add(audioSrc, audioVolume); - qLinkGstElements(audioSrc, audioVolume); + const auto *customDeviceInfo = + dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(m_audioDevice.handle()); + + if (customDeviceInfo) { + qCDebug(qLcMediaAudioInput) + << "requesting custom audio src element: " << customDeviceInfo->id; + + QGstElement element = QGstBin::createFromPipelineDescription(customDeviceInfo->id, + /*name=*/nullptr, + /*ghostUnlinkedPads=*/true); + if (element) + return element; + + qCWarning(qLcMediaAudioInput) + << "Cannot create audio source element:" << customDeviceInfo->id; + } + + const QByteArray &id = m_audioDevice.id(); + if constexpr (QT_CONFIG(pulseaudio) || QT_CONFIG(alsa)) { + QGstElement newSrc = QGstElement::createFromFactory(defaultSrcName.constData(), "audiosrc"); + if (newSrc) { + newSrc.set("device", id.constData()); + return newSrc; + } + + qWarning() << "Cannot create" << defaultSrcName; - gstAudioInput.addGhostPad(audioVolume, "src"); + } else { + auto *deviceInfo = dynamic_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle()); + if (deviceInfo && deviceInfo->gstDevice) { + QGstElement element = QGstElement::createFromDevice(deviceInfo->gstDevice, "audiosrc"); + if (element) + return element; + } + } + qCWarning(qLcMediaAudioInput) << "Invalid audio device"; + qCWarning(qLcMediaAudioInput) + << "Failed to create a gst element for the audio device, using a default audio source"; + return QGstElement::createFromFactory("autoaudiosrc", "audiosrc"); } QGstreamerAudioInput::~QGstreamerAudioInput() { - gstAudioInput.setStateSync(GST_STATE_NULL); + m_audioInputBin.setStateSync(GST_STATE_NULL); } void QGstreamerAudioInput::setVolume(float volume) { - audioVolume.set("volume", volume); + m_audioVolume.set("volume", volume); } void QGstreamerAudioInput::setMuted(bool muted) { - audioVolume.set("mute", muted); + m_audioVolume.set("mute", muted); } void QGstreamerAudioInput::setAudioDevice(const QAudioDevice &device) { if (device == m_audioDevice) return; - qCDebug(qLcMediaAudioInput) << "setAudioInput" << device.description() << device.isNull(); + qCDebug(qLcMediaAudioInput) << "setAudioDevice" << device.description() << device.isNull(); m_audioDevice = device; - QGstElement newSrc; - if constexpr (QT_CONFIG(pulseaudio)) { - auto id = m_audioDevice.id(); - newSrc = QGstElement::createFromFactory("pulsesrc", "audiosrc"); - if (!newSrc.isNull()) - newSrc.set("device", id.constData()); - else - qCWarning(qLcMediaAudioInput) << "Invalid audio device"; - } else { - auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle()); - if (deviceInfo && deviceInfo->gstDevice) - newSrc = QGstElement::createFromDevice(deviceInfo->gstDevice, "audiosrc"); - else - qCWarning(qLcMediaAudioInput) << "Invalid audio device"; + if (srcHasDeviceProperty(m_audioSrc) && !isCustomAudioDevice(m_audioDevice)) { + m_audioSrc.set("device", m_audioDevice.id().constData()); + return; } - if (newSrc.isNull()) { - qCWarning(qLcMediaAudioInput) << "Failed to create a gst element for the audio device, using a default audio source"; - newSrc = QGstElement::createFromFactory("autoaudiosrc", "audiosrc"); - } + QGstElement newSrc = createGstElement(); - QGstPipeline::modifyPipelineWhileNotRunning(gstAudioInput.getPipeline(), [&] { - qUnlinkGstElements(audioSrc, audioVolume); - gstAudioInput.stopAndRemoveElements(audioSrc); - audioSrc = std::move(newSrc); - gstAudioInput.add(audioSrc); - qLinkGstElements(audioSrc, audioVolume); - audioSrc.syncStateWithParent(); + m_audioVolume.sink().modifyPipelineInIdleProbe([&] { + qUnlinkGstElements(m_audioSrc, m_audioVolume); + m_audioInputBin.stopAndRemoveElements(m_audioSrc); + m_audioSrc = std::move(newSrc); + m_audioInputBin.add(m_audioSrc); + qLinkGstElements(m_audioSrc, m_audioVolume); + m_audioSrc.syncStateWithParent(); }); } -QAudioDevice QGstreamerAudioInput::audioInput() const -{ - return m_audioDevice; -} - QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput_p.h b/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput_p.h index 69500ecab..4b01b53a6 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreameraudioinput_p.h @@ -16,13 +16,9 @@ // #include <QtCore/qobject.h> -#include <QtMultimedia/private/qmultimediautils_p.h> #include <QtMultimedia/private/qplatformaudioinput_p.h> -#include <QtMultimedia/private/qtmultimediaglobal_p.h> -#include <QtMultimedia/qaudiodevice.h> #include <common/qgst_p.h> -#include <common/qgstpipeline_p.h> QT_BEGIN_NAMESPACE @@ -34,25 +30,24 @@ public: static QMaybe<QPlatformAudioInput *> create(QAudioInput *parent); ~QGstreamerAudioInput(); - bool setAudioInput(const QAudioDevice &); - QAudioDevice audioInput() const; - void setAudioDevice(const QAudioDevice &) override; void setVolume(float) override; void setMuted(bool) override; - QGstElement gstElement() const { return gstAudioInput; } + QGstElement gstElement() const { return m_audioInputBin; } private: - QGstreamerAudioInput(QGstElement autoaudiosrc, QGstElement volume, QAudioInput *parent); + explicit QGstreamerAudioInput(QAudioInput *parent); + + QGstElement createGstElement(); QAudioDevice m_audioDevice; // Gst elements - QGstBin gstAudioInput; + QGstBin m_audioInputBin; - QGstElement audioSrc; - QGstElement audioVolume; + QGstElement m_audioSrc; + QGstElement m_audioVolume; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput.cpp b/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput.cpp index f45c371e9..a28c97711 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput.cpp @@ -2,107 +2,165 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <common/qgstreameraudiooutput_p.h> -#include <audio/qgstreameraudiodevice_p.h> +#include <QtCore/qloggingcategory.h> #include <QtMultimedia/qaudiodevice.h> #include <QtMultimedia/qaudiooutput.h> -#include <QtCore/qloggingcategory.h> -#include <utility> +#include <common/qgstpipeline_p.h> +#include <audio/qgstreameraudiodevice_p.h> -static Q_LOGGING_CATEGORY(qLcMediaAudioOutput, "qt.multimedia.audiooutput") QT_BEGIN_NAMESPACE -QMaybe<QPlatformAudioOutput *> QGstreamerAudioOutput::create(QAudioOutput *parent) +namespace { + +Q_LOGGING_CATEGORY(qLcMediaAudioOutput, "qt.multimedia.audiooutput") + +constexpr QLatin1String defaultSinkName = [] { + using namespace Qt::Literals; + + if constexpr (QT_CONFIG(pulseaudio)) + return "pulsesink"_L1; + else if constexpr (QT_CONFIG(alsa)) + return "alsasink"_L1; + else + return "autoaudiosink"_L1; +}(); + +bool sinkHasDeviceProperty(const QGstElement &element) { - QGstElement audioconvert = QGstElement::createFromFactory("audioconvert", "audioConvert"); - if (!audioconvert) - return errorMessageCannotFindElement("audioconvert"); + using namespace Qt::Literals; + QLatin1String elementType = element.typeName(); + + if constexpr (QT_CONFIG(pulseaudio)) + return elementType == "GstPulseSink"_L1; + if constexpr (0 && QT_CONFIG(alsa)) // alsasrc has a "device" property, but it cannot be changed + // during playback + return elementType == "GstAlsaSink"_L1; - QGstElement audioresample = QGstElement::createFromFactory("audioresample", "audioResample"); - if (!audioresample) - return errorMessageCannotFindElement("audioresample"); + return false; +} - QGstElement volume = QGstElement::createFromFactory("volume", "volume"); - if (!volume) - return errorMessageCannotFindElement("volume"); +} // namespace - QGstElement autoaudiosink = QGstElement::createFromFactory("autoaudiosink", "autoAudioSink"); - if (!autoaudiosink) - return errorMessageCannotFindElement("autoaudiosink"); +QMaybe<QPlatformAudioOutput *> QGstreamerAudioOutput::create(QAudioOutput *parent) +{ + static const auto error = qGstErrorMessageIfElementsNotAvailable( + "audioconvert", "audioresample", "volume", "autoaudiosink"); + if (error) + return *error; - return new QGstreamerAudioOutput(audioconvert, audioresample, volume, autoaudiosink, parent); + return new QGstreamerAudioOutput(parent); } -QGstreamerAudioOutput::QGstreamerAudioOutput(QGstElement audioconvert, QGstElement audioresample, - QGstElement volume, QGstElement autoaudiosink, - QAudioOutput *parent) +QGstreamerAudioOutput::QGstreamerAudioOutput(QAudioOutput *parent) : QObject(parent), QPlatformAudioOutput(parent), - gstAudioOutput(QGstBin::create("audioOutput")), - audioConvert(std::move(audioconvert)), - audioResample(std::move(audioresample)), - audioVolume(std::move(volume)), - audioSink(std::move(autoaudiosink)) + m_audioOutputBin(QGstBin::create("audioOutput")), + m_audioQueue{ + QGstElement::createFromFactory("queue", "audioQueue"), + }, + m_audioConvert{ + QGstElement::createFromFactory("audioconvert", "audioConvert"), + }, + m_audioResample{ + QGstElement::createFromFactory("audioresample", "audioResample"), + }, + m_audioVolume{ + QGstElement::createFromFactory("volume", "volume"), + }, + m_audioSink{ + QGstElement::createFromFactory(defaultSinkName.constData(), "audiosink"), + } { - audioQueue = QGstElement::createFromFactory("queue", "audioQueue"); - gstAudioOutput.add(audioQueue, audioConvert, audioResample, audioVolume, audioSink); - qLinkGstElements(audioQueue, audioConvert, audioResample, audioVolume, audioSink); + m_audioOutputBin.add(m_audioQueue, m_audioConvert, m_audioResample, m_audioVolume, m_audioSink); + qLinkGstElements(m_audioQueue, m_audioConvert, m_audioResample, m_audioVolume, m_audioSink); - gstAudioOutput.addGhostPad(audioQueue, "sink"); + m_audioOutputBin.addGhostPad(m_audioQueue, "sink"); +} + +QGstElement QGstreamerAudioOutput::createGstElement() +{ + const auto *customDeviceInfo = + dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(m_audioDevice.handle()); + + if (customDeviceInfo) { + qCDebug(qLcMediaAudioOutput) + << "requesting custom audio sink element: " << customDeviceInfo->id; + + QGstElement element = + QGstBin::createFromPipelineDescription(customDeviceInfo->id, /*name=*/nullptr, + /*ghostUnlinkedPads=*/true); + if (element) + return element; + + qCWarning(qLcMediaAudioOutput) + << "Cannot create audio sink element:" << customDeviceInfo->id; + } + + const QByteArray &id = m_audioDevice.id(); + if constexpr (QT_CONFIG(pulseaudio) || QT_CONFIG(alsa)) { + QGstElement newSink = + QGstElement::createFromFactory(defaultSinkName.constData(), "audiosink"); + if (newSink) { + newSink.set("device", id.constData()); + return newSink; + } + + qWarning() << "Cannot create" << defaultSinkName; + } else { + auto *deviceInfo = dynamic_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle()); + if (deviceInfo && deviceInfo->gstDevice) { + QGstElement element = QGstElement::createFromDevice(deviceInfo->gstDevice, "audiosink"); + if (element) + return element; + } + } + qCWarning(qLcMediaAudioOutput) << "Invalid audio device:" << m_audioDevice.id(); + qCWarning(qLcMediaAudioOutput) + << "Failed to create a gst element for the audio device, using a default audio sink"; + return QGstElement::createFromFactory("autoaudiosink", "audiosink"); } QGstreamerAudioOutput::~QGstreamerAudioOutput() { - gstAudioOutput.setStateSync(GST_STATE_NULL); + m_audioOutputBin.setStateSync(GST_STATE_NULL); } void QGstreamerAudioOutput::setVolume(float volume) { - audioVolume.set("volume", volume); + m_audioVolume.set("volume", volume); } void QGstreamerAudioOutput::setMuted(bool muted) { - audioVolume.set("mute", muted); + m_audioVolume.set("mute", muted); } -void QGstreamerAudioOutput::setAudioDevice(const QAudioDevice &info) +void QGstreamerAudioOutput::setAudioDevice(const QAudioDevice &device) { - if (info == m_audioOutput) + if (device == m_audioDevice) return; - qCDebug(qLcMediaAudioOutput) << "setAudioOutput" << info.description() << info.isNull(); - m_audioOutput = info; - - QGstElement newSink; - if constexpr (QT_CONFIG(pulseaudio)) { - auto id = m_audioOutput.id(); - newSink = QGstElement::createFromFactory("pulsesink", "audiosink"); - if (!newSink.isNull()) - newSink.set("device", id.constData()); - else - qCWarning(qLcMediaAudioOutput) << "Invalid audio device"; - } else { - auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioOutput.handle()); - if (deviceInfo && deviceInfo->gstDevice) - newSink = QGstElement::createFromDevice(deviceInfo->gstDevice, "audiosink"); - else - qCWarning(qLcMediaAudioOutput) << "Invalid audio device"; - } + qCDebug(qLcMediaAudioOutput) << "setAudioDevice" << device.description() << device.isNull(); - if (newSink.isNull()) { - qCWarning(qLcMediaAudioOutput) << "Failed to create a gst element for the audio device, using a default audio sink"; - newSink = QGstElement::createFromFactory("autoaudiosink", "audiosink"); + m_audioDevice = device; + + if (sinkHasDeviceProperty(m_audioSink) && !isCustomAudioDevice(m_audioDevice)) { + m_audioSink.set("device", m_audioDevice.id().constData()); + return; } - QGstPipeline::modifyPipelineWhileNotRunning(gstAudioOutput.getPipeline(), [&] { - qUnlinkGstElements(audioVolume, audioSink); - gstAudioOutput.stopAndRemoveElements(audioSink); - audioSink = std::move(newSink); - gstAudioOutput.add(audioSink); - audioSink.syncStateWithParent(); - qLinkGstElements(audioVolume, audioSink); + QGstElement newSink = createGstElement(); + newSink.set("async", false); // no async state changes + + m_audioVolume.src().modifyPipelineInIdleProbe([&] { + qUnlinkGstElements(m_audioVolume, m_audioSink); + m_audioOutputBin.stopAndRemoveElements(m_audioSink); + m_audioSink = std::move(newSink); + m_audioOutputBin.add(m_audioSink); + m_audioSink.syncStateWithParent(); + qLinkGstElements(m_audioVolume, m_audioSink); }); } diff --git a/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput_p.h b/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput_p.h index 4b528d9ee..da11c39d2 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput_p.h @@ -16,13 +16,9 @@ // #include <QtCore/qobject.h> -#include <QtMultimedia/private/qmultimediautils_p.h> #include <QtMultimedia/private/qplatformaudiooutput_p.h> -#include <QtMultimedia/private/qtmultimediaglobal_p.h> -#include <QtMultimedia/qaudiodevice.h> #include <common/qgst_p.h> -#include <common/qgstpipeline_p.h> QT_BEGIN_NAMESPACE @@ -38,23 +34,23 @@ public: void setVolume(float) override; void setMuted(bool) override; - QGstElement gstElement() const { return gstAudioOutput; } + QGstElement gstElement() const { return m_audioOutputBin; } private: - QGstreamerAudioOutput(QGstElement audioconvert, QGstElement audioresample, QGstElement volume, - QGstElement autoaudiosink, QAudioOutput *parent); + explicit QGstreamerAudioOutput(QAudioOutput *parent); - QAudioDevice m_audioOutput; + QGstElement createGstElement(); + + QAudioDevice m_audioDevice; // Gst elements - QGstPipeline gstPipeline; - QGstBin gstAudioOutput; - - QGstElement audioQueue; - QGstElement audioConvert; - QGstElement audioResample; - QGstElement audioVolume; - QGstElement audioSink; + QGstBin m_audioOutputBin; + + QGstElement m_audioQueue; + QGstElement m_audioConvert; + QGstElement m_audioResample; + QGstElement m_audioVolume; + QGstElement m_audioSink; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp index 5fda7c409..ce193bc11 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer.cpp @@ -15,23 +15,21 @@ #include <qgstreamerformatinfo_p.h> #include <QtMultimedia/qaudiodevice.h> -#include <QtCore/qdir.h> -#include <QtCore/qsocketnotifier.h> #include <QtCore/qurl.h> #include <QtCore/qdebug.h> #include <QtCore/qloggingcategory.h> #include <QtCore/private/quniquehandle_p.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> +#if QT_CONFIG(gstreamer_gl) +# include <gst/gl/gl.h> +#endif static Q_LOGGING_CATEGORY(qLcMediaPlayer, "qt.multimedia.player") QT_BEGIN_NAMESPACE QGstreamerMediaPlayer::TrackSelector::TrackSelector(TrackType type, QGstElement selector) - : selector(selector), type(type) + : inputSelector(selector), type(type) { selector.set("sync-streams", true); selector.set("sync-mode", 1 /*clock*/); @@ -42,7 +40,7 @@ QGstreamerMediaPlayer::TrackSelector::TrackSelector(TrackType type, QGstElement QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad() { - auto pad = selector.getRequestPad("sink_%u"); + auto pad = inputSelector.getRequestPad("sink_%u"); tracks.append(pad); return pad; } @@ -50,13 +48,13 @@ QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad() void QGstreamerMediaPlayer::TrackSelector::removeAllInputPads() { for (auto &pad : tracks) - selector.releaseRequestPad(pad); + inputSelector.releaseRequestPad(pad); tracks.clear(); } -void QGstreamerMediaPlayer::TrackSelector::removeInputPad(QGstPad pad) +void QGstreamerMediaPlayer::TrackSelector::removeInputPad(const QGstPad &pad) { - selector.releaseRequestPad(pad); + inputSelector.releaseRequestPad(pad); tracks.removeOne(pad); } @@ -67,6 +65,26 @@ QGstPad QGstreamerMediaPlayer::TrackSelector::inputPad(int index) return {}; } +int QGstreamerMediaPlayer::TrackSelector::activeInputIndex() const +{ + return isConnected ? tracks.indexOf(activeInputPad()) : -1; +} + +QGstPad QGstreamerMediaPlayer::TrackSelector::activeInputPad() const +{ + return isConnected ? QGstPad{ inputSelector.getGstObject("active-pad") } : QGstPad{}; +} + +void QGstreamerMediaPlayer::TrackSelector::setActiveInputPad(const QGstPad &input) +{ + inputSelector.set("active-pad", input); +} + +int QGstreamerMediaPlayer::TrackSelector::trackCount() const +{ + return tracks.count(); +} + QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(TrackType type) { auto &ts = trackSelectors[type]; @@ -74,6 +92,23 @@ QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(Track return ts; } +void QGstreamerMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status) +{ + if (status != QMediaPlayer::StalledMedia) + m_stalledMediaNotifier.stop(); + + QPlatformMediaPlayer::mediaStatusChanged(status); +} + +void QGstreamerMediaPlayer::updateBufferProgress(float newProgress) +{ + if (qFuzzyIsNull(newProgress - m_bufferProgress)) + return; + + m_bufferProgress = newProgress; + bufferProgressChanged(m_bufferProgress); +} + void QGstreamerMediaPlayer::disconnectDecoderHandlers() { auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{ @@ -92,47 +127,34 @@ QMaybe<QPlatformMediaPlayer *> QGstreamerMediaPlayer::create(QMediaPlayer *paren if (!videoOutput) return videoOutput.error(); - QGstElement videoInputSelector = - QGstElement::createFromFactory("input-selector", "videoInputSelector"); - if (!videoInputSelector) - return errorMessageCannotFindElement("input-selector"); + static const auto error = + qGstErrorMessageIfElementsNotAvailable("input-selector", "decodebin", "uridecodebin"); + if (error) + return *error; - QGstElement audioInputSelector = - QGstElement::createFromFactory("input-selector", "audioInputSelector"); - if (!audioInputSelector) - return errorMessageCannotFindElement("input-selector"); - - QGstElement subTitleInputSelector = - QGstElement::createFromFactory("input-selector", "subTitleInputSelector"); - if (!subTitleInputSelector) - return errorMessageCannotFindElement("input-selector"); - - return new QGstreamerMediaPlayer(videoOutput.value(), videoInputSelector, audioInputSelector, - subTitleInputSelector, parent); + return new QGstreamerMediaPlayer(videoOutput.value(), parent); } QGstreamerMediaPlayer::QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput, - QGstElement videoInputSelector, - QGstElement audioInputSelector, - QGstElement subTitleInputSelector, QMediaPlayer *parent) : QObject(parent), QPlatformMediaPlayer(parent), - trackSelectors{ { { VideoStream, videoInputSelector }, - { AudioStream, audioInputSelector }, - { SubtitleStream, subTitleInputSelector } } }, + trackSelectors{ { + { VideoStream, + QGstElement::createFromFactory("input-selector", "videoInputSelector") }, + { AudioStream, + QGstElement::createFromFactory("input-selector", "audioInputSelector") }, + { SubtitleStream, + QGstElement::createFromFactory("input-selector", "subTitleInputSelector") }, + } }, playerPipeline(QGstPipeline::create("playerPipeline")), gstVideoOutput(videoOutput) { - playerPipeline.setFlushOnConfigChanges(true); - gstVideoOutput->setParent(this); - gstVideoOutput->setPipeline(playerPipeline); for (auto &ts : trackSelectors) - playerPipeline.add(ts.selector); + playerPipeline.add(ts.inputSelector); - playerPipeline.setState(GST_STATE_NULL); playerPipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this)); playerPipeline.installMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this)); @@ -143,7 +165,12 @@ QGstreamerMediaPlayer::QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput, gst_pipeline_use_clock(playerPipeline.pipeline(), systemClock.get()); connect(&positionUpdateTimer, &QTimer::timeout, this, [this] { - updatePosition(); + updatePositionFromPipeline(); + }); + + m_stalledMediaNotifier.setSingleShot(true); + connect(&m_stalledMediaNotifier, &QTimer::timeout, this, [this] { + mediaStatusChanged(QMediaPlayer::StalledMedia); }); } @@ -152,25 +179,45 @@ QGstreamerMediaPlayer::~QGstreamerMediaPlayer() playerPipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this)); playerPipeline.removeMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this)); playerPipeline.setStateSync(GST_STATE_NULL); - topology.free(); } -qint64 QGstreamerMediaPlayer::position() const +std::chrono::nanoseconds QGstreamerMediaPlayer::pipelinePosition() const { - if (playerPipeline.isNull() || m_url.isEmpty()) - return 0; + if (!hasMedia()) + return {}; - return playerPipeline.position()/1e6; + Q_ASSERT(playerPipeline); + return playerPipeline.position(); +} + +void QGstreamerMediaPlayer::updatePositionFromPipeline() +{ + using namespace std::chrono; + + positionChanged(round<milliseconds>(pipelinePosition())); +} + +void QGstreamerMediaPlayer::updateDurationFromPipeline() +{ + std::optional<std::chrono::milliseconds> duration = playerPipeline.durationInMs(); + if (!duration) + duration = std::chrono::milliseconds{ -1 }; + + if (duration != m_duration) { + qCDebug(qLcMediaPlayer) << "updateDurationFromPipeline" << (*duration).count(); + m_duration = *duration; + durationChanged(m_duration); + } } qint64 QGstreamerMediaPlayer::duration() const { - return m_duration; + return m_duration.count(); } float QGstreamerMediaPlayer::bufferProgress() const { - return m_bufferProgress/100.; + return m_bufferProgress; } QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const @@ -180,24 +227,46 @@ QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const qreal QGstreamerMediaPlayer::playbackRate() const { - return playerPipeline.playbackRate(); + return m_rate; } void QGstreamerMediaPlayer::setPlaybackRate(qreal rate) { - bool applyRateToPipeline = state() != QMediaPlayer::StoppedState; - if (playerPipeline.setPlaybackRate(rate, applyRateToPipeline)) - playbackRateChanged(rate); + if (rate == m_rate) + return; + + m_rate = rate; + + // workaround for workaround for + // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3545 + playerPipeline.waitForAsyncStateChangeComplete(); + if (playerPipeline.state() < GST_STATE_PLAYING) + m_pendingRate = m_rate; + else { + + // another workaround for workaround for + // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3545 + // waiting until we can query position and duration before we set the playback rate. + + playerPipeline.queryPositionAndDuration(); + playerPipeline.setPlaybackRate(m_rate); + } + playbackRateChanged(rate); } void QGstreamerMediaPlayer::setPosition(qint64 pos) { - qint64 currentPos = playerPipeline.position()/1e6; - if (pos == currentPos) + std::chrono::milliseconds posInMs{ pos }; + setPosition(posInMs); +} + +void QGstreamerMediaPlayer::setPosition(std::chrono::milliseconds pos) +{ + if (pos == playerPipeline.position()) return; playerPipeline.finishStateChange(); - playerPipeline.setPosition(pos*1e6); - qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos << playerPipeline.position()/1e6; + playerPipeline.setPosition(pos); + qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos.count() << playerPipeline.positionInMs().count(); if (mediaStatus() == QMediaPlayer::EndOfMedia) mediaStatusChanged(QMediaPlayer::LoadedMedia); positionChanged(pos); @@ -205,65 +274,75 @@ void QGstreamerMediaPlayer::setPosition(qint64 pos) void QGstreamerMediaPlayer::play() { - if (state() == QMediaPlayer::PlayingState || m_url.isEmpty()) + QMediaPlayer::PlaybackState currentState = state(); + if (currentState == QMediaPlayer::PlayingState || !hasMedia()) return; - resetCurrentLoop(); - playerPipeline.setInStoppedState(false); + if (currentState != QMediaPlayer::PausedState) + resetCurrentLoop(); + + gstVideoOutput->setActive(true); if (mediaStatus() == QMediaPlayer::EndOfMedia) { - playerPipeline.setPosition(0); - updatePosition(); + playerPipeline.setPosition({}); + positionChanged(0); + mediaStatusChanged(QMediaPlayer::LoadedMedia); } - qCDebug(qLcMediaPlayer) << "play()."; - int ret = playerPipeline.setState(GST_STATE_PLAYING); - if (m_requiresSeekOnPlay) { - // Flushing the pipeline is required to get track changes - // immediately, when they happen while paused. + qCDebug(qLcMediaPlayer) << "play()"; + bool success = playerPipeline.setStateSync(GST_STATE_PLAYING); + if (!success) { + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state."; + return; + } + + // workaround for workaround for + // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3545 + if (m_pendingRate) { + playerPipeline.setPlaybackRate(*m_pendingRate, /*forceFlushingSeek=*/true); + m_pendingRate = std::nullopt; + } else { playerPipeline.flush(); - m_requiresSeekOnPlay = false; } - if (ret == GST_STATE_CHANGE_FAILURE) - qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state."; positionUpdateTimer.start(100); - emit stateChanged(QMediaPlayer::PlayingState); + stateChanged(QMediaPlayer::PlayingState); } void QGstreamerMediaPlayer::pause() { - if (state() == QMediaPlayer::PausedState || m_url.isEmpty() + using namespace std::chrono_literals; + + if (state() == QMediaPlayer::PausedState || !hasMedia() || m_resourceErrorState != ResourceErrorState::NoError) return; positionUpdateTimer.stop(); - if (playerPipeline.inStoppedState()) { - playerPipeline.setInStoppedState(false); - playerPipeline.flush(); - } - int ret = playerPipeline.setState(GST_STATE_PAUSED); + + gstVideoOutput->setActive(true); + int ret = playerPipeline.setStateSync(GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_FAILURE) qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state."; if (mediaStatus() == QMediaPlayer::EndOfMedia) { - playerPipeline.setPosition(0); + playerPipeline.setPosition(0ms); + positionChanged(0); + } else { + updatePositionFromPipeline(); } - updatePosition(); - emit stateChanged(QMediaPlayer::PausedState); + stateChanged(QMediaPlayer::PausedState); if (m_bufferProgress > 0 || !canTrackProgress()) mediaStatusChanged(QMediaPlayer::BufferedMedia); else mediaStatusChanged(QMediaPlayer::BufferingMedia); - - emit bufferProgressChanged(m_bufferProgress / 100.); } void QGstreamerMediaPlayer::stop() { + using namespace std::chrono_literals; if (state() == QMediaPlayer::StoppedState) { if (position() != 0) { - playerPipeline.setPosition(0); - positionChanged(0); + playerPipeline.setPosition({}); + positionChanged(0ms); mediaStatusChanged(QMediaPlayer::LoadedMedia); } return; @@ -271,259 +350,352 @@ void QGstreamerMediaPlayer::stop() stopOrEOS(false); } -void *QGstreamerMediaPlayer::nativePipeline() +const QGstPipeline &QGstreamerMediaPlayer::pipeline() const { - return playerPipeline.pipeline(); + return playerPipeline; +} + +bool QGstreamerMediaPlayer::canPlayQrc() const +{ + return true; } void QGstreamerMediaPlayer::stopOrEOS(bool eos) { + using namespace std::chrono_literals; + positionUpdateTimer.stop(); - playerPipeline.setInStoppedState(true); + gstVideoOutput->setActive(false); bool ret = playerPipeline.setStateSync(GST_STATE_PAUSED); if (!ret) qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state."; - if (!eos) - playerPipeline.setPosition(0); - updatePosition(); - emit stateChanged(QMediaPlayer::StoppedState); + if (!eos) { + playerPipeline.setPosition(0ms); + positionChanged(0ms); + } + stateChanged(QMediaPlayer::StoppedState); if (eos) mediaStatusChanged(QMediaPlayer::EndOfMedia); else mediaStatusChanged(QMediaPlayer::LoadedMedia); m_initialBufferProgressSent = false; + bufferProgressChanged(0.f); +} + +void QGstreamerMediaPlayer::detectPipelineIsSeekable() +{ + std::optional<bool> canSeek = playerPipeline.canSeek(); + if (canSeek) { + qCDebug(qLcMediaPlayer) << "detectPipelineIsSeekable: pipeline is seekable:" << *canSeek; + seekableChanged(*canSeek); + } else { + qCWarning(qLcMediaPlayer) << "detectPipelineIsSeekable: query for seekable failed."; + seekableChanged(false); + } +} + +QGstElement QGstreamerMediaPlayer::getSinkElementForTrackType(TrackType trackType) +{ + switch (trackType) { + case AudioStream: + return gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{}; + case VideoStream: + return gstVideoOutput->gstElement(); + case SubtitleStream: + return gstVideoOutput->gstSubtitleElement(); + break; + default: + Q_UNREACHABLE_RETURN(QGstElement{}); + } +} + +bool QGstreamerMediaPlayer::hasMedia() const +{ + return !m_url.isEmpty() || m_stream; } bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message) { - qCDebug(qLcMediaPlayer) << "received bus message:" << message; + constexpr bool traceBusMessages = true; + if (traceBusMessages) + qCDebug(qLcMediaPlayer) << "received bus message:" << message; - GstMessage* gm = message.message(); switch (message.type()) { - case GST_MESSAGE_TAG: { - // #### This isn't ideal. We shouldn't catch stream specific tags here, rather the global ones - QGstTagListHandle tagList; - gst_message_parse_tag(gm, &tagList); - - qCDebug(qLcMediaPlayer) << " Got tags: " << tagList.get(); - auto metaData = QGstreamerMetaData::fromGstTagList(tagList.get()); - for (auto k : metaData.keys()) - m_metaData.insert(k, metaData.value(k)); - - if (gstVideoOutput) { - QVariant rotation = m_metaData.value(QMediaMetaData::Orientation); - gstVideoOutput->setRotation(rotation.value<QtVideo::Rotation>()); - } + case GST_MESSAGE_TAG: + // #### This isn't ideal. We shouldn't catch stream specific tags here, rather the global + // ones + return processBusMessageTags(message); + + case GST_MESSAGE_DURATION_CHANGED: + return processBusMessageDurationChanged(message); + + case GST_MESSAGE_EOS: + return processBusMessageEOS(message); + + case GST_MESSAGE_BUFFERING: + return processBusMessageBuffering(message); + + case GST_MESSAGE_STATE_CHANGED: + return processBusMessageStateChanged(message); + + case GST_MESSAGE_ERROR: + return processBusMessageError(message); + + case GST_MESSAGE_WARNING: + return processBusMessageWarning(message); + + case GST_MESSAGE_INFO: + return processBusMessageInfo(message); + + case GST_MESSAGE_SEGMENT_START: + return processBusMessageSegmentStart(message); + + case GST_MESSAGE_ELEMENT: + return processBusMessageElement(message); + + case GST_MESSAGE_ASYNC_DONE: + return processBusMessageAsyncDone(message); + + case GST_MESSAGE_LATENCY: + return processBusMessageLatency(message); + + default: +// qCDebug(qLcMediaPlayer) << " default message handler, doing nothing"; break; } - case GST_MESSAGE_DURATION_CHANGED: { - qint64 d = playerPipeline.duration()/1e6; - qCDebug(qLcMediaPlayer) << " duration changed message" << d; - if (d != m_duration) { - m_duration = d; - emit durationChanged(duration()); + + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageTags(const QGstreamerMessage &message) +{ + QGstTagListHandle tagList; + gst_message_parse_tag(message.message(), &tagList); + + qCDebug(qLcMediaPlayer) << " Got tags: " << tagList.get(); + + QMediaMetaData originalMetaData = m_metaData; + extendMetaDataFromTagList(m_metaData, tagList); + if (originalMetaData != m_metaData) + metaDataChanged(); + + QVariant rotation = m_metaData.value(QMediaMetaData::Orientation); + gstVideoOutput->setRotation(rotation.value<QtVideo::Rotation>()); + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageDurationChanged(const QGstreamerMessage &) +{ + if (!prerolling) + updateDurationFromPipeline(); + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageBuffering(const QGstreamerMessage &message) +{ + int progress = 0; + gst_message_parse_buffering(message.message(), &progress); + + if (state() != QMediaPlayer::StoppedState && !prerolling) { + if (!m_initialBufferProgressSent) { + mediaStatusChanged(QMediaPlayer::BufferingMedia); + m_initialBufferProgressSent = true; } + + if (m_bufferProgress > 0 && progress == 0) { + m_stalledMediaNotifier.start(stalledMediaDebouncePeriod); + } else if (progress >= 50) + // QTBUG-124517: rethink buffering + mediaStatusChanged(QMediaPlayer::BufferedMedia); + else + mediaStatusChanged(QMediaPlayer::BufferingMedia); + } + + updateBufferProgress(progress * 0.01); + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageEOS(const QGstreamerMessage &) +{ + positionChanged(m_duration); + if (doLoop()) { + setPosition(0); return false; } - case GST_MESSAGE_EOS: - if (doLoop()) { - setPosition(0); - break; - } - stopOrEOS(true); + stopOrEOS(true); + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageStateChanged(const QGstreamerMessage &message) +{ + if (message.source() != playerPipeline) + return false; + + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(message.message(), &oldState, &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_MESSAGE_BUFFERING: { - int progress = 0; - gst_message_parse_buffering(gm, &progress); + case GST_STATE_PAUSED: { + if (prerolling) { + qCDebug(qLcMediaPlayer) << "Preroll done, setting status to Loaded"; + playerPipeline.dumpGraph("playerPipelinePrerollDone"); - qCDebug(qLcMediaPlayer) << " buffering message: " << progress; + prerolling = false; - if (state() != QMediaPlayer::StoppedState && !prerolling) { - if (!m_initialBufferProgressSent) { - mediaStatusChanged(QMediaPlayer::BufferingMedia); - m_initialBufferProgressSent = true; - } + updateDurationFromPipeline(); - 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_metaData.insert(QMediaMetaData::Duration, duration()); + if (!m_url.isEmpty()) + m_metaData.insert(QMediaMetaData::Url, m_url); + parseStreamsAndMetadata(); + metaDataChanged(); - m_bufferProgress = progress; + tracksChanged(); + mediaStatusChanged(QMediaPlayer::LoadedMedia); - emit bufferProgressChanged(m_bufferProgress / 100.); - break; - } - case GST_MESSAGE_STATE_CHANGED: { - if (message.source() != playerPipeline) - return false; - - GstState oldState; - GstState newState; - GstState pending; - - gst_message_parse_state_changed(gm, &oldState, &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: { - 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"); - - qint64 d = playerPipeline.duration() / 1e6; - if (d != m_duration) { - m_duration = d; - qCDebug(qLcMediaPlayer) << " duration changed" << d; - emit durationChanged(duration()); - } - - parseStreamsAndMetadata(); - - emit tracksChanged(); - mediaStatusChanged(QMediaPlayer::LoadedMedia); - - GstQuery *query = gst_query_new_seeking(GST_FORMAT_TIME); - gboolean canSeek = false; - if (gst_element_query(playerPipeline.element(), query)) { - gst_query_parse_seeking(query, nullptr, &canSeek, nullptr, nullptr); - qCDebug(qLcMediaPlayer) << " pipeline is seekable:" << canSeek; - } else { - qCDebug(qLcMediaPlayer) << " query for seekable failed."; - } - 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); - } - } + if (state() == QMediaPlayer::PlayingState) { + Q_ASSERT(!m_initialBufferProgressSent); - break; - } - 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_STATE_PLAYING: { + if (!m_initialBufferProgressSent) { + bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0; + mediaStatusChanged(QMediaPlayer::BufferingMedia); + m_initialBufferProgressSent = true; + if (immediatelySendBuffered) + mediaStatusChanged(QMediaPlayer::BufferedMedia); } break; } - case GST_MESSAGE_ERROR: { - qCDebug(qLcMediaPlayer) << " error" << QCompactGstMessageAdaptor(message); - - QUniqueGErrorHandle err; - QUniqueGStringHandle debug; - gst_message_parse_error(gm, &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) - error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>")); - else { - 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 - error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message)); - m_resourceErrorState = ResourceErrorState::ErrorReported; - m_url.clear(); - } - } else { + } + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageError(const QGstreamerMessage &message) +{ + qCDebug(qLcMediaPlayer) << " error" << QCompactGstMessageAdaptor(message); + + QUniqueGErrorHandle err; + QUniqueGStringHandle debug; + gst_message_parse_error(message.message(), &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) + error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>")); + else { + 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 error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message)); + m_resourceErrorState = ResourceErrorState::ErrorReported; + m_url.clear(); + m_stream = nullptr; } } else { - playerPipeline.dumpGraph("error"); + error(QMediaPlayer::ResourceError, QString::fromUtf8(err.get()->message)); } - mediaStatusChanged(QMediaPlayer::InvalidMedia); - break; + } else { + playerPipeline.dumpGraph("error"); } + mediaStatusChanged(QMediaPlayer::InvalidMedia); + return false; +} - case GST_MESSAGE_WARNING: - qCWarning(qLcMediaPlayer) << "Warning:" << QCompactGstMessageAdaptor(message); - playerPipeline.dumpGraph("warning"); - break; +bool QGstreamerMediaPlayer::processBusMessageWarning(const QGstreamerMessage &message) +{ + qCWarning(qLcMediaPlayer) << "Warning:" << QCompactGstMessageAdaptor(message); + playerPipeline.dumpGraph("warning"); + return false; +} - case GST_MESSAGE_INFO: - if (qLcMediaPlayer().isDebugEnabled()) - qCDebug(qLcMediaPlayer) << "Info:" << QCompactGstMessageAdaptor(message); - break; +bool QGstreamerMediaPlayer::processBusMessageInfo(const QGstreamerMessage &message) +{ + if (qLcMediaPlayer().isDebugEnabled()) + qCDebug(qLcMediaPlayer) << "Info:" << QCompactGstMessageAdaptor(message); + return false; +} - case GST_MESSAGE_SEGMENT_START: { - qCDebug(qLcMediaPlayer) << " segment start message, updating position"; - QGstStructure structure(gst_message_get_structure(gm)); - auto p = structure["position"].toInt64(); - if (p) { - qint64 position = (*p)/1000000; - emit positionChanged(position); - } +bool QGstreamerMediaPlayer::processBusMessageSegmentStart(const QGstreamerMessage &message) +{ + using namespace std::chrono; + gint64 pos; + GstFormat fmt{}; + gst_message_parse_segment_start(message.message(), &fmt, &pos); + + switch (fmt) { + case GST_FORMAT_TIME: { + auto posNs = std::chrono::nanoseconds{ pos }; + qCDebug(qLcMediaPlayer) << " segment start message, updating position" << posNs.count(); + positionChanged(round<milliseconds>(posNs)); break; } - case GST_MESSAGE_ELEMENT: { - QGstStructure structure(gst_message_get_structure(gm)); - auto type = structure.name(); - if (type == "stream-topology") { - topology.free(); - topology = structure.copy(); - } + default: { + qWarning() << "GST_MESSAGE_SEGMENT_START with unknown format" << fmt << pos; break; } + } - default: -// qCDebug(qLcMediaPlayer) << " default message handler, doing nothing"; + return false; +} - break; - } +bool QGstreamerMediaPlayer::processBusMessageElement(const QGstreamerMessage &message) +{ + QGstStructureView structure(gst_message_get_structure(message.message())); + auto type = structure.name(); + if (type == "stream-topology") + topology = structure.clone(); + return false; +} + +bool QGstreamerMediaPlayer::processBusMessageAsyncDone(const QGstreamerMessage &) +{ + if (playerPipeline.state() >= GST_STATE_PAUSED) + detectPipelineIsSeekable(); return false; } +bool QGstreamerMediaPlayer::processBusMessageLatency(const QGstreamerMessage &) +{ + playerPipeline.recalculateLatency(); + return false; +} + bool QGstreamerMediaPlayer::processSyncMessage(const QGstreamerMessage &message) { -#if QT_CONFIG(gstreamer_gl) - if (message.type() != GST_MESSAGE_NEED_CONTEXT) - return false; - const gchar *type = nullptr; - gst_message_parse_context_type (message.message(), &type); - if (strcmp(type, GST_GL_DISPLAY_CONTEXT_TYPE)) - return false; - if (!gstVideoOutput || !gstVideoOutput->gstreamerVideoSink()) - return false; - auto *context = gstVideoOutput->gstreamerVideoSink()->gstGlDisplayContext(); - if (!context) + // GStreamer thread! + + constexpr bool traceSyncMessages = false; + if (traceSyncMessages) + qCDebug(qLcMediaPlayer) << "received sync message:" << message; + + switch (message.type()) { + default: return false; - gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message.message())), context); - playerPipeline.dumpGraph("need_context"); - return true; -#else - Q_UNUSED(message); - return false; -#endif + } } QUrl QGstreamerMediaPlayer::media() const @@ -568,21 +740,21 @@ void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPa if (ts.trackCount() == 1) { if (streamType == VideoStream) { - connectOutput(ts); ts.setActiveInputPad(sinkPad); - emit videoAvailableChanged(true); + connectTrackSelectorToOutput(ts); + videoAvailableChanged(true); } else if (streamType == AudioStream) { - connectOutput(ts); ts.setActiveInputPad(sinkPad); - emit audioAvailableChanged(true); + connectTrackSelectorToOutput(ts); + audioAvailableChanged(true); } } if (!prerolling) - emit tracksChanged(); + tracksChanged(); - decoderOutputMap.insert(pad.name(), sinkPad); + decoderOutputMap.emplace(pad, sinkPad); } void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGstPad &pad) @@ -591,20 +763,25 @@ void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGst return; qCDebug(qLcMediaPlayer) << "Removed pad" << pad.name() << "from" << src.name(); - auto track = decoderOutputMap.value(pad.name()); - if (track.isNull()) + + auto it = decoderOutputMap.find(pad); + if (it == decoderOutputMap.end()) return; + QGstPad track = it->second; auto ts = std::find_if(std::begin(trackSelectors), std::end(trackSelectors), - [&](TrackSelector &ts){ return ts.selector == track.parent(); }); + [&](TrackSelector &ts) { + return ts.inputSelector == track.parent(); + }); if (ts == std::end(trackSelectors)) return; - qCDebug(qLcMediaPlayer) << " was linked to pad" << track.name() << "from" << ts->selector.name(); + qCDebug(qLcMediaPlayer) << " was linked to pad" << track.name() << "from" + << ts->inputSelector.name(); ts->removeInputPad(track); if (ts->trackCount() == 0) { - removeOutput(*ts); + disconnectTrackSelectorFromOutput(*ts); if (ts->type == AudioStream) audioAvailableChanged(false); else if (ts->type == VideoStream) @@ -615,69 +792,39 @@ void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGst tracksChanged(); } -void QGstreamerMediaPlayer::removeAllOutputs() +void QGstreamerMediaPlayer::disconnectAllTrackSelectors() { for (auto &ts : trackSelectors) { - removeOutput(ts); + disconnectTrackSelectorFromOutput(ts); ts.removeAllInputPads(); } audioAvailableChanged(false); videoAvailableChanged(false); } -void QGstreamerMediaPlayer::connectOutput(TrackSelector &ts) +void QGstreamerMediaPlayer::connectTrackSelectorToOutput(TrackSelector &ts) { if (ts.isConnected) return; - QGstElement e; - switch (ts.type) { - case AudioStream: - e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{}; - break; - case VideoStream: - e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{}; - break; - case SubtitleStream: - if (gstVideoOutput) - gstVideoOutput->linkSubtitleStream(ts.selector); - break; - default: - return; - } - - if (!e.isNull()) { + QGstElement e = getSinkElementForTrackType(ts.type); + if (e) { qCDebug(qLcMediaPlayer) << "connecting output for track type" << ts.type; playerPipeline.add(e); - qLinkGstElements(ts.selector, e); - e.setState(GST_STATE_PAUSED); + qLinkGstElements(ts.inputSelector, e); + e.syncStateWithParent(); } ts.isConnected = true; } -void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts) +void QGstreamerMediaPlayer::disconnectTrackSelectorFromOutput(TrackSelector &ts) { if (!ts.isConnected) return; - QGstElement e; - switch (ts.type) { - case AudioStream: - e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{}; - break; - case VideoStream: - e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{}; - break; - case SubtitleStream: - if (gstVideoOutput) - gstVideoOutput->unlinkSubtitleStream(); - break; - default: - break; - } - - if (!e.isNull()) { + QGstElement e = getSinkElementForTrackType(ts.type); + if (e) { qCDebug(qLcMediaPlayer) << "removing output for track type" << ts.type; playerPipeline.stopAndRemoveElements(e); } @@ -687,15 +834,13 @@ void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts) void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement * /*uridecodebin*/, GstElement *child, - QGstreamerMediaPlayer *) + QGstreamerMediaPlayer * /*self*/) { QGstElement c(child, QGstElement::NeedsRef); qCDebug(qLcMediaPlayer) << "New element added to uridecodebin:" << c.name(); static const GType decodeBinType = [] { - QGstElementFactoryHandle factory = QGstElementFactoryHandle{ - gst_element_factory_find("decodebin"), - }; + QGstElementFactoryHandle factory = QGstElement::findFactory("decodebin"); return gst_element_factory_get_element_type(factory.get()); }(); @@ -705,14 +850,22 @@ void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement * /*urid } } -void QGstreamerMediaPlayer::sourceSetupCallback(GstElement *uridecodebin, GstElement *source, QGstreamerMediaPlayer *that) +void QGstreamerMediaPlayer::sourceSetupCallback(GstElement *uridecodebin, GstElement *source, + QGstreamerMediaPlayer *self) { Q_UNUSED(uridecodebin) - Q_UNUSED(that) + Q_UNUSED(self) - qCDebug(qLcMediaPlayer) << "Setting up source:" << g_type_name_from_instance((GTypeInstance*)source); + const gchar *typeName = g_type_name_from_instance((GTypeInstance *)source); + qCDebug(qLcMediaPlayer) << "Setting up source:" << typeName; - if (std::string_view("GstRTSPSrc") == g_type_name_from_instance((GTypeInstance *)source)) { + if (typeName == std::string_view("GstAppSrc")) { + QGstAppSource::attachQIODeviceToGstAppSrc(qGstCheckedCast<GstAppSrc>(source), + self->m_stream); + return; + } + + if (typeName == std::string_view("GstRTSPSrc")) { QGstElement s(source, QGstElement::NeedsRef); int latency{40}; bool ok{false}; @@ -754,16 +907,12 @@ 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"), - }; + QGstElementFactoryHandle factory = QGstElement::findFactory("queue"); return gst_element_factory_get_element_type(factory.get()); }(); static const GType multiQueueType = [] { - QGstElementFactoryHandle factory = QGstElementFactoryHandle{ - gst_element_factory_find("multiqueue"), - }; + QGstElementFactoryHandle factory = QGstElement::findFactory("multiqueue"); return gst_element_factory_get_element_type(factory.get()); }(); @@ -774,7 +923,11 @@ void QGstreamerMediaPlayer::decodebinElementAddedCallback(GstBin * /*decodebin*/ GstBin * /*sub_bin*/, GstElement *child, QGstreamerMediaPlayer *self) { + // gstreamer thread! + QGstElement c(child, QGstElement::NeedsRef); + qCDebug(qLcMediaPlayer) << "decodebinElementAddedCallback:" << c.name() << c.typeName(); + if (isQueue(c)) self->decodeBinQueues += 1; } @@ -783,13 +936,18 @@ void QGstreamerMediaPlayer::decodebinElementRemovedCallback(GstBin * /*decodebin GstBin * /*sub_bin*/, GstElement *child, QGstreamerMediaPlayer *self) { + using namespace Qt::Literals; QGstElement c(child, QGstElement::NeedsRef); + qCDebug(qLcMediaPlayer) << "decodebinElementRemovedCallback:" << c.name() << c.typeName(); + if (isQueue(c)) self->decodeBinQueues -= 1; } void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream) { + using namespace std::chrono_literals; + qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content; prerolling = true; @@ -802,112 +960,86 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream) m_url = content; m_stream = stream; - if (!src.isNull()) - playerPipeline.remove(src); - if (!decoder.isNull()) - playerPipeline.remove(decoder); - src = QGstElement(); + if (decoder) { + playerPipeline.stopAndRemoveElements(decoder); + decoder = QGstElement{}; + } + disconnectDecoderHandlers(); - decoder = QGstElement(); - removeAllOutputs(); + disconnectAllTrackSelectors(); seekableChanged(false); - Q_ASSERT(playerPipeline.inStoppedState()); - if (m_duration != 0) { - m_duration = 0; - durationChanged(0); + if (m_duration != 0ms) { + m_duration = 0ms; + durationChanged(0ms); } stateChanged(QMediaPlayer::StoppedState); if (position() != 0) - positionChanged(0); + positionChanged(0ms); if (!m_metaData.isEmpty()) { m_metaData.clear(); metaDataChanged(); } - if (content.isEmpty() && !stream) + if (content.isEmpty() && !stream) { mediaStatusChanged(QMediaPlayer::NoMedia); + return; + } - if (content.isEmpty()) + decoder = QGstElement::createFromFactory("uridecodebin", "decoder"); + if (!decoder) { + error(QMediaPlayer::ResourceError, qGstErrorMessageCannotFindElement("uridecodebin")); return; + } - if (m_stream) { - if (!m_appSrc) { - auto maybeAppSrc = QGstAppSource::create(this); - if (maybeAppSrc) { - m_appSrc = maybeAppSrc.value(); - } else { - error(QMediaPlayer::ResourceError, maybeAppSrc.error()); - return; - } - } - src = m_appSrc->element(); - decoder = QGstElement::createFromFactory("decodebin", "decoder"); - if (!decoder) { - error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("decodebin")); - return; - } + playerPipeline.add(decoder); + + constexpr bool hasPostStreamTopology = GST_CHECK_VERSION(1, 22, 0); + if constexpr (hasPostStreamTopology) { decoder.set("post-stream-topology", true); - decoder.set("use-buffering", true); - 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); - - m_appSrc->setup(m_stream); - seekableChanged(!stream->isSequential()); } else { - // use uridecodebin - decoder = QGstElement::createFromFactory("uridecodebin", "decoder"); - if (!decoder) { - error(QMediaPlayer::ResourceError, errorMessageCannotFindElement("uridecodebin")); - return; - } - playerPipeline.add(decoder); + // can't set post-stream-topology to true, as uridecodebin doesn't have the property. + // Use a hack + uridecodebinElementAdded = + decoder.connect("element-added", GCallback(uridecodebinElementAddedCallback), this); + } - constexpr bool hasPostStreamTopology = GST_CHECK_VERSION(1, 22, 0); - if constexpr (hasPostStreamTopology) { - decoder.set("post-stream-topology", true); - } else { - // can't set post-stream-topology to true, as uridecodebin doesn't have the property. - // Use a hack - uridecodebinElementAdded = decoder.connect( - "element-added", GCallback(uridecodebinElementAddedCallback), this); - } + sourceSetup = decoder.connect("source-setup", GCallback(sourceSetupCallback), this); + unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this); - sourceSetup = decoder.connect("source-setup", GCallback(sourceSetupCallback), this); - unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this); + decoder.set("use-buffering", true); - decoder.set("uri", content.toEncoded().constData()); - decoder.set("use-buffering", true); + constexpr int mb = 1024 * 1024; + decoder.set("ring-buffer-max-size", 2 * mb); - constexpr int mb = 1024 * 1024; - decoder.set("ring-buffer-max-size", 2 * mb); + updateBufferProgress(0.f); - 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(decodebinElementRemovedCallback), this); - 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); - mediaStatusChanged(QMediaPlayer::LoadingMedia); + if (m_stream) { + decoder.set("uri", "appsrc://"); + seekableChanged(!m_stream->isSequential()); + } else { + QByteArray contentUri = content.toEncoded(); + decoder.set("uri", contentUri.constData()); + if (contentUri.startsWith("qrc:")) + seekableChanged(true); // qrc resources are seekable + } - if (!playerPipeline.setState(GST_STATE_PAUSED)) + mediaStatusChanged(QMediaPlayer::LoadingMedia); + if (!playerPipeline.setStateSync(GST_STATE_PAUSED)) { qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the paused state."; + // Note: no further error handling: errors will be delivered via a GstMessage + return; + } - playerPipeline.setPosition(0); - positionChanged(0); + playerPipeline.setPosition(0ms); } void QGstreamerMediaPlayer::setAudioOutput(QPlatformAudioOutput *output) @@ -919,11 +1051,11 @@ void QGstreamerMediaPlayer::setAudioOutput(QPlatformAudioOutput *output) playerPipeline.modifyPipelineWhileNotRunning([&] { if (gstAudioOutput) - removeOutput(ts); + disconnectTrackSelectorFromOutput(ts); gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output); if (gstAudioOutput) - connectOutput(ts); + connectTrackSelectorToOutput(ts); }); } @@ -934,12 +1066,16 @@ QMediaMetaData QGstreamerMediaPlayer::metaData() const void QGstreamerMediaPlayer::setVideoSink(QVideoSink *sink) { + using namespace std::chrono_literals; gstVideoOutput->setVideoSink(sink); + + if (playerPipeline.state(1s) == GstState::GST_STATE_PAUSED) + playerPipeline.flush(); // ensure that we send the current video frame to the new sink } -static QGstStructure endOfChain(const QGstStructure &s) +static QGstStructureView endOfChain(const QGstStructureView &s) { - QGstStructure e = s; + QGstStructureView e = s; while (1) { auto next = e["next"].toStructure(); if (!next.isNull()) @@ -953,32 +1089,26 @@ static QGstStructure endOfChain(const QGstStructure &s) void QGstreamerMediaPlayer::parseStreamsAndMetadata() { qCDebug(qLcMediaPlayer) << "============== parse topology ============"; - if (topology.isNull()) { + + if (!topology) { qCDebug(qLcMediaPlayer) << " null topology"; return; } - auto caps = topology["caps"].toCaps(); - auto structure = caps.at(0); - auto fileFormat = QGstreamerFormatInfo::fileFormatForCaps(structure); - qCDebug(qLcMediaPlayer) << caps << fileFormat; - m_metaData.insert(QMediaMetaData::FileFormat, QVariant::fromValue(fileFormat)); - m_metaData.insert(QMediaMetaData::Duration, duration()); - m_metaData.insert(QMediaMetaData::Url, m_url); - QGValue tags = topology["tags"]; - if (!tags.isNull()) { - QGstTagListHandle tagList; - gst_structure_get(topology.structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr); - - const auto metaData = QGstreamerMetaData::fromGstTagList(tagList.get()); - for (auto k : metaData.keys()) - m_metaData.insert(k, metaData.value(k)); - } - auto demux = endOfChain(topology); - auto next = demux["next"]; + QGstStructureView topologyView{ topology }; + + QGstCaps caps = topologyView.caps(); + extendMetaDataFromCaps(m_metaData, caps); + + QGstTagListHandle tagList = QGstStructureView{ topology }.tags(); + if (tagList) + extendMetaDataFromTagList(m_metaData, tagList); + + QGstStructureView demux = endOfChain(topologyView); + QGValue next = demux["next"]; if (!next.isList()) { qCDebug(qLcMediaPlayer) << " no additional streams"; - emit metaDataChanged(); + metaDataChanged(); return; } @@ -986,43 +1116,28 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata() int size = next.listSize(); for (int i = 0; i < size; ++i) { auto val = next.at(i); - caps = val.toStructure()["caps"].toCaps(); - structure = caps.at(0); - if (structure.name().startsWith("audio/")) { - auto codec = QGstreamerFormatInfo::audioCodecForCaps(structure); - m_metaData.insert(QMediaMetaData::AudioCodec, QVariant::fromValue(codec)); - qCDebug(qLcMediaPlayer) << " audio" << caps << (int)codec; - } else if (structure.name().startsWith("video/")) { - auto codec = QGstreamerFormatInfo::videoCodecForCaps(structure); - m_metaData.insert(QMediaMetaData::VideoCodec, QVariant::fromValue(codec)); - qCDebug(qLcMediaPlayer) << " video" << caps << (int)codec; - auto framerate = structure["framerate"].getFraction(); - if (framerate) - m_metaData.insert(QMediaMetaData::VideoFrameRate, *framerate); - - QSize resolution = structure.resolution(); - if (resolution.isValid()) - m_metaData.insert(QMediaMetaData::Resolution, resolution); + caps = val.toStructure().caps(); + + extendMetaDataFromCaps(m_metaData, caps); + + QGstStructureView structure = caps.at(0); + if (structure.name().startsWith("video/")) { QSize nativeSize = structure.nativeSize(); gstVideoOutput->setNativeSize(nativeSize); } } auto sinkPad = trackSelector(VideoStream).activeInputPad(); - if (!sinkPad.isNull()) { - QGstTagListHandle tagList; - - g_object_get(sinkPad.object(), "tags", &tagList, nullptr); + if (sinkPad) { + QGstTagListHandle tagList = sinkPad.tags(); if (tagList) qCDebug(qLcMediaPlayer) << " tags=" << tagList.get(); else qCDebug(qLcMediaPlayer) << " tags=(null)"; } - qCDebug(qLcMediaPlayer) << "============== end parse topology ============"; - emit metaDataChanged(); playerPipeline.dumpGraph("playback"); } @@ -1034,13 +1149,11 @@ int QGstreamerMediaPlayer::trackCount(QPlatformMediaPlayer::TrackType type) QMediaMetaData QGstreamerMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackType type, int index) { auto track = trackSelector(type).inputPad(index); - if (track.isNull()) + if (!track) return {}; - QGstTagListHandle tagList; - g_object_get(track.object(), "tags", &tagList, nullptr); - - return tagList ? QGstreamerMetaData::fromGstTagList(tagList.get()) : QMediaMetaData{}; + QGstTagListHandle tagList = track.tags(); + return taglistToMetaData(tagList); } int QGstreamerMediaPlayer::activeTrack(TrackType type) @@ -1050,8 +1163,8 @@ int QGstreamerMediaPlayer::activeTrack(TrackType type) void QGstreamerMediaPlayer::setActiveTrack(TrackType type, int index) { - auto &ts = trackSelector(type); - auto track = ts.inputPad(index); + TrackSelector &ts = trackSelector(type); + QGstPad track = ts.inputPad(index); if (track.isNull() && index != -1) { qCWarning(qLcMediaPlayer) << "Attempt to set an incorrect index" << index << "for the track type" << type; @@ -1062,20 +1175,23 @@ void QGstreamerMediaPlayer::setActiveTrack(TrackType type, int index) if (type == QPlatformMediaPlayer::SubtitleStream) gstVideoOutput->flushSubtitles(); + setActivePad(ts, track); +} + +void QGstreamerMediaPlayer::setActivePad(TrackSelector &ts, const QGstPad &pad) +{ playerPipeline.modifyPipelineWhileNotRunning([&] { - if (track.isNull()) { - removeOutput(ts); + if (pad) { + ts.setActiveInputPad(pad); + connectTrackSelectorToOutput(ts); } else { - ts.setActiveInputPad(track); - connectOutput(ts); + disconnectTrackSelectorFromOutput(ts); } }); // seek to force an immediate change of the stream if (playerPipeline.state() == GST_STATE_PLAYING) playerPipeline.flush(); - else - m_requiresSeekOnPlay = true; } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h index 5c33d531f..1686e7bcb 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamermediaplayer_p.h @@ -15,23 +15,22 @@ // We mean it. // -#include <QtCore/qstack.h> -#include <private/qplatformmediaplayer_p.h> -#include <private/qtmultimediaglobal_p.h> -#include <private/qmultimediautils_p.h> -#include <qurl.h> -#include <common/qgst_p.h> -#include <common/qgstpipeline_p.h> +#include <QtMultimedia/private/qplatformmediaplayer_p.h> +#include <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QtMultimedia/private/qmultimediautils_p.h> +#include <QtCore/qurl.h> #include <QtCore/qtimer.h> +#include <common/qgst_bus_p.h> +#include <common/qgst_p.h> +#include <common/qgstpipeline_p.h> + #include <array> QT_BEGIN_NAMESPACE -class QNetworkAccessManager; class QGstreamerMessage; -class QGstAppSource; class QGstreamerAudioOutput; class QGstreamerVideoOutput; @@ -44,7 +43,6 @@ public: static QMaybe<QPlatformMediaPlayer *> create(QMediaPlayer *parent = nullptr); ~QGstreamerMediaPlayer(); - qint64 position() const override; qint64 duration() const override; float bufferProgress() const override; @@ -72,39 +70,32 @@ public: void setActiveTrack(TrackType, int /*streamNumber*/) override; void setPosition(qint64 pos) override; + void setPosition(std::chrono::milliseconds pos); void play() override; void pause() override; void stop() override; - void *nativePipeline() override; + const QGstPipeline &pipeline() const; - bool processBusMessage(const QGstreamerMessage& message) override; - bool processSyncMessage(const QGstreamerMessage& message) override; - - void updatePosition() { positionChanged(position()); } + bool canPlayQrc() const override; private: - QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput, QGstElement videoInputSelector, - QGstElement audioInputSelector, QGstElement subTitleInputSelector, - QMediaPlayer *parent); + QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput, QMediaPlayer *parent); struct TrackSelector { TrackSelector(TrackType, QGstElement selector); QGstPad createInputPad(); - void removeInputPad(QGstPad pad); + void removeInputPad(const QGstPad &pad); void removeAllInputPads(); QGstPad inputPad(int index); - int activeInputIndex() const { return isConnected ? tracks.indexOf(activeInputPad()) : -1; } - QGstPad activeInputPad() const - { - return isConnected ? QGstPad{ selector.getObject("active-pad") } : QGstPad{}; - } - void setActiveInputPad(QGstPad input) { selector.set("active-pad", input); } - int trackCount() const { return tracks.count(); } + int activeInputIndex() const; + QGstPad activeInputPad() const; + void setActiveInputPad(const QGstPad &input); + int trackCount() const; - QGstElement selector; + QGstElement inputSelector; TrackType type; QList<QGstPad> tracks; bool isConnected = false; @@ -114,30 +105,41 @@ 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); + QGstreamerMediaPlayer *); static void sourceSetupCallback(GstElement *uridecodebin, GstElement *source, - QGstreamerMediaPlayer *that); + QGstreamerMediaPlayer *); static void unknownTypeCallback(GstElement *decodebin, GstPad *pad, GstCaps *caps, - QGstreamerMediaPlayer *self); + QGstreamerMediaPlayer *); static void decodebinElementAddedCallback(GstBin *decodebin, GstBin *sub_bin, - GstElement *element, QGstreamerMediaPlayer *self); + GstElement *element, QGstreamerMediaPlayer *); static void decodebinElementRemovedCallback(GstBin *decodebin, GstBin *sub_bin, - GstElement *element, QGstreamerMediaPlayer *self); + GstElement *element, QGstreamerMediaPlayer *); void parseStreamsAndMetadata(); - void connectOutput(TrackSelector &ts); - void removeOutput(TrackSelector &ts); - void removeAllOutputs(); + void connectTrackSelectorToOutput(TrackSelector &); + void disconnectTrackSelectorFromOutput(TrackSelector &); + void disconnectAllTrackSelectors(); + void setActivePad(TrackSelector &, const QGstPad &pad); + void stopOrEOS(bool eos); bool canTrackProgress() const { return decodeBinQueues > 0; } + void detectPipelineIsSeekable(); + bool hasMedia() const; + + std::chrono::nanoseconds pipelinePosition() const; + void updatePositionFromPipeline(); + void updateDurationFromPipeline(); + void updateBufferProgress(float); + + QGstElement getSinkElementForTrackType(TrackType); std::array<TrackSelector, NTrackTypes> trackSelectors; TrackSelector &trackSelector(TrackType type); QMediaMetaData m_metaData; - int m_bufferProgress = 0; QUrl m_url; QIODevice *m_stream = nullptr; @@ -148,27 +150,49 @@ private: }; bool prerolling = false; - bool m_requiresSeekOnPlay = false; bool m_initialBufferProgressSent = false; ResourceErrorState m_resourceErrorState = ResourceErrorState::NoError; - qint64 m_duration = 0; + float m_rate = 1.f; + std::optional<float> m_pendingRate; + float m_bufferProgress = 0.f; + std::chrono::milliseconds m_duration{}; QTimer positionUpdateTimer; - QGstAppSource *m_appSrc = nullptr; - - QGstStructure topology; + QUniqueGstStructureHandle topology; // Gst elements QGstPipeline playerPipeline; - QGstElement src; QGstElement decoder; QGstreamerAudioOutput *gstAudioOutput = nullptr; QGstreamerVideoOutput *gstVideoOutput = nullptr; - // QGstElement streamSynchronizer; + struct QGstPadLess + { + bool operator()(const QGstPad &lhs, const QGstPad &rhs) const + { + return lhs.pad() < rhs.pad(); + } + }; - QHash<QByteArray, QGstPad> decoderOutputMap; + std::map<QGstPad, QGstPad, QGstPadLess> decoderOutputMap; + + // Message handler + bool processBusMessage(const QGstreamerMessage &message) override; + bool processBusMessageTags(const QGstreamerMessage &); + bool processBusMessageDurationChanged(const QGstreamerMessage &); + bool processBusMessageEOS(const QGstreamerMessage &); + bool processBusMessageBuffering(const QGstreamerMessage &); + bool processBusMessageStateChanged(const QGstreamerMessage &); + bool processBusMessageError(const QGstreamerMessage &); + bool processBusMessageWarning(const QGstreamerMessage &); + bool processBusMessageInfo(const QGstreamerMessage &); + bool processBusMessageSegmentStart(const QGstreamerMessage &); + bool processBusMessageElement(const QGstreamerMessage &); + bool processBusMessageAsyncDone(const QGstreamerMessage &); + bool processBusMessageLatency(const QGstreamerMessage &); + + bool processSyncMessage(const QGstreamerMessage &) override; // decoder connections QGObjectHandlerScopedConnection padAdded; @@ -180,6 +204,13 @@ private: QGObjectHandlerScopedConnection elementRemoved; int decodeBinQueues = 0; + + void mediaStatusChanged(QMediaPlayer::MediaStatus status); + static constexpr auto stalledMediaDebouncePeriod = std::chrono::milliseconds{ 500 }; + QTimer m_stalledMediaNotifier; + + static void configureAppSrcElement(GObject *, GObject *, GParamSpec *, + QGstreamerMediaPlayer *self); }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermessage_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamermessage_p.h index 01fe68acb..c253c0b52 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamermessage_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamermessage_p.h @@ -16,7 +16,7 @@ // #include <private/qtmultimediaglobal_p.h> -#include <common/qgst_p.h> +#include "qgst_p.h" QT_BEGIN_NAMESPACE @@ -43,7 +43,7 @@ public: GstMessageType type() const { return GST_MESSAGE_TYPE(get()); } QGstObject source() const { return QGstObject(GST_MESSAGE_SRC(get()), QGstObject::NeedsRef); } - QGstStructure structure() const { return QGstStructure(gst_message_get_structure(get())); } + QGstStructureView structure() const { return QGstStructureView(gst_message_get_structure(get())); } GstMessage *message() const { return get(); } }; diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp index b02e3e40f..cb22be260 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/private/qtvideo_p.h> #include <QtCore/qdebug.h> #include <QtCore/qdatetime.h> #include <QtCore/qlocale.h> @@ -12,9 +13,43 @@ #include <gst/gstversion.h> #include <common/qgst_handle_types_p.h> #include <common/qgstutils_p.h> +#include <qgstreamerformatinfo_p.h> QT_BEGIN_NAMESPACE +RotationResult parseRotationTag(std::string_view tag) +{ + using namespace std::string_view_literals; + Q_ASSERT(!tag.empty()); + + if (tag[0] == 'r') { + if (tag == "rotate-90"sv) + return { QtVideo::Rotation::Clockwise90, false }; + if (tag == "rotate-180"sv) + return { QtVideo::Rotation::Clockwise180, false }; + if (tag == "rotate-270"sv) + return { QtVideo::Rotation::Clockwise270, false }; + if (tag == "rotate-0"sv) + return { QtVideo::Rotation::None, false }; + } + if (tag[0] == 'f') { + // To flip by horizontal axis is the same as to mirror by vertical axis + // and rotate by 180 degrees. + + if (tag == "flip-rotate-90"sv) + return { QtVideo::Rotation::Clockwise270, true }; + if (tag == "flip-rotate-180"sv) + return { QtVideo::Rotation::None, true }; + if (tag == "flip-rotate-270"sv) + return { QtVideo::Rotation::Clockwise90, true }; + if (tag == "flip-rotate-0"sv) + return { QtVideo::Rotation::Clockwise180, true }; + } + + qCritical() << "cannot parse orientation: {}" << tag; + return { QtVideo::Rotation::None, false }; +} + namespace { namespace MetadataLookupImpl { @@ -142,104 +177,201 @@ const char *keyToTag(QMediaMetaData::Key key) #undef constexpr_lookup -//internal -void addTagToMap(const GstTagList *list, const gchar *tag, gpointer user_data) +QDateTime parseDate(const GDate *date) { + 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 parseDate(const GValue &val) +{ + Q_ASSERT(G_VALUE_TYPE(&val) == G_TYPE_DATE); + const GDate *date = (const GDate *)g_value_get_boxed(&val); + return parseDate(date); +} + +QDateTime parseDateTime(const GstDateTime *dateTime) +{ + 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 QDateTime{ + QDate(year, month, day), + QTime(hour, minute, second), + QTimeZone(tz * 60 * 60), + }; +} + +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); + return parseDateTime(dateTime); +} + +QImage parseImage(const GValue &val) +{ + 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 {}; +} + +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); +} + +constexpr std::string_view extendedComment{ GST_TAG_EXTENDED_COMMENT }; + +void addTagsFromExtendedComment(const GstTagList *list, const gchar *tag, QMediaMetaData &metadata) +{ + using namespace Qt::Literals; + assert(tag == extendedComment); + + int entryCount = gst_tag_list_get_tag_size(list, tag); + for (int i = 0; i != entryCount; ++i) { + const GValue *value = gst_tag_list_get_value_index(list, tag, i); + + const QLatin1StringView strValue{ g_value_get_string(value) }; + + auto equalIndex = strValue.indexOf(QLatin1StringView("=")); + if (equalIndex == -1) { + qDebug() << "Cannot parse GST_TAG_EXTENDED_COMMENT entry: " << value; + continue; + } + + const QLatin1StringView key = strValue.first(equalIndex); + const QLatin1StringView valueString = strValue.last(strValue.size() - equalIndex - 1); + + if (key == "DURATION"_L1) { + QUniqueGstDateTimeHandle duration{ + gst_date_time_new_from_iso8601_string(valueString.data()), + }; + + if (duration) { + using namespace std::chrono; + + auto chronoDuration = hours(gst_date_time_get_hour(duration.get())) + + minutes(gst_date_time_get_minute(duration.get())) + + seconds(gst_date_time_get_second(duration.get())) + + microseconds(gst_date_time_get_microsecond(duration.get())); + + metadata.insert(QMediaMetaData::Duration, + QVariant::fromValue(round<milliseconds>(chronoDuration).count())); + } + } + } +} + +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; + if (key == QMediaMetaData::Key::Date) + return; // date/datetime are handled on a higher layer - auto *map = reinterpret_cast<QHash<QMediaMetaData::Key, QVariant>* >(user_data); + if (key == QMediaMetaData::Key(-1)) { + if (tag == extendedComment) + addTagsFromExtendedComment(list, tag, metadata); - GValue val; - val.g_type = 0; + return; + } + + 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 (auto entryCount = gst_tag_list_get_tag_size(list, tag) != 0; entryCount != 1) + qWarning() << "addTagToMetaData: invaled entry count for" << tag << "-" << entryCount; + + 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(QGstUtils::codeToLanguage(str_value))); 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: { + RotationResult result = parseRotationTag(str_value); + metadata.insert(key, QVariant::fromValue(result.rotation)); + 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); } - break; + } else if (type == GST_TYPE_DATE_TIME) { + QDateTime date = parseDateTime(val); + if (date.isValid()) + 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); @@ -247,91 +379,190 @@ void addTagToMap(const GstTagList *list, const gchar *tag, gpointer user_data) } // namespace -QGstreamerMetaData QGstreamerMetaData::fromGstTagList(const GstTagList *tags) +QMediaMetaData taglistToMetaData(const QGstTagListHandle &handle) { - QGstreamerMetaData m; - gst_tag_list_foreach(tags, addTagToMap, &m.data); + QMediaMetaData m; + extendMetaDataFromTagList(m, handle); return m; } - -void QGstreamerMetaData::setMetaData(GstElement *element) const +void extendMetaDataFromTagList(QMediaMetaData &metadata, const QGstTagListHandle &handle) { - if (!GST_IS_TAG_SETTER(element)) - return; + if (handle) { + // gstreamer has both GST_TAG_DATE_TIME and GST_TAG_DATE tags. + // if both are present, we use GST_TAG_DATE_TIME, else we fall back to GST_TAG_DATE + + auto readDateTime = [&]() -> std::optional<QDateTime> { + GstDateTime *dateTimeHandle{}; + gst_tag_list_get_date_time(handle.get(), GST_TAG_DATE_TIME, &dateTimeHandle); + if (dateTimeHandle) { + QDateTime ret = parseDateTime(dateTimeHandle); + gst_date_time_unref(dateTimeHandle); + if (ret.isValid()) + return ret; + } + return std::nullopt; + }; + + auto readDate = [&]() -> std::optional<QDateTime> { + GDate *dateHandle{}; + gst_tag_list_get_date(handle.get(), GST_TAG_DATE, &dateHandle); + if (dateHandle) { + QDateTime ret = parseDate(dateHandle); + g_date_free(dateHandle); + if (ret.isValid()) + return ret; + } + return std::nullopt; + }; - gst_tag_setter_reset_tags(GST_TAG_SETTER(element)); + std::optional<QDateTime> date = readDateTime(); + if (!date) + date = readDate(); - for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) { - const char *tagName = keyToTag(it.key()); + if (date) + metadata.insert(QMediaMetaData::Key::Date, *date); + + gst_tag_list_foreach(handle.get(), reinterpret_cast<GstTagForeachFunc>(&addTagToMetaData), + &metadata); + } +} + +static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element) +{ + gst_tag_setter_reset_tags(element); + + 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::QDateTime: { + // tagName does not properly disambiguate between GST_TAG_DATE_TIME and + // GST_TAG_DATE, as both map to QMediaMetaData::Key::Date. so we set it accordingly to + // the QVariant. + + 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(element, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME, + dateTime.get(), nullptr); + break; + } + case QMetaType::QDate: { + QDate date = tagValue.toDate(); + + QUniqueGDateHandle dateHandle{ + g_date_new_dmy(date.day(), GDateMonth(date.month()), date.year()), + }; + + gst_tag_setter_add_tags(element, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, dateHandle.get(), + nullptr); + 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); } +void extendMetaDataFromCaps(QMediaMetaData &metadata, const QGstCaps &caps) +{ + QGstStructureView structure = caps.at(0); + + QMediaFormat::FileFormat fileFormat = QGstreamerFormatInfo::fileFormatForCaps(structure); + if (fileFormat != QMediaFormat::FileFormat::UnspecifiedFormat) { + // Container caps + metadata.insert(QMediaMetaData::FileFormat, fileFormat); + return; + } + + QMediaFormat::AudioCodec audioCodec = QGstreamerFormatInfo::audioCodecForCaps(structure); + if (audioCodec != QMediaFormat::AudioCodec::Unspecified) { + // Audio stream caps + metadata.insert(QMediaMetaData::AudioCodec, QVariant::fromValue(audioCodec)); + return; + } + + QMediaFormat::VideoCodec videoCodec = QGstreamerFormatInfo::videoCodecForCaps(structure); + if (videoCodec != QMediaFormat::VideoCodec::Unspecified) { + // Video stream caps + metadata.insert(QMediaMetaData::VideoCodec, QVariant::fromValue(videoCodec)); + std::optional<float> framerate = structure["framerate"].getFraction(); + if (framerate) + metadata.insert(QMediaMetaData::VideoFrameRate, *framerate); + + QSize resolution = structure.resolution(); + if (resolution.isValid()) + metadata.insert(QMediaMetaData::Resolution, resolution); + } +} + +QMediaMetaData capsToMetaData(const QGstCaps &caps) +{ + QMediaMetaData metadata; + extendMetaDataFromCaps(metadata, caps); + return metadata; +} 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..6f5211a9c 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamermetadata_p.h @@ -16,20 +16,31 @@ // #include <qmediametadata.h> -#include <qvariant.h> -#include <gst/gst.h> +#include "qgst_p.h" QT_BEGIN_NAMESPACE -class QGstreamerMetaData : public QMediaMetaData +QMediaMetaData taglistToMetaData(const QGstTagListHandle &); +void extendMetaDataFromTagList(QMediaMetaData &, const QGstTagListHandle &); + +QMediaMetaData capsToMetaData(const QGstCaps &); +void extendMetaDataFromCaps(QMediaMetaData &, const QGstCaps &); + +void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstBin &); +void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstElement &); + +struct RotationResult { -public: - static QGstreamerMetaData fromGstTagList(const GstTagList *tags); + QtVideo::Rotation rotation; + bool flip; - void setMetaData(GstBin *bin) const; - void setMetaData(GstElement *element) const; + bool operator==(const RotationResult &rhs) const + { + return std::tie(rotation, flip) == std::tie(rhs.rotation, rhs.flip); + } }; +RotationResult parseRotationTag(std::string_view); QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp index c6e363bef..3d44e021b 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp @@ -4,7 +4,6 @@ #include <QtMultimedia/qvideosink.h> #include <QtCore/qloggingcategory.h> -#include <QtCore/qthread.h> #include <common/qgstreamervideooutput_p.h> #include <common/qgstreamervideosink_p.h> @@ -14,197 +13,158 @@ static Q_LOGGING_CATEGORY(qLcMediaVideoOutput, "qt.multimedia.videooutput") QT_BEGIN_NAMESPACE +static QGstElement makeVideoConvertScale(const char *name) +{ + QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale"); + if (factory) // videoconvertscale is only available in gstreamer 1.20 + return QGstElement::createFromFactory(factory, name); + + return QGstBin::createFromPipelineDescription("videoconvert ! videoscale", name, + /*ghostUnlinkedPads=*/true); +} + QMaybe<QGstreamerVideoOutput *> QGstreamerVideoOutput::create(QObject *parent) { - QGstElement videoConvert; - QGstElement videoScale; - - QGstElementFactoryHandle factory = QGstElementFactoryHandle{ - gst_element_factory_find("videoconvertscale"), - }; - - if (factory) { // videoconvertscale is only available in gstreamer 1.20 - videoConvert = QGstElement{ - gst_element_factory_create(factory.get(), "videoConvertScale"), - QGstElement::NeedsRef, - }; - } else { - videoConvert = QGstElement::createFromFactory("videoconvert", "videoConvert"); - if (!videoConvert) - return errorMessageCannotFindElement("videoconvert"); + QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale"); - videoScale = QGstElement::createFromFactory("videoscale", "videoScale"); - if (!videoScale) - return errorMessageCannotFindElement("videoscale"); - } + static std::optional<QString> elementCheck = []() -> std::optional<QString> { + std::optional<QString> error = qGstErrorMessageIfElementsNotAvailable("fakesink", "queue"); + if (error) + return error; + + QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale"); + if (factory) + return std::nullopt; + + return qGstErrorMessageIfElementsNotAvailable("videoconvert", "videoscale"); + }(); - QGstElement videoSink = QGstElement::createFromFactory("fakesink", "fakeVideoSink"); - if (!videoSink) - return errorMessageCannotFindElement("fakesink"); - videoSink.set("sync", true); + if (elementCheck) + return *elementCheck; - return new QGstreamerVideoOutput(videoConvert, videoScale, videoSink, parent); + return new QGstreamerVideoOutput(parent); } -QGstreamerVideoOutput::QGstreamerVideoOutput(QGstElement convert, QGstElement scale, - QGstElement sink, QObject *parent) +QGstreamerVideoOutput::QGstreamerVideoOutput(QObject *parent) : QObject(parent), - gstVideoOutput(QGstBin::create("videoOutput")), - videoConvert(std::move(convert)), - videoScale(std::move(scale)), - videoSink(std::move(sink)) + m_outputBin{ + QGstBin::create("videoOutput"), + }, + m_videoQueue{ + QGstElement::createFromFactory("queue", "videoQueue"), + }, + m_videoConvertScale{ + makeVideoConvertScale("videoConvertScale"), + }, + m_videoSink{ + QGstElement::createFromFactory("fakesink", "fakeVideoSink"), + } { - videoQueue = QGstElement::createFromFactory("queue", "videoQueue"); + m_videoSink.set("sync", true); + m_videoSink.set("async", false); // no asynchronous state changes - videoSink.set("sync", true); - videoSink.set("async", false); // no asynchronous state changes + m_outputBin.add(m_videoQueue, m_videoConvertScale, m_videoSink); + qLinkGstElements(m_videoQueue, m_videoConvertScale, m_videoSink); - if (videoScale) { - gstVideoOutput.add(videoQueue, videoConvert, videoScale, videoSink); - qLinkGstElements(videoQueue, videoConvert, videoScale, videoSink); - } else { - gstVideoOutput.add(videoQueue, videoConvert, videoSink); - qLinkGstElements(videoQueue, videoConvert, videoSink); - } + m_subtitleSink = QGstSubtitleSink::createSink(this); - gstVideoOutput.addGhostPad(videoQueue, "sink"); + m_outputBin.addGhostPad(m_videoQueue, "sink"); } QGstreamerVideoOutput::~QGstreamerVideoOutput() { - gstVideoOutput.setStateSync(GST_STATE_NULL); + QObject::disconnect(m_subtitleConnection); + m_outputBin.setStateSync(GST_STATE_NULL); } void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink) { - auto *gstVideoSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr; - if (gstVideoSink == m_videoSink) - return; + using namespace std::chrono_literals; - if (m_videoSink) - m_videoSink->setPipeline({}); + auto *gstVideoSink = + sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr; + if (gstVideoSink == m_platformVideoSink) + return; - m_videoSink = gstVideoSink; - if (m_videoSink) { - m_videoSink->setPipeline(gstPipeline); - if (nativeSize.isValid()) - m_videoSink->setNativeSize(nativeSize); + m_platformVideoSink = gstVideoSink; + if (m_platformVideoSink) { + m_platformVideoSink->setActive(m_isActive); + if (m_nativeSize.isValid()) + m_platformVideoSink->setNativeSize(m_nativeSize); } - QGstElement gstSink; - if (m_videoSink) { - gstSink = m_videoSink->gstSink(); + QGstElement videoSink; + if (m_platformVideoSink) { + videoSink = m_platformVideoSink->gstSink(); } else { - gstSink = QGstElement::createFromFactory("fakesink", "fakevideosink"); - Q_ASSERT(gstSink); - gstSink.set("sync", true); - gstSink.set("async", false); // no asynchronous state changes + videoSink = QGstElement::createFromFactory("fakesink", "fakevideosink"); + Q_ASSERT(videoSink); + videoSink.set("sync", true); + videoSink.set("async", false); // no asynchronous state changes + } + + QObject::disconnect(m_subtitleConnection); + if (sink) { + m_subtitleConnection = QObject::connect(this, &QGstreamerVideoOutput::subtitleChanged, sink, + [sink](const QString &subtitle) { + sink->setSubtitleText(subtitle); + }); + sink->setSubtitleText(m_lastSubtitleString); } - if (videoSink == gstSink) + if (m_videoSink == videoSink) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { - if (!videoSink.isNull()) - gstVideoOutput.stopAndRemoveElements(videoSink); + m_videoConvertScale.src().modifyPipelineInIdleProbe([&] { + if (m_videoSink) + m_outputBin.stopAndRemoveElements(m_videoSink); - videoSink = gstSink; - gstVideoOutput.add(videoSink); + m_videoSink = std::move(videoSink); + m_outputBin.add(m_videoSink); - if (videoScale) - qLinkGstElements(videoScale, videoSink); - else - qLinkGstElements(videoConvert, videoSink); + qLinkGstElements(m_videoConvertScale, m_videoSink); GstEvent *event = gst_event_new_reconfigure(); - gst_element_send_event(videoSink.element(), event); - videoSink.syncStateWithParent(); - - doLinkSubtitleStream(); + gst_element_send_event(m_videoSink.element(), event); + m_videoSink.syncStateWithParent(); }); - qCDebug(qLcMediaVideoOutput) << "sinkChanged" << gstSink.name(); - - GST_DEBUG_BIN_TO_DOT_FILE(gstPipeline.bin(), - GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | - GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES), - videoSink.name()); - + qCDebug(qLcMediaVideoOutput) << "sinkChanged" << m_videoSink.name(); + m_videoConvertScale.dumpPipelineGraph(m_videoSink.name().constData()); } -void QGstreamerVideoOutput::setPipeline(const QGstPipeline &pipeline) +void QGstreamerVideoOutput::setActive(bool isActive) { - gstPipeline = pipeline; - if (m_videoSink) - m_videoSink->setPipeline(gstPipeline); -} - -void QGstreamerVideoOutput::linkSubtitleStream(QGstElement src) -{ - qCDebug(qLcMediaVideoOutput) << "link subtitle stream" << src.isNull(); - if (src == subtitleSrc) + if (m_isActive == isActive) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { - subtitleSrc = src; - doLinkSubtitleStream(); - }); -} - -void QGstreamerVideoOutput::unlinkSubtitleStream() -{ - if (subtitleSrc.isNull()) - return; - qCDebug(qLcMediaVideoOutput) << "unlink subtitle stream"; - subtitleSrc = {}; - if (!subtitleSink.isNull()) { - gstPipeline.modifyPipelineWhileNotRunning([&] { - gstPipeline.stopAndRemoveElements(subtitleSink); - return; - }); - subtitleSink = {}; - } - if (m_videoSink) - m_videoSink->setSubtitleText({}); -} - -void QGstreamerVideoOutput::doLinkSubtitleStream() -{ - if (!subtitleSink.isNull()) { - gstPipeline.stopAndRemoveElements(subtitleSink); - subtitleSink = {}; - } - if (!m_videoSink || subtitleSrc.isNull()) - return; - if (subtitleSink.isNull()) { - subtitleSink = m_videoSink->subtitleSink(); - gstPipeline.add(subtitleSink); - } - qLinkGstElements(subtitleSrc, subtitleSink); + m_isActive = isActive; + if (m_platformVideoSink) + m_platformVideoSink->setActive(isActive); } void QGstreamerVideoOutput::updateNativeSize() { - if (!m_videoSink) + if (!m_platformVideoSink) return; - m_videoSink->setNativeSize(qRotatedFrameSize(nativeSize, rotation)); + m_platformVideoSink->setNativeSize(qRotatedFrameSize(m_nativeSize, m_rotation)); } void QGstreamerVideoOutput::setIsPreview() { // configures the queue to be fast and lightweight for camera preview // also avoids blocking the queue in case we have an encodebin attached to the tee as well - videoQueue.set("leaky", 2 /*downstream*/); - videoQueue.set("silent", true); - videoQueue.set("max-size-buffers", uint(1)); - videoQueue.set("max-size-bytes", uint(0)); - videoQueue.set("max-size-time", quint64(0)); + m_videoQueue.set("leaky", 2 /*downstream*/); + m_videoQueue.set("silent", true); + m_videoQueue.set("max-size-buffers", uint(1)); + m_videoQueue.set("max-size-bytes", uint(0)); + m_videoQueue.set("max-size-time", quint64(0)); } void QGstreamerVideoOutput::flushSubtitles() { - if (!subtitleSink.isNull()) { - auto pad = subtitleSink.staticPad("sink"); + if (!m_subtitleSink.isNull()) { + auto pad = m_subtitleSink.staticPad("sink"); auto *event = gst_event_new_flush_start(); pad.sendEvent(event); event = gst_event_new_flush_stop(false); @@ -214,16 +174,26 @@ void QGstreamerVideoOutput::flushSubtitles() void QGstreamerVideoOutput::setNativeSize(QSize sz) { - nativeSize = sz; + m_nativeSize = sz; updateNativeSize(); } void QGstreamerVideoOutput::setRotation(QtVideo::Rotation rot) { - rotation = rot; + m_rotation = rot; updateNativeSize(); } +void QGstreamerVideoOutput::updateSubtitle(QString string) +{ + // GStreamer thread + + QMetaObject::invokeMethod(this, [this, string = std::move(string)]() mutable { + m_lastSubtitleString = string; + Q_EMIT subtitleChanged(std::move(string)); + }); +} + QT_END_NAMESPACE #include "moc_qgstreamervideooutput_p.cpp" diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h index 42acb18cc..0c9e04c2e 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput_p.h @@ -16,20 +16,18 @@ // #include <QtCore/qobject.h> -#include <private/qtmultimediaglobal_p.h> -#include <private/qmultimediautils_p.h> +#include <QtCore/qpointer.h> +#include <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QtMultimedia/private/qmultimediautils_p.h> #include <common/qgst_p.h> -#include <common/qgstpipeline_p.h> #include <common/qgstreamervideosink_p.h> -#include <qwaitcondition.h> -#include <qmutex.h> -#include <qpointer.h> +#include <common/qgstsubtitlesink_p.h> QT_BEGIN_NAMESPACE class QVideoSink; -class QGstreamerVideoOutput : public QObject +class QGstreamerVideoOutput : public QObject, QAbstractSubtitleObserver { Q_OBJECT @@ -38,13 +36,12 @@ public: ~QGstreamerVideoOutput(); void setVideoSink(QVideoSink *sink); - QGstreamerVideoSink *gstreamerVideoSink() const { return m_videoSink; } + QGstreamerVideoSink *gstreamerVideoSink() const { return m_platformVideoSink; } - void setPipeline(const QGstPipeline &pipeline); + QGstElement gstElement() const { return m_outputBin; } + QGstElement gstSubtitleElement() const { return m_subtitleSink; } - QGstElement gstElement() const { return gstVideoOutput; } - void linkSubtitleStream(QGstElement subtitleSrc); - void unlinkSubtitleStream(); + void setActive(bool); void setIsPreview(); void flushSubtitles(); @@ -52,29 +49,31 @@ public: void setNativeSize(QSize); void setRotation(QtVideo::Rotation); + void updateSubtitle(QString) override; + +signals: + void subtitleChanged(QString); + private: - QGstreamerVideoOutput(QGstElement videoConvert, QGstElement videoScale, QGstElement videoSink, - QObject *parent); + explicit QGstreamerVideoOutput(QObject *parent); - void doLinkSubtitleStream(); void updateNativeSize(); - QPointer<QGstreamerVideoSink> m_videoSink; + QPointer<QGstreamerVideoSink> m_platformVideoSink; // Gst elements - QGstPipeline gstPipeline; - - QGstBin gstVideoOutput; - QGstElement videoQueue; - QGstElement videoConvert; - QGstElement videoScale; - QGstElement videoSink; - - QGstElement subtitleSrc; - QGstElement subtitleSink; - - QSize nativeSize; - QtVideo::Rotation rotation{}; + QGstBin m_outputBin; + QGstElement m_videoQueue; + QGstElement m_videoConvertScale; + QGstElement m_videoSink; + + QGstElement m_subtitleSink; + QMetaObject::Connection m_subtitleConnection; + QString m_lastSubtitleString; + + bool m_isActive{ false }; + QSize m_nativeSize; + QtVideo::Rotation m_rotation{}; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay.cpp index 46fdcb921..6ca23006b 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay.cpp @@ -72,7 +72,7 @@ static QGstElement findBestVideoSink() if (!gst_element_factory_has_interface(f, "GstVideoOverlay")) continue; - choice = QGstElement(gst_element_factory_create(f, nullptr), QGstElement::NeedsRef); + choice = QGstElement::createFromFactory(f, nullptr); if (choice.isNull()) continue; diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay_p.h index 588e8b5e4..fc37187d4 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooverlay_p.h @@ -15,10 +15,11 @@ // We mean it. // -#include <common/qgstpipeline_p.h> +#include <QtGui/qwindowdefs.h> + #include <common/qgstreamerbufferprobe_p.h> #include <common/qgst_p.h> -#include <QtGui/qwindowdefs.h> +#include <common/qgst_bus_p.h> QT_BEGIN_NAMESPACE class QGstreamerVideoSink; diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideosink.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamervideosink.cpp index 6ba055cf7..b89dbf6b1 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideosink.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideosink.cpp @@ -3,45 +3,46 @@ #include <common/qgstreamervideosink_p.h> #include <common/qgstvideorenderersink_p.h> -#include <common/qgstsubtitlesink_p.h> #include <common/qgst_debug_p.h> #include <common/qgstutils_p.h> #include <QtGui/private/qrhi_p.h> -#if QT_CONFIG(gstreamer_gl) -#include <QtGui/private/qrhigles2_p.h> -#include <QGuiApplication> -#include <QtGui/qopenglcontext.h> -#include <QWindow> -#include <qpa/qplatformnativeinterface.h> -#include <gst/gl/gstglconfig.h> +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> -#if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h") +#if QT_CONFIG(gstreamer_gl) +# include <QtGui/QGuiApplication> +# include <QtGui/qopenglcontext.h> +# include <QtGui/QWindow> +# include <QtGui/qpa/qplatformnativeinterface.h> +# include <QtGui/private/qrhigles2_p.h> +# include <gst/gl/gstglconfig.h> + +# if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h") # include <gst/gl/x11/gstgldisplay_x11.h> -#endif -#if GST_GL_HAVE_PLATFORM_EGL +# endif +# if GST_GL_HAVE_PLATFORM_EGL # include <gst/gl/egl/gstgldisplay_egl.h> # include <EGL/egl.h> # include <EGL/eglext.h> -#endif -#if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h") +# endif +# if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h") # include <gst/gl/wayland/gstgldisplay_wayland.h> -#endif +# endif #endif // #if QT_CONFIG(gstreamer_gl) -#include <QtCore/qdebug.h> - -#include <QtCore/qloggingcategory.h> - QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink"); QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent) - : QPlatformVideoSink(parent) + : QPlatformVideoSink{ + parent, + }, + m_sinkBin{ + QGstBin::create("videoSinkBin"), + } { - sinkBin = QGstBin::create("videoSinkBin"); - // This is a hack for some iMX and NVidia platforms. These require the use of a special video // conversion element in the pipeline before the video sink, as they unfortunately // output some proprietary format from the decoder even though it's sometimes marked as @@ -49,41 +50,44 @@ QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent) // // To fix this, simply insert the element into the pipeline if it's available. Otherwise // we simply use an identity element. - gstQueue = QGstElement::createFromFactory("queue", "videoSinkQueue"); - QGstElementFactoryHandle factory; - // QT_MULTIMEDIA_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the + // QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the // conversion element. Ideally we construct the element programatically, though. - QByteArray preprocessOverride = - qgetenv("QT_MULTIMEDIA_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT"); + QByteArray preprocessOverride = qgetenv("QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT"); if (!preprocessOverride.isEmpty()) { - qCDebug(qLcGstVideoSink) << "requesting conversion element from environment: " + qCDebug(qLcGstVideoSink) << "requesting conversion element from environment:" << preprocessOverride; - factory = QGstElementFactoryHandle{ - gst_element_factory_find(preprocessOverride.constData()), - }; + + m_gstPreprocess = QGstBin::createFromPipelineDescription(preprocessOverride, nullptr, + /*ghostUnlinkedPads=*/true); + if (!m_gstPreprocess) + qCWarning(qLcGstVideoSink) << "Cannot create conversion element:" << preprocessOverride; } - if (!factory) - factory = QGstElementFactoryHandle{ - gst_element_factory_find("imxvideoconvert_g2d"), + if (!m_gstPreprocess) { + // This is a hack for some iMX and NVidia platforms. These require the use of a special + // video conversion element in the pipeline before the video sink, as they unfortunately + // output some proprietary format from the decoder even though it's sometimes marked as + // a regular supported video/x-raw format. + static constexpr auto decodersToTest = { + "imxvideoconvert_g2d", + "nvvidconv", }; - if (!factory) - factory = QGstElementFactoryHandle{ - gst_element_factory_find("nvvidconv"), - }; + for (const char *decoder : decodersToTest) { + factory = QGstElement::findFactory(decoder); + if (factory) + break; + } - if (factory) { - qCDebug(qLcGstVideoSink) << "instantiating conversion element: " - << g_type_name( - gst_element_factory_get_element_type(factory.get())); + if (factory) { + qCDebug(qLcGstVideoSink) + << "instantiating conversion element:" + << g_type_name(gst_element_factory_get_element_type(factory.get())); - gstPreprocess = QGstElement{ - gst_element_factory_create(factory.get(), "preprocess"), - QGstElement::NeedsRef, - }; + m_gstPreprocess = QGstElement::createFromFactory(factory, "preprocess"); + } } bool disablePixelAspectRatio = @@ -95,53 +99,55 @@ QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent) // pixel-aspect-ratio handling // // compare: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6242 - gstCapsFilter = + m_gstCapsFilter = QGstElement::createFromFactory("identity", "nullPixelAspectRatioCapsFilter"); } else { - gstCapsFilter = QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter"); + m_gstCapsFilter = + QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter"); QGstCaps capsFilterCaps{ gst_caps_new_simple("video/x-raw", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL), QGstCaps::HasRef, }; - g_object_set(gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL); + g_object_set(m_gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL); } - if (gstPreprocess) { - sinkBin.add(gstQueue, gstPreprocess, gstCapsFilter); - qLinkGstElements(gstQueue, gstPreprocess, gstCapsFilter); + if (m_gstPreprocess) { + m_sinkBin.add(m_gstPreprocess, m_gstCapsFilter); + qLinkGstElements(m_gstPreprocess, m_gstCapsFilter); + m_sinkBin.addGhostPad(m_gstPreprocess, "sink"); } else { - sinkBin.add(gstQueue, gstCapsFilter); - qLinkGstElements(gstQueue, gstCapsFilter); + m_sinkBin.add(m_gstCapsFilter); + m_sinkBin.addGhostPad(m_gstCapsFilter, "sink"); } - sinkBin.addGhostPad(gstQueue, "sink"); - - gstSubtitleSink = - QGstElement(GST_ELEMENT(QGstSubtitleSink::createSink(this)), QGstElement::NeedsRef); } QGstreamerVideoSink::~QGstreamerVideoSink() { - unrefGstContexts(); + emit aboutToBeDestroyed(); - setPipeline(QGstPipeline()); + unrefGstContexts(); } QGstElement QGstreamerVideoSink::gstSink() { - updateSinkElement(); - return sinkBin; -} + if (!m_gstVideoSink) { + if (!m_gstQtSink) + createQtSink(); -void QGstreamerVideoSink::setPipeline(QGstPipeline pipeline) -{ - gstPipeline = std::move(pipeline); + updateSinkElement(m_gstQtSink); + } + + return m_sinkBin; } -bool QGstreamerVideoSink::inStoppedState() const +void QGstreamerVideoSink::setActive(bool isActive) { - if (gstPipeline.isNull()) - return true; - return gstPipeline.inStoppedState(); + if (m_isActive == isActive) + return; + m_isActive = isActive; + + if (m_gstQtSink) + m_gstQtSink.setActive(isActive); } void QGstreamerVideoSink::setRhi(QRhi *rhi) @@ -153,45 +159,42 @@ void QGstreamerVideoSink::setRhi(QRhi *rhi) m_rhi = rhi; updateGstContexts(); - if (!gstQtSink.isNull()) { - // force creation of a new sink with proper caps + if (m_gstQtSink) { + QGstVideoRendererSinkElement oldSink = std::move(m_gstQtSink); + + // force creation of a new sink with proper caps. createQtSink(); - updateSinkElement(); + updateSinkElement(m_gstQtSink); } } void QGstreamerVideoSink::createQtSink() { - if (gstQtSink) - gstQtSink.setStateSync(GST_STATE_NULL); + Q_ASSERT(!m_gstQtSink); - gstQtSink = QGstElement(reinterpret_cast<GstElement *>(QGstVideoRendererSink::createSink(this)), - QGstElement::NeedsRef); + m_gstQtSink = QGstVideoRendererSink::createSink(this); + m_gstQtSink.set("async", false); // no asynchronous state changes + m_gstQtSink.setActive(m_isActive); } -void QGstreamerVideoSink::updateSinkElement() +void QGstreamerVideoSink::updateSinkElement(QGstVideoRendererSinkElement newSink) { - QGstElement newSink; - if (gstQtSink.isNull()) - createQtSink(); - newSink = gstQtSink; - - if (newSink == gstVideoSink) + if (newSink == m_gstVideoSink) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { - if (!gstVideoSink.isNull()) - sinkBin.stopAndRemoveElements(gstVideoSink); + m_gstCapsFilter.src().modifyPipelineInIdleProbe([&] { + if (m_gstVideoSink) + m_sinkBin.stopAndRemoveElements(m_gstVideoSink); - newSink.set("async", false); // no asynchronous state changes - - gstVideoSink = newSink; - sinkBin.add(gstVideoSink); - qLinkGstElements(gstCapsFilter, gstVideoSink); - gstVideoSink.setState(GST_STATE_PAUSED); + m_gstVideoSink = std::move(newSink); + m_sinkBin.add(m_gstVideoSink); + qLinkGstElements(m_gstCapsFilter, m_gstVideoSink); + GstEvent *event = gst_event_new_reconfigure(); + gst_element_send_event(m_gstVideoSink.element(), event); + m_gstVideoSink.syncStateWithParent(); }); - gstPipeline.dumpGraph("updateVideoSink"); + m_sinkBin.dumpPipelineGraph("updateVideoSink"); } void QGstreamerVideoSink::unrefGstContexts() @@ -204,6 +207,8 @@ void QGstreamerVideoSink::unrefGstContexts() void QGstreamerVideoSink::updateGstContexts() { + using namespace Qt::Literals; + unrefGstContexts(); #if QT_CONFIG(gstreamer_gl) @@ -216,12 +221,12 @@ void QGstreamerVideoSink::updateGstContexts() const QString platform = QGuiApplication::platformName(); QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface(); - m_eglDisplay = pni->nativeResourceForIntegration("egldisplay"); + m_eglDisplay = pni->nativeResourceForIntegration("egldisplay"_ba); // qDebug() << "platform is" << platform << m_eglDisplay; QGstGLDisplayHandle gstGlDisplay; - const char *contextName = "eglcontext"; + QByteArray contextName = "eglcontext"_ba; GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL; // use the egl display if we have one if (m_eglDisplay) { @@ -231,12 +236,12 @@ void QGstreamerVideoSink::updateGstContexts() m_eglImageTargetTexture2D = eglGetProcAddress("glEGLImageTargetTexture2DOES"); #endif } else { - auto display = pni->nativeResourceForIntegration("display"); + auto display = pni->nativeResourceForIntegration("display"_ba); if (display) { #if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h") if (platform == QLatin1String("xcb")) { - contextName = "glxcontext"; + contextName = "glxcontext"_ba; glPlatform = GST_GL_PLATFORM_GLX; gstGlDisplay.reset(GST_GL_DISPLAY_CAST( @@ -293,8 +298,8 @@ void QGstreamerVideoSink::updateGstContexts() gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr); displayContext.close(); - if (!gstPipeline.isNull()) - gst_element_set_context(gstPipeline.element(), m_gstGlLocalContext.get()); + // Note: after updating the context, we switch the sink and send gst_event_new_reconfigure() + // upstream. this will cause the context to be queried again. #endif // #if QT_CONFIG(gstreamer_gl) } diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideosink_p.h b/src/plugins/multimedia/gstreamer/common/qgstreamervideosink_p.h index 132eab557..6e4d7746a 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstreamervideosink_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideosink_p.h @@ -15,25 +15,18 @@ // We mean it. // -#include <private/qtmultimediaglobal_p.h> -#include <private/qplatformvideosink_p.h> +#include <QtMultimedia/qvideosink.h> +#include <QtMultimedia/private/qplatformvideosink_p.h> +#include <common/qgstvideorenderersink_p.h> #include <common/qgstpipeline_p.h> -#include <common/qgstreamervideooverlay_p.h> -#include <QtGui/qcolor.h> -#include <qvideosink.h> - -#if QT_CONFIG(gstreamer_gl) -#include <gst/gl/gl.h> -#endif QT_BEGIN_NAMESPACE -class QGstreamerVideoRenderer; -class QVideoWindow; class QGstreamerVideoSink : public QPlatformVideoSink { Q_OBJECT + public: explicit QGstreamerVideoSink(QVideoSink *parent = nullptr); ~QGstreamerVideoSink(); @@ -42,33 +35,32 @@ public: QRhi *rhi() const { return m_rhi; } QGstElement gstSink(); - QGstElement subtitleSink() const { return gstSubtitleSink; } - - void setPipeline(QGstPipeline pipeline); - bool inStoppedState() const; GstContext *gstGlDisplayContext() const { return m_gstGlDisplayContext.get(); } GstContext *gstGlLocalContext() const { return m_gstGlLocalContext.get(); } Qt::HANDLE eglDisplay() const { return m_eglDisplay; } QFunctionPointer eglImageTargetTexture2D() const { return m_eglImageTargetTexture2D; } + void setActive(bool); + +Q_SIGNALS: + void aboutToBeDestroyed(); + private: void createQtSink(); - void updateSinkElement(); + void updateSinkElement(QGstVideoRendererSinkElement newSink); void unrefGstContexts(); void updateGstContexts(); - QGstPipeline gstPipeline; - QGstBin sinkBin; - QGstElement gstQueue; - QGstElement gstPreprocess; - QGstElement gstCapsFilter; - QGstElement gstVideoSink; - QGstElement gstQtSink; - QGstElement gstSubtitleSink; + QGstBin m_sinkBin; + QGstElement m_gstPreprocess; + QGstElement m_gstCapsFilter; + QGstElement m_gstVideoSink; + QGstVideoRendererSinkElement m_gstQtSink; QRhi *m_rhi = nullptr; + bool m_isActive = true; Qt::HANDLE m_eglDisplay = nullptr; QFunctionPointer m_eglImageTargetTexture2D = nullptr; diff --git a/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink.cpp b/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink.cpp index c6b230d85..58b5c3f53 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink.cpp @@ -1,55 +1,62 @@ // Copyright (C) 2021 The Qt Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include <QDebug> -#include <QThread> -#include <QEvent> - -#include "qgstreamervideosink_p.h" #include "qgstsubtitlesink_p.h" +#include "qgst_debug_p.h" + +#include <QtCore/qdebug.h> QT_BEGIN_NAMESPACE -static GstBaseSinkClass *gst_sink_parent_class; -static thread_local QGstreamerVideoSink *gst_current_sink; +namespace { +GstBaseSinkClass *gst_sink_parent_class; +thread_local QAbstractSubtitleObserver *gst_current_observer; + +class QGstSubtitleSinkClass +{ +public: + GstBaseSinkClass parent_class; +}; + +} // namespace #define ST_SINK(s) QGstSubtitleSink *sink(reinterpret_cast<QGstSubtitleSink *>(s)) -QGstSubtitleSink *QGstSubtitleSink::createSink(QGstreamerVideoSink *sink) +QGstElement QGstSubtitleSink::createSink(QAbstractSubtitleObserver *observer) { - gst_current_sink = sink; + gst_current_observer = observer; QGstSubtitleSink *gstSink = reinterpret_cast<QGstSubtitleSink *>( g_object_new(QGstSubtitleSink::get_type(), nullptr)); g_object_set(gstSink, "async", false, nullptr); - return gstSink; + return QGstElement{ + qGstCheckedCast<GstElement>(gstSink), + QGstElement::NeedsRef, + }; } GType QGstSubtitleSink::get_type() { - static const GTypeInfo info = + // clang-format off + static constexpr GTypeInfo info = { - sizeof(QGstSubtitleSinkClass), // class_size - base_init, // base_init - nullptr, // base_finalize - class_init, // class_init - nullptr, // class_finalize - nullptr, // class_data - sizeof(QGstSubtitleSink), // instance_size - 0, // n_preallocs - instance_init, // instance_init - nullptr // value_table + sizeof(QGstSubtitleSinkClass), // class_size + base_init, // base_init + nullptr, // base_finalize + class_init, // class_init + nullptr, // class_finalize + nullptr, // class_data + sizeof(QGstSubtitleSink), // instance_size + 0, // n_preallocs + instance_init, // instance_init + nullptr // value_table }; + // clang-format on static const GType type = []() { const auto result = g_type_register_static( GST_TYPE_BASE_SINK, "QGstSubtitleSink", &info, GTypeFlags(0)); - - // Register the sink type to be used in custom piplines. - // When surface is ready the sink can be used. - gst_element_register(nullptr, "qtsubtitlesink", GST_RANK_PRIMARY, result); - return result; }(); @@ -83,21 +90,20 @@ void QGstSubtitleSink::class_init(gpointer g_class, gpointer class_data) void QGstSubtitleSink::base_init(gpointer g_class) { - static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE( - "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("ANY")); + static GstStaticPadTemplate sink_pad_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("ANY")); gst_element_class_add_pad_template( GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template)); } -void QGstSubtitleSink::instance_init(GTypeInstance *instance, gpointer g_class) +void QGstSubtitleSink::instance_init(GTypeInstance *instance, gpointer /*g_class*/) { - Q_UNUSED(g_class); ST_SINK(instance); - Q_ASSERT(gst_current_sink); - sink->sink = gst_current_sink; - gst_current_sink = nullptr; + Q_ASSERT(gst_current_observer); + sink->observer = gst_current_observer; + gst_current_observer = nullptr; } void QGstSubtitleSink::finalize(GObject *object) @@ -132,8 +138,8 @@ GstFlowReturn QGstSubtitleSink::wait_event(GstBaseSink *base, GstEvent *event) GstFlowReturn retval = gst_sink_parent_class->wait_event(base, event); ST_SINK(base); if (event->type == GST_EVENT_GAP) { -// qDebug() << "gap, clearing subtitle"; - sink->sink->setSubtitleText(QString()); + // qDebug() << "gap, clearing subtitle"; + sink->observer->updateSubtitle(QString()); } return retval; } @@ -148,7 +154,7 @@ GstFlowReturn QGstSubtitleSink::render(GstBaseSink *base, GstBuffer *buffer) subtitle = QString::fromUtf8(reinterpret_cast<const char *>(info.data)); gst_memory_unmap(mem, &info); // qDebug() << "render" << buffer << subtitle; - sink->sink->setSubtitleText(subtitle); + sink->observer->updateSubtitle(subtitle); return GST_FLOW_OK; } diff --git a/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink_p.h b/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink_p.h index 0f515cb99..1970ac48b 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstsubtitlesink_p.h @@ -17,24 +17,25 @@ #include <QtMultimedia/private/qtmultimediaglobal_p.h> -#include <QtCore/qlist.h> -#include <QtCore/qmutex.h> -#include <QtCore/qqueue.h> -#include <QtCore/qpointer.h> -#include <QtCore/qwaitcondition.h> +#include <QtCore/qstring.h> #include <common/qgst_p.h> #include <gst/base/gstbasesink.h> QT_BEGIN_NAMESPACE -class QGstreamerVideoSink; +class QAbstractSubtitleObserver +{ +public: + virtual ~QAbstractSubtitleObserver() = default; + virtual void updateSubtitle(QString) = 0; +}; class QGstSubtitleSink { public: GstBaseSink parent{}; - static QGstSubtitleSink *createSink(QGstreamerVideoSink *sink); + static QGstElement createSink(QAbstractSubtitleObserver *observer); private: static GType get_type(); @@ -55,14 +56,7 @@ private: static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer); private: - QGstreamerVideoSink *sink = nullptr; -}; - - -class QGstSubtitleSinkClass -{ -public: - GstBaseSinkClass parent_class; + QAbstractSubtitleObserver *observer = nullptr; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstutils.cpp b/src/plugins/multimedia/gstreamer/common/qgstutils.cpp index 0183f182c..21562b335 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstutils.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstutils.cpp @@ -56,7 +56,7 @@ QAudioFormat QGstUtils::audioFormatForSample(GstSample *sample) QAudioFormat QGstUtils::audioFormatForCaps(const QGstCaps &caps) { QAudioFormat format; - QGstStructure s = caps.at(0); + QGstStructureView s = caps.at(0); if (s.name() != "audio/x-raw") return format; @@ -138,4 +138,9 @@ GList *qt_gst_video_sinks() GST_RANK_MARGINAL); } +QLocale::Language QGstUtils::codeToLanguage(const gchar *lang) +{ + return QLocale::codeToLanguage(QString::fromUtf8(lang), QLocale::AnyLanguageCode); +} + QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstutils_p.h b/src/plugins/multimedia/gstreamer/common/qgstutils_p.h index c65fcf090..c96ca54b9 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstutils_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstutils_p.h @@ -19,6 +19,7 @@ #include <gst/gstbuffer.h> #include <QtCore/qglobal.h> +#include <QtCore/qlocale.h> QT_BEGIN_NAMESPACE @@ -32,6 +33,8 @@ QAudioFormat audioFormatForCaps(const QGstCaps &caps); QGstCaps capsForAudioFormat(const QAudioFormat &format); void setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer); + +QLocale::Language codeToLanguage(const gchar *); } // namespace QGstUtils GList *qt_gst_video_sinks(); diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp index 634a18ee5..493bcbcd5 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp +++ b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink.cpp @@ -3,28 +3,26 @@ #include "qgstvideorenderersink_p.h" -#include <QtMultimedia/qvideoframe.h> -#include <QtMultimedia/qvideosink.h> #include <QtCore/private/qfactoryloader_p.h> #include <QtCore/private/quniquehandle_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qdebug.h> -#include <QtCore/qdebug.h> #include <QtCore/qloggingcategory.h> -#include <QtCore/qmap.h> -#include <QtCore/qthread.h> -#include <QtGui/qevent.h> +#include <QtGui/private/qrhi_p.h> +#include <QtMultimedia/private/qvideoframe_p.h> +#include <QtMultimedia/qvideoframe.h> +#include <QtMultimedia/qvideosink.h> -#include <common/qgstvideobuffer_p.h> -#include <common/qgstreamervideosink_p.h> #include <common/qgst_debug_p.h> +#include <common/qgstreamermetadata_p.h> +#include <common/qgstreamervideosink_p.h> #include <common/qgstutils_p.h> +#include <common/qgstvideobuffer_p.h> #include <gst/video/video.h> #include <gst/video/gstvideometa.h> -#include <QtGui/private/qrhi_p.h> #if QT_CONFIG(gstreamer_gl) #include <gst/gl/gl.h> #endif // #if QT_CONFIG(gstreamer_gl) @@ -34,6 +32,8 @@ #include <gst/allocators/gstdmabuf.h> #endif +// NOLINTBEGIN(readability-convert-member-functions-to-static) + static Q_LOGGING_CATEGORY(qLcGstVideoRenderer, "qt.multimedia.gstvideorenderer") QT_BEGIN_NAMESPACE @@ -41,6 +41,13 @@ QT_BEGIN_NAMESPACE QGstVideoRenderer::QGstVideoRenderer(QGstreamerVideoSink *sink) : m_sink(sink), m_surfaceCaps(createSurfaceCaps(sink)) { + QObject::connect( + sink, &QGstreamerVideoSink::aboutToBeDestroyed, this, + [this] { + QMutexLocker locker(&m_sinkMutex); + m_sink = nullptr; + }, + Qt::DirectConnection); } QGstVideoRenderer::~QGstVideoRenderer() = default; @@ -103,102 +110,135 @@ QGstCaps QGstVideoRenderer::createSurfaceCaps([[maybe_unused]] QGstreamerVideoSi return caps; } -const QGstCaps &QGstVideoRenderer::caps() -{ - return m_surfaceCaps; -} - -bool QGstVideoRenderer::start(const QGstCaps& caps) +void QGstVideoRenderer::customEvent(QEvent *event) { - qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::start" << caps; - QMutexLocker locker(&m_mutex); +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wswitch") // case value not in enumerated type ‘QEvent::Type’ - m_frameMirrored = false; - m_frameRotationAngle = QtVideo::Rotation::None; + switch (event->type()) { + case renderFramesEvent: { + // LATER: we currently show every frame. however it may be reasonable to drop frames + // here if the queue contains more than one frame + while (std::optional<RenderBufferState> nextState = m_bufferQueue.dequeue()) + handleNewBuffer(std::move(*nextState)); + return; + } + case stopEvent: { + m_currentState.buffer = {}; + m_currentPipelineFrame = {}; + updateCurrentVideoFrame(m_currentVideoFrame); + return; + } - if (m_active) { - m_flush = true; - m_stop = true; + default: + return; } +QT_WARNING_POP +} - m_startCaps = caps; - /* - Waiting for start() to be invoked in the main thread may block - if gstreamer blocks the main thread until this call is finished. - This situation is rare and usually caused by setState(Null) - while pipeline is being prerolled. +void QGstVideoRenderer::handleNewBuffer(RenderBufferState state) +{ + auto videoBuffer = std::make_unique<QGstVideoBuffer>(state.buffer, m_videoInfo, m_sink, + state.format, state.memoryFormat); + QVideoFrame frame = QVideoFrame(videoBuffer.release(), state.format); + QGstUtils::setFrameTimeStampsFromBuffer(&frame, state.buffer.get()); + frame.setMirrored(state.mirrored); + frame.setRotationAngle(QVideoFrame::RotationAngle(state.rotationAngle)); - The proper solution to this involves controlling gstreamer pipeline from - other thread than video surface. + m_currentPipelineFrame = std::move(frame); + m_currentState = std::move(state); - Currently start() fails if wait() timed out. - */ - if (!waitForAsyncEvent(&locker, &m_setupCondition, 1000) && !m_startCaps.isNull()) { - qWarning() << "Failed to start video surface due to main thread blocked."; - m_startCaps = {}; + if (!m_isActive) { + qCDebug(qLcGstVideoRenderer) << " showing empty video frame"; + updateCurrentVideoFrame({}); + return; } - return m_active; + updateCurrentVideoFrame(m_currentPipelineFrame); } -void QGstVideoRenderer::stop() +const QGstCaps &QGstVideoRenderer::caps() { - QMutexLocker locker(&m_mutex); - - if (!m_active) - return; + return m_surfaceCaps; +} - m_flush = true; - m_stop = true; +bool QGstVideoRenderer::start(const QGstCaps& caps) +{ + qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::start" << caps; - m_startCaps = {}; + { + m_frameRotationAngle = QtVideo::Rotation::None; + auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo(); + if (optionalFormatAndVideoInfo) { + std::tie(m_format, m_videoInfo) = std::move(*optionalFormatAndVideoInfo); + } else { + m_format = {}; + m_videoInfo = {}; + } + m_memoryFormat = caps.memoryFormat(); + } - waitForAsyncEvent(&locker, &m_setupCondition, 500); + return true; } -void QGstVideoRenderer::unlock() +void QGstVideoRenderer::stop() { - QMutexLocker locker(&m_mutex); + qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::stop"; - m_setupCondition.wakeAll(); - m_renderCondition.wakeAll(); + m_bufferQueue.clear(); + QCoreApplication::postEvent(this, new QEvent(stopEvent)); } -bool QGstVideoRenderer::proposeAllocation(GstQuery *query) +void QGstVideoRenderer::unlock() { - Q_UNUSED(query); - QMutexLocker locker(&m_mutex); - return m_active; + qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::unlock"; } -void QGstVideoRenderer::flush() +bool QGstVideoRenderer::proposeAllocation(GstQuery *) { - QMutexLocker locker(&m_mutex); - - m_flush = true; - m_renderBuffer = {}; - m_renderCondition.wakeAll(); - - notify(); + qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::proposeAllocation"; + return true; } GstFlowReturn QGstVideoRenderer::render(GstBuffer *buffer) { - QMutexLocker locker(&m_mutex); qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::render"; - m_renderReturn = GST_FLOW_OK; - m_renderBuffer = QGstBufferHandle{ - buffer, - QGstBufferHandle::NeedsRef, + if (m_flushing) { + qCDebug(qLcGstVideoRenderer) + << " buffer received while flushing the sink ... discarding buffer"; + return GST_FLOW_FLUSHING; + } + + GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer); + if (meta) { + QRect vp(meta->x, meta->y, meta->width, meta->height); + if (m_format.viewport() != vp) { + qCDebug(qLcGstVideoRenderer) + << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x" + << meta->width << " | " << meta->x << "x" << meta->y << "]"; + // Update viewport if data is not the same + m_format.setViewport(vp); + } + } + + RenderBufferState state{ + .buffer = QGstBufferHandle{ buffer, QGstBufferHandle::NeedsRef }, + .format = m_format, + .memoryFormat = m_memoryFormat, + .mirrored = m_frameMirrored, + .rotationAngle = m_frameRotationAngle, }; - waitForAsyncEvent(&locker, &m_renderCondition, 300); + qCDebug(qLcGstVideoRenderer) << " sending video frame"; - m_renderBuffer = {}; + qsizetype sizeOfQueue = m_bufferQueue.enqueue(std::move(state)); + if (sizeOfQueue == 1) + // we only need to wake up, if we don't have a pending frame + QCoreApplication::postEvent(this, new QEvent(renderFramesEvent)); - return m_renderReturn; + return GST_FLOW_OK; } bool QGstVideoRenderer::query(GstQuery *query) @@ -211,6 +251,10 @@ bool QGstVideoRenderer::query(GstQuery *query) if (strcmp(type, "gst.gl.local_context") != 0) return false; + QMutexLocker locker(&m_sinkMutex); + if (!m_sink) + return false; + auto *gstGlContext = m_sink->gstGlLocalContext(); if (!gstGlContext) return false; @@ -227,20 +271,42 @@ bool QGstVideoRenderer::query(GstQuery *query) void QGstVideoRenderer::gstEvent(GstEvent *event) { + qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent:" << event; + 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); + case GST_EVENT_FLUSH_START: + return gstEventHandleFlushStart(event); + case GST_EVENT_FLUSH_STOP: + return gstEventHandleFlushStop(event); default: - qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: unhandled event - " << event; return; } } +void QGstVideoRenderer::setActive(bool isActive) +{ + if (isActive == m_isActive) + return; + + m_isActive = isActive; + if (isActive) + updateCurrentVideoFrame(m_currentPipelineFrame); + else + updateCurrentVideoFrame({}); +} + +void QGstVideoRenderer::updateCurrentVideoFrame(QVideoFrame frame) +{ + m_currentVideoFrame = std::move(frame); + if (m_sink) + m_sink->setVideoFrame(m_currentVideoFrame); +} + void QGstVideoRenderer::gstEventHandleTag(GstEvent *event) { GstTagList *taglist = nullptr; @@ -252,41 +318,10 @@ void QGstVideoRenderer::gstEventHandleTag(GstEvent *event) if (!gst_tag_list_get_string(taglist, GST_TAG_IMAGE_ORIENTATION, &value)) return; - constexpr const char rotate[] = "rotate-"; - constexpr const char flipRotate[] = "flip-rotate-"; - constexpr size_t rotateLen = sizeof(rotate) - 1; - constexpr size_t flipRotateLen = sizeof(flipRotate) - 1; - - bool mirrored = false; - int rotationAngle = 0; - - if (!strncmp(rotate, value.get(), rotateLen)) { - rotationAngle = atoi(value.get() + rotateLen); - } else if (!strncmp(flipRotate, value.get(), flipRotateLen)) { - // To flip by horizontal axis is the same as to mirror by vertical axis - // and rotate by 180 degrees. - mirrored = true; - rotationAngle = (180 + atoi(value.get() + flipRotateLen)) % 360; - } + RotationResult parsed = parseRotationTag(value.get()); - 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; - } + m_frameRotationAngle = parsed.rotation; + m_frameMirrored = parsed.flip; } void QGstVideoRenderer::gstEventHandleEOS(GstEvent *) @@ -294,137 +329,17 @@ void QGstVideoRenderer::gstEventHandleEOS(GstEvent *) stop(); } -bool QGstVideoRenderer::event(QEvent *event) +void QGstVideoRenderer::gstEventHandleFlushStart(GstEvent *) { - if (event->type() == QEvent::UpdateRequest) { - QMutexLocker locker(&m_mutex); - - if (m_notified) { - while (handleEvent(&locker)) {} - m_notified = false; - } - return true; - } - - return QObject::event(event); + // "data is to be discarded" + m_flushing = true; + m_bufferQueue.clear(); } -bool QGstVideoRenderer::handleEvent(QMutexLocker<QMutex> *locker) +void QGstVideoRenderer::gstEventHandleFlushStop(GstEvent *) { - if (m_flush) { - m_flush = false; - if (m_active) { - locker->unlock(); - - if (m_sink && !m_flushed) - m_sink->setVideoFrame(QVideoFrame()); - m_flushed = true; - locker->relock(); - } - } else if (m_stop) { - m_stop = false; - - if (m_active) { - m_active = false; - m_flushed = true; - } - } else if (!m_startCaps.isNull()) { - Q_ASSERT(!m_active); - - auto startCaps = m_startCaps; - m_startCaps = {}; - - if (m_sink) { - locker->unlock(); - - m_flushed = true; - auto optionalFormatAndVideoInfo = startCaps.formatAndVideoInfo(); - if (optionalFormatAndVideoInfo) { - std::tie(m_format, m_videoInfo) = std::move(*optionalFormatAndVideoInfo); - } else { - m_format = {}; - m_videoInfo = {}; - } - - memoryFormat = startCaps.memoryFormat(); - - locker->relock(); - m_active = m_format.isValid(); - } else if (m_active) { - m_active = false; - m_flushed = true; - } - - } else if (m_renderBuffer) { - 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) { - - locker->unlock(); - - m_flushed = false; - - 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) { - qCDebug(qLcGstVideoRenderer) << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x" << meta->width << " | " << meta->x << "x" << meta->y << "]"; - // Update viewport if data is not the same - m_format.setViewport(vp); - } - } - - if (m_sink->inStoppedState()) { - qCDebug(qLcGstVideoRenderer) << " sending empty video frame"; - m_sink->setVideoFrame(QVideoFrame()); - } else { - QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, m_videoInfo, m_sink, m_format, memoryFormat); - QVideoFrame frame(videoBuffer, m_format); - QGstUtils::setFrameTimeStampsFromBuffer(&frame, buffer.get()); - frame.setMirrored(m_frameMirrored); - frame.setRotationAngle(QVideoFrame::RotationAngle(m_frameRotationAngle)); - - qCDebug(qLcGstVideoRenderer) << " sending video frame"; - m_sink->setVideoFrame(frame); - } - - locker->relock(); - - m_renderReturn = GST_FLOW_OK; - } - - m_renderCondition.wakeAll(); - } else { - m_setupCondition.wakeAll(); - - return false; - } - return true; -} - -void QGstVideoRenderer::notify() -{ - if (!m_notified) { - m_notified = true; - QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); - } -} - -bool QGstVideoRenderer::waitForAsyncEvent( - QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time) -{ - if (QThread::currentThread() == thread()) { - while (handleEvent(locker)) {} - m_notified = false; - - return true; - } - - notify(); - - return condition->wait(&m_mutex, time); + // "data is allowed again" + m_flushing = false; } static GstVideoSinkClass *gvrs_sink_parent_class; @@ -432,15 +347,16 @@ static thread_local QGstreamerVideoSink *gvrs_current_sink; #define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s)) -QGstVideoRendererSink *QGstVideoRendererSink::createSink(QGstreamerVideoSink *sink) +QGstVideoRendererSinkElement QGstVideoRendererSink::createSink(QGstreamerVideoSink *sink) { setSink(sink); QGstVideoRendererSink *gstSink = reinterpret_cast<QGstVideoRendererSink *>( g_object_new(QGstVideoRendererSink::get_type(), nullptr)); - g_signal_connect(G_OBJECT(gstSink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), gstSink); - - return gstSink; + return QGstVideoRendererSinkElement{ + gstSink, + QGstElement::NeedsRef, + }; } void QGstVideoRendererSink::setSink(QGstreamerVideoSink *sink) @@ -535,41 +451,9 @@ void QGstVideoRendererSink::finalize(GObject *object) G_OBJECT_CLASS(gvrs_sink_parent_class)->finalize(object); } -void QGstVideoRendererSink::handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d) -{ - Q_UNUSED(o); - Q_UNUSED(p); - QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(d); - - gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default - g_object_get(G_OBJECT(sink), "show-preroll-frame", &showPrerollFrame, nullptr); - - if (!showPrerollFrame) { - GstState state = GST_STATE_VOID_PENDING; - GstClockTime timeout = 10000000; // 10 ms - gst_element_get_state(GST_ELEMENT(sink), &state, nullptr, timeout); - // show-preroll-frame being set to 'false' while in GST_STATE_PAUSED means - // the QMediaPlayer was stopped from the paused state. - // We need to flush the current frame. - if (state == GST_STATE_PAUSED) - sink->renderer->flush(); - } -} - GstStateChangeReturn QGstVideoRendererSink::change_state( GstElement *element, GstStateChange transition) { - QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(element); - - gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default - g_object_get(G_OBJECT(element), "show-preroll-frame", &showPrerollFrame, nullptr); - - // If show-preroll-frame is 'false' when transitioning from GST_STATE_PLAYING to - // GST_STATE_PAUSED, it means the QMediaPlayer was stopped. - // We need to flush the current frame. - if (transition == GST_STATE_CHANGE_PLAYING_TO_PAUSED && !showPrerollFrame) - sink->renderer->flush(); - return GST_ELEMENT_CLASS(gvrs_sink_parent_class)->change_state(element, transition); } @@ -641,4 +525,23 @@ gboolean QGstVideoRendererSink::event(GstBaseSink *base, GstEvent * event) return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->event(base, event); } +QGstVideoRendererSinkElement::QGstVideoRendererSinkElement(QGstVideoRendererSink *element, + RefMode mode) + : QGstBaseSink{ + qGstCheckedCast<GstBaseSink>(element), + mode, + } +{ +} + +void QGstVideoRendererSinkElement::setActive(bool isActive) +{ + qGstVideoRendererSink()->renderer->setActive(isActive); +} + +QGstVideoRendererSink *QGstVideoRendererSinkElement::qGstVideoRendererSink() const +{ + return reinterpret_cast<QGstVideoRendererSink *>(element()); +} + QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h index 99d1b0ac8..862a3e1cd 100644 --- a/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h +++ b/src/plugins/multimedia/gstreamer/common/qgstvideorenderersink_p.h @@ -15,92 +15,144 @@ // We mean it. // +#include <QtMultimedia/qvideoframeformat.h> +#include <QtMultimedia/qvideoframe.h> #include <QtMultimedia/private/qtmultimediaglobal_p.h> -#include <gst/video/gstvideosink.h> -#include <gst/video/video.h> - +#include <QtCore/qcoreevent.h> #include <QtCore/qlist.h> #include <QtCore/qmutex.h> -#include <QtCore/qqueue.h> #include <QtCore/qpointer.h> +#include <QtCore/qqueue.h> #include <QtCore/qwaitcondition.h> -#include <qvideoframeformat.h> -#include <qvideoframe.h> + +#include <gst/video/gstvideosink.h> +#include <gst/video/video.h> + #include <common/qgstvideobuffer_p.h> #include <common/qgst_p.h> QT_BEGIN_NAMESPACE -class QVideoSink; + +namespace QGstUtils { + +template <typename T> +class QConcurrentQueue +{ +public: + qsizetype enqueue(T value) + { + QMutexLocker locker(&mutex); + queue.append(std::move(value)); + return queue.size(); + } + + std::optional<T> dequeue() + { + QMutexLocker locker(&mutex); + if (queue.isEmpty()) + return std::nullopt; + + return queue.takeFirst(); + } + + void clear() + { + QMutexLocker locker(&mutex); + queue.clear(); + } + +private: + QMutex mutex; + QList<T> queue; +}; + +} // namespace QGstUtils class QGstVideoRenderer : public QObject { + struct RenderBufferState + { + QGstBufferHandle buffer; + QVideoFrameFormat format; + QGstCaps::MemoryFormat memoryFormat; + bool mirrored; + QtVideo::Rotation rotationAngle; + + bool operator==(const RenderBufferState &rhs) const + { + return std::tie(buffer, format, memoryFormat, mirrored, rotationAngle) + == std::tie(rhs.buffer, rhs.format, rhs.memoryFormat, rhs.mirrored, + rhs.rotationAngle); + } + }; + + static constexpr QEvent::Type renderFramesEvent = static_cast<QEvent::Type>(QEvent::User + 100); + static constexpr QEvent::Type stopEvent = static_cast<QEvent::Type>(QEvent::User + 101); + public: - explicit QGstVideoRenderer(QGstreamerVideoSink *sink); + explicit QGstVideoRenderer(QGstreamerVideoSink *); ~QGstVideoRenderer(); const QGstCaps &caps(); - bool start(const QGstCaps& caps); + bool start(const QGstCaps &); void stop(); void unlock(); - bool proposeAllocation(GstQuery *query); + bool proposeAllocation(GstQuery *); + GstFlowReturn render(GstBuffer *); + bool query(GstQuery *); + void gstEvent(GstEvent *); - void flush(); - - GstFlowReturn render(GstBuffer *buffer); - - bool event(QEvent *event) override; - bool query(GstQuery *query); - void gstEvent(GstEvent *event); - -private slots: - bool handleEvent(QMutexLocker<QMutex> *locker); + void setActive(bool); private: + void updateCurrentVideoFrame(QVideoFrame); + void notify(); - bool waitForAsyncEvent(QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time); static QGstCaps createSurfaceCaps(QGstreamerVideoSink *); + void customEvent(QEvent *) override; + void handleNewBuffer(RenderBufferState); + void gstEventHandleTag(GstEvent *); void gstEventHandleEOS(GstEvent *); + void gstEventHandleFlushStart(GstEvent *); + void gstEventHandleFlushStop(GstEvent *); - QPointer<QGstreamerVideoSink> m_sink; - - QMutex m_mutex; - QWaitCondition m_setupCondition; - QWaitCondition m_renderCondition; - - // --- accessed from multiple threads, need to hold mutex to access - GstFlowReturn m_renderReturn = GST_FLOW_OK; - bool m_active = false; + QMutex m_sinkMutex; + QGstreamerVideoSink *m_sink = nullptr; // written only from qt thread. so only readers on + // worker threads need to acquire the lock + // --- only accessed from gstreamer thread const QGstCaps m_surfaceCaps; - - QGstCaps m_startCaps; - QGstBufferHandle m_renderBuffer; - - bool m_notified = false; - bool m_stop = false; - bool m_flush = false; + QVideoFrameFormat m_format; + GstVideoInfo m_videoInfo{}; + QGstCaps::MemoryFormat m_memoryFormat = QGstCaps::CpuMemory; bool m_frameMirrored = false; QtVideo::Rotation m_frameRotationAngle = QtVideo::Rotation::None; - // --- only accessed from one thread - QVideoFrameFormat m_format; - GstVideoInfo m_videoInfo{}; - bool m_flushed = true; - QGstCaps::MemoryFormat memoryFormat = QGstCaps::CpuMemory; + // --- only accessed from qt thread + QVideoFrame m_currentPipelineFrame; + QVideoFrame m_currentVideoFrame; + bool m_isActive{ false }; + + RenderBufferState m_currentState; + QGstUtils::QConcurrentQueue<RenderBufferState> m_bufferQueue; + bool m_flushing{ false }; }; +class QGstVideoRendererSinkElement; + class QGstVideoRendererSink { public: GstVideoSink parent{}; - static QGstVideoRendererSink *createSink(QGstreamerVideoSink *surface); - static void setSink(QGstreamerVideoSink *surface); + static QGstVideoRendererSinkElement createSink(QGstreamerVideoSink *surface); private: + static void setSink(QGstreamerVideoSink *surface); + static GType get_type(); static void class_init(gpointer g_class, gpointer class_data); static void base_init(gpointer g_class); @@ -108,8 +160,6 @@ private: static void finalize(GObject *object); - static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d); - static GstStateChangeReturn change_state(GstElement *element, GstStateChange transition); static GstCaps *get_caps(GstBaseSink *sink, GstCaps *filter); @@ -123,19 +173,36 @@ private: static GstFlowReturn show_frame(GstVideoSink *sink, GstBuffer *buffer); static gboolean query(GstBaseSink *element, GstQuery *query); - static gboolean event(GstBaseSink *element, GstEvent * event); + static gboolean event(GstBaseSink *element, GstEvent *event); + + friend class QGstVideoRendererSinkElement; -private: QGstVideoRenderer *renderer = nullptr; }; - class QGstVideoRendererSinkClass { public: GstVideoSinkClass parent_class; }; +class QGstVideoRendererSinkElement : public QGstBaseSink +{ +public: + using QGstBaseSink::QGstBaseSink; + + explicit QGstVideoRendererSinkElement(QGstVideoRendererSink *, RefMode); + + QGstVideoRendererSinkElement(const QGstVideoRendererSinkElement &) = default; + QGstVideoRendererSinkElement(QGstVideoRendererSinkElement &&) noexcept = default; + QGstVideoRendererSinkElement &operator=(const QGstVideoRendererSinkElement &) = default; + QGstVideoRendererSinkElement &operator=(QGstVideoRendererSinkElement &&) noexcept = default; + + void setActive(bool); + + QGstVideoRendererSink *qGstVideoRendererSink() const; +}; + QT_END_NAMESPACE #endif diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera.cpp index 8d3cd6baf..91177ca31 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera.cpp +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera.cpp @@ -5,6 +5,7 @@ #include <QtMultimedia/qcameradevice.h> #include <QtMultimedia/qmediacapturesession.h> +#include <QtMultimedia/private/qcameradevice_p.h> #include <QtCore/qdebug.h> #include <common/qgst_debug_p.h> @@ -21,36 +22,35 @@ QT_BEGIN_NAMESPACE QMaybe<QPlatformCamera *> QGstreamerCamera::create(QCamera *camera) { - QGstElement videotestsrc = QGstElement::createFromFactory("videotestsrc"); - if (!videotestsrc) - return errorMessageCannotFindElement("videotestsrc"); + static const auto error = qGstErrorMessageIfElementsNotAvailable( + "videotestsrc", "capsfilter", "videoconvert", "videoscale", "identity"); + if (error) + return *error; - QGstElement capsFilter = QGstElement::createFromFactory("capsfilter", "videoCapsFilter"); - if (!capsFilter) - return errorMessageCannotFindElement("capsfilter"); - - QGstElement videoconvert = QGstElement::createFromFactory("videoconvert", "videoConvert"); - if (!videoconvert) - return errorMessageCannotFindElement("videoconvert"); - - QGstElement videoscale = QGstElement::createFromFactory("videoscale", "videoScale"); - if (!videoscale) - return errorMessageCannotFindElement("videoscale"); - - return new QGstreamerCamera(videotestsrc, capsFilter, videoconvert, videoscale, camera); + return new QGstreamerCamera(camera); } -QGstreamerCamera::QGstreamerCamera(QGstElement videotestsrc, QGstElement capsFilter, - QGstElement videoconvert, QGstElement videoscale, - QCamera *camera) - : QPlatformCamera(camera), - gstCamera(std::move(videotestsrc)), - gstCapsFilter(std::move(capsFilter)), - gstVideoConvert(std::move(videoconvert)), - gstVideoScale(std::move(videoscale)) +QGstreamerCamera::QGstreamerCamera(QCamera *camera) + : QGstreamerCameraBase(camera), + gstCameraBin{ + QGstBin::create("camerabin"), + }, + gstCamera{ + QGstElement::createFromFactory("videotestsrc"), + }, + gstCapsFilter{ + QGstElement::createFromFactory("capsfilter", "videoCapsFilter"), + }, + gstDecode{ + QGstElement::createFromFactory("identity"), + }, + gstVideoConvert{ + QGstElement::createFromFactory("videoconvert", "videoConvert"), + }, + gstVideoScale{ + QGstElement::createFromFactory("videoscale", "videoScale"), + } { - gstDecode = QGstElement::createFromFactory("identity"); - gstCameraBin = QGstBin::create("camerabin"); gstCameraBin.add(gstCamera, gstCapsFilter, gstDecode, gstVideoConvert, gstVideoScale); qLinkGstElements(gstCamera, gstCapsFilter, gstDecode, gstVideoConvert, gstVideoScale); gstCameraBin.addGhostPad(gstVideoScale, "src"); @@ -80,6 +80,8 @@ void QGstreamerCamera::setActive(bool active) void QGstreamerCamera::setCamera(const QCameraDevice &camera) { + using namespace Qt::Literals; + if (m_cameraDevice == camera) return; @@ -90,12 +92,24 @@ void QGstreamerCamera::setCamera(const QCameraDevice &camera) gstNewCamera = QGstElement::createFromFactory("videotestsrc"); } else { auto *integration = static_cast<QGstreamerIntegration *>(QGstreamerIntegration::instance()); - auto *device = integration->videoDevice(camera.id()); + GstDevice *device = integration->videoDevice(camera.id()); + + if (!device) { + updateError(QCamera::Error::CameraError, + u"Failed to create GstDevice for camera: "_s + + QString::fromUtf8(camera.id())); + return; + } + gstNewCamera = QGstElement::createFromDevice(device, "camerasrc"); - if (QGstStructure properties = gst_device_get_properties(device); !properties.isNull()) { - if (properties.name() == "v4l2deviceprovider") - m_v4l2DevicePath = QString::fromUtf8(properties["device.path"].toString()); - properties.free(); + QUniqueGstStructureHandle properties{ + gst_device_get_properties(device), + }; + + if (properties) { + QGstStructureView propertiesView{ properties }; + if (propertiesView.name() == "v4l2deviceprovider") + m_v4l2DevicePath = QString::fromUtf8(propertiesView["device.path"].toString()); } } @@ -104,7 +118,7 @@ void QGstreamerCamera::setCamera(const QCameraDevice &camera) auto gstNewDecode = QGstElement::createFromFactory( f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec" : "identity"); - QGstPipeline::modifyPipelineWhileNotRunning(gstCamera.getPipeline(), [&] { + gstVideoConvert.sink().modifyPipelineInIdleProbe([&] { qUnlinkGstElements(gstCamera, gstCapsFilter, gstDecode, gstVideoConvert); gstCameraBin.stopAndRemoveElements(gstCamera, gstDecode); @@ -136,9 +150,7 @@ bool QGstreamerCamera::setCameraFormat(const QCameraFormat &format) auto newGstDecode = QGstElement::createFromFactory( f.pixelFormat() == QVideoFrameFormat::Format_Jpeg ? "jpegdec" : "identity"); - QGstPipeline::modifyPipelineWhileNotRunning(gstCamera.getPipeline(), [&] { - newGstDecode.syncStateWithParent(); - + gstVideoConvert.sink().modifyPipelineInIdleProbe([&] { qUnlinkGstElements(gstCamera, gstCapsFilter, gstDecode, gstVideoConvert); gstCameraBin.stopAndRemoveElements(gstDecode); @@ -703,8 +715,53 @@ int QGstreamerCamera::getV4L2Parameter(quint32 id) const }); } -#endif +#endif // QT_CONFIG(linux_v4l) -QT_END_NAMESPACE +QGstreamerCustomCamera::QGstreamerCustomCamera(QCamera *camera) + : QGstreamerCameraBase{ + camera, + }, + m_userProvidedGstElement{ + false, + } +{ +} -#include "moc_qgstreamercamera_p.cpp" +QGstreamerCustomCamera::QGstreamerCustomCamera(QCamera *camera, QGstElement element) + : QGstreamerCameraBase{ + camera, + }, + gstCamera{ + std::move(element), + }, + m_userProvidedGstElement{ + true, + } +{ +} + +void QGstreamerCustomCamera::setCamera(const QCameraDevice &device) +{ + if (m_userProvidedGstElement) + return; + + gstCamera = QGstBin::createFromPipelineDescription(device.id(), /*name=*/nullptr, + /* ghostUnlinkedPads=*/true); +} + +bool QGstreamerCustomCamera::isActive() const +{ + return m_active; +} + +void QGstreamerCustomCamera::setActive(bool active) +{ + if (m_active == active) + return; + + m_active = active; + + emit activeChanged(active); +} + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera_p.h index 74f12f918..f43c01f34 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera_p.h +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamercamera_p.h @@ -24,9 +24,16 @@ QT_BEGIN_NAMESPACE -class QGstreamerCamera : public QPlatformCamera +class QGstreamerCameraBase : public QPlatformCamera +{ +public: + using QPlatformCamera::QPlatformCamera; + + virtual QGstElement gstElement() const = 0; +}; + +class QGstreamerCamera : public QGstreamerCameraBase { - Q_OBJECT public: static QMaybe<QPlatformCamera *> create(QCamera *camera); @@ -38,7 +45,7 @@ public: void setCamera(const QCameraDevice &camera) override; bool setCameraFormat(const QCameraFormat &format) override; - QGstElement gstElement() const { return gstCameraBin; } + QGstElement gstElement() const override { return gstCameraBin; } #if QT_CONFIG(gstreamer_photography) GstPhotography *photography() const; #endif @@ -63,8 +70,7 @@ public: void setColorTemperature(int temperature) override; private: - QGstreamerCamera(QGstElement videotestsrc, QGstElement capsFilter, QGstElement videoconvert, - QGstElement videoscale, QCamera *camera); + QGstreamerCamera(QCamera *camera); void updateCameraProperties(); @@ -118,12 +124,29 @@ private: QGstElement gstDecode; QGstElement gstVideoConvert; QGstElement gstVideoScale; - QGstPipeline gstPipeline; bool m_active = false; QString m_v4l2DevicePath; }; +class QGstreamerCustomCamera : public QGstreamerCameraBase +{ +public: + explicit QGstreamerCustomCamera(QCamera *); + explicit QGstreamerCustomCamera(QCamera *, QGstElement element); + + QGstElement gstElement() const override { return gstCamera; } + void setCamera(const QCameraDevice &) override; + + bool isActive() const override; + void setActive(bool) override; + +private: + QGstElement gstCamera; + bool m_active{}; + const bool m_userProvidedGstElement; +}; + QT_END_NAMESPACE #endif // QGSTREAMERCAMERACONTROL_H diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp index 3500d6b6f..a3e5ec523 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture.cpp @@ -10,6 +10,7 @@ #include <QtCore/qdebug.h> #include <QtCore/qdir.h> #include <QtCore/qstandardpaths.h> +#include <QtCore/qcoreapplication.h> #include <QtCore/qloggingcategory.h> #include <common/qgstreamermetadata_p.h> @@ -20,37 +21,96 @@ QT_BEGIN_NAMESPACE -static Q_LOGGING_CATEGORY(qLcImageCaptureGst, "qt.multimedia.imageCapture") +namespace { +Q_LOGGING_CATEGORY(qLcImageCaptureGst, "qt.multimedia.imageCapture") -QMaybe<QPlatformImageCapture *> QGstreamerImageCapture::create(QImageCapture *parent) +struct ThreadPoolSingleton { - QGstElement videoconvert = - QGstElement::createFromFactory("videoconvert", "imageCaptureConvert"); - if (!videoconvert) - return errorMessageCannotFindElement("videoconvert"); + QObject m_context; + QMutex m_poolMutex; + QThreadPool *m_instance{}; + bool m_appUnderDestruction = false; + + QThreadPool *get(const QMutexLocker<QMutex> &) + { + if (m_instance) + return m_instance; + if (m_appUnderDestruction || !qApp) + return nullptr; + + using namespace std::chrono; + + m_instance = new QThreadPool(qApp); + m_instance->setMaxThreadCount(1); // 1 thread; + static constexpr auto expiryTimeout = minutes(5); + m_instance->setExpiryTimeout(round<milliseconds>(expiryTimeout).count()); + + QObject::connect(qApp, &QCoreApplication::aboutToQuit, &m_context, [&] { + // we need to make sure that thread-local QRhi is destroyed before the application to + // prevent QTBUG-124189 + QMutexLocker guard(&m_poolMutex); + delete m_instance; + m_instance = {}; + m_appUnderDestruction = true; + }); + + QObject::connect(qApp, &QCoreApplication::destroyed, &m_context, [&] { + m_appUnderDestruction = false; + }); + return m_instance; + } + + template <typename Functor> + QFuture<void> run(Functor &&f) + { + QMutexLocker guard(&m_poolMutex); + QThreadPool *pool = get(guard); + if (!pool) + return QFuture<void>{}; + + return QtConcurrent::run(pool, std::forward<Functor>(f)); + } +}; - QGstElement jpegenc = QGstElement::createFromFactory("jpegenc", "jpegEncoder"); - if (!jpegenc) - return errorMessageCannotFindElement("jpegenc"); +ThreadPoolSingleton s_threadPoolSingleton; - QGstElement jifmux = QGstElement::createFromFactory("jifmux", "jpegMuxer"); - if (!jifmux) - return errorMessageCannotFindElement("jifmux"); +}; // namespace - return new QGstreamerImageCapture(videoconvert, jpegenc, jifmux, parent); +QMaybe<QPlatformImageCapture *> QGstreamerImageCapture::create(QImageCapture *parent) +{ + static const auto error = qGstErrorMessageIfElementsNotAvailable( + "queue", "capsfilter", "videoconvert", "jpegenc", "jifmux", "fakesink"); + if (error) + return *error; + + return new QGstreamerImageCapture(parent); } -QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstElement jpegenc, - QGstElement jifmux, QImageCapture *parent) +QGstreamerImageCapture::QGstreamerImageCapture(QImageCapture *parent) : QPlatformImageCapture(parent), QGstreamerBufferProbe(ProbeBuffers), - videoConvert(std::move(videoconvert)), - encoder(std::move(jpegenc)), - muxer(std::move(jifmux)) + bin{ + QGstBin::create("imageCaptureBin"), + }, + queue{ + QGstElement::createFromFactory("queue", "imageCaptureQueue"), + }, + filter{ + QGstElement::createFromFactory("capsfilter", "filter"), + }, + videoConvert{ + QGstElement::createFromFactory("videoconvert", "imageCaptureConvert"), + }, + encoder{ + QGstElement::createFromFactory("jpegenc", "jpegEncoder"), + }, + muxer{ + QGstElement::createFromFactory("jifmux", "jpegMuxer"), + }, + sink{ + QGstElement::createFromFactory("fakesink", "imageCaptureSink"), + } { - bin = QGstBin::create("imageCaptureBin"); - - queue = QGstElement::createFromFactory("queue", "imageCaptureQueue"); // configures the queue to be fast, lightweight and non blocking queue.set("leaky", 2 /*downstream*/); queue.set("silent", true); @@ -58,8 +118,6 @@ QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstEle queue.set("max-size-bytes", uint(0)); queue.set("max-size-time", quint64(0)); - sink = QGstElement::createFromFactory("fakesink", "imageCaptureSink"); - filter = QGstElement::createFromFactory("capsfilter", "filter"); // imageCaptureSink do not wait for a preroll buffer when going READY -> PAUSED // as no buffer will arrive until capture() is called sink.set("async", false); @@ -77,10 +135,20 @@ QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstEle QGstreamerImageCapture::~QGstreamerImageCapture() { bin.setStateSync(GST_STATE_NULL); + + // wait for pending futures + auto pendingFutures = [&] { + QMutexLocker guard(&m_mutex); + return std::move(m_pendingFutures); + }(); + + for (QFuture<void> &pendingImage : pendingFutures) + pendingImage.waitForFinished(); } bool QGstreamerImageCapture::isReadyForCapture() const { + QMutexLocker guard(&m_mutex); return m_session && !passImage && cameraActive; } @@ -101,43 +169,40 @@ 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) { - invokeDeferred([this] { - emit error(-1, QImageCapture::ResourceError, - QPlatformImageCapture::msgImageCaptureNotSet()); - }); - - qCDebug(qLcImageCaptureGst) << "error 1"; - return -1; + { + QMutexLocker guard(&m_mutex); + if (!m_session) { + invokeDeferred([this] { + emit error(-1, QImageCapture::ResourceError, + QPlatformImageCapture::msgImageCaptureNotSet()); + }); + + qCDebug(qLcImageCaptureGst) << "error 1"; + return -1; + } + if (!m_session->camera()) { + invokeDeferred([this] { + emit error(-1, QImageCapture::ResourceError, tr("No camera available.")); + }); + + qCDebug(qLcImageCaptureGst) << "error 2"; + return -1; + } + if (passImage) { + invokeDeferred([this] { + emit error(-1, QImageCapture::NotReadyError, + QPlatformImageCapture::msgCameraNotReady()); + }); + + qCDebug(qLcImageCaptureGst) << "error 3"; + return -1; + } + m_lastId++; + + pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} }); + // let one image pass the pipeline + passImage = true; } - if (!m_session->camera()) { - invokeDeferred([this] { - emit error(-1, QImageCapture::ResourceError, tr("No camera available.")); - }); - - qCDebug(qLcImageCaptureGst) << "error 2"; - return -1; - } - if (passImage) { - invokeDeferred([this] { - emit error(-1, QImageCapture::NotReadyError, - QPlatformImageCapture::msgCameraNotReady()); - }); - - qCDebug(qLcImageCaptureGst) << "error 3"; - return -1; - } - m_lastId++; - - pendingImages.enqueue({m_lastId, fileName, QMediaMetaData{}}); - // let one image pass the pipeline - passImage = true; emit readyForCaptureChanged(false); return m_lastId; @@ -159,8 +224,17 @@ void QGstreamerImageCapture::setResolution(const QSize &resolution) filter.set("caps", caps); } +// HACK: gcc-10 and earlier reject [=,this] when building with c++17 +#if __cplusplus >= 202002L +# define EQ_THIS_CAPTURE =, this +#else +# define EQ_THIS_CAPTURE = +#endif + bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer) { + QMutexLocker guard(&m_mutex); + if (!passImage) return false; qCDebug(qLcImageCaptureGst) << "probe buffer"; @@ -172,7 +246,10 @@ bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer) passImage = false; - emit readyForCaptureChanged(isReadyForCapture()); + bool ready = isReadyForCapture(); + invokeDeferred([this, ready] { + emit readyForCaptureChanged(ready); + }); QGstCaps caps = bin.staticPad("sink").currentCaps(); auto memoryFormat = caps.memoryFormat(); @@ -183,42 +260,61 @@ bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer) if (optionalFormatAndVideoInfo) std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo); - auto *sink = m_session->gstreamerVideoSink(); - auto *gstBuffer = new QGstVideoBuffer{ - std::move(bufferHandle), previewInfo, sink, fmt, memoryFormat, - }; - QVideoFrame frame(gstBuffer, fmt); - QImage img = frame.toImage(); - if (img.isNull()) { - qDebug() << "received a null image"; - return true; - } - - auto &imageData = pendingImages.head(); + int futureId = futureIDAllocator += 1; - emit imageExposed(imageData.id); - - qCDebug(qLcImageCaptureGst) << "Image available!"; - emit imageAvailable(imageData.id, frame); - - emit imageCaptured(imageData.id, img); + // ensure QVideoFrame::toImage is executed on a worker thread that is joined before the + // qApplication is destroyed + QFuture<void> future = s_threadPoolSingleton.run([EQ_THIS_CAPTURE]() mutable { + QMutexLocker guard(&m_mutex); + auto scopeExit = qScopeGuard([&] { + m_pendingFutures.remove(futureId); + }); - QMediaMetaData metaData = this->metaData(); - metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime()); - metaData.insert(QMediaMetaData::Resolution, frame.size()); - imageData.metaData = metaData; + if (!m_session) { + qDebug() << "QGstreamerImageCapture::probeBuffer: no session"; + return; + } + + auto *sink = m_session->gstreamerVideoSink(); + auto *gstBuffer = new QGstVideoBuffer{ + std::move(bufferHandle), previewInfo, sink, fmt, memoryFormat, + }; + QVideoFrame frame(gstBuffer, fmt); + + QImage img = frame.toImage(); + if (img.isNull()) { + qDebug() << "received a null image"; + return; + } + + QMediaMetaData imageMetaData = metaData(); + imageMetaData.insert(QMediaMetaData::Resolution, frame.size()); + pendingImages.head().metaData = std::move(imageMetaData); + PendingImage pendingImage = pendingImages.head(); + + invokeDeferred([this, pendingImage = std::move(pendingImage), frame = std::move(frame), + img = std::move(img)]() mutable { + emit imageExposed(pendingImage.id); + qCDebug(qLcImageCaptureGst) << "Image available!"; + emit imageAvailable(pendingImage.id, frame); + emit imageCaptured(pendingImage.id, img); + emit imageMetadataAvailable(pendingImage.id, pendingImage.metaData); + }); + }); - // ensure taginject injects this metaData - const auto &md = static_cast<const QGstreamerMetaData &>(metaData); - md.setMetaData(muxer.element()); + if (!future.isValid()) // during qApplication shutdown the threadpool becomes unusable + return true; - emit imageMetadataAvailable(imageData.id, metaData); + m_pendingFutures.insert(futureId, future); return true; } +#undef EQ_THIS_CAPTURE + void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session) { + QMutexLocker guard(&m_mutex); QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session); if (m_session == captureSession) return; @@ -244,6 +340,17 @@ void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *ses onCameraChanged(); } +void QGstreamerImageCapture::setMetaData(const QMediaMetaData &m) +{ + { + QMutexLocker guard(&m_mutex); + QPlatformImageCapture::setMetaData(m); + } + + // ensure taginject injects this metaData + applyMetaDataToTagSetter(m, muxer); +} + void QGstreamerImageCapture::cameraActiveChanged(bool active) { qCDebug(qLcImageCaptureGst) << "cameraActiveChanged" << cameraActive << active; @@ -256,9 +363,11 @@ void QGstreamerImageCapture::cameraActiveChanged(bool active) void QGstreamerImageCapture::onCameraChanged() { + QMutexLocker guard(&m_mutex); if (m_session->camera()) { cameraActiveChanged(m_session->camera()->isActive()); - connect(m_session->camera(), &QPlatformCamera::activeChanged, this, &QGstreamerImageCapture::cameraActiveChanged); + connect(m_session->camera(), &QPlatformCamera::activeChanged, this, + &QGstreamerImageCapture::cameraActiveChanged); } else { cameraActiveChanged(false); } @@ -273,33 +382,51 @@ gboolean QGstreamerImageCapture::saveImageFilter(GstElement *, GstBuffer *buffer void QGstreamerImageCapture::saveBufferToImage(GstBuffer *buffer) { + QMutexLocker guard(&m_mutex); passImage = false; if (pendingImages.isEmpty()) return; - auto imageData = pendingImages.dequeue(); + PendingImage imageData = pendingImages.dequeue(); if (imageData.filename.isEmpty()) return; - qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename; + int id = futureIDAllocator++; + QGstBufferHandle bufferHandle{ + buffer, + QGstBufferHandle::NeedsRef, + }; - QFile f(imageData.filename); - if (!f.open(QFile::WriteOnly)) { - qCDebug(qLcImageCaptureGst) << " could not open image file for writing"; - return; - } + QFuture<void> saveImageFuture = QtConcurrent::run([this, imageData, bufferHandle, + id]() mutable { + auto cleanup = qScopeGuard([&] { + QMutexLocker guard(&m_mutex); + m_pendingFutures.remove(id); + }); - 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(); + qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename; + + QFile f(imageData.filename); + if (!f.open(QFile::WriteOnly)) { + qCDebug(qLcImageCaptureGst) << " could not open image file for writing"; + return; + } - QMetaObject::invokeMethod(this, [this, imageData = std::move(imageData)]() mutable { - imageSaved(imageData.id, imageData.filename); + GstMapInfo info; + GstBuffer *buffer = bufferHandle.get(); + 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 { + emit imageSaved(imageData.id, imageData.filename); + }); }); + + m_pendingFutures.insert(id, saveImageFuture); } QImageEncoderSettings QGstreamerImageCapture::imageSettings() const diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h index 79c6a02e0..04a7c00b4 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamerimagecapture_p.h @@ -18,7 +18,9 @@ #include <QtMultimedia/private/qplatformimagecapture_p.h> #include <QtMultimedia/private/qmultimediautils_p.h> +#include <QtCore/qmutex.h> #include <QtCore/qqueue.h> +#include <QtConcurrent/QtConcurrentRun> #include <common/qgst_p.h> #include <common/qgstreamerbufferprobe_p.h> @@ -28,9 +30,9 @@ QT_BEGIN_NAMESPACE class QGstreamerImageCapture : public QPlatformImageCapture, private QGstreamerBufferProbe - { Q_OBJECT + public: static QMaybe<QPlatformImageCapture *> create(QImageCapture *parent); virtual ~QGstreamerImageCapture(); @@ -48,13 +50,14 @@ public: QGstElement gstElement() const { return bin; } + void setMetaData(const QMediaMetaData &m) override; + public Q_SLOTS: void cameraActiveChanged(bool active); void onCameraChanged(); private: - QGstreamerImageCapture(QGstElement videoconvert, QGstElement jpegenc, QGstElement jifmux, - QImageCapture *parent); + QGstreamerImageCapture(QImageCapture *parent); void setResolution(const QSize &resolution); int doCapture(const QString &fileName); @@ -63,6 +66,8 @@ private: void saveBufferToImage(GstBuffer *buffer); + mutable QRecursiveMutex + m_mutex; // guard all elements accessed from probeBuffer/saveBufferToImage QGstreamerMediaCapture *m_session = nullptr; int m_lastId = 0; QImageEncoderSettings m_settings; @@ -88,6 +93,15 @@ private: bool cameraActive = false; QGObjectHandlerScopedConnection m_handoffConnection; + + QMap<int, QFuture<void>> m_pendingFutures; + int futureIDAllocator = 0; + + template <typename Functor> + void invokeDeferred(Functor &&fn) + { + QMetaObject::invokeMethod(this, std::forward<decltype(fn)>(fn), Qt::QueuedConnection); + }; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp index 0574a1f1a..b495a9caa 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture.cpp @@ -9,6 +9,7 @@ #include <common/qgstreameraudioinput_p.h> #include <common/qgstreameraudiooutput_p.h> #include <common/qgstreamervideooutput_p.h> +#include <common/qgst_debug_p.h> #include <QtCore/qloggingcategory.h> #include <QtCore/private/quniquehandle_p.h> @@ -24,32 +25,26 @@ static void linkTeeToPad(QGstElement tee, QGstPad sink) source.link(sink); } -static void unlinkTeeFromPad(QGstElement tee, QGstPad sink) -{ - if (tee.isNull() || sink.isNull()) - return; - - auto source = sink.peer(); - source.unlink(sink); - - tee.releaseRequestPad(source); -} - QMaybe<QPlatformMediaCaptureSession *> QGstreamerMediaCapture::create() { auto videoOutput = QGstreamerVideoOutput::create(); if (!videoOutput) return videoOutput.error(); + static const auto error = qGstErrorMessageIfElementsNotAvailable("tee", "capsfilter"); + if (error) + return *error; + return new QGstreamerMediaCapture(videoOutput.value()); } QGstreamerMediaCapture::QGstreamerMediaCapture(QGstreamerVideoOutput *videoOutput) - : gstPipeline(QGstPipeline::create("mediaCapturePipeline")), gstVideoOutput(videoOutput) + : capturePipeline(QGstPipeline::create("mediaCapturePipeline")), gstVideoOutput(videoOutput) { gstVideoOutput->setParent(this); gstVideoOutput->setIsPreview(); - gstVideoOutput->setPipeline(gstPipeline); + + capturePipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this)); // Use system clock to drive all elements in the pipeline. Otherwise, // the clock is sourced from the elements (e.g. from an audio source). @@ -59,14 +54,14 @@ QGstreamerMediaCapture::QGstreamerMediaCapture(QGstreamerVideoOutput *videoOutpu QGstClockHandle systemClock{ gst_system_clock_obtain(), }; - gst_pipeline_use_clock(gstPipeline.pipeline(), systemClock.get()); + gst_pipeline_use_clock(capturePipeline.pipeline(), systemClock.get()); // This is the recording pipeline with only live sources, thus the pipeline // will be always in the playing state. - gstPipeline.setState(GST_STATE_PLAYING); - gstPipeline.setInStoppedState(false); + capturePipeline.setState(GST_STATE_PLAYING); + gstVideoOutput->setActive(true); - gstPipeline.dumpGraph("initial"); + capturePipeline.dumpGraph("initial"); } QGstreamerMediaCapture::~QGstreamerMediaCapture() @@ -74,7 +69,8 @@ QGstreamerMediaCapture::~QGstreamerMediaCapture() setMediaRecorder(nullptr); setImageCapture(nullptr); setCamera(nullptr); - gstPipeline.setStateSync(GST_STATE_NULL); + capturePipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this)); + capturePipeline.setStateSync(GST_STATE_NULL); } QPlatformCamera *QGstreamerMediaCapture::camera() @@ -84,7 +80,7 @@ QPlatformCamera *QGstreamerMediaCapture::camera() void QGstreamerMediaCapture::setCamera(QPlatformCamera *platformCamera) { - QGstreamerCamera *camera = static_cast<QGstreamerCamera *>(platformCamera); + auto *camera = static_cast<QGstreamerCameraBase *>(platformCamera); if (gstCamera == camera) return; @@ -97,7 +93,7 @@ void QGstreamerMediaCapture::setCamera(QPlatformCamera *platformCamera) gstCamera = camera; if (gstCamera) { - gstCameraActiveConnection = QObject::connect(camera, &QGstreamerCamera::activeChanged, this, + gstCameraActiveConnection = QObject::connect(camera, &QPlatformCamera::activeChanged, this, &QGstreamerMediaCapture::setCameraActive); if (gstCamera->isActive()) setCameraActive(true); @@ -108,13 +104,13 @@ void QGstreamerMediaCapture::setCamera(QPlatformCamera *platformCamera) void QGstreamerMediaCapture::setCameraActive(bool activate) { - gstPipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.modifyPipelineWhileNotRunning([&] { if (activate) { QGstElement cameraElement = gstCamera->gstElement(); gstVideoTee = QGstElement::createFromFactory("tee", "videotee"); gstVideoTee.set("allow-not-linked", true); - gstPipeline.add(gstVideoOutput->gstElement(), cameraElement, gstVideoTee); + capturePipeline.add(gstVideoOutput->gstElement(), cameraElement, gstVideoTee); linkTeeToPad(gstVideoTee, encoderVideoSink); linkTeeToPad(gstVideoTee, gstVideoOutput->gstElement().staticPad("sink")); @@ -122,21 +118,24 @@ void QGstreamerMediaCapture::setCameraActive(bool activate) qLinkGstElements(cameraElement, gstVideoTee); - gstPipeline.syncChildrenState(); + capturePipeline.syncChildrenState(); } else { - unlinkTeeFromPad(gstVideoTee, encoderVideoSink); - unlinkTeeFromPad(gstVideoTee, imageCaptureSink); + if (encoderVideoCapsFilter) + qUnlinkGstElements(gstVideoTee, encoderVideoCapsFilter); + if (m_imageCapture) + qUnlinkGstElements(gstVideoTee, m_imageCapture->gstElement()); auto camera = gstCamera->gstElement(); - gstPipeline.stopAndRemoveElements(camera, gstVideoTee, gstVideoOutput->gstElement()); + capturePipeline.stopAndRemoveElements(camera, gstVideoTee, + gstVideoOutput->gstElement()); gstVideoTee = {}; gstCamera->setCaptureSession(nullptr); } }); - gstPipeline.dumpGraph("camera"); + capturePipeline.dumpGraph("camera"); } QPlatformImageCapture *QGstreamerMediaCapture::imageCapture() @@ -150,10 +149,11 @@ void QGstreamerMediaCapture::setImageCapture(QPlatformImageCapture *imageCapture if (m_imageCapture == control) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.modifyPipelineWhileNotRunning([&] { if (m_imageCapture) { - unlinkTeeFromPad(gstVideoTee, imageCaptureSink); - gstPipeline.stopAndRemoveElements(m_imageCapture->gstElement()); + if (gstVideoTee) + qUnlinkGstElements(gstVideoTee, m_imageCapture->gstElement()); + capturePipeline.stopAndRemoveElements(m_imageCapture->gstElement()); imageCaptureSink = {}; m_imageCapture->setCaptureSession(nullptr); } @@ -161,14 +161,14 @@ void QGstreamerMediaCapture::setImageCapture(QPlatformImageCapture *imageCapture m_imageCapture = control; if (m_imageCapture) { imageCaptureSink = m_imageCapture->gstElement().staticPad("sink"); - gstPipeline.add(m_imageCapture->gstElement()); + capturePipeline.add(m_imageCapture->gstElement()); m_imageCapture->gstElement().syncStateWithParent(); linkTeeToPad(gstVideoTee, imageCaptureSink); m_imageCapture->setCaptureSession(this); } }); - gstPipeline.dumpGraph("imageCapture"); + capturePipeline.dumpGraph("imageCapture"); emit imageCaptureChanged(); } @@ -186,7 +186,7 @@ void QGstreamerMediaCapture::setMediaRecorder(QPlatformMediaRecorder *recorder) m_mediaEncoder->setCaptureSession(this); emit encoderChanged(); - gstPipeline.dumpGraph("encoder"); + capturePipeline.dumpGraph("encoder"); } QPlatformMediaRecorder *QGstreamerMediaCapture::mediaRecorder() @@ -196,7 +196,7 @@ QPlatformMediaRecorder *QGstreamerMediaCapture::mediaRecorder() void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink) { - gstPipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.modifyPipelineWhileNotRunning([&] { if (!gstVideoTee.isNull() && !videoSink.isNull()) { QGstCaps caps = gstVideoTee.sink().currentCaps(); @@ -205,7 +205,7 @@ void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink) Q_ASSERT(encoderVideoCapsFilter); encoderVideoCapsFilter.set("caps", caps); - gstPipeline.add(encoderVideoCapsFilter); + capturePipeline.add(encoderVideoCapsFilter); encoderVideoCapsFilter.src().link(videoSink); linkTeeToPad(gstVideoTee, encoderVideoCapsFilter.sink()); @@ -220,7 +220,7 @@ void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink) Q_ASSERT(encoderAudioCapsFilter); encoderAudioCapsFilter.set("caps", caps); - gstPipeline.add(encoderAudioCapsFilter); + capturePipeline.add(encoderAudioCapsFilter); encoderAudioCapsFilter.src().link(audioSink); linkTeeToPad(gstAudioTee, encoderAudioCapsFilter.sink()); @@ -231,18 +231,18 @@ void QGstreamerMediaCapture::linkEncoder(QGstPad audioSink, QGstPad videoSink) void QGstreamerMediaCapture::unlinkEncoder() { - gstPipeline.modifyPipelineWhileNotRunning([&] { - if (!encoderVideoCapsFilter.isNull()) { - encoderVideoCapsFilter.src().unlinkPeer(); - unlinkTeeFromPad(gstVideoTee, encoderVideoCapsFilter.sink()); - gstPipeline.stopAndRemoveElements(encoderVideoCapsFilter); + capturePipeline.modifyPipelineWhileNotRunning([&] { + if (encoderVideoCapsFilter) { + if (gstVideoTee) + qUnlinkGstElements(gstVideoTee, encoderVideoCapsFilter); + capturePipeline.stopAndRemoveElements(encoderVideoCapsFilter); encoderVideoCapsFilter = {}; } - if (!encoderAudioCapsFilter.isNull()) { - encoderAudioCapsFilter.src().unlinkPeer(); - unlinkTeeFromPad(gstAudioTee, encoderAudioCapsFilter.sink()); - gstPipeline.stopAndRemoveElements(encoderAudioCapsFilter); + if (encoderAudioCapsFilter) { + if (gstAudioTee) + qUnlinkGstElements(gstAudioTee, encoderAudioCapsFilter); + capturePipeline.stopAndRemoveElements(encoderAudioCapsFilter); encoderAudioCapsFilter = {}; } @@ -251,22 +251,27 @@ void QGstreamerMediaCapture::unlinkEncoder() }); } +const QGstPipeline &QGstreamerMediaCapture::pipeline() const +{ + return capturePipeline; +} + void QGstreamerMediaCapture::setAudioInput(QPlatformAudioInput *input) { if (gstAudioInput == input) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.modifyPipelineWhileNotRunning([&] { if (gstAudioInput) { - unlinkTeeFromPad(gstAudioTee, encoderAudioSink); + if (encoderAudioCapsFilter) + qUnlinkGstElements(gstAudioTee, encoderAudioCapsFilter); if (gstAudioOutput) { - unlinkTeeFromPad(gstAudioTee, gstAudioOutput->gstElement().staticPad("sink")); - gstPipeline.remove(gstAudioOutput->gstElement()); - gstAudioOutput->gstElement().setStateSync(GST_STATE_NULL); + qUnlinkGstElements(gstAudioTee, gstAudioOutput->gstElement()); + capturePipeline.stopAndRemoveElements(gstAudioOutput->gstElement()); } - gstPipeline.stopAndRemoveElements(gstAudioInput->gstElement(), gstAudioTee); + capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement(), gstAudioTee); gstAudioTee = {}; } @@ -275,16 +280,16 @@ void QGstreamerMediaCapture::setAudioInput(QPlatformAudioInput *input) Q_ASSERT(gstAudioTee.isNull()); gstAudioTee = QGstElement::createFromFactory("tee", "audiotee"); gstAudioTee.set("allow-not-linked", true); - gstPipeline.add(gstAudioInput->gstElement(), gstAudioTee); + capturePipeline.add(gstAudioInput->gstElement(), gstAudioTee); qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee); if (gstAudioOutput) { - gstPipeline.add(gstAudioOutput->gstElement()); + capturePipeline.add(gstAudioOutput->gstElement()); gstAudioOutput->gstElement().setState(GST_STATE_PLAYING); linkTeeToPad(gstAudioTee, gstAudioOutput->gstElement().staticPad("sink")); } - gstPipeline.syncChildrenState(); + capturePipeline.syncChildrenState(); linkTeeToPad(gstAudioTee, encoderAudioSink); } @@ -301,17 +306,17 @@ void QGstreamerMediaCapture::setAudioOutput(QPlatformAudioOutput *output) if (gstAudioOutput == output) return; - gstPipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.modifyPipelineWhileNotRunning([&] { if (gstAudioOutput && gstAudioInput) { // If audio input is set, the output is in the pipeline - unlinkTeeFromPad(gstAudioTee, gstAudioOutput->gstElement().staticPad("sink")); - gstPipeline.stopAndRemoveElements(gstAudioOutput->gstElement()); + qUnlinkGstElements(gstAudioTee, gstAudioOutput->gstElement()); + capturePipeline.stopAndRemoveElements(gstAudioOutput->gstElement()); } gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output); if (gstAudioOutput && gstAudioInput) { - gstPipeline.add(gstAudioOutput->gstElement()); - gstPipeline.syncChildrenState(); + capturePipeline.add(gstAudioOutput->gstElement()); + capturePipeline.syncChildrenState(); linkTeeToPad(gstAudioTee, gstAudioOutput->gstElement().staticPad("sink")); } }); @@ -322,6 +327,39 @@ QGstreamerVideoSink *QGstreamerMediaCapture::gstreamerVideoSink() const return gstVideoOutput ? gstVideoOutput->gstreamerVideoSink() : nullptr; } +bool QGstreamerMediaCapture::processBusMessage(const QGstreamerMessage &msg) +{ + switch (msg.type()) { + case GST_MESSAGE_ERROR: + return processBusMessageError(msg); + + case GST_MESSAGE_LATENCY: + return processBusMessageLatency(msg); + + default: + break; + } + + return false; +} + +bool QGstreamerMediaCapture::processBusMessageError(const QGstreamerMessage &msg) +{ + QUniqueGErrorHandle error; + QUniqueGStringHandle message; + gst_message_parse_error(msg.message(), &error, &message); + + qWarning() << "QGstreamerMediaCapture: received error from gstreamer" << error << message; + capturePipeline.dumpGraph("captureError"); + + return false; +} + +bool QGstreamerMediaCapture::processBusMessageLatency(const QGstreamerMessage &) +{ + capturePipeline.recalculateLatency(); + return false; +} 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..23122ae0a 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediacapture_p.h @@ -18,14 +18,13 @@ #include <private/qplatformmediacapture_p.h> #include <private/qplatformmediaintegration_p.h> +#include <common/qgst_bus_p.h> #include <common/qgst_p.h> #include <common/qgstpipeline_p.h> -#include <qtimer.h> - QT_BEGIN_NAMESPACE -class QGstreamerCamera; +class QGstreamerCameraBase; class QGstreamerImageCapture; class QGstreamerMediaEncoder; class QGstreamerAudioInput; @@ -33,7 +32,7 @@ class QGstreamerAudioOutput; class QGstreamerVideoOutput; class QGstreamerVideoSink; -class QGstreamerMediaCapture final : public QPlatformMediaCaptureSession +class QGstreamerMediaCapture final : public QPlatformMediaCaptureSession, QGstreamerBusMessageFilter { Q_OBJECT @@ -59,21 +58,25 @@ public: void linkEncoder(QGstPad audioSink, QGstPad videoSink); void unlinkEncoder(); - QGstPipeline pipeline() const { return gstPipeline; } + const QGstPipeline &pipeline() const; QGstreamerVideoSink *gstreamerVideoSink() const; private: + bool processBusMessage(const QGstreamerMessage &) override; + bool processBusMessageError(const QGstreamerMessage &); + bool processBusMessageLatency(const QGstreamerMessage &); + void setCameraActive(bool activate); explicit QGstreamerMediaCapture(QGstreamerVideoOutput *videoOutput); friend QGstreamerMediaEncoder; // Gst elements - QGstPipeline gstPipeline; + QGstPipeline capturePipeline; QGstreamerAudioInput *gstAudioInput = nullptr; - QGstreamerCamera *gstCamera = nullptr; + QGstreamerCameraBase *gstCamera = nullptr; QMetaObject::Connection gstCameraActiveConnection; QGstElement gstAudioTee; diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp index 24f33a19e..4ec10ca84 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder.cpp @@ -40,10 +40,10 @@ QGstreamerMediaEncoder::QGstreamerMediaEncoder(QMediaRecorder *parent) QGstreamerMediaEncoder::~QGstreamerMediaEncoder() { - if (!gstPipeline.isNull()) { + if (!capturePipeline.isNull()) { finalize(); - gstPipeline.removeMessageFilter(this); - gstPipeline.setStateSync(GST_STATE_NULL); + capturePipeline.removeMessageFilter(this); + capturePipeline.setStateSync(GST_STATE_NULL); } } @@ -54,7 +54,7 @@ bool QGstreamerMediaEncoder::isLocationWritable(const QUrl &) const void QGstreamerMediaEncoder::handleSessionError(QMediaRecorder::Error code, const QString &description) { - error(code, description); + updateError(code, description); stop(); } @@ -68,7 +68,7 @@ bool QGstreamerMediaEncoder::processBusMessage(const QGstreamerMessage &msg) switch (msg.type()) { case GST_MESSAGE_ELEMENT: { - QGstStructure s = msg.structure(); + QGstStructureView s = msg.structure(); if (s.name() == "GstBinForwarded") return processBusMessage(s.getMessage()); @@ -90,7 +90,7 @@ bool QGstreamerMediaEncoder::processBusMessage(const QGstreamerMessage &msg) QUniqueGErrorHandle err; QGString debug; gst_message_parse_error(msg.message(), &err, &debug); - error(QMediaRecorder::ResourceError, QString::fromUtf8(err.get()->message)); + updateError(QMediaRecorder::ResourceError, QString::fromUtf8(err.get()->message)); if (!m_finalizing) stop(); finalize(); @@ -262,7 +262,7 @@ void QGstreamerMediaEncoder::record(QMediaEncoderSettings &settings) const auto hasAudio = m_session->audioInput() != nullptr; if (!hasVideo && !hasAudio) { - error(QMediaRecorder::ResourceError, QMediaRecorder::tr("No camera or audio input")); + updateError(QMediaRecorder::ResourceError, QMediaRecorder::tr("No camera or audio input")); return; } @@ -310,10 +310,10 @@ void QGstreamerMediaEncoder::record(QMediaEncoderSettings &settings) videoPauseControl.installOn(videoSink); } - gstPipeline.modifyPipelineWhileNotRunning([&] { - gstPipeline.add(gstEncoder, gstFileSink); + capturePipeline.modifyPipelineWhileNotRunning([&] { + capturePipeline.add(gstEncoder, gstFileSink); qLinkGstElements(gstEncoder, gstFileSink); - m_metaData.setMetaData(gstEncoder.bin()); + applyMetaDataToTagSetter(m_metaData, gstEncoder); m_session->linkEncoder(audioSink, videoSink); @@ -322,7 +322,7 @@ void QGstreamerMediaEncoder::record(QMediaEncoderSettings &settings) }); signalDurationChangedTimer.start(); - gstPipeline.dumpGraph("recording"); + capturePipeline.dumpGraph("recording"); durationChanged(0); stateChanged(QMediaRecorder::RecordingState); @@ -335,13 +335,13 @@ void QGstreamerMediaEncoder::pause() return; signalDurationChangedTimer.stop(); durationChanged(duration()); - gstPipeline.dumpGraph("before-pause"); + capturePipeline.dumpGraph("before-pause"); stateChanged(QMediaRecorder::PausedState); } void QGstreamerMediaEncoder::resume() { - gstPipeline.dumpGraph("before-resume"); + capturePipeline.dumpGraph("before-resume"); if (!m_session || m_finalizing || state() != QMediaRecorder::PausedState) return; signalDurationChangedTimer.start(); @@ -369,7 +369,7 @@ void QGstreamerMediaEncoder::finalize() qCDebug(qLcMediaEncoderGst) << "finalize"; - gstPipeline.stopAndRemoveElements(gstEncoder, gstFileSink); + capturePipeline.stopAndRemoveElements(gstEncoder, gstFileSink); gstFileSink = {}; gstEncoder = {}; m_finalizing = false; @@ -380,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 @@ -403,17 +403,17 @@ void QGstreamerMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *ses loop.exec(); } - gstPipeline.removeMessageFilter(this); - gstPipeline = {}; + capturePipeline.removeMessageFilter(this); + capturePipeline = {}; } m_session = captureSession; if (!m_session) return; - gstPipeline = captureSession->gstPipeline; - gstPipeline.set("message-forward", true); - gstPipeline.installMessageFilter(this); + capturePipeline = captureSession->capturePipeline; + capturePipeline.set("message-forward", true); + capturePipeline.installMessageFilter(this); } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h index f570f069e..c79834962 100644 --- a/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h +++ b/src/plugins/multimedia/gstreamer/mediacapture/qgstreamermediaencoder_p.h @@ -18,6 +18,7 @@ #include <mediacapture/qgstreamermediacapture_p.h> #include <common/qgstreamermetadata_p.h> +#include <common/qgst_bus_p.h> #include <QtMultimedia/private/qplatformmediarecorder_p.h> #include <QtCore/qurl.h> @@ -76,10 +77,10 @@ private: void finalize(); QGstreamerMediaCapture *m_session = nullptr; - QGstreamerMetaData m_metaData; + QMediaMetaData m_metaData; QTimer signalDurationChangedTimer; - QGstPipeline gstPipeline; + QGstPipeline capturePipeline; QGstBin gstEncoder; QGstElement gstFileSink; diff --git a/src/plugins/multimedia/gstreamer/qgstreamerformatinfo.cpp b/src/plugins/multimedia/gstreamer/qgstreamerformatinfo.cpp index 86d59a9a8..ac5941e38 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamerformatinfo.cpp +++ b/src/plugins/multimedia/gstreamer/qgstreamerformatinfo.cpp @@ -8,14 +8,15 @@ QT_BEGIN_NAMESPACE -QMediaFormat::AudioCodec QGstreamerFormatInfo::audioCodecForCaps(QGstStructure structure) +QMediaFormat::AudioCodec QGstreamerFormatInfo::audioCodecForCaps(QGstStructureView structure) { + using namespace std::string_view_literals; const char *name = structure.name().data(); - if (!name || strncmp(name, "audio/", 6)) + if (!name || (strncmp(name, "audio/", 6) != 0)) return QMediaFormat::AudioCodec::Unspecified; name += 6; - if (!strcmp(name, "mpeg")) { + if (name == "mpeg"sv) { auto version = structure["mpegversion"].toInt(); if (version == 1) { auto layer = structure["layer"]; @@ -24,91 +25,120 @@ QMediaFormat::AudioCodec QGstreamerFormatInfo::audioCodecForCaps(QGstStructure s } if (version == 4) return QMediaFormat::AudioCodec::AAC; - } else if (!strcmp(name, "x-ac3")) { + return QMediaFormat::AudioCodec::Unspecified; + } + if (name == "x-ac3"sv) return QMediaFormat::AudioCodec::AC3; - } else if (!strcmp(name, "x-eac3")) { + + if (name == "x-eac3"sv) return QMediaFormat::AudioCodec::EAC3; - } else if (!strcmp(name, "x-flac")) { + + if (name == "x-flac"sv) return QMediaFormat::AudioCodec::FLAC; - } else if (!strcmp(name, "x-alac")) { + + if (name == "x-alac"sv) return QMediaFormat::AudioCodec::ALAC; - } else if (!strcmp(name, "x-true-hd")) { + + if (name == "x-true-hd"sv) return QMediaFormat::AudioCodec::DolbyTrueHD; - } else if (!strcmp(name, "x-vorbis")) { + + if (name == "x-vorbis"sv) return QMediaFormat::AudioCodec::Vorbis; - } else if (!strcmp(name, "x-opus")) { + + if (name == "x-opus"sv) return QMediaFormat::AudioCodec::Opus; - } else if (!strcmp(name, "x-wav")) { + + if (name == "x-wav"sv) return QMediaFormat::AudioCodec::Wave; - } else if (!strcmp(name, "x-wma")) { + + if (name == "x-wma"sv) return QMediaFormat::AudioCodec::WMA; - } + return QMediaFormat::AudioCodec::Unspecified; } -QMediaFormat::VideoCodec QGstreamerFormatInfo::videoCodecForCaps(QGstStructure structure) +QMediaFormat::VideoCodec QGstreamerFormatInfo::videoCodecForCaps(QGstStructureView structure) { + using namespace std::string_view_literals; const char *name = structure.name().data(); - if (!name || strncmp(name, "video/", 6)) + if (!name || (strncmp(name, "video/", 6) != 0)) return QMediaFormat::VideoCodec::Unspecified; name += 6; - if (!strcmp(name, "mpeg")) { + if (name == "mpeg"sv) { auto version = structure["mpegversion"].toInt(); if (version == 1) return QMediaFormat::VideoCodec::MPEG1; - else if (version == 2) + if (version == 2) return QMediaFormat::VideoCodec::MPEG2; - else if (version == 4) + if (version == 4) return QMediaFormat::VideoCodec::MPEG4; - } else if (!strcmp(name, "x-h264")) { + return QMediaFormat::VideoCodec::Unspecified; + } + if (name == "x-h264"sv) return QMediaFormat::VideoCodec::H264; + #if GST_CHECK_VERSION(1, 17, 0) // x265enc seems to be broken on 1.16 at least - } else if (!strcmp(name, "x-h265")) { + if (name == "x-h265"sv) return QMediaFormat::VideoCodec::H265; #endif - } else if (!strcmp(name, "x-vp8")) { + + if (name == "x-vp8"sv) return QMediaFormat::VideoCodec::VP8; - } else if (!strcmp(name, "x-vp9")) { + + if (name == "x-vp9"sv) return QMediaFormat::VideoCodec::VP9; - } else if (!strcmp(name, "x-av1")) { + + if (name == "x-av1"sv) return QMediaFormat::VideoCodec::AV1; - } else if (!strcmp(name, "x-theora")) { + + if (name == "x-theora"sv) return QMediaFormat::VideoCodec::Theora; - } else if (!strcmp(name, "x-jpeg")) { + + if (name == "x-jpeg"sv) return QMediaFormat::VideoCodec::MotionJPEG; - } else if (!strcmp(name, "x-wmv")) { + + if (name == "x-wmv"sv) return QMediaFormat::VideoCodec::WMV; - } + return QMediaFormat::VideoCodec::Unspecified; } -QMediaFormat::FileFormat QGstreamerFormatInfo::fileFormatForCaps(QGstStructure structure) +QMediaFormat::FileFormat QGstreamerFormatInfo::fileFormatForCaps(QGstStructureView structure) { + using namespace std::string_view_literals; const char *name = structure.name().data(); - if (!strcmp(name, "video/x-ms-asf")) { + if (name == "video/x-ms-asf"sv) return QMediaFormat::FileFormat::WMV; - } else if (!strcmp(name, "video/x-msvideo")) { + + if (name == "video/x-msvideo"sv) return QMediaFormat::FileFormat::AVI; - } else if (!strcmp(name, "video/x-matroska")) { + + if (name == "video/x-matroska"sv) return QMediaFormat::FileFormat::Matroska; - } else if (!strcmp(name, "video/quicktime")) { - auto variant = structure["variant"].toString(); + + if (name == "video/quicktime"sv) { + const char *variant = structure["variant"].toString(); if (!variant) return QMediaFormat::FileFormat::QuickTime; - else if (!strcmp(variant, "iso")) + if (variant == "iso"sv) return QMediaFormat::FileFormat::MPEG4; - } else if (!strcmp(name, "video/ogg")) { + } + if (name == "video/ogg"sv) return QMediaFormat::FileFormat::Ogg; - } else if (!strcmp(name, "video/webm")) { + + if (name == "video/webm"sv) return QMediaFormat::FileFormat::WebM; - } else if (!strcmp(name, "audio/x-m4a")) { + + if (name == "audio/x-m4a"sv) return QMediaFormat::FileFormat::Mpeg4Audio; - } else if (!strcmp(name, "audio/x-wav")) { + + if (name == "audio/x-wav"sv) return QMediaFormat::FileFormat::Wave; - } else if (!strcmp(name, "audio/mpeg")) { + + if (name == "audio/mpeg"sv) { auto mpegversion = structure["mpegversion"].toInt(); if (mpegversion == 1) { auto layer = structure["layer"]; @@ -116,23 +146,37 @@ QMediaFormat::FileFormat QGstreamerFormatInfo::fileFormatForCaps(QGstStructure s return QMediaFormat::FileFormat::MP3; } } + + if (name == "audio/aac"sv) + return QMediaFormat::FileFormat::AAC; + + if (name == "audio/x-ms-wma"sv) + return QMediaFormat::FileFormat::WMA; + + if (name == "audio/x-flac"sv) + return QMediaFormat::FileFormat::FLAC; + return QMediaFormat::UnspecifiedFormat; } -QImageCapture::FileFormat QGstreamerFormatInfo::imageFormatForCaps(QGstStructure structure) +QImageCapture::FileFormat QGstreamerFormatInfo::imageFormatForCaps(QGstStructureView structure) { + using namespace std::string_view_literals; const char *name = structure.name().data(); - if (!strcmp(name, "image/jpeg")) { + if (name == "image/jpeg"sv) return QImageCapture::JPEG; - } else if (!strcmp(name, "image/png")) { + + if (name == "image/png"sv) return QImageCapture::PNG; - } else if (!strcmp(name, "image/webp")) { + + if (name == "image/webp"sv) return QImageCapture::WebP; - } else if (!strcmp(name, "image/tiff")) { + + if (name == "image/tiff"sv) return QImageCapture::Tiff; - } + return QImageCapture::UnspecifiedFormat; } @@ -149,13 +193,13 @@ static QPair<QList<QMediaFormat::AudioCodec>, QList<QMediaFormat::VideoCodec>> g for (GstElementFactory *factory : QGstUtils::GListRangeAdaptor<GstElementFactory *>(elementList)) { for (GstStaticPadTemplate *padTemplate : - QGstUtils::GListRangeAdaptor<GstStaticPadTemplate *>( + QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>( gst_element_factory_get_static_pad_templates(factory))) { if (padTemplate->direction == padDirection) { auto caps = QGstCaps(gst_static_caps_get(&padTemplate->static_caps), QGstCaps::HasRef); for (int i = 0; i < caps.size(); i++) { - QGstStructure structure = caps.at(i); + QGstStructureView structure = caps.at(i); auto a = QGstreamerFormatInfo::audioCodecForCaps(structure); if (a != QMediaFormat::AudioCodec::Unspecified && !audio.contains(a)) audio.append(a); @@ -170,17 +214,22 @@ static QPair<QList<QMediaFormat::AudioCodec>, QList<QMediaFormat::VideoCodec>> g return {audio, video}; } - -QList<QGstreamerFormatInfo::CodecMap> QGstreamerFormatInfo::getMuxerList(bool demuxer, - QList<QMediaFormat::AudioCodec> supportedAudioCodecs, - QList<QMediaFormat::VideoCodec> supportedVideoCodecs) +QList<QGstreamerFormatInfo::CodecMap> +QGstreamerFormatInfo::getCodecMaps(QMediaFormat::ConversionMode conversionMode, + QList<QMediaFormat::AudioCodec> supportedAudioCodecs, + QList<QMediaFormat::VideoCodec> supportedVideoCodecs) { - QList<QGstreamerFormatInfo::CodecMap> muxers; + QList<QGstreamerFormatInfo::CodecMap> maps; - GstPadDirection padDirection = demuxer ? GST_PAD_SINK : GST_PAD_SRC; + GstPadDirection dataPadDirection = + (conversionMode == QMediaFormat::Decode) ? GST_PAD_SINK : GST_PAD_SRC; + auto encodableFactoryTypes = + (GST_ELEMENT_FACTORY_TYPE_MUXER | GST_ELEMENT_FACTORY_TYPE_PAYLOADER + | GST_ELEMENT_FACTORY_TYPE_ENCRYPTOR | GST_ELEMENT_FACTORY_TYPE_ENCODER); GList *elementList = gst_element_factory_list_get_elements( - demuxer ? GST_ELEMENT_FACTORY_TYPE_DEMUXER : GST_ELEMENT_FACTORY_TYPE_MUXER, + (conversionMode == QMediaFormat::Decode) ? GST_ELEMENT_FACTORY_TYPE_DECODABLE + : encodableFactoryTypes, GST_RANK_MARGINAL); for (GstElementFactory *factory : @@ -188,20 +237,24 @@ QList<QGstreamerFormatInfo::CodecMap> QGstreamerFormatInfo::getMuxerList(bool de QList<QMediaFormat::FileFormat> fileFormats; for (GstStaticPadTemplate *padTemplate : - QGstUtils::GListRangeAdaptor<GstStaticPadTemplate *>( + QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>( gst_element_factory_get_static_pad_templates(factory))) { - if (padTemplate->direction == padDirection) { + // Check pads on data side for file formats, except for parsers check source side + if (padTemplate->direction == dataPadDirection + || (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_PARSER) + && padTemplate->direction == GST_PAD_SRC)) { auto caps = QGstCaps(gst_static_caps_get(&padTemplate->static_caps), QGstCaps::HasRef); for (int i = 0; i < caps.size(); i++) { - QGstStructure structure = caps.at(i); + QGstStructureView structure = caps.at(i); auto fmt = fileFormatForCaps(structure); if (fmt != QMediaFormat::UnspecifiedFormat) fileFormats.append(fmt); } } } + if (fileFormats.isEmpty()) continue; @@ -209,16 +262,16 @@ QList<QGstreamerFormatInfo::CodecMap> QGstreamerFormatInfo::getMuxerList(bool de QList<QMediaFormat::VideoCodec> videoCodecs; for (GstStaticPadTemplate *padTemplate : - QGstUtils::GListRangeAdaptor<GstStaticPadTemplate *>( + QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>( gst_element_factory_get_static_pad_templates(factory))) { // check the other side for supported inputs/outputs - if (padTemplate->direction != padDirection) { + if (padTemplate->direction != dataPadDirection) { auto caps = QGstCaps(gst_static_caps_get(&padTemplate->static_caps), QGstCaps::HasRef); bool acceptsRawAudio = false; for (int i = 0; i < caps.size(); i++) { - QGstStructure structure = caps.at(i); + QGstStructureView structure = caps.at(i); if (structure.name() == "audio/x-raw") acceptsRawAudio = true; auto audio = audioCodecForCaps(structure); @@ -248,19 +301,19 @@ QList<QGstreamerFormatInfo::CodecMap> QGstreamerFormatInfo::getMuxerList(bool de } if (!audioCodecs.isEmpty() || !videoCodecs.isEmpty()) { for (auto f : std::as_const(fileFormats)) { - muxers.append({f, audioCodecs, videoCodecs}); + maps.append({f, audioCodecs, videoCodecs}); if (f == QMediaFormat::MPEG4 && !fileFormats.contains(QMediaFormat::Mpeg4Audio)) { - muxers.append({QMediaFormat::Mpeg4Audio, audioCodecs, {}}); + maps.append({QMediaFormat::Mpeg4Audio, audioCodecs, {}}); if (audioCodecs.contains(QMediaFormat::AudioCodec::AAC)) - muxers.append({QMediaFormat::AAC, { QMediaFormat::AudioCodec::AAC }, {}}); + maps.append({QMediaFormat::AAC, { QMediaFormat::AudioCodec::AAC }, {}}); } else if (f == QMediaFormat::WMV && !fileFormats.contains(QMediaFormat::WMA)) { - muxers.append({QMediaFormat::WMA, audioCodecs, {}}); + maps.append({QMediaFormat::WMA, audioCodecs, {}}); } } } } gst_plugin_feature_list_free(elementList); - return muxers; + return maps; } static QList<QImageCapture::FileFormat> getImageFormatList() @@ -274,13 +327,13 @@ static QList<QImageCapture::FileFormat> getImageFormatList() QGstUtils::GListRangeAdaptor<GstElementFactory *>(elementList)) { for (GstStaticPadTemplate *padTemplate : - QGstUtils::GListRangeAdaptor<GstStaticPadTemplate *>( + QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>( gst_element_factory_get_static_pad_templates(factory))) { if (padTemplate->direction == GST_PAD_SRC) { QGstCaps caps = QGstCaps(gst_static_caps_get(&padTemplate->static_caps), QGstCaps::HasRef); for (int i = 0; i < caps.size(); i++) { - QGstStructure structure = caps.at(i); + QGstStructureView structure = caps.at(i); auto f = QGstreamerFormatInfo::imageFormatForCaps(structure); if (f != QImageCapture::UnspecifiedFormat) { // qDebug() << structure.toString() << f; @@ -327,10 +380,10 @@ static void dumpMuxers(const QList<QPlatformMediaFormatInfo::CodecMap> &muxerLis QGstreamerFormatInfo::QGstreamerFormatInfo() { auto codecs = getCodecsList(/*decode = */ true); - decoders = getMuxerList(true, codecs.first, codecs.second); + decoders = getCodecMaps(QMediaFormat::Decode, codecs.first, codecs.second); codecs = getCodecsList(/*decode = */ false); - encoders = getMuxerList(/* demuxer = */false, codecs.first, codecs.second); + encoders = getCodecMaps(QMediaFormat::Encode, codecs.first, codecs.second); // dumpAudioCodecs(codecs.first); // dumpVideoCodecs(codecs.second); // dumpMuxers(encoders); diff --git a/src/plugins/multimedia/gstreamer/qgstreamerformatinfo_p.h b/src/plugins/multimedia/gstreamer/qgstreamerformatinfo_p.h index def42b7ea..eecaca2f0 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamerformatinfo_p.h +++ b/src/plugins/multimedia/gstreamer/qgstreamerformatinfo_p.h @@ -31,12 +31,14 @@ public: QGstCaps audioCaps(const QMediaFormat &f) const; QGstCaps videoCaps(const QMediaFormat &f) const; - static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructure structure); - static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructure structure); - static QMediaFormat::FileFormat fileFormatForCaps(QGstStructure structure); - static QImageCapture::FileFormat imageFormatForCaps(QGstStructure structure); - - QList<CodecMap> getMuxerList(bool demuxer, QList<QMediaFormat::AudioCodec> audioCodecs, QList<QMediaFormat::VideoCodec> videoCodecs); + static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructureView structure); + static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructureView structure); + static QMediaFormat::FileFormat fileFormatForCaps(QGstStructureView structure); + static QImageCapture::FileFormat imageFormatForCaps(QGstStructureView structure); + +private: + QList<CodecMap> getCodecMaps(QMediaFormat::ConversionMode conversionMode, QList<QMediaFormat::AudioCodec> audioCodecs, + QList<QMediaFormat::VideoCodec> videoCodecs); }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/qgstreamerintegration.cpp b/src/plugins/multimedia/gstreamer/qgstreamerintegration.cpp index be139cae0..05f05926b 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamerintegration.cpp +++ b/src/plugins/multimedia/gstreamer/qgstreamerintegration.cpp @@ -4,6 +4,7 @@ #include <qgstreamerintegration_p.h> #include <qgstreamerformatinfo_p.h> #include <qgstreamervideodevices_p.h> +#include <audio/qgstreameraudiodevice_p.h> #include <audio/qgstreameraudiodecoder_p.h> #include <common/qgstreameraudioinput_p.h> #include <common/qgstreameraudiooutput_p.h> @@ -13,18 +14,155 @@ #include <mediacapture/qgstreamerimagecapture_p.h> #include <mediacapture/qgstreamermediacapture_p.h> #include <mediacapture/qgstreamermediaencoder_p.h> +#include <uri_handler/qgstreamer_qrc_handler_p.h> #include <QtCore/qloggingcategory.h> +#include <QtMultimedia/private/qmediaplayer_p.h> +#include <QtMultimedia/private/qmediacapturesession_p.h> +#include <QtMultimedia/private/qcameradevice_p.h> QT_BEGIN_NAMESPACE +static thread_local bool inCustomCameraConstruction = false; +static thread_local QGstElement pendingCameraElement{}; + +QGStreamerPlatformSpecificInterfaceImplementation:: + ~QGStreamerPlatformSpecificInterfaceImplementation() = default; + +QAudioDevice QGStreamerPlatformSpecificInterfaceImplementation::makeCustomGStreamerAudioInput( + const QByteArray &gstreamerPipeline) +{ + return qMakeCustomGStreamerAudioInput(gstreamerPipeline); +} + +QAudioDevice QGStreamerPlatformSpecificInterfaceImplementation::makeCustomGStreamerAudioOutput( + const QByteArray &gstreamerPipeline) +{ + return qMakeCustomGStreamerAudioOutput(gstreamerPipeline); +} + +QCamera *QGStreamerPlatformSpecificInterfaceImplementation::makeCustomGStreamerCamera( + const QByteArray &gstreamerPipeline, QObject *parent) +{ + QCameraDevicePrivate *info = new QCameraDevicePrivate; + info->id = gstreamerPipeline; + QCameraDevice device = info->create(); + + inCustomCameraConstruction = true; + auto guard = qScopeGuard([] { + inCustomCameraConstruction = false; + }); + + return new QCamera(device, parent); +} + +QCamera * +QGStreamerPlatformSpecificInterfaceImplementation::makeCustomGStreamerCamera(GstElement *element, + QObject *parent) +{ + QCameraDevicePrivate *info = new QCameraDevicePrivate; + info->id = "Custom Camera from GstElement"; + QCameraDevice device = info->create(); + + pendingCameraElement = QGstElement{ + element, + QGstElement::NeedsRef, + }; + + inCustomCameraConstruction = true; + auto guard = qScopeGuard([] { + inCustomCameraConstruction = false; + Q_ASSERT(!pendingCameraElement); + }); + + return new QCamera(device, parent); +} + +GstPipeline *QGStreamerPlatformSpecificInterfaceImplementation::gstPipeline(QMediaPlayer *player) +{ + auto *priv = reinterpret_cast<QMediaPlayerPrivate *>(QMediaPlayerPrivate::get(player)); + if (!priv) + return nullptr; + + QGstreamerMediaPlayer *gstreamerPlayer = dynamic_cast<QGstreamerMediaPlayer *>(priv->control); + return gstreamerPlayer ? gstreamerPlayer->pipeline().pipeline() : nullptr; +} + +GstPipeline * +QGStreamerPlatformSpecificInterfaceImplementation::gstPipeline(QMediaCaptureSession *session) +{ + auto *priv = QMediaCaptureSessionPrivate::get(session); + if (!priv) + return nullptr; + + QGstreamerMediaCapture *gstreamerCapture = + dynamic_cast<QGstreamerMediaCapture *>(priv->captureSession.get()); + return gstreamerCapture ? gstreamerCapture->pipeline().pipeline() : nullptr; +} + Q_LOGGING_CATEGORY(lcGstreamer, "qt.multimedia.gstreamer") +namespace { + +void rankDownPlugin(GstRegistry *reg, const char *name) +{ + QGstPluginFeatureHandle pluginFeature{ + gst_registry_lookup_feature(reg, name), + QGstPluginFeatureHandle::HasRef, + }; + if (pluginFeature) + gst_plugin_feature_set_rank(pluginFeature.get(), GST_RANK_PRIMARY - 1); +} + +// https://gstreamer.freedesktop.org/documentation/vaapi/index.html +constexpr auto vaapiPluginNames = { + "vaapidecodebin", "vaapih264dec", "vaapih264enc", "vaapih265dec", + "vaapijpegdec", "vaapijpegenc", "vaapimpeg2dec", "vaapipostproc", + "vaapisink", "vaapivp8dec", "vaapivp9dec", +}; + +// https://gstreamer.freedesktop.org/documentation/va/index.html +constexpr auto vaPluginNames = { + "vaav1dec", "vacompositor", "vadeinterlace", "vah264dec", "vah264enc", "vah265dec", + "vajpegdec", "vampeg2dec", "vapostproc", "vavp8dec", "vavp9dec", +}; + +// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html +constexpr auto nvcodecPluginNames = { + "cudaconvert", "cudaconvertscale", "cudadownload", "cudaipcsink", "cudaipcsrc", + "cudascale", "cudaupload", "nvautogpuh264enc", "nvautogpuh265enc", "nvav1dec", + "nvcudah264enc", "nvcudah265enc", "nvd3d11h264enc", "nvd3d11h265enc", "nvh264dec", + "nvh264enc", "nvh265dec", "nvh265enc", "nvjpegdec", "nvjpegenc", + "nvmpeg2videodec", "nvmpeg4videodec", "nvmpegvideodec", "nvvp8dec", "nvvp9dec", +}; + +} // namespace + QGstreamerIntegration::QGstreamerIntegration() : QPlatformMediaIntegration(QLatin1String("gstreamer")) { gst_init(nullptr, nullptr); qCDebug(lcGstreamer) << "Using gstreamer version: " << gst_version_string(); + + GstRegistry *reg = gst_registry_get(); + + if constexpr (!GST_CHECK_VERSION(1, 22, 0)) { + GstRegistry* reg = gst_registry_get(); + for (const char *name : vaapiPluginNames) + rankDownPlugin(reg, name); + } + + if (qEnvironmentVariableIsSet("QT_GSTREAMER_DISABLE_VA")) { + for (const char *name : vaPluginNames) + rankDownPlugin(reg, name); + } + + if (qEnvironmentVariableIsSet("QT_GSTREAMER_DISABLE_NVCODEC")) { + for (const char *name : nvcodecPluginNames) + rankDownPlugin(reg, name); + } + + qGstRegisterQRCHandler(nullptr); } QPlatformMediaFormatInfo *QGstreamerIntegration::createFormatInfo() @@ -59,6 +197,12 @@ QMaybe<QPlatformMediaPlayer *> QGstreamerIntegration::createPlayer(QMediaPlayer QMaybe<QPlatformCamera *> QGstreamerIntegration::createCamera(QCamera *camera) { + if (inCustomCameraConstruction) { + QGstElement element = std::exchange(pendingCameraElement, {}); + return element ? new QGstreamerCustomCamera{ camera, std::move(element) } + : new QGstreamerCustomCamera{ camera }; + } + return QGstreamerCamera::create(camera); } @@ -93,4 +237,9 @@ GstDevice *QGstreamerIntegration::videoDevice(const QByteArray &id) return devices ? static_cast<QGstreamerVideoDevices *>(devices)->videoDevice(id) : nullptr; } +QAbstractPlatformSpecificInterface *QGstreamerIntegration::platformSpecificInterface() +{ + return &m_platformSpecificImplementation; +} + QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/qgstreamerintegration_p.h b/src/plugins/multimedia/gstreamer/qgstreamerintegration_p.h index 9cb84c57b..229bbd48e 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamerintegration_p.h +++ b/src/plugins/multimedia/gstreamer/qgstreamerintegration_p.h @@ -15,13 +15,31 @@ // We mean it. // -#include <private/qplatformmediaintegration_p.h> +#include <QtMultimedia/private/qplatformmediaintegration_p.h> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> + #include <gst/gst.h> QT_BEGIN_NAMESPACE class QGstreamerFormatInfo; +class QGStreamerPlatformSpecificInterfaceImplementation : public QGStreamerPlatformSpecificInterface +{ +public: + ~QGStreamerPlatformSpecificInterfaceImplementation() override; + + QAudioDevice makeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline) override; + QAudioDevice makeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline) override; + QCamera *makeCustomGStreamerCamera(const QByteArray &gstreamerPipeline, + QObject *parent) override; + + QCamera *makeCustomGStreamerCamera(GstElement *, QObject *parent) override; + + GstPipeline *gstPipeline(QMediaPlayer *) override; + GstPipeline *gstPipeline(QMediaCaptureSession *) override; +}; + class QGstreamerIntegration : public QPlatformMediaIntegration { public: @@ -47,9 +65,13 @@ public: const QGstreamerFormatInfo *gstFormatsInfo(); GstDevice *videoDevice(const QByteArray &id); + QAbstractPlatformSpecificInterface *platformSpecificInterface() override; + protected: QPlatformMediaFormatInfo *createFormatInfo() override; QPlatformVideoDevices *createVideoDevices() override; + + QGStreamerPlatformSpecificInterfaceImplementation m_platformSpecificImplementation; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/qgstreamervideodevices.cpp b/src/plugins/multimedia/gstreamer/qgstreamervideodevices.cpp index cceaca621..bee30677d 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamervideodevices.cpp +++ b/src/plugins/multimedia/gstreamer/qgstreamervideodevices.cpp @@ -9,41 +9,28 @@ #include <common/qgstutils_p.h> #include <common/qglist_helper_p.h> -QT_BEGIN_NAMESPACE +#if QT_CONFIG(linux_v4l) +# include <linux/videodev2.h> +# include <errno.h> +#endif -static gboolean deviceMonitorCallback(GstBus *, GstMessage *message, gpointer m) -{ - auto *manager = static_cast<QGstreamerVideoDevices *>(m); - QGstDeviceHandle device; - - switch (GST_MESSAGE_TYPE(message)) { - case GST_MESSAGE_DEVICE_ADDED: - gst_message_parse_device_added(message, &device); - manager->addDevice(std::move(device)); - break; - case GST_MESSAGE_DEVICE_REMOVED: - gst_message_parse_device_removed(message, &device); - manager->removeDevice(std::move(device)); - break; - default: - break; - } - - return G_SOURCE_CONTINUE; -} +QT_BEGIN_NAMESPACE QGstreamerVideoDevices::QGstreamerVideoDevices(QPlatformMediaIntegration *integration) : QPlatformVideoDevices(integration), m_deviceMonitor{ gst_device_monitor_new(), + }, + m_bus{ + QGstBusHandle{ + gst_device_monitor_get_bus(m_deviceMonitor.get()), + QGstBusHandle::HasRef, + }, } { gst_device_monitor_add_filter(m_deviceMonitor.get(), "Video/Source", nullptr); - QGstBusHandle bus{ - gst_device_monitor_get_bus(m_deviceMonitor.get()), - }; - gst_bus_add_watch(bus.get(), deviceMonitorCallback, this); + m_bus.installMessageFilter(this); gst_device_monitor_start(m_deviceMonitor.get()); GList *devices = gst_device_monitor_get_devices(m_deviceMonitor.get()); @@ -76,11 +63,13 @@ QList<QCameraDevice> QGstreamerVideoDevices::videoDevices() const info->description = desc.toQString(); info->id = device.id; - if (QGstStructure properties = gst_device_get_properties(device.gstDevice.get()); - !properties.isNull()) { - auto def = properties["is-default"].toBool(); + QUniqueGstStructureHandle properties{ + gst_device_get_properties(device.gstDevice.get()), + }; + if (properties) { + QGstStructureView view{ properties }; + auto def = view["is-default"].toBool(); info->isDefault = def && *def; - properties.free(); } if (info->isDefault) @@ -89,25 +78,33 @@ QList<QCameraDevice> QGstreamerVideoDevices::videoDevices() const devices.append(info->create()); auto caps = QGstCaps(gst_device_get_caps(device.gstDevice.get()), QGstCaps::HasRef); - if (!caps.isNull()) { + if (caps) { QList<QCameraFormat> formats; QSet<QSize> photoResolutions; int size = caps.size(); for (int i = 0; i < size; ++i) { auto cap = caps.at(i); - - QSize resolution = cap.resolution(); - if (!resolution.isValid()) - continue; - auto pixelFormat = cap.pixelFormat(); auto frameRate = cap.frameRateRange(); - auto *f = new QCameraFormatPrivate{ QSharedData(), pixelFormat, resolution, - frameRate.min, frameRate.max }; - formats << f->create(); - photoResolutions.insert(resolution); + auto addFormatForResolution = [&](QSize resolution) { + auto *f = new QCameraFormatPrivate{ + QSharedData(), pixelFormat, resolution, frameRate.min, frameRate.max, + }; + formats.append(f->create()); + photoResolutions.insert(resolution); + }; + + std::optional<QGRange<QSize>> resolutionRange = cap.resolutionRange(); + if (resolutionRange) { + addFormatForResolution(resolutionRange->min); + addFormatForResolution(resolutionRange->max); + } else { + QSize resolution = cap.resolution(); + if (resolution.isValid()) + addFormatForResolution(resolution); + } } info->videoFormats = formats; // ### sort resolutions? @@ -121,6 +118,32 @@ void QGstreamerVideoDevices::addDevice(QGstDeviceHandle device) { Q_ASSERT(gst_device_has_classes(device.get(), "Video/Source")); +#if QT_CONFIG(linux_v4l) + QUniqueGstStructureHandle structureHandle{ + gst_device_get_properties(device.get()), + }; + + const auto *p = QGstStructureView(structureHandle.get())["device.path"].toString(); + if (p) { + QFileDescriptorHandle fd{ + qt_safe_open(p, O_RDONLY), + }; + int index; + if (::ioctl(fd.get(), VIDIOC_G_INPUT, &index) < 0) { + switch (errno) { + case ENOTTY: // no video inputs + case EINVAL: // ioctl is not supported. E.g. the Broadcom Image Signal Processor + // available on Raspberry Pi + return; + + default: + qWarning() << "ioctl failed: VIDIOC_G_INPUT" << qt_error_string(errno) << p; + return; + } + } + } +#endif + auto it = std::find_if(m_videoSources.begin(), m_videoSources.end(), [&](const QGstRecordDevice &a) { return a.gstDevice == device; }); @@ -146,6 +169,26 @@ void QGstreamerVideoDevices::removeDevice(QGstDeviceHandle device) } } +bool QGstreamerVideoDevices::processBusMessage(const QGstreamerMessage &message) +{ + QGstDeviceHandle device; + + switch (message.type()) { + case GST_MESSAGE_DEVICE_ADDED: + gst_message_parse_device_added(message.message(), &device); + addDevice(std::move(device)); + break; + case GST_MESSAGE_DEVICE_REMOVED: + gst_message_parse_device_removed(message.message(), &device); + removeDevice(std::move(device)); + break; + default: + break; + } + + return false; +} + GstDevice *QGstreamerVideoDevices::videoDevice(const QByteArray &id) const { auto it = std::find_if(m_videoSources.begin(), m_videoSources.end(), diff --git a/src/plugins/multimedia/gstreamer/qgstreamervideodevices_p.h b/src/plugins/multimedia/gstreamer/qgstreamervideodevices_p.h index a321ae66b..d9095ee20 100644 --- a/src/plugins/multimedia/gstreamer/qgstreamervideodevices_p.h +++ b/src/plugins/multimedia/gstreamer/qgstreamervideodevices_p.h @@ -21,10 +21,12 @@ #include <vector> #include <common/qgst_handle_types_p.h> +#include <common/qgst_bus_p.h> QT_BEGIN_NAMESPACE -class QGstreamerVideoDevices : public QPlatformVideoDevices +class QGstreamerVideoDevices final : public QPlatformVideoDevices, + private QGstreamerBusMessageFilter { public: explicit QGstreamerVideoDevices(QPlatformMediaIntegration *integration); @@ -37,6 +39,8 @@ public: void removeDevice(QGstDeviceHandle); private: + bool processBusMessage(const QGstreamerMessage &message) override; + struct QGstRecordDevice { QGstDeviceHandle gstDevice; @@ -47,6 +51,7 @@ private: std::vector<QGstRecordDevice> m_videoSources; QGstDeviceMonitorHandle m_deviceMonitor; + QGstBus m_bus; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler.cpp b/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler.cpp new file mode 100644 index 000000000..b51ab2ec6 --- /dev/null +++ b/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler.cpp @@ -0,0 +1,359 @@ +// 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 + +#include "qgstreamer_qrc_handler_p.h" + +#include <QtCore/qfile.h> +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> +#include <QtCore/qurl.h> + +#include <gst/base/gstbasesrc.h> + +QT_BEGIN_NAMESPACE + +namespace { + +// qt helpers + +using namespace Qt::Literals; + +std::optional<QString> qQUrlToQrcPath(const QUrl &url) +{ + if (url.scheme() == "qrc"_L1) + return ':'_L1 + url.path(); + return std::nullopt; +} + +std::optional<QUrl> qQrcPathToQUrl(QStringView path) +{ + if (!path.empty() && path[0] == ':'_L1) + return QUrl(u"qrc://"_s + path.mid(1)); + return std::nullopt; +} + +// glib / gstreamer object + +enum PropertyId : uint8_t { + PROP_NONE, + PROP_URI, +}; + +struct QGstQrcSrc +{ + void getProperty(guint propId, GValue *value, const GParamSpec *pspec) const; + void setProperty(guint propId, const GValue *value, const GParamSpec *pspec); + auto lockObject() const; + + bool start(); + bool stop(); + + std::optional<guint64> size(); + GstFlowReturn fill(guint64 offset, guint length, GstBuffer *buf); + void getURI(GValue *value) const; + bool setURI(const char *location, GError **err = nullptr); + + GstBaseSrc baseSrc; + QFile file; +}; + +void QGstQrcSrc::getProperty(guint propId, GValue *value, const GParamSpec *pspec) const +{ + switch (propId) { + case PROP_URI: + return getURI(value); + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec); + break; + } +} + +void QGstQrcSrc::setProperty(guint propId, const GValue *value, const GParamSpec *pspec) +{ + switch (propId) { + case PROP_URI: + setURI(g_value_get_string(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec); + break; + } +} + +auto QGstQrcSrc::lockObject() const +{ + GST_OBJECT_LOCK(this); + return qScopeGuard([this] { + GST_OBJECT_UNLOCK(this); + }); +} + +bool QGstQrcSrc::start() +{ + auto lock = lockObject(); + if (file.fileName().isEmpty()) { + GST_ELEMENT_ERROR(this, RESOURCE, NOT_FOUND, ("No resource name specified for reading."), + (nullptr)); + return false; + } + + bool opened = file.open(QIODevice::ReadOnly); + if (!opened) { + GST_ELEMENT_ERROR(this, RESOURCE, NOT_FOUND, (nullptr), + ("No such resource \"%s\"", file.fileName().toUtf8().constData())); + return false; + } + + gst_base_src_set_dynamic_size(&baseSrc, false); + + Q_ASSERT(file.isOpen()); + return true; +} + +bool QGstQrcSrc::stop() +{ + auto lock = lockObject(); + file.close(); + return true; +} + +std::optional<guint64> QGstQrcSrc::size() +{ + auto lock = lockObject(); + if (!file.isOpen()) + return std::nullopt; + return file.size(); +} + +GstFlowReturn QGstQrcSrc::fill(guint64 offset, guint length, GstBuffer *buf) +{ + if (!file.isOpen()) + return GST_FLOW_ERROR; + + if (G_UNLIKELY(offset != guint64(-1) && guint64(file.pos()) != offset)) { + bool success = file.seek(int64_t(offset)); + if (!success) { + GST_ELEMENT_ERROR(this, RESOURCE, READ, (nullptr), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } + } + + GstMapInfo info; + if (!gst_buffer_map(buf, &info, GST_MAP_WRITE)) { + GST_ELEMENT_ERROR(this, RESOURCE, WRITE, (nullptr), ("Can't map buffer for writing")); + return GST_FLOW_ERROR; + } + + int64_t remain = length; + int64_t totalRead = 0; + while (remain) { + int64_t bytesRead = file.read(reinterpret_cast<char *>(info.data) + totalRead, remain); + if (bytesRead == -1) { + if (file.atEnd()) { + gst_buffer_unmap(buf, &info); + gst_buffer_resize(buf, 0, 0); + return GST_FLOW_EOS; + } + GST_ELEMENT_ERROR(this, RESOURCE, READ, (nullptr), GST_ERROR_SYSTEM); + gst_buffer_unmap(buf, &info); + gst_buffer_resize(buf, 0, 0); + return GST_FLOW_ERROR; + } + + remain -= bytesRead; + totalRead += bytesRead; + } + + gst_buffer_unmap(buf, &info); + if (totalRead != length) + gst_buffer_resize(buf, 0, totalRead); + + GST_BUFFER_OFFSET(buf) = offset; + GST_BUFFER_OFFSET_END(buf) = offset + totalRead; + + return GST_FLOW_OK; +} + +void QGstQrcSrc::getURI(GValue *value) const +{ + auto lock = lockObject(); + std::optional<QUrl> url = qQrcPathToQUrl(file.fileName()); + if (url) + g_value_set_string(value, url->toString().toUtf8().constData()); + else + g_value_set_string(value, nullptr); +} + +bool QGstQrcSrc::setURI(const char *location, GError **err) +{ + Q_ASSERT(QLatin1StringView(location).startsWith("qrc:/"_L1)); + + { + auto lock = lockObject(); + GstState state = GST_STATE(this); + if (state != GST_STATE_READY && state != GST_STATE_NULL) { + g_warning("Changing the `uri' property on qrcsrc when the resource is open is not " + "supported."); + if (err) + g_set_error(err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE, + "Changing the `uri' property on qrcsrc when the resource is open " + "is not supported."); + return false; + } + } + + std::optional<QString> path = qQUrlToQrcPath(QString::fromUtf8(location)); + + { + auto lock = lockObject(); + file.close(); + file.setFileName(path.value_or(u""_s)); + } + + g_object_notify(G_OBJECT(this), "uri"); + + return true; +} + +struct QGstQrcSrcClass +{ + GstBaseSrcClass parent_class; +}; + +// GObject +static void gst_qrc_src_class_init(QGstQrcSrcClass *klass); +static void gst_qrc_src_init(QGstQrcSrc *self); + +GType gst_qrc_src_get_type(); + +template <typename T> +QGstQrcSrc *asQGstQrcSrc(T *obj) +{ + return (G_TYPE_CHECK_INSTANCE_CAST((obj), gst_qrc_src_get_type(), QGstQrcSrc)); +} + +// URI handler +void qGstInitURIHandler(gpointer, gpointer); + +#define gst_qrc_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(QGstQrcSrc, gst_qrc_src, GST_TYPE_BASE_SRC, + G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, qGstInitURIHandler)); + +// implementations + +void gst_qrc_src_class_init(QGstQrcSrcClass *klass) +{ + // GObject + GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); + gobjectClass->set_property = [](GObject *instance, guint propId, const GValue *value, + GParamSpec *pspec) { + QGstQrcSrc *src = asQGstQrcSrc(instance); + return src->setProperty(propId, value, pspec); + }; + + gobjectClass->get_property = [](GObject *instance, guint propId, GValue *value, + GParamSpec *pspec) { + QGstQrcSrc *src = asQGstQrcSrc(instance); + return src->getProperty(propId, value, pspec); + }; + + g_object_class_install_property( + gobjectClass, PROP_URI, + g_param_spec_string("uri", "QRC Location", "Path of the qrc to read", nullptr, + GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS + | GST_PARAM_MUTABLE_READY))); + + gobjectClass->finalize = [](GObject *instance) { + QGstQrcSrc *src = asQGstQrcSrc(instance); + src->file.~QFile(); + G_OBJECT_CLASS(parent_class)->finalize(instance); + }; + + // GstElement + GstElementClass *gstelementClass = GST_ELEMENT_CLASS(klass); + gst_element_class_set_static_metadata(gstelementClass, "QRC Source", "Source/QRC", + "Read from arbitrary point in QRC resource", + "Tim Blechmann <tim.blechmann@qt.io>"); + + static GstStaticPadTemplate srctemplate = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); + + gst_element_class_add_static_pad_template(gstelementClass, &srctemplate); + + // GstBaseSrc + GstBaseSrcClass *gstbasesrcClass = GST_BASE_SRC_CLASS(klass); + gstbasesrcClass->start = [](GstBaseSrc *basesrc) -> gboolean { + QGstQrcSrc *src = asQGstQrcSrc(basesrc); + return src->start(); + }; + gstbasesrcClass->stop = [](GstBaseSrc *basesrc) -> gboolean { + QGstQrcSrc *src = asQGstQrcSrc(basesrc); + return src->stop(); + }; + + gstbasesrcClass->is_seekable = [](GstBaseSrc *) -> gboolean { + return true; + }; + gstbasesrcClass->get_size = [](GstBaseSrc *basesrc, guint64 *size) -> gboolean { + QGstQrcSrc *src = asQGstQrcSrc(basesrc); + auto optionalSize = src->size(); + if (!optionalSize) + return false; + *size = optionalSize.value(); + return true; + }; + gstbasesrcClass->fill = [](GstBaseSrc *basesrc, guint64 offset, guint length, + GstBuffer *buf) -> GstFlowReturn { + QGstQrcSrc *src = asQGstQrcSrc(basesrc); + return src->fill(offset, length, buf); + }; +} + +void gst_qrc_src_init(QGstQrcSrc *self) +{ + new (reinterpret_cast<void *>(&self->file)) QFile; + + static constexpr guint defaultBlockSize = 16384; + gst_base_src_set_blocksize(&self->baseSrc, defaultBlockSize); +} + +void qGstInitURIHandler(gpointer g_handlerInterface, gpointer) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *)g_handlerInterface; + + iface->get_type = [](GType) { + return GST_URI_SRC; + }; + iface->get_protocols = [](GType) { + static constexpr const gchar *protocols[] = { + "qrc", + nullptr, + }; + return protocols; + }; + iface->get_uri = [](GstURIHandler *handler) -> gchar * { + QGstQrcSrc *src = asQGstQrcSrc(handler); + auto lock = src->lockObject(); + std::optional<QUrl> url = qQrcPathToQUrl(src->file.fileName()); + if (url) + return g_strdup(url->toString().toUtf8().constData()); + + return nullptr; + }; + iface->set_uri = [](GstURIHandler *handler, const gchar *uri, GError **err) -> gboolean { + QGstQrcSrc *src = asQGstQrcSrc(handler); + return src->setURI(uri, err); + }; +} + +} // namespace + +// plugin registration + +void qGstRegisterQRCHandler(GstPlugin *plugin) +{ + gst_element_register(plugin, "qrcsrc", GST_RANK_PRIMARY, gst_qrc_src_get_type()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler_p.h b/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler_p.h new file mode 100644 index 000000000..87d122f28 --- /dev/null +++ b/src/plugins/multimedia/gstreamer/uri_handler/qgstreamer_qrc_handler_p.h @@ -0,0 +1,27 @@ +// 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 QGSTREAMER_QRC_HANDLER_H +#define QGSTREAMER_QRC_HANDLER_H + +#include <QtCore/qglobal.h> +#include <gst/gstplugin.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. +// + +QT_BEGIN_NAMESPACE + +void qGstRegisterQRCHandler(GstPlugin *plugin); + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/multimedia/wasm/mediacapture/qwasmcamera.cpp b/src/plugins/multimedia/wasm/mediacapture/qwasmcamera.cpp index 08d32ac0b..5f6a86fa6 100644 --- a/src/plugins/multimedia/wasm/mediacapture/qwasmcamera.cpp +++ b/src/plugins/multimedia/wasm/mediacapture/qwasmcamera.cpp @@ -37,7 +37,7 @@ bool QWasmCamera::isActive() const void QWasmCamera::setActive(bool active) { if (!m_CaptureSession) { - emit error(QCamera::CameraError, QStringLiteral("video surface error")); + emit updateError(QCamera::CameraError, QStringLiteral("video surface error")); return; } @@ -75,7 +75,7 @@ void QWasmCamera::setCamera(const QCameraDevice &camera) m_cameraDev = camera; createCamera(m_cameraDev); } else { - emit error(QCamera::CameraError, QStringLiteral("Failed to find a camera")); + updateError(QCamera::CameraError, QStringLiteral("Failed to find a camera")); } } diff --git a/src/plugins/multimedia/wasm/mediacapture/qwasmmediarecorder.cpp b/src/plugins/multimedia/wasm/mediacapture/qwasmmediarecorder.cpp index 7adc5ee96..c21823a35 100644 --- a/src/plugins/multimedia/wasm/mediacapture/qwasmmediarecorder.cpp +++ b/src/plugins/multimedia/wasm/mediacapture/qwasmmediarecorder.cpp @@ -233,7 +233,8 @@ void QWasmMediaRecorder::setStream(emscripten::val stream) theError["target"]["data-mediarecordercontext"].as<quintptr>()); if (recorder) { - recorder->error(QMediaRecorder::ResourceError, QString::fromStdString(theError["message"].as<std::string>())); + recorder->updateError(QMediaRecorder::ResourceError, + QString::fromStdString(theError["message"].as<std::string>())); emit recorder->stateChanged(recorder->state()); } }; @@ -276,15 +277,13 @@ void QWasmMediaRecorder::audioDataAvailable(emscripten::val blob, double timeCod auto fileReader = std::make_shared<qstdweb::FileReader>(); - fileReader->onError( - [=](emscripten::val theError) { - - error(QMediaRecorder::ResourceError, QString::fromStdString(theError["message"].as<std::string>())); + fileReader->onError([=](emscripten::val theError) { + updateError(QMediaRecorder::ResourceError, + QString::fromStdString(theError["message"].as<std::string>())); }); - fileReader->onAbort( - [=](emscripten::val ) { - error(QMediaRecorder::ResourceError, QStringLiteral("File read aborted")); + fileReader->onAbort([=](emscripten::val) { + updateError(QMediaRecorder::ResourceError, QStringLiteral("File read aborted")); }); fileReader->onLoad( @@ -370,7 +369,8 @@ void QWasmMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, ems qCDebug(qWasmMediaRecorder) << theError["code"].as<int>() << QString::fromStdString(theError["message"].as<std::string>()); - error(QMediaRecorder::ResourceError, QString::fromStdString(theError["message"].as<std::string>())); + updateError(QMediaRecorder::ResourceError, + QString::fromStdString(theError["message"].as<std::string>())); } }, constraints); } diff --git a/src/plugins/multimedia/windows/mediacapture/qwindowsmediaencoder.cpp b/src/plugins/multimedia/windows/mediacapture/qwindowsmediaencoder.cpp index 4a031043d..d3b2ed9e3 100644 --- a/src/plugins/multimedia/windows/mediacapture/qwindowsmediaencoder.cpp +++ b/src/plugins/multimedia/windows/mediacapture/qwindowsmediaencoder.cpp @@ -52,8 +52,8 @@ void QWindowsMediaEncoder::record(QMediaEncoderSettings &settings) m_mediaDeviceSession->setActive(true); if (!m_mediaDeviceSession->isActivating()) { - error(QMediaRecorder::ResourceError, - QMediaRecorderPrivate::msgFailedStartRecording()); + updateError(QMediaRecorder::ResourceError, + QMediaRecorderPrivate::msgFailedStartRecording()); return; } } @@ -72,7 +72,7 @@ void QWindowsMediaEncoder::record(QMediaEncoderSettings &settings) stateChanged(m_state); } else { - error(ec, QMediaRecorderPrivate::msgFailedStartRecording()); + updateError(ec, QMediaRecorderPrivate::msgFailedStartRecording()); } } @@ -85,7 +85,7 @@ void QWindowsMediaEncoder::pause() m_state = QMediaRecorder::PausedState; stateChanged(m_state); } else { - error(QMediaRecorder::FormatError, tr("Failed to pause recording")); + updateError(QMediaRecorder::FormatError, tr("Failed to pause recording")); } } @@ -98,7 +98,7 @@ void QWindowsMediaEncoder::resume() m_state = QMediaRecorder::RecordingState; stateChanged(m_state); } else { - error(QMediaRecorder::FormatError, tr("Failed to resume recording")); + updateError(QMediaRecorder::FormatError, tr("Failed to resume recording")); } } @@ -178,11 +178,11 @@ void QWindowsMediaEncoder::onDurationChanged(qint64 duration) void QWindowsMediaEncoder::onStreamingError(int errorCode) { if (errorCode == MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED) - error(QMediaRecorder::ResourceError, tr("Camera is no longer present")); + updateError(QMediaRecorder::ResourceError, tr("Camera is no longer present")); else if (errorCode == MF_E_AUDIO_RECORDING_DEVICE_INVALIDATED) - error(QMediaRecorder::ResourceError, tr("Audio input is no longer present")); + updateError(QMediaRecorder::ResourceError, tr("Audio input is no longer present")); else - error(QMediaRecorder::ResourceError, tr("Streaming error")); + updateError(QMediaRecorder::ResourceError, tr("Streaming error")); if (m_state != QMediaRecorder::StoppedState) { m_mediaDeviceSession->stopRecording(); @@ -194,7 +194,7 @@ void QWindowsMediaEncoder::onStreamingError(int errorCode) void QWindowsMediaEncoder::onRecordingError(int errorCode) { Q_UNUSED(errorCode); - error(QMediaRecorder::ResourceError, tr("Recording error")); + updateError(QMediaRecorder::ResourceError, tr("Recording error")); auto lastState = m_state; m_state = QMediaRecorder::StoppedState; diff --git a/src/plugins/multimedia/windows/player/mfplayersession.cpp b/src/plugins/multimedia/windows/player/mfplayersession.cpp index a834b3314..e3eb05b32 100644 --- a/src/plugins/multimedia/windows/player/mfplayersession.cpp +++ b/src/plugins/multimedia/windows/player/mfplayersession.cpp @@ -1708,21 +1708,21 @@ void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int i int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type) { - if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + if (type >= QPlatformMediaPlayer::NTrackTypes) return -1; return m_trackInfo[type].currentIndex; } int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type) { - if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + if (type >= QPlatformMediaPlayer::NTrackTypes) return -1; return m_trackInfo[type].metaData.count(); } QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber) { - if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes) + if (type >= QPlatformMediaPlayer::NTrackTypes) return {}; if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count()) diff --git a/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt index 0133c7498..d2206182f 100644 --- a/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt +++ b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt @@ -27,11 +27,3 @@ qt_internal_add_test(tst_qaudiodecoderbackend Qt::MultimediaPrivate TESTDATA ${test_data} ) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qaudiodecoderbackend CONDITION boot2qt - DEFINES - WAV_SUPPORT_NOT_FORCED -) diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp index 619158d24..a5c43af1e 100644 --- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp +++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp @@ -14,6 +14,18 @@ constexpr char TEST_CORRUPTED_FILE_NAME[] = "testdata/test-corrupted.wav"; constexpr char TEST_INVALID_SOURCE[] = "invalid"; constexpr char TEST_NO_AUDIO_TRACK[] = "testdata/test-no-audio-track.mp4"; +constexpr int testFileSampleCount = 44094; +constexpr int testFileSampleRate = 44100; + +constexpr std::chrono::microseconds testFileDuration = [] { + using namespace std::chrono; + using namespace std::chrono_literals; + auto duration = nanoseconds(1s) * testFileSampleCount / testFileSampleRate; + return round<microseconds>(duration); +}(); + +constexpr qint64 testFileDurationUs = qint64(testFileDuration.count()); + QT_USE_NAMESPACE /* @@ -144,7 +156,7 @@ void tst_QAudioDecoderBackend::directBruteForceReading() checkNoMoreChanges(decoder); - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); } void tst_QAudioDecoderBackend::indirectReadingByBufferReadySignal() @@ -159,7 +171,6 @@ void tst_QAudioDecoderBackend::indirectReadingByBufferReadySignal() connect(&decoder, &QAudioDecoder::bufferReady, this, [&]() { QVERIFY(decoder.bufferAvailable()); - QVERIFY(decoder.isDecoding()); auto buffer = decoder.read(); QVERIFY(buffer.isValid()); @@ -183,7 +194,7 @@ void tst_QAudioDecoderBackend::indirectReadingByBufferReadySignal() checkNoMoreChanges(decoder); - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); QCOMPARE(finishSpy.size(), 1); } @@ -202,8 +213,6 @@ void tst_QAudioDecoderBackend::indirectReadingByBufferAvailableSignal() { if (!available) return; - QVERIFY(decoder.isDecoding()); - while (decoder.bufferAvailable()) { auto buffer = decoder.read(); QVERIFY(buffer.isValid()); @@ -227,7 +236,7 @@ void tst_QAudioDecoderBackend::indirectReadingByBufferAvailableSignal() { checkNoMoreChanges(decoder); - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); QCOMPARE(finishSpy.size(), 1); } @@ -260,6 +269,8 @@ void tst_QAudioDecoderBackend::stopOnBufferReady() void tst_QAudioDecoderBackend::restartOnBufferReady() { + QSKIP_GSTREAMER("QTBUG-124005: failures on gstreamer"); + CHECK_SELECTED_URL(m_wavFile); QAudioDecoder decoder; @@ -295,7 +306,7 @@ void tst_QAudioDecoderBackend::restartOnBufferReady() checkNoMoreChanges(decoder); - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); } void tst_QAudioDecoderBackend::restartOnFinish() @@ -336,7 +347,7 @@ void tst_QAudioDecoderBackend::restartOnFinish() QVERIFY(!decoder.isDecoding()); checkNoMoreChanges(decoder); - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); } void tst_QAudioDecoderBackend::fileTest() @@ -390,7 +401,7 @@ void tst_QAudioDecoderBackend::fileTest() // Test file is 44.1K 16bit mono, 44094 samples QCOMPARE(buffer.format().channelCount(), 1); - QCOMPARE(buffer.format().sampleRate(), 44100); + QCOMPARE(buffer.format().sampleRate(), testFileSampleRate); QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono @@ -403,28 +414,35 @@ void tst_QAudioDecoderBackend::fileTest() byteCount += buffer.byteCount(); // Now drain the decoder - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } + auto durationToMs = [](uint64_t dur) { + if (isGStreamerPlatform()) + return std::round(dur / 1000.0); + else + return dur / 1000.0; + }; + while (d.bufferAvailable()) { buffer = d.read(); QVERIFY(buffer.isValid()); QTRY_VERIFY(!positionSpy.isEmpty()); - QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qint64(duration / 1000)); + QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qint64(durationToMs(duration))); duration += buffer.duration(); sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } } // Make sure the duration is roughly correct (+/- 20ms) - QCOMPARE(sampleCount, 44094); - QCOMPARE(byteCount, 44094 * 2); + QCOMPARE(sampleCount, testFileSampleCount); + QCOMPARE(byteCount, testFileSampleCount * 2); QVERIFY(qAbs(qint64(duration) - 1000000) < 20000); QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20); QTRY_COMPARE(finishedSpy.size(), 1); @@ -490,34 +508,29 @@ void tst_QAudioDecoderBackend::fileTest() sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - // Now drain the decoder - if (duration < 996000) { - QTRY_COMPARE(d.bufferAvailable(), true); - } + while (finishedSpy.isEmpty() || d.bufferAvailable()) { + if (!d.bufferAvailable()) { + QTest::qWait(10); + continue; + } - while (d.bufferAvailable()) { buffer = d.read(); QVERIFY(buffer.isValid()); QTRY_VERIFY(!positionSpy.isEmpty()); - QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qlonglong(duration / 1000)); - QCOMPARE_LT(d.position() - (duration / 1000), 20u); + QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qlonglong(durationToMs(duration))); + QCOMPARE_LT(d.position() - durationToMs(duration), 20u); duration += buffer.duration(); sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - - if (duration < 996000) { - QTRY_COMPARE(d.bufferAvailable(), true); - } } // Resampling might end up with fewer or more samples // so be a bit sloppy QCOMPARE_LT(qAbs(sampleCount - 22047), 100); QCOMPARE_LT(qAbs(byteCount - 22047), 100); - QCOMPARE_LT(qAbs(qint64(duration) - 1000000), 20000); + QCOMPARE_LT(qAbs(qint64(duration) - testFileDurationUs), 20000); QCOMPARE_LT(qAbs((d.position() + (buffer.duration() / 1000)) - 1000), 20); - QTRY_COMPARE(finishedSpy.size(), 1); QVERIFY(!d.bufferAvailable()); QVERIFY(!d.isDecoding()); @@ -777,6 +790,7 @@ void tst_QAudioDecoderBackend::invalidSource() void tst_QAudioDecoderBackend::deviceTest() { + using namespace std::chrono; CHECK_SELECTED_URL(m_wavFile); QAudioDecoder d; @@ -825,7 +839,7 @@ void tst_QAudioDecoderBackend::deviceTest() // Test file is 44.1K 16bit mono QCOMPARE(buffer.format().channelCount(), 1); - QCOMPARE(buffer.format().sampleRate(), 44100); + QCOMPARE(buffer.format().sampleRate(), testFileSampleRate); QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QVERIFY(errorSpy.isEmpty()); @@ -834,7 +848,7 @@ void tst_QAudioDecoderBackend::deviceTest() sampleCount += buffer.sampleCount(); // Now drain the decoder - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } @@ -842,18 +856,24 @@ void tst_QAudioDecoderBackend::deviceTest() buffer = d.read(); QVERIFY(buffer.isValid()); QTRY_VERIFY(!positionSpy.isEmpty()); - QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000)); + if (isGStreamerPlatform()) + QCOMPARE_EQ(positionSpy.takeLast().at(0).toLongLong(), + round<milliseconds>(microseconds{ duration }).count()); + else + QCOMPARE_EQ(positionSpy.takeLast().at(0).toLongLong(), + floor<milliseconds>(microseconds{ duration }).count()); + QVERIFY(d.position() - (duration / 1000) < 20); duration += buffer.duration(); sampleCount += buffer.sampleCount(); - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } } // Make sure the duration is roughly correct (+/- 20ms) - QCOMPARE(sampleCount, 44094); + QCOMPARE(sampleCount, testFileSampleCount); QVERIFY(qAbs(qint64(duration) - 1000000) < 20000); QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20); QTRY_COMPARE(finishedSpy.size(), 1); @@ -919,8 +939,6 @@ void tst_QAudioDecoderBackend::deviceTest() void tst_QAudioDecoderBackend::play_emitsFormatError_whenMediaHasNoAudioTrack() { - QSKIP_GSTREAMER("QTBUG-124206: gstreamer does not emit errors"); - QAudioDecoder decoder; QSignalSpy errors{ &decoder, qOverload<QAudioDecoder::Error>(&QAudioDecoder::error) }; diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp index 7b3011e41..ab68b90af 100644 --- a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp +++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp @@ -24,6 +24,8 @@ public: private slots: void initTestCase(); + void cleanupTestCase(); + void format(); void invalidFormat_data(); void invalidFormat(); @@ -286,6 +288,12 @@ void tst_QAudioSink::initTestCase() } } +void tst_QAudioSink::cleanupTestCase() +{ + audioFiles.clear(); + testFormats.clear(); +} + void tst_QAudioSink::format() { QAudioSink audioOutput(audioDevice.preferredFormat(), this); diff --git a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp index 19fda577c..861036070 100644 --- a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp +++ b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp @@ -59,6 +59,8 @@ private slots: void volume_data(){generate_audiofile_testrows();} void volume(); + void stop_finishesPushMode_whenInvokedUponReadyReadSignal(); + private: using FilePtr = QSharedPointer<QFile>; @@ -809,6 +811,37 @@ void tst_QAudioSource::volume() audioInput.setVolume(volume); } +void tst_QAudioSource::stop_finishesPushMode_whenInvokedUponReadyReadSignal() +{ + const auto defaultAudioInputDevice = QMediaDevices::defaultAudioInput(); + + QAudioFormat audioFormat; + audioFormat.setSampleFormat(QAudioFormat::Int16); + audioFormat.setSampleRate(qBound(defaultAudioInputDevice.minimumSampleRate(), 48000, + defaultAudioInputDevice.maximumSampleRate())); + audioFormat.setChannelCount(qBound(defaultAudioInputDevice.minimumChannelCount(), 2, + defaultAudioInputDevice.maximumChannelCount())); + + const auto isFormatSupported = defaultAudioInputDevice.isFormatSupported(audioFormat); + QCOMPARE(isFormatSupported, true); + + QAudioSource audioInput(audioFormat, this); + + const auto audioInputDevice = audioInput.start(); + + auto isReadyReadReceived = false; + connect(audioInputDevice, &QIODevice::readyRead, this, [&]() { + audioInput.stop(); + isReadyReadReceived = true; + }); + + const auto awaitedValue = QTest::qWaitFor([&] { return isReadyReadReceived; }); + QVERIFY2(awaitedValue, "didn't receive readyRead signal"); + + QVERIFY2((audioInput.state() == QAudio::StoppedState), + "didn't transitions to StoppedState after close()"); +} + QTEST_MAIN(tst_QAudioSource) #include "tst_qaudiosource.moc" diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp index e0b85d4aa..0809664df 100644 --- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp +++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp @@ -106,9 +106,6 @@ public Q_SLOTS: void tst_QCameraBackend::initTestCase() { -#ifdef Q_OS_ANDROID - QSKIP("SKIP initTestCase on CI, because of QTBUG-118571"); -#endif QCamera camera; noCamera = !camera.isAvailable(); } @@ -218,7 +215,7 @@ void tst_QCameraBackend::testCameraActive() QCOMPARE(camera.error(), QCamera::NoError); camera.start(); - QCOMPARE(camera.isActive(), true); + QTRY_COMPARE(camera.isActive(), true); QTRY_COMPARE(activeChangedSignal.size(), 1); QCOMPARE(activeChangedSignal.last().first().value<bool>(), true); @@ -436,6 +433,12 @@ void tst_QCameraBackend::testCameraCaptureMetadata() camera.setFlashMode(QCamera::FlashOff); + QMediaMetaData referenceMetaData; + referenceMetaData.insert(QMediaMetaData::Title, QStringLiteral("Title")); + referenceMetaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German)); + referenceMetaData.insert(QMediaMetaData::Description, QStringLiteral("Description")); + imageCapture.setMetaData(referenceMetaData); + QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable); QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); @@ -448,7 +451,19 @@ void tst_QCameraBackend::testCameraCaptureMetadata() int id = imageCapture.captureToFile(tmpFile); QTRY_VERIFY(!savedSignal.isEmpty()); QVERIFY(!metadataSignal.isEmpty()); + QCOMPARE(metadataSignal.first().first().toInt(), id); + QMediaMetaData receivedMetaData = metadataSignal.first()[1].value<QMediaMetaData>(); + + if (isGStreamerPlatform()) { + for (auto key : { + QMediaMetaData::Title, + QMediaMetaData::Language, + QMediaMetaData::Description, + }) + QCOMPARE(receivedMetaData[key], referenceMetaData[key]); + QVERIFY(receivedMetaData[QMediaMetaData::Resolution].isValid()); + } } void tst_QCameraBackend::testExposureCompensation() @@ -515,7 +530,7 @@ void tst_QCameraBackend::testExposureMode() camera.setExposureMode(QCamera::ExposureAuto); QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); camera.start(); - QVERIFY(camera.isActive()); + QTRY_VERIFY(camera.isActive()); QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); // Manual @@ -538,9 +553,10 @@ void tst_QCameraBackend::testVideoRecording_data() QTest::addColumn<QCameraDevice>("device"); const auto devices = QMediaDevices::videoInputs(); + int i = 0; for (const auto &device : devices) - QTest::newRow(device.description().toUtf8()) << device; + QTest::addRow("%d - %s", i++, device.description().toUtf8().constData()) << device; if (devices.isEmpty()) QTest::newRow("Null device") << QCameraDevice(); @@ -675,8 +691,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; @@ -688,15 +702,20 @@ 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()); + QVERIFY(player.metaData().value(QMediaMetaData::Resolution).isValid()); - metadataChangedSpy.clear(); + if (isGStreamerPlatform()) + QVERIFY(player.metaData().value(QMediaMetaData::Date).isValid()); player.stop(); player.setSource({}); diff --git a/tests/auto/integration/qmediacapturesession/CMakeLists.txt b/tests/auto/integration/qmediacapturesession/CMakeLists.txt index 1aec26493..09687ccbf 100644 --- a/tests/auto/integration/qmediacapturesession/CMakeLists.txt +++ b/tests/auto/integration/qmediacapturesession/CMakeLists.txt @@ -18,9 +18,3 @@ qt_internal_add_test(tst_qmediacapturesession Qt::MultimediaPrivate Qt::MultimediaWidgets ) - -if(QT_FEATURE_gstreamer) - set_tests_properties(tst_qmediacapturesession - PROPERTIES ENVIRONMENT "G_DEBUG=fatal_criticals" - ) -endif() diff --git a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp index 07fb1f223..1ae640860 100644 --- a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp +++ b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp @@ -30,6 +30,8 @@ QT_USE_NAMESPACE +// NOLINTBEGIN(readability-convert-member-functions-to-static) + /* This is the backend conformance test. @@ -43,14 +45,6 @@ class tst_QMediaCaptureSession: public QObject private slots: - void initTestCase() - { - if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") { -#ifdef Q_OS_ANDROID - QSKIP("SKIP initTestCase on CI, because of QTBUG-118571"); -#endif - } - } void testAudioMute(); void stress_test_setup_and_teardown(); void stress_test_setup_and_teardown_keep_session(); @@ -135,7 +129,7 @@ void tst_QMediaCaptureSession::recordFail(QMediaCaptureSession &session) void tst_QMediaCaptureSession::stress_test_setup_and_teardown() { - QSKIP_GSTREAMER("QTBUG-123905: stress_test_setup_and_teardown and friends can crash"); + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); for (int i = 0; i < 50; i++) { QMediaCaptureSession session; @@ -145,8 +139,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown() QAudioOutput output; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -158,6 +154,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_session() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QMediaCaptureSession session; for (int i = 0; i < 50; i++) { QMediaRecorder recorder; @@ -166,8 +164,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_session() QAudioOutput output; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -179,6 +179,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_session() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_recorder() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QMediaCaptureSession session; QMediaRecorder recorder; for (int i = 0; i < 50; i++) { @@ -187,8 +189,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_recorder() QAudioOutput output; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -200,6 +204,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_recorder() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_camera() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QCamera camera; for (int i = 0; i < 50; i++) { QMediaCaptureSession session; @@ -208,8 +214,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_camera() QAudioOutput output; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -221,6 +229,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_camera() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audioinput() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QAudioInput input; for (int i = 0; i < 50; i++) { QMediaCaptureSession session; @@ -229,8 +239,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audioinput() QAudioOutput output; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -242,6 +254,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audioinput() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audiooutput() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QAudioOutput output; for (int i = 0; i < 50; i++) { QMediaCaptureSession session; @@ -250,8 +264,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audiooutput() QAudioInput input; QVideoWidget video; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -263,6 +279,8 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audiooutput() void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_video() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures on CI"); + QVideoWidget video; for (int i = 0; i < 50; i++) { QMediaCaptureSession session; @@ -271,8 +289,10 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_video() QAudioInput input; QAudioOutput output; - session.setAudioInput(&input); - session.setAudioOutput(&output); + if (!input.device().isNull()) + session.setAudioInput(&input); + if (!output.device().isNull()) + session.setAudioOutput(&output); session.setRecorder(&recorder); session.setCamera(&camera); session.setVideoOutput(&video); @@ -967,6 +987,8 @@ void tst_QMediaCaptureSession::can_add_and_remove_ImageCapture() void tst_QMediaCaptureSession::can_move_ImageCapture_between_sessions() { + QSKIP_GSTREAMER("QTBUG-124005: Spurious failure on CI"); + QMediaCaptureSession session0; QMediaCaptureSession session1; QSignalSpy imageCaptureChanged0(&session0, &QMediaCaptureSession::imageCaptureChanged); @@ -997,7 +1019,6 @@ void tst_QMediaCaptureSession::can_move_ImageCapture_between_sessions() QVERIFY(session1.imageCapture() == nullptr); } - void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null() { QCamera camera; diff --git a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt index 39684a32d..f794e397b 100644 --- a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt +++ b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt @@ -18,6 +18,7 @@ qt_internal_add_test(tst_qmediaplayerbackend ../shared/mediafileselector.h ../shared/mediabackendutils.h ../shared/testvideosink.h + ../../shared/qsequentialfileadaptor.h mediaplayerstate.h fake.h fixture.h @@ -30,23 +31,11 @@ qt_internal_add_test(tst_qmediaplayerbackend Qt::Qml Qt::Quick Qt::QuickPrivate - TESTDATA ${testdata_resource_files} - INCLUDE_DIRECTORIES - ../shared/ -) - -qt_internal_add_resource(tst_qmediaplayerbackend "testdata" - PREFIX - "/" - FILES + BUILTIN_TESTDATA + TESTDATA ${testdata_resource_files} "LazyLoad.qml" -) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qmediaplayerbackend CONDITION boot2qt - DEFINES - SKIP_OGV_TEST + INCLUDE_DIRECTORIES + ../shared/ + ../../shared/ ) diff --git a/tests/auto/integration/qmediaplayerbackend/fixture.h b/tests/auto/integration/qmediaplayerbackend/fixture.h index 2bf16b88e..948a55b0d 100644 --- a/tests/auto/integration/qmediaplayerbackend/fixture.h +++ b/tests/auto/integration/qmediaplayerbackend/fixture.h @@ -76,4 +76,20 @@ public: // Helper to create an object that is comparable to a QSignalSpy using SignalList = QList<QList<QVariant>>; +struct TestSubtitleSink : QObject +{ + Q_OBJECT + +public Q_SLOTS: + void addSubtitle(QString string) + { + QMetaObject::invokeMethod(this, [this, string = std::move(string)]() mutable { + subtitles.append(std::move(string)); + }); + } + +public: + QStringList subtitles; +}; + #endif // FIXTURE_H diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv Binary files differnew file mode 100644 index 000000000..80ee0f923 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv Binary files differnew file mode 100644 index 000000000..1962f00c1 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv Binary files differnew file mode 100644 index 000000000..a3c2e9bb9 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv Binary files differnew file mode 100644 index 000000000..2051e4df5 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp index 8f255aadb..da856146c 100644 --- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp +++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp @@ -10,9 +10,11 @@ #include "server.h" #include <qmediametadata.h> #include <qaudiobuffer.h> +#include <qaudiodevice.h> #include <qvideosink.h> #include <qvideoframe.h> #include <qaudiooutput.h> +#include <qmediadevices.h> #include <qprocess.h> #include <private/qglobal_p.h> #ifdef QT_FEATURE_network @@ -30,17 +32,20 @@ #include "mediafileselector.h" #include "mediabackendutils.h" +#include "qsequentialfileadaptor.h" #include <QtMultimedia/private/qtmultimedia-config_p.h> #include "private/qquickvideooutput_p.h" #include <array> +// NOLINTBEGIN(readability-convert-member-functions-to-static) + QT_USE_NAMESPACE using namespace Qt::Literals; namespace { -static qreal colorDifference(QRgb first, QRgb second) +qreal colorDifference(QRgb first, QRgb second) { const auto diffVector = QVector3D(qRed(first), qGreen(first), qBlue(first)) - QVector3D(qRed(second), qGreen(second), qBlue(second)); @@ -62,7 +67,7 @@ auto findSimilarColorIndex(const Colors &colors, QRgb color) return std::distance(std::begin(colors), findSimilarColor(std::begin(colors), std::end(colors), color)); } -} +} // namespace /* This is the backend conformance test. @@ -92,16 +97,25 @@ private slots: void setSource_emitsMediaStatusChange_whenCalledWithInvalidFile(); void setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile(); void setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile(); + void setSource_initializesExpectedDefaultState(); + void setSource_initializesExpectedDefaultState_data(); void setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond(); void setSource_changesSourceAndMediaStatus_whenCalledWithValidFile(); void setSource_updatesExpectedAttributes_whenMediaHasLoaded(); void setSource_stopsAndEntersErrorState_whenPlayerWasPlaying(); void setSource_loadsAudioTrack_whenCalledWithValidWavFile(); void setSource_resetsState_whenCalledWithEmptyUrl(); + void setSource_resetsState_whenCalledWithEmptyUrl_data(); void setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded(); void setSource_loadsCorrectTracks_whenLoadingMediaInSequence(); void setSource_remainsInStoppedState_whenPlayerWasStopped(); void setSource_entersStoppedState_whenPlayerWasPlaying(); + void setSource_updatesTrackProperties_data(); + void setSource_updatesTrackProperties(); + void setSource_emitsTracksChanged_data(); + void setSource_emitsTracksChanged(); + void setSource_emitsError_whenSdpFileIsLoaded(); + void setSource_doesNotCrash_whenCalledWithEmptyUrlWhileLoadingMedia(); void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data(); void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio(); @@ -109,6 +123,10 @@ private slots: void pause_doesNotChangePlayerState_whenInvalidFileLoaded(); void pause_doesNothing_whenMediaIsNotLoaded(); void pause_entersPauseState_whenPlayerWasPlaying(); + void pause_initializesExpectedDefaultState(); + void pause_initializesExpectedDefaultState_data(); + void pause_doesNotAdvancePosition(); + void pause_playback_resumesFromPausedPosition(); void play_resetsErrorState_whenCalledWithInvalidFile(); void play_resumesPlaying_whenValidMediaIsProvidedAfterInvalidMedia(); @@ -121,13 +139,21 @@ private slots: void play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames(); void play_startsPlayback_withAndWithoutOutputsConnected(); void play_startsPlayback_withAndWithoutOutputsConnected_data(); + void play_playsRtpStream_whenSdpFileIsLoaded(); + void play_succeedsFromSourceDevice(); + void play_succeedsFromSourceDevice_data(); + void play_playbackLastsForTheExpectedTime(); + void play_playbackLastsForTheExpectedTime_data(); void stop_entersStoppedState_whenPlayerWasPaused(); + void stop_entersStoppedState_whenPlayerWasPaused_data(); void stop_setsPositionToZero_afterPlayingToEndOfMedia(); void playbackRate_returnsOne_byDefault(); void setPlaybackRate_changesPlaybackRateAndEmitsSignal_data(); void setPlaybackRate_changesPlaybackRateAndEmitsSignal(); + void setPlaybackRate_changesPlaybackDuration(); + void setPlaybackRate_changesPlaybackDuration_data(); void setVolume_changesVolume_whenVolumeIsInRange(); void setVolume_clampsToRange_whenVolumeIsOutsideRange(); @@ -138,6 +164,7 @@ private slots: void processEOS(); void deleteLaterAtEOS(); + void playToEOS_finishesWithEmptyFrame(); void volumeAcrossFiles_data(); void volumeAcrossFiles(); @@ -145,13 +172,16 @@ private slots: void seekPauseSeek(); void seekInStoppedState(); void subsequentPlayback(); + void subsequentPlayback_playsForExpectedDuration(); void surfaceTest(); void metadata(); void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data(); void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail(); void playerStateAtEOS(); void playFromBuffer(); + void playFromSequentialStream(); void audioVideoAvailable(); + void audioVideoAvailable_updatedOnNewMedia(); void isSeekable(); void positionAfterSeek(); void pause_rendersVideoAtCorrectResolution_data(); @@ -165,11 +195,12 @@ private slots: void durationDetectionIssues_data(); void durationDetectionIssues(); void finiteLoops(); + void finiteLoops_data(); void infiniteLoops(); void seekOnLoops(); void changeLoopsOnTheFly(); void seekAfterLoopReset(); - void changeVideoOutputNoFramesLost(); + void cleanSinkAndNoMoreFramesAfterStop(); void lazyLoadVideo(); void videoSinkSignals(); @@ -178,11 +209,42 @@ private slots: void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata_data(); void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata(); + void setVideoOutput_doesNotStopPlayback_data(); + void setVideoOutput_doesNotStopPlayback(); + void setVideoOutput_whilePaused_updatesNewSink(); + void setVideoOutput_whilePlaying_doesNotDropFrames(); + + void setAudioOutput_doesNotStopPlayback_data(); + void setAudioOutput_doesNotStopPlayback(); + void swapAudioDevice_doesNotStopPlayback_data(); + void swapAudioDevice_doesNotStopPlayback(); + + void play_readsSubtitle(); + void multiTrack_validateMetadata(); + void play_readsSubtitle_fromMultiTrack(); + void play_readsSubtitle_fromMultiTrack_data(); + + void setActiveSubtitleTrack_switchesSubtitles(); + void setActiveSubtitleTrack_switchesSubtitles_data(); + + void setActiveVideoTrack_switchesVideoTrack(); + + void disablingAllTracks_doesNotStopPlayback(); + void disablingAllTracks_beforeTracksChanged_doesNotStopPlayback(); + + void makeStressTestCases(); + void stressTest_setupAndTeardown(); + void stressTest_setupAndTeardown_data(); + void stressTest_setupAndTeardown_keepAudioOutput(); + void stressTest_setupAndTeardown_keepAudioOutput_data(); + void stressTest_setupAndTeardown_keepVideoOutput(); + void stressTest_setupAndTeardown_keepVideoOutput_data(); + private: QUrl selectVideoFile(const QStringList &mediaCandidates); - bool canCreateRtspStream() const; - std::unique_ptr<QProcess> createRtspStreamProcess(QString fileName, QString outputUrl); + bool canCreateRtpStream() const; + std::unique_ptr<QProcess> createRtpStreamProcess(QString fileName, QString sdpUrl); void detectVlcCommand(); // one second local wav file @@ -190,6 +252,7 @@ private: MaybeUrl m_localWavFile2 = QUnexpect{}; MaybeUrl m_localVideoFile = QUnexpect{}; MaybeUrl m_localVideoFile2 = QUnexpect{}; + MaybeUrl m_localVideoFile1Sec = QUnexpect{}; MaybeUrl m_av1File = QUnexpect{}; MaybeUrl m_videoDimensionTestFile = QUnexpect{}; MaybeUrl m_localCompressedSoundFile = QUnexpect{}; @@ -204,6 +267,10 @@ private: MaybeUrl m_colorMatrix90degClockwiseVideo = QUnexpect{}; MaybeUrl m_colorMatrix180degClockwiseVideo = QUnexpect{}; MaybeUrl m_colorMatrix270degClockwiseVideo = QUnexpect{}; + MaybeUrl m_15sVideo = QUnexpect{}; + MaybeUrl m_subtitleVideo = QUnexpect{}; + MaybeUrl m_multitrackVideo = QUnexpect{}; + MaybeUrl m_multitrackSubtitleStartsAtZeroVideo = QUnexpect{}; MediaFileSelector m_mediaSelector; @@ -276,7 +343,7 @@ void tst_QMediaPlayerBackend::detectVlcCommand() m_vlcCommand.clear(); } -bool tst_QMediaPlayerBackend::canCreateRtspStream() const +bool tst_QMediaPlayerBackend::canCreateRtpStream() const { return !m_vlcCommand.isEmpty(); } @@ -284,7 +351,10 @@ bool tst_QMediaPlayerBackend::canCreateRtspStream() const void tst_QMediaPlayerBackend::initTestCase() { #ifdef Q_OS_ANDROID - QSKIP("SKIP initTestCase on CI, because of QTBUG-118571"); + // Emulator x86 with API 23 (Android 6) has issue with playing sound + // Test should not be run on Emulator with API 23 + if (QNativeInterface::QAndroidApplication::sdkVersion() <= __ANDROID_API_M__) + QSKIP("SKIP initTestCase on API 23"); #endif QMediaPlayer player; @@ -315,6 +385,8 @@ void tst_QMediaPlayerBackend::initTestCase() m_localVideoFile2 = m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4", "qrc:/testdata/busMpeg4.mp4"); + m_localVideoFile1Sec = m_mediaSelector.select("qrc:/testdata/busMpeg4.mp4"); + m_videoDimensionTestFile = m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4"); m_localCompressedSoundFile = @@ -335,6 +407,12 @@ void tst_QMediaPlayerBackend::initTestCase() m_colorMatrix270degClockwiseVideo = m_mediaSelector.select("qrc:/testdata/color_matrix_270_deg_clockwise.mp4"); + m_15sVideo = m_mediaSelector.select("qrc:/testdata/15s.mkv"); + m_subtitleVideo = m_mediaSelector.select("qrc:/testdata/subtitletest.mkv"); + m_multitrackVideo = m_mediaSelector.select("qrc:/testdata/multitrack.mkv"); + m_multitrackSubtitleStartsAtZeroVideo = + m_mediaSelector.select("qrc:/testdata/multitrack-subtitle-start-at-zero.mkv"); + detectVlcCommand(); } @@ -374,7 +452,10 @@ void tst_QMediaPlayerBackend::destructor_emitsOnlyQObjectDestroyedSignal_whenPla // Arrange m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound); m_fixture->player.play(); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + + // Wait for started + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); m_fixture->clearSpies(); @@ -490,6 +571,44 @@ void tst_QMediaPlayerBackend::setSource_setsSourceMediaStatusAndError_whenCalled COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); } +void tst_QMediaPlayerBackend::setSource_initializesExpectedDefaultState() +{ + QFETCH(MaybeUrl, url); + CHECK_SELECTED_URL(url); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*url); + + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.source = *url; + expectedState.mediaStatus = QMediaPlayer::LoadingMedia; + + if (isGStreamerPlatform()) { + // gstreamer initializes the tracks + expectedState.audioTracks = std::nullopt; + expectedState.videoTracks = std::nullopt; + expectedState.activeAudioTrack = std::nullopt; + expectedState.activeVideoTrack = std::nullopt; + expectedState.hasAudio = std::nullopt; + expectedState.hasVideo = std::nullopt; + + expectedState.isSeekable = true; + } + + const MediaPlayerState actualState{ m_fixture->player }; + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); +} + +void tst_QMediaPlayerBackend::setSource_initializesExpectedDefaultState_data() +{ + QTest::addColumn<MaybeUrl>("url"); + + QTest::addRow("with wave file") << m_localWavFile; + QTest::addRow("with video file") << m_localVideoFile; + QTest::addRow("with av1 file") << m_av1File; + QTest::addRow("with compressed sound file") << m_localCompressedSoundFile; +} + void tst_QMediaPlayerBackend::setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond() { #ifdef QT_FEATURE_network @@ -511,10 +630,14 @@ void tst_QMediaPlayerBackend::setSource_silentlyCancelsPreviousCall_whenServerDo // Cancellation is silent QVERIFY(m_fixture->errorOccurred.empty()); - // Media status is emitted as if only one file was loaded - const SignalList expectedMediaStatus = { { QMediaPlayer::LoadingMedia }, - { QMediaPlayer::LoadedMedia } }; - QCOMPARE_EQ(m_fixture->mediaStatusChanged, expectedMediaStatus); + if (!isGStreamerPlatform()) { + // QTBUG-124005: gstreamer sees multiple loading/loaded transitions + + // Media status is emitted as if only one file was loaded + const SignalList expectedMediaStatus = { { QMediaPlayer::LoadingMedia }, + { QMediaPlayer::LoadedMedia } }; + QCOMPARE_EQ(m_fixture->mediaStatusChanged, expectedMediaStatus); + } // Two media source changed signals should be emitted still const SignalList expectedSource = { { server.address() }, { *m_localVideoFile } }; @@ -542,6 +665,7 @@ void tst_QMediaPlayerBackend::setSource_changesSourceAndMediaStatus_whenCalledWi MediaPlayerState actualState{ m_fixture->player }; + QSKIP_GSTREAMER("QTBUG-124005: spurious failures"); COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); } @@ -638,25 +762,31 @@ void tst_QMediaPlayerBackend::setSource_loadsAudioTrack_whenCalledWithValidWavFi void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl() { - CHECK_SELECTED_URL(m_localWavFile); + QFETCH(MaybeUrl, url); + CHECK_SELECTED_URL(url); + + QMediaPlayer &player = m_fixture->player; // Load valid media and start playing - m_fixture->player.setSource(*m_localWavFile); + player.setSource(*url); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); - QVERIFY(m_fixture->player.position() == 0); -#ifdef Q_OS_QNX - // QNX mm-renderer only updates the duration when 'play' is triggered - QVERIFY(m_fixture->player.duration() == 0); -#else - QVERIFY(m_fixture->player.duration() > 0); -#endif + QCOMPARE(player.position(), 0); - m_fixture->player.play(); + if (isQNXPlatform()) + // QNX mm-renderer updates the duration when 'play' is triggered + QCOMPARE(player.duration(), 0); + else + QCOMPARE_GT(player.duration(), 0); - QTRY_VERIFY(m_fixture->player.position() > 0); - QVERIFY(m_fixture->player.duration() > 0); + player.play(); + + QTRY_COMPARE_GT(player.position(), 0); + if (isGStreamerPlatform()) + QTRY_COMPARE_GT(player.duration(), 0); // duration update is asynchronous + else + QCOMPARE_GT(player.duration(), 0); // Set empty URL and verify that state is fully reset to default m_fixture->clearSpies(); @@ -666,12 +796,22 @@ void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl() QVERIFY(!m_fixture->mediaStatusChanged.isEmpty()); QVERIFY(!m_fixture->sourceChanged.isEmpty()); - const MediaPlayerState expectedState = MediaPlayerState::defaultState(); - const MediaPlayerState actualState{ m_fixture->player }; + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + if (isGStreamerPlatform()) // QTBUG-124005: no buffer progress update + expectedState.bufferProgress = std::nullopt; + const MediaPlayerState actualState{ player }; COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); } +void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl_data() +{ + QTest::addColumn<MaybeUrl>("url"); + + QTest::addRow("with wave file") << m_localWavFile; + QTest::addRow("with video file") << m_localVideoFile; +} + void tst_QMediaPlayerBackend::setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded() { CHECK_SELECTED_URL(m_localWavFile); @@ -687,7 +827,8 @@ void tst_QMediaPlayerBackend::setSource_loadsNewMedia_whenPreviousMediaWasFullyL QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia); QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); m_fixture->player.play(); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); // Load first file again, and wait for it to start loading m_fixture->player.setSource(*m_localWavFile2); @@ -776,12 +917,14 @@ void tst_QMediaPlayerBackend::setSource_entersStoppedState_whenPlayerWasPlaying( // Assert QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); QTRY_COMPARE(m_fixture->mediaStatusChanged, - SignalList({ { QMediaPlayer::LoadedMedia }, - { QMediaPlayer::BufferingMedia }, - { QMediaPlayer::BufferedMedia }, - { QMediaPlayer::LoadedMedia }, - { QMediaPlayer::LoadingMedia }, - { QMediaPlayer::LoadedMedia } })); + SignalList({ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::LoadingMedia }, + { QMediaPlayer::LoadedMedia }, + })); QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); QTRY_COMPARE(m_fixture->playbackStateChanged, @@ -793,6 +936,130 @@ void tst_QMediaPlayerBackend::setSource_entersStoppedState_whenPlayerWasPlaying( QCOMPARE(m_fixture->player.position(), 0); } +void tst_QMediaPlayerBackend::setSource_updatesTrackProperties_data() +{ + QTest::addColumn<MaybeUrl>("url"); + QTest::addColumn<int>("numberOfVideoTracks"); + QTest::addColumn<int>("numberOfAudioTracks"); + QTest::addColumn<int>("numberOfSubtitleTracks"); + + QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0; + QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0; + QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0; + QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0; + QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1; + QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2; +} + +void tst_QMediaPlayerBackend::setSource_updatesTrackProperties() +{ + QFETCH(MaybeUrl, url); + QFETCH(int, numberOfVideoTracks); + QFETCH(int, numberOfAudioTracks); + QFETCH(int, numberOfSubtitleTracks); + + QMediaPlayer &player = m_fixture->player; + + CHECK_SELECTED_URL(url); + + player.setSource(*url); + + QTRY_COMPARE(player.videoTracks().size(), numberOfVideoTracks); + QTRY_COMPARE(player.audioTracks().size(), numberOfAudioTracks); + QTRY_COMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks); +} + +void tst_QMediaPlayerBackend::setSource_emitsTracksChanged_data() +{ + QTest::addColumn<MaybeUrl>("url"); + QTest::addColumn<int>("numberOfVideoTracks"); + QTest::addColumn<int>("numberOfAudioTracks"); + QTest::addColumn<int>("numberOfSubtitleTracks"); + + QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0; + QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0; + QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0; + QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0; + QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1; + QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2; +} + +void tst_QMediaPlayerBackend::setSource_emitsTracksChanged() +{ + QFETCH(MaybeUrl, url); + QFETCH(int, numberOfVideoTracks); + QFETCH(int, numberOfAudioTracks); + QFETCH(int, numberOfSubtitleTracks); + + QMediaPlayer &player = m_fixture->player; + + CHECK_SELECTED_URL(url); + + QSignalSpy tracksChanged(&player, &QMediaPlayer::tracksChanged); + player.setSource(*url); + + QVERIFY(tracksChanged.wait()); + + QCOMPARE(player.videoTracks().size(), numberOfVideoTracks); + QCOMPARE(player.audioTracks().size(), numberOfAudioTracks); + QCOMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks); +} + +void tst_QMediaPlayerBackend::setSource_emitsError_whenSdpFileIsLoaded() +{ + // NOTE: This test checks that playing rtp streams using local .sdp file as a source is blocked + // by default. For when the user wants to override these defaults, see + // play_playsRtpStream_whenSdpFileIsLoaded + + if (!isFFMPEGPlatform()) + QSKIP("This test is only for FFmpeg backend"); + + // Create stream + if (!canCreateRtpStream()) + QSKIP("Rtp stream cannot be created"); + + // Make sure the default whitelist is used + qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST"); + + auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4"); + QVERIFY(temporaryFile); + + // Pass a "file:" URL to VLC in order to generate an .sdp file + const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath()); + + auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString()); + QVERIFY2(process, "Cannot start rtp process"); + + auto processCloser = qScopeGuard([&process, &sdpUrl]() { + // End stream + process->close(); + + // Remove .sdp file created by VLC + QFile(sdpUrl.toLocalFile()).remove(); + }); + + m_fixture->player.setSource(sdpUrl); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::FormatError); +} + +void tst_QMediaPlayerBackend::setSource_doesNotCrash_whenCalledWithEmptyUrlWhileLoadingMedia() +{ + // Added to prevent Q_ASSERT failures in QFFmpegMediaPlayer::setMediaAsync(), like QTBUG-128159 + CHECK_SELECTED_URL(m_localVideoFile); + + // Act + m_fixture->player.setSource(*m_localVideoFile); // Loads media in a separate thread + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::LoadingMedia); + m_fixture->player.setSource(QUrl("")); // Cancels loading before the thread returns + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::NoMedia); + + // Assert + // At this point, due to a waitForFinished() in setMedia(), the loading thread has + // finished its invokeMethod() call to transition back to the calling thread. + // qWait() triggers the transition immediately, so 10 ms is sufficient. + QTest::qWait(10); +} + void tst_QMediaPlayerBackend:: setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data() { @@ -801,13 +1068,22 @@ void tst_QMediaPlayerBackend:: QTest::addRow("Horizontal expanding (par=3/2)") << m_192x108_PAR_3_2_Video << QSize(192 * 3 / 2, 108); - QTest::addRow("Vertical expanding (par=2/3)") - << m_192x108_PAR_2_3_Video << QSize(192, 108 * 3 / 2); + + if (isGStreamerPlatform()) + // QTBUG-125249: gstreamer tries "to keep the input height (because of interlacing)" + QTest::addRow("Horizontal shrinking (par=2/3)") + << m_192x108_PAR_2_3_Video << QSize(192 * 2 / 3, 108); + else + QTest::addRow("Vertical expanding (par=2/3)") + << m_192x108_PAR_2_3_Video << QSize(192, 108 * 3 / 2); } void tst_QMediaPlayerBackend:: setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio() { + if (isGStreamerPlatform() && isCI()) + QSKIP("QTBUG-124005: Fails with gstreamer on CI"); + QFETCH(MaybeUrl, url); QFETCH(QSize, expectedVideoSize); @@ -839,13 +1115,13 @@ void tst_QMediaPlayerBackend:: // Video schema: // // 192 - // /---------------------\ + // *---------------------* // | White | | // | | | // |----------/ | 108 // | Red | // | | - // \---------------------/ + // *---------------------* // clang-format on @@ -907,7 +1183,7 @@ void tst_QMediaPlayerBackend::pause_entersPauseState_whenPlayerWasPlaying() // Arrange m_fixture->player.setSource(*m_localWavFile); m_fixture->player.play(); - QTRY_VERIFY(m_fixture->player.position() > 100); + QTRY_COMPARE_GT(m_fixture->player.position(), 100); m_fixture->clearSpies(); const qint64 positionBeforePause = m_fixture->player.position(); @@ -919,9 +1195,123 @@ void tst_QMediaPlayerBackend::pause_entersPauseState_whenPlayerWasPlaying() QCOMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PausedState } })); QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200); + QTest::qWait(500); - QTRY_VERIFY(qAbs(m_fixture->player.position() - positionBeforePause) < 150); + QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200); +} + +void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState() +{ + QFETCH(MaybeUrl, url); + QFETCH(bool, hasVideo); + QFETCH(bool, hasAudio); + CHECK_SELECTED_URL(url); + + if (isFFMPEGPlatform() && url->path().contains("Av1")) + QSKIP("QTBUG-119711: ffmpeg's binaries on CI do not support av1"); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*url); + player.pause(); + + QTRY_COMPARE(player.playbackState(), QMediaPlayer::PausedState); + + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.source = *url; + expectedState.playbackState = QMediaPlayer::PausedState; + expectedState.isSeekable = true; + + expectedState.mediaStatus = std::nullopt; + expectedState.duration = std::nullopt; + expectedState.bufferProgress = std::nullopt; + + expectedState.audioTracks = std::nullopt; + expectedState.videoTracks = std::nullopt; + expectedState.metaData = std::nullopt; + + if (hasVideo) { + expectedState.activeVideoTrack = 0; + expectedState.hasVideo = std::nullopt; + } + + if (hasAudio) { + expectedState.activeAudioTrack = 0; + expectedState.hasAudio = std::nullopt; + } + + const MediaPlayerState actualState{ player }; + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); + + QVERIFY(actualState.mediaStatus == QMediaPlayer::BufferingMedia + || actualState.mediaStatus == QMediaPlayer::BufferedMedia); + + if (hasVideo) + QCOMPARE(actualState.videoTracks->size(), 1); + if (hasAudio) + QCOMPARE(actualState.audioTracks->size(), 1); + + QSKIP_GSTREAMER("GStreamer doesn't update bufferProgress while paused"); + + QTRY_COMPARE_GT(actualState.bufferProgress, 0); +} + +void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState_data() +{ + QTest::addColumn<MaybeUrl>("url"); + QTest::addColumn<bool>("hasVideo"); + QTest::addColumn<bool>("hasAudio"); + + QTest::addRow("with wave file") << m_localWavFile << false << true; + QTest::addRow("with video file") << m_localVideoFile << true << true; + QTest::addRow("with av1 file") << m_av1File << true << false; + QTest::addRow("with compressed sound file") << m_localCompressedSoundFile << false << true; +} + +void tst_QMediaPlayerBackend::pause_doesNotAdvancePosition() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_localVideoFile); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*m_localVideoFile); + + player.pause(); + + QTest::qWait(1'000); + + QTRY_COMPARE_EQ(player.position(), 0); +} + +void tst_QMediaPlayerBackend::pause_playback_resumesFromPausedPosition() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_localVideoFile); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*m_localVideoFile); + + player.play(); + + QTRY_COMPARE_GT(player.position(), 100); + + player.pause(); + + qint64 pausePos = player.position(); + QTest::qWait(1'000); + + QCOMPARE_EQ(player.position(), pausePos); + + player.play(); + + // Make sure the media player does not make up for the lost time + m_fixture->positionChanged.wait(); + m_fixture->positionChanged.wait(); + + QCOMPARE_LT(player.position(), pausePos + 500); } void tst_QMediaPlayerBackend::play_resetsErrorState_whenCalledWithInvalidFile() @@ -959,7 +1349,8 @@ void tst_QMediaPlayerBackend::play_resumesPlaying_whenValidMediaIsProvidedAfterI // Assert QTRY_VERIFY(m_fixture->framesCount > 0); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); QCOMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState); QCOMPARE(m_fixture->player.error(), QMediaPlayer::NoError); } @@ -987,14 +1378,19 @@ void tst_QMediaPlayerBackend::play_setsPlaybackStateAndMediaStatus_whenValidFile m_fixture->player.play(); QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } })); - QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged, - SignalList({ { QMediaPlayer::LoadingMedia }, - { QMediaPlayer::LoadedMedia }, - { QMediaPlayer::BufferingMedia }, - { QMediaPlayer::BufferedMedia } })); + + auto expectedMediaStatus = SignalList{ + { QMediaPlayer::LoadingMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + }; + + QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged.first(4), expectedMediaStatus); QTRY_COMPARE_GT(m_fixture->bufferProgressChanged.size(), 0); QTRY_COMPARE_NE(m_fixture->bufferProgressChanged.front().front(), 0.f); @@ -1032,15 +1428,26 @@ void tst_QMediaPlayerBackend::play_doesNotEnterMediaLoadingState_whenResumingPla // Assert QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); - QCOMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } })); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); + QTRY_VERIFY(m_fixture->playbackStateChanged.contains({ QMediaPlayer::PlayingState })); // Note: Should not go through Loading again when play -> stop -> play - QCOMPARE_EQ(m_fixture->mediaStatusChanged, - SignalList({ - { QMediaPlayer::BufferingMedia }, - { QMediaPlayer::BufferedMedia }, - })); + if (!isGStreamerPlatform()) { + QCOMPARE_EQ(m_fixture->mediaStatusChanged, + SignalList({ + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + })); + } else { + QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged, + // gstreamer may see EndOfMedia + SignalList({ + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::EndOfMedia }, + })); + } } void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl() @@ -1061,12 +1468,25 @@ void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayb const MediaPlayerState actualState{ m_fixture->player }; COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); - QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged, - SignalList({ { QMediaPlayer::LoadedMedia }, - { QMediaPlayer::BufferingMedia }, - { QMediaPlayer::BufferedMedia }, - { QMediaPlayer::LoadedMedia }, - { QMediaPlayer::NoMedia } })); + QList allowedSignalSequences = { + SignalList{ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::NoMedia }, + }, + SignalList{ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::EndOfMedia }, // EndOfMedia can be reached before setSource({}) + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::NoMedia }, + }, + }; + + QTRY_VERIFY(allowedSignalSequences.contains(m_fixture->mediaStatusChanged)); QTRY_COMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState }, { QMediaPlayer::StoppedState } })); @@ -1078,15 +1498,17 @@ void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayb void tst_QMediaPlayerBackend:: play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream() { - if (!canCreateRtspStream()) + if (!canCreateRtpStream()) QSKIP("Rtsp stream cannot be created"); + QSKIP_GSTREAMER("GStreamer tests fail"); + auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4"); QVERIFY(temporaryFile); const QString streamUrl = "rtsp://localhost:8083/stream"; - auto process = createRtspStreamProcess(temporaryFile->fileName(), streamUrl); + auto process = createRtpStreamProcess(temporaryFile->fileName(), streamUrl); QVERIFY2(process, "Cannot start rtsp process"); auto processCloser = qScopeGuard([&process]() { process->close(); }); @@ -1142,6 +1564,9 @@ void tst_QMediaPlayerBackend:: void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames() { + if (isCI() && isGStreamerPlatform()) + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); + CHECK_SELECTED_URL(m_oneRedFrameVideo); m_fixture->surface.setStoreFrames(true); @@ -1162,7 +1587,7 @@ void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLong // QTBUG-124005: GStreamer timing seems to be off // 1000 is expected - QCOMPARE_GT(elapsed, 900); + QCOMPARE_GT(elapsed, 850); QCOMPARE_LT(elapsed, 1400); } @@ -1173,15 +1598,18 @@ void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLong 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) + if (!videoConnected && !audioConnected) { QSKIP_FFMPEG("FFMPEG backend playback fails when no output is connected"); + QSKIP_GSTREAMER("GStreamer backend playback fails when no output is connected"); + } + + if (videoConnected && !audioConnected) + QSKIP_GSTREAMER("GStreamer backend playback fails when no video is connected"); // Arrange m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound); @@ -1219,37 +1647,206 @@ void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected QTest::addRow("no output connected") << false << false; } +void tst_QMediaPlayerBackend::play_playsRtpStream_whenSdpFileIsLoaded() +{ + if (!isFFMPEGPlatform()) + QSKIP("This test is only for FFmpeg backend"); + + // Create stream + if (!canCreateRtpStream()) + QSKIP("Rtp stream cannot be created"); + + auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4"); + QVERIFY(temporaryFile); + + // Pass a "file:" URL to VLC in order to generate an .sdp file + const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath()); + + auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString()); + QVERIFY2(process, "Cannot start rtp process"); + + // Set reasonable protocol whitelist that includes rtp and udp + qputenv("QT_FFMPEG_PROTOCOL_WHITELIST", "file,crypto,data,rtp,udp"); + + auto processCloser = qScopeGuard([&process, &sdpUrl]() { + // End stream + process->close(); + + // Remove .sdp file created by VLC + QFile(sdpUrl.toLocalFile()).remove(); + + // Unset environment variable + qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST"); + }); + + m_fixture->player.setSource(sdpUrl); + + // Play + m_fixture->player.play(); + QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState); +} + +void tst_QMediaPlayerBackend::play_succeedsFromSourceDevice() +{ + QFETCH(const MaybeUrl, mediaUrl); + QFETCH(bool, streamOutlivesPlayer); + + CHECK_SELECTED_URL(mediaUrl); + + auto *stream = new QFile(u":"_s + mediaUrl->path()); + + QVERIFY(stream->open(QFile::ReadOnly)); + + QMediaPlayer &player = m_fixture->player; + + player.setSourceDevice(stream); + + player.play(); + QTRY_COMPARE_GT(player.position(), 100); + + if (streamOutlivesPlayer) + stream->setParent(&player); + else + delete stream; +} + +void tst_QMediaPlayerBackend::play_succeedsFromSourceDevice_data() +{ + QTest::addColumn<MaybeUrl>("mediaUrl"); + QTest::addColumn<bool>("streamOutlivesPlayer"); + + QTest::addRow("audio file") << m_localWavFile << true; + QTest::addRow("video file") << m_localVideoFile << true; + + // QMediaPlayer crashes when we delete the stream during playback + constexpr bool validateStreamDestructionDuringPlayback = false; + if constexpr (validateStreamDestructionDuringPlayback) { + QTest::addRow("audio file, stream destroyed during playback") << m_localWavFile << false; + QTest::addRow("video file, stream destroyed during playback") << m_localVideoFile << false; + } +} + +void tst_QMediaPlayerBackend::play_playbackLastsForTheExpectedTime() +{ + using namespace std::chrono; + using namespace std::chrono_literals; + + QFETCH(const QUrl, media); + QFETCH(const int, loops); + QFETCH(const float, rate); + QFETCH(const bool, pauseBeforePlay); + + if (media == *m_localVideoFile1Sec && loops) + QSKIP_GSTREAMER("QTBUG-126799: Video looping video files fails with gstreamer"); + + if (isGStreamerPlatform() && isCI()) + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer (significant startup lag)"); + + QMediaPlayer &player = m_fixture->player; + + player.setSource(media); + + // wait for preroll + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + if (pauseBeforePlay) + player.pause(); + + if (loops > 1) + player.setLoops(loops); + + if (rate != 1.f) + player.setPlaybackRate(rate); + + player.play(); + + auto timer = QElapsedTimer(); + timer.start(); + + QTRY_COMPARE_EQ_WITH_TIMEOUT(player.playbackState(), QMediaPlayer::StoppedState, 10'000); + + nanoseconds duration{timer.nsecsElapsed()}; + nanoseconds expectedDuration = milliseconds{ int(loops / rate * 1000) }; + + QVERIFY2(abs(duration - expectedDuration) < 600ms, + qPrintable(u"expected duration: %1ms, actual duration: %2ms"_s + .arg(round<milliseconds>(expectedDuration).count()) + .arg(round<milliseconds>(duration).count()))); + + QCOMPARE_EQ(player.mediaStatus(), QMediaPlayer::EndOfMedia); +} + +void tst_QMediaPlayerBackend::play_playbackLastsForTheExpectedTime_data() +{ + QTest::addColumn<QUrl>("media"); + QTest::addColumn<int>("loops"); + QTest::addColumn<float>("rate"); + QTest::addColumn<bool>("pauseBeforePlay"); + + for (MaybeUrl maybeUrl : { *m_localWavFile, *m_localVideoFile1Sec }) { + if (!maybeUrl) + continue; + + for (float rate : { 1.f, 2.f, 0.5f }) { + for (bool pauseBeforePlay : { false, true }) { + for (int loops : { 1, 2 }) { + auto name = QStringLiteral("file %1, loops %2, rate %3, pause before %4") + .arg(maybeUrl->toString()) + .arg(loops) + .arg(rate) + .arg(pauseBeforePlay); + QTest::addRow("%s", name.toLatin1().constData()) + << *maybeUrl << loops << rate << pauseBeforePlay; + } + } + } + } +} + void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused() { - CHECK_SELECTED_URL(m_localWavFile); + QFETCH(const MaybeUrl, mediaUrl); + + CHECK_SELECTED_URL(mediaUrl); + QMediaPlayer &player = m_fixture->player; // Arrange - m_fixture->player.setSource(*m_localWavFile); - m_fixture->player.play(); - QTRY_VERIFY(m_fixture->player.position() > 100); - m_fixture->player.pause(); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + player.setSource(*mediaUrl); + player.play(); + QTRY_COMPARE_GT(player.position(), 100); + player.pause(); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); m_fixture->clearSpies(); + if (!isGStreamerPlatform()) // Gstreamer may see EOS already + QCOMPARE_GT(player.position(), 100); + // Act - m_fixture->player.stop(); + player.stop(); // Assert - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::StoppedState } })); // it's allowed to emit statusChanged() signal async QTRY_COMPARE(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadedMedia } })); - if (!isGStreamerPlatform()) - // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages - QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } })); + QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } })); QTRY_COMPARE(m_fixture->player.position(), qint64(0)); + + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer - possibly due to EOS?"); QTRY_VERIFY(!m_fixture->positionChanged.empty()); QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), qint64(0)); - QVERIFY(m_fixture->player.duration() > 0); + QVERIFY(player.duration() > 0); +} + +void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused_data() +{ + QTest::addColumn<MaybeUrl>("mediaUrl"); + + QTest::addRow("audio file") << m_localWavFile; + QTest::addRow("video file") << m_localVideoFile; } void tst_QMediaPlayerBackend::stop_setsPositionToZero_afterPlayingToEndOfMedia() @@ -1269,6 +1866,10 @@ void tst_QMediaPlayerBackend::stop_setsPositionToZero_afterPlayingToEndOfMedia() QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); m_fixture->player.play(); + + if (isGStreamerPlatform()) + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); + QVERIFY(m_fixture->surface.waitForFrame().isValid()); } @@ -1324,6 +1925,68 @@ void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal( QCOMPARE_EQ(m_fixture->player.playbackRate(), expectedPlaybackRate); } +void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackDuration() +{ + using namespace std::chrono; + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + + // speeding up a 15s file by 5 should result in a duration of 3s, but in CI, + // time measurements may be quite off. We can therefore only do basic + // sanity checking to make sure the playback has a duration greater + // than zero, and less than the video duration at normal playback rate + auto minDuration = 1s; // Reasonable approximation for zero + auto maxDuration = 14s; // Approximation for less than 15 seconds + auto playbackRate = 5.0; + + QFETCH(const QLatin1String, testMode); + + QMediaPlayer &player = m_fixture->player; + + if (testMode == "SetRateBeforeSetSource"_L1) + player.setPlaybackRate(playbackRate); + + player.setSource(*m_15sVideo); + + QTRY_COMPARE_EQ(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + auto begin = steady_clock::now(); + + if (testMode == "SetRateBeforePlay"_L1) + player.setPlaybackRate(playbackRate); + + player.play(); + + if (testMode == "SetRateAfterPlay"_L1) + player.setPlaybackRate(playbackRate); + + if (testMode == "SetRateAfterPlaybackStarted"_L1) { + QTRY_COMPARE_GT(player.position(), 50); + player.setPlaybackRate(playbackRate); + } + + QCOMPARE(player.playbackRate(), playbackRate); + + QTRY_COMPARE_EQ_WITH_TIMEOUT(player.playbackState(), QMediaPlayer::StoppedState, 20'000); + + auto end = steady_clock::now(); + auto duration = end - begin; + + QCOMPARE_LT(duration, maxDuration); + QCOMPARE_GT(duration, minDuration); +} + +void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackDuration_data() +{ + QTest::addColumn<QLatin1String>("testMode"); + + QTest::addRow("SetRateBeforeSetSource") << "SetRateBeforeSetSource"_L1; + QTest::addRow("SetRateBeforePlay") << "SetRateBeforePlay"_L1; + QTest::addRow("SetRateAfterPlay") << "SetRateAfterPlay"_L1; + QTest::addRow("SetRateAfterPlaybackStarted") << "SetRateAfterPlaybackStarted"_L1; +} + void tst_QMediaPlayerBackend::setVolume_changesVolume_whenVolumeIsInRange() { m_fixture->output.setVolume(0.0f); @@ -1393,105 +2056,118 @@ void tst_QMediaPlayerBackend::setMuted_doesNotChangeVolume() void tst_QMediaPlayerBackend::processEOS() { - CHECK_SELECTED_URL(m_localWavFile); + QSKIP_GSTREAMER("QTBUG-124005: spurious failure with gstreamer"); - m_fixture->player.setSource(*m_localWavFile); + QMediaPlayer &player = m_fixture->player; + QSignalSpy &mediaStatusChanged = m_fixture->mediaStatusChanged; + QSignalSpy &playbackStateChanged = m_fixture->playbackStateChanged; + QSignalSpy &positionChanged = m_fixture->positionChanged; + QSignalSpy &bufferProgressChanged = m_fixture->bufferProgressChanged; - m_fixture->player.play(); - m_fixture->player.setPosition(900); + CHECK_SELECTED_URL(m_localWavFile); + player.setSource(*m_localWavFile); + + player.play(); + player.setPosition(900); //wait up to 5 seconds for EOS - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); - QVERIFY(m_fixture->mediaStatusChanged.size() > 0); - QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia); - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(m_fixture->playbackStateChanged.size(), 2); - QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); + QVERIFY(mediaStatusChanged.size() > 0); + QCOMPARE(mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), + QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); + QCOMPARE(playbackStateChanged.size(), 2); + QCOMPARE(playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), + QMediaPlayer::StoppedState); //at EOS the 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()); + QCOMPARE(player.position(), player.duration()); + QTRY_VERIFY(positionChanged.size() > 0); + QTRY_COMPARE(positionChanged.last()[0].value<qint64>(), player.duration()); - m_fixture->playbackStateChanged.clear(); - m_fixture->mediaStatusChanged.clear(); - m_fixture->positionChanged.clear(); + playbackStateChanged.clear(); + mediaStatusChanged.clear(); + positionChanged.clear(); - m_fixture->player.play(); + player.play(); //position is reset to start - QTRY_VERIFY(m_fixture->player.position() < 100); - QTRY_VERIFY(m_fixture->positionChanged.size() > 0); - QCOMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0); - - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_COMPARE_LT(player.position(), 500); + QTRY_VERIFY(positionChanged.size() > 0); + QCOMPARE(positionChanged.first()[0].value<qint64>(), 0); - QCOMPARE(m_fixture->playbackStateChanged.size(), 1); - QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState); - QVERIFY(m_fixture->mediaStatusChanged.size() > 0); - QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia); - - m_fixture->positionChanged.clear(); - QTRY_VERIFY(m_fixture->player.position() > 100); - QTRY_VERIFY(m_fixture->positionChanged.size() > 0 && m_fixture->positionChanged.last()[0].value<qint64>() > 100); - m_fixture->player.setPosition(900); + QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); + QTRY_VERIFY(player.mediaStatus() == QMediaPlayer::BufferedMedia + || player.mediaStatus() == QMediaPlayer::EndOfMedia); + + QCOMPARE(playbackStateChanged.size(), 1); + QCOMPARE(playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), + QMediaPlayer::PlayingState); + QVERIFY(mediaStatusChanged.size() > 0); + QCOMPARE(mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), + QMediaPlayer::BufferedMedia); + + positionChanged.clear(); + QTRY_VERIFY(player.position() > 100); + QTRY_VERIFY(positionChanged.size() > 0 && positionChanged.last()[0].value<qint64>() > 100); + player.setPosition(900); //wait up to 5 seconds for EOS - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); - QVERIFY(m_fixture->mediaStatusChanged.size() > 0); - QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia); - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(m_fixture->playbackStateChanged.size(), 2); - QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QVERIFY(mediaStatusChanged.size() > 0); + QCOMPARE(mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), + QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); + QCOMPARE(playbackStateChanged.size(), 2); + QCOMPARE(playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), + QMediaPlayer::StoppedState); - 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); - } + QCOMPARE_GT(bufferProgressChanged.size(), 1); + QCOMPARE(bufferProgressChanged.back().front(), 0.f); // 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()); + QCOMPARE(player.position(), player.duration()); + QTRY_VERIFY(positionChanged.size() > 0); + QTRY_COMPARE(positionChanged.last()[0].value<qint64>(), player.duration()); //after setPosition EndOfMedia status should be reset to Loaded - m_fixture->playbackStateChanged.clear(); - m_fixture->mediaStatusChanged.clear(); - m_fixture->player.setPosition(500); + playbackStateChanged.clear(); + mediaStatusChanged.clear(); + player.setPosition(500); //this transition can be async, so allow backend to perform it - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); - QCOMPARE(m_fixture->playbackStateChanged.size(), 0); - QTRY_VERIFY(m_fixture->mediaStatusChanged.size() > 0 && - m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::LoadedMedia); + QCOMPARE(playbackStateChanged.size(), 0); + QTRY_VERIFY(mediaStatusChanged.size() > 0 + && mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>() + == QMediaPlayer::LoadedMedia); - m_fixture->player.play(); - m_fixture->player.setPosition(900); + player.play(); + player.setPosition(900); //wait up to 5 seconds for EOS - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(m_fixture->player.position(), m_fixture->player.duration()); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); + QCOMPARE(player.position(), player.duration()); - m_fixture->playbackStateChanged.clear(); - m_fixture->mediaStatusChanged.clear(); - m_fixture->positionChanged.clear(); + playbackStateChanged.clear(); + mediaStatusChanged.clear(); + positionChanged.clear(); // pause() should reset position to beginning and status to Buffered - m_fixture->player.pause(); + player.pause(); - QTRY_COMPARE(m_fixture->player.position(), 0); - QTRY_VERIFY(m_fixture->positionChanged.size() > 0); - QTRY_COMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0); + QTRY_COMPARE(player.position(), 0); + QTRY_VERIFY(positionChanged.size() > 0); + QTRY_COMPARE(positionChanged.first()[0].value<qint64>(), 0); - QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PausedState); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); - QCOMPARE(m_fixture->playbackStateChanged.size(), 1); - QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PausedState); - QVERIFY(m_fixture->mediaStatusChanged.size() > 0); + QCOMPARE(playbackStateChanged.size(), 1); + QCOMPARE(playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), + QMediaPlayer::PausedState); + QVERIFY(mediaStatusChanged.size() > 0); QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia); } @@ -1551,6 +2227,44 @@ void tst_QMediaPlayerBackend::deleteLaterAtEOS() QVERIFY(player.isNull()); } +void tst_QMediaPlayerBackend::playToEOS_finishesWithEmptyFrame() +{ + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); + + QVector<bool> frameValidState; + TestVideoSink surface(false); + QObject::connect(&surface, &QVideoSink::videoFrameChanged, &surface, + [&](const QVideoFrame &frame) { + frameValidState.append(frame.isValid()); + }); + + QMediaPlayer player; + player.setVideoSink(&surface); + player.setSource(*m_localVideoFile3ColorsWithSound); + player.play(); + + // play to end + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QTRY_VERIFY(!frameValidState.isEmpty()); + + // ensure that we end with an empty frame + QTRY_VERIFY(!frameValidState.back()); + + // while all earlier frames are valid + frameValidState.pop_back(); + QVERIFY(std::all_of(frameValidState.begin(), frameValidState.end(), q20::identity{})); + + constexpr int framesInMedia = 77; + constexpr int expectedMaxFrameCount = framesInMedia + 1; + constexpr bool strictFrameCountValidation = false; + if constexpr (strictFrameCountValidation) { + QCOMPARE_EQ(surface.m_totalFrames, expectedMaxFrameCount); + } else { + // the backend may drop frames, so there's no reasonable lower limit + QCOMPARE_LE(surface.m_totalFrames, expectedMaxFrameCount); + } +} + void tst_QMediaPlayerBackend::volumeAcrossFiles_data() { QTest::addColumn<int>("volume"); @@ -1718,6 +2432,8 @@ void tst_QMediaPlayerBackend::seekPauseSeek() void tst_QMediaPlayerBackend::seekInStoppedState() { + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); @@ -1756,11 +2472,11 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_VERIFY(player.position() > position); + QTRY_COMPARE_GT(player.position(), position); QTest::qWait(100); // Check that it never played from the beginning - QVERIFY(player.position() > position); + QCOMPARE_GT(player.position(), position); for (int i = 0; i < positionSpy.size(); ++i) QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200)); @@ -1822,20 +2538,23 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.play(); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(player.mediaStatus() == QMediaPlayer::BufferedMedia + || player.mediaStatus() == QMediaPlayer::EndOfMedia); positionSpy.clear(); - QTRY_VERIFY(player.position() > (position - 200)); + QTRY_COMPARE_GT(player.position(), (position - 200)); QTest::qWait(500); // Check that it never played from the beginning - QVERIFY(player.position() > (position - 200)); + QCOMPARE_GT(player.position(), (position - 200)); for (int i = 0; i < positionSpy.size(); ++i) QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200)); } void tst_QMediaPlayerBackend::subsequentPlayback() { + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures with gstreamer"); + CHECK_SELECTED_URL(m_localCompressedSoundFile); QAudioOutput output; @@ -1849,11 +2568,11 @@ void tst_QMediaPlayerBackend::subsequentPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 10000); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); // Could differ by up to 1 compressed frame length QVERIFY(qAbs(player.position() - player.duration()) < 100); - QVERIFY(player.position() > 0); + QCOMPARE_GT(player.position(), 0); player.play(); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); @@ -1861,7 +2580,7 @@ void tst_QMediaPlayerBackend::subsequentPlayback() player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); // make sure position does not "jump" closer to the end of the file - QVERIFY(player.position() > 1000); + QCOMPARE_GT(player.position(), 1000); // try to seek back to zero player.setPosition(0); QTRY_COMPARE(player.position(), qint64(0)); @@ -1873,6 +2592,35 @@ void tst_QMediaPlayerBackend::subsequentPlayback() QCOMPARE_GT(player.position(), 1000); } +void tst_QMediaPlayerBackend::subsequentPlayback_playsForExpectedDuration() +{ + using namespace std::chrono_literals; + QSKIP_GSTREAMER("QTBUG-127346: subsequent playback finishes almost immediately"); + + CHECK_SELECTED_URL(m_localCompressedSoundFile); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*m_localCompressedSoundFile); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + player.setPosition(5000); + player.play(); + + QVERIFY(player.position() >= 5000); + + QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 10'000); + + QElapsedTimer timer; + timer.start(); + + // playback should take 7 seconds + player.play(); + QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 15'000); + std::chrono::nanoseconds duration = std::chrono::nanoseconds(timer.nsecsElapsed()); + QCOMPARE_GE(duration + 100ms, std::chrono::milliseconds(player.duration())); + QCOMPARE_LT(duration, std::chrono::seconds(12)); +} + void tst_QMediaPlayerBackend::multipleMediaPlayback() { CHECK_SELECTED_URL(m_localVideoFile); @@ -1894,7 +2642,8 @@ void tst_QMediaPlayerBackend::multipleMediaPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_VERIFY(player.position() > 0); + QVERIFY(player.isSeekable()); + QTRY_COMPARE_GT(player.position(), 0); QCOMPARE(player.source(), *m_localVideoFile); player.stop(); @@ -1910,7 +2659,7 @@ void tst_QMediaPlayerBackend::multipleMediaPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_VERIFY(player.position() > 0); + QTRY_COMPARE_GT(player.position(), 0); QCOMPARE(player.source(), *m_localVideoFile2); player.stop(); @@ -1922,11 +2671,14 @@ void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest() { CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); -#ifdef Q_OS_MACOS - if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") - QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To be " - "investigated."); -#endif + if (isCI()) { + if (isDarwinPlatform()) + QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To " + "be investigated."); + } + + QSKIP_GSTREAMER( + "playback rate changes are flushing the pipeline, so this test is not representative"); TestVideoSink surface(false); QAudioOutput output; @@ -1943,24 +2695,35 @@ void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest() QSignalSpy spy(&player, &QMediaPlayer::playbackStateChanged); - constexpr qint64 expectedVideoDuration = 3000; - constexpr int waitingInterval = 200; - constexpr qint64 maxDuration = expectedVideoDuration + 2000; - constexpr qint64 minDuration = expectedVideoDuration - 100; - constexpr qint64 maxFrameDelay = 2000; + using namespace std::chrono_literals; + using namespace std::chrono; + + constexpr milliseconds expectedVideoDuration = 3000ms; + constexpr milliseconds waitingInterval = 200ms; + constexpr milliseconds maxDuration = expectedVideoDuration + 2000ms; + constexpr milliseconds minDuration = expectedVideoDuration - 100ms; + constexpr milliseconds maxFrameDelay = 2000ms; surface.m_elapsedTimer.start(); - qint64 duration = 0; + nanoseconds duration = 0ns; - for (int i = 0; !spy.wait(waitingInterval); ++i) { - duration += waitingInterval * player.playbackRate(); + auto waitForPlaybackStateChange = [&]() { + QElapsedTimer timer; + timer.start(); + QScopeGuard addDuration([&]() { + duration += nanoseconds(static_cast<int64_t>(timer.nsecsElapsed() * player.playbackRate())); + }); + return spy.wait(waitingInterval.count()); + }; + + for (int i = 0; !waitForPlaybackStateChange(); ++i) { player.setPlaybackRate(0.5 * (i % 4 + 1)); QCOMPARE_LE(duration, maxDuration); - QVERIFY2(surface.m_elapsedTimer.elapsed() < maxFrameDelay, + QVERIFY2(surface.m_elapsedTimer.elapsed() < maxFrameDelay.count(), "If the delay is more than 2s, we consider the video playing is hanging."); /* Some debug code for windows. Use the code instead of the check above to debug the bug. @@ -1975,8 +2738,6 @@ void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest() }*/ } - duration += waitingInterval * player.playbackRate(); - QCOMPARE_GT(duration, minDuration); QCOMPARE(spy.size(), 1); @@ -1989,6 +2750,8 @@ void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest() void tst_QMediaPlayerBackend::multipleSeekStressTest() { + QSKIP_GSTREAMER("QTBUG-124005: spurious test failures with gstreamer"); + #ifdef Q_OS_ANDROID QSKIP("frame.toImage will return null image because of QTBUG-108446"); #endif @@ -2044,8 +2807,8 @@ void tst_QMediaPlayerBackend::multipleSeekStressTest() QTRY_VERIFY(positionSpy.size() >= 1); int setPosition = positionSpy.first().first().toInt(); - QCOMPARE_GT(setPosition, pos - 100); - QCOMPARE_LT(setPosition, pos + 100); + QCOMPARE_GT(setPosition, pos - 120); + QCOMPARE_LT(setPosition, pos + 120); }; constexpr qint64 posInterval = 10; @@ -2088,7 +2851,7 @@ void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderin QTest::addColumn<bool>("withAudio"); QTest::addColumn<int>("positionDeviationMs"); - QTest::newRow("Without audio") << false << 150; + QTest::newRow("Without audio") << false << 170; // set greater positionDeviationMs for case with audio due to possible synchronization. QTest::newRow("With audio") << true << 200; @@ -2096,6 +2859,8 @@ void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderin void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime() { + QSKIP_GSTREAMER("QTBUG-124005: timing issues"); + QFETCH(bool, withAudio); QFETCH(int, positionDeviationMs); @@ -2192,6 +2957,8 @@ void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderin void tst_QMediaPlayerBackend::surfaceTest() { + QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery"); + CHECK_SELECTED_URL(m_localVideoFile); // 25 fps video file @@ -2208,6 +2975,10 @@ void tst_QMediaPlayerBackend::surfaceTest() void tst_QMediaPlayerBackend::metadata() { + // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage + QMediaMetaData::Key thumbnailKey = + isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage; + CHECK_SELECTED_URL(m_localFileWithMetadata); m_fixture->player.setSource(*m_localFileWithMetadata); @@ -2219,7 +2990,7 @@ void tst_QMediaPlayerBackend::metadata() QCOMPARE(metadata.value(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist")); QCOMPARE(metadata.value(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum")); QCOMPARE(metadata.value(QMediaMetaData::Duration), QVariant(7704)); - QVERIFY(!metadata.value(QMediaMetaData::ThumbnailImage).value<QImage>().isNull()); + QVERIFY(!metadata.value(thumbnailKey).value<QImage>().isNull()); m_fixture->clearSpies(); m_fixture->player.setSource(QUrl()); @@ -2302,6 +3073,8 @@ void tst_QMediaPlayerBackend::playerStateAtEOS() void tst_QMediaPlayerBackend::playFromBuffer() { + QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); @@ -2316,6 +3089,38 @@ void tst_QMediaPlayerBackend::playFromBuffer() QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames))); } +void tst_QMediaPlayerBackend::playFromSequentialStream() +{ + using namespace std::chrono_literals; + CHECK_SELECTED_URL(m_localVideoFile); + + TestVideoSink surface(false); + QMediaPlayer player; + player.setVideoOutput(&surface); + QSequentialFileAdaptor file(u":"_s + m_localVideoFile->toEncoded(QUrl::RemoveScheme)); + + QSKIP_GSTREAMER("QGstAppSource misbehaves on sequential streams"); + + QObject::connect(&player, &QMediaPlayer::errorOccurred, &player, [] { + QFAIL("playFromSequentialStream: error occurred"); + }); + + QVERIFY(file.open(QIODevice::ReadOnly)); + + player.setSourceDevice(&file, *m_localVideoFile); + QCOMPARE(player.isSeekable(), false); + player.play(); + QTRY_COMPARE_GE(player.position(), 1000); + + QSKIP_FFMPEG("QTBUG-128802: media is not seekable, but isSeekable return `true`"); + QCOMPARE(player.isSeekable(), false); + + player.setPosition(0); + QCOMPARE_GE(player.position(), 1000); + QTest::qWait(200); + QTRY_COMPARE_GE(player.position(), 1000); +} + void tst_QMediaPlayerBackend::audioVideoAvailable() { CHECK_SELECTED_URL(m_localVideoFile); @@ -2339,6 +3144,46 @@ void tst_QMediaPlayerBackend::audioVideoAvailable() QCOMPARE(hasAudioSpy.size(), 2); } +void tst_QMediaPlayerBackend::audioVideoAvailable_updatedOnNewMedia() +{ + CHECK_SELECTED_URL(m_localVideoFile); + CHECK_SELECTED_URL(m_localWavFile); + + TestVideoSink surface(false); + QAudioOutput output; + QMediaPlayer player; + QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged); + QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged); + player.setVideoOutput(&surface); + player.setAudioOutput(&output); + player.setSource(*m_localVideoFile); + QTRY_VERIFY(player.hasVideo()); + QTRY_VERIFY(player.hasAudio()); + QCOMPARE(hasVideoSpy.size(), 1); + QCOMPARE(hasAudioSpy.size(), 1); + + hasVideoSpy.clear(); + hasAudioSpy.clear(); + + player.setSource(*m_localWavFile); + + auto expectedHasVideoSignals = SignalList{ + { false }, + }; + QTRY_COMPARE(hasVideoSpy, expectedHasVideoSignals); + + if (isGStreamerPlatform()) { + // GStreamer unsets hasAudio/hasVideo on new URIs + auto expectedHasAudioSignals = SignalList{ + { false }, + { true }, + }; + QTRY_COMPARE(hasAudioSpy, expectedHasAudioSignals); + } else { + QCOMPARE(hasAudioSpy.size(), 0); + } +} + void tst_QMediaPlayerBackend::isSeekable() { CHECK_SELECTED_URL(m_localVideoFile); @@ -2363,15 +3208,15 @@ void tst_QMediaPlayerBackend::positionAfterSeek() QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); player.pause(); player.setPosition(500); - QTRY_VERIFY(player.position() == 500); + QTRY_COMPARE(player.position(), 500); player.setPosition(700); QVERIFY(player.position() != 0); - QTRY_VERIFY(player.position() == 700); + QTRY_COMPARE(player.position(), 700); player.play(); - QTRY_VERIFY(player.position() > 700); + QTRY_COMPARE_GT(player.position(), 700); player.setPosition(200); QVERIFY(player.position() != 0); - QTRY_VERIFY(player.position() < 700); + QTRY_COMPARE_LT(player.position(), 700); } void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution_data() @@ -2401,9 +3246,12 @@ void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution() // Act player.pause(); - - if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") - QEXPECT_FAIL("av1", "QTBUG-119711: AV1 decoding requires HW support", Abort); +#ifndef Q_OS_ANDROID + // isCI() does not work on Android (variable is set on host instead of device where tests are run) + if (isCI() && isFFMPEGPlatform()) +#endif + QEXPECT_FAIL("av1", "QTBUG-119711: AV1 decoding requires HW support in the FFMPEG backend", + Abort); QTRY_COMPARE(surface.m_totalFrames, 1); @@ -2427,17 +3275,32 @@ void tst_QMediaPlayerBackend::position() player.play(); player.setPosition(1000); - QVERIFY(player.position() > 950); - QVERIFY(player.position() < 1050); - QTRY_VERIFY(player.position() > 1050); + QCOMPARE_GT(player.position(), 950); + QCOMPARE_LT(player.position(), 1050); + QTRY_COMPARE_GT(player.position(), 1050); player.pause(); player.setPosition(500); - QVERIFY(player.position() > 450); - QVERIFY(player.position() < 550); + QCOMPARE_GT(player.position(), 450); + QCOMPARE_LT(player.position(), 550); QTest::qWait(200); - QVERIFY(player.position() > 450); - QVERIFY(player.position() < 550); + QCOMPARE_GT(player.position(), 450); + QCOMPARE_LT(player.position(), 550); + + using namespace std::chrono; + using namespace std::chrono_literals; + + // colors.mp4 is 25fps + // ffmpeg will round down the start time of the frame fo 480ms, while gstreamer will set a + // reduced frame time + const QVideoFrame &lastFrame = surface.m_frameList.back(); + if (isGStreamerPlatform()) { + QCOMPARE_EQ(microseconds(lastFrame.startTime()), 500ms); + QCOMPARE_EQ(microseconds(lastFrame.endTime()), 520ms); + } else { + QCOMPARE_EQ(microseconds(lastFrame.startTime()), 480ms); + QCOMPARE_EQ(microseconds(lastFrame.endTime()), 520ms); + } } void tst_QMediaPlayerBackend::durationDetectionIssues_data() @@ -2450,14 +3313,17 @@ void tst_QMediaPlayerBackend::durationDetectionIssues_data() QTest::addColumn<QVariant>("expectedAudioTrackDuration"); // clang-format off + if (!isGStreamerPlatform()) { + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); - QTest::newRow("stream-duration-in-metadata") + QTest::newRow("stream-duration-in-metadata") << QString{ "qrc:/testdata/duration_issues.webm" } << 400ll // Total media duration << 1 // Number of video tracks in file << 400ll // Video stream duration << 0 // Number of audio tracks in file << QVariant{}; // Audio stream duration (unused) + } QTest::newRow("no-stream-duration-in-metadata") << QString{ "qrc:/testdata/nokia-tune.mkv" } @@ -2472,6 +3338,9 @@ void tst_QMediaPlayerBackend::durationDetectionIssues_data() void tst_QMediaPlayerBackend::durationDetectionIssues() { + if (isGStreamerPlatform() && isCI()) + QSKIP("QTBUG-124005: Fails with gstreamer on CI"); + QFETCH(QString, mediaFile); QFETCH(qint64, expectedDuration); QFETCH(int, expectedVideoTrackCount); @@ -2526,9 +3395,15 @@ struct LoopIteration { static std::vector<LoopIteration> loopIterations(const QSignalSpy &positionSpy) { std::vector<LoopIteration> result; + + static constexpr bool dumpPositions = false; + static constexpr bool dumpLoops = false; + // Loops through all positions emitted by QMediaPlayer::positionChanged for (auto ¶ms : positionSpy) { const auto pos = params.front().value<qint64>(); + if constexpr (dumpPositions) + qDebug() << pos; // Adds new LoopIteration struct to result if position is lower than previous position if (result.empty() || pos < result.back().endPos) { @@ -2540,6 +3415,11 @@ static std::vector<LoopIteration> loopIterations(const QSignalSpy &positionSpy) result.back().endPos = pos; } } + + if constexpr (dumpLoops) + for (auto &element : result) + qDebug() << element.startPos << element.endPos << element.posCount; + return result; } @@ -2547,6 +3427,11 @@ void tst_QMediaPlayerBackend::finiteLoops() { CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); + QFETCH(bool, pauseDuringPlayback); + QFETCH(bool, rateChange); + if (pauseDuringPlayback) + QSKIP_GSTREAMER("Spurious test failures on CI"); + #ifdef Q_OS_MACOS if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be " @@ -2558,52 +3443,70 @@ void tst_QMediaPlayerBackend::finiteLoops() QCOMPARE(m_fixture->player.loops(), 3); m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound); - m_fixture->player.setPlaybackRate(5); + if (rateChange) + m_fixture->player.setPlaybackRate(5); QCOMPARE(m_fixture->player.loops(), 3); m_fixture->player.play(); m_fixture->surface.waitForFrame(); - // check pause doesn't affect looping - { + if (pauseDuringPlayback) { + // check pause doesn't affect looping QTest::qWait(static_cast<int>(m_fixture->player.duration() * 3 * 0.6 /*relative pos*/ / m_fixture->player.playbackRate())); m_fixture->player.pause(); m_fixture->player.play(); } - QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); + QTRY_COMPARE_WITH_TIMEOUT(m_fixture->player.playbackState(), QMediaPlayer::StoppedState, 15'000); // Check for expected number of loop iterations and startPos, endPos and posCount per iteration std::vector<LoopIteration> iterations = loopIterations(m_fixture->positionChanged); QCOMPARE(iterations.size(), 3u); QCOMPARE_GT(iterations[0].startPos, 0); QCOMPARE(iterations[0].endPos, m_fixture->player.duration()); - QCOMPARE_GT(iterations[0].posCount, 10); QCOMPARE(iterations[1].startPos, 0); QCOMPARE(iterations[1].endPos, m_fixture->player.duration()); - QCOMPARE_GT(iterations[1].posCount, 10); QCOMPARE(iterations[2].startPos, 0); QCOMPARE(iterations[2].endPos, m_fixture->player.duration()); - QCOMPARE_GT(iterations[2].posCount, 10); + if (isFFMPEGPlatform()) { + QCOMPARE_GT(iterations[0].posCount, 10); + QCOMPARE_GT(iterations[1].posCount, 10); + QCOMPARE_GT(iterations[2].posCount, 10); + } QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); + if constexpr (QT_CONFIG(pulseaudio)) + QSKIP_GSTREAMER( + "StoppedState is never reached, with pulseaudio. Possibly related to QTBUG-124372"); + // Check that loop counter is reset when playback is restarted. - { - m_fixture->positionChanged.clear(); - m_fixture->player.play(); - m_fixture->player.setPlaybackRate(10); - m_fixture->surface.waitForFrame(); + m_fixture->positionChanged.clear(); + m_fixture->player.play(); + m_fixture->player.setPlaybackRate(10); + m_fixture->surface.waitForFrame(); - QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(loopIterations(m_fixture->positionChanged).size(), 3u); - QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); - } + QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); + QCOMPARE(loopIterations(m_fixture->positionChanged).size(), 3u); + QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); +} + +void tst_QMediaPlayerBackend::finiteLoops_data() +{ + QTest::addColumn<bool>("pauseDuringPlayback"); + QTest::addColumn<bool>("rateChange"); + + QTest::newRow("No pause, default rate") << false << false; + QTest::newRow("No pause, fast rate") << false << true; + QTest::newRow("Pause, default rate") << true << false; + QTest::newRow("Pause, fast rate") << true << true; } void tst_QMediaPlayerBackend::infiniteLoops() { + QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend"); + CHECK_SELECTED_URL(m_localVideoFile2); #ifdef Q_OS_MACOS @@ -2638,7 +3541,8 @@ void tst_QMediaPlayerBackend::infiniteLoops() QCOMPARE(iterations.front().endPos, m_fixture->player.duration()); } - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); m_fixture->player.stop(); // QMediaPlayer::stop stops whether or not looping is infinite QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); @@ -2653,6 +3557,8 @@ void tst_QMediaPlayerBackend::infiniteLoops() void tst_QMediaPlayerBackend::seekOnLoops() { + QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend"); + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); #ifdef Q_OS_MACOS @@ -2704,6 +3610,8 @@ void tst_QMediaPlayerBackend::seekOnLoops() void tst_QMediaPlayerBackend::changeLoopsOnTheFly() { + QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend"); + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); #ifdef Q_OS_MACOS @@ -2777,8 +3685,10 @@ void tst_QMediaPlayerBackend::seekAfterLoopReset() QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia); } -void tst_QMediaPlayerBackend::changeVideoOutputNoFramesLost() +void tst_QMediaPlayerBackend::setVideoOutput_whilePlaying_doesNotDropFrames() { + QSKIP_GSTREAMER("QTBUG-124005: gstreamer will lose frames, possibly due to buffering"); + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); QVideoSink sinks[4]; @@ -2795,32 +3705,37 @@ void tst_QMediaPlayerBackend::changeVideoOutputNoFramesLost() player.setVideoOutput(&sinks[0]); player.setSource(*m_localVideoFile3ColorsWithSound); player.play(); - QTRY_VERIFY(!player.isPlaying()); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); player.setPlaybackRate(4); player.setVideoOutput(&sinks[1]); player.play(); - QTRY_VERIFY(framesCount[1] >= framesCount[0] / 4); + QTRY_COMPARE_GE(framesCount[1], framesCount[0] / 4); player.setVideoOutput(&sinks[2]); const int savedFrameNumber1 = framesCount[1]; - QTRY_VERIFY(framesCount[2] >= (framesCount[0] - savedFrameNumber1) / 2); + QTRY_COMPARE_GE(framesCount[2], (framesCount[0] - savedFrameNumber1) / 2); player.setVideoOutput(&sinks[3]); const int savedFrameNumber2 = framesCount[2]; - QTRY_VERIFY(!player.isPlaying()); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); // check if no frames sent to old sinks QCOMPARE(framesCount[1], savedFrameNumber1); QCOMPARE(framesCount[2], savedFrameNumber2); + constexpr int videoOutputChanges = 2; // video frame goes from the previous sink. + // no frames lost - QCOMPARE(framesCount[1] + framesCount[2] + framesCount[3], framesCount[0]); + QCOMPARE(framesCount[1] + framesCount[2] + framesCount[3], framesCount[0] + videoOutputChanges); } void tst_QMediaPlayerBackend::cleanSinkAndNoMoreFramesAfterStop() { + QSKIP_GSTREAMER( + "QTBUG-124005: spurious failures on gstreamer, probably due to asynchronous play()"); + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); QVideoSink sink; @@ -2852,6 +3767,9 @@ void tst_QMediaPlayerBackend::cleanSinkAndNoMoreFramesAfterStop() QTest::qWait(30); + if (isGStreamerPlatform()) + continue; // QTBUG-124005: stop() is asynchronous in gstreamer + // check if nothing changed after short waiting QCOMPARE(framesCount, 0); } @@ -2943,7 +3861,8 @@ void tst_QMediaPlayerBackend::nonAsciiFileName() m_fixture->player.setSource(temporaryFile->fileName()); m_fixture->player.play(); - QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); QCOMPARE(m_fixture->errorOccurred.size(), 0); } @@ -2975,8 +3894,8 @@ void tst_QMediaPlayerBackend::setMedia_setsVideoSinkSize_beforePlaying() QCOMPARE(spy2.size(), 1); } -std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtspStreamProcess(QString fileName, - QString outputUrl) +std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtpStreamProcess(QString fileName, + QString sdpUrl) { Q_ASSERT(!m_vlcCommand.isEmpty()); @@ -2985,22 +3904,22 @@ std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtspStreamProcess(QStri fileName.replace('/', '\\'); #endif - // clang-format off - QStringList vlcParams = - { - "-vvv", fileName, - "--sout", QLatin1String("#rtp{sdp=%1}").arg(outputUrl), - "--intf", "dummy" - }; - // clang-format on + QStringList vlcParams = { "-vvv", fileName, + "--sout", QStringLiteral("#rtp{dst=localhost,sdp=%1}").arg(sdpUrl), + "--intf", "dummy" }; process->start(m_vlcCommand, vlcParams); if (!process->waitForStarted()) return nullptr; - // rtsp stream might be with started some delay after the vlc process starts. - // Ideally, we should wait for open connections, it requires some extra work + QNetwork dependency. - QTest::qWait(500); + // rtp stream might be with started some delay after the vlc process starts. + // Ideally, we should wait for open connections, it requires some extra work + QNetwork + // dependency. + int timeout = 500; +#ifdef Q_OS_MACOS + timeout = 2000; +#endif + QTest::qWait(timeout); return process; } @@ -3037,6 +3956,9 @@ void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrien void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata() { + if (isGStreamerPlatform() && isCI()) + QSKIP("QTBUG-124005: Fails with gstreamer on CI"); + // This test uses 4 video files with a 2x2 color matrix consisting of // red (upper left), blue (lower left), yellow (lower right) and green (upper right). // The files are identical, except that three of them contain @@ -3075,16 +3997,637 @@ void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrien QVideoFrame videoFrame = m_fixture->surface.waitForFrame(); QVERIFY(videoFrame.isValid()); QCOMPARE(QtVideo::Rotation(videoFrame.rotationAngle()), expectedRotationAngle); +#ifdef Q_OS_ANDROID + QSKIP("frame.toImage will return null image because of QTBUG-108446"); +#endif QImage image = videoFrame.toImage(); QVERIFY(!image.isNull()); QRgb upperLeftColor = image.pixel(5, 5); QCOMPARE_LT(colorDifference(upperLeftColor, expectedColor), 0.005); + QSKIP_GSTREAMER("QTBUG-124005: surface.videoSize() not updated with rotation"); + // Compare videoSize of the output video sink with the expected value after getting a frame QCOMPARE(m_fixture->surface.videoSize(), videoSize); } +void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setAudioOutput(&audioOut); + player.setSource(*m_15sVideo); + + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + player.pause(); + break; + case QMediaPlayer::PlayingState: + QSKIP_GSTREAMER("QTBUG-124005: Test failure with the gstreamer backend"); + player.play(); + break; + } + + // set video output + QTest::qWait(1'000); + player.setVideoOutput(&surface); + + if (playbackState == QMediaPlayer::PlayingState) { + QVideoFrame frame = surface.waitForFrame(); + QCOMPARE(frame.size(), QSize(20, 20)); + } + + // unset video output + QTest::qWait(1'000); + player.setVideoOutput(nullptr); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::setVideoOutput_whilePaused_updatesNewSink() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + + TestVideoSink surface1(/*storeFrames=*/false); + TestVideoSink surface2(/*storeFrames=*/false); + + QAudioOutput audioOut; + + QMediaPlayer player; + player.setAudioOutput(&audioOut); + player.setVideoOutput(&surface1); + player.setSource(*m_15sVideo); + + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + player.play(); + QTRY_COMPARE_GT(player.position(), 100); + +#ifndef Q_OS_ANDROID // fails with android/ffmpeg + QCOMPARE(surface1.videoFrame().toImage().pixelColor(10, 10), QColorConstants::White); +#endif + + player.pause(); + + QTest::qWait(100/*ms*/); // pause() may be asynchronous + + player.setVideoOutput(&surface2); + + // new frame is delivered asynchronously + QTRY_COMPARE_EQ(surface2.m_totalFrames, 1); + +#ifndef Q_OS_ANDROID // fails with android/ffmpeg + QCOMPARE(surface2.videoFrame().toImage().pixelColor(10, 10), QColorConstants::White); +#endif + + if (isFFMPEGPlatform()) + QCOMPARE(surface1.videoFrame(), surface2.videoFrame()); + else if (isGStreamerPlatform()) + // timestamps differ, so we only compare pixels + QCOMPARE(surface1.videoFrame().toImage(), surface2.videoFrame().toImage()); +} + +void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback() +{ + QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend"); + + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setVideoOutput(&surface); + player.setSource(*m_15sVideo); + + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + player.pause(); + break; + case QMediaPlayer::PlayingState: + player.play(); + break; + } + + // set audio output + QTest::qWait(1'000); + player.setAudioOutput(&audioOut); + + // unset audio output + QTest::qWait(1'000); + player.setAudioOutput(nullptr); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback() +{ + using namespace std::chrono_literals; + + const QList<QAudioDevice> outputDevices = QMediaDevices::audioOutputs(); + + if (outputDevices.size() < 2) + QSKIP("swapAudioDevice_doesNotStopPlayback requires two audio output devices"); + + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); + + CHECK_SELECTED_URL(m_15sVideo); + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setVideoOutput(&surface); + player.setAudioOutput(&audioOut); + player.setSource(*m_15sVideo); + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + player.pause(); + break; + case QMediaPlayer::PlayingState: + player.play(); + break; + } + + // swap output device + QTest::qWait(1'000); + audioOut.setDevice(outputDevices[0]); + + QTest::qWait(1'000); + audioOut.setDevice(outputDevices[1]); + + QTest::qWait(1'000); + audioOut.setDevice(outputDevices[0]); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::play_readsSubtitle() +{ + using namespace std::chrono_literals; + CHECK_SELECTED_URL(m_subtitleVideo); + + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(*m_subtitleVideo); + QTRY_COMPARE(player.subtitleTracks().size(), 1); + if (!isGStreamerPlatform()) { + // QTBUG-124005: spurious failures with gstreamer + QCOMPARE_EQ(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 3000); + } + + player.setActiveSubtitleTrack(0); + + if (!isGStreamerPlatform()) // FIXME: spurious deadlocks + player.setPlaybackRate(5.f); + + player.play(); + + QSKIP_GSTREAMER("QTBUG-124005: gstreamer sometimes reports more than 4 subtitle events"); + + QStringList expectedSubtitleList = { + u"Hello"_s, + u""_s, + u"World"_s, + u""_s, + }; + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitleList); +} + +void tst_QMediaPlayerBackend::multiTrack_validateMetadata() +{ + CHECK_SELECTED_URL(m_multitrackVideo); + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.videoTracks().size(), 2); + QTRY_COMPARE(player.audioTracks().size(), 2); + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + QSKIP_GSTREAMER("GStreamer does not provide correct track order"); + + QCOMPARE(player.videoTracks()[0][QMediaMetaData::Title], u"One"_s); + QCOMPARE(player.videoTracks()[1][QMediaMetaData::Title], u"Two"_s); + + QCOMPARE(player.audioTracks()[0][QMediaMetaData::Language], QLocale::Language::English); + QCOMPARE(player.audioTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish); + QCOMPARE(player.subtitleTracks()[0][QMediaMetaData::Language], QLocale::Language::English); + QCOMPARE(player.subtitleTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish); +} + +void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack() +{ + using namespace std::chrono_literals; + CHECK_SELECTED_URL(m_multitrackVideo); + + QFETCH(int, track); + QFETCH(const QStringList, expectedSubtitles); + + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + if (track != -1) { + if (isGStreamerPlatform()) + QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 4000); + if (isFFMPEGPlatform()) + QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 15046); + } + + if (isGStreamerPlatform()) { + bool swapTracks = + player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish; + + if (swapTracks && track == 1) + track = 0; + if (swapTracks && track == 0) + track = 1; + } + + player.setActiveSubtitleTrack(track); + if (!isGStreamerPlatform()) + player.setPlaybackRate(5.f); + player.play(); + + if (expectedSubtitles.isEmpty()) + QTRY_COMPARE_GT(player.position(), 2000); + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles); +} + +void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack_data() +{ + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + QTest::addColumn<int>("track"); + QTest::addColumn<QStringList>("expectedSubtitles"); + + QTest::addRow("track 0") << 0 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 1"_s, + u""_s, + }; + QTest::addRow("track 1") << 1 + << QStringList{ + u"1s track 2"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + + QTest::addRow("no subtitles") << -1 << QStringList{}; +} + +void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles() +{ + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + QFETCH(const QUrl, media); + QFETCH(const int, positionToSwapTrack); + QFETCH(const QLatin1String, testMode); + QFETCH(const QStringList, expectedSubtitles); + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(media); + + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + int track0 = 0; + int track1 = 1; + if (isGStreamerPlatform()) { + bool swapTracks = + player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish; + + if (swapTracks) { + track1 = 0; + track0 = 1; + } + } + + player.setActiveSubtitleTrack(track0); + + player.play(); + QTRY_COMPARE_GT(player.position(), positionToSwapTrack); + + if (testMode == "setWhilePaused"_L1) { + player.pause(); + player.setActiveSubtitleTrack(track1); + player.play(); + } else if (testMode == "setWhilePlaying"_L1) { + player.setActiveSubtitleTrack(track1); + } else { + QFAIL("should not reach"); + } + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles); +} + +void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles_data() +{ + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + QTest::addColumn<QUrl>("media"); + QTest::addColumn<QLatin1String>("testMode"); + QTest::addColumn<int>("positionToSwapTrack"); + QTest::addColumn<QStringList>("expectedSubtitles"); + + QTest::addRow("while paused") << *m_multitrackVideo << "setWhilePaused"_L1 << 2100 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + QTest::addRow("while playing") << *m_multitrackVideo << "setWhilePlaying"_L1 << 2100 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + + QTest::addRow("while paused, subtitles start at zero") + << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePaused"_L1 << 1100 + << QStringList{ + u"0s track 1"_s, + u""_s, + u"2s track 2"_s, + u""_s, + }; + QTest::addRow("while playing, subtitles start at zero") + << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePlaying"_L1 << 1100 + << QStringList{ + u"0s track 1"_s, + u""_s, + u"2s track 2"_s, + u""_s, + }; +} + +void tst_QMediaPlayerBackend::setActiveVideoTrack_switchesVideoTrack() +{ + using namespace std::chrono_literals; + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + TestVideoSink &sink = m_fixture->surface; + sink.setStoreFrames(); + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.videoTracks().size(), 2); + + int track0 = 0; + int track1 = 1; + if (isGStreamerPlatform()) { + bool swapTracks = player.subtitleTracks()[0][QMediaMetaData::Title] != u"One"_s; + + if (swapTracks) { + track0 = 1; + track1 = 0; + } + } + + player.setActiveVideoTrack(track0); + player.play(); + + sink.waitForFrame(); + + QTest::qWait(500); + sink.waitForFrame(); + QCOMPARE(QColor{ sink.m_frameList.back().toImage().pixel(10, 10) }, QColor(0xff, 0x80, 0x7f)); + + player.setActiveVideoTrack(track1); + + QTest::qWait(500); + sink.waitForFrame(); + QCOMPARE(QColor{ sink.m_frameList.back().toImage().pixel(10, 10) }, QColor(0x80, 0x80, 0xff)); +} + +void tst_QMediaPlayerBackend::disablingAllTracks_doesNotStopPlayback() +{ + QSKIP_GSTREAMER("position does not advance in GStreamer"); + + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + // CAVEAT: we cannot set active tracks before tracksChanged is emitted + QTRY_COMPARE(player.videoTracks().size(), 2); + + player.setActiveVideoTrack(-1); + player.setActiveAudioTrack(-1); + + player.play(); + QTRY_COMPARE_GT(player.position(), 1000); + + QCOMPARE(m_fixture->surface.m_totalFrames, 0); +} + +void tst_QMediaPlayerBackend::disablingAllTracks_beforeTracksChanged_doesNotStopPlayback() +{ + QSKIP_GSTREAMER("position does not advance in GStreamer"); + QSKIP_FFMPEG("setActiveXXXTrack(-1) only works after tracksChanged"); + + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + player.setActiveVideoTrack(-1); + player.setActiveAudioTrack(-1); + + player.play(); + QTRY_COMPARE_GT(player.position(), 1000); + + QCOMPARE(m_fixture->surface.m_totalFrames, 0); +} + +void tst_QMediaPlayerBackend::makeStressTestCases() +{ + QTest::addColumn<MaybeUrl>("media"); + QTest::addColumn<bool>("play"); + + QTest::newRow("no media") << MaybeUrl{ unexpect } << false; + QTest::newRow("audio, not playing") << m_localWavFile << false; + QTest::newRow("audio, playing") << m_localWavFile << true; + QTest::newRow("video, not playing") << m_localVideoFile << false; + QTest::newRow("video, playing") << m_localVideoFile << true; +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown() +{ +#ifdef Q_OS_MACOS + QSKIP_FFMPEG("QTBUG-127137: Crashes on CI"); +#endif + + QFETCH(MaybeUrl, media); + QFETCH(bool, play); + QRandomGenerator rng; + + for (int i = 0; i < 50; i++) { + QMediaPlayer player; + QAudioOutput output; + TestVideoSink videoSink; + + player.setAudioOutput(&output); + player.setVideoOutput(&videoSink); + + if (media) { + player.setSource(*media); + if (play) { + player.play(); + QTRY_COMPARE_GT(player.position(), 10); + } + } + QTest::qWait(rng.bounded(200)); + } +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown_data() +{ + makeStressTestCases(); +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown_keepAudioOutput() +{ + QFETCH(MaybeUrl, media); + QFETCH(bool, play); + QRandomGenerator rng; + + QAudioOutput output; + + for (int i = 0; i < 50; i++) { + QMediaPlayer player; + TestVideoSink videoSink; + + player.setAudioOutput(&output); + player.setVideoOutput(&videoSink); + + if (media) { + player.setSource(*media); + if (play) { + player.play(); + QTRY_COMPARE_GT(player.position(), 10); + } + } + QTest::qWait(rng.bounded(200)); + } +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown_keepAudioOutput_data() +{ + makeStressTestCases(); +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown_keepVideoOutput() +{ + QFETCH(MaybeUrl, media); + QFETCH(bool, play); + QRandomGenerator rng; + + TestVideoSink videoSink; + + for (int i = 0; i < 50; i++) { + QMediaPlayer player; + QAudioOutput output; + + player.setAudioOutput(&output); + player.setVideoOutput(&videoSink); + + if (media) { + player.setSource(*media); + if (play) { + player.play(); + QTRY_COMPARE_GT(player.position(), 10); + } + } + QTest::qWait(rng.bounded(200)); + } +} + +void tst_QMediaPlayerBackend::stressTest_setupAndTeardown_keepVideoOutput_data() +{ + makeStressTestCases(); +} + QTEST_MAIN(tst_QMediaPlayerBackend) + #include "tst_qmediaplayerbackend.moc" diff --git a/tests/auto/integration/qsoundeffect/CMakeLists.txt b/tests/auto/integration/qsoundeffect/CMakeLists.txt index d3760e816..d403d9514 100644 --- a/tests/auto/integration/qsoundeffect/CMakeLists.txt +++ b/tests/auto/integration/qsoundeffect/CMakeLists.txt @@ -7,37 +7,14 @@ ## tst_qsoundeffect Test: ##################################################################### -# Collect test data -list(APPEND test_data "test.wav") - qt_internal_add_test(tst_qsoundeffect SOURCES tst_qsoundeffect.cpp LIBRARIES Qt::Gui Qt::MultimediaPrivate - TESTDATA ${test_data} -) - -# Resources: -set(resources_resource_files - "test.wav" - "test_corrupted.wav" - "test_tone.wav" -) - -qt_internal_add_resource(tst_qsoundeffect "resources" - PREFIX - "/" - FILES - ${resources_resource_files} -) - - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qsoundeffect CONDITION UNIX AND NOT APPLE AND NOT QT_FEATURE_pulseaudio - DEFINES - QT_MULTIMEDIA_QMEDIAPLAYER + TESTDATA + "test.wav" + "test_corrupted.wav" + "test_tone.wav" ) diff --git a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp index 668dbbbb5..207db34fa 100644 --- a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp +++ b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp @@ -234,6 +234,7 @@ void tst_QSoundEffect::testPlaying() //invalid source sound->setSource(QUrl((QLatin1String("invalid source")))); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Error decoding source.*")); QTestEventLoop::instance().enterLoop(1); sound->play(); QTestEventLoop::instance().enterLoop(1); @@ -262,6 +263,7 @@ void tst_QSoundEffect::testStatus() sound->setLoopCount(QSoundEffect::Infinite); sound->setSource(QUrl(QLatin1String("invalid source"))); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Error decoding source.*")); QTestEventLoop::instance().enterLoop(1); QCOMPARE(sound->status(), QSoundEffect::Error); } @@ -374,8 +376,14 @@ void tst_QSoundEffect::testSupportedMimeTypes() void tst_QSoundEffect::testCorruptFile() { + using namespace Qt::Literals; + auto expectedMessagePattern = + QRegularExpression(uR"(^QSoundEffect\(qaudio\): Error decoding source .*$)"_s); + for (int i = 0; i < 10; i++) { QSignalSpy statusSpy(sound, &QSoundEffect::statusChanged); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, expectedMessagePattern); + sound->setSource(urlCorrupted); QVERIFY(!sound->isPlaying()); QVERIFY(sound->status() == QSoundEffect::Loading || sound->status() == QSoundEffect::Error); diff --git a/tests/auto/integration/qvideoframebackend/CMakeLists.txt b/tests/auto/integration/qvideoframebackend/CMakeLists.txt index 8c129fa97..e4fa79201 100644 --- a/tests/auto/integration/qvideoframebackend/CMakeLists.txt +++ b/tests/auto/integration/qvideoframebackend/CMakeLists.txt @@ -19,14 +19,8 @@ qt_internal_add_test(tst_qvideoframebackend LIBRARIES Qt::Gui Qt::MultimediaPrivate + BUILTIN_TESTDATA TESTDATA ${testdata_resource_files} INCLUDE_DIRECTORIES ../shared/ ) - -qt_internal_add_resource(tst_qvideoframebackend "testdata" - PREFIX - "/" - FILES - ${testdata_resource_files} -) diff --git a/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp index c08ad1088..accc0cc33 100644 --- a/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp +++ b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp @@ -7,6 +7,7 @@ #include <qdebug.h> #include "mediafileselector.h" +#include "mediabackendutils.h" #include "testvideosink.h" #include "private/qvideotexturehelper_p.h" #include "private/qvideowindow_p.h" @@ -81,6 +82,11 @@ void tst_QVideoFrameBackend::addMediaPlayerFrameTestData(F &&f) return; } + if (isGStreamerPlatform()) { + qWarning() << "createMediaPlayerFrame spuriously fails with gstreamer"; + return; + } + f(); } diff --git a/tests/auto/integration/shared/mediabackendutils.h b/tests/auto/integration/shared/mediabackendutils.h index 3279f7044..a8bb05e3a 100644 --- a/tests/auto/integration/shared/mediabackendutils.h +++ b/tests/auto/integration/shared/mediabackendutils.h @@ -12,6 +12,11 @@ inline bool isGStreamerPlatform() return QPlatformMediaIntegration::instance()->name() == "gstreamer"; } +inline bool isQNXPlatform() +{ + return QPlatformMediaIntegration::instance()->name() == "qnx"; +} + inline bool isDarwinPlatform() { return QPlatformMediaIntegration::instance()->name() == "darwin"; @@ -32,6 +37,11 @@ inline bool isWindowsPlatform() return QPlatformMediaIntegration::instance()->name() == "windows"; } +inline bool isCI() +{ + return qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci"; +} + #define QSKIP_GSTREAMER(message) \ do { \ if (isGStreamerPlatform()) \ diff --git a/tests/auto/integration/shared/mediafileselector.h b/tests/auto/integration/shared/mediafileselector.h index 1ebbef222..36f84d263 100644 --- a/tests/auto/integration/shared/mediafileselector.h +++ b/tests/auto/integration/shared/mediafileselector.h @@ -107,10 +107,19 @@ private: player.play(); const auto waitingFinished = QTest::qWaitFor([&]() { - const auto status = player.mediaStatus(); - return status == QMediaPlayer::BufferedMedia || status == QMediaPlayer::EndOfMedia - || status == QMediaPlayer::InvalidMedia - || player.error() != QMediaPlayer::NoError; + if (player.error() != QMediaPlayer::NoError) + return true; + + switch (player.mediaStatus()) { + case QMediaPlayer::BufferingMedia: + case QMediaPlayer::BufferedMedia: + case QMediaPlayer::EndOfMedia: + case QMediaPlayer::InvalidMedia: + return true; + + default: + return false; + } }); auto enumValueToString = [](auto enumValue) { diff --git a/tests/auto/shared/qscopedenvironmentvariable.h b/tests/auto/shared/qscopedenvironmentvariable.h new file mode 100644 index 000000000..390dfd400 --- /dev/null +++ b/tests/auto/shared/qscopedenvironmentvariable.h @@ -0,0 +1,29 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QSCOPEDENVIRONMENTVARIABLE_H +#define QSCOPEDENVIRONMENTVARIABLE_H + +#include <QtCore/qbytearrayview.h> +#include <QtCore/qtenvironmentvariables.h> + +struct QScopedEnvironmentVariable +{ + QScopedEnvironmentVariable(const QScopedEnvironmentVariable &) = delete; + QScopedEnvironmentVariable(QScopedEnvironmentVariable &&) = delete; + QScopedEnvironmentVariable &operator=(const QScopedEnvironmentVariable &) = delete; + QScopedEnvironmentVariable &operator=(QScopedEnvironmentVariable &&) = delete; + + QScopedEnvironmentVariable(const char *envvar, QByteArrayView name) : envvar{ envvar } + { + Q_ASSERT(envvar); + qputenv(envvar, name); + }; + + ~QScopedEnvironmentVariable() { qunsetenv(envvar); } + +private: + const char *envvar; +}; + +#endif // QSCOPEDENVIRONMENTVARIABLE_H diff --git a/tests/auto/shared/qsequentialfileadaptor.h b/tests/auto/shared/qsequentialfileadaptor.h new file mode 100644 index 000000000..92686fc11 --- /dev/null +++ b/tests/auto/shared/qsequentialfileadaptor.h @@ -0,0 +1,44 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QSEQUENTIALFILEADAPTOR_P +#define QSEQUENTIALFILEADAPTOR_P + +#include <QtCore/qfile.h> + +struct QSequentialFileAdaptor : QIODevice +{ + template <typename... Args> + explicit QSequentialFileAdaptor(Args... args) : m_file(args...) + { + } + + bool open(QIODeviceBase::OpenMode mode) override + { + QIODevice::open(mode); + return m_file.open(mode); + } + + void close() override + { + m_file.close(); + QIODevice::close(); + } + + qint64 pos() const override { return m_file.pos(); } + qint64 size() const override { return m_file.size(); } + bool seek(qint64 pos) override { return m_file.seek(pos); } + bool atEnd() const override { return m_file.atEnd(); } + bool reset() override { return m_file.reset(); } + + qint64 bytesAvailable() const override { return m_file.bytesAvailable(); } + + bool isSequential() const override { return true; } + + qint64 readData(char *data, qint64 maxlen) override { return m_file.read(data, maxlen); } + qint64 writeData(const char *data, qint64 len) override { return m_file.write(data, len); } + + QFile m_file; +}; + +#endif // QSEQUENTIALFILEADAPTOR_P diff --git a/tests/auto/shared/qsinewavevalidator.h b/tests/auto/shared/qsinewavevalidator.h new file mode 100644 index 000000000..7322c41f4 --- /dev/null +++ b/tests/auto/shared/qsinewavevalidator.h @@ -0,0 +1,91 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QSINEWAVEVALIDATOR_H +#define QSINEWAVEVALIDATOR_H + +#include <QtCore/qmath.h> + +#include <array> + +#include <QtCore/QDebug> + +// sine wave validator: accumulating peak value and result of passing the signal through a notch +// filter. A sine wave of that frequency should not pass the notch filter, so if the peak exceeds a +// small threshold, it we can detect discontinuities for example. +struct QSineWaveValidator +{ + QSineWaveValidator(const QSineWaveValidator &) = delete; + QSineWaveValidator(QSineWaveValidator &&) = delete; + QSineWaveValidator &operator=(const QSineWaveValidator &) = delete; + QSineWaveValidator &operator=(QSineWaveValidator &&) = delete; + + QSineWaveValidator(float notchFrequency, float sampleRate) + { + using namespace std; + float pi = M_PI; + float f0 = notchFrequency; + float Fs = sampleRate; + float Q = 1 / sqrt(2.f); // higher Q gives narrow bandwidth, but requires a larger number of + // frames during the transient + + // compare https://webaudio.github.io/Audio-EQ-Cookbook/Audio-EQ-Cookbook.txt + float w0 = 2 * pi * f0 / Fs; + float alpha = sin(w0) / (2 * Q); + + b0 = 1; + b1 = -2 * cos(w0); + b2 = 1; + a0 = 1 + alpha; + a1 = -2 * cos(w0); + a2 = 1 - alpha; + } + + void feedSample(float sample) + { + if (pendingFramesBeforeAnalysis == framesBeforeAnalysis) { + x.fill(sample); + y.fill(b0 / a0 * sample); + } + + x[2] = x[1]; + x[1] = x[0]; + x[0] = sample; + + y[2] = y[1]; + y[1] = y[0]; + y[0] = (b0 / a0) * x[0] + (b1 / a0) * x[1] + (b2 / a0) * x[2] - (a1 / a0) * y[1] + - (a2 / a0) * y[2]; + + accumPeak = std::max(std::abs(sample), accumPeak); + + if (pendingFramesBeforeAnalysis) { + pendingFramesBeforeAnalysis -= 1; + return; + } + + accumNotchPeak = std::max(std::abs(y[0]), accumNotchPeak); + } + + float peak() const { return accumPeak; } + float notchPeak() const + { + if (pendingFramesBeforeAnalysis) + qWarning() << "notchPeak during initial frames. Result will not be accurate"; + return accumNotchPeak; + } + +private: + float a0, a1, a2; + float b0, b1, b2; + + std::array<float, 3> x{}, y{}; + int framesBeforeAnalysis = 128; // unscientific estimate, larger than the transient response + // time for the IIR filter + int pendingFramesBeforeAnalysis = framesBeforeAnalysis; + + float accumPeak{}; + float accumNotchPeak{}; +}; + +#endif // QSINEWAVEVALIDATOR_H diff --git a/tests/auto/unit/mockbackend/qmockcamera.cpp b/tests/auto/unit/mockbackend/qmockcamera.cpp index e18bcc98c..1e6d2daa4 100644 --- a/tests/auto/unit/mockbackend/qmockcamera.cpp +++ b/tests/auto/unit/mockbackend/qmockcamera.cpp @@ -34,12 +34,6 @@ void QMockCamera::setActive(bool active) emit activeChanged(active); } -/* helper method to emit the signal error */ -void QMockCamera::setError(QCamera::Error err, QString errorString) -{ - emit error(err, errorString); -} - void QMockCamera::setCamera(const QCameraDevice &camera) { m_camera = camera; diff --git a/tests/auto/unit/mockbackend/qmockcamera.h b/tests/auto/unit/mockbackend/qmockcamera.h index d0ef3d502..21e980f93 100644 --- a/tests/auto/unit/mockbackend/qmockcamera.h +++ b/tests/auto/unit/mockbackend/qmockcamera.h @@ -31,9 +31,6 @@ public: void setActive(bool active) override; - /* helper method to emit the signal error */ - void setError(QCamera::Error err, QString errorString); - void setCamera(const QCameraDevice &camera) override; bool setCameraFormat(const QCameraFormat &format) override; diff --git a/tests/auto/unit/mockbackend/qmockmediaencoder.h b/tests/auto/unit/mockbackend/qmockmediaencoder.h index 1c86c3fc3..e825564a0 100644 --- a/tests/auto/unit/mockbackend/qmockmediaencoder.h +++ b/tests/auto/unit/mockbackend/qmockmediaencoder.h @@ -37,11 +37,11 @@ public: virtual void setMetaData(const QMediaMetaData &m) override { m_metaData = m; - emit metaDataChanged(); + metaDataChanged(); } virtual QMediaMetaData metaData() const override { return m_metaData; } - using QPlatformMediaRecorder::error; + using QPlatformMediaRecorder::updateError; public: void record(QMediaEncoderSettings &settings) override @@ -49,30 +49,30 @@ public: m_state = QMediaRecorder::RecordingState; m_settings = settings; m_position=1; - emit stateChanged(m_state); - emit durationChanged(m_position); + stateChanged(m_state); + durationChanged(m_position); QUrl actualLocation = outputLocation().isEmpty() ? QUrl::fromLocalFile("default_name.mp4") : outputLocation(); - emit actualLocationChanged(actualLocation); + actualLocationChanged(actualLocation); } void pause() override { m_state = QMediaRecorder::PausedState; - emit stateChanged(m_state); + stateChanged(m_state); } void resume() override { m_state = QMediaRecorder::RecordingState; - emit stateChanged(m_state); + stateChanged(m_state); } void stop() override { m_position=0; m_state = QMediaRecorder::StoppedState; - emit stateChanged(m_state); + stateChanged(m_state); } void reset() @@ -80,8 +80,8 @@ public: m_state = QMediaRecorder::StoppedState; m_settings = QMediaEncoderSettings(); m_position = 0; - emit stateChanged(m_state); - emit durationChanged(m_position); + stateChanged(m_state); + durationChanged(m_position); clearActualLocation(); } diff --git a/tests/auto/unit/mockbackend/qmockmediaplayer.h b/tests/auto/unit/mockbackend/qmockmediaplayer.h index f40f8788b..877c096a4 100644 --- a/tests/auto/unit/mockbackend/qmockmediaplayer.h +++ b/tests/auto/unit/mockbackend/qmockmediaplayer.h @@ -44,11 +44,15 @@ public: } qint64 duration() const override { return _duration; } - void setDuration(qint64 duration) { emit durationChanged(_duration = duration); } + void setDuration(qint64 duration) { durationChanged(_duration = duration); } qint64 position() const override { return _position; } - void setPosition(qint64 position) override { if (position != _position) emit positionChanged(_position = position); } + void setPosition(qint64 position) override + { + if (position != _position) + positionChanged(_position = position); + } float bufferProgress() const override { return _bufferProgress; } void setBufferStatus(float status) @@ -63,13 +67,17 @@ public: bool isVideoAvailable() const override { return _videoAvailable; } bool isSeekable() const override { return _isSeekable; } - void setSeekable(bool seekable) { emit seekableChanged(_isSeekable = seekable); } + void setSeekable(bool seekable) { seekableChanged(_isSeekable = seekable); } QMediaTimeRange availablePlaybackRanges() const override { return QMediaTimeRange(_seekRange.first, _seekRange.second); } void setSeekRange(qint64 minimum, qint64 maximum) { _seekRange = qMakePair(minimum, maximum); } qreal playbackRate() const override { return _playbackRate; } - void setPlaybackRate(qreal rate) override { if (rate != _playbackRate) emit playbackRateChanged(_playbackRate = rate); } + void setPlaybackRate(qreal rate) override + { + if (rate != _playbackRate) + playbackRateChanged(_playbackRate = rate); + } QUrl media() const override { return _media; } void setMedia(const QUrl &content, QIODevice *stream) override @@ -92,10 +100,7 @@ public: void setAudioOutput(QPlatformAudioOutput *output) override { m_audioOutput = output; } - void emitError(QMediaPlayer::Error err, const QString &errorString) - { - emit error(err, errorString); - } + void emitError(QMediaPlayer::Error err, const QString &errorString) { error(err, errorString); } void setState(QMediaPlayer::PlaybackState state) { @@ -119,8 +124,16 @@ public: void setIsValid(bool isValid) { _isValid = isValid; } void setMedia(QUrl media) { _media = media; } void setVideoAvailable(bool videoAvailable) { _videoAvailable = videoAvailable; } - void setError(QMediaPlayer::Error err) { _error = err; emit error(_error, _errorString); } - void setErrorString(QString errorString) { _errorString = errorString; emit error(_error, _errorString); } + void setError(QMediaPlayer::Error err) + { + _error = err; + error(_error, _errorString); + } + void setErrorString(QString errorString) + { + _errorString = errorString; + error(_error, _errorString); + } void reset() { diff --git a/tests/auto/unit/multimedia/CMakeLists.txt b/tests/auto/unit/multimedia/CMakeLists.txt index 20b86c251..f259691d0 100644 --- a/tests/auto/unit/multimedia/CMakeLists.txt +++ b/tests/auto/unit/multimedia/CMakeLists.txt @@ -19,7 +19,9 @@ add_subdirectory(qmediatimerange) add_subdirectory(qmultimediautils) add_subdirectory(qvideoframe) add_subdirectory(qvideoframeformat) -add_subdirectory(qvideoframecolormanagement) +if(QT_FEATURE_ffmpeg) + add_subdirectory(qvideoframecolormanagement) +endif() add_subdirectory(qaudiobuffer) add_subdirectory(qaudiodecoder) add_subdirectory(qsamplecache) @@ -33,5 +35,6 @@ 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 index 6d52d09e1..287df7364 100644 --- a/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt +++ b/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt @@ -11,5 +11,5 @@ qt_internal_add_test(tst_gstreamer_backend tst_gstreamer_backend.h LIBRARIES Qt::MultimediaPrivate - Qt::QGstreamerMediaPluginPrivate + Qt::QGstreamerMediaPluginImplPrivate ) diff --git a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp index 3b4577ca2..cd702dfdc 100644 --- a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp +++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp @@ -1,29 +1,475 @@ // Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_gstreamer_backend.h" #include <QtTest/QtTest> -#include <QtQGstreamerMediaPlugin/private/qgstreamermetadata_p.h> -#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h> +#include <QtMultimedia/qmediaformat.h> + +#include <QtQGstreamerMediaPluginImpl/private/qgst_handle_types_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgst_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgst_debug_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstpipeline_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstreamermetadata_p.h> + +#include <set> QT_USE_NAMESPACE +// NOLINTBEGIN(readability-convert-member-functions-to-static) + using namespace Qt::Literals; -void tst_GStreamer::metadata_fromGstTagList() +namespace { + +template <typename... Pairs> +QMediaMetaData makeQMediaMetaData(Pairs &&...pairs) +{ + QMediaMetaData metadata; + + auto addKeyValuePair = [&](auto &&pair) { + metadata.insert(pair.first, pair.second); + return; + }; + + (addKeyValuePair(pairs), ...); + + return metadata; +} + +QGString makeQGString(std::string_view str) +{ + char *s = (char *)g_malloc(str.size() + 1); + snprintf(s, str.size() + 1, "%s", str.data()); + return QGString{ s }; +}; + +} // namespace + +QGstTagListHandle tst_GStreamer::parseTagList(const char *str) { QGstTagListHandle tagList{ - gst_tag_list_new_from_string(R"(taglist, title="My Video", comment="yada")"), + 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::QGString_conversions() +{ + QGString str = makeQGString("yada"); + + QCOMPARE(str.toQString(), u"yada"_s); + QCOMPARE(str.asStringView(), "yada"_L1); + QCOMPARE(str.asByteArrayView(), "yada"_ba); +} + +void tst_GStreamer::QGString_transparentCompare() +{ + QGString str = makeQGString("yada"); + + std::set<QByteArray, std::less<>> set; + set.emplace(str); + + QVERIFY(set.find(str) != set.end()); +} + +void tst_GStreamer::qGstCasts_withElement() +{ + QGstElement element = QGstElement::createFromFactory("identity", "myPipeline"); + QVERIFY(element); + + QVERIFY(!qIsGstObjectOfType<GstPipeline>(element.element())); + QVERIFY(!qIsGstObjectOfType<GstBin>(element.element())); +} + +void tst_GStreamer::qGstCasts_withBin() +{ + QGstBin bin = QGstBin::create("bin"); + QVERIFY(bin); + + QVERIFY(!qIsGstObjectOfType<GstPipeline>(bin.element())); + QVERIFY(qIsGstObjectOfType<GstBin>(bin.element())); +} + +void tst_GStreamer::qGstCasts_withPipeline() +{ + QGstPipeline pipeline = QGstPipeline::create("myPipeline"); + + QGstElement element{ + qGstSafeCast<GstElement>(pipeline.pipeline()), + QGstElement::NeedsRef, + }; + + QVERIFY(element); + QVERIFY(qIsGstObjectOfType<GstPipeline>(element.element())); + QVERIFY(qIsGstObjectOfType<GstBin>(element.element())); +} + +void tst_GStreamer::metadata_taglistToMetaData() +{ + QGstTagListHandle tagList = parseTagList(R"(taglist, title="My Video", comment="yada")"); - QGstreamerMetaData parsed = QGstreamerMetaData::fromGstTagList(tagList.get()); + 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); + QCOMPARE(parsed[QMediaMetaData::Duration].value<int>(), 400); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsLanguage() +{ + QFETCH(QByteArray, tagListString); + QFETCH(QLocale::Language, language); + + QGstTagListHandle tagList = parseTagList(tagListString); + QVERIFY(tagList); + + QMediaMetaData parsed = taglistToMetaData(tagList); + QCOMPARE(parsed[QMediaMetaData::Language].value<QLocale::Language>(), language); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsLanguage_data() +{ + QTest::addColumn<QByteArray>("tagListString"); + QTest::addColumn<QLocale::Language>("language"); + + QTest::newRow("english, en") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)en, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::English; + QTest::newRow("spanish, es") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)es, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::Spanish; + QTest::newRow("english, eng") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)eng, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::English; + QTest::newRow("spanish, spa") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)spa, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::Spanish; +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsDate() +{ + QFETCH(QByteArray, tagListString); + QFETCH(QDateTime, expectedDate); + + QGstTagListHandle tagList = parseTagList(tagListString); + QVERIFY(tagList); + + QMediaMetaData parsed = taglistToMetaData(tagList); + QCOMPARE(parsed[QMediaMetaData::Date].value<QDateTime>(), expectedDate); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsDate_data() +{ + QTest::addColumn<QByteArray>("tagListString"); + QTest::addColumn<QDateTime>("expectedDate"); + + QTest::newRow("datetime") << R"__(taglist, datetime=(datetime)2024)__"_ba + << QDateTime(QDate(2024, 0, 0), QTime{}); + QTest::newRow("date") << R"__(taglist, date=(date)2024-01-01)__"_ba + << QDateTime(QDate(2024, 1, 1), QTime{}); + QTest::newRow("date and datetime") + << R"__(taglist, datetime=(datetime)2024, date=(date)2024-01-01)__"_ba + << QDateTime(QDate(2024, 1, 1), QTime{}); +} + +void tst_GStreamer::metadata_capsToMetaData() +{ + QFETCH(QByteArray, capsString); + QFETCH(QMediaMetaData, expectedMetadata); + + QGstCaps caps{ + gst_caps_from_string(capsString.constData()), + QGstCaps::HasRef, + }; + + QMediaMetaData md = capsToMetaData(caps); + + QCOMPARE(md, expectedMetadata); +} + +void tst_GStreamer::metadata_capsToMetaData_data() +{ + using Key = QMediaMetaData::Key; + using KVPair = std::pair<QMediaMetaData::Key, QVariant>; + + auto makeKVPair = [](Key key, auto value) { + return KVPair{ + key, + QVariant::fromValue(value), + }; + }; + + QTest::addColumn<QByteArray>("capsString"); + QTest::addColumn<QMediaMetaData>("expectedMetadata"); + + QTest::newRow("container") << R"(video/quicktime, variant=(string)iso)"_ba + << makeQMediaMetaData(makeKVPair(Key::FileFormat, + QMediaFormat::FileFormat::MPEG4)); + + QTest::newRow("video") + << R"(video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3.1, profile=(string)main, codec_data=(buffer)014d401fffe10017674d401fda014016ec0440000003004000000c83c60ca801000468ef3c80, width=(int)1280, height=(int)720, framerate=(fraction)25/1, pixel-aspect-ratio=(fraction)1/1)"_ba + << makeQMediaMetaData(makeKVPair(Key::VideoCodec, QMediaFormat::VideoCodec::H264), + makeKVPair(Key::VideoFrameRate, 25), + makeKVPair(Key::Resolution, QSize(1280, 720))); + + QTest::newRow("audio") + << R"(audio/mpeg, mpegversion=(int)4, framed=(boolean)true, stream-format=(string)raw, level=(string)4, base-profile=(string)lc, profile=(string)lc, codec_data=(buffer)11b0, rate=(int)48000, channels=(int)6)"_ba + << makeQMediaMetaData(makeKVPair(Key::AudioCodec, QMediaFormat::AudioCodec::AAC)); +} + +void tst_GStreamer::parseRotationTag_returnsCorrectResults() +{ + QCOMPARE_EQ(parseRotationTag("rotate-0"), (RotationResult{ QtVideo::Rotation::None, false })); + QCOMPARE_EQ(parseRotationTag("rotate-90"), + (RotationResult{ QtVideo::Rotation::Clockwise90, false })); + QCOMPARE_EQ(parseRotationTag("rotate-180"), + (RotationResult{ QtVideo::Rotation::Clockwise180, false })); + QCOMPARE_EQ(parseRotationTag("rotate-270"), + (RotationResult{ QtVideo::Rotation::Clockwise270, false })); + + QCOMPARE_EQ(parseRotationTag("flip-rotate-0"), + (RotationResult{ QtVideo::Rotation::Clockwise180, true })); + QCOMPARE_EQ(parseRotationTag("flip-rotate-90"), + (RotationResult{ QtVideo::Rotation::Clockwise270, true })); + QCOMPARE_EQ(parseRotationTag("flip-rotate-180"), + (RotationResult{ QtVideo::Rotation::None, true })); + QCOMPARE_EQ(parseRotationTag("flip-rotate-270"), + (RotationResult{ QtVideo::Rotation::Clockwise90, true })); +} + +void tst_GStreamer::QGstBin_createFromPipelineDescription() +{ + QGstBin bin = QGstBin::createFromPipelineDescription("identity name=foo ! identity name=bar"); + + QVERIFY(bin); + QVERIFY(bin.findByName("foo")); + QCOMPARE_EQ(bin.findByName("foo").getParent(), bin); + QVERIFY(bin.findByName("bar")); + QVERIFY(!bin.findByName("baz")); + bin.dumpGraph("QGstBin_createFromPipelineDescription"); +} + +void tst_GStreamer::QGstElement_createFromPipelineDescription() +{ + using namespace std::string_view_literals; + QGstElement element = QGstElement::createFromPipelineDescription("identity name=foo"); + QCOMPARE_EQ(element.name().constData(), "foo"sv); + QCOMPARE_EQ(element.typeName().constData(), "GstIdentity"sv); +} + +void tst_GStreamer::QGstElement_createFromPipelineDescription_multipleElementsCreatesBin() +{ + using namespace std::string_view_literals; + QGstElement element = + QGstElement::createFromPipelineDescription("identity name=foo ! identity name=bar"); + + QVERIFY(element); + QCOMPARE_EQ(element.typeName().constData(), "GstPipeline"sv); + + QGstBin bin{ + qGstSafeCast<GstBin>(element.element()), + QGstBin::NeedsRef, + }; + + QVERIFY(bin); + QVERIFY(bin.findByName("foo")); + QCOMPARE_EQ(bin.findByName("foo").getParent(), bin); + QVERIFY(bin.findByName("bar")); + QVERIFY(!bin.findByName("baz")); + + bin.dumpGraph("QGstElement_createFromPipelineDescription_multipleElements"); +} + +void tst_GStreamer::QGstPad_inferTypeFromName() +{ + auto makePad = [](const char *name, GstPadDirection direction) { + return QGstPad{ + gst_pad_new(name, direction), + QGstPad::NeedsRef, + }; + }; + + QVERIFY(makePad("audio_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::AudioStream); + QVERIFY(makePad("video_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::VideoStream); + QVERIFY(makePad("text_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::SubtitleStream); + QVERIFY(makePad("src_0", GST_PAD_SRC).inferTrackTypeFromName() == std::nullopt); + QVERIFY(makePad("text", GST_PAD_SRC).inferTrackTypeFromName() == std::nullopt); +} + +void tst_GStreamer::qDebug_GstPadDirection() +{ + auto validate = [](GstPadDirection direction, QString expectedString) { + QString str; + QDebug dbg(&str); + + dbg << direction; + + QCOMPARE_EQ(str, expectedString); + }; + + validate(GST_PAD_UNKNOWN, u"GST_PAD_UNKNOWN "_s); + validate(GST_PAD_SRC, u"GST_PAD_SRC "_s); + validate(GST_PAD_SINK, u"GST_PAD_SINK "_s); +} + +void tst_GStreamer::qDebug_GstStreamStatusType() +{ + auto validate = [](GstStreamStatusType type, QString expectedString) { + QString str; + QDebug dbg(&str); + + dbg << type; + + QCOMPARE_EQ(str, expectedString); + }; + + validate(GST_STREAM_STATUS_TYPE_CREATE, u"GST_STREAM_STATUS_TYPE_CREATE "_s); + validate(GST_STREAM_STATUS_TYPE_ENTER, u"GST_STREAM_STATUS_TYPE_ENTER "_s); + validate(GST_STREAM_STATUS_TYPE_LEAVE, u"GST_STREAM_STATUS_TYPE_LEAVE "_s); + validate(GST_STREAM_STATUS_TYPE_DESTROY, u"GST_STREAM_STATUS_TYPE_DESTROY "_s); + validate(GST_STREAM_STATUS_TYPE_START, u"GST_STREAM_STATUS_TYPE_START "_s); + validate(GST_STREAM_STATUS_TYPE_PAUSE, u"GST_STREAM_STATUS_TYPE_PAUSE "_s); + validate(GST_STREAM_STATUS_TYPE_STOP, u"GST_STREAM_STATUS_TYPE_STOP "_s); +} + +void tst_GStreamer::QGstStructureView_parseCameraFormat() +{ + auto makeStructure = [](const char *str) { + return QUniqueGstStructureHandle{ + gst_structure_new_from_string(str), + }; + }; + + auto compareFraction = [](std::optional<Fraction> lhs, Fraction rhs) { + if (!lhs) + return false; + return std::tie(lhs->numerator, lhs->denominator) + == std::tie(rhs.numerator, rhs.denominator); + }; + + // single frame rate (taken from Logitech Brio 300) + { + auto structure = makeStructure( + R"__(video/x-raw, format=(string)YUY2, width=(int)1920, height=(int)1080, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)5/1)__"); + + Fraction expectedFracion{ 1, 1 }; + QGRange<float> expectedFramerateRange{ 5, 5 }; + + QCOMPARE(QGstStructureView(structure).resolution(), QSize(1920, 1080)); + QVERIFY(compareFraction(QGstStructureView(structure).pixelAspectRatio(), expectedFracion)); + QCOMPARE(QGstStructureView(structure).frameRateRange(), expectedFramerateRange); + QCOMPARE(QGstStructureView(structure).pixelFormat(), + QVideoFrameFormat::PixelFormat::Format_YUYV); + } + + // multiple frame rates (taken from Logitech Brio 300) + { + auto structure = makeStructure( + R"__(video/x-raw, format=(string)YUY2, width=(int)640, height=(int)480, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction){ 30/1, 24/1, 20/1, 15/1, 10/1, 15/2, 5/1 })__"); + + Fraction expectedFracion{ 1, 1 }; + QGRange<float> expectedFramerateRange{ 5, 30 }; + + QCOMPARE(QGstStructureView(structure).resolution(), QSize(640, 480)); + QVERIFY(compareFraction(QGstStructureView(structure).pixelAspectRatio(), expectedFracion)); + QCOMPARE(QGstStructureView(structure).frameRateRange(), expectedFramerateRange); + QCOMPARE(QGstStructureView(structure).pixelFormat(), + QVideoFrameFormat::PixelFormat::Format_YUYV); + } + + // jpeg (taken from Logitech Brio 300) + { + auto structure = makeStructure( + R"__(image/jpeg, parsed=(boolean)true, width=(int)1920, height=(int)1080, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction){ 30/1, 24/1, 20/1, 15/1, 10/1, 15/2, 5/1 })__"); + + Fraction expectedFracion{ 1, 1 }; + QGRange<float> expectedFramerateRange{ 5, 30 }; + + QCOMPARE(QGstStructureView(structure).resolution(), QSize(1920, 1080)); + QVERIFY(compareFraction(QGstStructureView(structure).pixelAspectRatio(), expectedFracion)); + QCOMPARE(QGstStructureView(structure).frameRateRange(), expectedFramerateRange); + QCOMPARE(QGstStructureView(structure).pixelFormat(), + QVideoFrameFormat::PixelFormat::Format_Jpeg); + } + + // steped frame rate, undefined frame rate (taken from Raspberry Pi 4, Camera Module v2) + { + QGRange<float> expectedFramerateRange{ 0.0, 2147483647.0 }; + + auto cameraFormat = makeStructure( + R"__(video/x-raw, format=(string)YUY2, width=(int)[ 64, 16384, 2 ], height=(int)[ 64, 16384, 2 ], framerate=(fraction)[ 0/1, 2147483647/1 ])__"); + + QCOMPARE(QGstStructureView(cameraFormat).pixelAspectRatio(), std::nullopt); + QCOMPARE(QGstStructureView(cameraFormat).frameRateRange(), expectedFramerateRange); + QCOMPARE(QGstStructureView(cameraFormat).pixelFormat(), + QVideoFrameFormat::PixelFormat::Format_YUYV); + } + + // steped frame rate, valid rate range (taken from Raspberry Pi 4, Camera Module v2) + { + auto cameraFormat = makeStructure( + R"__(video/x-raw, format=(string)YUY2, width=(int)[ 32, 3280, 2 ], height=(int)[ 32, 2464, 2 ], framerate=(fraction)[ 1/1, 90/1 ])__"); + + QGRange<float> expectedFramerateRange{ 1.0, 90.0 }; + QGRange<QSize> expectedResolutionRange{ + QSize(32, 32), + QSize(3280, 2464), + }; + + QCOMPARE(QGstStructureView(cameraFormat).resolutionRange(), expectedResolutionRange); + QCOMPARE(QGstStructureView(cameraFormat).pixelAspectRatio(), std::nullopt); + QCOMPARE(QGstStructureView(cameraFormat).frameRateRange(), expectedFramerateRange); + QCOMPARE(QGstStructureView(cameraFormat).pixelFormat(), + QVideoFrameFormat::PixelFormat::Format_YUYV); + } +} + 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 index 5bac3a599..f9f9e06f1 100644 --- a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h +++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h @@ -1,12 +1,13 @@ // Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_GSTREAMER_BACKEND_H #define TST_GSTREAMER_BACKEND_H #include <QtTest/QtTest> -#include <QtQGstreamerMediaPlugin/private/qgstreamerintegration_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstreamerintegration_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgst_handle_types_p.h> QT_USE_NAMESPACE @@ -14,8 +15,41 @@ class tst_GStreamer : public QObject { Q_OBJECT + QGstTagListHandle parseTagList(const char *); + QGstTagListHandle parseTagList(const QByteArray &); + private slots: - void metadata_fromGstTagList(); + void QGString_conversions(); + void QGString_transparentCompare(); + + void qGstCasts_withElement(); + void qGstCasts_withBin(); + void qGstCasts_withPipeline(); + + void metadata_taglistToMetaData(); + void metadata_taglistToMetaData_extractsOrientation(); + void metadata_taglistToMetaData_extractsOrientation_data(); + void metadata_taglistToMetaData_extractsDuration(); + void metadata_taglistToMetaData_extractsLanguage(); + void metadata_taglistToMetaData_extractsLanguage_data(); + void metadata_taglistToMetaData_extractsDate(); + void metadata_taglistToMetaData_extractsDate_data(); + + void metadata_capsToMetaData(); + void metadata_capsToMetaData_data(); + + void parseRotationTag_returnsCorrectResults(); + + void QGstBin_createFromPipelineDescription(); + void QGstElement_createFromPipelineDescription(); + void QGstElement_createFromPipelineDescription_multipleElementsCreatesBin(); + + void QGstPad_inferTypeFromName(); + + void qDebug_GstPadDirection(); + void qDebug_GstStreamStatusType(); + + void QGstStructureView_parseCameraFormat(); private: QGstreamerIntegration integration; diff --git a/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt index cced66bda..73b1ccc70 100644 --- a/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt @@ -17,5 +17,5 @@ qt_internal_add_test(tst_qaudiodecoder Qt::Gui Qt::Multimedia Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt index b2dc16b69..cb6846685 100644 --- a/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt @@ -16,5 +16,5 @@ qt_internal_add_test(tst_qaudiorecorder # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qcamera/CMakeLists.txt b/tests/auto/unit/multimedia/qcamera/CMakeLists.txt index 484793923..8dbd34a04 100644 --- a/tests/auto/unit/multimedia/qcamera/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qcamera/CMakeLists.txt @@ -16,5 +16,5 @@ qt_internal_add_test(tst_multimedia_qcamera # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp index 9c070d567..f698213b3 100644 --- a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp +++ b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp @@ -607,7 +607,7 @@ void tst_QCamera::testErrorSignal() 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")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); QVERIFY(spyError.size() == 1); QCamera::Error err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); @@ -616,7 +616,8 @@ void tst_QCamera::testErrorSignal() spyError.clear(); /* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("InvalidRequestError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); QVERIFY(spyError.size() == 1); err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); QVERIFY(err == QCamera::CameraError); @@ -624,7 +625,8 @@ void tst_QCamera::testErrorSignal() spyError.clear(); /* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("NotSupportedFeatureError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("NotSupportedFeatureError Error")); QVERIFY(spyError.size() == 1); err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); QVERIFY(err == QCamera::CameraError); @@ -640,15 +642,17 @@ void tst_QCamera::testError() auto *service = QMockIntegration::instance()->lastCaptureService(); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("Camera Error")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); QVERIFY(camera.error() == QCamera::CameraError); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("InvalidRequestError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); QVERIFY(camera.error() == QCamera::CameraError); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("CameraError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("CameraError Error")); QVERIFY(camera.error() == QCamera::CameraError); } @@ -662,15 +666,17 @@ void tst_QCamera::testErrorString() auto *service = QMockIntegration::instance()->lastCaptureService(); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("Camera Error")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); QVERIFY(camera.errorString() == QStringLiteral("Camera Error")); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("InvalidRequestError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); QVERIFY(camera.errorString() == QStringLiteral("InvalidRequestError Error")); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QStringLiteral("CameraError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("CameraError Error")); QVERIFY(camera.errorString() == QStringLiteral("CameraError Error")); } diff --git a/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt b/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt index 4d9977e8e..e7a5e228a 100644 --- a/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt @@ -16,5 +16,5 @@ qt_internal_add_test(tst_qcameradevice # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp b/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp index afb6f68e1..9bca73577 100644 --- a/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp +++ b/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp @@ -9,40 +9,25 @@ #include <qmediadevices.h> #include "qmockintegration.h" -#include "qmockmediacapturesession.h" QT_USE_NAMESPACE Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN +using namespace Qt::Literals; + class tst_QCameraDevice: public QObject { Q_OBJECT -public slots: - void initTestCase(); - void init(); - void cleanup(); - private slots: void constructor(); void defaultCamera(); void availableCameras(); void equality_operators(); + void qDebug_operator(); }; -void tst_QCameraDevice::initTestCase() -{ -} - -void tst_QCameraDevice::init() -{ -} - -void tst_QCameraDevice::cleanup() -{ -} - void tst_QCameraDevice::constructor() { { @@ -50,8 +35,8 @@ void tst_QCameraDevice::constructor() QCamera camera; QCameraDevice info(camera.cameraDevice()); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); } @@ -66,14 +51,14 @@ void tst_QCameraDevice::constructor() QCamera camera(info); QCOMPARE(info, camera.cameraDevice()); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("back")); - QCOMPARE(info.description(), QStringLiteral("backCamera")); + QCOMPARE(info.id(), u"back"_s); + QCOMPARE(info.description(), u"backCamera"_s); QCOMPARE(info.position(), QCameraDevice::BackFace); QCameraDevice info2(info); QVERIFY(!info2.isNull()); - QCOMPARE(info2.id(), QStringLiteral("back")); - QCOMPARE(info2.description(), QStringLiteral("backCamera")); + QCOMPARE(info2.id(), u"back"_s); + QCOMPARE(info2.description(), u"backCamera"_s); QCOMPARE(info2.position(), QCameraDevice::BackFace); } @@ -82,8 +67,8 @@ void tst_QCameraDevice::defaultCamera() QCameraDevice info = QMediaDevices::defaultVideoInput(); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); QCamera camera(info); @@ -97,8 +82,8 @@ void tst_QCameraDevice::availableCameras() QCameraDevice info = cameras.at(0); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); info = cameras.at(1); @@ -110,8 +95,8 @@ void tst_QCameraDevice::availableCameras() QCOMPARE(cameras.size(), 3); info = cameras.at(2); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("back")); - QCOMPARE(info.description(), QStringLiteral("backCamera")); + QCOMPARE(info.id(), u"back"_s); + QCOMPARE(info.description(), u"backCamera"_s); QCOMPARE(info.position(), QCameraDevice::BackFace); } @@ -136,6 +121,18 @@ void tst_QCameraDevice::equality_operators() } } +void tst_QCameraDevice::qDebug_operator() +{ + QString outputString; + QDebug debug(&outputString); + debug.nospace(); + + QCameraDevice defaultCamera = QMediaDevices::defaultVideoInput(); + debug << defaultCamera; + + QCOMPARE(outputString, + u"\"QCameraDevice(name=defaultCamera, id=default, position=UnspecifiedPosition)\" "_s); +} QTEST_MAIN(tst_QCameraDevice) diff --git a/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt b/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt index 908154cf9..6fac8c900 100644 --- a/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt @@ -16,5 +16,5 @@ qt_internal_add_test(tst_qimagecapture # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) 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..d45ac5e1e --- /dev/null +++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt @@ -0,0 +1,18 @@ +# 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 + ../../../shared/qscopedenvironmentvariable.h + INCLUDE_DIRECTORIES + ../../../shared + LIBRARIES + Qt::Multimedia + Qt::MultimediaPrivate + Qt::QGstreamerMediaPluginImplPrivate +) 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..a84514969 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> +#include <QtMultimedia/QAudioDevice> +#include <QtMultimedia/QAudioInput> +#include <QtMultimedia/QAudioOutput> +#include <QtMultimedia/QCamera> +#include <QtMultimedia/QMediaCaptureSession> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> +#include <QtMultimedia/private/qplatformmediacapture_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstpipeline_p.h> + +#include <qscopedenvironmentvariable.h> + +#include <memory> + +// NOLINTBEGIN(readability-convert-member-functions-to-static) + +QT_USE_NAMESPACE + +using namespace Qt::Literals; + +class tst_QMediaCaptureGStreamer : public QObject +{ + Q_OBJECT + +public: + tst_QMediaCaptureGStreamer(); + +public slots: + void init(); + void cleanup(); + +private slots: + void mediaIntegration_hasPlatformSpecificInterface(); + void constructor_preparesGstPipeline(); + void audioInput_makeCustomGStreamerAudioInput_fromPipelineDescription(); + void audioOutput_makeCustomGStreamerAudioOutput_fromPipelineDescription(); + + void makeCustomGStreamerCamera_fromPipelineDescription(); + void makeCustomGStreamerCamera_fromPipelineDescription_multipleItems(); + void makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement(); + +private: + std::unique_ptr<QMediaCaptureSession> session; + + QGStreamerPlatformSpecificInterface *gstInterface() + { + return QGStreamerPlatformSpecificInterface::instance(); + } + + GstPipeline *getGstPipeline() + { + auto *iface = QGStreamerPlatformSpecificInterface::instance(); + return iface ? iface->gstPipeline(session.get()) : nullptr; + } + + QGstPipeline getPipeline() + { + return QGstPipeline{ + getGstPipeline(), + QGstPipeline::NeedsRef, + }; + } + + 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::mediaIntegration_hasPlatformSpecificInterface() +{ + QVERIFY(QGStreamerPlatformSpecificInterface::instance()); +} + +void tst_QMediaCaptureGStreamer::constructor_preparesGstPipeline() +{ + auto *rawPipeline = getGstPipeline(); + QVERIFY(rawPipeline); + + QGstPipeline pipeline{ + rawPipeline, + QGstPipeline::NeedsRef, + }; + QVERIFY(pipeline); + + dumpGraph("constructor_preparesGstPipeline"); +} + +void tst_QMediaCaptureGStreamer::audioInput_makeCustomGStreamerAudioInput_fromPipelineDescription() +{ + auto pipelineString = + "audiotestsrc wave=2 freq=200 name=myOscillator ! identity name=myConverter"_ba; + + QAudioInput input{ + gstInterface()->makeCustomGStreamerAudioInput(pipelineString), + }; + + session->setAudioInput(&input); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + pipeline.finishStateChange(); + + QVERIFY(pipeline.findByName("myOscillator")); + QVERIFY(pipeline.findByName("myConverter")); + + dumpGraph("audioInput_customAudioDevice"); +} + +void tst_QMediaCaptureGStreamer:: + audioOutput_makeCustomGStreamerAudioOutput_fromPipelineDescription() +{ + auto pipelineStringInput = + "audiotestsrc wave=2 freq=200 name=myOscillator ! identity name=myConverter"_ba; + QAudioInput input{ + gstInterface()->makeCustomGStreamerAudioInput(pipelineStringInput), + }; + session->setAudioInput(&input); + + auto pipelineStringOutput = "identity name=myConverter ! fakesink name=mySink"_ba; + QAudioOutput output{ + gstInterface()->makeCustomGStreamerAudioOutput(pipelineStringOutput), + }; + session->setAudioOutput(&output); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + pipeline.finishStateChange(); + + QVERIFY(pipeline.findByName("mySink")); + QVERIFY(pipeline.findByName("myConverter")); + + dumpGraph("audioOutput_customAudioDevice"); +} + +void tst_QMediaCaptureGStreamer::makeCustomGStreamerCamera_fromPipelineDescription() +{ + auto pipelineString = "videotestsrc name=mySrc"_ba; + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(pipelineString, /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QVERIFY(pipeline.findByName("mySrc")); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription"); +} + +void tst_QMediaCaptureGStreamer::makeCustomGStreamerCamera_fromPipelineDescription_multipleItems() +{ + auto pipelineString = "videotestsrc name=mySrc ! gamma gamma=2.0 name=myFilter"_ba; + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(pipelineString, /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QVERIFY(pipeline.findByName("mySrc")); + QVERIFY(pipeline.findByName("myFilter")); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_multipleItems"); +} + +void tst_QMediaCaptureGStreamer:: + makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement() +{ + QGstElement element = QGstElement::createFromPipelineDescription("videotestsrc"); + gst_element_set_name(element.element(), "mySrc"); + + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(element.element(), /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QCOMPARE(pipeline.findByName("mySrc"), element); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement"); + + element.set("foreground-color", 0xff0000); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement2"); +} + +QTEST_GUILESS_MAIN(tst_QMediaCaptureGStreamer) + +#include "tst_qmediacapture_gstreamer.moc" diff --git a/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt b/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt index e8360f8b5..fd9adf4c4 100644 --- a/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt @@ -9,5 +9,5 @@ qt_internal_add_test(tst_qmediadevices LIBRARIES Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp b/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp index 4df34c8c4..f6f198984 100644 --- a/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp +++ b/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp @@ -11,6 +11,8 @@ class tst_QMediaFormat : public QObject private slots: void testResolveForEncoding(); + void mimetype_returnsExpectedMimeType_data(); + void mimetype_returnsExpectedMimeType(); }; void tst_QMediaFormat::testResolveForEncoding() @@ -58,7 +60,44 @@ void tst_QMediaFormat::testResolveForEncoding() QVERIFY(format.audioCodec() == QMediaFormat::AudioCodec::Wave); } } +} + +void tst_QMediaFormat::mimetype_returnsExpectedMimeType_data() +{ + QTest::addColumn<QMediaFormat::FileFormat>("format"); + QTest::addColumn<QMimeType>("mimetype"); + + const QMimeDatabase db; + + // clang-format off + QTest::addRow("WMV") << QMediaFormat::FileFormat::WMV << db.mimeTypeForName("video/x-ms-wmv"); + QTest::addRow("AVI") << QMediaFormat::FileFormat::AVI << db.mimeTypeForName("video/x-msvideo"); + QTest::addRow("Matroska") << QMediaFormat::FileFormat::Matroska << db.mimeTypeForName("video/x-matroska"); + QTest::addRow("MPEG4") << QMediaFormat::FileFormat::MPEG4 << db.mimeTypeForName("video/mp4"); + QTest::addRow("Ogg") << QMediaFormat::FileFormat::Ogg << db.mimeTypeForName("video/ogg"); + QTest::addRow("QuickTime") << QMediaFormat::FileFormat::QuickTime << db.mimeTypeForName("video/quicktime"); + QTest::addRow("WebM") << QMediaFormat::FileFormat::WebM << db.mimeTypeForName("video/webm"); + QTest::addRow("Mpeg4Audio") << QMediaFormat::FileFormat::Mpeg4Audio << db.mimeTypeForName("audio/mp4"); + QTest::addRow("AAC") << QMediaFormat::FileFormat::AAC << db.mimeTypeForName("audio/aac"); + QTest::addRow("WMA") << QMediaFormat::FileFormat::WMA << db.mimeTypeForName("audio/x-ms-wma"); + QTest::addRow("MP3") << QMediaFormat::FileFormat::MP3 << db.mimeTypeForName("audio/mpeg"); + QTest::addRow("FLAC") << QMediaFormat::FileFormat::FLAC << db.mimeTypeForName("audio/flac"); + QTest::addRow("Wave") << QMediaFormat::FileFormat::Wave << db.mimeTypeForName("audio/wav"); + // clang-format on +} + +void tst_QMediaFormat::mimetype_returnsExpectedMimeType() +{ + // Purpose of this test is to make sure that the + // mapping from FileFormat to MIME type remains intact + // Note: The test assumes that the MIME database is the + // ground truth, and will not detect missing MIME types + // in the MIME database. + QFETCH(const QMediaFormat::FileFormat, format); + QFETCH(const QMimeType, mimetype); + const QMediaFormat mediaFormat{ format }; + QCOMPARE_EQ(mediaFormat.mimeType(), mimetype); } QTEST_MAIN(tst_QMediaFormat) diff --git a/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt index 4d3f2f865..80f10542b 100644 --- a/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt @@ -17,7 +17,7 @@ qt_internal_add_test(tst_qmediaplayer Qt::Gui Qt::MultimediaPrivate Qt::Network - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) # Resources: diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt index 2437df00c..baeef0c26 100644 --- a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt @@ -8,7 +8,25 @@ qt_internal_add_test(tst_qmediaplayer_gstreamer SOURCES tst_qmediaplayer_gstreamer.cpp + tst_qmediaplayer_gstreamer.h + ../../../shared/qscopedenvironmentvariable.h + INCLUDE_DIRECTORIES + ../../../shared LIBRARIES Qt::MultimediaPrivate - Qt::QGstreamerMediaPluginPrivate + Qt::QGstreamerMediaPluginImplPrivate ) + + +# Resources: +set(testdata_resource_files + "testdata/color_matrix.mp4" +) + +qt_internal_add_resource(tst_qmediaplayer_gstreamer "testdata" + PREFIX + "/" + FILES + ${testdata_resource_files} +) + diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 Binary files differnew file mode 100644 index 000000000..a3661b9d2 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp index 8b0f3f073..7b8fc10f5 100644 --- a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp @@ -1,50 +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 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmediaplayer_gstreamer.h" #include <QtTest/QtTest> -#include <QtMultimedia/qmediaplayer.h> #include <QtMultimedia/private/qmediaplayer_p.h> -#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h> -#include <memory> +#include <qscopedenvironmentvariable.h> QT_USE_NAMESPACE -class tst_QMediaPlayerGStreamer : public QObject -{ - Q_OBJECT - -public: - tst_QMediaPlayerGStreamer(); +using namespace Qt::Literals; -public slots: - void init(); - void cleanup(); - -private slots: - void constructor_preparesGstPipeline(); +QGStreamerPlatformSpecificInterface *tst_QMediaPlayerGStreamer::gstInterface() +{ + return dynamic_cast<QGStreamerPlatformSpecificInterface *>( + QPlatformMediaIntegration::instance()->platformSpecificInterface()); +} -private: - std::unique_ptr<QMediaPlayer> player; +GstPipeline *tst_QMediaPlayerGStreamer::getGstPipeline() +{ + QGStreamerPlatformSpecificInterface *iface = gstInterface(); + return iface ? iface->gstPipeline(player.get()) : nullptr; +} - GstPipeline *getGstPipeline() - { - return reinterpret_cast<GstPipeline *>(QPlatformMediaPlayer::nativePipeline(player.get())); - } +QGstPipeline tst_QMediaPlayerGStreamer::getPipeline() +{ + return QGstPipeline{ + getGstPipeline(), + QGstPipeline::NeedsRef, + }; +} - void dumpGraph(const char *fileNamePrefix) - { - GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(getGstPipeline()), - GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_VERBOSE), - fileNamePrefix); - } -}; +void tst_QMediaPlayerGStreamer::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::initTestCase() +{ + using namespace std::chrono_literals; + + QMediaPlayer player; + + QVideoSink sink; + player.setVideoSink(&sink); + player.setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + for (;;) { + QMediaPlayer::MediaStatus status = player.mediaStatus(); + switch (status) { + case QMediaPlayer::MediaStatus::InvalidMedia: { + mediaSupported = false; + return; + } + case QMediaPlayer::MediaStatus::NoMedia: + case QMediaPlayer::MediaStatus::StalledMedia: + case QMediaPlayer::MediaStatus::LoadingMedia: + QTest::qWait(20 /*ms*/); + continue; + + default: { + mediaSupported = true; + return; + } + } + } +} + void tst_QMediaPlayerGStreamer::init() { player = std::make_unique<QMediaPlayer>(); @@ -73,6 +102,52 @@ void tst_QMediaPlayerGStreamer::constructor_preparesGstPipeline() dumpGraph("constructor_preparesGstPipeline"); } +void tst_QMediaPlayerGStreamer::videoSink_constructor_overridesConversionElement() +{ + if (!mediaSupported) + QSKIP("Media playback not supported"); + + QScopedEnvironmentVariable convOverride{ + "QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT", + "identity name=myConverter", + }; + + QVideoSink sink; + player->setVideoSink(&sink); + player->setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + QTRY_VERIFY(pipeline.findByName("myConverter")); + + dumpGraph("videoSink_constructor_overridesConversionElement"); +} + +void tst_QMediaPlayerGStreamer:: + videoSink_constructor_overridesConversionElement_withMultipleElements() +{ + if (!mediaSupported) + QSKIP("Media playback not supported"); + + QScopedEnvironmentVariable convOverride{ + "QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT", + "identity name=myConverter ! identity name=myConverter2", + }; + + QVideoSink sink; + player->setVideoSink(&sink); + player->setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + QTRY_VERIFY(pipeline.findByName("myConverter")); + QTRY_VERIFY(pipeline.findByName("myConverter2")); + + dumpGraph("videoSink_constructer_overridesConversionElement_withMultipleElements"); +} + QTEST_GUILESS_MAIN(tst_QMediaPlayerGStreamer) -#include "tst_qmediaplayer_gstreamer.moc" +#include "moc_tst_qmediaplayer_gstreamer.cpp" diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h new file mode 100644 index 000000000..6479c9f35 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h @@ -0,0 +1,46 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_GMEDIAPLAYER_GSTREAMER_H +#define TST_GMEDIAPLAYER_GSTREAMER_H + +#include <QtCore/qtemporaryfile.h> +#include <QtCore/qstandardpaths.h> +#include <QtMultimedia/qmediaplayer.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstpipeline_p.h> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> + +#include <memory> + +QT_USE_NAMESPACE + +class tst_QMediaPlayerGStreamer : public QObject +{ + Q_OBJECT + +public: + tst_QMediaPlayerGStreamer(); + +public slots: + void initTestCase(); + void init(); + void cleanup(); + +private slots: + void constructor_preparesGstPipeline(); + void videoSink_constructor_overridesConversionElement(); + void videoSink_constructor_overridesConversionElement_withMultipleElements(); + +private: + std::unique_ptr<QMediaPlayer> player; + + static QGStreamerPlatformSpecificInterface *gstInterface(); + + GstPipeline *getGstPipeline(); + QGstPipeline getPipeline(); + void dumpGraph(const char *fileNamePrefix); + + bool mediaSupported; +}; + +#endif // TST_GMEDIAPLAYER_GSTREAMER_H diff --git a/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt b/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt index 83e40012a..125e7da19 100644 --- a/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt @@ -16,5 +16,5 @@ qt_internal_add_test(tst_qmediarecorder # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp index 8848968da..2904fa904 100644 --- a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp +++ b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp @@ -191,7 +191,7 @@ void tst_QMediaRecorder::testError() QCOMPARE(encoder->error(), QMediaRecorder::NoError); QCOMPARE(encoder->errorString(), QString()); - mock->error(QMediaRecorder::FormatError, errorString); + mock->updateError(QMediaRecorder::FormatError, errorString); QCOMPARE(encoder->error(), QMediaRecorder::FormatError); QCOMPARE(encoder->errorString(), errorString); QCOMPARE(spy.size(), 1); @@ -414,7 +414,7 @@ void tst_QMediaRecorder::testEnum() QCOMPARE(encoder->error(), QMediaRecorder::NoError); QCOMPARE(encoder->errorString(), QString()); - emit mock->error(QMediaRecorder::ResourceError, errorString); + mock->updateError(QMediaRecorder::ResourceError, errorString); QCOMPARE(encoder->error(), QMediaRecorder::ResourceError); QCOMPARE(encoder->errorString(), errorString); QCOMPARE(spy.size(), 1); diff --git a/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp b/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp index 44933403e..9c133a7cb 100644 --- a/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp +++ b/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp @@ -100,13 +100,17 @@ void tst_QMultimediaUtils::qMediaFromUserInput_addsFilePrefix_whenCalledWithLoca using namespace Qt::Literals; QCOMPARE(qMediaFromUserInput(QUrl(u"/foo/bar/baz"_s)), QUrl(u"file:///foo/bar/baz"_s)); - QCOMPARE(qMediaFromUserInput(QUrl::fromLocalFile(u"C:/foo/bar/baz"_s)), - QUrl(u"file:///C:/foo/bar/baz"_s)); QCOMPARE(qMediaFromUserInput(QUrl(u"file:///foo/bar/baz"_s)), QUrl(u"file:///foo/bar/baz"_s)); QCOMPARE(qMediaFromUserInput(QUrl(u"http://foo/bar/baz"_s)), QUrl(u"http://foo/bar/baz"_s)); QCOMPARE(qMediaFromUserInput(QUrl(u"foo/bar/baz"_s)), QUrl::fromLocalFile(QDir::currentPath() + u"/foo/bar/baz"_s)); + +#ifdef Q_OS_WIN + QCOMPARE(qMediaFromUserInput(QUrl(u"C:/foo/bar/baz"_s)), QUrl(u"file:///c:/foo/bar/baz"_s)); +#else + QCOMPARE(qMediaFromUserInput(QUrl(u"C:/foo/bar/baz"_s)), QUrl(u"c:/foo/bar/baz"_s)); +#endif } QTEST_MAIN(tst_QMultimediaUtils) diff --git a/tests/auto/unit/multimedia/qsamplecache/testdata/corrupted.wav b/tests/auto/unit/multimedia/qsamplecache/testdata/corrupted.wav Binary files differnew file mode 100644 index 000000000..9c6c83597 --- /dev/null +++ b/tests/auto/unit/multimedia/qsamplecache/testdata/corrupted.wav diff --git a/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp b/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp index 0a0e10090..c9fa8e6c1 100644 --- a/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp +++ b/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp @@ -17,6 +17,7 @@ private slots: void testEnoughCapacity(); void testNotEnoughCapacity(); void testInvalidFile(); + void testIncompatibleFile(); private: @@ -96,6 +97,7 @@ void tst_QSampleCache::testEnoughCapacity() void tst_QSampleCache::testNotEnoughCapacity() { + using namespace Qt::Literals; QSampleCache cache; QSample* sample = cache.requestSample(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.wav"))); @@ -108,6 +110,8 @@ void tst_QSampleCache::testNotEnoughCapacity() QVERIFY(!cache.isCached(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.wav")))); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + QRegularExpression("QSampleCache: usage .* out of limit .*")); sample = cache.requestSample(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.wav"))); QVERIFY(sample); QVERIFY(cache.isLoading()); @@ -117,7 +121,10 @@ void tst_QSampleCache::testNotEnoughCapacity() QVERIFY(cache.isCached(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.wav")))); // load another sample to force sample cache to destroy first sample - QSample* sampleOther = cache.requestSample(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test2.wav"))); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + QRegularExpression("QSampleCache: usage .* out of limit .*")); + QSample *sampleOther = + cache.requestSample(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test2.wav"))); QVERIFY(sampleOther); QVERIFY(cache.isLoading()); QTRY_VERIFY(!cache.isLoading()); @@ -139,6 +146,25 @@ void tst_QSampleCache::testInvalidFile() QVERIFY(!cache.isCached(QUrl::fromLocalFile("invalid"))); } +void tst_QSampleCache::testIncompatibleFile() +{ + QSampleCache cache; + cache.setCapacity(10024); + + // Load a sample that is known to fail and verify that + // it remains in the cache with an error status. + const QUrl corruptedWavUrl = QUrl::fromLocalFile(QFINDTESTDATA("testdata/corrupted.wav")); + for (int i = 0; i < 3; ++i) { + QSample* sample = cache.requestSample(corruptedWavUrl); + QVERIFY(sample); + QTRY_VERIFY(!cache.isLoading()); + QCOMPARE(sample->state(), QSample::Error); + sample->release(); + + QVERIFY(cache.isCached(corruptedWavUrl)); + } +} + QTEST_MAIN(tst_QSampleCache) #include "tst_qsamplecache.moc" diff --git a/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt b/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt index f5b152034..ff5ceed64 100644 --- a/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt @@ -14,5 +14,5 @@ qt_internal_add_test(tst_qscreencapture # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp b/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp index c84fa09f6..a0133048d 100644 --- a/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp +++ b/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp @@ -33,11 +33,6 @@ private slots: void mapMemoryBufferWithReadOnly_doesntDetachArray(); - void mapMemoryBufferWithWriteModes_detachsArray_data(); - void mapMemoryBufferWithWriteModes_detachsArray(); - - void underlyingByteArray_returnsCorrectValueForPlanes(); - void unmap_resetsMappedState_whenBufferIsMapped_data(); void unmap_resetsMappedState_whenBufferIsMapped(); @@ -158,45 +153,12 @@ void tst_QVideoBuffers::map_doesNothing_whenBufferIsMapped() void tst_QVideoBuffers::mapMemoryBufferWithReadOnly_doesntDetachArray() { auto buffer = createMemoryBuffer(); - auto underlyingArray = buffer->underlyingByteArray(0); auto mappedData = buffer->map(QVideoFrame::ReadOnly); QCOMPARE(mappedData.nPlanes, 1); - QCOMPARE(mappedData.data[0], reinterpret_cast<const uchar *>(underlyingArray.constData())); QCOMPARE(mappedData.data[0], reinterpret_cast<const uchar *>(m_byteArray.constData())); } -void tst_QVideoBuffers::mapMemoryBufferWithWriteModes_detachsArray_data() -{ - QTest::addColumn<QVideoFrame::MapMode>("mapMode"); - - QTest::newRow(mapModeToString(QVideoFrame::WriteOnly).toUtf8().constData()) << QVideoFrame::WriteOnly; - QTest::newRow(mapModeToString(QVideoFrame::WriteOnly).toUtf8().constData()) << QVideoFrame::WriteOnly; -} - -void tst_QVideoBuffers::mapMemoryBufferWithWriteModes_detachsArray() -{ - QFETCH(QVideoFrame::MapMode, mapMode); - - auto buffer = createMemoryBuffer(); - auto underlyingArray = buffer->underlyingByteArray(0); - - auto mappedData = buffer->map(mapMode); - QCOMPARE(mappedData.nPlanes, 1); - QCOMPARE_NE(mappedData.data[0], reinterpret_cast<const uchar *>(underlyingArray.constData())); -} - -void tst_QVideoBuffers::underlyingByteArray_returnsCorrectValueForPlanes() -{ - auto buffer = createMemoryBuffer(); - - QCOMPARE(buffer->underlyingByteArray(0).constData(), m_byteArray.constData()); - - QVERIFY(buffer->underlyingByteArray(-1).isNull()); - QVERIFY(buffer->underlyingByteArray(1).isNull()); - QVERIFY(buffer->underlyingByteArray(2).isNull()); -} - void tst_QVideoBuffers::unmap_resetsMappedState_whenBufferIsMapped_data() { generateImageAndMemoryBuffersWithAllModes(); diff --git a/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp b/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp index 1c1efa2d3..25092c2b7 100644 --- a/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp +++ b/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp @@ -9,6 +9,7 @@ #include <QtGui/QImage> #include <QtCore/QPointer> #include <QtMultimedia/private/qtmultimedia-config_p.h> +#include "private/qvideoframeconverter_p.h" // Adds an enum, and the stringized version #define ADD_ENUM_TEST(x) \ @@ -17,6 +18,53 @@ << QString(QLatin1String(#x)); +QSet s_pixelFormats{ QVideoFrameFormat::Format_ARGB8888, + QVideoFrameFormat::Format_ARGB8888_Premultiplied, + QVideoFrameFormat::Format_XRGB8888, + QVideoFrameFormat::Format_BGRA8888, + QVideoFrameFormat::Format_BGRA8888_Premultiplied, + QVideoFrameFormat::Format_BGRX8888, + QVideoFrameFormat::Format_ABGR8888, + QVideoFrameFormat::Format_XBGR8888, + QVideoFrameFormat::Format_RGBA8888, + QVideoFrameFormat::Format_RGBX8888, + QVideoFrameFormat::Format_NV12, + QVideoFrameFormat::Format_NV21, + QVideoFrameFormat::Format_IMC1, + QVideoFrameFormat::Format_IMC2, + QVideoFrameFormat::Format_IMC3, + QVideoFrameFormat::Format_IMC4, + QVideoFrameFormat::Format_AYUV, + QVideoFrameFormat::Format_AYUV_Premultiplied, + QVideoFrameFormat::Format_YV12, + QVideoFrameFormat::Format_YUV420P, + QVideoFrameFormat::Format_YUV422P, + QVideoFrameFormat::Format_UYVY, + QVideoFrameFormat::Format_YUYV, + QVideoFrameFormat::Format_Y8, + QVideoFrameFormat::Format_Y16, + QVideoFrameFormat::Format_P010, + QVideoFrameFormat::Format_P016, + QVideoFrameFormat::Format_YUV420P10 }; + +bool isSupportedPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat) +{ +#ifdef Q_OS_ANDROID + // TODO: QTBUG-125238 + switch (pixelFormat) { + case QVideoFrameFormat::Format_Y16: + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + case QVideoFrameFormat::Format_YUV420P10: + return false; + default: + return true; + } +#else + return true; +#endif +} + class tst_QVideoFrame : public QObject { Q_OBJECT @@ -51,6 +99,9 @@ private slots: void formatConversion_data(); void formatConversion(); + void qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames_data(); + void qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames(); + void isMapped(); void isReadable(); void isWritable(); @@ -59,6 +110,8 @@ private slots: void image(); void emptyData(); + + void mirrored_takesValue_fromVideoFrameFormat(); }; class QtTestDummyVideoBuffer : public QObject, public QAbstractVideoBuffer @@ -820,6 +873,69 @@ void tst_QVideoFrame::formatConversion() QCOMPARE(QVideoFrameFormat::imageFormatFromPixelFormat(pixelFormat), imageFormat); } +void tst_QVideoFrame::qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames_data() { + QTest::addColumn<QSize>("size"); + QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); + QTest::addColumn<bool>("forceCpuConversion"); + QTest::addColumn<bool>("supportedOnPlatform"); + + const std::vector<QSize> sizes{ + // Even sized + { 2, 2 }, + { 2, 10 }, + { 10, 2 }, + { 640, 480 }, + { 4096, 2160 }, + // Odd sized + { 0, 0 }, + { 3, 3 }, + { 2, 3 }, + { 3, 2 }, + { 641, 480 }, + { 640, 481 }, + // TODO: Crashes + // { 1, 1 } // TODO: Division by zero in QVideoFrame::map (Debug) + // { 1, 2 } // TODO: D3D validation error in QRhiD3D11::executeCommandBuffer + // { 2, 1 } // TODO: D3D validation error in QRhiD3D11::executeCommandBuffer + }; + + for (const QSize &size : sizes) { + for (const QVideoFrameFormat::PixelFormat pixelFormat : s_pixelFormats) { + for (const bool forceCpu : { false, true }) { + + if (pixelFormat == QVideoFrameFormat::Format_YUV420P10 && forceCpu) + continue; // TODO: Cpu conversion not implemented + + QString name = QStringLiteral("%1x%2_%3%4") + .arg(size.width()) + .arg(size.height()) + .arg(QVideoFrameFormat::pixelFormatToString(pixelFormat)) + .arg(forceCpu ? "_cpu" : ""); + + QTest::addRow("%s", name.toLatin1().data()) + << size << pixelFormat << forceCpu << isSupportedPixelFormat(pixelFormat); + } + } + } +} + +void tst_QVideoFrame::qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames() { + QFETCH(const QSize, size); + QFETCH(const QVideoFrameFormat::PixelFormat, pixelFormat); + QFETCH(const bool, forceCpuConversion); + QFETCH(const bool, supportedOnPlatform); + + const QVideoFrameFormat format{ size, pixelFormat }; + const QVideoFrame frame{ format }; + const QImage actual = qImageFromVideoFrame(frame, QtVideo::Rotation::None, false, false, + forceCpuConversion); + + if (supportedOnPlatform) + QCOMPARE_EQ(actual.isNull(), size.isEmpty()); + // Otherwise, we don't expect an image being produced, although it might. + // TODO: Investigate why 16 bit formats fail on some Android flavors. +} + #define TEST_MAPPED(frame, mode) \ do { \ QVERIFY(frame.bits(0)); \ @@ -992,6 +1108,19 @@ void tst_QVideoFrame::emptyData() QVERIFY(!f.map(QVideoFrame::ReadOnly)); } +void tst_QVideoFrame::mirrored_takesValue_fromVideoFrameFormat() +{ + QVideoFrameFormat format(QSize(10, 20), QVideoFrameFormat::Format_ARGB8888); + format.setMirrored(true); + + QVideoFrame frame(format); + QVERIFY(frame.mirrored()); + + frame.setMirrored(false); + QVERIFY(!frame.mirrored()); + QVERIFY(!frame.surfaceFormat().isMirrored()); +} + QTEST_MAIN(tst_QVideoFrame) #include "tst_qvideoframe.moc" diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt b/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt index ed1aa7062..d2e3086d2 100644 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt @@ -7,19 +7,12 @@ file(GLOB_RECURSE test_data_glob testdata/*) list(APPEND testdata_resource_files ${test_data_glob}) - qt_internal_add_test(tst_qvideoframecolormanagement SOURCES tst_qvideoframecolormanagement.cpp LIBRARIES Qt::Gui Qt::MultimediaPrivate + BUILTIN_TESTDATA TESTDATA ${testdata_resource_files} ) - -qt_internal_add_resource(tst_qvideoframecolormanagement "testdata" - PREFIX - "/" - FILES - ${testdata_resource_files} -) diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg Binary files differindex 5ed72dbd8..52b0f620b 100644 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Full.png Binary files differdeleted file mode 100644 index 1ffaa49a7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Video.png Binary files differdeleted file mode 100644 index bbb449843..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Full.png Binary files differdeleted file mode 100644 index 8d23d5f97..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Video.png Binary files differdeleted file mode 100644 index b93a5bbb5..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Full.png Binary files differdeleted file mode 100644 index fd03d6c1b..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Video.png Binary files differdeleted file mode 100644 index ba25be895..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_420p_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 1f7981cfb..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 1f7981cfb..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Full.png Binary files differdeleted file mode 100644 index 15cc4c820..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Video.png Binary files differdeleted file mode 100644 index 2adc8f256..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Full.png Binary files differdeleted file mode 100644 index 7e4dda498..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Video.png Binary files differdeleted file mode 100644 index 041390cf2..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Full.png Binary files differdeleted file mode 100644 index f8ebfd1c1..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Video.png Binary files differdeleted file mode 100644 index c4f0d8481..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_422p_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Full.png Binary files differdeleted file mode 100644 index 1ffaa49a7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Video.png Binary files differdeleted file mode 100644 index bbb449843..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Full.png Binary files differdeleted file mode 100644 index 8d23d5f97..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Video.png Binary files differdeleted file mode 100644 index b93a5bbb5..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Full.png Binary files differdeleted file mode 100644 index fd03d6c1b..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Video.png Binary files differdeleted file mode 100644 index ba25be895..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index ae268bb2c..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index ae268bb2c..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Full.png Binary files differdeleted file mode 100644 index 936c09291..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Video.png Binary files differdeleted file mode 100644 index b82d839ff..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Full.png Binary files differdeleted file mode 100644 index b31fe973f..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Video.png Binary files differdeleted file mode 100644 index acee1379f..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Full.png Binary files differdeleted file mode 100644 index 29907ab63..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Video.png Binary files differdeleted file mode 100644 index 484bebb18..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png Binary files differnew file mode 100644 index 000000000..90b2b3601 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png Binary files differnew file mode 100644 index 000000000..90b2b3601 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png Binary files differnew file mode 100644 index 000000000..2e78cfc31 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d673b7ce5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png Binary files differnew file mode 100644 index 000000000..8be30a706 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png Binary files differnew file mode 100644 index 000000000..1f64ea0f1 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png Binary files differnew file mode 100644 index 000000000..24fb9065e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png Binary files differnew file mode 100644 index 000000000..f737d8602 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Full.png Binary files differdeleted file mode 100644 index 1ffaa49a7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Video.png Binary files differdeleted file mode 100644 index bbb449843..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Full.png Binary files differdeleted file mode 100644 index 8d23d5f97..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Video.png Binary files differdeleted file mode 100644 index b93a5bbb5..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Full.png Binary files differdeleted file mode 100644 index fd03d6c1b..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Video.png Binary files differdeleted file mode 100644 index ba25be895..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5a9b205d7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5a9b205d7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Full.png Binary files differdeleted file mode 100644 index 7b4bf0e80..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Video.png Binary files differdeleted file mode 100644 index c4337e393..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Full.png Binary files differdeleted file mode 100644 index ffb3cb44d..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Video.png Binary files differdeleted file mode 100644 index b0bb04ff6..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Full.png Binary files differdeleted file mode 100644 index 8d0863068..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Video.png Binary files differdeleted file mode 100644 index 13768c1f8..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png Binary files differnew file mode 100644 index 000000000..6efa73ea2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png Binary files differnew file mode 100644 index 000000000..6efa73ea2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png Binary files differnew file mode 100644 index 000000000..8d6a36a1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png Binary files differnew file mode 100644 index 000000000..dab23bf0d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png Binary files differnew file mode 100644 index 000000000..36e787cef --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png Binary files differnew file mode 100644 index 000000000..01e6ab967 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png Binary files differnew file mode 100644 index 000000000..22beff2e8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png Binary files differnew file mode 100644 index 000000000..c2af074b8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Full.png Binary files differdeleted file mode 100644 index 1ffaa49a7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Video.png Binary files differdeleted file mode 100644 index bbb449843..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Full.png Binary files differdeleted file mode 100644 index 8d23d5f97..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Video.png Binary files differdeleted file mode 100644 index b93a5bbb5..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Full.png Binary files differdeleted file mode 100644 index fd03d6c1b..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Video.png Binary files differdeleted file mode 100644 index ba25be895..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 5ee0ffd50..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Full.png Binary files differdeleted file mode 100644 index 1ffaa49a7..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Video.png Binary files differdeleted file mode 100644 index bbb449843..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Full.png Binary files differdeleted file mode 100644 index 8d23d5f97..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Video.png Binary files differdeleted file mode 100644 index b93a5bbb5..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Full.png Binary files differdeleted file mode 100644 index fd03d6c1b..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Video.png Binary files differdeleted file mode 100644 index ba25be895..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png Binary files differnew file mode 100644 index 000000000..71e107b8a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..1242dd25b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png Binary files differnew file mode 100644 index 000000000..71e107b8a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..1242dd25b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png Binary files differnew file mode 100644 index 000000000..58a7ebc92 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..4286840f8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d8756caac --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..fb6d356f8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png Binary files differnew file mode 100644 index 000000000..905568bf9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..d819e478c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png Binary files differnew file mode 100644 index 000000000..f374df207 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..2fbc2225c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png Binary files differnew file mode 100644 index 000000000..d2ee0f8e2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..d819e478c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png Binary files differnew file mode 100644 index 000000000..740de7f79 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..d19223883 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png Binary files differnew file mode 100644 index 000000000..ad76d393a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..68509c232 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png Binary files differnew file mode 100644 index 000000000..ad76d393a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..68509c232 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png Binary files differnew file mode 100644 index 000000000..a6e47132c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2cb927d35 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d9760b9c9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..3f65f27db --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png Binary files differnew file mode 100644 index 000000000..04ae5e1cd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..299548d61 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png Binary files differnew file mode 100644 index 000000000..9faa15fad --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..d544f8767 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png Binary files differnew file mode 100644 index 000000000..84b04ff9e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..299548d61 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png Binary files differnew file mode 100644 index 000000000..505752c10 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7da761925 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 4fd00f938..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 4fd00f938..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Full.png Binary files differdeleted file mode 100644 index 309454576..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Video.png Binary files differdeleted file mode 100644 index f97e71817..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Full.png Binary files differdeleted file mode 100644 index d513a8123..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Video.png Binary files differdeleted file mode 100644 index 6e9c36b39..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Full.png Binary files differdeleted file mode 100644 index c0568cb62..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Video.png Binary files differdeleted file mode 100644 index 207fc0be1..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png Binary files differnew file mode 100644 index 000000000..74fd12726 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png Binary files differnew file mode 100644 index 000000000..e358d16d8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png Binary files differnew file mode 100644 index 000000000..cb1cbbd34 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png Binary files differnew file mode 100644 index 000000000..6dd95a078 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png Binary files differnew file mode 100644 index 000000000..3e92f3695 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png Binary files differnew file mode 100644 index 000000000..e94891e1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png Binary files differnew file mode 100644 index 000000000..b1dc781f2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..584ad4c25 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png Binary files differnew file mode 100644 index 000000000..b1dc781f2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..584ad4c25 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png Binary files differnew file mode 100644 index 000000000..619ee36a4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..16445be0c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png Binary files differnew file mode 100644 index 000000000..881f6be33 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..9c7e87238 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png Binary files differnew file mode 100644 index 000000000..b1d3111df --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..1a3025e2d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png Binary files differnew file mode 100644 index 000000000..e4d1ce940 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..614b71e3e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png Binary files differnew file mode 100644 index 000000000..b1d3111df --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..1a3025e2d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png Binary files differnew file mode 100644 index 000000000..df8df3edd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..d6bed0482 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png Binary files differnew file mode 100644 index 000000000..130a3b541 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..61d2c6ca0 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png Binary files differnew file mode 100644 index 000000000..130a3b541 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..61d2c6ca0 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png Binary files differnew file mode 100644 index 000000000..21ed2218a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..188efe1d9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png Binary files differnew file mode 100644 index 000000000..f60f53d02 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..512c467b6 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png Binary files differnew file mode 100644 index 000000000..df59b71e7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..bfc57d849 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png Binary files differnew file mode 100644 index 000000000..dbca71c70 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..52f4b0223 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png Binary files differnew file mode 100644 index 000000000..df59b71e7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..bfc57d849 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png Binary files differnew file mode 100644 index 000000000..3479bb890 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..b3a488e2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png Binary files differnew file mode 100644 index 000000000..20b24da65 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png Binary files differnew file mode 100644 index 000000000..20b24da65 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png Binary files differnew file mode 100644 index 000000000..b96379a0b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png Binary files differnew file mode 100644 index 000000000..c77645b59 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png Binary files differnew file mode 100644 index 000000000..a1b8b62da --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png Binary files differnew file mode 100644 index 000000000..7a69f6afa --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png Binary files differnew file mode 100644 index 000000000..644b083fe --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png Binary files differnew file mode 100644 index 000000000..d4e9debd7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png Binary files differnew file mode 100644 index 000000000..74fd12726 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png Binary files differnew file mode 100644 index 000000000..e358d16d8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png Binary files differnew file mode 100644 index 000000000..cb1cbbd34 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png Binary files differnew file mode 100644 index 000000000..6dd95a078 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png Binary files differnew file mode 100644 index 000000000..3e92f3695 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png Binary files differnew file mode 100644 index 000000000..e94891e1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Full.png Binary files differdeleted file mode 100644 index 4fd00f938..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Video.png Binary files differdeleted file mode 100644 index 4fd00f938..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_AdobeRgb_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Full.png Binary files differdeleted file mode 100644 index 309454576..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Video.png Binary files differdeleted file mode 100644 index f97e71817..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT2020_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Full.png Binary files differdeleted file mode 100644 index d513a8123..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Video.png Binary files differdeleted file mode 100644 index 6e9c36b39..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT601_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Full.png Binary files differdeleted file mode 100644 index c0568cb62..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Full.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Video.png Binary files differdeleted file mode 100644 index 207fc0be1..000000000 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_BT709_Video.png +++ /dev/null diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png Binary files differnew file mode 100644 index 000000000..74fd12726 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png Binary files differnew file mode 100644 index 000000000..e358d16d8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png Binary files differnew file mode 100644 index 000000000..cb1cbbd34 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png Binary files differnew file mode 100644 index 000000000..6dd95a078 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png Binary files differnew file mode 100644 index 000000000..3e92f3695 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png Binary files differnew file mode 100644 index 000000000..e94891e1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp b/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp index ec711e98f..551a88c0d 100644 --- a/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp @@ -6,10 +6,15 @@ #include <qvideoframe.h> #include <qvideoframeformat.h> #include "private/qmemoryvideobuffer_p.h" +#include "private/qvideoframeconverter_p.h" +#include "private/qplatformmediaintegration_p.h" +#include "private/qimagevideobuffer_p.h" #include <QtGui/QColorSpace> #include <QtGui/QImage> #include <QtCore/QPointer> +#include "../../../integration/shared/mediabackendutils.h" + QT_USE_NAMESPACE namespace { @@ -20,6 +25,7 @@ struct TestParams QVideoFrameFormat::PixelFormat pixelFormat; QVideoFrameFormat::ColorSpace colorSpace; QVideoFrameFormat::ColorRange colorRange; + bool forceCpu; }; QString toString(QVideoFrameFormat::ColorRange r) @@ -43,44 +49,75 @@ std::vector<QVideoFrameFormat::ColorRange> colorRanges() }; } +const QSet s_formats{ QVideoFrameFormat::Format_ARGB8888, + QVideoFrameFormat::Format_ARGB8888_Premultiplied, + QVideoFrameFormat::Format_XRGB8888, + QVideoFrameFormat::Format_BGRA8888, + QVideoFrameFormat::Format_BGRA8888_Premultiplied, + QVideoFrameFormat::Format_BGRX8888, + QVideoFrameFormat::Format_ABGR8888, + QVideoFrameFormat::Format_XBGR8888, + QVideoFrameFormat::Format_RGBA8888, + QVideoFrameFormat::Format_RGBX8888, + QVideoFrameFormat::Format_NV12, + QVideoFrameFormat::Format_NV21, + QVideoFrameFormat::Format_IMC1, + QVideoFrameFormat::Format_IMC2, + QVideoFrameFormat::Format_IMC3, + QVideoFrameFormat::Format_IMC4, + QVideoFrameFormat::Format_AYUV, + QVideoFrameFormat::Format_AYUV_Premultiplied, + QVideoFrameFormat::Format_YV12, + QVideoFrameFormat::Format_YUV420P, + QVideoFrameFormat::Format_YUV422P, + QVideoFrameFormat::Format_UYVY, + QVideoFrameFormat::Format_YUYV, + QVideoFrameFormat::Format_Y8, + QVideoFrameFormat::Format_Y16, + QVideoFrameFormat::Format_P010, + QVideoFrameFormat::Format_P016, + QVideoFrameFormat::Format_YUV420P10 }; + +bool hasCorrespondingFFmpegFormat(QVideoFrameFormat::PixelFormat format) +{ + return format != QVideoFrameFormat::Format_AYUV + && format != QVideoFrameFormat::Format_AYUV_Premultiplied; +} + +bool supportsCpuConversion(QVideoFrameFormat::PixelFormat format) +{ + return format != QVideoFrameFormat::Format_YUV420P10; +} + QString toString(QVideoFrameFormat::PixelFormat f) { - switch (f) { - case QVideoFrameFormat::Format_NV12: - return "nv12"; - case QVideoFrameFormat::Format_NV21: - return "nv21"; - case QVideoFrameFormat::Format_IMC1: - return "imc1"; - case QVideoFrameFormat::Format_IMC2: - return "imc2"; - case QVideoFrameFormat::Format_IMC3: - return "imc3"; - case QVideoFrameFormat::Format_IMC4: - return "imc4"; - case QVideoFrameFormat::Format_YUV420P: - return "420p"; - case QVideoFrameFormat::Format_YUV422P: - return "422p"; - case QVideoFrameFormat::Format_UYVY: - return "uyvy"; - case QVideoFrameFormat::Format_YUYV: - return "yuyv"; - default: - Q_ASSERT(false); - return ""; // Not implemented yet - } + return QVideoFrameFormat::pixelFormatToString(f); } -std::vector<QVideoFrameFormat::PixelFormat> pixelFormats() +QSet<QVideoFrameFormat::PixelFormat> pixelFormats() { - return { QVideoFrameFormat::Format_NV12, QVideoFrameFormat::Format_NV21, - QVideoFrameFormat::Format_IMC1, QVideoFrameFormat::Format_IMC2, - QVideoFrameFormat::Format_IMC3, QVideoFrameFormat::Format_IMC4, - QVideoFrameFormat::Format_YUV420P, QVideoFrameFormat::Format_YUV422P, - QVideoFrameFormat::Format_UYVY, QVideoFrameFormat::Format_YUYV }; + return s_formats; } +bool isSupportedPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat) +{ +#ifdef Q_OS_ANDROID + // TODO: QTBUG-125238 + switch (pixelFormat) { + case QVideoFrameFormat::Format_Y16: + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + case QVideoFrameFormat::Format_YUV420P10: + return false; + default: + return true; + } +#else + return true; +#endif +} + + QString toString(QVideoFrameFormat::ColorSpace s) { switch (s) { @@ -106,11 +143,15 @@ std::vector<QVideoFrameFormat::ColorSpace> colorSpaces() QString name(const TestParams &p) { - return QStringLiteral("%1_%2_%3_%4") - .arg(p.fileName) - .arg(toString(p.pixelFormat)) - .arg(toString(p.colorSpace)) - .arg(toString(p.colorRange)); + QString name = QStringLiteral("%1_%2_%3_%4%5") + .arg(p.fileName) + .arg(toString(p.pixelFormat)) + .arg(toString(p.colorSpace)) + .arg(toString(p.colorRange)) + .arg(p.forceCpu ? "_cpu" : "") + .toLower(); + name.replace(" ", "_"); + return name; } QString path(const QTemporaryDir &dir, const TestParams ¶m, const QString &suffix = ".png") @@ -118,206 +159,6 @@ QString path(const QTemporaryDir &dir, const TestParams ¶m, const QString &s return dir.filePath(name(param) + suffix); } -// clang-format off - -class RgbToYCbCrConverter -{ -public: - constexpr RgbToYCbCrConverter(double Wr, double Wg) - : m_wr{ Wr }, m_wg{ Wg }, m_wb{ 1.0 - Wr - Wg } - { } - - // Calculate Y in range [0..255] - constexpr double Y(QRgb rgb) const - { - return m_wr * qRed(rgb) + m_wg * qGreen(rgb) + m_wb * qBlue(rgb); - } - - // Calculate Cb in range [0..255] - constexpr double Cb(QRgb rgb) const - { - return (qBlue(rgb) - Y(rgb)) / (2 * (1.0 - m_wb)) + 255.0 / 2; - } - - // Calculate Cr in range [0..255] - constexpr double Cr(QRgb rgb) const - { - return (qRed(rgb) - Y(rgb)) / (2 * (1.0 - m_wr)) + 255.0 / 2; - } - -private: - const double m_wr; - const double m_wg; - const double m_wb; -}; - -// clang-format on - -constexpr RgbToYCbCrConverter rgb2yuv_bt709_full{0.2126, 0.7152}; - -constexpr uchar double2uchar(double v) -{ - return static_cast<uchar>(std::clamp(v + 0.5, 0.5, 255.5)); -} - -constexpr uchar rgb2y(const QRgb &rgb) -{ - const double Y = rgb2yuv_bt709_full.Y(rgb); - return double2uchar(Y); -} - -constexpr uchar rgb2u(const QRgb &rgb) -{ - const double U = rgb2yuv_bt709_full.Cb(rgb); - return double2uchar(U); -} - -constexpr uchar rgb2v(const QRgb &rgb) -{ - const double V = rgb2yuv_bt709_full.Cr(rgb); - return double2uchar(V); -} - -void rgb2y_planar(const QImage &image, QVideoFrame &frame, int yPlane) -{ - uchar *bits = frame.bits(yPlane); - for (int row = 0; row < image.height(); ++row) { - for (int col = 0; col < image.width(); ++col) { - const QRgb pixel = image.pixel(col, row); - bits[col] = rgb2y(pixel); - } - bits += frame.bytesPerLine(yPlane); - } -} - -void rgb2uv_planar(const QImage &image, QVideoFrame &frame) -{ - uchar *vBits = nullptr; - uchar *uBits = nullptr; - int vStride = 0; - int uStride = 0; - int sampleIncrement = 1; - int verticalScale = 2; - if (frame.pixelFormat() == QVideoFrameFormat::Format_IMC1) { - uStride = frame.bytesPerLine(2); - vStride = frame.bytesPerLine(1); - uBits = frame.bits(2); - vBits = frame.bits(1); - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_IMC2) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(1); - uBits = frame.bits(1) + vStride / 2; - vBits = frame.bits(1); - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_IMC3) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(2); - uBits = frame.bits(1); - vBits = frame.bits(2); - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_IMC4) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(1); - uBits = frame.bits(1); - vBits = frame.bits(1) + vStride / 2; - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_NV12) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(1); - uBits = frame.bits(1); - vBits = frame.bits(1) + 1; - sampleIncrement = 2; - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_NV21) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(1); - uBits = frame.bits(1) + 1; - vBits = frame.bits(1); - sampleIncrement = 2; - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_YUV420P) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(2); - uBits = frame.bits(1); - vBits = frame.bits(2); - } else if (frame.pixelFormat() == QVideoFrameFormat::Format_YUV422P) { - uStride = frame.bytesPerLine(1); - vStride = frame.bytesPerLine(2); - uBits = frame.bits(1); - vBits = frame.bits(2); - verticalScale = 1; - } - - const QImage downSampled = image.scaled(image.width() / 2, image.height() / verticalScale); - const int width = downSampled.width(); - const int height = downSampled.height(); - { - for (int row = 0; row < height; ++row) { - for (int col = 0; col < width; ++col) { - const QRgb pixel = downSampled.pixel(col, row); - uBits[col * sampleIncrement] = rgb2u(pixel); - vBits[col * sampleIncrement] = rgb2v(pixel); - } - vBits += vStride; - uBits += uStride; - } - } -} - -void naive_rgbToYuv_planar(const QImage &image, QVideoFrame &frame) -{ - Q_ASSERT(image.format() == QImage::Format_RGB32); - Q_ASSERT(frame.planeCount() > 1); - Q_ASSERT(image.size() == frame.size()); - - frame.map(QVideoFrame::WriteOnly); - - rgb2y_planar(image, frame, 0); - rgb2uv_planar(image, frame); - - frame.unmap(); -} - -void naive_rgbToYuv422(const QImage &image, QVideoFrame &frame) -{ - // Packed format uyvy or yuyv. Each 32 bit frame sample represents - // two pixels with distinct y values, but shared u and v values - Q_ASSERT(image.format() == QImage::Format_RGB32); - Q_ASSERT(frame.planeCount() == 1); - Q_ASSERT(image.size() == frame.size()); - - const QVideoFrameFormat::PixelFormat format = frame.pixelFormat(); - - Q_ASSERT(format == QVideoFrameFormat::Format_UYVY || format == QVideoFrameFormat::Format_YUYV); - - constexpr int plane = 0; - frame.map(QVideoFrame::WriteOnly); - - uchar *line = frame.bits(plane); - for (int row = 0; row < image.height(); ++row) { - uchar *bits = line; - for (int col = 0; col < image.width() - 1; col += 2) { - // Handle to image pixels at a time - const QRgb pixel0 = image.pixel(col, row); - const QRgb pixel1 = image.pixel(col + 1, row); - - // Down-sample u and v channels - bits[0] = (rgb2u(pixel0) + rgb2u(pixel1)) / 2; - bits[2] = (rgb2v(pixel0) + rgb2v(pixel1)) / 2; - - // But not the y-channel - bits[1] = rgb2y(pixel0); - bits[3] = rgb2y(pixel1); - - // Swizzle fom uyuv to yuyv - if (format == QVideoFrameFormat::Format_YUYV) { - std::swap(bits[0], bits[1]); - std::swap(bits[2], bits[3]); - } - - bits += 4; - } - line += frame.bytesPerLine(plane); - } - - frame.unmap(); -} - QVideoFrame createTestFrame(const TestParams ¶ms, const QImage &image) { QVideoFrameFormat format(image.size(), params.pixelFormat); @@ -325,27 +166,13 @@ QVideoFrame createTestFrame(const TestParams ¶ms, const QImage &image) format.setColorSpace(params.colorSpace); format.setColorTransfer(QVideoFrameFormat::ColorTransfer_Unknown); - QVideoFrame frame(format); - - if (params.pixelFormat == QVideoFrameFormat::Format_IMC1 - || params.pixelFormat == QVideoFrameFormat::Format_IMC2 - || params.pixelFormat == QVideoFrameFormat::Format_IMC3 - || params.pixelFormat == QVideoFrameFormat::Format_IMC4 - || params.pixelFormat == QVideoFrameFormat::Format_NV12 - || params.pixelFormat == QVideoFrameFormat::Format_NV21 - || params.pixelFormat == QVideoFrameFormat::Format_YUV420P - || params.pixelFormat == QVideoFrameFormat::Format_YUV422P) { - naive_rgbToYuv_planar(image, frame); - } else if (params.pixelFormat == QVideoFrameFormat::Format_UYVY - || params.pixelFormat == QVideoFrameFormat::Format_YUYV) { - naive_rgbToYuv422(image, frame); - } else { - qDebug() << "Not implemented yet"; - Q_ASSERT(false); - return {}; - } + auto buffer = std::make_unique<QImageVideoBuffer>(image); + QVideoFrameFormat imageFormat = { + image.size(), QVideoFrameFormat::pixelFormatFromImageFormat(image.format()) + }; - return frame; + QVideoFrame source{ buffer.release(), imageFormat }; + return QPlatformMediaIntegration::instance()->convertVideoFrame(source, format); } struct ImageDiffReport @@ -356,11 +183,6 @@ struct ImageDiffReport QImage DiffImage; // The difference image (absolute per-channel difference) }; -double aboveThresholdDiffRatio(const ImageDiffReport &report) -{ - return static_cast<double>(report.DiffCountAboveThreshold) / report.PixelCount; -} - int maxChannelDiff(QRgb lhs, QRgb rhs) { // clang-format off @@ -561,8 +383,13 @@ class tst_qvideoframecolormanagement : public QObject { Q_OBJECT private slots: + void initTestCase() + { + if (!isFFMPEGPlatform()) + QSKIP("This test requires the ffmpeg backend to create test frames"); + } - void toImage_savesWithCorrectColors_data() + void qImageFromVideoFrame_returnsQImageWithCorrectColors_data() { QTest::addColumn<QString>("fileName"); QTest::addColumn<TestParams>("params"); @@ -570,8 +397,22 @@ private slots: for (const QVideoFrameFormat::PixelFormat pixelFormat : pixelFormats()) { for (const QVideoFrameFormat::ColorSpace colorSpace : colorSpaces()) { for (const QVideoFrameFormat::ColorRange colorRange : colorRanges()) { - TestParams param{ file, pixelFormat, colorSpace, colorRange }; - QTest::addRow("%s", name(param).toLatin1().data()) << file << param; + for (const bool forceCpu : { false, true }) { + + if (!isSupportedPixelFormat(pixelFormat)) + continue; + + if (forceCpu && !supportsCpuConversion(pixelFormat)) + continue; // TODO: CPU Conversion not implemented + + if (!hasCorrespondingFFmpegFormat(pixelFormat)) + continue; + + TestParams param{ + file, pixelFormat, colorSpace, colorRange, forceCpu, + }; + QTest::addRow("%s", name(param).toLatin1().data()) << file << param; + } } } } @@ -579,10 +420,11 @@ private slots: } // This test is a regression test for the QMultimedia display pipeline. - // It compares rendered output (as created by toImage) against reference - // images stored to file. The reference images were created by the test - // itself, and does not verify correctness, just changes to render output. - void toImage_savesWithCorrectColors() + // It compares rendered output (as created by qImageFromVideoFrame) + // against reference images stored to file. The reference images were + // created by the test itself, and does not verify correctness, just + // changes to render output. + void qImageFromVideoFrame_returnsQImageWithCorrectColors() { QFETCH(const QString, fileName); QFETCH(const TestParams, params); @@ -593,7 +435,8 @@ private slots: const QVideoFrame frame = createTestFrame(params, templateImage); // Act - const QImage actual = frame.toImage(); + const QImage actual = + qImageFromVideoFrame(frame, QtVideo::Rotation::None, false, false, params.forceCpu); // Assert constexpr int diffThreshold = 4; @@ -607,10 +450,16 @@ private slots: // Verify that images are similar const double ratioAboveThreshold = static_cast<double>(result->DiffCountAboveThreshold) / result->PixelCount; - QCOMPARE_LT(ratioAboveThreshold, 0.01); - QCOMPARE_LT(result->MaxDiff, 5); + + // These thresholds are empirically determined to allow tests to pass in CI. + // If tests fail, review the difference between the reference and actual + // output to determine if it is a platform dependent inaccuracy before + // adjusting the limits + QCOMPARE_LT(ratioAboveThreshold, 0.01); // Fraction of pixels with larger differences + QCOMPARE_LT(result->MaxDiff, 6); // Maximum per-channel difference } + private: ReferenceData m_reference; }; diff --git a/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt b/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt index 4567e95ac..ce89424a8 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt @@ -16,6 +16,8 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qwavedecoder SOURCES tst_qwavedecoder.cpp + ../../../shared/qsinewavevalidator.h + data/gendata.sh LIBRARIES Qt::Gui Qt::MultimediaPrivate diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh b/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh index 99a04129b..a1723d767 100755 --- a/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh +++ b/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh @@ -17,14 +17,14 @@ for channel in 1 2; do endian="big" endian_extn="be" fi - for samplebits in 8 16 32; do - for samplerate in 44100 8000; do + for samplerate in 44100 8000; do + for samplebits in 8 16 24 32; do if [ $samplebits -ne 8 ]; then - sox -n --endian "${endian}" -c ${channel} -b ${samplebits} -r ${samplerate} isawav_${channel}_${samplebits}_${samplerate}_${endian_extn}.wav synth 0.25 sine 300-3300 + sox -n --endian "${endian}" -c ${channel} -b ${samplebits} -r ${samplerate} isawav_${channel}_${samplebits}_${samplerate}_${endian_extn}.wav synth 0.25 sine 220 else - sox -n -c ${channel} -b ${samplebits} -r ${samplerate} isawav_${channel}_${samplebits}_${samplerate}.wav synth 0.25 sine 300-3300 + sox -n -c ${channel} -b ${samplebits} -r ${samplerate} isawav_${channel}_${samplebits}_${samplerate}.wav synth 0.25 sine 220 fi done - done + sox -n --endian "${endian}" -c ${channel} -e float -b 32 -r ${samplerate} isawav_${channel}_f32_${samplerate}_${endian_extn}.wav synth 0.25 sine 220 + done done - diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_44100_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_44100_le.wav Binary files differindex 88b1a8379..eee821ce7 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_44100_le.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_44100_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_8000_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_8000_le.wav Binary files differindex 83a405907..c0ca2cbc2 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_8000_le.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_16_8000_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_44100_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_44100_le.wav Binary files differnew file mode 100644 index 000000000..a74b381e9 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_44100_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_8000_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_8000_le.wav Binary files differnew file mode 100644 index 000000000..aa745e8b3 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_24_8000_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_44100_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_44100_le.wav Binary files differindex 9c437b155..fed460cae 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_44100_le.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_44100_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_8000_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_8000_le.wav Binary files differindex f90a8bc35..135985a36 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_8000_le.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_32_8000_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_44100.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_44100.wav Binary files differindex 7d10829ea..d85bb5f0a 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_44100.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_44100.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000.wav Binary files differindex 76c08e89e..931ab0c65 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_44100_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_44100_le.wav Binary files differnew file mode 100644 index 000000000..895730a00 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_44100_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_8000_le.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_8000_le.wav Binary files differnew file mode 100644 index 000000000..4e106e4fc --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_f32_8000_le.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_44100_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_44100_be.wav Binary files differindex ca0cd425a..52e7703c3 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_44100_be.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_44100_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_8000_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_8000_be.wav Binary files differindex 3a684590b..41f0f1eac 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_8000_be.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_16_8000_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_44100_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_44100_be.wav Binary files differnew file mode 100644 index 000000000..05f02837e --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_44100_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_8000_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_8000_be.wav Binary files differnew file mode 100644 index 000000000..73ff5b186 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_24_8000_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_44100_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_44100_be.wav Binary files differindex f1aaf2906..fdc8d1eac 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_44100_be.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_44100_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_8000_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_8000_be.wav Binary files differindex c10c20872..48b50e77a 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_8000_be.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_32_8000_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_44100.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_44100.wav Binary files differindex befd02baf..b291393ef 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_44100.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_44100.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_8000.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_8000.wav Binary files differindex ce8b0d06a..b26f51359 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_8000.wav +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_8_8000.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_44100_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_44100_be.wav Binary files differnew file mode 100644 index 000000000..59569ee53 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_44100_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_8000_be.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_8000_be.wav Binary files differnew file mode 100644 index 000000000..847db5988 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_2_f32_8000_be.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp index 1c022218a..600d33e59 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp +++ b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp @@ -3,6 +3,7 @@ #include <QtTest/QtTest> #include <qwavedecoder.h> +#include "../../../shared/qsinewavevalidator.h" #include <QNetworkAccessManager> #include <QNetworkRequest> @@ -69,35 +70,52 @@ void tst_QWaveDecoder::file_data() QTest::addColumn<int>("channels"); QTest::addColumn<int>("samplesize"); QTest::addColumn<int>("samplerate"); - - QTest::newRow("File is empty") << testFilePath("empty.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; - QTest::newRow("File is one byte") << testFilePath("onebyte.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; - QTest::newRow("File is not a wav(text)") << testFilePath("notawav.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; - QTest::newRow("Wav file has no sample data") << testFilePath("nosampledata.wav") << tst_QWaveDecoder::NoSampleData << -1 << -1 << -1; - QTest::newRow("corrupt fmt chunk descriptor") << testFilePath("corrupt_fmtdesc_1_16_8000.le.wav") << tst_QWaveDecoder::FormatDescriptor << -1 << -1 << -1; - QTest::newRow("corrupt fmt string") << testFilePath("corrupt_fmtstring_1_16_8000.le.wav") << tst_QWaveDecoder::FormatString << -1 << -1 << -1; - QTest::newRow("corrupt data chunk descriptor") << testFilePath("corrupt_datadesc_1_16_8000.le.wav") << tst_QWaveDecoder::DataDescriptor << -1 << -1 << -1; - - QTest::newRow("File isawav_1_8_8000.wav") << testFilePath("isawav_1_8_8000.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; - QTest::newRow("File isawav_1_8_44100.wav") << testFilePath("isawav_1_8_44100.wav") << tst_QWaveDecoder::None << 1 << 8 << 44100; - QTest::newRow("File isawav_2_8_8000.wav") << testFilePath("isawav_2_8_8000.wav") << tst_QWaveDecoder::None << 2 << 8 << 8000; - QTest::newRow("File isawav_2_8_44100.wav") << testFilePath("isawav_2_8_44100.wav") << tst_QWaveDecoder::None << 2 << 8 << 44100; - - QTest::newRow("File isawav_1_16_8000_le.wav") << testFilePath("isawav_1_16_8000_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 8000; - QTest::newRow("File isawav_1_16_44100_le.wav") << testFilePath("isawav_1_16_44100_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100; - QTest::newRow("File isawav_2_16_8000_be.wav") << testFilePath("isawav_2_16_8000_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 8000; - QTest::newRow("File isawav_2_16_44100_be.wav") << testFilePath("isawav_2_16_44100_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 44100; + QTest::addColumn<bool>("validateContent"); + + // clang-format off + QTest::newRow("File is empty") << testFilePath("empty.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << true; + QTest::newRow("File is one byte") << testFilePath("onebyte.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << true; + QTest::newRow("File is not a wav(text)") << testFilePath("notawav.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << true; + QTest::newRow("Wav file has no sample data") << testFilePath("nosampledata.wav") << tst_QWaveDecoder::NoSampleData << -1 << -1 << -1 << true; + QTest::newRow("corrupt fmt chunk descriptor") << testFilePath("corrupt_fmtdesc_1_16_8000.le.wav") << tst_QWaveDecoder::FormatDescriptor << -1 << -1 << -1 << true; + QTest::newRow("corrupt fmt string") << testFilePath("corrupt_fmtstring_1_16_8000.le.wav") << tst_QWaveDecoder::FormatString << -1 << -1 << -1 << true; + QTest::newRow("corrupt data chunk descriptor") << testFilePath("corrupt_datadesc_1_16_8000.le.wav") << tst_QWaveDecoder::DataDescriptor << -1 << -1 << -1 << true; + + QTest::newRow("File isawav_1_8_8000.wav") << testFilePath("isawav_1_8_8000.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000 << true; + QTest::newRow("File isawav_1_8_44100.wav") << testFilePath("isawav_1_8_44100.wav") << tst_QWaveDecoder::None << 1 << 8 << 44100 << true; + QTest::newRow("File isawav_2_8_8000.wav") << testFilePath("isawav_2_8_8000.wav") << tst_QWaveDecoder::None << 2 << 8 << 8000 << true; + QTest::newRow("File isawav_2_8_44100.wav") << testFilePath("isawav_2_8_44100.wav") << tst_QWaveDecoder::None << 2 << 8 << 44100 << true; + + QTest::newRow("File isawav_1_16_8000_le.wav") << testFilePath("isawav_1_16_8000_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 8000 << true; + QTest::newRow("File isawav_1_16_44100_le.wav") << testFilePath("isawav_1_16_44100_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100 << true; + QTest::newRow("File isawav_2_16_8000_be.wav") << testFilePath("isawav_2_16_8000_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 8000 << true; + QTest::newRow("File isawav_2_16_44100_be.wav") << testFilePath("isawav_2_16_44100_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 44100 << true; // The next file has extra data in the wave header. - QTest::newRow("File isawav_1_16_44100_le_2.wav") << testFilePath("isawav_1_16_44100_le_2.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100; + QTest::newRow("File isawav_1_16_44100_le_2.wav") << testFilePath("isawav_1_16_44100_le_2.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100 << false; // The next file has embedded bext chunk with odd payload (QTBUG-122193) - QTest::newRow("File isawav_1_8_8000_odd_bext.wav") << testFilePath("isawav_1_8_8000_odd_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; + QTest::newRow("File isawav_1_8_8000_odd_bext.wav") << testFilePath("isawav_1_8_8000_odd_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000 << false; // The next file has embedded bext chunk with even payload - QTest::newRow("File isawav_1_8_8000_even_bext.wav") << testFilePath("isawav_1_8_8000_even_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; + QTest::newRow("File isawav_1_8_8000_even_bext.wav") << testFilePath("isawav_1_8_8000_even_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000 << false; + + // 24bit wav are not supported + QTest::newRow("File isawav_1_24_8000_le.wav") << testFilePath("isawav_1_24_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 24 << 8000 << true; + QTest::newRow("File isawav_1_24_44100_le.wav") << testFilePath("isawav_1_24_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 24 << 44100 << true; + QTest::newRow("File isawav_2_24_8000_be.wav") << testFilePath("isawav_2_24_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 24 << 8000 << true; + QTest::newRow("File isawav_2_24_44100_be.wav") << testFilePath("isawav_2_24_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 24 << 44100 << true; + // 32 bit waves are not supported - QTest::newRow("File isawav_1_32_8000_le.wav") << testFilePath("isawav_1_32_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 8000; - QTest::newRow("File isawav_1_32_44100_le.wav") << testFilePath("isawav_1_32_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 44100; - QTest::newRow("File isawav_2_32_8000_be.wav") << testFilePath("isawav_2_32_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 8000; - QTest::newRow("File isawav_2_32_44100_be.wav") << testFilePath("isawav_2_32_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 44100; + QTest::newRow("File isawav_1_32_8000_le.wav") << testFilePath("isawav_1_32_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 8000 << true; + QTest::newRow("File isawav_1_32_44100_le.wav") << testFilePath("isawav_1_32_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 44100 << true; + QTest::newRow("File isawav_2_32_8000_be.wav") << testFilePath("isawav_2_32_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 8000 << true; + QTest::newRow("File isawav_2_32_44100_be.wav") << testFilePath("isawav_2_32_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 44100 << true; + + // f32 waves are not supported + QTest::newRow("File isawav_1_f32_8000_le.wav") << testFilePath("isawav_1_f32_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 8000 << true; + QTest::newRow("File isawav_1_f32_44100_le.wav") << testFilePath("isawav_1_f32_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 44100 << true; + QTest::newRow("File isawav_2_f32_8000_be.wav") << testFilePath("isawav_2_f32_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 8000 << true; + QTest::newRow("File isawav_2_f32_44100_be.wav") << testFilePath("isawav_2_f32_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 44100 << true; + + // clang-format on } void tst_QWaveDecoder::file() @@ -107,6 +125,7 @@ void tst_QWaveDecoder::file() QFETCH(int, channels); QFETCH(int, samplesize); QFETCH(int, samplerate); + QFETCH(bool, validateContent); QFile stream; stream.setFileName(file); @@ -152,6 +171,31 @@ void tst_QWaveDecoder::file() QVERIFY(format.channelCount() == channels); QCOMPARE(format.bytesPerSample() * 8, samplesize); QVERIFY(format.sampleRate() == samplerate); + + std::array<QSineWaveValidator, 2> validators{ + QSineWaveValidator(220, format.sampleRate()), + QSineWaveValidator(220, format.sampleRate()), + }; + + auto expectedNumberOfFrames = format.sampleRate() / 4; // 250ms + for (int frame = 0; frame != expectedNumberOfFrames; ++frame) { + for (int channel = 0; channel != format.channelCount(); ++channel) { + QByteArray array = waveDecoder.read(format.bytesPerSample()); + float sample = format.normalizedSampleValue(array.constData()); + validators[channel].feedSample(sample); + } + } + + if (validateContent) { + QCOMPARE_GT(validators[0].peak(), 0.05); + QCOMPARE_LE(validators[0].peak(), 1); + QCOMPARE_LT(validators[0].notchPeak(), 0.05); + if (format.channelCount() == 2) { + QCOMPARE_GT(validators[1].peak(), 0.05); + QCOMPARE_LE(validators[1].peak(), 1); + QCOMPARE_LT(validators[1].notchPeak(), 0.05); + } + } } stream.close(); diff --git a/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt index 486f34690..3139677d7 100644 --- a/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt @@ -18,5 +18,5 @@ qt_internal_add_test(tst_qcamerawidgets Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt index 599042725..ef5292fdd 100644 --- a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt @@ -17,5 +17,5 @@ qt_internal_add_test(tst_qgraphicsvideoitem Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt index 7c54e67b9..f2aa5c496 100644 --- a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt @@ -19,5 +19,5 @@ qt_internal_add_test(tst_qmediaplayerwidgets Qt::MultimediaWidgetsPrivate Qt::Network Qt::Widgets - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt index b179b0b3b..016e13d7b 100644 --- a/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt @@ -17,5 +17,5 @@ qt_internal_add_test(tst_qvideowidget Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - MockMultimediaPlugin + Qt::MockMultimediaPlugin ) diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index 08e38e893..ff17b414d 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -3,9 +3,16 @@ add_subdirectory(audiodecoder) add_subdirectory(devices) +add_subdirectory(mediaformats) +add_subdirectory(minimal-audio-recorder) add_subdirectory(minimal-player) add_subdirectory(wasm) +if(QT_FEATURE_gstreamer) + add_subdirectory(gstreamer-custom-camera) + add_subdirectory(gstreamer-custom-camera-rtp) +endif() + if(TARGET Qt::Quick) add_subdirectory(qml-minimal-camera) add_subdirectory(qml-minimal-player) diff --git a/tests/manual/audiodecoder/audiodecoder.cpp b/tests/manual/audiodecoder/audiodecoder.cpp index e14834a3a..b398fe795 100644 --- a/tests/manual/audiodecoder/audiodecoder.cpp +++ b/tests/manual/audiodecoder/audiodecoder.cpp @@ -3,9 +3,8 @@ #include "audiodecoder.h" -#include <QFile> - -#include <stdio.h> +#include <QtCore/qfile.h> +#include <QtCore/qdebug.h> AudioDecoder::AudioDecoder(bool isPlayback, bool isDelete, const QString &targetFileName) : m_cout(stdout, QIODevice::WriteOnly), m_targetFilename(targetFileName) diff --git a/tests/manual/devices/main.cpp b/tests/manual/devices/main.cpp index 6a84a8c82..d382d4fe3 100644 --- a/tests/manual/devices/main.cpp +++ b/tests/manual/devices/main.cpp @@ -107,6 +107,10 @@ static void printVideoDeviceInfo(QTextStream &out, const QCameraDevice &cameraDe out.setFieldWidth(30); out << "Frame Rate: " << qSetFieldWidth(0) << "Min:" << format.minFrameRate() << " Max:" << format.maxFrameRate() << Qt::endl; + out.setFieldWidth(30); + out << "Format: " << qSetFieldWidth(0) + << QVideoFrameFormat::pixelFormatToString(format.pixelFormat()) << Qt::endl; + out << Qt::endl; } out << Qt::endl; diff --git a/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt b/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt new file mode 100644 index 000000000..0ddd01642 --- /dev/null +++ b/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(gstreamer-custom-camera-rtp LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/gstreamer-custom-camera-rtp") + +find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets QGstreamerMediaPluginImplPrivate) + +qt_add_executable( gstreamer-custom-camera-rtp WIN32 MACOSX_BUNDLE + gstreamer-custom-camera-rtp.cpp + Info.plist.in +) + +target_link_libraries( gstreamer-custom-camera-rtp PUBLIC + Qt::Widgets + Qt::Multimedia + Qt::MultimediaPrivate + Qt::MultimediaWidgets + + Qt::QGstreamerMediaPluginImplPrivate +) + +install(TARGETS gstreamer-custom-camera-rtp + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) + +set_target_properties( gstreamer-custom-camera-rtp PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in +) diff --git a/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in b/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in new file mode 100644 index 000000000..46a9ecf2d --- /dev/null +++ b/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleLongVersionString</key> + <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> + + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + + <key>CFBundleGetInfoString</key> + <string>${MACOSX_BUNDLE_INFO_STRING}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + + <key>NSCameraUsageDescription</key> + <string>Qt Multimedia Example</string> + <key>NSMicrophoneUsageDescription</key> + <string>Qt Multimedia Example</string> + + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> +</dict> +</plist> diff --git a/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp b/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp new file mode 100644 index 000000000..cb663099d --- /dev/null +++ b/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtMultimedia/QAudioOutput> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> +#include <QtMultimediaWidgets/QtMultimediaWidgets> +#include <QtWidgets/QApplication> +#include <QtCore/QCommandLineParser> + +#include <QtQGstreamerMediaPluginImpl/private/qgst_p.h> +#include <QtQGstreamerMediaPluginImpl/private/qgstpipeline_p.h> + +using namespace std::chrono_literals; +using namespace Qt::Literals; + +struct GStreamerRtpStreamSender +{ + GStreamerRtpStreamSender() + { + element = QGstElement::createFromPipelineDescription( + "videotestsrc ! jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=50004"_ba); + + pipeline.add(element); + pipeline.setStateSync(GstState::GST_STATE_PLAYING); + pipeline.dumpGraph("sender"); + } + + ~GStreamerRtpStreamSender() { pipeline.setStateSync(GstState::GST_STATE_NULL); } + + QGstPipeline pipeline = QGstPipeline::create("UdpSend"); + QGstElement element; +}; + +int main(int argc, char **argv) +{ + qputenv("QT_MEDIA_BACKEND", "gstreamer"); + + gst_init(&argc, &argv); + GStreamerRtpStreamSender sender; + + QApplication app(argc, argv); + + QByteArray pipelineString = + R"(udpsrc port=50004 ! application/x-rtp,encoding=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! videoconvert)"_ba; + QVideoWidget wid; + wid.show(); + + QMediaCaptureSession session; + session.setVideoSink(wid.videoSink()); + + QCamera *cam = QGStreamerPlatformSpecificInterface::instance()->makeCustomGStreamerCamera( + pipelineString, &session); + session.setCamera(cam); + cam->start(); + + return QApplication::exec(); +} diff --git a/tests/manual/gstreamer-custom-camera/CMakeLists.txt b/tests/manual/gstreamer-custom-camera/CMakeLists.txt new file mode 100644 index 000000000..f161a0630 --- /dev/null +++ b/tests/manual/gstreamer-custom-camera/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(gstreamer-custom-camera-rtp LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/gstreamer-custom-camera") + +find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets) + +qt_add_executable( gstreamer-custom-camera WIN32 MACOSX_BUNDLE + gstreamer-custom-camera.cpp + Info.plist.in +) + +target_link_libraries( gstreamer-custom-camera PUBLIC + Qt::Widgets + Qt::Multimedia + Qt::MultimediaPrivate + Qt::MultimediaWidgets +) + +install(TARGETS gstreamer-custom-camera + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) + +set_target_properties( gstreamer-custom-camera PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in +) diff --git a/tests/manual/gstreamer-custom-camera/Info.plist.in b/tests/manual/gstreamer-custom-camera/Info.plist.in new file mode 100644 index 000000000..46a9ecf2d --- /dev/null +++ b/tests/manual/gstreamer-custom-camera/Info.plist.in @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleLongVersionString</key> + <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> + + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + + <key>CFBundleGetInfoString</key> + <string>${MACOSX_BUNDLE_INFO_STRING}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + + <key>NSCameraUsageDescription</key> + <string>Qt Multimedia Example</string> + <key>NSMicrophoneUsageDescription</key> + <string>Qt Multimedia Example</string> + + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> +</dict> +</plist> diff --git a/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp b/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp new file mode 100644 index 000000000..dbd729d15 --- /dev/null +++ b/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtMultimedia/QAudioOutput> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> +#include <QtMultimediaWidgets/QtMultimediaWidgets> +#include <QtWidgets/QApplication> +#include <QtCore/QCommandLineParser> + +using namespace std::chrono_literals; +using namespace Qt::Literals; + +int main(int argc, char **argv) +{ + qputenv("QT_MEDIA_BACKEND", "gstreamer"); + + QApplication app(argc, argv); + + QCommandLineParser parser; + parser.setApplicationDescription("GStreamer Custom Camera"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument( + "pipeline", "Pipeline string, e.g. `videotestsrc pattern=smpte-rp-219 is-live=true`"); + + parser.process(app); + + QByteArray pipelineString; + + if (parser.positionalArguments().isEmpty()) { + // pipelineString = "videotestsrc pattern=smpte-rp-219 is-live=true"; + pipelineString = "videotestsrc is-live=true ! gamma gamma=2.0"; + } else { + pipelineString = parser.positionalArguments()[0].toLatin1(); + } + + QVideoWidget wid; + + QMediaCaptureSession session; + session.setVideoSink(wid.videoSink()); + + QCamera *cam = QGStreamerPlatformSpecificInterface::instance()->makeCustomGStreamerCamera( + pipelineString, &session); + session.setCamera(cam); + cam->start(); + + wid.show(); + + return QApplication::exec(); +} diff --git a/tests/manual/mediaformats/CMakeLists.txt b/tests/manual/mediaformats/CMakeLists.txt new file mode 100644 index 000000000..ed58e628e --- /dev/null +++ b/tests/manual/mediaformats/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(devices LANGUAGES CXX) + +if(ANDROID OR IOS) + message(FATAL_ERROR "This is a commandline tool that is not supported on mobile platforms") +endif() + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/mediaformats") + +find_package(Qt6 REQUIRED COMPONENTS Core Multimedia) + +qt_add_executable(mediaformats + main.cpp +) + +set_target_properties(mediaformats PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(mediaformats PUBLIC + Qt::Core + Qt::Multimedia +) + +install(TARGETS mediaformats + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/tests/manual/mediaformats/main.cpp b/tests/manual/mediaformats/main.cpp new file mode 100644 index 000000000..57d0ad831 --- /dev/null +++ b/tests/manual/mediaformats/main.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QCoreApplication> +#include <QtCore/QMimeType> +#include <QtCore/QTextStream> +#include <QtMultimedia/QMediaFormat> + +#include <stdio.h> + +namespace { + +void printFileFormatEntry(QMediaFormat::FileFormat format, QTextStream &out) +{ + out << " " << QMediaFormat::fileFormatName(format) << " - " + << QMediaFormat::fileFormatDescription(format); + + QMimeType mimeType = QMediaFormat(format).mimeType(); + if (mimeType.isValid()) { + out << " (" << mimeType.name() << ")\n"; + out << " " << mimeType.suffixes().join(", ") << "\n"; + } +} + +void printCodecEntry(QMediaFormat::AudioCodec codec, QTextStream &out) +{ + out << " " << QMediaFormat::audioCodecName(codec) << " - " + << QMediaFormat::audioCodecDescription(codec) << "\n"; +} + +void printCodecEntry(QMediaFormat::VideoCodec codec, QTextStream &out) +{ + out << " " << QMediaFormat::videoCodecName(codec) << " - " + << QMediaFormat::videoCodecDescription(codec) << "\n"; +} + +void printFileFormats(QTextStream &out) +{ + out << "Supported file formats for decoding: \n"; + for (QMediaFormat::FileFormat format : + QMediaFormat().supportedFileFormats(QMediaFormat::Decode)) + printFileFormatEntry(format, out); + + out << "\nSupported file formats for encoding: \n"; + for (QMediaFormat::FileFormat format : + QMediaFormat().supportedFileFormats(QMediaFormat::Encode)) + printFileFormatEntry(format, out); +} + +void printAudioCodecs(QTextStream &out) +{ + out << "Supported audio codecs for decoding: \n"; + for (QMediaFormat::AudioCodec codec : QMediaFormat().supportedAudioCodecs(QMediaFormat::Decode)) + printCodecEntry(codec, out); + + out << "\nSupported audio codecs for encoding: \n"; + for (QMediaFormat::AudioCodec codec : QMediaFormat().supportedAudioCodecs(QMediaFormat::Encode)) + printCodecEntry(codec, out); +} + +void printVideoCodecs(QTextStream &out) +{ + out << "Supported video codecs for decoding: \n"; + for (QMediaFormat::VideoCodec codec : QMediaFormat().supportedVideoCodecs(QMediaFormat::Decode)) + printCodecEntry(codec, out); + + out << "\nSupported video codecs for encoding: \n"; + for (QMediaFormat::VideoCodec codec : QMediaFormat().supportedVideoCodecs(QMediaFormat::Encode)) + printCodecEntry(codec, out); +} + +} // namespace + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); // QtMultimedia needs an application singleton + + QTextStream out(stdout); + + printFileFormats(out); + out << "\n"; + printAudioCodecs(out); + out << "\n"; + printVideoCodecs(out); + + return 0; +} diff --git a/tests/manual/minimal-audio-recorder/CMakeLists.txt b/tests/manual/minimal-audio-recorder/CMakeLists.txt new file mode 100644 index 000000000..9f5727e62 --- /dev/null +++ b/tests/manual/minimal-audio-recorder/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(sidepanel LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/minimal-audio-recorder") + +find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets) + +qt_add_executable( minimal-audio-recorder MACOSX_BUNDLE + minimal-audio-recorder.cpp +) + +target_link_libraries( minimal-audio-recorder PUBLIC + Qt::Widgets + Qt::Multimedia + Qt::MultimediaWidgets +) + +install(TARGETS minimal-audio-recorder + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) + +set_target_properties( minimal-audio-recorder PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in +) diff --git a/tests/manual/minimal-audio-recorder/info.plist.in b/tests/manual/minimal-audio-recorder/info.plist.in new file mode 100644 index 000000000..d97735d46 --- /dev/null +++ b/tests/manual/minimal-audio-recorder/info.plist.in @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleLongVersionString</key> + <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> + + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + + <key>CFBundleGetInfoString</key> + <string>${MACOSX_BUNDLE_INFO_STRING}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + + <key>NSCameraUsageDescription</key> + <string>Qt Multimedia Example</string> + <key>NSMicrophoneUsageDescription</key> + <string>Qt Multimedia Example</string> + + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> + </dict> +</plist>
\ No newline at end of file diff --git a/tests/manual/minimal-audio-recorder/minimal-audio-recorder.cpp b/tests/manual/minimal-audio-recorder/minimal-audio-recorder.cpp new file mode 100644 index 000000000..76b0d31e3 --- /dev/null +++ b/tests/manual/minimal-audio-recorder/minimal-audio-recorder.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QTimer> +#include <QtCore/QCommandLineParser> +#include <QtCore/QCoreApplication> +#include <QtCore/QUrl> +#include <QtCore/QtEnvironmentVariables> +#include <QtMultimedia/QAudioInput> +#include <QtMultimedia/QMediaCaptureSession> +#include <QtMultimedia/QMediaRecorder> +#include <QtMultimedia/QMediaFormat> +#include <QtMultimedia/QMediaDevices> +#include <QtMultimedia/QAudioDevice> +#include <chrono> + +using namespace std::chrono_literals; +using namespace Qt::Literals; + +QMediaFormat::FileFormat toFileFormat(const QString &format) +{ + if (format == "mpeg4audio") + return QMediaFormat::Mpeg4Audio; + if (format == "aac") + return QMediaFormat::AAC; + if (format == "wma") + return QMediaFormat::WMA; + if (format == "mp3") + return QMediaFormat::MP3; + if (format == "flac") + return QMediaFormat::FLAC; + if (format == "wave") + return QMediaFormat::Wave; + + qWarning() << "Invalid file format, using default AAC"; + + return QMediaFormat::AAC; +} + +int main(int argc, char **argv) +{ + if (qEnvironmentVariableIsEmpty("QT_LOGGING_RULES")) + qputenv("QT_LOGGING_RULES", "qt.multimedia.ffmpeg.audioencoder=true"); + + QCoreApplication app(argc, argv); + + QCommandLineParser parser; + parser.setApplicationDescription("Minimal Audio Recorder"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("output_file", "Output audio file"); + + const QCommandLineOption durationOption{ + { "d", "duration" }, "Duration of recording in seconds", "seconds", "5" + }; + parser.addOption(durationOption); + + const QCommandLineOption formatOption{ + { "f", "format" }, "Container format", "Mpeg4Audio, AAC, WMA, MP3, FLAC, Wave", "AAC" + }; + parser.addOption(formatOption); + + const QCommandLineOption deviceOption{ { "i", "device_id" }, "Audio device ID", "string" }; + parser.addOption(deviceOption); + + parser.process(app); + + if (parser.positionalArguments().isEmpty()) + parser.showHelp(1); // Exits process + + const QUrl mediaUrl = QUrl::fromLocalFile(parser.positionalArguments()[0]); + const std::chrono::seconds duration{ parser.value(durationOption).toInt() }; + const QMediaFormat::FileFormat format = toFileFormat(parser.value(formatOption).toLower()); + const QString deviceId = parser.value(deviceOption); + + QList<QAudioDevice> inputs = QMediaDevices::audioInputs(); + QAudioDevice selectedDevice; + + qInfo() << "Available audio devices:"; + for (const QAudioDevice &device : inputs) { + qInfo() << "ID" << device.id() << "Description" << device.description(); + if (device.id() == deviceId) + selectedDevice = device; + } + + QAudioInput input; + if (selectedDevice.isNull()) { + qInfo() << "Using default device"; + } else { + qInfo() << "Using device" << selectedDevice.description(); + input.setDevice(selectedDevice); + } + + QMediaRecorder recorder; + recorder.setOutputLocation(mediaUrl); + recorder.setMediaFormat({ format }); + + QMediaCaptureSession session; + session.setRecorder(&recorder); + session.setAudioInput(&input); + + recorder.record(); + + qInfo() << "Recording" << duration.count() << "seconds of audio to" + << recorder.actualLocation().toLocalFile(); + + QTimer::singleShot(duration, &app, [&] { + recorder.stop(); + qDebug() << "Recording completed"; + QCoreApplication::quit(); + }); + + return QCoreApplication::exec(); +} diff --git a/tests/manual/minimal-player/minimal-player.cpp b/tests/manual/minimal-player/minimal-player.cpp index 41fc4608f..a39c384e9 100644 --- a/tests/manual/minimal-player/minimal-player.cpp +++ b/tests/manual/minimal-player/minimal-player.cpp @@ -12,47 +12,107 @@ using namespace std::chrono_literals; using namespace Qt::Literals; -int mainToggleWidgets(QString filename) +struct CLIArgs { - QMediaPlayer player; - QVideoWidget widget1; - QVideoWidget widget2; - QAudioOutput audioOutput; - player.setVideoOutput(&widget1); - player.setAudioOutput(&audioOutput); - player.setSource(filename); + bool loop; + bool noAudio; + bool toggleWidgets; + QString media; + bool playAfterEndOfMediaOption; +}; + +std::optional<CLIArgs> parseArgs(QCoreApplication &app) +{ + QCommandLineParser parser; + parser.setApplicationDescription("Minimal Player"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("media", "File to play"); - QTimer toggleOutput; - bool toggled = {}; + QCommandLineOption toggleWidgetsOption{ + "toggle-widgets", + "Toggle between widgets.", + }; + parser.addOption(toggleWidgetsOption); - toggleOutput.callOnTimeout([&] { - toggled = !toggled; - if (toggled) - player.setVideoOutput(&widget2); - else - player.setVideoOutput(&widget1); - }); + QCommandLineOption playAfterEndOfMediaOption{ + "play-after-end-of-media", + "Play after end of media.", + }; + parser.addOption(playAfterEndOfMediaOption); - toggleOutput.setInterval(1s); - toggleOutput.start(); + QCommandLineOption disableAudioOption{ + "no-audio", + "Disable audio output.", + }; + parser.addOption(disableAudioOption); - widget1.show(); - widget2.show(); - player.play(); - return QApplication::exec(); + QCommandLineOption loopOption{ + "loop", + "Loop.", + }; + parser.addOption(loopOption); + + parser.process(app); + + if (parser.positionalArguments().isEmpty()) { + qInfo() << "Please specify a media source"; + return std::nullopt; + } + + QString filename = parser.positionalArguments()[0]; + + return CLIArgs{ + parser.isSet(loopOption), + parser.isSet(disableAudioOption), + parser.isSet(toggleWidgetsOption), + filename, + parser.isSet(playAfterEndOfMediaOption), + }; } -int mainSimple(QString filename) +int run(const CLIArgs &args) { + QTimer toggleOutput; + bool toggled = {}; + QMediaPlayer player; QVideoWidget widget1; + QVideoWidget widget2; QAudioOutput audioOutput; player.setVideoOutput(&widget1); - player.setAudioOutput(&audioOutput); - player.setSource(filename); + if (args.noAudio) + player.setAudioOutput(nullptr); + else + player.setAudioOutput(&audioOutput); + player.setSource(args.media); widget1.show(); + + if (args.toggleWidgets) { + toggleOutput.callOnTimeout([&] { + toggled = !toggled; + if (toggled) + player.setVideoOutput(&widget2); + else + player.setVideoOutput(&widget1); + }); + + toggleOutput.setInterval(1s); + toggleOutput.start(); + widget2.show(); + } + player.play(); + + if (args.playAfterEndOfMediaOption) { + QObject::connect(&player, &QMediaPlayer::mediaStatusChanged, &player, + [&](QMediaPlayer::MediaStatus status) { + if (status == QMediaPlayer::MediaStatus::EndOfMedia) + player.play(); + }); + } + return QApplication::exec(); } @@ -60,26 +120,9 @@ 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; - } - - QString filename = parser.positionalArguments()[0]; - - if (parser.isSet(toggleWidgetsOption)) - return mainToggleWidgets(filename); + std::optional<CLIArgs> args = parseArgs(app); + if (!args) + return 1; - return mainSimple(filename); + return run(*args); } |
