Skip to content

Commit

Permalink
Add SDL camera handler
Browse files Browse the repository at this point in the history
# Conflicts:
#	rpcs3/rpcs3.vcxproj.filters
  • Loading branch information
Megamouse committed Mar 10, 2025
1 parent db945f6 commit 54b2454
Show file tree
Hide file tree
Showing 30 changed files with 1,760 additions and 460 deletions.
4 changes: 4 additions & 0 deletions rpcs3/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion rpcs3/Emu/Cell/Modules/cellGem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1331,8 +1331,14 @@ 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;
default:
video_conversion_in_progress = false;
done();
continue;
Expand Down
24 changes: 16 additions & 8 deletions rpcs3/Emu/Io/camera_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
}
9 changes: 5 additions & 4 deletions rpcs3/Emu/Io/camera_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,16 @@ 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;
default:
break;
}

add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE);
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Emu/system_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
3 changes: 3 additions & 0 deletions rpcs3/Emu/system_config_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion rpcs3/Emu/system_config_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ enum class camera_handler
{
null,
fake,
qt
qt,
#ifdef HAVE_SDL3
sdl,
#endif
};

enum class camera_flip
Expand Down
230 changes: 230 additions & 0 deletions rpcs3/Input/camera_video_sink.cpp
Original file line number Diff line number Diff line change
@@ -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());
}

Loading

0 comments on commit 54b2454

Please sign in to comment.