Initial camera (Part 3 of 0.15.1 branch) (#4156)

* using new emulator_settings

* the default user is now just player one

* transfer install, addon dirs

* fix load custom config issue

* initial openal backend

* linux fix?

* camera module updated

---------

Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com>
This commit is contained in:
georgemoralis 2026-03-22 09:08:44 +02:00 committed by GitHub
parent 060703f627
commit 880445c2ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 350 additions and 8 deletions

View File

@ -3,17 +3,27 @@
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/camera/camera.h"
#include "core/libraries/camera/camera_error.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/process.h"
#include "core/libraries/libs.h"
#include <utility>
#include <thread>
#include "SDL3/SDL_camera.h"
namespace Libraries::Camera {
static bool g_library_opened = false;
static s32 g_firmware_version = 0;
static s32 g_handles = 0;
static constexpr s32 c_width = 1280, c_height = 800;
SDL_Camera* sdl_camera = nullptr;
OrbisCameraConfigExtention output_config0, output_config1;
s32 PS4_SYSV_ABI sceCameraAccGetData() {
LOG_ERROR(Lib_Camera, "(STUBBED) called");
@ -325,16 +335,126 @@ s32 PS4_SYSV_ABI sceCameraGetExposureGain(s32 handle, OrbisCameraChannel channel
return ORBIS_OK;
}
static std::vector<u16> raw16_buffer1, raw16_buffer2;
static std::vector<u8> raw8_buffer1, raw8_buffer2;
static void ConvertRGBA8888ToRAW16(const u8* src, u16* dst, int width, int height) {
for (int y = 0; y < height; ++y) {
const u8* row = src + y * width * 4;
u16* outRow = dst + y * width;
for (int x = 0; x < width; ++x) {
const u8* px = row + x * 4;
u16 b = u16(px[1]) << 4;
u16 g = u16(px[2]) << 4;
u16 r = u16(px[3]) << 4;
// BGGR Bayer layout
// B G
// G R
bool evenRow = (y & 1) == 0;
bool evenCol = (x & 1) == 0;
if (evenRow && evenCol) {
outRow[x] = b;
} else if (evenRow && !evenCol) {
outRow[x] = g;
} else if (!evenRow && evenCol) {
outRow[x] = g;
} else {
outRow[x] = r;
}
}
}
}
static void ConvertRGBA8888ToRAW8(const u8* src, u8* dst, int width, int height) {
for (int y = 0; y < height; ++y) {
const u8* row = src + y * width * 4;
u8* outRow = dst + y * width;
for (int x = 0; x < width; ++x) {
const u8* px = row + x * 4;
u8 b = px[1];
u8 g = px[2];
u8 r = px[3];
// BGGR Bayer layout
// B G
// G R
bool evenRow = (y & 1) == 0;
bool evenCol = (x & 1) == 0;
if (evenRow && evenCol) {
outRow[x] = b;
} else if (evenRow && !evenCol) {
outRow[x] = g;
} else if (!evenRow && evenCol) {
outRow[x] = g;
} else {
outRow[x] = r;
}
}
}
}
s32 PS4_SYSV_ABI sceCameraGetFrameData(s32 handle, OrbisCameraFrameData* frame_data) {
LOG_DEBUG(Lib_Camera, "called");
if (handle < 1 || frame_data == nullptr || frame_data->sizeThis > 584) {
return ORBIS_CAMERA_ERROR_PARAM;
}
if (!g_library_opened) {
if (!g_library_opened || !sdl_camera) {
return ORBIS_CAMERA_ERROR_NOT_OPEN;
}
if (EmulatorSettings.GetCameraId() == -1) {
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
}
Uint64 timestampNS = 0;
static SDL_Surface* frame = nullptr;
if (frame) { // release previous frame, if it exists
SDL_ReleaseCameraFrame(sdl_camera, frame);
}
frame = SDL_AcquireCameraFrame(sdl_camera, &timestampNS);
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
if (!frame) {
return ORBIS_CAMERA_ERROR_BUSY;
}
switch (output_config0.format.formatLevel0) {
case ORBIS_CAMERA_FORMAT_YUV422:
frame_data->pFramePointerList[0][0] = frame->pixels;
break;
case ORBIS_CAMERA_FORMAT_RAW16:
ConvertRGBA8888ToRAW16((u8*)frame->pixels, raw16_buffer1.data(), c_width, c_height);
frame_data->pFramePointerList[0][0] = raw16_buffer1.data();
break;
case ORBIS_CAMERA_FORMAT_RAW8:
ConvertRGBA8888ToRAW8((u8*)frame->pixels, raw8_buffer1.data(), c_width, c_height);
frame_data->pFramePointerList[0][0] = raw8_buffer1.data();
break;
default:
UNREACHABLE();
}
switch (output_config1.format.formatLevel0) {
case ORBIS_CAMERA_FORMAT_YUV422:
frame_data->pFramePointerList[1][0] = frame->pixels;
break;
case ORBIS_CAMERA_FORMAT_RAW16:
ConvertRGBA8888ToRAW16((u8*)frame->pixels, raw16_buffer2.data(), c_width, c_height);
frame_data->pFramePointerList[1][0] = raw16_buffer2.data();
break;
case ORBIS_CAMERA_FORMAT_RAW8:
ConvertRGBA8888ToRAW8((u8*)frame->pixels, raw8_buffer2.data(), c_width, c_height);
frame_data->pFramePointerList[1][0] = raw8_buffer2.data();
break;
default:
UNREACHABLE();
}
frame_data->meta.format[0][0] = output_config0.format.formatLevel0;
frame_data->meta.format[1][0] = output_config1.format.formatLevel0;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceCameraGetGamma(s32 handle, OrbisCameraChannel channel, OrbisCameraGamma* gamma,
@ -499,7 +619,7 @@ s32 PS4_SYSV_ABI sceCameraIsAttached(s32 index) {
return ORBIS_CAMERA_ERROR_PARAM;
}
// 0 = disconnected, 1 = connected
return 0;
return EmulatorSettings.GetCameraId() == -1 ? 0 : 1;
}
s32 PS4_SYSV_ABI sceCameraIsConfigChangeDone() {
@ -516,16 +636,16 @@ s32 PS4_SYSV_ABI sceCameraIsValidFrameData(s32 handle, OrbisCameraFrameData* fra
return ORBIS_CAMERA_ERROR_NOT_OPEN;
}
return ORBIS_OK;
return 1; // valid
}
s32 PS4_SYSV_ABI sceCameraOpen(Libraries::UserService::OrbisUserServiceUserId user_id, s32 type,
s32 index, OrbisCameraOpenParameter* param) {
LOG_INFO(Lib_Camera, "called");
if (user_id != Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || type != 0 ||
index != 0) {
return ORBIS_CAMERA_ERROR_PARAM;
}
LOG_WARNING(Lib_Camera, "Cameras are not supported yet");
g_library_opened = true;
return ++g_handles;
@ -609,15 +729,44 @@ s32 PS4_SYSV_ABI sceCameraSetCalibData() {
}
s32 PS4_SYSV_ABI sceCameraSetConfig(s32 handle, OrbisCameraConfig* config) {
LOG_DEBUG(Lib_Camera, "called");
LOG_INFO(Lib_Camera, "called");
if (handle < 1 || config == nullptr || config->sizeThis != sizeof(OrbisCameraConfig)) {
return ORBIS_CAMERA_ERROR_PARAM;
}
if (!g_library_opened) {
return ORBIS_CAMERA_ERROR_NOT_OPEN;
}
if (EmulatorSettings.GetCameraId() == -1) {
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
}
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
switch (config->configType) {
case ORBIS_CAMERA_CONFIG_TYPE1:
case ORBIS_CAMERA_CONFIG_TYPE2:
case ORBIS_CAMERA_CONFIG_TYPE3:
case ORBIS_CAMERA_CONFIG_TYPE4:
output_config0 = camera_config_types[config->configType - 1][0];
output_config1 = camera_config_types[config->configType - 1][1];
break;
case ORBIS_CAMERA_CONFIG_TYPE5:
int sdk_ver;
Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver);
if (sdk_ver < Common::ElfInfo::FW_45) {
return ORBIS_CAMERA_ERROR_UNKNOWN_CONFIG;
}
output_config0 = camera_config_types[config->configType - 1][0];
output_config1 = camera_config_types[config->configType - 1][1];
break;
case ORBIS_CAMERA_CONFIG_EXTENTION:
output_config0 = config->configExtention[0];
output_config1 = config->configExtention[1];
break;
default:
LOG_ERROR(Lib_Camera, "Invalid config type {}", std::to_underlying(config->configType));
return ORBIS_CAMERA_ERROR_PARAM;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceCameraSetConfigInternal(s32 handle, OrbisCameraConfig* config) {
@ -851,7 +1000,7 @@ s32 PS4_SYSV_ABI sceCameraSetWhiteBalance(s32 handle, OrbisCameraChannel channel
}
s32 PS4_SYSV_ABI sceCameraStart(s32 handle, OrbisCameraStartParameter* param) {
LOG_DEBUG(Lib_Camera, "called");
LOG_INFO(Lib_Camera, "called");
if (handle < 1 || param == nullptr || param->sizeThis != sizeof(OrbisCameraStartParameter)) {
return ORBIS_CAMERA_ERROR_PARAM;
}
@ -864,6 +1013,79 @@ s32 PS4_SYSV_ABI sceCameraStart(s32 handle, OrbisCameraStartParameter* param) {
return ORBIS_CAMERA_ERROR_FORMAT_UNKNOWN;
}
if (param->formatLevel[0] > 1 || param->formatLevel[1] > 1) {
LOG_ERROR(Lib_Camera, "Downscaled image retrieval isn't supported yet!");
}
SDL_CameraID* devices = NULL;
int devcount = 0;
devices = SDL_GetCameras(&devcount);
if (devices == NULL) {
LOG_ERROR(Lib_Camera, "Couldn't enumerate camera devices: {}", SDL_GetError());
return ORBIS_CAMERA_ERROR_FATAL;
} else if (devcount == 0) {
LOG_INFO(Lib_Camera, "No camera devices connected");
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
}
raw8_buffer1.resize(c_width * c_height);
raw16_buffer1.resize(c_width * c_height);
raw8_buffer2.resize(c_width * c_height);
raw16_buffer2.resize(c_width * c_height);
SDL_CameraSpec cam_spec{};
switch (output_config0.format.formatLevel0) {
case ORBIS_CAMERA_FORMAT_YUV422:
cam_spec.format = SDL_PIXELFORMAT_YUY2;
break;
case ORBIS_CAMERA_FORMAT_RAW8:
cam_spec.format = SDL_PIXELFORMAT_RGBA8888; // to be swizzled
break;
case ORBIS_CAMERA_FORMAT_RAW16:
cam_spec.format = SDL_PIXELFORMAT_RGBA8888; // to be swizzled
break;
default:
LOG_ERROR(Lib_Camera, "Invalid format {}",
std::to_underlying(output_config0.format.formatLevel0));
break;
}
cam_spec.height = c_height;
cam_spec.width = c_width;
cam_spec.framerate_numerator = 60;
cam_spec.framerate_denominator = 1;
sdl_camera = SDL_OpenCamera(devices[EmulatorSettings.GetCameraId()], &cam_spec);
LOG_INFO(Lib_Camera, "SDL backend in use: {}", SDL_GetCurrentCameraDriver());
char const* camera_name = SDL_GetCameraName(devices[EmulatorSettings.GetCameraId()]);
if (camera_name)
LOG_INFO(Lib_Camera, "SDL camera name: {}", camera_name);
SDL_CameraSpec spec;
SDL_GetCameraFormat(sdl_camera, &spec);
LOG_INFO(Lib_Camera, "SDL camera format: {:#x}", std::to_underlying(spec.format));
LOG_INFO(Lib_Camera, "SDL camera framerate: {}",
(float)spec.framerate_numerator / (float)spec.framerate_denominator);
LOG_INFO(Lib_Camera, "SDL camera dimensions: {}x{}", spec.width, spec.height);
SDL_free(devices);
// "warm up" the device, as recommended by SDL
u64 timestamp;
SDL_Surface* frame = nullptr;
frame = SDL_AcquireCameraFrame(sdl_camera, &timestamp);
if (!frame) {
for (int i = 0; i < 1000; i++) {
frame = SDL_AcquireCameraFrame(sdl_camera, &timestamp);
if (frame) {
SDL_ReleaseCameraFrame(sdl_camera, frame);
break;
}
std::this_thread::sleep_for(std::chrono::nanoseconds(10));
}
}
if (!sdl_camera) {
LOG_ERROR(Lib_Camera, "Failed to open camera: {}", SDL_GetError());
return ORBIS_CAMERA_ERROR_FATAL;
}
return ORBIS_OK;
}

View File

@ -102,6 +102,123 @@ struct OrbisCameraConfig {
OrbisCameraConfigExtention configExtention[ORBIS_CAMERA_MAX_DEVICE_NUM];
};
constexpr OrbisCameraConfigExtention camera_config_types[5][ORBIS_CAMERA_MAX_DEVICE_NUM]{
{
// type 1
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
},
{
// type 2
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
},
{
// type 3
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
},
{
// type 4
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
},
{
// type 5
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
{
.format =
{
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
},
.framerate = ORBIS_CAMERA_FRAMERATE_60,
},
}};
enum OrbisCameraAecAgcTarget {
ORBIS_CAMERA_ATTRIBUTE_AECAGC_TARGET_DEF = 0x00,
ORBIS_CAMERA_ATTRIBUTE_AECAGC_TARGET_2_0 = 0x20,

View File

@ -288,6 +288,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
if (!SDL_Init(SDL_INIT_VIDEO)) {
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
}
if (!SDL_Init(SDL_INIT_CAMERA)) {
UNREACHABLE_MSG("Failed to initialize SDL camera subsystem: {}", SDL_GetError());
}
SDL_InitSubSystem(SDL_INIT_AUDIO);
SDL_PropertiesID props = SDL_CreateProperties();