rpcsx/rpcs3/rpcs3qt/qt_camera_handler.cpp
Megamouse f3ef4f3658 cellCamera: try to fix internal state on stop
When stopping, the camera is supposed to be open still.
Add an expected state to check the camera signal does what it should.
2024-12-18 03:54:25 +01:00

411 lines
11 KiB
C++

#include "stdafx.h"
#include "qt_camera_handler.h"
#include "Emu/system_config.h"
#include "Emu/System.h"
#include "Emu/Io/camera_config.h"
#include <QMediaDevices>
#if QT_CONFIG(permissions)
#include <QGuiApplication>
#include <QPermissions>
#endif
LOG_CHANNEL(camera_log, "Camera");
qt_camera_handler::qt_camera_handler() : camera_handler_base()
{
// List available cameras
for (const QCameraDevice& camera_device : QMediaDevices::videoInputs())
{
camera_log.success("Found camera: id=%s, description=%s", camera_device.id().toStdString(), camera_device.description());
}
if (!g_cfg_camera.load())
{
camera_log.notice("Could not load camera config. Using defaults.");
}
}
qt_camera_handler::~qt_camera_handler()
{
Emu.BlockingCallFromMainThread([&]()
{
close_camera();
reset();
});
}
void qt_camera_handler::reset()
{
m_camera.reset();
m_video_sink.reset();
m_media_capture_session.reset();
}
void qt_camera_handler::set_camera(const QCameraDevice& camera_info)
{
if (camera_info.isNull())
{
set_expected_state(camera_handler_state::closed);
reset();
return;
}
// Determine if the camera is front facing, in which case we will need to flip the image horizontally.
const bool front_facing = camera_info.position() == QCameraDevice::Position::FrontFace;
camera_log.success("Using camera: id=\"%s\", description=\"%s\", front_facing=%d", camera_info.id().toStdString(), camera_info.description(), front_facing);
// Create camera and video surface
m_media_capture_session = std::make_unique<QMediaCaptureSession>(nullptr);
m_video_sink = std::make_unique<qt_camera_video_sink>(front_facing, nullptr);
m_camera = std::make_unique<QCamera>(camera_info);
connect(m_camera.get(), &QCamera::activeChanged, this, &qt_camera_handler::handle_camera_active);
connect(m_camera.get(), &QCamera::errorOccurred, this, &qt_camera_handler::handle_camera_error);
// Setup video sink
m_media_capture_session->setCamera(m_camera.get());
m_media_capture_session->setVideoSink(m_video_sink.get());
// Update the settings
update_camera_settings();
}
void qt_camera_handler::handle_camera_active(bool is_active)
{
camera_log.notice("Camera active status changed to %d", is_active);
// Check if the camera does what it's supposed to do.
const camera_handler_state expected_state = get_expected_state();
switch (expected_state)
{
case camera_handler_state::closed:
case camera_handler_state::open:
{
if (is_active)
{
// This is not supposed to happen and indicates an unexpected QCamera issue
camera_log.error("Camera started unexpectedly");
set_state(camera_handler_state::running);
return;
}
break;
}
case camera_handler_state::running:
{
if (!is_active)
{
// This is not supposed to happen and indicates an unexpected QCamera issue
camera_log.error("Camera stopped unexpectedly");
set_state(camera_handler_state::open);
return;
}
break;
}
}
set_state(expected_state);
}
void qt_camera_handler::handle_camera_error(QCamera::Error error, const QString& errorString)
{
camera_log.error("Error event: \"%s\" (error=%d)", errorString, static_cast<int>(error));
}
void qt_camera_handler::open_camera()
{
camera_log.notice("Loading camera");
if (const std::string camera_id = g_cfg.io.camera_id.to_string();
m_camera_id != camera_id)
{
camera_log.notice("Switching camera from %s to %s", m_camera_id, camera_id);
camera_log.notice("Stopping old camera...");
if (m_camera)
{
set_expected_state(camera_handler_state::open);
m_camera->stop();
}
m_camera_id = camera_id;
}
QCameraDevice selected_camera{};
if (m_camera_id == g_cfg.io.camera_id.def)
{
selected_camera = QMediaDevices::defaultVideoInput();
}
else if (!m_camera_id.empty())
{
const QString camera_id = QString::fromStdString(m_camera_id);
for (const QCameraDevice& camera_device : QMediaDevices::videoInputs())
{
if (camera_id == camera_device.id())
{
selected_camera = camera_device;
break;
}
}
}
set_camera(selected_camera);
if (!m_camera)
{
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
else camera_log.error("No camera found");
set_state(camera_handler_state::closed);
return;
}
if (m_camera->isActive())
{
camera_log.notice("Camera already active");
return;
}
// List all supported formats for debugging
for (const QCameraFormat& format : m_camera->cameraDevice().videoFormats())
{
camera_log.notice("Supported format: pixelformat=%s, resolution=%dx%d framerate=%f-%f", format.pixelFormat(), format.resolution().width(), format.resolution().height(), format.minFrameRate(), format.maxFrameRate());
}
// Update camera and view finder settings
update_camera_settings();
set_state(camera_handler_state::open);
}
void qt_camera_handler::close_camera()
{
camera_log.notice("Unloading camera");
if (!m_camera)
{
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
else camera_log.error("No camera found");
set_state(camera_handler_state::closed);
return;
}
// Unload/close camera
set_expected_state(camera_handler_state::closed);
m_camera->stop();
}
void qt_camera_handler::start_camera()
{
camera_log.notice("Starting camera");
if (!m_camera)
{
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
else camera_log.error("No camera found");
set_state(camera_handler_state::closed);
return;
}
if (m_camera->isActive())
{
camera_log.notice("Camera already started");
return;
}
#if QT_CONFIG(permissions)
const QCameraPermission permission;
switch (qApp->checkPermission(permission))
{
case Qt::PermissionStatus::Undetermined:
camera_log.notice("Requesting camera permission");
qApp->requestPermission(permission, this, [this]()
{
start_camera();
});
return;
case Qt::PermissionStatus::Denied:
camera_log.error("RPCS3 has no permissions to access cameras on this device.");
return;
case Qt::PermissionStatus::Granted:
camera_log.notice("Camera permission granted");
break;
}
#endif
// Start camera. We will start receiving frames now.
set_expected_state(camera_handler_state::running);
m_camera->start();
}
void qt_camera_handler::stop_camera()
{
camera_log.notice("Stopping camera");
if (!m_camera)
{
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
else camera_log.error("No camera found");
set_state(camera_handler_state::closed);
return;
}
if (!m_camera->isActive())
{
camera_log.notice("Camera already stopped");
return;
}
// Stop camera. The camera will still be drawing power.
set_expected_state(camera_handler_state::open);
m_camera->stop();
}
void qt_camera_handler::set_format(s32 format, u32 bytesize)
{
m_format = format;
m_bytesize = bytesize;
if (m_video_sink)
{
m_video_sink->set_format(m_format, m_bytesize);
}
}
void qt_camera_handler::set_frame_rate(u32 frame_rate)
{
m_frame_rate = frame_rate;
}
void qt_camera_handler::set_resolution(u32 width, u32 height)
{
m_width = width;
m_height = height;
if (m_video_sink)
{
m_video_sink->set_resolution(m_width, m_height);
}
}
void qt_camera_handler::set_mirrored(bool mirrored)
{
m_mirrored = mirrored;
if (m_video_sink)
{
m_video_sink->set_mirrored(m_mirrored);
}
}
u64 qt_camera_handler::frame_number() const
{
return m_video_sink ? m_video_sink->frame_number() : 0;
}
camera_handler_base::camera_handler_state qt_camera_handler::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
{
width = 0;
height = 0;
frame_number = 0;
bytes_read = 0;
if (const std::string camera_id = g_cfg.io.camera_id.to_string();
m_camera_id != camera_id)
{
camera_log.notice("Switching cameras");
set_state(camera_handler_state::closed);
return camera_handler_state::closed;
}
if (m_camera_id.empty())
{
camera_log.notice("Camera disabled");
set_state(camera_handler_state::closed);
return camera_handler_state::closed;
}
if (!m_camera || !m_video_sink)
{
camera_log.fatal("Error: camera invalid");
set_state(camera_handler_state::closed);
return camera_handler_state::closed;
}
// Backup current state. State may change through events.
const camera_handler_state current_state = get_state();
if (current_state == camera_handler_state::running)
{
// Copy latest image into out buffer.
m_video_sink->get_image(buf, size, width, height, frame_number, bytes_read);
}
else
{
camera_log.error("Camera not running (m_state=%d)", static_cast<int>(current_state));
}
return current_state;
}
void qt_camera_handler::update_camera_settings()
{
// Update camera if possible. We can only do this if it is already loaded.
if (m_camera && m_camera->isAvailable())
{
// Get camera id. Use camera id of Qt default if the "Default" camera is selected.
const std::string camera_id = (m_camera_id == g_cfg.io.camera_id.def) ? QMediaDevices::defaultVideoInput().id().toStdString() : m_camera_id;
// Load selected settings from config file
bool success = false;
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(camera_id, success);
if (success)
{
camera_log.notice("Found config entry for camera \"%s\" (m_camera_id='%s')", camera_id, m_camera_id);
// List all available settings and choose the proper value if possible.
const double epsilon = 0.001;
success = false;
for (const QCameraFormat& supported_setting : m_camera->cameraDevice().videoFormats())
{
if (supported_setting.resolution().width() == cfg_setting.width &&
supported_setting.resolution().height() == cfg_setting.height &&
supported_setting.minFrameRate() >= (cfg_setting.min_fps - epsilon) &&
supported_setting.minFrameRate() <= (cfg_setting.min_fps + epsilon) &&
supported_setting.maxFrameRate() >= (cfg_setting.max_fps - epsilon) &&
supported_setting.maxFrameRate() <= (cfg_setting.max_fps + epsilon) &&
supported_setting.pixelFormat() == static_cast<QVideoFrameFormat::PixelFormat>(cfg_setting.format))
{
// Apply settings.
camera_log.notice("Setting view finder settings: frame_rate=%f, width=%d, height=%d, pixel_format=%s",
supported_setting.maxFrameRate(), supported_setting.resolution().width(), supported_setting.resolution().height(), supported_setting.pixelFormat());
m_camera->setCameraFormat(supported_setting);
success = true;
break;
}
}
if (!success)
{
camera_log.warning("No matching camera setting available for the camera config: max_fps=%f, width=%d, height=%d, format=%d",
cfg_setting.max_fps, cfg_setting.width, cfg_setting.height, cfg_setting.format);
}
}
if (!success)
{
camera_log.notice("Using default view finder settings");
}
}
// Update video surface if possible
if (m_video_sink)
{
m_video_sink->set_resolution(m_width, m_height);
m_video_sink->set_format(m_format, m_bytesize);
m_video_sink->set_mirrored(m_mirrored);
}
}