Skip to content

Commit 7ea5fb0

Browse files
committed
Add SDL camera handler
1 parent 0724fa3 commit 7ea5fb0

30 files changed

+1637
-356
lines changed

rpcs3/Emu/Cell/Modules/cellGem.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -1331,8 +1331,15 @@ void gem_config_data::operator()()
13311331
vc = vc_attribute;
13321332
}
13331333

1334-
if (g_cfg.io.camera != camera_handler::qt)
1334+
switch (g_cfg.io.camera)
13351335
{
1336+
#ifdef HAVE_SDL3
1337+
case camera_handler::sdl:
1338+
#endif
1339+
case camera_handler::qt:
1340+
break;
1341+
case camera_handler::fake:
1342+
case camera_handler::null:
13361343
video_conversion_in_progress = false;
13371344
done();
13381345
continue;

rpcs3/Emu/Io/camera_config.cpp

+16-8
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,38 @@ void cfg_camera::save() const
3636
}
3737
}
3838

39-
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success)
39+
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& handler, const std::string& camera, bool& success)
4040
{
41-
camera_setting setting;
42-
const std::string value = cameras.get_value(camera);
41+
camera_setting setting {};
42+
const std::string value = cameras.get_value(handler + "-" + camera);
4343
success = !value.empty();
4444
if (success)
4545
{
46-
setting.from_string(cameras.get_value(camera));
46+
setting.from_string(value);
4747
}
4848
return setting;
4949
}
5050

51-
void cfg_camera::set_camera_setting(const std::string& camera, const camera_setting& setting)
51+
void cfg_camera::set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting)
5252
{
53+
if (handler.empty())
54+
{
55+
camera_log.error("String '%s' cannot be used as handler key.", handler);
56+
return;
57+
}
58+
5359
if (camera.empty())
5460
{
5561
camera_log.error("String '%s' cannot be used as camera key.", camera);
5662
return;
5763
}
5864

59-
cameras.set_value(camera, setting.to_string());
65+
cameras.set_value(handler + "-" + camera, setting.to_string());
6066
}
6167

6268
std::string cfg_camera::camera_setting::to_string() const
6369
{
64-
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
70+
return fmt::format("%d,%d,%f,%f,%d,%d", width, height, min_fps, max_fps, format, colorspace);
6571
}
6672

6773
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)
106112
!to_integer(::at32(list, 1), height) ||
107113
!to_double(::at32(list, 2), min_fps) ||
108114
!to_double(::at32(list, 3), max_fps) ||
109-
!to_integer(::at32(list, 4), format))
115+
!to_integer(::at32(list, 4), format) ||
116+
!to_integer(::at32(list, 4), colorspace))
110117
{
111118
width = 0;
112119
height = 0;
113120
min_fps = 0;
114121
max_fps = 0;
115122
format = 0;
123+
colorspace = 0;
116124
}
117125
}

rpcs3/Emu/Io/camera_config.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,19 @@ struct cfg_camera final : cfg::node
1515
double min_fps = 0;
1616
double max_fps = 0;
1717
int format = 0;
18+
int colorspace = 0;
1819

19-
static constexpr u32 member_count = 5;
20+
static constexpr u32 member_count = 6;
2021

2122
std::string to_string() const;
2223
void from_string(const std::string& text);
2324
};
24-
camera_setting get_camera_setting(const std::string& camera, bool& success);
25-
void set_camera_setting(const std::string& camera, const camera_setting& setting);
25+
camera_setting get_camera_setting(const std::string& handler, const std::string& camera, bool& success);
26+
void set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting);
2627

2728
const std::string path;
2829

29-
cfg::map_entry cameras{ this, "Cameras" }; // <camera>: <width>,<height>,<min_fps>,<max_fps>,<format>
30+
cfg::map_entry cameras{ this, "Cameras" }; // <handler-camera>: <width>,<height>,<min_fps>,<max_fps>,<format>,<colorspace>
3031
};
3132

3233
extern cfg_camera g_cfg_camera;

rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp

+9-1
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,17 @@ namespace rsx
7575
add_checkbox(&g_cfg.io.keep_pads_connected, localized_string_id::HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED);
7676
add_checkbox(&g_cfg.io.show_move_cursor, localized_string_id::HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR);
7777

78-
if (g_cfg.io.camera == camera_handler::qt)
78+
switch (g_cfg.io.camera)
7979
{
80+
#ifdef HAVE_SDL3
81+
case camera_handler::sdl:
82+
#endif
83+
case camera_handler::qt:
8084
add_dropdown(&g_cfg.io.camera_flip_option, localized_string_id::HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP);
85+
break;
86+
case camera_handler::fake:
87+
case camera_handler::null:
88+
break;
8189
}
8290

