This commit is contained in:
capitalistspz 2025-04-27 17:35:43 +01:00 committed by GitHub
commit fdc2d6d50a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 692 additions and 202 deletions

4
.gitmodules vendored
View file

@ -18,3 +18,7 @@
path = dependencies/imgui
url = https://github.com/ocornut/imgui
shallow = true
[submodule "dependencies/openpnp-capture"]
path = dependencies/openpnp-capture
url = https://github.com/capitalistspz/openpnp-capture
branch = dev

View file

@ -222,6 +222,9 @@ endif()
add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL)
add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL SYSTEM)
set_property(TARGET openpnp-capture PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
find_package(ZArchive)
if (NOT ZArchive_FOUND)
add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL)

1
dependencies/openpnp-capture vendored Submodule

@ -0,0 +1 @@
Subproject commit 3daf77d3d4013238af4c97f0e3ca46c6d3666d1b

View file

@ -46,6 +46,7 @@ add_subdirectory(Cemu)
add_subdirectory(config)
add_subdirectory(input)
add_subdirectory(audio)
add_subdirectory(camera)
add_subdirectory(util)
add_subdirectory(imgui)
add_subdirectory(resource)
@ -125,6 +126,7 @@ set_target_properties(CemuBin PROPERTIES
target_link_libraries(CemuBin PRIVATE
CemuAudio
CemuCafe
CemuCamera
CemuCommon
CemuComponents
CemuConfig

View file

@ -546,6 +546,7 @@ endif()
target_link_libraries(CemuCafe PRIVATE
CemuAsm
CemuAudio
CemuCamera
CemuCommon
CemuComponents
CemuConfig

View file

@ -1,257 +1,291 @@
#include "Common/precompiled.h"
#include "Cafe/OS/common/OSCommon.h"
#include "camera.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "util/helpers/helpers.h"
#include "util/helpers/ringbuffer.h"
#include "camera/CameraManager.h"
#include "Cafe/HW/Espresso/Const.h"
#include "Common/CafeString.h"
namespace camera
{
constexpr unsigned CAMERA_WIDTH = 640;
constexpr unsigned CAMERA_HEIGHT = 480;
enum CAMStatus : sint32
{
CAM_STATUS_SUCCESS = 0,
CAM_STATUS_INVALID_ARG = -1,
CAM_STATUS_INVALID_HANDLE = -2,
CAM_STATUS_SURFACE_QUEUE_FULL = -4,
CAM_STATUS_INSUFFICIENT_MEMORY = -5,
CAM_STATUS_NOT_READY = -6,
CAM_STATUS_UNINITIALIZED = -8,
CAM_STATUS_UVC_ERROR = -9,
CAM_STATUS_DECODER_INIT_INIT_FAILED = -10,
CAM_STATUS_DEVICE_IN_USE = -12,
CAM_STATUS_DECODER_SESSION_FAILED = -13,
CAM_STATUS_INVALID_PROPERTY = -14,
CAM_STATUS_SEGMENT_VIOLATION = -15
};
enum class CAMFps : uint32
{
_15 = 0,
_30 = 1
};
enum class CAMEventType : uint32
{
Decode = 0,
Detached = 1
};
enum class CAMForceDisplay
{
None = 0,
DRC = 1
};
enum class CAMImageType : uint32
{
Default = 0
};
struct CAMImageInfo
{
betype<CAMImageType> type;
uint32be height;
uint32be width;
};
static_assert(sizeof(CAMImageInfo) == 0x0C);
struct CAMInitInfo_t
{
/* +0x00 */ uint32be ukn00;
/* +0x04 */ uint32be width;
/* +0x08 */ uint32be height;
/* +0x0C */ uint32be workMemorySize;
/* +0x10 */ MEMPTR<void> workMemory;
/* +0x14 */ uint32be handlerFuncPtr;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be fps;
/* +0x20 */ uint32be ukn20;
CAMImageInfo imageInfo;
uint32be workMemorySize;
MEMPTR<void> workMemoryData;
MEMPTR<void> callback;
betype<CAMForceDisplay> forceDisplay;
betype<CAMFps> fps;
uint32be threadFlags;
uint8 unk[0x10];
};
static_assert(sizeof(CAMInitInfo_t) == 0x34);
struct CAMTargetSurface
struct CAMTargetSurface
{
/* +0x00 */ uint32be surfaceSize;
/* +0x04 */ MEMPTR<void> surfacePtr;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be ukn0C;
/* +0x10 */ uint32be ukn10;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be ukn1C;
sint32be size;
MEMPTR<uint8> data;
uint8 unused[0x18];
};
static_assert(sizeof(CAMTargetSurface) == 0x20);
struct CAMCallbackParam
struct CAMDecodeEventParam
{
// type 0 - frame decoded | field1 - imagePtr, field2 - imageSize, field3 - ukn (0)
// type 1 - ???
/* +0x0 */ uint32be type; // 0 -> Frame decoded
/* +0x4 */ uint32be field1;
/* +0x8 */ uint32be field2;
/* +0xC */ uint32be field3;
betype<CAMEventType> type;
MEMPTR<void> data;
uint32be channel;
uint32be errored;
};
static_assert(sizeof(CAMDecodeEventParam) == 0x10);
constexpr static int32_t CAM_HANDLE = 0;
#define CAM_ERROR_SUCCESS 0
#define CAM_ERROR_INVALID_HANDLE -8
std::vector<struct CameraInstance*> g_table_cameraHandles;
std::vector<struct CameraInstance*> g_activeCameraInstances;
std::recursive_mutex g_mutex_camera;
std::atomic_int g_cameraCounter{ 0 };
SysAllocator<coreinit::OSAlarm_t, 1> g_alarm_camera;
SysAllocator<CAMCallbackParam, 1> g_cameraHandlerParam;
CameraInstance* GetCameraInstanceByHandle(sint32 camHandle)
struct
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (camHandle <= 0)
return nullptr;
camHandle -= 1;
if (camHandle >= g_table_cameraHandles.size())
return nullptr;
return g_table_cameraHandles[camHandle];
std::recursive_mutex mutex{};
bool initialized = false;
bool shouldTriggerCallback = false;
std::atomic_bool isOpen = false;
std::atomic_bool isExiting = false;
bool isWorking = false;
unsigned fps = 30;
MEMPTR<void> eventCallback = nullptr;
RingBuffer<MEMPTR<uint8>, 20> inTargetBuffers{};
RingBuffer<MEMPTR<uint8>, 20> outTargetBuffers{};
} s_instance;
SysAllocator<CAMDecodeEventParam> s_cameraEventData;
SysAllocator<OSThread_t> s_cameraWorkerThread;
SysAllocator<uint8, 1024 * 64> s_cameraWorkerThreadStack;
SysAllocator<CafeString<22>> s_cameraWorkerThreadNameBuffer;
SysAllocator<coreinit::OSEvent> s_cameraOpenEvent;
void WorkerThread(PPCInterpreter_t*)
{
s_cameraEventData->type = CAMEventType::Decode;
s_cameraEventData->channel = 0;
s_cameraEventData->data = nullptr;
s_cameraEventData->errored = false;
PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR());
while (!s_instance.isExiting)
{
coreinit::OSWaitEvent(s_cameraOpenEvent);
while (true)
{
if (!s_instance.isOpen || s_instance.isExiting)
{
// Fill leftover buffers before stopping
if (!s_instance.inTargetBuffers.HasData())
break;
}
s_cameraEventData->type = CAMEventType::Decode;
s_cameraEventData->channel = 0;
const auto surfaceBuffer = s_instance.inTargetBuffers.Pop();
if (surfaceBuffer.IsNull())
{
s_cameraEventData->data = nullptr;
s_cameraEventData->errored = true;
}
else
{
CameraManager::FillNV12Buffer(surfaceBuffer.GetPtr());
s_cameraEventData->data = surfaceBuffer;
s_cameraEventData->errored = false;
}
PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR());
coreinit::OSSleepTicks(Espresso::TIMER_CLOCK / (s_instance.fps - 1));
}
}
coreinit::OSExitThread(0);
}
struct CameraInstance
{
CameraInstance(uint32 frameWidth, uint32 frameHeight, MPTR handlerFunc) : width(frameWidth), height(frameHeight), handlerFunc(handlerFunc) { AcquireHandle(); };
~CameraInstance() { if (isOpen) { CloseCam(); } ReleaseHandle(); };
sint32 handle{ 0 };
uint32 width;
uint32 height;
bool isOpen{false};
std::queue<CAMTargetSurface> queue_targetSurfaces;
MPTR handlerFunc;
bool OpenCam()
{
if (isOpen)
return false;
isOpen = true;
g_activeCameraInstances.push_back(this);
return true;
}
bool CloseCam()
{
if (!isOpen)
return false;
isOpen = false;
vectorRemoveByValue(g_activeCameraInstances, this);
return true;
}
void QueueTargetSurface(CAMTargetSurface* targetSurface)
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
cemu_assert_debug(queue_targetSurfaces.size() < 100); // check for sane queue length
queue_targetSurfaces.push(*targetSurface);
}
private:
void AcquireHandle()
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
{
if (g_table_cameraHandles[i] == nullptr)
{
g_table_cameraHandles[i] = this;
this->handle = i + 1;
return;
}
}
this->handle = (sint32)(g_table_cameraHandles.size() + 1);
g_table_cameraHandles.push_back(this);
}
void ReleaseHandle()
{
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
{
if (g_table_cameraHandles[i] == this)
{
g_table_cameraHandles[i] = nullptr;
return;
}
}
cemu_assert_debug(false);
}
};
sint32 CAMGetMemReq(void* ukn)
sint32 CAMGetMemReq(const CAMImageInfo* info)
{
if (!info)
return CAM_STATUS_INVALID_ARG;
return 1 * 1024; // always return 1KB
}
sint32 CAMCheckMemSegmentation(void* base, uint32 size)
CAMStatus CAMCheckMemSegmentation(void* base, uint32 size)
{
return CAM_ERROR_SUCCESS; // always return success
if (!base || size == 0)
return CAM_STATUS_INVALID_ARG;
return CAM_STATUS_SUCCESS;
}
void ppcCAMUpdate60(PPCInterpreter_t* hCPU)
sint32 CAMInit(uint32 cameraId, const CAMInitInfo_t* initInfo, betype<CAMStatus>* error)
{
// update all open camera instances
size_t numCamInstances = g_activeCameraInstances.size();
//for (auto& itr : g_activeCameraInstances)
for(size_t i=0; i<numCamInstances; i++)
*error = CAM_STATUS_SUCCESS;
std::scoped_lock lock(s_instance.mutex);
if (s_instance.initialized)
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (i >= g_activeCameraInstances.size())
break;
CameraInstance* camInstance = g_activeCameraInstances[i];
// todo - handle 30 / 60 FPS
if (camInstance->queue_targetSurfaces.empty())
continue;
auto& targetSurface = camInstance->queue_targetSurfaces.front();
g_cameraHandlerParam->type = 0;
g_cameraHandlerParam->field1 = targetSurface.surfacePtr.GetMPTR();
g_cameraHandlerParam->field2 = targetSurface.surfaceSize;
g_cameraHandlerParam->field3 = 0;
cemu_assert_debug(camInstance->handlerFunc != MPTR_NULL);
camInstance->queue_targetSurfaces.pop();
_lock.unlock();
PPCCoreCallback(camInstance->handlerFunc, g_cameraHandlerParam.GetPtr());
*error = CAM_STATUS_DEVICE_IN_USE;
return -1;
}
osLib_returnFromFunction(hCPU, 0);
}
sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error)
{
CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr);
*error = 0; // Hunter's Trophy 2 will fail to boot if we don't set this
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (g_cameraCounter == 0)
if (!initInfo || !initInfo->workMemoryData ||
!match_any_of(initInfo->forceDisplay, CAMForceDisplay::None, CAMForceDisplay::DRC) ||
!match_any_of(initInfo->fps, CAMFps::_15, CAMFps::_30) ||
initInfo->imageInfo.type != CAMImageType::Default)
{
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
*error = CAM_STATUS_INVALID_ARG;
return -1;
}
g_cameraCounter++;
CameraManager::Init();
return camInstance->handle;
cemu_assert_debug(initInfo->forceDisplay != CAMForceDisplay::DRC);
cemu_assert_debug(initInfo->workMemorySize != 0);
cemu_assert_debug(initInfo->imageInfo.type == CAMImageType::Default);
s_instance.isExiting = false;
s_instance.fps = initInfo->fps == CAMFps::_15 ? 15 : 30;
s_instance.initialized = true;
s_instance.eventCallback = initInfo->callback;
coreinit::OSInitEvent(s_cameraOpenEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
coreinit::__OSCreateThreadType(
s_cameraWorkerThread, RPLLoader_MakePPCCallable(WorkerThread), 0, nullptr,
s_cameraWorkerThreadStack.GetPtr() + s_cameraWorkerThreadStack.GetByteSize(), s_cameraWorkerThreadStack.GetByteSize(),
0x10, initInfo->threadFlags & 7, OSThread_t::THREAD_TYPE::TYPE_DRIVER);
s_cameraWorkerThreadNameBuffer->assign("CameraWorkerThread");
coreinit::OSSetThreadName(s_cameraWorkerThread.GetPtr(), s_cameraWorkerThreadNameBuffer->c_str());
coreinit::OSResumeThread(s_cameraWorkerThread.GetPtr());
return CAM_STATUS_SUCCESS;
}
sint32 CAMExit(sint32 camHandle)
CAMStatus CAMClose(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
CAMClose(camHandle);
delete camInstance;
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
g_cameraCounter--;
if (g_cameraCounter == 0)
coreinit::OSCancelAlarm(g_alarm_camera.GetPtr());
return CAM_ERROR_SUCCESS;
if (camHandle != CAM_HANDLE)
return CAM_STATUS_INVALID_HANDLE;
{
std::scoped_lock lock(s_instance.mutex);
if (!s_instance.initialized || !s_instance.isOpen)
return CAM_STATUS_UNINITIALIZED;
s_instance.isOpen = false;
}
CameraManager::Close();
return CAM_STATUS_SUCCESS;
}
sint32 CAMOpen(sint32 camHandle)
CAMStatus CAMOpen(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->OpenCam();
return CAM_ERROR_SUCCESS;
if (camHandle != CAM_HANDLE)
return CAM_STATUS_INVALID_HANDLE;
auto lock = std::scoped_lock(s_instance.mutex);
if (!s_instance.initialized)
return CAM_STATUS_UNINITIALIZED;
if (s_instance.isOpen)
return CAM_STATUS_DEVICE_IN_USE;
CameraManager::Open();
s_instance.isOpen = true;
coreinit::OSSignalEvent(s_cameraOpenEvent);
s_instance.inTargetBuffers.Clear();
s_instance.outTargetBuffers.Clear();
return CAM_STATUS_SUCCESS;
}
sint32 CAMClose(sint32 camHandle)
CAMStatus CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->CloseCam();
return CAM_ERROR_SUCCESS;
if (camHandle != CAM_HANDLE)
return CAM_STATUS_INVALID_HANDLE;
if (!targetSurface || targetSurface->data.IsNull() || targetSurface->size < 1)
return CAM_STATUS_INVALID_ARG;
auto lock = std::scoped_lock(s_instance.mutex);
if (!s_instance.initialized)
return CAM_STATUS_UNINITIALIZED;
if (!s_instance.inTargetBuffers.Push(targetSurface->data))
return CAM_STATUS_SURFACE_QUEUE_FULL;
return CAM_STATUS_SUCCESS;
}
sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface)
void CAMExit(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->QueueTargetSurface(targetSurface);
return CAM_ERROR_SUCCESS;
if (camHandle != CAM_HANDLE)
return;
std::scoped_lock lock(s_instance.mutex);
if (!s_instance.initialized)
return;
s_instance.isExiting = true;
if (s_instance.isOpen)
CAMClose(camHandle);
coreinit::OSSignalEvent(s_cameraOpenEvent.GetPtr());
coreinit::OSJoinThread(s_cameraWorkerThread, nullptr);
s_instance.initialized = false;
}
void reset()
{
g_cameraCounter = 0;
CAMExit(0);
}
void load()
{
reset();
cafeExportRegister("camera", CAMGetMemReq, LogType::Placeholder);
cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Placeholder);
cafeExportRegister("camera", CAMInit, LogType::Placeholder);
cafeExportRegister("camera", CAMExit, LogType::Placeholder);
cafeExportRegister("camera", CAMOpen, LogType::Placeholder);
cafeExportRegister("camera", CAMClose, LogType::Placeholder);
cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Placeholder);
cafeExportRegister("camera", CAMGetMemReq, LogType::Force);
cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Force);
cafeExportRegister("camera", CAMInit, LogType::Force);
cafeExportRegister("camera", CAMExit, LogType::Force);
cafeExportRegister("camera", CAMOpen, LogType::Force);
cafeExportRegister("camera", CAMClose, LogType::Force);
cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Force);
}
}
} // namespace camera

View file

@ -2,9 +2,5 @@
namespace camera
{
sint32 CAMOpen(sint32 camHandle);
sint32 CAMClose(sint32 camHandle);
void load();
};

16
src/camera/CMakeLists.txt Normal file
View file

@ -0,0 +1,16 @@
add_library(CemuCamera
CameraManager.cpp
CameraManager.h
Rgb2Nv12.cpp
Rgb2Nv12.h
)
set_property(TARGET CemuCamera PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
target_include_directories(CemuCamera PUBLIC "../")
target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil openpnp-capture)
if (ENABLE_WXWIDGETS)
target_link_libraries(CemuCamera PRIVATE wx::base)
endif()

View file

@ -0,0 +1,195 @@
#include "CameraManager.h"
#include "config/CemuConfig.h"
#include "util/helpers/helpers.h"
#include "Rgb2Nv12.h"
#include <algorithm>
#include <mutex>
#include <optional>
#include <thread>
#include <openpnp-capture.h>
constexpr unsigned CAMERA_WIDTH = 640;
constexpr unsigned CAMERA_HEIGHT = 480;
constexpr unsigned CAMERA_PITCH = 768;
namespace CameraManager
{
std::mutex s_mutex;
bool s_initialized = false;
CapContext s_ctx;
std::optional<CapDeviceID> s_device;
std::optional<CapStream> s_stream;
std::array<uint8, CAMERA_WIDTH * CAMERA_HEIGHT * 3> s_rgbBuffer;
std::array<uint8, CAMERA_PITCH * CAMERA_HEIGHT * 3 / 2> s_nv12Buffer;
int s_refCount = 0;
std::thread s_captureThread;
std::atomic_bool s_capturing = false;
std::atomic_bool s_running = false;
std::optional<CapFormatID> FindCorrectFormat()
{
const auto formatCount = Cap_getNumFormats(s_ctx, *s_device);
for (int32_t formatId = 0; formatId < formatCount; ++formatId)
{
CapFormatInfo formatInfo;
if (Cap_getFormatInfo(s_ctx, *s_device, formatId, &formatInfo) != CAPRESULT_OK)
continue;
if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT)
return formatId;
}
return std::nullopt;
}
void CaptureWorker()
{
SetThreadName("CameraManager");
while (s_running)
{
while (s_capturing)
{
s_mutex.lock();
if (s_stream && Cap_hasNewFrame(s_ctx, *s_stream) &&
Cap_captureFrame(s_ctx, *s_stream, s_rgbBuffer.data(), s_rgbBuffer.size()) == CAPRESULT_OK)
Rgb2Nv12(s_rgbBuffer.data(), CAMERA_WIDTH, CAMERA_HEIGHT, s_nv12Buffer.data(), CAMERA_PITCH);
s_mutex.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::this_thread::yield();
}
}
void OpenStream()
{
const auto formatId = FindCorrectFormat();
if (!formatId)
return;
const auto stream = Cap_openStream(s_ctx, *s_device, *formatId);
if (stream == -1)
return;
s_capturing = true;
s_stream = stream;
}
void CloseStream()
{
s_capturing = false;
if (s_stream)
{
Cap_closeStream(s_ctx, *s_stream);
s_stream = std::nullopt;
}
}
void ResetBuffers()
{
std::ranges::fill(s_rgbBuffer, 0);
std::ranges::fill_n(s_nv12Buffer.begin(), CAMERA_WIDTH * CAMERA_PITCH, 16);
std::ranges::fill_n(s_nv12Buffer.begin() + CAMERA_WIDTH * CAMERA_PITCH, (CAMERA_WIDTH / 2), 128);
}
void Init()
{
std::scoped_lock lock(s_mutex);
if (s_initialized)
return;
s_mutex.unlock();
s_running = true;
s_captureThread = std::thread(&CaptureWorker);
s_ctx = Cap_createContext();
const auto uniqueId = GetConfig().camera_id.GetValue();
if (!uniqueId.empty())
{
const auto devices = EnumerateDevices();
for (CapDeviceID deviceId = 0; deviceId < devices.size(); ++deviceId)
{
if (devices[deviceId].uniqueId == uniqueId)
{
s_device = deviceId;
return;
}
}
}
ResetBuffers();
}
void Deinit()
{
CloseStream();
Cap_releaseContext(s_ctx);
s_captureThread.join();
s_initialized = false;
}
void FillNV12Buffer(uint8* nv12Buffer)
{
std::scoped_lock lock(s_mutex);
std::ranges::copy(s_nv12Buffer, nv12Buffer);
}
void FillRGBBuffer(uint8* rgbBuffer)
{
std::scoped_lock lock(s_mutex);
std::ranges::copy(s_rgbBuffer, rgbBuffer);
}
void SetDevice(uint32 deviceNo)
{
std::scoped_lock lock(s_mutex);
CloseStream();
if (deviceNo == DEVICE_NONE)
{
s_device = std::nullopt;
ResetBuffers();
return;
}
s_device = deviceNo;
if (s_refCount != 0)
OpenStream();
}
void Open()
{
std::scoped_lock lock(s_mutex);
if (s_device && s_refCount == 0)
{
OpenStream();
}
s_refCount += 1;
}
void Close()
{
std::scoped_lock lock(s_mutex);
if (s_refCount == 0)
return;
s_refCount -= 1;
if (s_refCount != 0)
return;
CloseStream();
}
std::vector<DeviceInfo> EnumerateDevices()
{
std::scoped_lock lock(s_mutex);
std::vector<DeviceInfo> infos;
const auto deviceCount = Cap_getDeviceCount(s_ctx);
for (CapDeviceID deviceNo = 0; deviceNo < deviceCount; ++deviceNo)
{
const auto uniqueId = Cap_getDeviceUniqueID(s_ctx, deviceNo);
const auto name = Cap_getDeviceName(s_ctx, deviceNo);
DeviceInfo info;
info.uniqueId = uniqueId;
if (name)
info.name = fmt::format("{}: {}", deviceNo + 1, name);
else
info.name = fmt::format("{}: Unknown", deviceNo + 1);
infos.push_back(info);
}
return infos;
}
void SaveDevice()
{
std::scoped_lock lock(s_mutex);
if (s_device)
GetConfig().camera_id = Cap_getDeviceUniqueID(s_ctx, *s_device);
else
GetConfig().camera_id = "";
}
} // namespace CameraManager

View file

@ -0,0 +1,24 @@
#pragma once
#include <string>
namespace CameraManager
{
struct DeviceInfo
{
std::string uniqueId;
std::string name;
};
constexpr static uint32 DEVICE_NONE = std::numeric_limits<uint32>::max();
void Init();
void Deinit();
void Open();
void Close();
void FillNV12Buffer(uint8* nv12Buffer);
void FillRGBBuffer(uint8* rgbBuffer);
void SetDevice(uint32 deviceNo);
std::vector<DeviceInfo> EnumerateDevices();
void SaveDevice();
} // namespace CameraManager

65
src/camera/Rgb2Nv12.cpp Normal file
View file

@ -0,0 +1,65 @@
// Based on https://github.com/cohenrotem/Rgb2NV12
#include "Rgb2Nv12.h"
constexpr static glm::mat3x3 coefficientMatrix =
{
+0.257f, -0.148f, 0.439f,
+0.504f, -0.291f, -0.368f,
+0.098f, +0.439f, -0.071f};
constexpr static glm::mat4x3 offsetMatrix = {
16.0f + 0.5f, 128.0f + 2.0f, 128.0f + 2.0f,
16.0f + 0.5f, 128.0f + 2.0f, 128.0f + 2.0f,
16.0f + 0.5f, 128.0f + 2.0f, 128.0f + 2.0f,
16.0f + 0.5f, 128.0f + 2.0f, 128.0f + 2.0f};
static void Rgb2Nv12TwoRows(const uint8* topLine,
const uint8* bottomLine,
unsigned imageWidth,
uint8* topLineY,
uint8* bottomLineY,
uint8* uv)
{
auto* topIn = reinterpret_cast<const glm::u8vec3*>(topLine);
auto* botIn = reinterpret_cast<const glm::u8vec3*>(bottomLine);
for (auto x = 0u; x < imageWidth; x += 2)
{
const glm::mat4x3 rgbMatrix{
topIn[x],
topIn[x + 1],
botIn[x],
botIn[x + 1],
};
const auto result = coefficientMatrix * rgbMatrix + offsetMatrix;
topLineY[x + 0] = result[0].s;
topLineY[x + 1] = result[1].s;
bottomLineY[x + 0] = result[2].s;
bottomLineY[x + 1] = result[3].s;
uv[x + 0] = (result[0].t + result[1].t + result[2].t + result[3].t) * 0.25f;
uv[x + 1] = (result[0].p + result[1].p + result[2].p + result[3].p) * 0.25f;
}
}
void Rgb2Nv12(const uint8* rgbImage,
unsigned imageWidth,
unsigned imageHeight,
uint8* outNv12Image,
unsigned nv12Pitch)
{
cemu_assert_debug(!((imageWidth | imageHeight) & 1));
unsigned char* UV = outNv12Image + nv12Pitch * imageHeight;
for (auto row = 0u; row < imageHeight; row += 2)
{
Rgb2Nv12TwoRows(&rgbImage[row * imageWidth * 3],
&rgbImage[(row + 1) * imageWidth * 3],
imageWidth,
&outNv12Image[row * nv12Pitch],
&outNv12Image[(row + 1) * nv12Pitch],
&UV[(row / 2) * nv12Pitch]);
}
}

7
src/camera/Rgb2Nv12.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
void Rgb2Nv12(const uint8* rgbImage,
unsigned imageWidth,
unsigned imageHeight,
uint8* outNv12Image,
unsigned nv12Pitch);

View file

@ -343,6 +343,9 @@ void CemuConfig::Load(XMLConfigParser& parser)
dsu_client.host = dsuc.get_attribute("host", dsu_client.host);
dsu_client.port = dsuc.get_attribute("port", dsu_client.port);
auto camera = parser.get("Camera");
camera_id = camera.get_attribute("Id", "");
// emulatedusbdevices
auto usbdevices = parser.get("EmulatedUsbDevices");
emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal);
@ -544,6 +547,9 @@ void CemuConfig::Save(XMLConfigParser& parser)
dsuc.set_attribute("host", dsu_client.host);
dsuc.set_attribute("port", dsu_client.port);
auto camera = config.set("Camera");
camera.set("Id", camera_id);
// emulated usb devices
auto usbdevices = config.set("EmulatedUsbDevices");
usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue());

View file

@ -499,6 +499,9 @@ struct CemuConfig
ConfigValue<uint16> port{ 26760 };
}dsu_client{};
// camera
ConfigValue<std::string> camera_id;
// debug
ConfigValueBounds<CrashDump> crash_dump{ CrashDump::Disabled };
ConfigValue<uint16> gdb_port{ 1337 };

