mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-19 19:15:26 +00:00
Add SDL camera handler
This commit is contained in:
parent
ff21a05c96
commit
4953bff1d5
30 changed files with 1784 additions and 460 deletions
|
@ -79,6 +79,7 @@ if (NOT ANDROID)
|
|||
|
||||
Input/basic_keyboard_handler.cpp
|
||||
Input/basic_mouse_handler.cpp
|
||||
Input/camera_video_sink.cpp
|
||||
Input/ds3_pad_handler.cpp
|
||||
Input/ds4_pad_handler.cpp
|
||||
Input/dualsense_pad_handler.cpp
|
||||
|
@ -96,6 +97,9 @@ if (NOT ANDROID)
|
|||
Input/ps_move_tracker.cpp
|
||||
Input/raw_mouse_config.cpp
|
||||
Input/raw_mouse_handler.cpp
|
||||
Input/sdl_camera_handler.cpp
|
||||
Input/sdl_camera_video_sink.cpp
|
||||
Input/sdl_instance.cpp
|
||||
Input/sdl_pad_handler.cpp
|
||||
Input/skateboard_pad_handler.cpp
|
||||
Input/xinput_pad_handler.cpp
|
||||
|
|
|
@ -1331,8 +1331,15 @@ void gem_config_data::operator()()
|
|||
vc = vc_attribute;
|
||||
}
|
||||
|
||||
if (g_cfg.io.camera != camera_handler::qt)
|
||||
switch (g_cfg.io.camera)
|
||||
{
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
case camera_handler::qt:
|
||||
break;
|
||||
case camera_handler::fake:
|
||||
case camera_handler::null:
|
||||
video_conversion_in_progress = false;
|
||||
done();
|
||||
continue;
|
||||
|
|
|
@ -36,32 +36,38 @@ void cfg_camera::save() const
|
|||
}
|
||||
}
|
||||
|
||||
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success)
|
||||
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& handler, const std::string& camera, bool& success)
|
||||
{
|
||||
camera_setting setting;
|
||||
const std::string value = cameras.get_value(camera);
|
||||
camera_setting setting {};
|
||||
const std::string value = cameras.get_value(handler + "-" + camera);
|
||||
success = !value.empty();
|
||||
if (success)
|
||||
{
|
||||
setting.from_string(cameras.get_value(camera));
|
||||
setting.from_string(value);
|
||||
}
|
||||
return setting;
|
||||
}
|
||||
|
||||
void cfg_camera::set_camera_setting(const std::string& camera, const camera_setting& setting)
|
||||
void cfg_camera::set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting)
|
||||
{
|
||||
if (handler.empty())
|
||||
{
|
||||
camera_log.error("String '%s' cannot be used as handler key.", handler);
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera.empty())
|
||||
{
|
||||
camera_log.error("String '%s' cannot be used as camera key.", camera);
|
||||
return;
|
||||
}
|
||||
|
||||
cameras.set_value(camera, setting.to_string());
|
||||
cameras.set_value(handler + "-" + camera, setting.to_string());
|
||||
}
|
||||
|
||||
std::string cfg_camera::camera_setting::to_string() const
|
||||
{
|
||||
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
|
||||
return fmt::format("%d,%d,%f,%f,%d,%d", width, height, min_fps, max_fps, format, colorspace);
|
||||
}
|
||||
|
||||
void cfg_camera::camera_setting::from_string(const std::string& text)
|
||||
|
@ -106,12 +112,14 @@ void cfg_camera::camera_setting::from_string(const std::string& text)
|
|||
!to_integer(::at32(list, 1), height) ||
|
||||
!to_double(::at32(list, 2), min_fps) ||
|
||||
!to_double(::at32(list, 3), max_fps) ||
|
||||
!to_integer(::at32(list, 4), format))
|
||||
!to_integer(::at32(list, 4), format) ||
|
||||
!to_integer(::at32(list, 4), colorspace))
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
min_fps = 0;
|
||||
max_fps = 0;
|
||||
format = 0;
|
||||
colorspace = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,18 +15,19 @@ struct cfg_camera final : cfg::node
|
|||
double min_fps = 0;
|
||||
double max_fps = 0;
|
||||
int format = 0;
|
||||
int colorspace = 0;
|
||||
|
||||
static constexpr u32 member_count = 5;
|
||||
static constexpr u32 member_count = 6;
|
||||
|
||||
std::string to_string() const;
|
||||
void from_string(const std::string& text);
|
||||
};
|
||||
camera_setting get_camera_setting(const std::string& camera, bool& success);
|
||||
void set_camera_setting(const std::string& camera, const camera_setting& setting);
|
||||
camera_setting get_camera_setting(const std::string& handler, const std::string& camera, bool& success);
|
||||
void set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting);
|
||||
|
||||
const std::string path;
|
||||
|
||||
cfg::map_entry cameras{ this, "Cameras" }; // <camera>: <width>,<height>,<min_fps>,<max_fps>,<format>
|
||||
cfg::map_entry cameras{ this, "Cameras" }; // <handler-camera>: <width>,<height>,<min_fps>,<max_fps>,<format>,<colorspace>
|
||||
};
|
||||
|
||||
extern cfg_camera g_cfg_camera;
|
||||
|
|
|
@ -75,9 +75,17 @@ namespace rsx
|
|||
add_checkbox(&g_cfg.io.keep_pads_connected, localized_string_id::HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED);
|
||||
add_checkbox(&g_cfg.io.show_move_cursor, localized_string_id::HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR);
|
||||
|
||||
if (g_cfg.io.camera == camera_handler::qt)
|
||||
switch (g_cfg.io.camera)
|
||||
{
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
case camera_handler::qt:
|
||||
add_dropdown(&g_cfg.io.camera_flip_option, localized_string_id::HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP);
|
||||
break;
|
||||
case camera_handler::fake:
|
||||
case camera_handler::null:
|
||||
break;
|
||||
}
|
||||
|
||||
add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE);
|
||||
|
|
|
@ -269,6 +269,7 @@ struct cfg_root : cfg::node
|
|||
cfg::_enum<fake_camera_type> camera_type{ this, "Camera type", fake_camera_type::unknown };
|
||||
cfg::_enum<camera_flip> camera_flip_option{ this, "Camera flip", camera_flip::none, true };
|
||||
cfg::string camera_id{ this, "Camera ID", "Default", true };
|
||||
cfg::string sdl_camera_id{ this, "SDL Camera ID", "Default", true };
|
||||
cfg::_enum<move_handler> move{ this, "Move", move_handler::null, true };
|
||||
cfg::_enum<buzz_handler> buzz{ this, "Buzz emulated controller", buzz_handler::null };
|
||||
cfg::_enum<turntable_handler> turntable{this, "Turntable emulated controller", turntable_handler::null};
|
||||
|
|
|
@ -374,6 +374,9 @@ void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
|
|||
case camera_handler::null: return "Null";
|
||||
case camera_handler::fake: return "Fake";
|
||||
case camera_handler::qt: return "Qt";
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl: return "SDL";
|
||||
#endif
|
||||
}
|
||||
|
||||
return unknown;
|
||||
|
|
|
@ -116,7 +116,10 @@ enum class camera_handler
|
|||
{
|
||||
null,
|
||||
fake,
|
||||
qt
|
||||
qt,
|
||||
#ifdef HAVE_SDL3
|
||||
sdl,
|
||||
#endif
|
||||
};
|
||||
|
||||
enum class camera_flip
|
||||
|
|
230
rpcs3/Input/camera_video_sink.cpp
Normal file
230
rpcs3/Input/camera_video_sink.cpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
#include "stdafx.h"
|
||||
#include "camera_video_sink.h"
|
||||
|
||||
#include "Emu/Cell/Modules/cellCamera.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
camera_video_sink::camera_video_sink(bool front_facing)
|
||||
: m_front_facing(front_facing)
|
||||
{
|
||||
}
|
||||
|
||||
camera_video_sink::~camera_video_sink()
|
||||
{
|
||||
}
|
||||
|
||||
bool camera_video_sink::present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr)
|
||||
{
|
||||
ensure(!!src_line_ptr);
|
||||
|
||||
const u64 new_size = m_bytesize;
|
||||
image_buffer& image_buffer = m_image_buffer[m_write_index];
|
||||
|
||||
// Reset buffer if necessary
|
||||
if (image_buffer.data.size() != new_size)
|
||||
{
|
||||
image_buffer.data.clear();
|
||||
}
|
||||
|
||||
// Create buffer if necessary
|
||||
if (image_buffer.data.empty() && new_size > 0)
|
||||
{
|
||||
image_buffer.data.resize(new_size);
|
||||
image_buffer.width = m_width;
|
||||
image_buffer.height = m_height;
|
||||
}
|
||||
|
||||
if (!image_buffer.data.empty() && src_width && src_height)
|
||||
{
|
||||
// Convert image to proper layout
|
||||
// TODO: check if pixel format and bytes per pixel match and convert if necessary
|
||||
// TODO: implement or improve more conversions
|
||||
|
||||
const u32 width = std::min<u32>(image_buffer.width, src_width);
|
||||
const u32 height = std::min<u32>(image_buffer.height, src_height);
|
||||
|
||||
switch (m_format)
|
||||
{
|
||||
case CELL_CAMERA_RAW8: // The game seems to expect BGGR
|
||||
{
|
||||
// Let's use a very simple algorithm to convert the image to raw BGGR
|
||||
u8* dst = image_buffer.data.data();
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* src = src_line_ptr(y);
|
||||
const bool is_top_pixel = (y % 2) == 0;
|
||||
|
||||
// Split loops (roughly twice the performance by removing one condition)
|
||||
if (is_top_pixel)
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++, src += 4)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = src[2]; // Blue
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = src[1]; // Green
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++, src += 4)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = src[1]; // Green
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = src[0]; // Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
//case CELL_CAMERA_YUV422:
|
||||
case CELL_CAMERA_Y0_U_Y1_V:
|
||||
case CELL_CAMERA_V_Y1_U_Y0:
|
||||
{
|
||||
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
|
||||
constexpr int yuv_bytes_per_pixel = 2;
|
||||
const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel;
|
||||
|
||||
const int y0_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3;
|
||||
const int u_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2;
|
||||
const int y1_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1;
|
||||
const int v_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* src = src_line_ptr(y);
|
||||
u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch];
|
||||
|
||||
for (u32 x = 0; x < width - 1; x += 2, src += 8)
|
||||
{
|
||||
const f32 r1 = src[0];
|
||||
const f32 g1 = src[1];
|
||||
const f32 b1 = src[2];
|
||||
const f32 r2 = src[4];
|
||||
const f32 g2 = src[5];
|
||||
const f32 b2 = src[6];
|
||||
|
||||
const f32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f;
|
||||
const f32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f;
|
||||
const f32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f;
|
||||
const f32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f;
|
||||
|
||||
const int yuv_index = x * yuv_bytes_per_pixel;
|
||||
yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0.0f, 255.0f));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CELL_CAMERA_JPG:
|
||||
case CELL_CAMERA_RGBA:
|
||||
case CELL_CAMERA_RAW10:
|
||||
case CELL_CAMERA_YUV420:
|
||||
case CELL_CAMERA_FORMAT_UNKNOWN:
|
||||
default:
|
||||
const u32 bytes_per_line = src_bytes_per_pixel * src_width;
|
||||
if (src_pitch == bytes_per_line)
|
||||
{
|
||||
std::memcpy(image_buffer.data.data(), src_line_ptr(0), std::min<usz>(image_buffer.data.size(), src_height * bytes_per_line));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 y = 0, pos = 0; y < src_height && pos < image_buffer.data.size(); y++, pos += bytes_per_line)
|
||||
{
|
||||
std::memcpy(&image_buffer.data[pos], src_line_ptr(y), std::min<usz>(image_buffer.data.size() - pos, bytes_per_line));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d",
|
||||
m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize);
|
||||
|
||||
// Toggle write/read index
|
||||
std::lock_guard lock(m_mutex);
|
||||
image_buffer.frame_number = m_frame_number++;
|
||||
m_write_index = read_index();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_format(s32 format, u32 bytesize)
|
||||
{
|
||||
camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize);
|
||||
|
||||
m_format = format;
|
||||
m_bytesize = bytesize;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_resolution(u32 width, u32 height)
|
||||
{
|
||||
camera_log.notice("Setting resolution: width=%d, height=%d", width, height);
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_mirrored(bool mirrored)
|
||||
{
|
||||
camera_log.notice("Setting mirrored: mirrored=%d", mirrored);
|
||||
|
||||
m_mirrored = mirrored;
|
||||
}
|
||||
|
||||
u64 camera_video_sink::frame_number() const
|
||||
{
|
||||
return m_frame_number.load();
|
||||
}
|
||||
|
||||
void camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
|
||||
{
|
||||
// Lock read buffer
|
||||
std::lock_guard lock(m_mutex);
|
||||
const image_buffer& image_buffer = m_image_buffer[read_index()];
|
||||
|
||||
width = image_buffer.width;
|
||||
height = image_buffer.height;
|
||||
frame_number = image_buffer.frame_number;
|
||||
|
||||
// Copy to out buffer
|
||||
if (buf && !image_buffer.data.empty())
|
||||
{
|
||||
bytes_read = std::min<u64>(image_buffer.data.size(), size);
|
||||
std::memcpy(buf, image_buffer.data.data(), bytes_read);
|
||||
|
||||
if (image_buffer.data.size() != size)
|
||||
{
|
||||
camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes_read = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 camera_video_sink::read_index() const
|
||||
{
|
||||
// The read buffer index cannot be the same as the write index
|
||||
return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size());
|
||||
}
|
||||
|
43
rpcs3/Input/camera_video_sink.h
Normal file
43
rpcs3/Input/camera_video_sink.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
class camera_video_sink
|
||||
{
|
||||
public:
|
||||
camera_video_sink(bool front_facing);
|
||||
virtual ~camera_video_sink();
|
||||
|
||||
void set_format(s32 format, u32 bytesize);
|
||||
void set_resolution(u32 width, u32 height);
|
||||
void set_mirrored(bool mirrored);
|
||||
|
||||
u64 frame_number() const;
|
||||
|
||||
bool present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr);
|
||||
|
||||
void get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read);
|
||||
|
||||
protected:
|
||||
u32 read_index() const;
|
||||
|
||||
bool m_front_facing = false;
|
||||
bool m_mirrored = false; // Set by cellCamera
|
||||
s32 m_format = 2; // CELL_CAMERA_RAW8, set by cellCamera
|
||||
u32 m_bytesize = 0;
|
||||
u32 m_width = 640;
|
||||
u32 m_height = 480;
|
||||
|
||||
std::mutex m_mutex;
|
||||
atomic_t<u64> m_frame_number{0};
|
||||
u32 m_write_index{0};
|
||||
|
||||
struct image_buffer
|
||||
{
|
||||
u64 frame_number = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
std::array<image_buffer, 2> m_image_buffer;
|
||||
};
|
462
rpcs3/Input/sdl_camera_handler.cpp
Normal file
462
rpcs3/Input/sdl_camera_handler.cpp
Normal file
|
@ -0,0 +1,462 @@
|
|||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_camera_handler.h"
|
||||
#include "sdl_camera_video_sink.h"
|
||||
#include "sdl_instance.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Io/camera_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
template <>
|
||||
void fmt_class_string<SDL_CameraSpec>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const SDL_CameraSpec& spec = get_object(arg);
|
||||
out += fmt::format("format=0x%x, colorspace=0x%x, width=%d, height=%d, framerate_numerator=%d, framerate_denominator=%d, fps=%f",
|
||||
static_cast<u32>(spec.format), static_cast<u32>(spec.colorspace), spec.width, spec.height,
|
||||
spec.framerate_numerator, spec.framerate_denominator, spec.framerate_numerator / static_cast<f32>(spec.framerate_denominator));
|
||||
}
|
||||
|
||||
std::vector<std::string> sdl_camera_handler::get_drivers()
|
||||
{
|
||||
std::vector<std::string> drivers;
|
||||
|
||||
if (const int num_drivers = SDL_GetNumCameraDrivers(); num_drivers > 0)
|
||||
{
|
||||
for (int i = 0; i < num_drivers; i++)
|
||||
{
|
||||
if (const char* driver = SDL_GetCameraDriver(i))
|
||||
{
|
||||
camera_log.notice("Found driver: %s", driver);
|
||||
drivers.push_back(driver);
|
||||
continue;
|
||||
}
|
||||
|
||||
camera_log.error("Failed to get driver %d. SDL Error: %s", i, SDL_GetError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera drivers found");
|
||||
}
|
||||
|
||||
return drivers;
|
||||
}
|
||||
|
||||
std::map<SDL_CameraID, std::string> sdl_camera_handler::get_cameras()
|
||||
{
|
||||
int camera_count = 0;
|
||||
if (SDL_CameraID* cameras = SDL_GetCameras(&camera_count))
|
||||
{
|
||||
std::map<SDL_CameraID, std::string> camera_map;
|
||||
|
||||
for (int i = 0; i < camera_count && cameras[i]; i++)
|
||||
{
|
||||
if (const char* name = SDL_GetCameraName(cameras[i]))
|
||||
{
|
||||
camera_log.notice("Found camera: name=%s", name);
|
||||
camera_map[cameras[i]] = name;
|
||||
continue;
|
||||
}
|
||||
|
||||
camera_log.error("Found camera (Failed to get name. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_free(cameras);
|
||||
return camera_map;
|
||||
}
|
||||
|
||||
camera_log.error("Could not get cameras! SDL Error: %s", SDL_GetError());
|
||||
return {};
|
||||
}
|
||||
|
||||
sdl_camera_handler::sdl_camera_handler() : camera_handler_base()
|
||||
{
|
||||
if (!g_cfg_camera.load())
|
||||
{
|
||||
camera_log.notice("Could not load camera config. Using defaults.");
|
||||
}
|
||||
|
||||
if (!sdl_instance::get_instance().initialize())
|
||||
{
|
||||
camera_log.error("Could not initialize SDL");
|
||||
return;
|
||||
}
|
||||
|
||||
// List available camera drivers
|
||||
sdl_camera_handler::get_drivers();
|
||||
|
||||
// List available cameras
|
||||
sdl_camera_handler::get_cameras();
|
||||
}
|
||||
|
||||
sdl_camera_handler::~sdl_camera_handler()
|
||||
{
|
||||
Emu.BlockingCallFromMainThread([&]()
|
||||
{
|
||||
close_camera();
|
||||
});
|
||||
}
|
||||
|
||||
void sdl_camera_handler::reset()
|
||||
{
|
||||
m_video_sink.reset();
|
||||
|
||||
if (m_camera)
|
||||
{
|
||||
SDL_CloseCamera(m_camera);
|
||||
m_camera = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_handler::open_camera()
|
||||
{
|
||||
camera_log.notice("Loading camera");
|
||||
|
||||
if (const std::string camera_id = g_cfg.io.sdl_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);
|
||||
reset();
|
||||
}
|
||||
m_camera_id = camera_id;
|
||||
}
|
||||
|
||||
// List available cameras
|
||||
int camera_count = 0;
|
||||
SDL_CameraID* cameras = SDL_GetCameras(&camera_count);
|
||||
|
||||
if (!cameras)
|
||||
{
|
||||
camera_log.error("Could not get cameras! SDL Error: %s", SDL_GetError());
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera_count <= 0)
|
||||
{
|
||||
camera_log.error("No cameras found");
|
||||
set_state(camera_handler_state::closed);
|
||||
SDL_free(cameras);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sdl_camera_id = 0;
|
||||
|
||||
if (m_camera_id == g_cfg.io.sdl_camera_id.def)
|
||||
{
|
||||
m_sdl_camera_id = cameras[0];
|
||||
}
|
||||
else if (!m_camera_id.empty())
|
||||
{
|
||||
for (int i = 0; i < camera_count && cameras[i]; i++)
|
||||
{
|
||||
if (const char* name = SDL_GetCameraName(cameras[i]))
|
||||
{
|
||||
if (m_camera_id == name)
|
||||
{
|
||||
m_sdl_camera_id = cameras[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(cameras);
|
||||
|
||||
if (!m_sdl_camera_id)
|
||||
{
|
||||
camera_log.error("Camera %s not found", m_camera_id);
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string camera_id;
|
||||
|
||||
if (const char* name = SDL_GetCameraName(m_sdl_camera_id))
|
||||
{
|
||||
camera_log.notice("Using camera: name=%s", name);
|
||||
camera_id = name;
|
||||
}
|
||||
|
||||
SDL_CameraSpec used_spec
|
||||
{
|
||||
.format = SDL_PixelFormat::SDL_PIXELFORMAT_RGBA32,
|
||||
.colorspace = SDL_Colorspace::SDL_COLORSPACE_RGB_DEFAULT,
|
||||
.width = static_cast<int>(m_width),
|
||||
.height = static_cast<int>(m_height),
|
||||
.framerate_numerator = 30,
|
||||
.framerate_denominator = 1
|
||||
};
|
||||
|
||||
int num_formats = 0;
|
||||
if (SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(m_sdl_camera_id, &num_formats))
|
||||
{
|
||||
if (num_formats <= 0)
|
||||
{
|
||||
camera_log.error("No SDL camera specs found");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load selected settings from config file
|
||||
bool success = false;
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::sdl), 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.
|
||||
constexpr double epsilon = 0.001;
|
||||
success = false;
|
||||
|
||||
for (int i = 0; i < num_formats; i++)
|
||||
{
|
||||
if (!specs[i]) continue;
|
||||
|
||||
const SDL_CameraSpec& spec = *specs[i];
|
||||
const f64 fps = spec.framerate_numerator / static_cast<f64>(spec.framerate_denominator);
|
||||
|
||||
if (spec.width == cfg_setting.width &&
|
||||
spec.height == cfg_setting.height &&
|
||||
fps >= (cfg_setting.min_fps - epsilon) &&
|
||||
fps <= (cfg_setting.min_fps + epsilon) &&
|
||||
fps >= (cfg_setting.max_fps - epsilon) &&
|
||||
fps <= (cfg_setting.max_fps + epsilon) &&
|
||||
spec.format == static_cast<SDL_PixelFormat>(cfg_setting.format) &&
|
||||
spec.colorspace == static_cast<SDL_Colorspace>(cfg_setting.colorspace))
|
||||
{
|
||||
// Apply settings.
|
||||
camera_log.notice("Setting camera spec: %s", spec);
|
||||
|
||||
// TODO: SDL converts the image for us. We would have to do this manually if we want to use other formats.
|
||||
//used_spec = spec;
|
||||
used_spec.width = spec.width;
|
||||
used_spec.height = spec.height;
|
||||
used_spec.framerate_numerator = spec.framerate_numerator;
|
||||
used_spec.framerate_denominator = spec.framerate_denominator;
|
||||
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, colorspace=%d",
|
||||
cfg_setting.max_fps, cfg_setting.width, cfg_setting.height, cfg_setting.format, cfg_setting.colorspace);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
camera_log.notice("Using default camera spec: %s", used_spec);
|
||||
}
|
||||
}
|
||||
SDL_free(specs);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera specs found. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
camera_log.notice("Requesting camera spec: %s", used_spec);
|
||||
|
||||
m_camera = SDL_OpenCamera(m_sdl_camera_id, &used_spec);
|
||||
|
||||
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 (const char* driver = SDL_GetCurrentCameraDriver())
|
||||
{
|
||||
camera_log.notice("Using driver: %s", driver);
|
||||
}
|
||||
|
||||
if (SDL_CameraSpec spec {}; SDL_GetCameraFormat(m_camera, &spec))
|
||||
{
|
||||
camera_log.notice("Using camera spec: %s", spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("Could not get camera spec. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
const SDL_CameraPosition position = SDL_GetCameraPosition(m_sdl_camera_id);
|
||||
const bool front_facing = position == SDL_CameraPosition::SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
|
||||
// TODO: this doesn't seem to have any properties at the moment
|
||||
// const SDL_PropertiesID property_id = SDL_GetCameraProperties(m_camera);
|
||||
// SDL_HasProperty(property_id, ...);
|
||||
|
||||
m_video_sink = std::make_unique<sdl_camera_video_sink>(front_facing, m_camera);
|
||||
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);
|
||||
|
||||
set_state(camera_handler_state::open);
|
||||
}
|
||||
|
||||
void sdl_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
|
||||
reset();
|
||||
|
||||
set_state(camera_handler_state::closed);
|
||||
}
|
||||
|
||||
void sdl_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;
|
||||
}
|
||||
|
||||
const int camera_permission = SDL_GetCameraPermissionState(m_camera);
|
||||
switch (camera_permission)
|
||||
{
|
||||
case -1: // Denied
|
||||
camera_log.error("Camera permission denied");
|
||||
set_state(camera_handler_state::closed);
|
||||
reset();
|
||||
return;
|
||||
case 0: // Pending
|
||||
// TODO: try to get permission
|
||||
break;
|
||||
case 1: // Approved
|
||||
break;
|
||||
}
|
||||
|
||||
// Start camera. We will start receiving frames now.
|
||||
set_state(camera_handler_state::running);
|
||||
}
|
||||
|
||||
void sdl_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;
|
||||
}
|
||||
|
||||
// Stop camera. The camera will still be drawing power.
|
||||
set_expected_state(camera_handler_state::open);
|
||||
}
|
||||
|
||||
void sdl_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 sdl_camera_handler::set_frame_rate(u32 frame_rate)
|
||||
{
|
||||
m_frame_rate = frame_rate;
|
||||
}
|
||||
|
||||
void sdl_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 sdl_camera_handler::set_mirrored(bool mirrored)
|
||||
{
|
||||
m_mirrored = mirrored;
|
||||
|
||||
if (m_video_sink)
|
||||
{
|
||||
m_video_sink->set_mirrored(m_mirrored);
|
||||
}
|
||||
}
|
||||
|
||||
u64 sdl_camera_handler::frame_number() const
|
||||
{
|
||||
return m_video_sink ? m_video_sink->frame_number() : 0;
|
||||
}
|
||||
|
||||
camera_handler_base::camera_handler_state sdl_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.sdl_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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#endif
|
49
rpcs3/Input/sdl_camera_handler.h
Normal file
49
rpcs3/Input/sdl_camera_handler.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "Emu/Io/camera_handler_base.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
class sdl_camera_video_sink;
|
||||
|
||||
class sdl_camera_handler : public camera_handler_base
|
||||
{
|
||||
public:
|
||||
sdl_camera_handler();
|
||||
virtual ~sdl_camera_handler();
|
||||
|
||||
void open_camera() override;
|
||||
void close_camera() override;
|
||||
void start_camera() override;
|
||||
void stop_camera() override;
|
||||
void set_format(s32 format, u32 bytesize) override;
|
||||
void set_frame_rate(u32 frame_rate) override;
|
||||
void set_resolution(u32 width, u32 height) override;
|
||||
void set_mirrored(bool mirrored) override;
|
||||
u64 frame_number() const override;
|
||||
camera_handler_state get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) override;
|
||||
|
||||
static std::vector<std::string> get_drivers();
|
||||
static std::map<SDL_CameraID, std::string> get_cameras();
|
||||
|
||||
private:
|
||||
void reset();
|
||||
|
||||
std::string m_camera_id;
|
||||
SDL_CameraID m_sdl_camera_id = 0;
|
||||
SDL_Camera* m_camera = nullptr;
|
||||
std::unique_ptr<sdl_camera_video_sink> m_video_sink;
|
||||
};
|
||||
|
||||
#endif
|
168
rpcs3/Input/sdl_camera_video_sink.cpp
Normal file
168
rpcs3/Input/sdl_camera_video_sink.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_camera_video_sink.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
sdl_camera_video_sink::sdl_camera_video_sink(bool front_facing, SDL_Camera* camera)
|
||||
: camera_video_sink(front_facing), m_camera(camera)
|
||||
{
|
||||
ensure(m_camera);
|
||||
|
||||
m_thread = std::make_unique<std::thread>(&sdl_camera_video_sink::run, this);
|
||||
}
|
||||
|
||||
sdl_camera_video_sink::~sdl_camera_video_sink()
|
||||
{
|
||||
m_terminate = true;
|
||||
|
||||
if (m_thread && m_thread->joinable())
|
||||
{
|
||||
m_thread->join();
|
||||
m_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_video_sink::present(SDL_Surface* frame)
|
||||
{
|
||||
const int bytes_per_pixel = SDL_BYTESPERPIXEL(frame->format);
|
||||
const u32 src_width_in_bytes = std::max(0, frame->w * bytes_per_pixel);
|
||||
const u32 dst_width_in_bytes = std::max<u32>(0, m_width * bytes_per_pixel);
|
||||
const u8* pixels = reinterpret_cast<const u8*>(frame->pixels);
|
||||
|
||||
bool use_buffer = false;
|
||||
|
||||
// Scale image if necessary
|
||||
const bool scale_image = m_width > 0 && m_height > 0 && m_width != static_cast<u32>(frame->w) && m_height != static_cast<u32>(frame->h);
|
||||
|
||||
// Determine image flip
|
||||
const camera_flip flip_setting = g_cfg.io.camera_flip_option;
|
||||
|
||||
bool flip_horizontally = m_front_facing; // Front facing cameras are flipped already
|
||||
if (flip_setting == camera_flip::horizontal || flip_setting == camera_flip::both)
|
||||
{
|
||||
flip_horizontally = !flip_horizontally;
|
||||
}
|
||||
if (m_mirrored) // Set by the game
|
||||
{
|
||||
flip_horizontally = !flip_horizontally;
|
||||
}
|
||||
|
||||
bool flip_vertically = false;
|
||||
if (flip_setting == camera_flip::vertical || flip_setting == camera_flip::both)
|
||||
{
|
||||
flip_vertically = !flip_vertically;
|
||||
}
|
||||
|
||||
// Flip image if necessary
|
||||
if (flip_horizontally || flip_vertically || scale_image)
|
||||
{
|
||||
m_buffer.resize(m_height * dst_width_in_bytes);
|
||||
use_buffer = true;
|
||||
|
||||
if (m_width > 0 && m_height > 0 && frame->w > 0 && frame->h > 0)
|
||||
{
|
||||
const f32 scale_x = frame->w / static_cast<f32>(m_width);
|
||||
const f32 scale_y = frame->h / static_cast<f32>(m_height);
|
||||
|
||||
if (flip_horizontally && flip_vertically)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = frame->h - static_cast<u32>(scale_y * y) - 1;
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = frame->w - static_cast<u32>(scale_x * x) - 1;
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (flip_horizontally)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = static_cast<u32>(scale_y * y);
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = frame->w - static_cast<u32>(scale_x * x) - 1;
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (flip_vertically)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = frame->h - static_cast<u32>(scale_y * y) - 1;
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = static_cast<u32>(scale_x * x);
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = static_cast<u32>(scale_y * y);
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = static_cast<u32>(scale_x * x);
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (use_buffer)
|
||||
{
|
||||
camera_video_sink::present(m_width, m_height, dst_width_in_bytes, bytes_per_pixel, [src = m_buffer.data(), dst_width_in_bytes](u32 y){ return src + y * dst_width_in_bytes; });
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_video_sink::present(frame->w, frame->h, frame->pitch, bytes_per_pixel, [pixels, pitch = frame->pitch](u32 y){ return pixels + y * pitch; });
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_video_sink::run()
|
||||
{
|
||||
thread_base::set_name("SDL Capture Thread");
|
||||
|
||||
camera_log.notice("SDL Capture Thread started");
|
||||
|
||||
while (!m_terminate)
|
||||
{
|
||||
// Copy latest image into out buffer.
|
||||
u64 timestamp_ns = 0;
|
||||
SDL_Surface* frame = SDL_AcquireCameraFrame(m_camera, ×tamp_ns);
|
||||
if (!frame)
|
||||
{
|
||||
// No new frame
|
||||
std::this_thread::sleep_for(100us);
|
||||
continue;
|
||||
}
|
||||
|
||||
present(frame);
|
||||
|
||||
SDL_ReleaseCameraFrame(m_camera, frame);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
34
rpcs3/Input/sdl_camera_video_sink.h
Normal file
34
rpcs3/Input/sdl_camera_video_sink.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "Input/camera_video_sink.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <thread>
|
||||
|
||||
class sdl_camera_video_sink final : public camera_video_sink
|
||||
{
|
||||
public:
|
||||
sdl_camera_video_sink(bool front_facing, SDL_Camera* camera);
|
||||
virtual ~sdl_camera_video_sink();
|
||||
|
||||
private:
|
||||
void present(SDL_Surface* frame);
|
||||
void run();
|
||||
|
||||
std::vector<u8> m_buffer;
|
||||
atomic_t<bool> m_terminate = false;
|
||||
SDL_Camera* m_camera = nullptr;
|
||||
std::unique_ptr<std::thread> m_thread;
|
||||
};
|
||||
|
||||
#endif
|
137
rpcs3/Input/sdl_instance.cpp
Normal file
137
rpcs3/Input/sdl_instance.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_instance.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(sdl_log, "SDL");
|
||||
|
||||
sdl_instance::~sdl_instance()
|
||||
{
|
||||
// Only quit SDL once on exit. SDL uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
sdl_log.notice("Quitting SDL ...");
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
bool sdl_instance::initialize()
|
||||
{
|
||||
std::lock_guard lock(mtx);
|
||||
|
||||
if (m_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool instance_success = false;
|
||||
|
||||
Emu.BlockingCallFromMainThread([this, &instance_success]()
|
||||
{
|
||||
instance_success = initialize_impl();
|
||||
});
|
||||
|
||||
return instance_success;
|
||||
}
|
||||
|
||||
bool sdl_instance::initialize_impl()
|
||||
{
|
||||
// Only init SDL once. SDL uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
sdl_log.notice("Initializing SDL ...");
|
||||
|
||||
// Set non-dynamic hints before SDL_Init
|
||||
if (!SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"))
|
||||
{
|
||||
sdl_log.error("Could not set SDL_HINT_JOYSTICK_THREAD: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD | SDL_INIT_CAMERA))
|
||||
{
|
||||
sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
|
||||
SDL_SetLogOutputFunction([](void*, int category, SDL_LogPriority priority, const char* message)
|
||||
{
|
||||
std::string category_name;
|
||||
switch (category)
|
||||
{
|
||||
case SDL_LOG_CATEGORY_APPLICATION:
|
||||
category_name = "app";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_ERROR:
|
||||
category_name = "error";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_ASSERT:
|
||||
category_name = "assert";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_SYSTEM:
|
||||
category_name = "system";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_AUDIO:
|
||||
category_name = "audio";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_VIDEO:
|
||||
category_name = "video";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_RENDER:
|
||||
category_name = "render";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_INPUT:
|
||||
category_name = "input";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_TEST:
|
||||
category_name = "test";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_GPU:
|
||||
category_name = "gpu";
|
||||
break;
|
||||
default:
|
||||
category_name = fmt::format("unknown(%d)", category);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (priority)
|
||||
{
|
||||
case SDL_LOG_PRIORITY_VERBOSE:
|
||||
case SDL_LOG_PRIORITY_DEBUG:
|
||||
sdl_log.trace("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_INFO:
|
||||
sdl_log.notice("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_WARN:
|
||||
sdl_log.warning("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_ERROR:
|
||||
sdl_log.error("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_CRITICAL:
|
||||
sdl_log.error("%s: %s", category_name, message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, nullptr);
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
28
rpcs3/Input/sdl_instance.h
Normal file
28
rpcs3/Input/sdl_instance.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
|
||||
#include <mutex>
|
||||
|
||||
struct sdl_instance
|
||||
{
|
||||
public:
|
||||
sdl_instance() = default;
|
||||
virtual ~sdl_instance();
|
||||
|
||||
static sdl_instance& get_instance()
|
||||
{
|
||||
static sdl_instance instance {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool initialize();
|
||||
|
||||
private:
|
||||
bool initialize_impl();
|
||||
|
||||
bool m_initialized = false;
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_pad_handler.h"
|
||||
#include "sdl_instance.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/System.h"
|
||||
|
@ -10,117 +11,6 @@
|
|||
|
||||
LOG_CHANNEL(sdl_log, "SDL");
|
||||
|
||||
struct sdl_instance
|
||||
{
|
||||
public:
|
||||
sdl_instance() = default;
|
||||
~sdl_instance()
|
||||
{
|
||||
// Only quit SDL once on exit. SDL uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
sdl_log.notice("Quitting SDL ...");
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
static sdl_instance& get_instance()
|
||||
{
|
||||
static sdl_instance instance {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool initialize()
|
||||
{
|
||||
// Only init SDL once. SDL uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
sdl_log.notice("Initializing SDL ...");
|
||||
|
||||
// Set non-dynamic hints before SDL_Init
|
||||
if (!SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"))
|
||||
{
|
||||
sdl_log.error("Could not set SDL_HINT_JOYSTICK_THREAD: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD))
|
||||
{
|
||||
sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
|
||||
SDL_SetLogOutputFunction([](void*, int category, SDL_LogPriority priority, const char* message)
|
||||
{
|
||||
std::string category_name;
|
||||
switch (category)
|
||||
{
|
||||
case SDL_LOG_CATEGORY_APPLICATION:
|
||||
category_name = "app";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_ERROR:
|
||||
category_name = "error";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_ASSERT:
|
||||
category_name = "assert";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_SYSTEM:
|
||||
category_name = "system";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_AUDIO:
|
||||
category_name = "audio";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_VIDEO:
|
||||
category_name = "video";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_RENDER:
|
||||
category_name = "render";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_INPUT:
|
||||
category_name = "input";
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_TEST:
|
||||
category_name = "test";
|
||||
break;
|
||||
default:
|
||||
category_name = fmt::format("unknown(%d)", category);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (priority)
|
||||
{
|
||||
case SDL_LOG_PRIORITY_VERBOSE:
|
||||
case SDL_LOG_PRIORITY_DEBUG:
|
||||
sdl_log.trace("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_INFO:
|
||||
sdl_log.notice("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_WARN:
|
||||
sdl_log.warning("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_ERROR:
|
||||
sdl_log.error("%s: %s", category_name, message);
|
||||
break;
|
||||
case SDL_LOG_PRIORITY_CRITICAL:
|
||||
sdl_log.error("%s: %s", category_name, message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, nullptr);
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
sdl_pad_handler::sdl_pad_handler() : PadHandlerBase(pad_handler::sdl)
|
||||
{
|
||||
button_list =
|
||||
|
@ -266,14 +156,7 @@ bool sdl_pad_handler::Init()
|
|||
if (m_is_init)
|
||||
return true;
|
||||
|
||||
bool instance_success;
|
||||
|
||||
Emu.BlockingCallFromMainThread([&instance_success]()
|
||||
{
|
||||
instance_success = sdl_instance::get_instance().initialize();
|
||||
});
|
||||
|
||||
if (!instance_success)
|
||||
if (!sdl_instance::get_instance().initialize())
|
||||
return false;
|
||||
|
||||
if (g_cfg.io.load_sdl_mappings)
|
||||
|
|
|
@ -98,6 +98,9 @@ void headless_application::InitializeCallbacks()
|
|||
return std::make_shared<null_camera_handler>();
|
||||
}
|
||||
case camera_handler::qt:
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
{
|
||||
fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get());
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="display_sleep_control.cpp" />
|
||||
<ClCompile Include="Input\camera_video_sink.cpp" />
|
||||
<ClCompile Include="Input\dualsense_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\evdev_gun_handler.cpp" />
|
||||
<ClCompile Include="Input\gui_pad_thread.cpp" />
|
||||
|
@ -185,6 +186,9 @@
|
|||
<ClCompile Include="Input\raw_mouse_config.cpp" />
|
||||
<ClCompile Include="Input\raw_mouse_handler.cpp" />
|
||||
<ClCompile Include="Input\ps_move_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_camera_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_camera_video_sink.cpp" />
|
||||
<ClCompile Include="Input\sdl_instance.cpp" />
|
||||
<ClCompile Include="Input\sdl_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\skateboard_pad_handler.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
|
@ -890,6 +894,7 @@
|
|||
<ClInclude Include="Input\basic_keyboard_handler.h" />
|
||||
<ClInclude Include="Input\basic_mouse_handler.h" />
|
||||
<ClInclude Include="display_sleep_control.h" />
|
||||
<ClInclude Include="Input\camera_video_sink.h" />
|
||||
<ClInclude Include="Input\ds3_pad_handler.h" />
|
||||
<ClInclude Include="Input\ds4_pad_handler.h" />
|
||||
<ClInclude Include="Input\dualsense_pad_handler.h" />
|
||||
|
@ -1004,6 +1009,9 @@
|
|||
<ClInclude Include="Input\raw_mouse_config.h" />
|
||||
<ClInclude Include="Input\raw_mouse_handler.h" />
|
||||
<ClInclude Include="Input\ps_move_handler.h" />
|
||||
<ClInclude Include="Input\sdl_camera_handler.h" />
|
||||
<ClInclude Include="Input\sdl_camera_video_sink.h" />
|
||||
<ClInclude Include="Input\sdl_instance.h" />
|
||||
<ClInclude Include="Input\sdl_pad_handler.h" />
|
||||
<ClInclude Include="Input\skateboard_pad_handler.h" />
|
||||
<ClInclude Include="main_application.h" />
|
||||
|
|
|
@ -1176,6 +1176,18 @@
|
|||
<ClCompile Include="rpcs3qt\gui_game_info.cpp">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\sdl_camera_handler.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\sdl_instance.cpp">
|
||||
<Filter>Io\SDL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\camera_video_sink.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\sdl_camera_video_sink.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
|
@ -1385,6 +1397,18 @@
|
|||
<ClInclude Include="rpcs3qt\gui_game_info.h">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\sdl_camera_handler.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\sdl_instance.h">
|
||||
<Filter>Io\SDL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\camera_video_sink.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\sdl_camera_video_sink.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
#include "ui_camera_settings_dialog.h"
|
||||
#include "permissions.h"
|
||||
#include "Emu/Io/camera_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Input/sdl_instance.h"
|
||||
|
||||
#include <QCameraDevice>
|
||||
#include <QMediaDevices>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QVideoSink>
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
#include "Input/sdl_camera_handler.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
|
@ -53,6 +60,80 @@ void fmt_class_string<QVideoFrameFormat::PixelFormat>::format(std::string& out,
|
|||
});
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
static QString sdl_pixelformat_to_string(SDL_PixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_UNKNOWN: return "UNKNOWN";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1LSB: return "INDEX1LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1MSB: return "INDEX1MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2LSB: return "INDEX2LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2MSB: return "INDEX2MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4LSB: return "INDEX4LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4MSB: return "INDEX4MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX8: return "INDEX8";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB332: return "RGB332";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB4444: return "XRGB4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR4444: return "XBGR4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB1555: return "XRGB1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR1555: return "XBGR1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB4444: return "ARGB4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA4444: return "RGBA4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR4444: return "ABGR4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA4444: return "BGRA4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB1555: return "ARGB1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA5551: return "RGBA5551";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR1555: return "ABGR1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA5551: return "BGRA5551";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB565: return "RGB565";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR565: return "BGR565";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB24: return "RGB24";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR24: return "BGR24";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB8888: return "XRGB8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBX8888: return "RGBX8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR8888: return "XBGR8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRX8888: return "BGRX8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB8888: return "ARGB8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA8888: return "RGBA8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR8888: return "ABGR8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA8888: return "BGRA8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB2101010: return "XRGB2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR2101010: return "XBGR2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB2101010: return "ARGB2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR2101010: return "ABGR2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48: return "RGB48";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48: return "BGR48";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64: return "RGBA64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64: return "ARGB64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64: return "BGRA64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64: return "ABGR64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48_FLOAT: return "RGB48_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48_FLOAT: return "BGR48_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64_FLOAT: return "RGBA64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64_FLOAT: return "ARGB64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64_FLOAT: return "BGRA64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64_FLOAT: return "ABGR64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB96_FLOAT: return "RGB96_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR96_FLOAT: return "BGR96_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA128_FLOAT: return "RGBA128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB128_FLOAT: return "ARGB128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA128_FLOAT: return "BGRA128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR128_FLOAT: return "ABGR128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YV12: return "YV12";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_IYUV: return "IYUV";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YUY2: return "YUY2";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_UYVY: return "UYVY";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YVYU: return "YVYU";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_NV12: return "NV12";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_NV21: return "NV21";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_P010: return "P010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_EXTERNAL_OES: return "EXTERNAL_OES";
|
||||
default: return QObject::tr("Unknown: %0").arg(static_cast<int>(format));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_DECLARE_METATYPE(QCameraDevice);
|
||||
|
||||
camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
||||
|
@ -61,15 +142,16 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
|||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
load_config();
|
||||
|
||||
for (const QCameraDevice& camera_info : QMediaDevices::videoInputs())
|
||||
{
|
||||
if (camera_info.isNull()) continue;
|
||||
ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info));
|
||||
camera_log.notice("Found camera: '%s'", camera_info.description());
|
||||
}
|
||||
ui->combo_handlers->addItem("Qt", QVariant::fromValue(static_cast<int>(camera_handler::qt)));
|
||||
#ifdef HAVE_SDL3
|
||||
ui->combo_handlers->addItem("SDL", QVariant::fromValue(static_cast<int>(camera_handler::sdl)));
|
||||
#endif
|
||||
|
||||
connect(ui->combo_handlers, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_handler_change);
|
||||
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_camera_change);
|
||||
connect(ui->combo_settings, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_settings_change);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
||||
|
@ -85,33 +167,182 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
|||
}
|
||||
});
|
||||
|
||||
if (ui->combo_camera->count() == 0)
|
||||
{
|
||||
ui->combo_camera->setEnabled(false);
|
||||
ui->combo_settings->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
|
||||
}
|
||||
else
|
||||
ui->combo_handlers->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
camera_settings_dialog::~camera_settings_dialog()
|
||||
{
|
||||
reset_cameras();
|
||||
}
|
||||
|
||||
void camera_settings_dialog::enable_combos()
|
||||
{
|
||||
reset_cameras();
|
||||
|
||||
const bool is_enabled = ui->combo_camera->count() > 0;
|
||||
|
||||
ui->combo_camera->setEnabled(is_enabled);
|
||||
ui->combo_settings->setEnabled(is_enabled);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(is_enabled);
|
||||
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(is_enabled);
|
||||
|
||||
if (is_enabled)
|
||||
{
|
||||
// TODO: show camera ID somewhere
|
||||
ui->combo_camera->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
camera_settings_dialog::~camera_settings_dialog()
|
||||
void camera_settings_dialog::reset_cameras()
|
||||
{
|
||||
#ifdef HAVE_SDL3
|
||||
if (m_sdl_thread)
|
||||
{
|
||||
auto& thread = *m_sdl_thread;
|
||||
thread = thread_state::aborting;
|
||||
thread();
|
||||
m_sdl_thread.reset();
|
||||
}
|
||||
|
||||
if (m_sdl_camera)
|
||||
{
|
||||
SDL_CloseCamera(m_sdl_camera);
|
||||
m_sdl_camera = nullptr;
|
||||
}
|
||||
|
||||
m_video_frame_input.reset();
|
||||
#endif
|
||||
|
||||
m_camera.reset();
|
||||
m_media_capture_session.reset();
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_handler_change(int index)
|
||||
{
|
||||
if (index < 0 || !ui->combo_handlers->itemData(index).canConvert<int>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
ui->combo_camera->clear();
|
||||
enable_combos();
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler = static_cast<camera_handler>(ui->combo_handlers->itemData(index).value<int>());
|
||||
|
||||
ui->combo_settings->blockSignals(true);
|
||||
ui->combo_camera->blockSignals(true);
|
||||
|
||||
ui->combo_settings->clear();
|
||||
ui->combo_camera->clear();
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
{
|
||||
for (const QCameraDevice& camera_info : QMediaDevices::videoInputs())
|
||||
{
|
||||
if (camera_info.isNull()) continue;
|
||||
ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info));
|
||||
camera_log.notice("Found camera: '%s'", camera_info.description());
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
{
|
||||
if (!sdl_instance::get_instance().initialize())
|
||||
{
|
||||
camera_log.error("Could not initialize SDL");
|
||||
break;
|
||||
}
|
||||
|
||||
// Log camera drivers
|
||||
sdl_camera_handler::get_drivers();
|
||||
|
||||
// Get cameras
|
||||
const std::map<SDL_CameraID, std::string> cameras = sdl_camera_handler::get_cameras();
|
||||
|
||||
// Add cameras
|
||||
for (const auto& [camera_id, name] : cameras)
|
||||
{
|
||||
ui->combo_camera->addItem(QString::fromStdString(name), QVariant::fromValue(static_cast<u32>(camera_id)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
|
||||
ui->combo_settings->blockSignals(false);
|
||||
ui->combo_camera->blockSignals(false);
|
||||
|
||||
enable_combos();
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_camera_change(int index)
|
||||
{
|
||||
if (index < 0 || !ui->combo_camera->itemData(index).canConvert<QCameraDevice>())
|
||||
if (index < 0)
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QCameraDevice camera_info = ui->combo_camera->itemData(index).value<QCameraDevice>();
|
||||
reset_cameras();
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
handle_qt_camera_change(ui->combo_camera->itemData(index));
|
||||
break;
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
handle_sdl_camera_change(ui->combo_camera->itemText(index), ui->combo_camera->itemData(index));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_settings_change(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gui::utils::check_camera_permission(this,
|
||||
[this, index](){ handle_settings_change(index); },
|
||||
[this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); }))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
handle_qt_settings_change(ui->combo_settings->itemData(index));
|
||||
break;
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
handle_sdl_settings_change(ui->combo_settings->itemData(index));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_qt_camera_change(const QVariant& item_data)
|
||||
{
|
||||
if (!item_data.canConvert<QCameraDevice>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QCameraDevice camera_info = item_data.value<QCameraDevice>();
|
||||
|
||||
if (camera_info.isNull())
|
||||
{
|
||||
|
@ -119,10 +350,10 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
return;
|
||||
}
|
||||
|
||||
m_camera.reset(new QCamera(camera_info));
|
||||
m_media_capture_session.reset(new QMediaCaptureSession(nullptr));
|
||||
m_camera = std::make_unique<QCamera>(camera_info);
|
||||
m_media_capture_session = std::make_unique<QMediaCaptureSession>(nullptr);
|
||||
m_media_capture_session->setCamera(m_camera.get());
|
||||
m_media_capture_session->setVideoSink(ui->videoWidget->videoSink());
|
||||
m_media_capture_session->setVideoOutput(ui->videoWidget);
|
||||
|
||||
if (!m_camera->isAvailable())
|
||||
{
|
||||
|
@ -173,14 +404,14 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
int index = 0;
|
||||
bool success = false;
|
||||
const std::string key = camera_info.id().toStdString();
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(key, success);
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::qt), key, success);
|
||||
|
||||
if (success)
|
||||
{
|
||||
camera_log.notice("Found config entry for camera \"%s\"", key);
|
||||
|
||||
// Select matching drowdown entry
|
||||
const double epsilon = 0.001;
|
||||
constexpr double epsilon = 0.001;
|
||||
|
||||
for (int i = 0; i < ui->combo_settings->count(); i++)
|
||||
{
|
||||
|
@ -202,19 +433,10 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
|
||||
ui->combo_settings->setCurrentIndex(std::max<int>(0, index));
|
||||
ui->combo_settings->setEnabled(true);
|
||||
|
||||
// Update config to match user interface outcome
|
||||
const QCameraFormat setting = ui->combo_settings->currentData().value<QCameraFormat>();
|
||||
cfg_setting.width = setting.resolution().width();
|
||||
cfg_setting.height = setting.resolution().height();
|
||||
cfg_setting.min_fps = setting.minFrameRate();
|
||||
cfg_setting.max_fps = setting.maxFrameRate();
|
||||
cfg_setting.format = static_cast<int>(setting.pixelFormat());
|
||||
g_cfg_camera.set_camera_setting(key, cfg_setting);
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_settings_change(int index)
|
||||
void camera_settings_dialog::handle_qt_settings_change(const QVariant& item_data)
|
||||
{
|
||||
if (!m_camera)
|
||||
{
|
||||
|
@ -227,16 +449,9 @@ void camera_settings_dialog::handle_settings_change(int index)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!gui::utils::check_camera_permission(this,
|
||||
[this, index](){ handle_settings_change(index); },
|
||||
[this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); }))
|
||||
if (item_data.canConvert<QCameraFormat>() && ui->combo_camera->currentData().canConvert<QCameraDevice>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= 0 && ui->combo_settings->itemData(index).canConvert<QCameraFormat>() && ui->combo_camera->currentData().canConvert<QCameraDevice>())
|
||||
{
|
||||
const QCameraFormat setting = ui->combo_settings->itemData(index).value<QCameraFormat>();
|
||||
const QCameraFormat setting = item_data.value<QCameraFormat>();
|
||||
if (!setting.isNull())
|
||||
{
|
||||
m_camera->setCameraFormat(setting);
|
||||
|
@ -248,12 +463,212 @@ void camera_settings_dialog::handle_settings_change(int index)
|
|||
cfg_setting.min_fps = setting.minFrameRate();
|
||||
cfg_setting.max_fps = setting.maxFrameRate();
|
||||
cfg_setting.format = static_cast<int>(setting.pixelFormat());
|
||||
g_cfg_camera.set_camera_setting(ui->combo_camera->currentData().value<QCameraDevice>().id().toStdString(), cfg_setting);
|
||||
cfg_setting.colorspace = 0;
|
||||
g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::qt), ui->combo_camera->currentData().value<QCameraDevice>().id().toStdString(), cfg_setting);
|
||||
}
|
||||
|
||||
m_camera->start();
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
void camera_settings_dialog::handle_sdl_camera_change(const QString& name, const QVariant& item_data)
|
||||
{
|
||||
if (!item_data.canConvert<u32>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 camera_id = item_data.value<u32>();
|
||||
|
||||
if (!camera_id)
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ui->combo_settings->blockSignals(true);
|
||||
ui->combo_settings->clear();
|
||||
|
||||
std::vector<SDL_CameraSpec> settings;
|
||||
|
||||
int num_formats = 0;
|
||||
if (SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(camera_id, &num_formats))
|
||||
{
|
||||
if (num_formats <= 0)
|
||||
{
|
||||
camera_log.error("No SDL camera specs found");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < num_formats; i++)
|
||||
{
|
||||
if (!specs[i]) continue;
|
||||
settings.push_back(*specs[i]);
|
||||
}
|
||||
}
|
||||
SDL_free(specs);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera specs found. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
std::sort(settings.begin(), settings.end(), [](const SDL_CameraSpec& l, const SDL_CameraSpec& r) -> bool
|
||||
{
|
||||
const f32 l_fps = l.framerate_numerator / static_cast<f32>(l.framerate_denominator);
|
||||
const f32 r_fps = r.framerate_numerator / static_cast<f32>(r.framerate_denominator);
|
||||
|
||||
if (l.width > r.width) return true;
|
||||
if (l.width < r.width) return false;
|
||||
if (l.height > r.height) return true;
|
||||
if (l.height < r.height) return false;
|
||||
if (l_fps > r_fps) return true;
|
||||
if (l_fps < r_fps) return false;
|
||||
if (l.format > r.format) return true;
|
||||
if (l.format < r.format) return false;
|
||||
if (l.colorspace > r.colorspace) return true;
|
||||
if (l.colorspace < r.colorspace) return false;
|
||||
return false;
|
||||
});
|
||||
|
||||
for (const SDL_CameraSpec& setting : settings)
|
||||
{
|
||||
const f32 fps = setting.framerate_numerator / static_cast<f32>(setting.framerate_denominator);
|
||||
const QString description = tr("%0x%1, %2 FPS, Format=%3")
|
||||
.arg(setting.width)
|
||||
.arg(setting.height)
|
||||
.arg(fps)
|
||||
.arg(sdl_pixelformat_to_string(setting.format));
|
||||
ui->combo_settings->addItem(description, QVariant::fromValue(setting));
|
||||
}
|
||||
ui->combo_settings->blockSignals(false);
|
||||
|
||||
if (ui->combo_settings->count() == 0)
|
||||
{
|
||||
ui->combo_settings->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load selected settings from config file
|
||||
int index = 0;
|
||||
bool success = false;
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::sdl), name.toStdString(), success);
|
||||
|
||||
if (success)
|
||||
{
|
||||
camera_log.notice("Found config entry for camera \"%s\"", name);
|
||||
|
||||
// Select matching drowdown entry
|
||||
constexpr double epsilon = 0.001;
|
||||
|
||||
for (int i = 0; i < ui->combo_settings->count(); i++)
|
||||
{
|
||||
const SDL_CameraSpec tmp = ui->combo_settings->itemData(i).value<SDL_CameraSpec>();
|
||||
const f32 fps = tmp.framerate_numerator / static_cast<f32>(tmp.framerate_denominator);
|
||||
|
||||
if (tmp.width == cfg_setting.width &&
|
||||
tmp.height == cfg_setting.height &&
|
||||
fps >= (cfg_setting.min_fps - epsilon) &&
|
||||
fps <= (cfg_setting.min_fps + epsilon) &&
|
||||
fps >= (cfg_setting.max_fps - epsilon) &&
|
||||
fps <= (cfg_setting.max_fps + epsilon) &&
|
||||
tmp.format == static_cast<SDL_PixelFormat>(cfg_setting.format) &&
|
||||
tmp.colorspace == static_cast<SDL_Colorspace>(cfg_setting.colorspace))
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sdl_camera_id = camera_id;
|
||||
|
||||
ui->combo_settings->setCurrentIndex(std::max<int>(0, index));
|
||||
ui->combo_settings->setEnabled(true);
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_sdl_settings_change(const QVariant& item_data)
|
||||
{
|
||||
reset_cameras();
|
||||
|
||||
if (item_data.canConvert<SDL_CameraSpec>())
|
||||
{
|
||||
// TODO: SDL converts the image for us. We would have to do this manually if we want to use other formats.
|
||||
const SDL_CameraSpec setting = item_data.value<SDL_CameraSpec>();
|
||||
const SDL_CameraSpec used_spec
|
||||
{
|
||||
.format = SDL_PixelFormat::SDL_PIXELFORMAT_RGBA32,
|
||||
.colorspace = SDL_Colorspace::SDL_COLORSPACE_RGB_DEFAULT,
|
||||
.width = setting.width,
|
||||
.height = setting.height,
|
||||
.framerate_numerator = setting.framerate_numerator,
|
||||
.framerate_denominator = setting.framerate_denominator
|
||||
};
|
||||
|
||||
m_sdl_camera = SDL_OpenCamera(m_sdl_camera_id, &used_spec);
|
||||
|
||||
m_video_frame_input = std::make_unique<QVideoFrameInput>();
|
||||
|
||||
m_media_capture_session = std::make_unique<QMediaCaptureSession>(nullptr);
|
||||
m_media_capture_session->setVideoFrameInput(m_video_frame_input.get());
|
||||
m_media_capture_session->setVideoOutput(ui->videoWidget);
|
||||
|
||||
connect(this, &camera_settings_dialog::video_frame_ready, m_video_frame_input.get(), &QVideoFrameInput::sendVideoFrame);
|
||||
|
||||
const f32 fps = setting.framerate_numerator / static_cast<f32>(setting.framerate_denominator);
|
||||
|
||||
cfg_camera::camera_setting cfg_setting;
|
||||
cfg_setting.width = setting.width;
|
||||
cfg_setting.height = setting.height;
|
||||
cfg_setting.min_fps = fps;
|
||||
cfg_setting.max_fps = fps;
|
||||
cfg_setting.format = static_cast<int>(setting.format);
|
||||
cfg_setting.colorspace = static_cast<int>(setting.colorspace);
|
||||
g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::sdl), ui->combo_camera->currentText().toStdString(), cfg_setting);
|
||||
}
|
||||
|
||||
if (!m_sdl_camera)
|
||||
{
|
||||
camera_log.error("Failed to open SDL camera %d. SDL Error: %s", m_sdl_camera_id, SDL_GetError());
|
||||
QMessageBox::warning(this, tr("Camera not available"), tr("The selected camera is not available.\nIt might be blocked by another application."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_sdl_thread = std::make_unique<named_thread<std::function<void()>>>("GUI SDL Capture Thread", [this](){ run_sdl(); });
|
||||
}
|
||||
|
||||
void camera_settings_dialog::run_sdl()
|
||||
{
|
||||
camera_log.notice("GUI SDL Capture Thread started");
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Copy latest image into out buffer.
|
||||
u64 timestamp_ns = 0;
|
||||
SDL_Surface* frame = SDL_AcquireCameraFrame(m_sdl_camera, ×tamp_ns);
|
||||
if (!frame)
|
||||
{
|
||||
// No new frame
|
||||
thread_ctrl::wait_for(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
const QImage::Format format = SDL_ISPIXELFORMAT_ALPHA(frame->format) ? QImage::Format_RGBA8888 : QImage::Format_RGB888;
|
||||
const QImage image = QImage(reinterpret_cast<const u8*>(frame->pixels), frame->w, frame->h, format);
|
||||
const QImage converted = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
QVideoFrame video_frame(QVideoFrameFormat(converted.size(), QVideoFrameFormat::Format_RGBA8888));
|
||||
video_frame.map(QVideoFrame::WriteOnly);
|
||||
std::memcpy(video_frame.bits(0), converted.constBits(), converted.sizeInBytes());
|
||||
video_frame.unmap();
|
||||
|
||||
Q_EMIT video_frame_ready(video_frame);
|
||||
|
||||
SDL_ReleaseCameraFrame(m_sdl_camera, frame);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void camera_settings_dialog::load_config()
|
||||
{
|
||||
if (!g_cfg_camera.load())
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/system_config.h"
|
||||
#include "Utilities/Thread.h"
|
||||
|
||||
#include <QCamera>
|
||||
#include <QDialog>
|
||||
#include <QMediaCaptureSession>
|
||||
#include <QVideoFrameInput>
|
||||
#include <QVideoFrame>
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
@ -17,15 +33,38 @@ public:
|
|||
camera_settings_dialog(QWidget* parent = nullptr);
|
||||
virtual ~camera_settings_dialog();
|
||||
|
||||
Q_SIGNALS:
|
||||
void video_frame_ready(const QVideoFrame& frame);
|
||||
|
||||
private Q_SLOTS:
|
||||
void handle_handler_change(int index);
|
||||
void handle_camera_change(int index);
|
||||
void handle_settings_change(int index);
|
||||
|
||||
private:
|
||||
void enable_combos();
|
||||
void reset_cameras();
|
||||
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void handle_qt_camera_change(const QVariant& item_data);
|
||||
void handle_qt_settings_change(const QVariant& item_data);
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
void handle_sdl_camera_change(const QString& name, const QVariant& item_data);
|
||||
void handle_sdl_settings_change(const QVariant& item_data);
|
||||
|
||||
void run_sdl();
|
||||
|
||||
SDL_Camera* m_sdl_camera = nullptr;
|
||||
SDL_CameraID m_sdl_camera_id = 0;
|
||||
std::unique_ptr<named_thread<std::function<void()>>> m_sdl_thread;
|
||||
std::unique_ptr<QVideoFrameInput> m_video_frame_input;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Ui::camera_settings_dialog> ui;
|
||||
std::unique_ptr<QCamera> m_camera;
|
||||
std::unique_ptr<QMediaCaptureSession> m_media_capture_session;
|
||||
camera_handler m_handler = camera_handler::qt;
|
||||
};
|
||||
|
|
|
@ -15,7 +15,23 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout" stretch="0,1,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="settingsLayout" stretch="1,2">
|
||||
<layout class="QHBoxLayout" name="settingsLayout" stretch="0,1,2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbHandler">
|
||||
<property name="title">
|
||||
<string>Handler</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="handler_layout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="combo_handlers">
|
||||
<property name="placeholderText">
|
||||
<string>No handlers found</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbCamera">
|
||||
<property name="title">
|
||||
|
@ -75,10 +91,10 @@
|
|||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -1129,6 +1129,9 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_
|
|||
case camera_handler::null: return tr("Null", "Camera handler");
|
||||
case camera_handler::fake: return tr("Fake", "Camera handler");
|
||||
case camera_handler::qt: return tr("Qt", "Camera handler");
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl: return tr("SDL", "Camera handler");
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case emu_settings_type::MusicHandler:
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include "_discord_utils.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
#include "Input/sdl_camera_handler.h"
|
||||
#endif
|
||||
|
||||
#include "Emu/Audio/audio_utils.h"
|
||||
#include "Emu/Io/Null/null_camera_handler.h"
|
||||
#include "Emu/Io/Null/null_music_handler.h"
|
||||
|
@ -576,6 +580,12 @@ void gui_application::InitializeCallbacks()
|
|||
{
|
||||
return std::make_shared<qt_camera_handler>();
|
||||
}
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
{
|
||||
return std::make_shared<sdl_camera_handler>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
|
|
@ -2992,8 +2992,8 @@ void main_window::CreateConnects()
|
|||
|
||||
connect(ui->confCamerasAct, &QAction::triggered, this, [this]()
|
||||
{
|
||||
camera_settings_dialog dlg(this);
|
||||
dlg.exec();
|
||||
camera_settings_dialog* dlg = new camera_settings_dialog(this);
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
connect(ui->confRPCNAct, &QAction::triggered, this, [this]()
|
||||
|
|
|
@ -26,8 +26,8 @@ movie_item_base::~movie_item_base()
|
|||
|
||||
void movie_item_base::init_pointers()
|
||||
{
|
||||
m_icon_loading_aborted.reset(new atomic_t<bool>(false));
|
||||
m_size_on_disk_loading_aborted.reset(new atomic_t<bool>(false));
|
||||
m_icon_loading_aborted = std::make_shared<atomic_t<bool>>(false);
|
||||
m_size_on_disk_loading_aborted = std::make_shared<atomic_t<bool>>(false);
|
||||
}
|
||||
|
||||
void movie_item_base::set_active(bool active)
|
||||
|
@ -67,7 +67,7 @@ void movie_item_base::init_movie()
|
|||
|
||||
if (lower.endsWith(".gif"))
|
||||
{
|
||||
m_movie.reset(new QMovie(m_movie_path));
|
||||
m_movie = std::make_shared<QMovie>(m_movie_path);
|
||||
m_movie_path.clear();
|
||||
|
||||
if (!m_movie->isValid())
|
||||
|
@ -99,17 +99,17 @@ void movie_item_base::init_movie()
|
|||
return;
|
||||
}
|
||||
|
||||
m_movie_buffer.reset(new QBuffer(&m_movie_data));
|
||||
m_movie_buffer = std::make_unique<QBuffer>(&m_movie_data);
|
||||
m_movie_buffer->open(QIODevice::ReadOnly);
|
||||
}
|
||||
|
||||
m_video_sink.reset(new QVideoSink());
|
||||
m_video_sink = std::make_shared<QVideoSink>();
|
||||
QObject::connect(m_video_sink.get(), &QVideoSink::videoFrameChanged, m_video_sink.get(), [this](const QVideoFrame& frame)
|
||||
{
|
||||
m_icon_callback(frame);
|
||||
});
|
||||
|
||||
m_media_player.reset(new QMediaPlayer());
|
||||
m_media_player = std::make_unique<QMediaPlayer>();
|
||||
m_media_player->setVideoSink(m_video_sink.get());
|
||||
m_media_player->setLoops(QMediaPlayer::Infinite);
|
||||
|
||||
|
|
|
@ -341,14 +341,14 @@ void qt_camera_handler::update_camera_settings()
|
|||
|
||||
// Load selected settings from config file
|
||||
bool success = false;
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(camera_id, success);
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::qt), 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;
|
||||
constexpr double epsilon = 0.001;
|
||||
success = false;
|
||||
for (const QCameraFormat& supported_setting : m_camera->cameraDevice().videoFormats())
|
||||
{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "stdafx.h"
|
||||
#include "qt_camera_video_sink.h"
|
||||
|
||||
#include "Emu/Cell/Modules/cellCamera.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
@ -9,7 +8,7 @@
|
|||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
qt_camera_video_sink::qt_camera_video_sink(bool front_facing, QObject *parent)
|
||||
: QVideoSink(parent), m_front_facing(front_facing)
|
||||
: camera_video_sink(front_facing), QVideoSink(parent)
|
||||
{
|
||||
connect(this, &QVideoSink::videoFrameChanged, this, &qt_camera_video_sink::present);
|
||||
}
|
||||
|
@ -36,6 +35,8 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame)
|
|||
|
||||
// Get image. This usually also converts the image to ARGB32.
|
||||
QImage image = frame.toImage();
|
||||
const u32 width = image.isNull() ? 0 : static_cast<u32>(image.width());
|
||||
const u32 height = image.isNull() ? 0 : static_cast<u32>(image.height());
|
||||
|
||||
if (image.isNull())
|
||||
{
|
||||
|
@ -44,7 +45,7 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame)
|
|||
else
|
||||
{
|
||||
// Scale image if necessary
|
||||
if (m_width > 0 && m_height > 0 && m_width != static_cast<u32>(image.width()) && m_height != static_cast<u32>(image.height()))
|
||||
if (m_width > 0 && m_height > 0 && m_width != width && m_height != height)
|
||||
{
|
||||
image = image.scaled(m_width, m_height, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
@ -80,239 +81,10 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame)
|
|||
}
|
||||
}
|
||||
|
||||
const u64 new_size = m_bytesize;
|
||||
image_buffer& image_buffer = m_image_buffer[m_write_index];
|
||||
|
||||
// Reset buffer if necessary
|
||||
if (image_buffer.data.size() != new_size)
|
||||
{
|
||||
image_buffer.data.clear();
|
||||
}
|
||||
|
||||
// Create buffer if necessary
|
||||
if (image_buffer.data.empty() && new_size > 0)
|
||||
{
|
||||
image_buffer.data.resize(new_size);
|
||||
image_buffer.width = m_width;
|
||||
image_buffer.height = m_height;
|
||||
}
|
||||
|
||||
if (!image_buffer.data.empty() && !image.isNull())
|
||||
{
|
||||
// Convert image to proper layout
|
||||
// TODO: check if pixel format and bytes per pixel match and convert if necessary
|
||||
// TODO: implement or improve more conversions
|
||||
|
||||
const u32 width = std::min<u32>(image_buffer.width, image.width());
|
||||
const u32 height = std::min<u32>(image_buffer.height, image.height());
|
||||
|
||||
switch (m_format)
|
||||
{
|
||||
case CELL_CAMERA_RAW8: // The game seems to expect BGGR
|
||||
{
|
||||
// Let's use a very simple algorithm to convert the image to raw BGGR
|
||||
const auto convert_to_bggr = [&image_buffer, &image, width, height](u32 y_begin, u32 y_end)
|
||||
{
|
||||
u8* dst = &image_buffer.data[image_buffer.width * y_begin];
|
||||
|
||||
for (u32 y = y_begin; y < height && y < y_end; y++)
|
||||
{
|
||||
const u8* src = image.constScanLine(y);
|
||||
const bool is_top_pixel = (y % 2) == 0;
|
||||
|
||||
// Split loops (roughly twice the performance by removing one condition)
|
||||
if (is_top_pixel)
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++, src += 4)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = src[2]; // Blue
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = src[1]; // Green
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++, src += 4)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = src[1]; // Green
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = src[0]; // Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Use a multithreaded workload. The faster we get this done, the better.
|
||||
constexpr u32 thread_count = 4;
|
||||
const u32 lines_per_thread = std::ceil(image_buffer.height / static_cast<double>(thread_count));
|
||||
u32 y_begin = 0;
|
||||
u32 y_end = lines_per_thread;
|
||||
|
||||
QFutureSynchronizer<void> synchronizer;
|
||||
for (u32 i = 0; i < thread_count; i++)
|
||||
{
|
||||
synchronizer.addFuture(QtConcurrent::run(convert_to_bggr, y_begin, y_end));
|
||||
y_begin = y_end;
|
||||
y_end += lines_per_thread;
|
||||
}
|
||||
synchronizer.waitForFinished();
|
||||
break;
|
||||
}
|
||||
//case CELL_CAMERA_YUV422:
|
||||
case CELL_CAMERA_Y0_U_Y1_V:
|
||||
case CELL_CAMERA_V_Y1_U_Y0:
|
||||
{
|
||||
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
|
||||
const auto convert_to_yuv422 = [&image_buffer, &image, width, height, format = m_format](u32 y_begin, u32 y_end)
|
||||
{
|
||||
constexpr int yuv_bytes_per_pixel = 2;
|
||||
const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel;
|
||||
|
||||
const int y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3;
|
||||
const int u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2;
|
||||
const int y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1;
|
||||
const int v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0;
|
||||
|
||||
for (u32 y = y_begin; y < height && y < y_end; y++)
|
||||
{
|
||||
const u8* src = image.constScanLine(y);
|
||||
u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch];
|
||||
|
||||
for (u32 x = 0; x < width - 1; x += 2, src += 8)
|
||||
{
|
||||
const float r1 = src[0];
|
||||
const float g1 = src[1];
|
||||
const float b1 = src[2];
|
||||
const float r2 = src[4];
|
||||
const float g2 = src[5];
|
||||
const float b2 = src[6];
|
||||
|
||||
const int y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f;
|
||||
const int u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f;
|
||||
const int v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f;
|
||||
const int y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f;
|
||||
|
||||
const int yuv_index = x * yuv_bytes_per_pixel;
|
||||
yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0, 255));
|
||||
yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0, 255));
|
||||
yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0, 255));
|
||||
yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0, 255));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Use a multithreaded workload. The faster we get this done, the better.
|
||||
constexpr u32 thread_count = 4;
|
||||
const u32 lines_per_thread = std::ceil(image_buffer.height / static_cast<double>(thread_count));
|
||||
u32 y_begin = 0;
|
||||
u32 y_end = lines_per_thread;
|
||||
|
||||
QFutureSynchronizer<void> synchronizer;
|
||||
for (u32 i = 0; i < thread_count; i++)
|
||||
{
|
||||
synchronizer.addFuture(QtConcurrent::run(convert_to_yuv422, y_begin, y_end));
|
||||
y_begin = y_end;
|
||||
y_end += lines_per_thread;
|
||||
}
|
||||
synchronizer.waitForFinished();
|
||||
break;
|
||||
}
|
||||
case CELL_CAMERA_JPG:
|
||||
case CELL_CAMERA_RGBA:
|
||||
case CELL_CAMERA_RAW10:
|
||||
case CELL_CAMERA_YUV420:
|
||||
case CELL_CAMERA_FORMAT_UNKNOWN:
|
||||
default:
|
||||
std::memcpy(image_buffer.data.data(), image.constBits(), std::min<usz>(image_buffer.data.size(), image.height() * image.bytesPerLine()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
camera_video_sink::present(width, height, image.bytesPerLine(), 4, [&image](u32 y){ return image.constScanLine(y); });
|
||||
|
||||
// Unmap frame memory
|
||||
tmp.unmap();
|
||||
|
||||
camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d",
|
||||
m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize);
|
||||
|
||||
// Toggle write/read index
|
||||
std::lock_guard lock(m_mutex);
|
||||
image_buffer.frame_number = m_frame_number++;
|
||||
m_write_index = read_index();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void qt_camera_video_sink::set_format(s32 format, u32 bytesize)
|
||||
{
|
||||
camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize);
|
||||
|
||||
m_format = format;
|
||||
m_bytesize = bytesize;
|
||||
}
|
||||
|
||||
void qt_camera_video_sink::set_resolution(u32 width, u32 height)
|
||||
{
|
||||
camera_log.notice("Setting resolution: width=%d, height=%d", width, height);
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
void qt_camera_video_sink::set_mirrored(bool mirrored)
|
||||
{
|
||||
camera_log.notice("Setting mirrored: mirrored=%d", mirrored);
|
||||
|
||||
m_mirrored = mirrored;
|
||||
}
|
||||
|
||||
u64 qt_camera_video_sink::frame_number() const
|
||||
{
|
||||
return m_frame_number.load();
|
||||
}
|
||||
|
||||
void qt_camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
|
||||
{
|
||||
// Lock read buffer
|
||||
std::lock_guard lock(m_mutex);
|
||||
const image_buffer& image_buffer = m_image_buffer[read_index()];
|
||||
|
||||
width = image_buffer.width;
|
||||
height = image_buffer.height;
|
||||
frame_number = image_buffer.frame_number;
|
||||
|
||||
// Copy to out buffer
|
||||
if (buf && !image_buffer.data.empty())
|
||||
{
|
||||
bytes_read = std::min<u64>(image_buffer.data.size(), size);
|
||||
std::memcpy(buf, image_buffer.data.data(), bytes_read);
|
||||
|
||||
if (image_buffer.data.size() != size)
|
||||
{
|
||||
camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes_read = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 qt_camera_video_sink::read_index() const
|
||||
{
|
||||
// The read buffer index cannot be the same as the write index
|
||||
return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size());
|
||||
}
|
||||
|
|
|
@ -1,50 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/atomic.hpp"
|
||||
#include "util/types.hpp"
|
||||
#include "Input/camera_video_sink.h"
|
||||
|
||||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
#include <QImage>
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
|
||||
class qt_camera_video_sink final : public QVideoSink
|
||||
class qt_camera_video_sink final : public camera_video_sink, public QVideoSink
|
||||
{
|
||||
public:
|
||||
qt_camera_video_sink(bool front_facing, QObject *parent = nullptr);
|
||||
virtual ~qt_camera_video_sink();
|
||||
|
||||
bool present(const QVideoFrame& frame);
|
||||
|
||||
void set_format(s32 format, u32 bytesize);
|
||||
void set_resolution(u32 width, u32 height);
|
||||
void set_mirrored(bool mirrored);
|
||||
|
||||
u64 frame_number() const;
|
||||
|
||||
void get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read);
|
||||
|
||||
private:
|
||||
u32 read_index() const;
|
||||
|
||||
bool m_front_facing = false;
|
||||
bool m_mirrored = false; // Set by cellCamera
|
||||
s32 m_format = 2; // CELL_CAMERA_RAW8, set by cellCamera
|
||||
u32 m_bytesize = 0;
|
||||
u32 m_width = 640;
|
||||
u32 m_height = 480;
|
||||
|
||||
std::mutex m_mutex;
|
||||
atomic_t<u64> m_frame_number{0};
|
||||
u32 m_write_index{0};
|
||||
|
||||
struct image_buffer
|
||||
{
|
||||
u64 frame_number = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
std::array<image_buffer, 2> m_image_buffer;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue