From 4a833e7206ea0a603def7f9a8538b1dc032eea8e Mon Sep 17 00:00:00 2001 From: smiRaphi Date: Sat, 28 Feb 2026 01:06:59 +0100 Subject: [PATCH] [desktop] port IR Camera to Qt6 & fix camera saving on windows (#3630) The camera was previously saved without escaping the name which made the values unusable after a settings load, for whatever reason replacing the backslashes when saving with / doesn't work but replacing them with | does. Also note that the OBS virtual cam (and any other cameras that only have directshow drivers) won't work because Qt6 dropped support for that and the ffmpeg backend doesn't seem to support it either. Closes #3468 Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3630 Reviewed-by: crueter Reviewed-by: DraVee Co-authored-by: smiRaphi Co-committed-by: smiRaphi --- CMakeLists.txt | 14 ++-- src/yuzu/CMakeLists.txt | 8 +- src/yuzu/bootmanager.cpp | 57 ++++++------- src/yuzu/bootmanager.h | 10 ++- src/yuzu/configuration/configure_camera.cpp | 80 +++++++++---------- src/yuzu/configuration/configure_camera.h | 11 ++- .../configure_input_advanced.cpp | 4 +- 7 files changed, 90 insertions(+), 94 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d97724de..4490df21cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ endif() option(ENABLE_QT "Enable the Qt frontend" ON) option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF) -# option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) +cmake_dependent_option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF "NOT YUZU_USE_BUNDLED_QT" OFF) cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF) set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) @@ -577,9 +577,9 @@ if (ENABLE_QT) find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent) - # if (YUZU_USE_QT_MULTIMEDIA) - # find_package(Qt6 REQUIRED COMPONENTS Multimedia) - # endif() + if (YUZU_USE_QT_MULTIMEDIA) + find_package(Qt6 REQUIRED COMPONENTS Multimedia) + endif() if (PLATFORM_LINUX OR PLATFORM_FREEBSD) # yes Qt, we get it @@ -618,9 +618,9 @@ if (ENABLE_QT) if (PLATFORM_LINUX) list(APPEND YUZU_QT_COMPONENTS DBus) endif() - # if (YUZU_USE_QT_MULTIMEDIA) - # list(APPEND YUZU_QT_COMPONENTS Multimedia) - # endif() + if (YUZU_USE_QT_MULTIMEDIA) + list(APPEND YUZU_QT_COMPONENTS Multimedia) + endif() if (YUZU_USE_QT_WEB_ENGINE) list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets) endif() diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c85d43235a..99fb2fec15 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -412,10 +412,10 @@ if (ENABLE_WEB_SERVICE) target_compile_definitions(yuzu PRIVATE ENABLE_WEB_SERVICE) endif() -# if (YUZU_USE_QT_MULTIMEDIA) -# target_link_libraries(yuzu PRIVATE Qt6::Multimedia) -# target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA) -# endif () +if (YUZU_USE_QT_MULTIMEDIA) + target_link_libraries(yuzu PRIVATE Qt6::Multimedia) + target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA) +endif () if (YUZU_USE_QT_WEB_ENGINE) target_link_libraries(yuzu PRIVATE Qt6::WebEngineCore Qt6::WebEngineWidgets) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 1b88f4133f..72a5157fc4 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -13,10 +13,11 @@ #include #include "common/settings_enums.h" #include "qt_common/config/uisettings.h" -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA #include -#include -#include +#include +#include +#include #endif #include #include @@ -756,24 +757,25 @@ void GRenderWindow::TouchEndEvent() { } void GRenderWindow::InitializeCamera() { -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA constexpr auto camera_update_ms = std::chrono::milliseconds{50}; // (50ms, 20Hz) if (!Settings::values.enable_ir_sensor) { return; } bool camera_found = false; - const QList cameras = QCameraInfo::availableCameras(); - for (const QCameraInfo& cameraInfo : cameras) { - if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || - Settings::values.ir_sensor_device.GetValue() == "Auto") { - camera = std::make_unique(cameraInfo); - if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && - !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { - LOG_ERROR(Frontend, - "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + std::string current_device = Settings::values.ir_sensor_device.GetValue(); +#ifdef _WIN32 + std::replace(current_device.begin(), current_device.end(), '|', '\\'); +#endif + const QList cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice& cameraDevice : cameras) { + if (current_device == cameraDevice.id().toStdString() || current_device == "auto") { + if (cameraDevice.videoFormats().isEmpty()) { + LOG_ERROR(Frontend, "Camera doesn't provide any video formats."); continue; } + camera = std::make_unique(cameraDevice); camera_found = true; break; } @@ -783,27 +785,16 @@ void GRenderWindow::InitializeCamera() { return; } - camera_capture = std::make_unique(camera.get()); - - if (!camera_capture->isCaptureDestinationSupported( - QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { - LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); - return; - } + capture_session = std::make_unique(); + camera_capture = std::make_unique(); + capture_session->setCamera(camera.get()); + capture_session->setImageCapture(camera_capture.get()); const auto camera_width = input_subsystem->GetCamera()->getImageWidth(); const auto camera_height = input_subsystem->GetCamera()->getImageHeight(); camera_data.resize(camera_width * camera_height); - camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); - connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + connect(camera_capture.get(), &QImageCapture::imageCaptured, this, &GRenderWindow::OnCameraCapture); - camera->unload(); - if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { - camera->setCaptureMode(QCamera::CaptureViewfinder); - } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { - camera->setCaptureMode(QCamera::CaptureStillImage); - } - camera->load(); camera->start(); pending_camera_snapshots = 0; @@ -817,18 +808,18 @@ void GRenderWindow::InitializeCamera() { } void GRenderWindow::FinalizeCamera() { -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA if (camera_timer) { camera_timer->stop(); } if (camera) { - camera->unload(); + camera->stop(); } #endif } void GRenderWindow::RequestCameraCapture() { -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA if (!Settings::values.enable_ir_sensor) { return; } @@ -849,7 +840,7 @@ void GRenderWindow::RequestCameraCapture() { } void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) { -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA // TODO: Capture directly in the format and resolution needed const auto camera_width = input_subsystem->GetCamera()->getImageWidth(); const auto camera_height = input_subsystem->GetCamera()->getImageHeight(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 83d364df4b..837156a725 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -34,7 +34,8 @@ class MainWindow; class QCamera; -class QCameraImageCapture; +class QImageCapture; +class QMediaCaptureSession; class QCloseEvent; class QFocusEvent; class QKeyEvent; @@ -264,12 +265,13 @@ private: bool first_frame = false; InputCommon::TasInput::TasState last_tas_state; -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA bool is_virtual_camera; int pending_camera_snapshots; std::vector camera_data; std::unique_ptr camera; - std::unique_ptr camera_capture; + std::unique_ptr camera_capture; + std::unique_ptr capture_session; std::unique_ptr camera_timer; #endif diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp index 3368f53f30..eb39bbdc9f 100644 --- a/src/yuzu/configuration/configure_camera.cpp +++ b/src/yuzu/configuration/configure_camera.cpp @@ -1,11 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // Text : Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include #include -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA -#include -#include +#if YUZU_USE_QT_MULTIMEDIA +#include +#include +#include +#include #endif #include #include @@ -36,22 +41,20 @@ ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* i ConfigureCamera::~ConfigureCamera() = default; void ConfigureCamera::PreviewCamera() { -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA const auto index = ui->ir_sensor_combo_box->currentIndex(); bool camera_found = false; - const QList cameras = QCameraInfo::availableCameras(); - for (const QCameraInfo& cameraInfo : cameras) { - if (input_devices[index] == cameraInfo.deviceName().toStdString() || - input_devices[index] == "Auto") { - LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), - cameraInfo.deviceName().toStdString()); - camera = std::make_unique(cameraInfo); - if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && - !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { - LOG_ERROR(Frontend, - "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + const QList cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice& cameraDevice : cameras) { + if (input_devices[index] == cameraDevice.id().toStdString() || + input_devices[index] == "auto") { + LOG_INFO(Frontend, "Selected Camera {} {}", cameraDevice.description().toStdString(), + cameraDevice.id().toStdString()); + if (cameraDevice.videoFormats().isEmpty()) { + LOG_ERROR(Frontend, "Camera doesn't provide any video formats."); continue; } + camera = std::make_unique(cameraDevice); camera_found = true; break; } @@ -66,24 +69,12 @@ void ConfigureCamera::PreviewCamera() { return; } - camera_capture = std::make_unique(camera.get()); - - if (!camera_capture->isCaptureDestinationSupported( - QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { - LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); - return; - } - - camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); - connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + capture_session = std::make_unique(); + camera_capture = std::make_unique(); + capture_session->setCamera(camera.get()); + capture_session->setImageCapture(camera_capture.get()); + connect(camera_capture.get(), &QImageCapture::imageCaptured, this, &ConfigureCamera::DisplayCapturedFrame); - camera->unload(); - if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { - camera->setCaptureMode(QCamera::CaptureViewfinder); - } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { - camera->setCaptureMode(QCamera::CaptureStillImage); - } - camera->load(); camera->start(); pending_snapshots = 0; @@ -129,24 +120,31 @@ void ConfigureCamera::RetranslateUI() { } void ConfigureCamera::ApplyConfiguration() { - const auto index = ui->ir_sensor_combo_box->currentIndex(); - Settings::values.ir_sensor_device.SetValue(input_devices[index]); + std::string current_device = input_devices[ui->ir_sensor_combo_box->currentIndex()]; +#ifdef _WIN32 + // for whatever reason replacing with / isn't enough so we use | for saving + std::replace(current_device.begin(), current_device.end(), '\\', '|'); +#endif + Settings::values.ir_sensor_device.SetValue(current_device); } void ConfigureCamera::LoadConfiguration() { input_devices.clear(); ui->ir_sensor_combo_box->clear(); - input_devices.push_back("Auto"); + input_devices.push_back("auto"); ui->ir_sensor_combo_box->addItem(tr("Auto")); -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA - const auto cameras = QCameraInfo::availableCameras(); - for (const QCameraInfo& cameraInfo : cameras) { - input_devices.push_back(cameraInfo.deviceName().toStdString()); - ui->ir_sensor_combo_box->addItem(cameraInfo.description()); +#if YUZU_USE_QT_MULTIMEDIA + const auto cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice& cameraDevice : cameras) { + input_devices.push_back(cameraDevice.id().toStdString()); + ui->ir_sensor_combo_box->addItem(cameraDevice.description()); } #endif - const auto current_device = Settings::values.ir_sensor_device.GetValue(); + std::string current_device = Settings::values.ir_sensor_device.GetValue(); +#ifdef _WIN32 + std::replace(current_device.begin(), current_device.end(), '|', '\\'); +#endif const auto devices_it = std::find_if( input_devices.begin(), input_devices.end(), diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h index 3d822da7bb..610b362419 100644 --- a/src/yuzu/configuration/configure_camera.h +++ b/src/yuzu/configuration/configure_camera.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // Text : Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -8,7 +11,8 @@ class QTimer; class QCamera; -class QCameraImageCapture; +class QImageCapture; +class QMediaCaptureSession; namespace InputCommon { class InputSubsystem; @@ -46,9 +50,10 @@ private: bool is_virtual_camera; int pending_snapshots; -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#if YUZU_USE_QT_MULTIMEDIA std::unique_ptr camera; - std::unique_ptr camera_capture; + std::unique_ptr camera_capture; + std::unique_ptr capture_session; #endif std::unique_ptr camera_timer; std::vector input_devices; diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 241d445cea..b1c19114bf 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -201,7 +201,7 @@ void ConfigureInputAdvanced::UpdateUIEnabled() { ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); -#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA) +#if !defined(YUZU_USE_QT_MULTIMEDIA) ui->enable_ir_sensor->setEnabled(false); ui->camera_configure->setEnabled(false); #endif