View file

@ -127,6 +127,8 @@ add_library(CemuGui
wxcomponents/unchecked.xpm
wxgui.h
wxHelper.h
CameraSettingsWindow.cpp
CameraSettingsWindow.h
)
set_property(TARGET CemuGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
@ -139,6 +141,7 @@ target_include_directories(CemuGui PUBLIC ${RAPIDJSON_INCLUDE_DIRS})
target_link_libraries(CemuGui PRIVATE
CemuAudio
CemuCafe
CemuCamera
CemuCommon
CemuComponents
CemuConfig

View file

@ -0,0 +1,98 @@
#include "CameraSettingsWindow.h"
#include "camera/CameraManager.h"
#include <wx/sizer.h>
#include <wx/dcclient.h>
#include <wx/dcbuffer.h>
#include <wx/rawbmp.h>
constexpr unsigned CAMERA_WIDTH = 640;
constexpr unsigned CAMERA_HEIGHT = 480;
CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Camera settings"), wxDefaultPosition),
m_imageBitmap(CAMERA_WIDTH, CAMERA_HEIGHT, 24), m_imageBuffer(CAMERA_WIDTH * CAMERA_HEIGHT * 3)
{
auto* rootSizer = new wxBoxSizer(wxVERTICAL);
{
auto* topSizer = new wxBoxSizer(wxHORIZONTAL);
{
m_cameraChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, {300, -1});
m_cameraChoice->Bind(wxEVT_CHOICE, &CameraSettingsWindow::OnSelectCameraChoice, this);
m_refreshButton = new wxButton(this, wxID_ANY, wxString::FromUTF8(""));
m_refreshButton->Fit();
m_refreshButton->Bind(wxEVT_BUTTON, &CameraSettingsWindow::OnRefreshPressed, this);
wxQueueEvent(m_refreshButton, new wxCommandEvent{wxEVT_BUTTON});
topSizer->Add(m_cameraChoice);
topSizer->Add(m_refreshButton);
}
m_imageWindow = new wxWindow(this, wxID_ANY, wxDefaultPosition, {CAMERA_WIDTH, CAMERA_HEIGHT});
rootSizer->Add(topSizer);
rootSizer->Add(m_imageWindow, wxEXPAND);
}
SetSizerAndFit(rootSizer);
CameraManager::Init();
CameraManager::Open();
m_imageUpdateTimer.Bind(wxEVT_TIMER, &CameraSettingsWindow::UpdateImage, this);
m_imageUpdateTimer.Start(33, wxTIMER_CONTINUOUS);
this->Bind(wxEVT_CLOSE_WINDOW, &CameraSettingsWindow::OnClose, this);
}
void CameraSettingsWindow::OnSelectCameraChoice(wxCommandEvent&)
{
const auto selection = m_cameraChoice->GetSelection();
if (selection < 0)
return;
if (selection == 0)
CameraManager::SetDevice(CameraManager::DEVICE_NONE);
else
CameraManager::SetDevice(selection - 1);
}
void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&)
{
wxArrayString choices = {_("None")};
for (const auto& entry : CameraManager::EnumerateDevices())
{
choices.push_back(entry.name);
}
m_cameraChoice->Set(choices);
wxArrayString str;
}
void CameraSettingsWindow::UpdateImage(const wxTimerEvent&)
{
CameraManager::FillRGBBuffer(m_imageBuffer.data());
wxNativePixelData data{m_imageBitmap};
if (!data)
return;
wxNativePixelData::Iterator p{data};
for (auto row = 0u; row < CAMERA_HEIGHT; ++row)
{
const auto* rowPtr = m_imageBuffer.data() + row * CAMERA_WIDTH * 3;
wxNativePixelData::Iterator rowStart = p;
for (auto col = 0u; col < CAMERA_WIDTH; ++col, ++p)
{
auto* colour = rowPtr + col * 3;
p.Red() = colour[0];
p.Green() = colour[1];
p.Blue() = colour[2];
}
p = rowStart;
p.OffsetY(data, 1);
}
wxClientDC dc{m_imageWindow};
dc.DrawBitmap(m_imageBitmap, 0, 0);
}
void CameraSettingsWindow::OnClose(wxCloseEvent& event)
{
CameraManager::Close();
CameraManager::SaveDevice();
event.Skip();
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <wx/dialog.h>
#include <wx/timer.h>
#include <wx/choice.h>
#include <wx/bmpbuttn.h>
class CameraSettingsWindow : public wxDialog
{
wxChoice* m_cameraChoice;
wxButton* m_refreshButton;
wxWindow* m_imageWindow;
wxBitmap m_imageBitmap;
wxTimer m_imageUpdateTimer;
std::vector<uint8> m_imageBuffer;
public:
explicit CameraSettingsWindow(wxWindow* parent);
void OnSelectCameraChoice(wxCommandEvent&);
void OnRefreshPressed(wxCommandEvent&);
void UpdateImage(const wxTimerEvent&);
void OnClose(wxCloseEvent& event);
};

View file

@ -61,6 +61,7 @@
#include "gamemode_client.h"
#endif
#include "CameraSettingsWindow.h"
#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/TitleList/TitleList.h"
#include "wxHelper.h"
@ -91,6 +92,7 @@ enum
MAINFRAME_MENU_ID_OPTIONS_GENERAL2,
MAINFRAME_MENU_ID_OPTIONS_AUDIO,
MAINFRAME_MENU_ID_OPTIONS_INPUT,
MAINFRAME_MENU_ID_OPTIONS_CAMERA,
// options -> account
MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350,
MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11,
@ -187,6 +189,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_CAMERA, MainWindow::OnOptionsInput)
// tools menu
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)
@ -923,6 +926,12 @@ void MainWindow::OnOptionsInput(wxCommandEvent& event)
frame->Destroy();
break;
}
case MAINFRAME_MENU_ID_OPTIONS_CAMERA:
{
auto* frame = new CameraSettingsWindow(this);
frame->ShowModal();
frame->Destroy();
}
}
}
@ -2159,6 +2168,7 @@ void MainWindow::RecreateMenu()
optionsMenu->AppendSeparator();
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings"));
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings"));
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_CAMERA, _("&Camera settings"));
optionsMenu->AppendSeparator();
optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account"));