8391
add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE);

rpcs3/Emu/system_config.h

+1
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ struct cfg_root : cfg::node
270270
cfg::_enum<fake_camera_type> camera_type{ this, "Camera type", fake_camera_type::unknown };
271271
cfg::_enum<camera_flip> camera_flip_option{ this, "Camera flip", camera_flip::none, true };
272272
cfg::string camera_id{ this, "Camera ID", "Default", true };
273+
cfg::string sdl_camera_id{ this, "SDL Camera ID", "Default", true };
273274
cfg::_enum<move_handler> move{ this, "Move", move_handler::null, true };
274275
cfg::_enum<buzz_handler> buzz{ this, "Buzz emulated controller", buzz_handler::null };
275276
cfg::_enum<turntable_handler> turntable{this, "Turntable emulated controller", turntable_handler::null};

rpcs3/Emu/system_config_types.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@ void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
374374
case camera_handler::null: return "Null";
375375
case camera_handler::fake: return "Fake";
376376
case camera_handler::qt: return "Qt";
377+
#ifdef HAVE_SDL3
378+
case camera_handler::sdl: return "SDL";
379+
#endif
377380
}
378381

379382
return unknown;

rpcs3/Emu/system_config_types.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ enum class camera_handler
116116
{
117117
null,
118118
fake,
119-
qt
119+
qt,
120+
#ifdef HAVE_SDL3
121+
sdl,
122+
#endif
120123
};
121124

122125
enum class camera_flip

rpcs3/Input/camera_video_sink.cpp

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#include "stdafx.h"
2+
#include "camera_video_sink.h"
3+
4+
#include "Emu/Cell/Modules/cellCamera.h"
5+
#include "Emu/system_config.h"
6+
7+
LOG_CHANNEL(camera_log, "Camera");
8+
9+
camera_video_sink::camera_video_sink(bool front_facing)
10+
: m_front_facing(front_facing)
11+
{
12+
}
13+
14+
camera_video_sink::~camera_video_sink()
15+
{
16+
}
17+
18+
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)
19+
{
20+
ensure(!!src_line_ptr);
21+
22+
const u64 new_size = m_bytesize;
23+
image_buffer& image_buffer = m_image_buffer[m_write_index];
24+
25+
// Reset buffer if necessary
26+
if (image_buffer.data.size() != new_size)
27+
{
28+
image_buffer.data.clear();
29+
}
30+
31+
// Create buffer if necessary
32+
if (image_buffer.data.empty() && new_size > 0)
33+
{
34+
image_buffer.data.resize(new_size);
35+
image_buffer.width = m_width;
36+
image_buffer.height = m_height;
37+
}
38+
39+
if (!image_buffer.data.empty() && src_width && src_height)
40+
{
41+
// Convert image to proper layout
42+
// TODO: check if pixel format and bytes per pixel match and convert if necessary
43+
// TODO: implement or improve more conversions
44+
45+
const u32 width = std::min<u32>(image_buffer.width, src_width);
46+
const u32 height = std::min<u32>(image_buffer.height, src_height);
47+
48+
switch (m_format)
49+
{
50+
case CELL_CAMERA_RAW8: // The game seems to expect BGGR
51+
{
52+
// Let's use a very simple algorithm to convert the image to raw BGGR
53+
u8* dst = image_buffer.data.data();
54+
55+
for (u32 y = 0; y < height; y++)
56+
{
57+
const u8* src = src_line_ptr(y);
58+
const bool is_top_pixel = (y % 2) == 0;
59+
60+
// Split loops (roughly twice the performance by removing one condition)
61+
if (is_top_pixel)
62+
{
63+
for (u32 x = 0; x < width; x++, dst++, src += 4)
64+
{
65+
const bool is_left_pixel = (x % 2) == 0;
66+
67+
if (is_left_pixel)
68+
{
69+
*dst = src[2]; // Blue
70+
}
71+
else
72+
{
73+
*dst = src[1]; // Green
74+
}
75+
}
76+
}
77+
else
78+
{
79+
for (u32 x = 0; x < width; x++, dst++, src += 4)
80+
{
81+
const bool is_left_pixel = (x % 2) == 0;
82+
83+
if (is_left_pixel)
84+
{
85+
*dst = src[1]; // Green
86+
}
87+
else
88+
{
89+
*dst = src[0]; // Red
90+
}
91+
}
92+
}
93+
}
94+
break;
95+
}
96+
//case CELL_CAMERA_YUV422:
97+
case CELL_CAMERA_Y0_U_Y1_V:
98+
case CELL_CAMERA_V_Y1_U_Y0:
99+
{
100+
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
101+
constexpr int yuv_bytes_per_pixel = 2;
102+
const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel;
103+
104+
const int y0_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3;
105+
const int u_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2;
106+
const int y1_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1;
107+
const int v_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0;
108+
109+
for (u32 y = 0; y < height; y++)
110+
{
111+
const u8* src = src_line_ptr(y);
112+
u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch];
113+
114+
for (u32 x = 0; x < width - 1; x += 2, src += 8)
115+
{
116+
const f32 r1 = src[0];
117+
const f32 g1 = src[1];
118+
const f32 b1 = src[2];
119+
const f32 r2 = src[4];
120+
const f32 g2 = src[5];
121+
const f32 b2 = src[6];
122+
123+
const f32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f;
124+
const f32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f;
125+
const f32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f;
126+
const f32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f;
127+
128+
const int yuv_index = x * yuv_bytes_per_pixel;
129+
yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0.0f, 255.0f));
130+
yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0.0f, 255.0f));
131+
yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0.0f, 255.0f));
132+
yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0.0f, 255.0f));
133+
}
134+
}
135+
break;
136+
}
137+
case CELL_CAMERA_JPG:
138+
case CELL_CAMERA_RGBA:
139+
case CELL_CAMERA_RAW10:
140+
case CELL_CAMERA_YUV420:
141+
case CELL_CAMERA_FORMAT_UNKNOWN:
142+
default:
143+
const u32 bytes_per_line = src_bytes_per_pixel * src_width;
144+
if (src_pitch == bytes_per_line)
145+
{
146+
std::memcpy(image_buffer.data.data(), src_line_ptr(0), std::min<usz>(image_buffer.data.size(), src_height * bytes_per_line));
147+
}
148+
else
149+
{
150+
for (u32 y = 0, pos = 0; y < src_height && pos < image_buffer.data.size(); y++, pos += bytes_per_line)
151+
{
152+
std::memcpy(&image_buffer.data[pos], src_line_ptr(y), std::min<usz>(image_buffer.data.size() - pos, bytes_per_line));
153+
}
154+
}
155+
break;
156+
}
157+
}
158+
159+
camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d",
160+
m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize);
161+
162+
// Toggle write/read index
163+
std::lock_guard lock(m_mutex);
164+
image_buffer.frame_number = m_frame_number++;
165+
m_write_index = read_index();
166+
167+
return true;
168+
}
169+
170+
void camera_video_sink::set_format(s32 format, u32 bytesize)
171+
{
172+
camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize);
173+
174+
m_format = format;
175+
m_bytesize = bytesize;
176+
}
177+
178+
void camera_video_sink::set_resolution(u32 width, u32 height)
179+
{
180+
camera_log.notice("Setting resolution: width=%d, height=%d", width, height);
181+
182+
m_width = width;
183+
m_height = height;
184+
}
185+
186+
void camera_video_sink::set_mirrored(bool mirrored)
187+
{
188+
camera_log.notice("Setting mirrored: mirrored=%d", mirrored);
189+
190+
m_mirrored = mirrored;
191+
}
192+
193+
u64 camera_video_sink::frame_number() const
194+
{
195+
return m_frame_number.load();
196+
}
197+
198+
void camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
199+
{
200+
// Lock read buffer
201+
std::lock_guard lock(m_mutex);
202+
const image_buffer& image_buffer = m_image_buffer[read_index()];
203+
204+
width = image_buffer.width;
205+
height = image_buffer.height;
206+
frame_number = image_buffer.frame_number;
207+
208+
// Copy to out buffer
209+
if (buf && !image_buffer.data.empty())
210+
{
211+
bytes_read = std::min<u64>(image_buffer.data.size(), size);
212+
std::memcpy(buf, image_buffer.data.data(), bytes_read);
213+
214+
if (image_buffer.data.size() != size)
215+
{
216+
camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size());
217+
}
218+
}
219+
else
220+
{
221+
bytes_read = 0;
222+
}
223+
}
224+
225+
u32 camera_video_sink::read_index() const
226+
{
227+
// The read buffer index cannot be the same as the write index
228+
return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size());
229+
}
230+

0 commit comments

Comments
 (0)