From aea2b40702709de19ea0335e4ac231bd9788543a Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 2 Sep 2024 23:58:01 +0100 Subject: [PATCH 01/37] Use camera in camera.rpl --- .gitmodules | 3 + CMakeLists.txt | 3 + dependencies/openpnp-capture | 1 + src/Cafe/CMakeLists.txt | 3 + src/Cafe/OS/libs/camera/Rgb2Nv12.cpp | 65 ++++ src/Cafe/OS/libs/camera/Rgb2Nv12.h | 7 + src/Cafe/OS/libs/camera/camera.cpp | 453 ++++++++++++++++----------- src/Cafe/OS/libs/camera/camera.h | 4 - 8 files changed, 344 insertions(+), 195 deletions(-) create mode 160000 dependencies/openpnp-capture create mode 100644 src/Cafe/OS/libs/camera/Rgb2Nv12.cpp create mode 100644 src/Cafe/OS/libs/camera/Rgb2Nv12.h diff --git a/.gitmodules b/.gitmodules index dc69c441..109260dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,6 @@ path = dependencies/imgui url = https://github.com/ocornut/imgui shallow = true +[submodule "dependencies/openpnp-capture"] + path = dependencies/openpnp-capture + url = https://github.com/openpnp/openpnp-capture diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b5cff6c..f09ba085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,6 +231,9 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) +add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL) +set_target_properties(openpnp-capture PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "") + find_package(ZArchive) if (NOT ZArchive_FOUND) add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture new file mode 160000 index 00000000..8badbbae --- /dev/null +++ b/dependencies/openpnp-capture @@ -0,0 +1 @@ +Subproject commit 8badbbae826a578c2d66deecbffb6a2a079f1817 diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 91d257b2..018fc14c 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -257,6 +257,8 @@ add_library(CemuCafe OS/libs/avm/avm.h OS/libs/camera/camera.cpp OS/libs/camera/camera.h + OS/libs/camera/Rgb2Nv12.h + OS/libs/camera/Rgb2Nv12.cpp OS/libs/coreinit/coreinit_Alarm.cpp OS/libs/coreinit/coreinit_Alarm.h OS/libs/coreinit/coreinit_Atomic.cpp @@ -554,6 +556,7 @@ target_link_libraries(CemuCafe PRIVATE ZArchive::zarchive ZLIB::ZLIB zstd::zstd + openpnp-capture ) if (ENABLE_WAYLAND) diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp new file mode 100644 index 00000000..ab372f6f --- /dev/null +++ b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp @@ -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(topLine); + auto* botIn = reinterpret_cast(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 + imageWidth * 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]); + } +} diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.h b/src/Cafe/OS/libs/camera/Rgb2Nv12.h new file mode 100644 index 00000000..d0e01d01 --- /dev/null +++ b/src/Cafe/OS/libs/camera/Rgb2Nv12.h @@ -0,0 +1,7 @@ +#pragma once + +void Rgb2Nv12(const uint8* rgbImage, + unsigned imageWidth, + unsigned imageHeight, + uint8* outNv12Image, + unsigned nv12Pitch); \ No newline at end of file diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 4debb37f..cbc3b593 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -1,257 +1,328 @@ #include "Common/precompiled.h" #include "Cafe/OS/common/OSCommon.h" #include "camera.h" + +#include "Rgb2Nv12.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 +#include namespace camera { + constexpr static size_t g_width = 640; + constexpr static size_t g_height = 480; + constexpr static size_t g_pitch = 768; + + enum class CAMError : sint32 + { + Success = 0, + InvalidArg = -1, + InvalidHandle = -2, + SurfaceQueueFull = -4, + InsufficientMemory = -5, + NotReady = -6, + Uninitialized = -8, + DeviceInitFailed = -9, + DecoderInitFailed = -10, + DeviceInUse = -12, + DecoderSessionFailed = -13, + }; + + 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 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 workMemory; - - /* +0x14 */ uint32be handlerFuncPtr; - - /* +0x18 */ uint32be ukn18; - /* +0x1C */ uint32be fps; - - /* +0x20 */ uint32be ukn20; + CAMImageInfo imageInfo; + uint32be workMemorySize; + MEMPTR workMemory; + MEMPTR callback; + betype forceDisplay; + betype fps; + uint32be threadFlags; + uint8 unk[0x10]; }; + static_assert(sizeof(CAMInitInfo_t) == 0x34); - struct CAMTargetSurface + struct CAMTargetSurface { - /* +0x00 */ uint32be surfaceSize; + /* +0x00 */ sint32be surfaceSize; /* +0x04 */ MEMPTR surfacePtr; - /* +0x08 */ uint32be ukn08; - /* +0x0C */ uint32be ukn0C; + /* +0x08 */ uint32be height; + /* +0x0C */ uint32be width; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be ukn1C; }; - 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 type; + MEMPTR buffer; + uint32be channel; + uint32be errored; }; + static_assert(sizeof(CAMDecodeEventParam) == 0x10); + std::recursive_mutex g_cameraMutex; - #define CAM_ERROR_SUCCESS 0 - #define CAM_ERROR_INVALID_HANDLE -8 + SysAllocator g_cameraEventData; + SysAllocator g_cameraAlarm; - std::vector g_table_cameraHandles; - std::vector g_activeCameraInstances; - std::recursive_mutex g_mutex_camera; - std::atomic_int g_cameraCounter{ 0 }; - SysAllocator g_alarm_camera; - SysAllocator g_cameraHandlerParam; + void DecodeAlarmCallback(PPCInterpreter_t*); - CameraInstance* GetCameraInstanceByHandle(sint32 camHandle) + class CAMInstance { - std::unique_lock _lock(g_mutex_camera); - if (camHandle <= 0) - return nullptr; - camHandle -= 1; - if (camHandle >= g_table_cameraHandles.size()) - return nullptr; - return g_table_cameraHandles[camHandle]; + constexpr static auto surfaceBufferSize = g_pitch * g_height * 3 / 2; + + public: + CAMInstance(CAMFps frameRate, MEMPTR callbackPtr) + : m_capCtx(Cap_createContext()), + m_capNv12Buffer(surfaceBufferSize), + m_frameRate(30), + m_callbackPtr(callbackPtr), + m_alarm(OSAllocFromSystem(sizeof(coreinit::OSAlarm_t), 64)) + { + if (callbackPtr.IsNull() || frameRate != CAMFps::_15 && frameRate != CAMFps::_30) + throw CAMError::InvalidArg; + coreinit::OSCreateAlarm(g_cameraAlarm.GetPtr()); + coreinit::OSSetPeriodicAlarm(g_cameraAlarm.GetPtr(), + coreinit::OSGetTime(), + coreinit::EspressoTime::GetTimerClock() / m_frameRate, + RPLLoader_MakePPCCallable(DecodeAlarmCallback)); + } + ~CAMInstance() + { + m_capWorker.request_stop(); + m_capWorker.join(); + coreinit::OSCancelAlarm(m_alarm); + OSFreeToSystem(m_alarm.GetMPTR()); + Cap_releaseContext(m_capCtx); + } + + void OnAlarm() + { + std::scoped_lock captureLock(m_capMutex); + const auto surface = m_targetSurfaceQueue.Pop(); + if (surface.IsNull()) + return; + std::memcpy(surface->surfacePtr.GetPtr(), m_capNv12Buffer.data(), m_capNv12Buffer.size()); + g_cameraEventData->type = CAMEventType::Decode; + g_cameraEventData->buffer = surface->surfacePtr; + g_cameraEventData->channel = 0; + g_cameraEventData->errored = false; + PPCCoreCallback(m_callbackPtr, g_cameraEventData.GetPtr()); + } + + void Worker(std::stop_token stopToken, CapFormatID formatId) + { + SetThreadName("CAMWorker"); + auto stream = Cap_openStream(m_capCtx, m_capDeviceId, formatId); + std::vector m_capBuffer(g_width * g_height * 3); + while (!stopToken.stop_requested()) + { + if (!m_opened) + { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + std::this_thread::yield(); + continue; + } + std::scoped_lock lock(m_capMutex); + Cap_captureFrame(m_capCtx, stream, m_capBuffer.data(), m_capBuffer.size()); + Rgb2Nv12(m_capBuffer.data(), g_width, g_height, m_capNv12Buffer.data(), g_pitch); + } + Cap_closeStream(m_capCtx, stream); + } + + CAMError Open() + { + std::scoped_lock lock(m_capMutex); + if (m_opened) + return CAMError::DeviceInUse; + const auto formatCount = Cap_getNumFormats(m_capCtx, m_capDeviceId); + int formatId = -1; + for (auto i = 0; i < formatCount; ++i) + { + CapFormatInfo formatInfo; + Cap_getFormatInfo(m_capCtx, m_capDeviceId, i, &formatInfo); + if (formatInfo.width == g_width && formatInfo.height == g_height && formatInfo.fps == m_frameRate) + { + formatId = i; + break; + } + } + if (formatId == -1) + { + cemuLog_log(LogType::Force, "camera: Open failed, {}x{} @ {} fps not supported by host camera", g_width, g_height, m_frameRate); + return CAMError::DeviceInitFailed; + } + m_targetSurfaceQueue.Clear(); + m_opened = true; + m_capWorker = std::jthread(&CAMInstance::Worker, this, formatId); + return CAMError::Success; + } + + CAMError Close() + { + std::scoped_lock lock(m_capMutex); + m_opened = false; + return CAMError::Success; + } + + CAMError SubmitTargetSurface(MEMPTR surface) + { + cemu_assert(surface->surfaceSize >= surfaceBufferSize); + if (!m_opened) + return CAMError::NotReady; + if (!m_targetSurfaceQueue.Push(surface)) + return CAMError::SurfaceQueueFull; + std::cout << "Surface submitted" << std::endl; + return CAMError::Success; + } + + private: + CapContext m_capCtx; + CapDeviceID m_capDeviceId = 0; + std::vector m_capNv12Buffer; + std::mutex m_capMutex{}; + std::jthread m_capWorker; + + uint32 m_frameRate; + MEMPTR m_callbackPtr; + RingBuffer, 20> m_targetSurfaceQueue{}; + MEMPTR m_alarm; + bool m_opened = false; + }; + std::optional g_camInstance; + + void DecodeAlarmCallback(PPCInterpreter_t*) + { + std::scoped_lock camLock(g_cameraMutex); + if (!g_camInstance) + return; + g_camInstance->OnAlarm(); } - 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 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 _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 _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(CAMImageInfo*) { return 1 * 1024; // always return 1KB } - sint32 CAMCheckMemSegmentation(void* base, uint32 size) + CAMError CAMCheckMemSegmentation(void* base, uint32 size) { - return CAM_ERROR_SUCCESS; // always return success + return CAMError::Success; // always return success } - void ppcCAMUpdate60(PPCInterpreter_t* hCPU) + sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, betype* error) { - // update all open camera instances - size_t numCamInstances = g_activeCameraInstances.size(); - //for (auto& itr : g_activeCameraInstances) - for(size_t i=0; i _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 = CAMError::DeviceInUse; + 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); - - std::unique_lock _lock(g_mutex_camera); - if (g_cameraCounter == 0) + std::unique_lock _lock(g_cameraMutex); + const auto& [t, height, width] = camInitInfo->imageInfo; + cemu_assert(width == g_width && height == g_height); + try { - coreinit::OSCreateAlarm(g_alarm_camera.GetPtr()); - coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60)); + g_camInstance.emplace(camInitInfo->fps, camInitInfo->callback); } - g_cameraCounter++; - - return camInstance->handle; + catch (CAMError e) + { + *error = e; + return -1; + } + *error = CAMError::Success; + return 0; } - sint32 CAMExit(sint32 camHandle) + void CAMExit(sint32 camHandle) { - CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); - if (!camInstance) - return CAM_ERROR_INVALID_HANDLE; - CAMClose(camHandle); - delete camInstance; - - std::unique_lock _lock(g_mutex_camera); - g_cameraCounter--; - if (g_cameraCounter == 0) - coreinit::OSCancelAlarm(g_alarm_camera.GetPtr()); - return CAM_ERROR_SUCCESS; + if (camHandle != 0 || !g_camInstance) + return; + g_camInstance.reset(); } - sint32 CAMOpen(sint32 camHandle) + CAMError CAMClose(sint32 camHandle) { - CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); - if (!camInstance) - return CAM_ERROR_INVALID_HANDLE; - camInstance->OpenCam(); - return CAM_ERROR_SUCCESS; + if (camHandle != 0) + return CAMError::InvalidHandle; + std::scoped_lock lock(g_cameraMutex); + if (!g_camInstance) + return CAMError::Uninitialized; + return g_camInstance->Close(); } - sint32 CAMClose(sint32 camHandle) + CAMError CAMOpen(sint32 camHandle) { - CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); - if (!camInstance) - return CAM_ERROR_INVALID_HANDLE; - camInstance->CloseCam(); - return CAM_ERROR_SUCCESS; + if (camHandle != 0) + return CAMError::InvalidHandle; + auto lock = std::scoped_lock(g_cameraMutex); + if (!g_camInstance) + return CAMError::Uninitialized; + return g_camInstance->Open(); } - sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) + CAMError CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) { - CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); - if (!camInstance) - return CAM_ERROR_INVALID_HANDLE; - - camInstance->QueueTargetSurface(targetSurface); - - return CAM_ERROR_SUCCESS; + if (camHandle != 0) + return CAMError::InvalidHandle; + if (!targetSurface || targetSurface->surfacePtr.IsNull() || targetSurface->surfaceSize < 1) + return CAMError::InvalidArg; + auto lock = std::scoped_lock(g_cameraMutex); + if (!g_camInstance) + return CAMError::Uninitialized; + return g_camInstance->SubmitTargetSurface(targetSurface); } void reset() { - g_cameraCounter = 0; + g_camInstance.reset(); } 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 diff --git a/src/Cafe/OS/libs/camera/camera.h b/src/Cafe/OS/libs/camera/camera.h index 04248bbc..175edb62 100644 --- a/src/Cafe/OS/libs/camera/camera.h +++ b/src/Cafe/OS/libs/camera/camera.h @@ -2,9 +2,5 @@ namespace camera { - - sint32 CAMOpen(sint32 camHandle); - sint32 CAMClose(sint32 camHandle); - void load(); }; \ No newline at end of file From 36ae1571c78fc6b75fe42a73deb06560af06432b Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 3 Sep 2024 00:02:29 +0100 Subject: [PATCH 02/37] Remove unused alarm var --- src/Cafe/OS/libs/camera/camera.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index cbc3b593..08b8f2f7 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -114,8 +114,7 @@ namespace camera : m_capCtx(Cap_createContext()), m_capNv12Buffer(surfaceBufferSize), m_frameRate(30), - m_callbackPtr(callbackPtr), - m_alarm(OSAllocFromSystem(sizeof(coreinit::OSAlarm_t), 64)) + m_callbackPtr(callbackPtr) { if (callbackPtr.IsNull() || frameRate != CAMFps::_15 && frameRate != CAMFps::_30) throw CAMError::InvalidArg; @@ -129,8 +128,7 @@ namespace camera { m_capWorker.request_stop(); m_capWorker.join(); - coreinit::OSCancelAlarm(m_alarm); - OSFreeToSystem(m_alarm.GetMPTR()); + coreinit::OSCancelAlarm(g_cameraAlarm.GetPtr()); Cap_releaseContext(m_capCtx); } @@ -224,7 +222,6 @@ namespace camera uint32 m_frameRate; MEMPTR m_callbackPtr; RingBuffer, 20> m_targetSurfaceQueue{}; - MEMPTR m_alarm; bool m_opened = false; }; std::optional g_camInstance; From 277c3efacd3ab6648825035497ec2ed266d366e9 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 3 Sep 2024 16:30:24 +0100 Subject: [PATCH 03/37] log with `cemuLog`, use unique_ptr for capture buffer, reduce capture mutex lock time --- src/Cafe/OS/libs/camera/camera.cpp | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 08b8f2f7..f661cb16 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -107,12 +107,12 @@ namespace camera class CAMInstance { - constexpr static auto surfaceBufferSize = g_pitch * g_height * 3 / 2; + constexpr static auto nv12BufferSize = g_pitch * g_height * 3 / 2; public: CAMInstance(CAMFps frameRate, MEMPTR callbackPtr) : m_capCtx(Cap_createContext()), - m_capNv12Buffer(surfaceBufferSize), + m_capNv12Buffer(new uint8_t[nv12BufferSize]), m_frameRate(30), m_callbackPtr(callbackPtr) { @@ -134,11 +134,13 @@ namespace camera void OnAlarm() { - std::scoped_lock captureLock(m_capMutex); const auto surface = m_targetSurfaceQueue.Pop(); if (surface.IsNull()) return; - std::memcpy(surface->surfacePtr.GetPtr(), m_capNv12Buffer.data(), m_capNv12Buffer.size()); + { + std::scoped_lock lock(m_callbackMutex); + std::memcpy(surface->surfacePtr.GetPtr(), m_capNv12Buffer.get(), nv12BufferSize); + } g_cameraEventData->type = CAMEventType::Decode; g_cameraEventData->buffer = surface->surfacePtr; g_cameraEventData->channel = 0; @@ -149,8 +151,9 @@ namespace camera void Worker(std::stop_token stopToken, CapFormatID formatId) { SetThreadName("CAMWorker"); - auto stream = Cap_openStream(m_capCtx, m_capDeviceId, formatId); - std::vector m_capBuffer(g_width * g_height * 3); + const auto stream = Cap_openStream(m_capCtx, m_capDeviceId, formatId); + constexpr static auto rgbBufferSize = g_width * g_height * 3; + auto rgbBuffer = std::make_unique(rgbBufferSize); while (!stopToken.stop_requested()) { if (!m_opened) @@ -159,16 +162,15 @@ namespace camera std::this_thread::yield(); continue; } - std::scoped_lock lock(m_capMutex); - Cap_captureFrame(m_capCtx, stream, m_capBuffer.data(), m_capBuffer.size()); - Rgb2Nv12(m_capBuffer.data(), g_width, g_height, m_capNv12Buffer.data(), g_pitch); + Cap_captureFrame(m_capCtx, stream, rgbBuffer.get(), rgbBufferSize); + std::scoped_lock lock(m_callbackMutex); + Rgb2Nv12(rgbBuffer.get(), g_width, g_height, m_capNv12Buffer.get(), g_pitch); } Cap_closeStream(m_capCtx, stream); } CAMError Open() { - std::scoped_lock lock(m_capMutex); if (m_opened) return CAMError::DeviceInUse; const auto formatCount = Cap_getNumFormats(m_capCtx, m_capDeviceId); @@ -196,33 +198,31 @@ namespace camera CAMError Close() { - std::scoped_lock lock(m_capMutex); m_opened = false; return CAMError::Success; } CAMError SubmitTargetSurface(MEMPTR surface) { - cemu_assert(surface->surfaceSize >= surfaceBufferSize); + cemu_assert(surface->surfaceSize >= nv12BufferSize); if (!m_opened) return CAMError::NotReady; if (!m_targetSurfaceQueue.Push(surface)) return CAMError::SurfaceQueueFull; - std::cout << "Surface submitted" << std::endl; + cemuLog_logDebug(LogType::Force, "camera: surface submitted"); return CAMError::Success; } private: CapContext m_capCtx; CapDeviceID m_capDeviceId = 0; - std::vector m_capNv12Buffer; - std::mutex m_capMutex{}; + std::unique_ptr m_capNv12Buffer; std::jthread m_capWorker; - + std::mutex m_callbackMutex{}; uint32 m_frameRate; MEMPTR m_callbackPtr; RingBuffer, 20> m_targetSurfaceQueue{}; - bool m_opened = false; + std::atomic_bool m_opened = false; }; std::optional g_camInstance; From 6093ee03a5dbd0d8b6ed7894f096024b1bcf6376 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 5 Sep 2024 16:44:27 +0100 Subject: [PATCH 04/37] Only convert upon successful capture --- src/Cafe/OS/libs/camera/camera.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index f661cb16..be01ed7b 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -158,13 +158,16 @@ namespace camera { if (!m_opened) { - std::this_thread::sleep_for(std::chrono::milliseconds(2)); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); std::this_thread::yield(); continue; } - Cap_captureFrame(m_capCtx, stream, rgbBuffer.get(), rgbBufferSize); - std::scoped_lock lock(m_callbackMutex); - Rgb2Nv12(rgbBuffer.get(), g_width, g_height, m_capNv12Buffer.get(), g_pitch); + auto res = Cap_captureFrame(m_capCtx, stream, rgbBuffer.get(), rgbBufferSize); + if (res == CAPRESULT_OK) + { + std::scoped_lock lock(m_callbackMutex); + Rgb2Nv12(rgbBuffer.get(), g_width, g_height, m_capNv12Buffer.get(), g_pitch); + } } Cap_closeStream(m_capCtx, stream); } @@ -209,7 +212,7 @@ namespace camera return CAMError::NotReady; if (!m_targetSurfaceQueue.Push(surface)) return CAMError::SurfaceQueueFull; - cemuLog_logDebug(LogType::Force, "camera: surface submitted"); + cemuLog_logDebug(LogType::Force, "camera: surface {} submitted", surface->surfacePtr); return CAMError::Success; } From 457672accff0385b3c914f38d0b3686e5310058a Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Wed, 25 Sep 2024 23:39:20 +0100 Subject: [PATCH 05/37] Rgb2Nv12 coefficient correction --- src/Cafe/OS/libs/camera/Rgb2Nv12.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp index ab372f6f..935d627d 100644 --- a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp +++ b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp @@ -3,8 +3,8 @@ constexpr static glm::mat3x3 coefficientMatrix = { - +0.257f, -0.148f, -0.439f, - -0.504f, -0.291f, -0.368f, + +0.257f, -0.148f, 0.439f, + +0.504f, -0.291f, -0.368f, +0.098f, +0.439f, -0.071f}; constexpr static glm::mat4x3 offsetMatrix = { From 4d59741b2531e499676c8f39ab4f5184e8419212 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 17 Mar 2025 16:59:45 +0000 Subject: [PATCH 06/37] Correct UV pointer offset --- src/Cafe/OS/libs/camera/Rgb2Nv12.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp index 935d627d..2390be0a 100644 --- a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp +++ b/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp @@ -51,7 +51,7 @@ void Rgb2Nv12(const uint8* rgbImage, unsigned nv12Pitch) { cemu_assert_debug(!((imageWidth | imageHeight) & 1)); - unsigned char* UV = outNv12Image + imageWidth * imageHeight; + unsigned char* UV = outNv12Image + nv12Pitch * imageHeight; for (auto row = 0u; row < imageHeight; row += 2) { From cdd178d68d09b7a1416303b12f4d5e4b8502a2cc Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 23 Mar 2025 22:24:28 +0000 Subject: [PATCH 07/37] Use alternate repo for openpnp-capture, simplify CMakeLists.txt --- .gitmodules | 3 ++- CMakeLists.txt | 3 +-- dependencies/openpnp-capture | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 109260dd..9ff86b40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,4 +20,5 @@ shallow = true [submodule "dependencies/openpnp-capture"] path = dependencies/openpnp-capture - url = https://github.com/openpnp/openpnp-capture + url = https://github.com/capitalistspz/openpnp-capture + branch = dev diff --git a/CMakeLists.txt b/CMakeLists.txt index f09ba085..0d1e42db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,8 +231,7 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) -add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL) -set_target_properties(openpnp-capture PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "") +add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL SYSTEM) find_package(ZArchive) if (NOT ZArchive_FOUND) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index 8badbbae..ba456ac7 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit 8badbbae826a578c2d66deecbffb6a2a079f1817 +Subproject commit ba456ac7572c6e6db49da53d2f4985d65309e582 From 95238d60b7a6bb11256913f36e566722510066ad Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 05:44:24 +0000 Subject: [PATCH 08/37] Separate camera code --- src/CMakeLists.txt | 2 + src/Cafe/CMakeLists.txt | 4 +- src/Cafe/OS/libs/camera/camera.cpp | 328 +++++++++------------ src/camera/CMakeLists.txt | 8 + src/camera/CameraManager.cpp | 99 +++++++ src/camera/CameraManager.h | 29 ++ src/{Cafe/OS/libs => }/camera/Rgb2Nv12.cpp | 0 src/{Cafe/OS/libs => }/camera/Rgb2Nv12.h | 0 8 files changed, 281 insertions(+), 189 deletions(-) create mode 100644 src/camera/CMakeLists.txt create mode 100644 src/camera/CameraManager.cpp create mode 100644 src/camera/CameraManager.h rename src/{Cafe/OS/libs => }/camera/Rgb2Nv12.cpp (100%) rename src/{Cafe/OS/libs => }/camera/Rgb2Nv12.h (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d64d91b..c3271790 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) @@ -119,6 +120,7 @@ set_target_properties(CemuBin PROPERTIES target_link_libraries(CemuBin PRIVATE CemuAudio CemuCafe + CemuCamera CemuCommon CemuComponents CemuConfig diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 018fc14c..5f597731 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -257,8 +257,6 @@ add_library(CemuCafe OS/libs/avm/avm.h OS/libs/camera/camera.cpp OS/libs/camera/camera.h - OS/libs/camera/Rgb2Nv12.h - OS/libs/camera/Rgb2Nv12.cpp OS/libs/coreinit/coreinit_Alarm.cpp OS/libs/coreinit/coreinit_Alarm.h OS/libs/coreinit/coreinit_Atomic.cpp @@ -535,6 +533,7 @@ target_include_directories(CemuCafe PUBLIC "../") target_link_libraries(CemuCafe PRIVATE CemuAsm CemuAudio + CemuCamera CemuCommon CemuComponents CemuConfig @@ -556,7 +555,6 @@ target_link_libraries(CemuCafe PRIVATE ZArchive::zarchive ZLIB::ZLIB zstd::zstd - openpnp-capture ) if (ENABLE_WAYLAND) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index be01ed7b..cce5d7bd 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -2,23 +2,20 @@ #include "Cafe/OS/common/OSCommon.h" #include "camera.h" -#include "Rgb2Nv12.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 -#include - +#include "util/helpers/ringbuffer.h" +#include "camera/CameraManager.h" namespace camera { - constexpr static size_t g_width = 640; - constexpr static size_t g_height = 480; - constexpr static size_t g_pitch = 768; + constexpr unsigned CAMERA_WIDTH = 640; + constexpr unsigned CAMERA_HEIGHT = 480; - enum class CAMError : sint32 + enum class CAMStatus : sint32 { Success = 0, InvalidArg = -1, @@ -27,7 +24,7 @@ namespace camera InsufficientMemory = -5, NotReady = -6, Uninitialized = -8, - DeviceInitFailed = -9, + UVCError = -9, DecoderInitFailed = -10, DeviceInUse = -12, DecoderSessionFailed = -13, @@ -68,7 +65,7 @@ namespace camera { CAMImageInfo imageInfo; uint32be workMemorySize; - MEMPTR workMemory; + MEMPTR workMemoryData; MEMPTR callback; betype forceDisplay; betype fps; @@ -79,8 +76,8 @@ namespace camera struct CAMTargetSurface { - /* +0x00 */ sint32be surfaceSize; - /* +0x04 */ MEMPTR surfacePtr; + /* +0x00 */ sint32be size; + /* +0x04 */ MEMPTR data; /* +0x08 */ uint32be height; /* +0x0C */ uint32be width; /* +0x10 */ uint32be ukn10; @@ -92,149 +89,74 @@ namespace camera struct CAMDecodeEventParam { betype type; - MEMPTR buffer; + MEMPTR data; uint32be channel; uint32be errored; }; static_assert(sizeof(CAMDecodeEventParam) == 0x10); - std::recursive_mutex g_cameraMutex; + constexpr static int32_t CAM_HANDLE = 0; - SysAllocator g_cameraEventData; - SysAllocator g_cameraAlarm; - - void DecodeAlarmCallback(PPCInterpreter_t*); - - class CAMInstance + struct { - constexpr static auto nv12BufferSize = g_pitch * g_height * 3 / 2; + std::recursive_mutex mutex{}; + bool initialized = false; + bool shouldTriggerCallback = false; + std::atomic_bool isOpen = false; + MEMPTR eventCallback = nullptr; + RingBuffer, 20> inTargetBuffers{}; + RingBuffer, 20> outTargetBuffers{}; + std::thread updateThread; + } s_instance; - public: - CAMInstance(CAMFps frameRate, MEMPTR callbackPtr) - : m_capCtx(Cap_createContext()), - m_capNv12Buffer(new uint8_t[nv12BufferSize]), - m_frameRate(30), - m_callbackPtr(callbackPtr) - { - if (callbackPtr.IsNull() || frameRate != CAMFps::_15 && frameRate != CAMFps::_30) - throw CAMError::InvalidArg; - coreinit::OSCreateAlarm(g_cameraAlarm.GetPtr()); - coreinit::OSSetPeriodicAlarm(g_cameraAlarm.GetPtr(), - coreinit::OSGetTime(), - coreinit::EspressoTime::GetTimerClock() / m_frameRate, - RPLLoader_MakePPCCallable(DecodeAlarmCallback)); - } - ~CAMInstance() - { - m_capWorker.request_stop(); - m_capWorker.join(); - coreinit::OSCancelAlarm(g_cameraAlarm.GetPtr()); - Cap_releaseContext(m_capCtx); - } + SysAllocator s_cameraEventData; + SysAllocator s_cameraAlarm; - void OnAlarm() + void UpdateLoop() + { + while (s_instance.isOpen) { - const auto surface = m_targetSurfaceQueue.Pop(); - if (surface.IsNull()) - return; { - std::scoped_lock lock(m_callbackMutex); - std::memcpy(surface->surfacePtr.GetPtr(), m_capNv12Buffer.get(), nv12BufferSize); - } - g_cameraEventData->type = CAMEventType::Decode; - g_cameraEventData->buffer = surface->surfacePtr; - g_cameraEventData->channel = 0; - g_cameraEventData->errored = false; - PPCCoreCallback(m_callbackPtr, g_cameraEventData.GetPtr()); - } - - void Worker(std::stop_token stopToken, CapFormatID formatId) - { - SetThreadName("CAMWorker"); - const auto stream = Cap_openStream(m_capCtx, m_capDeviceId, formatId); - constexpr static auto rgbBufferSize = g_width * g_height * 3; - auto rgbBuffer = std::make_unique(rgbBufferSize); - while (!stopToken.stop_requested()) - { - if (!m_opened) + std::scoped_lock lock(s_instance.mutex); + auto surfaceBuffer = s_instance.inTargetBuffers.Pop(); + if (surfaceBuffer.IsNull()) { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - std::this_thread::yield(); continue; } - auto res = Cap_captureFrame(m_capCtx, stream, rgbBuffer.get(), rgbBufferSize); - if (res == CAPRESULT_OK) - { - std::scoped_lock lock(m_callbackMutex); - Rgb2Nv12(rgbBuffer.get(), g_width, g_height, m_capNv12Buffer.get(), g_pitch); - } + CameraManager::instance().GetNV12Data(surfaceBuffer.GetPtr()); + s_instance.outTargetBuffers.Push(surfaceBuffer); } - Cap_closeStream(m_capCtx, stream); + } + } - CAMError Open() - { - if (m_opened) - return CAMError::DeviceInUse; - const auto formatCount = Cap_getNumFormats(m_capCtx, m_capDeviceId); - int formatId = -1; - for (auto i = 0; i < formatCount; ++i) - { - CapFormatInfo formatInfo; - Cap_getFormatInfo(m_capCtx, m_capDeviceId, i, &formatInfo); - if (formatInfo.width == g_width && formatInfo.height == g_height && formatInfo.fps == m_frameRate) - { - formatId = i; - break; - } - } - if (formatId == -1) - { - cemuLog_log(LogType::Force, "camera: Open failed, {}x{} @ {} fps not supported by host camera", g_width, g_height, m_frameRate); - return CAMError::DeviceInitFailed; - } - m_targetSurfaceQueue.Clear(); - m_opened = true; - m_capWorker = std::jthread(&CAMInstance::Worker, this, formatId); - return CAMError::Success; - } - - CAMError Close() - { - m_opened = false; - return CAMError::Success; - } - - CAMError SubmitTargetSurface(MEMPTR surface) - { - cemu_assert(surface->surfaceSize >= nv12BufferSize); - if (!m_opened) - return CAMError::NotReady; - if (!m_targetSurfaceQueue.Push(surface)) - return CAMError::SurfaceQueueFull; - cemuLog_logDebug(LogType::Force, "camera: surface {} submitted", surface->surfacePtr); - return CAMError::Success; - } - - private: - CapContext m_capCtx; - CapDeviceID m_capDeviceId = 0; - std::unique_ptr m_capNv12Buffer; - std::jthread m_capWorker; - std::mutex m_callbackMutex{}; - uint32 m_frameRate; - MEMPTR m_callbackPtr; - RingBuffer, 20> m_targetSurfaceQueue{}; - std::atomic_bool m_opened = false; - }; - std::optional g_camInstance; - - void DecodeAlarmCallback(PPCInterpreter_t*) + void AlarmCallback(PPCInterpreter_t*) { - std::scoped_lock camLock(g_cameraMutex); - if (!g_camInstance) + s_cameraEventData->type = CAMEventType::Decode; + s_cameraEventData->channel = 0; + + if (s_instance.shouldTriggerCallback) + { + s_instance.shouldTriggerCallback = false; + s_cameraEventData->data = nullptr; + s_cameraEventData->errored = false; + } + else if (s_instance.isOpen) + { + if (auto buffer = s_instance.outTargetBuffers.Pop()) + { + s_cameraEventData->data = buffer; + s_cameraEventData->errored = false; + PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR()); + } + else + { + s_cameraEventData->data = nullptr; + s_cameraEventData->errored = true; + } + } + else return; - g_camInstance->OnAlarm(); } sint32 CAMGetMemReq(CAMImageInfo*) @@ -242,76 +164,110 @@ namespace camera return 1 * 1024; // always return 1KB } - CAMError CAMCheckMemSegmentation(void* base, uint32 size) + CAMStatus CAMCheckMemSegmentation(void* base, uint32 size) { - return CAMError::Success; // always return success + if (!base || size == 0) + return CAMStatus::InvalidArg; + return CAMStatus::Success; } - sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, betype* error) + sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* initInfo, betype* error) { - if (g_camInstance) + *error = CAMStatus::Success; + std::scoped_lock lock(s_instance.mutex); + if (s_instance.initialized) { - *error = CAMError::DeviceInUse; + *error = CAMStatus::DeviceInUse; return -1; } - std::unique_lock _lock(g_cameraMutex); - const auto& [t, height, width] = camInitInfo->imageInfo; - cemu_assert(width == g_width && height == g_height); - try + + 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) { - g_camInstance.emplace(camInitInfo->fps, camInitInfo->callback); - } - catch (CAMError e) - { - *error = e; + *error = CAMStatus::InvalidArg; return -1; } - *error = CAMError::Success; + + cemu_assert_debug(initInfo->forceDisplay != CAMForceDisplay::DRC); + cemu_assert_debug(initInfo->workMemorySize != 0); + cemu_assert_debug(initInfo->imageInfo.type == CAMImageType::Default); + + auto frameRate = initInfo->fps == CAMFps::_15 ? 15 : 30; + s_instance.initialized = true; + s_instance.shouldTriggerCallback = true; + s_instance.eventCallback = initInfo->callback; + coreinit::OSCreateAlarm(s_cameraAlarm.GetPtr()); + coreinit::OSSetPeriodicAlarm(s_cameraAlarm.GetPtr(), + coreinit::OSGetTime(), + coreinit::EspressoTime::GetTimerClock() / frameRate, + RPLLoader_MakePPCCallable(AlarmCallback)); return 0; } + CAMStatus CAMClose(sint32 camHandle) + { + if (camHandle != CAM_HANDLE) + return CAMStatus::InvalidHandle; + std::scoped_lock lock(s_instance.mutex); + if (!s_instance.initialized || !s_instance.isOpen) + return CAMStatus::Uninitialized; + s_instance.isOpen = false; + s_instance.updateThread.join(); + CameraManager::instance().Close(); + return CAMStatus::Success; + } + + CAMStatus CAMOpen(sint32 camHandle) + { + if (camHandle != CAM_HANDLE) + return CAMStatus::InvalidHandle; + auto lock = std::scoped_lock(s_instance.mutex); + if (!s_instance.initialized) + return CAMStatus::Uninitialized; + if (s_instance.isOpen) + return CAMStatus::DeviceInUse; + if (!CameraManager::instance().Open(false)) + return CAMStatus::UVCError; + s_instance.inTargetBuffers.Clear(); + s_instance.outTargetBuffers.Clear(); + s_instance.isOpen = true; + s_instance.updateThread = std::thread(UpdateLoop); + return CAMStatus::Success; + } + + CAMStatus CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) + { + if (camHandle != CAM_HANDLE) + return CAMStatus::InvalidHandle; + if (!targetSurface || targetSurface->data.IsNull() || targetSurface->size < 1) + return CAMStatus::InvalidArg; + auto lock = std::scoped_lock(s_instance.mutex); + if (!s_instance.initialized) + return CAMStatus::Uninitialized; + if (!s_instance.inTargetBuffers.Push(targetSurface->data)) + return CAMStatus::SurfaceQueueFull; + return CAMStatus::Success; + } + void CAMExit(sint32 camHandle) { - if (camHandle != 0 || !g_camInstance) + if (camHandle != CAM_HANDLE) return; - g_camInstance.reset(); - } - - CAMError CAMClose(sint32 camHandle) - { - if (camHandle != 0) - return CAMError::InvalidHandle; - std::scoped_lock lock(g_cameraMutex); - if (!g_camInstance) - return CAMError::Uninitialized; - return g_camInstance->Close(); - } - - CAMError CAMOpen(sint32 camHandle) - { - if (camHandle != 0) - return CAMError::InvalidHandle; - auto lock = std::scoped_lock(g_cameraMutex); - if (!g_camInstance) - return CAMError::Uninitialized; - return g_camInstance->Open(); - } - - CAMError CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) - { - if (camHandle != 0) - return CAMError::InvalidHandle; - if (!targetSurface || targetSurface->surfacePtr.IsNull() || targetSurface->surfaceSize < 1) - return CAMError::InvalidArg; - auto lock = std::scoped_lock(g_cameraMutex); - if (!g_camInstance) - return CAMError::Uninitialized; - return g_camInstance->SubmitTargetSurface(targetSurface); + std::scoped_lock lock(s_instance.mutex); + if (!s_instance.initialized) + return; + if (s_instance.isOpen) + CAMClose(camHandle); + s_instance.initialized = false; + s_instance.shouldTriggerCallback = false; + coreinit::OSCancelAlarm(s_cameraAlarm); } void reset() { - g_camInstance.reset(); + CAMExit(0); } void load() diff --git a/src/camera/CMakeLists.txt b/src/camera/CMakeLists.txt new file mode 100644 index 00000000..3e899fec --- /dev/null +++ b/src/camera/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(CemuCamera + CameraManager.cpp + CameraManager.h + Rgb2Nv12.cpp + Rgb2Nv12.h +) +target_include_directories(CemuCamera PUBLIC "../") +target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil PUBLIC openpnp-capture) \ No newline at end of file diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp new file mode 100644 index 00000000..eb62dc59 --- /dev/null +++ b/src/camera/CameraManager.cpp @@ -0,0 +1,99 @@ +#include "CameraManager.h" +#include "Rgb2Nv12.h" +#include "util/helpers/helpers.h" + +constexpr unsigned CAMERA_WIDTH = 640; +constexpr unsigned CAMERA_HEIGHT = 480; +constexpr unsigned CAMERA_PITCH = 768; + +CameraManager::CameraManager() + : m_ctx(Cap_createContext()), + m_rgbBuffer(CAMERA_WIDTH * CAMERA_HEIGHT * 3), + m_nv12Buffer(CAMERA_PITCH * CAMERA_HEIGHT * 3 / 2), + m_refCount(0) +{ + if (Cap_getDeviceCount(m_ctx) > 0) + m_device = 0; +} +CameraManager::~CameraManager() +{ + Close(); + Cap_releaseContext(m_ctx); +} + +void CameraManager::SetDevice(uint deviceNo) +{ + std::scoped_lock lock(m_mutex); + if (m_device == deviceNo) + return; + if (m_stream) + Cap_closeStream(m_ctx, *m_stream); + m_device = deviceNo; +} +bool CameraManager::Open(bool weak) +{ + std::scoped_lock lock(m_mutex); + if (!m_device) + return false; + if (m_refCount == 0) + { + const auto formatCount = Cap_getNumFormats(m_ctx, *m_device); + CapFormatID formatId = 0; + for (; formatId < formatCount; ++formatId) + { + CapFormatInfo formatInfo; + if (Cap_getFormatInfo(m_ctx, *m_device, formatId, &formatInfo) != CAPRESULT_OK) + continue; + if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT) + break; + } + if (formatId == formatCount) + return false; + auto stream = Cap_openStream(m_ctx, *m_device, formatId); + if (stream == -1) + return false; + m_capturing = true; + m_stream = stream; + m_captureThread = std::thread(&CameraManager::CaptureWorker, this); + } + else if (!weak) + m_refCount += 1; + return true; +} +void CameraManager::Close() +{ + std::scoped_lock lock(m_mutex); + if (m_refCount == 0) + return; + m_refCount -= 1; + if (m_refCount != 0) + return; + Cap_closeStream(m_ctx, *m_stream); + m_stream = std::nullopt; + m_capturing = false; + m_captureThread.join(); +} + +void CameraManager::GetNV12Data(uint8_t* nv12Buffer) const +{ + std::shared_lock lock(m_mutex); + std::ranges::copy(m_nv12Buffer, nv12Buffer); +} +void CameraManager::CaptureWorker() +{ + SetThreadName("CameraManager"); + while (m_capturing) + { + { + std::scoped_lock lock(m_mutex); + bool frameAvailable = Cap_hasNewFrame(m_ctx, *m_stream); + if (frameAvailable && + Cap_captureFrame(m_ctx, *m_stream, m_rgbBuffer.data(), m_rgbBuffer.size()) == CAPRESULT_OK) + { + Rgb2Nv12(m_rgbBuffer.data(), CAMERA_WIDTH, CAMERA_HEIGHT, m_nv12Buffer.data(), CAMERA_PITCH); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + std::this_thread::yield(); + } +} \ No newline at end of file diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h new file mode 100644 index 00000000..009f9f62 --- /dev/null +++ b/src/camera/CameraManager.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#include +#include "util/helpers/Singleton.h" +class CameraManager : public Singleton +{ + CapContext m_ctx; + std::optional m_device; + std::optional m_stream; + std::vector m_rgbBuffer; + std::vector m_nv12Buffer; + int m_refCount; + std::thread m_captureThread; + std::atomic_bool m_capturing; + mutable std::shared_mutex m_mutex; +public: + CameraManager(); + ~CameraManager(); + + void SetDevice(uint deviceNo); + + bool Open(bool weak); + void Close(); + + void GetNV12Data(uint8_t* nv12Buffer) const; +private: + void CaptureWorker(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.cpp b/src/camera/Rgb2Nv12.cpp similarity index 100% rename from src/Cafe/OS/libs/camera/Rgb2Nv12.cpp rename to src/camera/Rgb2Nv12.cpp diff --git a/src/Cafe/OS/libs/camera/Rgb2Nv12.h b/src/camera/Rgb2Nv12.h similarity index 100% rename from src/Cafe/OS/libs/camera/Rgb2Nv12.h rename to src/camera/Rgb2Nv12.h From fd946f63db77c6cf725f1c40b4c273bde3a76029 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 06:16:23 +0000 Subject: [PATCH 09/37] Formatting --- src/Cafe/OS/libs/camera/camera.cpp | 1 - src/camera/CameraManager.h | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index cce5d7bd..b9f3e761 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -126,7 +126,6 @@ namespace camera CameraManager::instance().GetNV12Data(surfaceBuffer.GetPtr()); s_instance.outTargetBuffers.Push(surfaceBuffer); } - } } diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index 009f9f62..a7024e3d 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -1,8 +1,8 @@ #pragma once #include - #include #include "util/helpers/Singleton.h" + class CameraManager : public Singleton { CapContext m_ctx; @@ -14,7 +14,8 @@ class CameraManager : public Singleton std::thread m_captureThread; std::atomic_bool m_capturing; mutable std::shared_mutex m_mutex; -public: + + public: CameraManager(); ~CameraManager(); @@ -24,6 +25,7 @@ public: void Close(); void GetNV12Data(uint8_t* nv12Buffer) const; -private: + + private: void CaptureWorker(); }; \ No newline at end of file From e9f26a5068f936d47ae70d0fa492de710cf0a173 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 06:33:41 +0000 Subject: [PATCH 10/37] Attempt to fix close freeze --- src/Cafe/OS/libs/camera/camera.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index b9f3e761..724b17f8 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -209,10 +209,12 @@ namespace camera { if (camHandle != CAM_HANDLE) return CAMStatus::InvalidHandle; - std::scoped_lock lock(s_instance.mutex); - if (!s_instance.initialized || !s_instance.isOpen) - return CAMStatus::Uninitialized; - s_instance.isOpen = false; + { + std::scoped_lock lock(s_instance.mutex); + if (!s_instance.initialized || !s_instance.isOpen) + return CAMStatus::Uninitialized; + s_instance.isOpen = false; + } s_instance.updateThread.join(); CameraManager::instance().Close(); return CAMStatus::Success; From cd3dc745486a52bd5b76c4f0724e873a7f00988e Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 2 Sep 2024 23:58:01 +0100 Subject: [PATCH 11/37] MSVC stuff --- CMakeLists.txt | 1 + src/camera/CMakeLists.txt | 3 +++ src/camera/CameraManager.cpp | 2 +- src/camera/CameraManager.h | 6 +++--- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d24bd81..929259fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,7 @@ 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$<$:Debug>") find_package(ZArchive) if (NOT ZArchive_FOUND) diff --git a/src/camera/CMakeLists.txt b/src/camera/CMakeLists.txt index 3e899fec..8a8e96f3 100644 --- a/src/camera/CMakeLists.txt +++ b/src/camera/CMakeLists.txt @@ -4,5 +4,8 @@ add_library(CemuCamera Rgb2Nv12.cpp Rgb2Nv12.h ) + +set_property(TARGET CemuCamera PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + target_include_directories(CemuCamera PUBLIC "../") target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil PUBLIC openpnp-capture) \ No newline at end of file diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index eb62dc59..b5c03983 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -21,7 +21,7 @@ CameraManager::~CameraManager() Cap_releaseContext(m_ctx); } -void CameraManager::SetDevice(uint deviceNo) +void CameraManager::SetDevice(unsigned deviceNo) { std::scoped_lock lock(m_mutex); if (m_device == deviceNo) diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index a7024e3d..502ff82e 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -8,8 +8,8 @@ class CameraManager : public Singleton CapContext m_ctx; std::optional m_device; std::optional m_stream; - std::vector m_rgbBuffer; - std::vector m_nv12Buffer; + std::vector m_rgbBuffer; + std::vector m_nv12Buffer; int m_refCount; std::thread m_captureThread; std::atomic_bool m_capturing; @@ -19,7 +19,7 @@ class CameraManager : public Singleton CameraManager(); ~CameraManager(); - void SetDevice(uint deviceNo); + void SetDevice(unsigned deviceNo); bool Open(bool weak); void Close(); From 154eacd85cc90b38ec9d7bd04017254c03944525 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 10:43:52 +0000 Subject: [PATCH 12/37] Use an `OSThread` for handling the callback --- src/Cafe/OS/libs/camera/camera.cpp | 102 ++++++++++++++--------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 044cd5cd..2872b49b 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -3,13 +3,13 @@ #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" + namespace camera { constexpr unsigned CAMERA_WIDTH = 640; @@ -103,59 +103,58 @@ namespace camera bool initialized = false; bool shouldTriggerCallback = false; std::atomic_bool isOpen = false; + std::atomic_bool isExiting = false; + bool isWorking = false; + unsigned fps = 30; MEMPTR eventCallback = nullptr; RingBuffer, 20> inTargetBuffers{}; RingBuffer, 20> outTargetBuffers{}; - std::thread updateThread; } s_instance; SysAllocator s_cameraEventData; - SysAllocator s_cameraAlarm; + SysAllocator s_cameraWorkerThread; + SysAllocator s_cameraWorkerThreadStack; + SysAllocator s_cameraOpenEvent; - void UpdateLoop() - { - while (s_instance.isOpen) - { - { - std::scoped_lock lock(s_instance.mutex); - auto surfaceBuffer = s_instance.inTargetBuffers.Pop(); - if (surfaceBuffer.IsNull()) - { - continue; - } - CameraManager::instance().GetNV12Data(surfaceBuffer.GetPtr()); - s_instance.outTargetBuffers.Push(surfaceBuffer); - } - } - } - - void AlarmCallback(PPCInterpreter_t*) + 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()); - if (s_instance.shouldTriggerCallback) + while (!s_instance.isExiting) { - s_instance.shouldTriggerCallback = false; - s_cameraEventData->data = nullptr; - s_cameraEventData->errored = false; - } - else if (s_instance.isOpen) - { - if (auto buffer = s_instance.outTargetBuffers.Pop()) + coreinit::OSWaitEvent(s_cameraOpenEvent); + while (true) { - s_cameraEventData->data = buffer; - s_cameraEventData->errored = false; + 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; + + auto surfaceBuffer = s_instance.inTargetBuffers.Pop(); + if (surfaceBuffer.IsNull()) + { + s_cameraEventData->data = nullptr; + s_cameraEventData->errored = true; + } + else + { + CameraManager::instance().GetNV12Data(surfaceBuffer.GetPtr()); + s_cameraEventData->data = surfaceBuffer; + s_cameraEventData->errored = false; + } PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR()); - } - else - { - s_cameraEventData->data = nullptr; - s_cameraEventData->errored = true; + coreinit::OSSleepTicks(Espresso::TIMER_CLOCK / (s_instance.fps - 1)); } } - else - return; + cemuLog_logDebug(LogType::Force, "Camera Worker Thread Exited"); } sint32 CAMGetMemReq(CAMImageInfo*) @@ -179,7 +178,7 @@ namespace camera *error = CAMStatus::DeviceInUse; return -1; } - + if (!initInfo || !initInfo->workMemoryData || !match_any_of(initInfo->forceDisplay, CAMForceDisplay::None, CAMForceDisplay::DRC) || !match_any_of(initInfo->fps, CAMFps::_15, CAMFps::_30) || @@ -193,15 +192,15 @@ namespace camera cemu_assert_debug(initInfo->workMemorySize != 0); cemu_assert_debug(initInfo->imageInfo.type == CAMImageType::Default); - auto frameRate = initInfo->fps == CAMFps::_15 ? 15 : 30; + s_instance.fps = initInfo->fps == CAMFps::_15 ? 15 : 30; s_instance.initialized = true; - s_instance.shouldTriggerCallback = true; s_instance.eventCallback = initInfo->callback; - coreinit::OSCreateAlarm(s_cameraAlarm.GetPtr()); - coreinit::OSSetPeriodicAlarm(s_cameraAlarm.GetPtr(), - coreinit::OSGetTime(), - coreinit::EspressoTime::GetTimerClock() / frameRate, - RPLLoader_MakePPCCallable(AlarmCallback)); + 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); + coreinit::OSResumeThread(s_cameraWorkerThread); return 0; } @@ -215,7 +214,6 @@ namespace camera return CAMStatus::Uninitialized; s_instance.isOpen = false; } - s_instance.updateThread.join(); CameraManager::instance().Close(); return CAMStatus::Success; } @@ -231,10 +229,10 @@ namespace camera return CAMStatus::DeviceInUse; if (!CameraManager::instance().Open(false)) return CAMStatus::UVCError; + s_instance.isOpen = true; + coreinit::OSSignalEvent(s_cameraOpenEvent); s_instance.inTargetBuffers.Clear(); s_instance.outTargetBuffers.Clear(); - s_instance.isOpen = true; - s_instance.updateThread = std::thread(UpdateLoop); return CAMStatus::Success; } @@ -259,11 +257,11 @@ namespace camera 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()); s_instance.initialized = false; - s_instance.shouldTriggerCallback = false; - coreinit::OSCancelAlarm(s_cameraAlarm); } void reset() From 062ac500e4adea0b1dec8cc6adcda2af4d1e9e72 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 11:19:49 +0000 Subject: [PATCH 13/37] Correct mistakes in `CameraManager::Open` and `CameraManager::Close` --- src/camera/CameraManager.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index b5c03983..9cca41bd 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -56,21 +56,23 @@ bool CameraManager::Open(bool weak) m_stream = stream; m_captureThread = std::thread(&CameraManager::CaptureWorker, this); } - else if (!weak) + if (!weak) m_refCount += 1; return true; } void CameraManager::Close() { - std::scoped_lock lock(m_mutex); - if (m_refCount == 0) - return; - m_refCount -= 1; - if (m_refCount != 0) - return; - Cap_closeStream(m_ctx, *m_stream); - m_stream = std::nullopt; - m_capturing = false; + { + std::scoped_lock lock(m_mutex); + if (m_refCount == 0) + return; + m_refCount -= 1; + if (m_refCount != 0) + return; + Cap_closeStream(m_ctx, *m_stream); + m_stream = std::nullopt; + m_capturing = false; + } m_captureThread.join(); } From 0f13272977e41c7896ddb800328a5ac7141d801b Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 11:31:20 +0000 Subject: [PATCH 14/37] Make exiting worker thread possible --- src/Cafe/OS/libs/camera/camera.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 2872b49b..8fbec743 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -154,7 +154,7 @@ namespace camera coreinit::OSSleepTicks(Espresso::TIMER_CLOCK / (s_instance.fps - 1)); } } - cemuLog_logDebug(LogType::Force, "Camera Worker Thread Exited"); + coreinit::OSExitThread(0); } sint32 CAMGetMemReq(CAMImageInfo*) @@ -191,7 +191,7 @@ namespace camera 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; @@ -261,6 +261,7 @@ namespace camera if (s_instance.isOpen) CAMClose(camHandle); coreinit::OSSignalEvent(s_cameraOpenEvent.GetPtr()); + coreinit::OSJoinThread(s_cameraWorkerThread, nullptr); s_instance.initialized = false; } From 2218d552cf0ba8ce7dd67ef8ab187c8633c70dc0 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 11:32:08 +0000 Subject: [PATCH 15/37] Set worker thread name --- src/Cafe/OS/libs/camera/camera.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 8fbec743..06296fe8 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -9,6 +9,7 @@ #include "util/helpers/ringbuffer.h" #include "camera/CameraManager.h" #include "Cafe/HW/Espresso/Const.h" +#include "Common/CafeString.h" namespace camera { @@ -114,6 +115,8 @@ namespace camera SysAllocator s_cameraEventData; SysAllocator s_cameraWorkerThread; SysAllocator s_cameraWorkerThreadStack; + SysAllocator> s_cameraWorkerThreadNameBuffer; + SysAllocator s_cameraOpenEvent; void WorkerThread(PPCInterpreter_t*) @@ -200,7 +203,9 @@ namespace camera 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); - coreinit::OSResumeThread(s_cameraWorkerThread); + s_cameraWorkerThreadNameBuffer->assign("CameraWorkerThread"); + coreinit::OSSetThreadName(s_cameraWorkerThread.GetPtr(), s_cameraWorkerThreadNameBuffer->c_str()); + coreinit::OSResumeThread(s_cameraWorkerThread.GetPtr()); return 0; } From 95f5295e0cb5b05de2029c0c3e893d52ba1799ce Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 24 Mar 2025 12:42:05 +0000 Subject: [PATCH 16/37] Formatting and style --- src/Cafe/OS/libs/camera/camera.cpp | 94 +++++++++++++++--------------- src/camera/CameraManager.cpp | 2 +- src/camera/CameraManager.h | 2 +- 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 06296fe8..cfdd01df 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -16,19 +16,21 @@ namespace camera constexpr unsigned CAMERA_WIDTH = 640; constexpr unsigned CAMERA_HEIGHT = 480; - enum class CAMStatus : sint32 + enum CAMStatus : sint32 { - Success = 0, - InvalidArg = -1, - InvalidHandle = -2, - SurfaceQueueFull = -4, - InsufficientMemory = -5, - NotReady = -6, - Uninitialized = -8, - UVCError = -9, - DecoderInitFailed = -10, - DeviceInUse = -12, - DecoderSessionFailed = -13, + 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 @@ -77,15 +79,11 @@ namespace camera struct CAMTargetSurface { - /* +0x00 */ sint32be size; - /* +0x04 */ MEMPTR data; - /* +0x08 */ uint32be height; - /* +0x0C */ uint32be width; - /* +0x10 */ uint32be ukn10; - /* +0x14 */ uint32be ukn14; - /* +0x18 */ uint32be ukn18; - /* +0x1C */ uint32be ukn1C; + sint32be size; + MEMPTR data; + uint8 unused[0x18]; }; + static_assert(sizeof(CAMTargetSurface) == 0x20); struct CAMDecodeEventParam { @@ -108,15 +106,14 @@ namespace camera bool isWorking = false; unsigned fps = 30; MEMPTR eventCallback = nullptr; - RingBuffer, 20> inTargetBuffers{}; - RingBuffer, 20> outTargetBuffers{}; + RingBuffer, 20> inTargetBuffers{}; + RingBuffer, 20> outTargetBuffers{}; } s_instance; SysAllocator s_cameraEventData; SysAllocator s_cameraWorkerThread; SysAllocator s_cameraWorkerThreadStack; SysAllocator> s_cameraWorkerThreadNameBuffer; - SysAllocator s_cameraOpenEvent; void WorkerThread(PPCInterpreter_t*) @@ -141,7 +138,7 @@ namespace camera s_cameraEventData->type = CAMEventType::Decode; s_cameraEventData->channel = 0; - auto surfaceBuffer = s_instance.inTargetBuffers.Pop(); + const auto surfaceBuffer = s_instance.inTargetBuffers.Pop(); if (surfaceBuffer.IsNull()) { s_cameraEventData->data = nullptr; @@ -149,7 +146,7 @@ namespace camera } else { - CameraManager::instance().GetNV12Data(surfaceBuffer.GetPtr()); + CameraManager::instance().FillNV12Buffer(surfaceBuffer.GetPtr()); s_cameraEventData->data = surfaceBuffer; s_cameraEventData->errored = false; } @@ -160,25 +157,27 @@ namespace camera coreinit::OSExitThread(0); } - sint32 CAMGetMemReq(CAMImageInfo*) + sint32 CAMGetMemReq(const CAMImageInfo* info) { + if (!info) + return CAM_STATUS_INVALID_ARG; return 1 * 1024; // always return 1KB } CAMStatus CAMCheckMemSegmentation(void* base, uint32 size) { if (!base || size == 0) - return CAMStatus::InvalidArg; - return CAMStatus::Success; + return CAM_STATUS_INVALID_ARG; + return CAM_STATUS_SUCCESS; } - sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* initInfo, betype* error) + sint32 CAMInit(uint32 cameraId, const CAMInitInfo_t* initInfo, betype* error) { - *error = CAMStatus::Success; + *error = CAM_STATUS_SUCCESS; std::scoped_lock lock(s_instance.mutex); if (s_instance.initialized) { - *error = CAMStatus::DeviceInUse; + *error = CAM_STATUS_DEVICE_IN_USE; return -1; } @@ -187,18 +186,21 @@ namespace camera !match_any_of(initInfo->fps, CAMFps::_15, CAMFps::_30) || initInfo->imageInfo.type != CAMImageType::Default) { - *error = CAMStatus::InvalidArg; + *error = CAM_STATUS_INVALID_ARG; return -1; } 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(), @@ -206,53 +208,53 @@ namespace camera s_cameraWorkerThreadNameBuffer->assign("CameraWorkerThread"); coreinit::OSSetThreadName(s_cameraWorkerThread.GetPtr(), s_cameraWorkerThreadNameBuffer->c_str()); coreinit::OSResumeThread(s_cameraWorkerThread.GetPtr()); - return 0; + return CAM_STATUS_SUCCESS; } CAMStatus CAMClose(sint32 camHandle) { if (camHandle != CAM_HANDLE) - return CAMStatus::InvalidHandle; + return CAM_STATUS_INVALID_HANDLE; { std::scoped_lock lock(s_instance.mutex); if (!s_instance.initialized || !s_instance.isOpen) - return CAMStatus::Uninitialized; + return CAM_STATUS_UNINITIALIZED; s_instance.isOpen = false; } CameraManager::instance().Close(); - return CAMStatus::Success; + return CAM_STATUS_SUCCESS; } CAMStatus CAMOpen(sint32 camHandle) { if (camHandle != CAM_HANDLE) - return CAMStatus::InvalidHandle; + return CAM_STATUS_INVALID_HANDLE; auto lock = std::scoped_lock(s_instance.mutex); if (!s_instance.initialized) - return CAMStatus::Uninitialized; + return CAM_STATUS_UNINITIALIZED; if (s_instance.isOpen) - return CAMStatus::DeviceInUse; + return CAM_STATUS_DEVICE_IN_USE; if (!CameraManager::instance().Open(false)) - return CAMStatus::UVCError; + return CAM_STATUS_UVC_ERROR; s_instance.isOpen = true; coreinit::OSSignalEvent(s_cameraOpenEvent); s_instance.inTargetBuffers.Clear(); s_instance.outTargetBuffers.Clear(); - return CAMStatus::Success; + return CAM_STATUS_SUCCESS; } CAMStatus CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) { if (camHandle != CAM_HANDLE) - return CAMStatus::InvalidHandle; + return CAM_STATUS_INVALID_HANDLE; if (!targetSurface || targetSurface->data.IsNull() || targetSurface->size < 1) - return CAMStatus::InvalidArg; + return CAM_STATUS_INVALID_ARG; auto lock = std::scoped_lock(s_instance.mutex); if (!s_instance.initialized) - return CAMStatus::Uninitialized; + return CAM_STATUS_UNINITIALIZED; if (!s_instance.inTargetBuffers.Push(targetSurface->data)) - return CAMStatus::SurfaceQueueFull; - return CAMStatus::Success; + return CAM_STATUS_SURFACE_QUEUE_FULL; + return CAM_STATUS_SUCCESS; } void CAMExit(sint32 camHandle) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 9cca41bd..7a8d80fb 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -76,7 +76,7 @@ void CameraManager::Close() m_captureThread.join(); } -void CameraManager::GetNV12Data(uint8_t* nv12Buffer) const +void CameraManager::FillNV12Buffer(uint8* nv12Buffer) const { std::shared_lock lock(m_mutex); std::ranges::copy(m_nv12Buffer, nv12Buffer); diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index 502ff82e..345fe7e0 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -24,7 +24,7 @@ class CameraManager : public Singleton bool Open(bool weak); void Close(); - void GetNV12Data(uint8_t* nv12Buffer) const; + void FillNV12Buffer(uint8* nv12Buffer) const; private: void CaptureWorker(); From 791e358bf7ad91bfd471ec5fc85ab8fd7ef8ae08 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 28 Mar 2025 21:27:38 +0000 Subject: [PATCH 17/37] Small --- src/camera/CameraManager.cpp | 3 ++- src/camera/CameraManager.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 7a8d80fb..d0d4be11 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -12,6 +12,7 @@ CameraManager::CameraManager() m_nv12Buffer(CAMERA_PITCH * CAMERA_HEIGHT * 3 / 2), m_refCount(0) { + // Set default device if it exists if (Cap_getDeviceCount(m_ctx) > 0) m_device = 0; } @@ -21,7 +22,7 @@ CameraManager::~CameraManager() Cap_releaseContext(m_ctx); } -void CameraManager::SetDevice(unsigned deviceNo) +void CameraManager::SetDevice(uint32 deviceNo) { std::scoped_lock lock(m_mutex); if (m_device == deviceNo) diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index 345fe7e0..601ab44f 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -19,7 +19,7 @@ class CameraManager : public Singleton CameraManager(); ~CameraManager(); - void SetDevice(unsigned deviceNo); + void SetDevice(uint32 deviceNo); bool Open(bool weak); void Close(); From b9000bd66738f2827e96f5114db0ebb86a9daf82 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 03:03:49 +0100 Subject: [PATCH 18/37] Add camera settings window and save camera config --- src/Cafe/OS/libs/camera/camera.cpp | 3 +- src/camera/CMakeLists.txt | 6 +- src/camera/CameraManager.cpp | 167 ++++++++++++++++++++--------- src/camera/CameraManager.h | 25 ++++- src/config/CemuConfig.cpp | 6 ++ src/config/CemuConfig.h | 3 + src/gui/CMakeLists.txt | 3 + src/gui/CameraSettingsWindow.cpp | 98 +++++++++++++++++ src/gui/CameraSettingsWindow.h | 22 ++++ src/gui/MainWindow.cpp | 10 ++ 10 files changed, 284 insertions(+), 59 deletions(-) create mode 100644 src/gui/CameraSettingsWindow.cpp create mode 100644 src/gui/CameraSettingsWindow.h diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index cfdd01df..90f1b073 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -234,8 +234,7 @@ namespace camera return CAM_STATUS_UNINITIALIZED; if (s_instance.isOpen) return CAM_STATUS_DEVICE_IN_USE; - if (!CameraManager::instance().Open(false)) - return CAM_STATUS_UVC_ERROR; + CameraManager::instance().Open(); s_instance.isOpen = true; coreinit::OSSignalEvent(s_cameraOpenEvent); s_instance.inTargetBuffers.Clear(); diff --git a/src/camera/CMakeLists.txt b/src/camera/CMakeLists.txt index 8a8e96f3..44342018 100644 --- a/src/camera/CMakeLists.txt +++ b/src/camera/CMakeLists.txt @@ -8,4 +8,8 @@ add_library(CemuCamera set_property(TARGET CemuCamera PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuCamera PUBLIC "../") -target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil PUBLIC openpnp-capture) \ No newline at end of file +target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil PUBLIC openpnp-capture) + +if (ENABLE_WXWIDGETS) + target_link_libraries(CemuCamera PRIVATE wx::base) +endif() \ No newline at end of file diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index d0d4be11..4dfa52c2 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -1,5 +1,6 @@ #include "CameraManager.h" #include "Rgb2Nv12.h" +#include "config/CemuConfig.h" #include "util/helpers/helpers.h" constexpr unsigned CAMERA_WIDTH = 640; @@ -10,93 +11,157 @@ CameraManager::CameraManager() : m_ctx(Cap_createContext()), m_rgbBuffer(CAMERA_WIDTH * CAMERA_HEIGHT * 3), m_nv12Buffer(CAMERA_PITCH * CAMERA_HEIGHT * 3 / 2), - m_refCount(0) + m_refCount(0), m_capturing(false), m_running(true) { - // Set default device if it exists - if (Cap_getDeviceCount(m_ctx) > 0) - m_device = 0; + m_captureThread = std::thread(&CameraManager::CaptureWorker, this); + + 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) + { + m_device = deviceId; + return; + } + } + } + ResetBuffers(); } CameraManager::~CameraManager() { - Close(); + m_running = false; + CloseStream(); Cap_releaseContext(m_ctx); } void CameraManager::SetDevice(uint32 deviceNo) { std::scoped_lock lock(m_mutex); - if (m_device == deviceNo) + CloseStream(); + if (deviceNo == DEVICE_NONE) + { + m_device = std::nullopt; + ResetBuffers(); return; - if (m_stream) - Cap_closeStream(m_ctx, *m_stream); + } m_device = deviceNo; + if (m_refCount != 0) + OpenStream(); } -bool CameraManager::Open(bool weak) +std::vector CameraManager::EnumerateDevices() { std::scoped_lock lock(m_mutex); - if (!m_device) - return false; - if (m_refCount == 0) + std::vector infos; + const auto deviceCount = Cap_getDeviceCount(m_ctx); + for (CapDeviceID deviceNo = 0; deviceNo < deviceCount; ++deviceNo) { - const auto formatCount = Cap_getNumFormats(m_ctx, *m_device); - CapFormatID formatId = 0; - for (; formatId < formatCount; ++formatId) - { - CapFormatInfo formatInfo; - if (Cap_getFormatInfo(m_ctx, *m_device, formatId, &formatInfo) != CAPRESULT_OK) - continue; - if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT) - break; - } - if (formatId == formatCount) - return false; - auto stream = Cap_openStream(m_ctx, *m_device, formatId); - if (stream == -1) - return false; - m_capturing = true; - m_stream = stream; - m_captureThread = std::thread(&CameraManager::CaptureWorker, this); + const auto uniqueId = Cap_getDeviceUniqueID(m_ctx, deviceNo); + const auto name = Cap_getDeviceName(m_ctx, deviceNo); + + if (name) + infos.emplace_back(uniqueId, fmt::format("{}: {}", deviceNo + 1, name)); + else + infos.emplace_back(uniqueId, fmt::format("{}: Unknown", deviceNo + 1)); } - if (!weak) - m_refCount += 1; - return true; + return infos; +} +void CameraManager::SaveDevice() +{ + std::scoped_lock lock(m_mutex); + if (m_device) + GetConfig().camera_id = Cap_getDeviceUniqueID(m_ctx, *m_device); + else + GetConfig().camera_id = ""; +} +void CameraManager::Open() +{ + std::scoped_lock lock(m_mutex); + if (m_device && m_refCount == 0) + { + OpenStream(); + } + m_refCount += 1; } void CameraManager::Close() { + std::scoped_lock lock(m_mutex); + if (m_refCount == 0) + return; + m_refCount -= 1; + if (m_refCount != 0) + return; + CloseStream(); +} + +std::optional CameraManager::FindCorrectFormat() +{ + const auto formatCount = Cap_getNumFormats(m_ctx, *m_device); + for (CapFormatID formatId = 0; formatId < formatCount; ++formatId) { - std::scoped_lock lock(m_mutex); - if (m_refCount == 0) - return; - m_refCount -= 1; - if (m_refCount != 0) - return; - Cap_closeStream(m_ctx, *m_stream); - m_stream = std::nullopt; - m_capturing = false; + CapFormatInfo formatInfo; + if (Cap_getFormatInfo(m_ctx, *m_device, formatId, &formatInfo) != CAPRESULT_OK) + continue; + if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT) + return formatId; } - m_captureThread.join(); + return std::nullopt; +} +void CameraManager::ResetBuffers() +{ + std::ranges::fill(m_rgbBuffer, 0); + std::ranges::fill_n(m_nv12Buffer.begin(), CAMERA_WIDTH * CAMERA_PITCH, 16); + std::ranges::fill_n(m_nv12Buffer.begin() + CAMERA_WIDTH * CAMERA_PITCH, (CAMERA_WIDTH / 2), 128); } void CameraManager::FillNV12Buffer(uint8* nv12Buffer) const { - std::shared_lock lock(m_mutex); + std::scoped_lock lock(m_mutex); std::ranges::copy(m_nv12Buffer, nv12Buffer); } + +void CameraManager::FillRGBBuffer(uint8* rgbBuffer) const +{ + std::scoped_lock lock(m_mutex); + std::ranges::copy(m_rgbBuffer, rgbBuffer); +} void CameraManager::CaptureWorker() { SetThreadName("CameraManager"); - while (m_capturing) + while (m_running) { + while (m_capturing) { - std::scoped_lock lock(m_mutex); - bool frameAvailable = Cap_hasNewFrame(m_ctx, *m_stream); - if (frameAvailable && + m_mutex.lock(); + if (m_stream && Cap_hasNewFrame(m_ctx, *m_stream) && Cap_captureFrame(m_ctx, *m_stream, m_rgbBuffer.data(), m_rgbBuffer.size()) == CAPRESULT_OK) - { Rgb2Nv12(m_rgbBuffer.data(), CAMERA_WIDTH, CAMERA_HEIGHT, m_nv12Buffer.data(), CAMERA_PITCH); - } + m_mutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); } - std::this_thread::sleep_for(std::chrono::milliseconds(16)); + std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::yield(); } +} +void CameraManager::OpenStream() +{ + const auto formatId = FindCorrectFormat(); + if (!formatId) + return; + const auto stream = Cap_openStream(m_ctx, *m_device, *formatId); + if (stream == -1) + return; + m_capturing = true; + m_stream = stream; +} +void CameraManager::CloseStream() +{ + m_capturing = false; + if (m_stream) + { + Cap_closeStream(m_ctx, *m_stream); + m_stream = std::nullopt; + } } \ No newline at end of file diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index 601ab44f..e8e0c971 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -1,5 +1,7 @@ #pragma once -#include +#include +#include +#include #include #include "util/helpers/Singleton.h" @@ -13,19 +15,32 @@ class CameraManager : public Singleton int m_refCount; std::thread m_captureThread; std::atomic_bool m_capturing; - mutable std::shared_mutex m_mutex; + std::atomic_bool m_running; + mutable std::recursive_mutex m_mutex; public: + constexpr static uint32 DEVICE_NONE = std::numeric_limits::max(); + struct DeviceInfo + { + std::string uniqueId; + std::string name; + }; CameraManager(); ~CameraManager(); void SetDevice(uint32 deviceNo); + std::vector EnumerateDevices(); + void SaveDevice(); - bool Open(bool weak); + void Open(); void Close(); - void FillNV12Buffer(uint8* nv12Buffer) const; + void FillRGBBuffer(uint8* rgbBuffer) const; private: + std::optional FindCorrectFormat(); + void ResetBuffers(); void CaptureWorker(); -}; \ No newline at end of file + void OpenStream(); + void CloseStream(); +}; diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 6bb7ac34..95ef42b2 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -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()); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 62665f6d..f429a438 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -499,6 +499,9 @@ struct CemuConfig ConfigValue port{ 26760 }; }dsu_client{}; + // camera + ConfigValue camera_id; + // debug ConfigValueBounds crash_dump{ CrashDump::Disabled }; ConfigValue gdb_port{ 1337 }; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7cdc208e..aa302fdf 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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$<$:Debug>") @@ -139,6 +141,7 @@ target_include_directories(CemuGui PUBLIC ${RAPIDJSON_INCLUDE_DIRS}) target_link_libraries(CemuGui PRIVATE CemuAudio CemuCafe + CemuCamera CemuCommon CemuComponents CemuConfig diff --git a/src/gui/CameraSettingsWindow.cpp b/src/gui/CameraSettingsWindow.cpp new file mode 100644 index 00000000..e73d9211 --- /dev/null +++ b/src/gui/CameraSettingsWindow.cpp @@ -0,0 +1,98 @@ +#include "CameraSettingsWindow.h" + +#include "camera/CameraManager.h" + +#include +#include +#include +#include + +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); + { + wxString choices[] = {_("None")}; + m_cameraChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, {300, -1}, 1, choices); + 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::instance().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::instance().SetDevice(CameraManager::DEVICE_NONE); + else + CameraManager::instance().SetDevice(selection - 1); +} +void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&) +{ + wxArrayString choices = {_("None")}; + for (const auto& entry : CameraManager::instance().EnumerateDevices()) + { + choices.push_back(entry.name); + } + m_cameraChoice->Set(choices); + wxArrayString str; + +} +void CameraSettingsWindow::UpdateImage(const wxTimerEvent&) +{ + CameraManager::instance().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::instance().Close(); + CameraManager::instance().SaveDevice(); + event.Skip(); +} diff --git a/src/gui/CameraSettingsWindow.h b/src/gui/CameraSettingsWindow.h new file mode 100644 index 00000000..86014d65 --- /dev/null +++ b/src/gui/CameraSettingsWindow.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include +#include + +class CameraSettingsWindow : public wxDialog +{ + wxChoice* m_cameraChoice; + wxButton* m_refreshButton; + wxWindow* m_imageWindow; + wxBitmap m_imageBitmap; + wxTimer m_imageUpdateTimer; + std::vector m_imageBuffer; + public: + explicit CameraSettingsWindow(wxWindow* parent); + void OnSelectCameraChoice(wxCommandEvent&); + void OnRefreshPressed(wxCommandEvent&); + void UpdateImage(const wxTimerEvent&); + void OnClose(wxCloseEvent& event); +}; + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 48bdd7d7..16811faa 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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, @@ -186,6 +188,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) @@ -921,6 +924,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")); From 8042e1c14ca89d5f6e140199c9fe6f563a91db85 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 03:10:46 +0100 Subject: [PATCH 19/37] macOS compile error fix attempt --- src/camera/CameraManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 4dfa52c2..e756d288 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -62,9 +62,9 @@ std::vector CameraManager::EnumerateDevices() const auto name = Cap_getDeviceName(m_ctx, deviceNo); if (name) - infos.emplace_back(uniqueId, fmt::format("{}: {}", deviceNo + 1, name)); + infos.emplace_back(std::string(uniqueId), fmt::format("{}: {}", deviceNo + 1, name)); else - infos.emplace_back(uniqueId, fmt::format("{}: Unknown", deviceNo + 1)); + infos.emplace_back(std::string(uniqueId), fmt::format("{}: Unknown", deviceNo + 1)); } return infos; } From c74891a4fb2b2b7b7486862f31538ee23de06cab Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 04:07:52 +0100 Subject: [PATCH 20/37] macOS compile error fix attempt #2 also ubuntu --- src/camera/CameraManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index e756d288..47f2bb5f 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -62,9 +62,9 @@ std::vector CameraManager::EnumerateDevices() const auto name = Cap_getDeviceName(m_ctx, deviceNo); if (name) - infos.emplace_back(std::string(uniqueId), fmt::format("{}: {}", deviceNo + 1, name)); + infos.emplace_back(DeviceInfo{uniqueId, fmt::format("{}: {}", deviceNo + 1, name)}); else - infos.emplace_back(std::string(uniqueId), fmt::format("{}: Unknown", deviceNo + 1)); + infos.emplace_back(DeviceInfo(uniqueId, fmt::format("{}: Unknown", deviceNo + 1))); } return infos; } From 5fe8d8d90a04783a966c1bf27272c99670d6d817 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 04:16:43 +0100 Subject: [PATCH 21/37] macOS compile error fix attempt no. 3 --- src/camera/CameraManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 47f2bb5f..b1479a24 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -62,9 +62,9 @@ std::vector CameraManager::EnumerateDevices() const auto name = Cap_getDeviceName(m_ctx, deviceNo); if (name) - infos.emplace_back(DeviceInfo{uniqueId, fmt::format("{}: {}", deviceNo + 1, name)}); + infos.emplace_back(DeviceInfo{std::string(uniqueId), fmt::format("{}: {}", deviceNo + 1, name)}); else - infos.emplace_back(DeviceInfo(uniqueId, fmt::format("{}: Unknown", deviceNo + 1))); + infos.emplace_back(DeviceInfo(std::string(uniqueId), fmt::format("{}: Unknown", deviceNo + 1))); } return infos; } From 3a88019cb226f1dca811dd24dccfc56a4bdad4b9 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 04:22:53 +0100 Subject: [PATCH 22/37] macOS compile error fix attempt no. 4 --- src/camera/CameraManager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index b1479a24..e68db52e 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -60,11 +60,13 @@ std::vector CameraManager::EnumerateDevices() { const auto uniqueId = Cap_getDeviceUniqueID(m_ctx, deviceNo); const auto name = Cap_getDeviceName(m_ctx, deviceNo); + DeviceInfo info; + info.uniqueId = uniqueId; if (name) - infos.emplace_back(DeviceInfo{std::string(uniqueId), fmt::format("{}: {}", deviceNo + 1, name)}); + info.name = fmt::format("{}: {}", deviceNo + 1, name); else - infos.emplace_back(DeviceInfo(std::string(uniqueId), fmt::format("{}: Unknown", deviceNo + 1))); + info.name = fmt::format("{}: Unknown", deviceNo + 1); } return infos; } From 9b878f82de20ca489bdefa6827f8319dd76d0526 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 04:23:25 +0100 Subject: [PATCH 23/37] add missing `push_back` --- src/camera/CameraManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index e68db52e..88bbf993 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -67,6 +67,7 @@ std::vector CameraManager::EnumerateDevices() info.name = fmt::format("{}: {}", deviceNo + 1, name); else info.name = fmt::format("{}: Unknown", deviceNo + 1); + infos.push_back(info); } return infos; } From 29feae80e4aa5a895a55a13a3cde7d919ba4596b Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 30 Mar 2025 22:05:01 +0100 Subject: [PATCH 24/37] Update openpnp branch --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 9ff86b40..55bf6afe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,4 +21,4 @@ [submodule "dependencies/openpnp-capture"] path = dependencies/openpnp-capture url = https://github.com/capitalistspz/openpnp-capture - branch = dev + branch = dev-no-explicit-shared From d00222be11c0455a6a5ec6b1f244f471e4ca9a50 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 31 Mar 2025 10:04:25 +0100 Subject: [PATCH 25/37] Change from singleton `CameraManager` to namespace. Update `openpnp-capture` dep for static linking. Fix camera selection dropdown width --- .gitmodules | 2 +- dependencies/openpnp-capture | 2 +- src/Cafe/OS/libs/camera/camera.cpp | 7 +- src/camera/CMakeLists.txt | 3 +- src/camera/CameraManager.cpp | 305 ++++++++++++++++------------- src/camera/CameraManager.h | 46 ++--- src/gui/CameraSettingsWindow.cpp | 18 +- 7 files changed, 194 insertions(+), 189 deletions(-) diff --git a/.gitmodules b/.gitmodules index 55bf6afe..9ff86b40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,4 +21,4 @@ [submodule "dependencies/openpnp-capture"] path = dependencies/openpnp-capture url = https://github.com/capitalistspz/openpnp-capture - branch = dev-no-explicit-shared + branch = dev diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index ba456ac7..3daf77d3 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit ba456ac7572c6e6db49da53d2f4985d65309e582 +Subproject commit 3daf77d3d4013238af4c97f0e3ca46c6d3666d1b diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 90f1b073..8c8e22da 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -146,7 +146,7 @@ namespace camera } else { - CameraManager::instance().FillNV12Buffer(surfaceBuffer.GetPtr()); + CameraManager::FillNV12Buffer(surfaceBuffer.GetPtr()); s_cameraEventData->data = surfaceBuffer; s_cameraEventData->errored = false; } @@ -189,6 +189,7 @@ namespace camera *error = CAM_STATUS_INVALID_ARG; return -1; } + CameraManager::Init(); cemu_assert_debug(initInfo->forceDisplay != CAMForceDisplay::DRC); cemu_assert_debug(initInfo->workMemorySize != 0); @@ -221,7 +222,7 @@ namespace camera return CAM_STATUS_UNINITIALIZED; s_instance.isOpen = false; } - CameraManager::instance().Close(); + CameraManager::Close(); return CAM_STATUS_SUCCESS; } @@ -234,7 +235,7 @@ namespace camera return CAM_STATUS_UNINITIALIZED; if (s_instance.isOpen) return CAM_STATUS_DEVICE_IN_USE; - CameraManager::instance().Open(); + CameraManager::Open(); s_instance.isOpen = true; coreinit::OSSignalEvent(s_cameraOpenEvent); s_instance.inTargetBuffers.Clear(); diff --git a/src/camera/CMakeLists.txt b/src/camera/CMakeLists.txt index 44342018..efe3f99f 100644 --- a/src/camera/CMakeLists.txt +++ b/src/camera/CMakeLists.txt @@ -8,7 +8,8 @@ add_library(CemuCamera set_property(TARGET CemuCamera PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuCamera PUBLIC "../") -target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil PUBLIC openpnp-capture) +target_link_libraries(CemuCamera PRIVATE CemuCommon CemuUtil openpnp-capture) + if (ENABLE_WXWIDGETS) target_link_libraries(CemuCamera PRIVATE wx::base) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 88bbf993..33698ff0 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -1,170 +1,195 @@ #include "CameraManager.h" -#include "Rgb2Nv12.h" + #include "config/CemuConfig.h" #include "util/helpers/helpers.h" +#include "Rgb2Nv12.h" + +#include +#include +#include +#include + +#include constexpr unsigned CAMERA_WIDTH = 640; constexpr unsigned CAMERA_HEIGHT = 480; constexpr unsigned CAMERA_PITCH = 768; -CameraManager::CameraManager() - : m_ctx(Cap_createContext()), - m_rgbBuffer(CAMERA_WIDTH * CAMERA_HEIGHT * 3), - m_nv12Buffer(CAMERA_PITCH * CAMERA_HEIGHT * 3 / 2), - m_refCount(0), m_capturing(false), m_running(true) +namespace CameraManager { - m_captureThread = std::thread(&CameraManager::CaptureWorker, this); + std::mutex s_mutex; + bool s_initialized = false; + CapContext s_ctx; + std::optional s_device; + std::optional s_stream; + std::array s_rgbBuffer; + std::array s_nv12Buffer; + int s_refCount = 0; + std::thread s_captureThread; + std::atomic_bool s_capturing = false; + std::atomic_bool s_running = false; - const auto uniqueId = GetConfig().camera_id.GetValue(); - if (!uniqueId.empty()) + std::optional FindCorrectFormat() { - const auto devices = EnumerateDevices(); - for (CapDeviceID deviceId = 0; deviceId < devices.size(); ++deviceId) + const auto formatCount = Cap_getNumFormats(s_ctx, *s_device); + for (int32_t formatId = 0; formatId < formatCount; ++formatId) { - if (devices[deviceId].uniqueId == uniqueId) + 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) { - m_device = deviceId; - return; + 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(); -} -CameraManager::~CameraManager() -{ - m_running = false; - CloseStream(); - Cap_releaseContext(m_ctx); -} - -void CameraManager::SetDevice(uint32 deviceNo) -{ - std::scoped_lock lock(m_mutex); - CloseStream(); - if (deviceNo == DEVICE_NONE) - { - m_device = std::nullopt; ResetBuffers(); - return; } - m_device = deviceNo; - if (m_refCount != 0) - OpenStream(); -} -std::vector CameraManager::EnumerateDevices() -{ - std::scoped_lock lock(m_mutex); - std::vector infos; - const auto deviceCount = Cap_getDeviceCount(m_ctx); - for (CapDeviceID deviceNo = 0; deviceNo < deviceCount; ++deviceNo) + void Deinit() { - const auto uniqueId = Cap_getDeviceUniqueID(m_ctx, deviceNo); - const auto name = Cap_getDeviceName(m_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); + CloseStream(); + Cap_releaseContext(s_ctx); + s_captureThread.join(); + s_initialized = false; } - return infos; -} -void CameraManager::SaveDevice() -{ - std::scoped_lock lock(m_mutex); - if (m_device) - GetConfig().camera_id = Cap_getDeviceUniqueID(m_ctx, *m_device); - else - GetConfig().camera_id = ""; -} -void CameraManager::Open() -{ - std::scoped_lock lock(m_mutex); - if (m_device && m_refCount == 0) + void FillNV12Buffer(uint8* nv12Buffer) { - OpenStream(); + std::scoped_lock lock(s_mutex); + std::ranges::copy(s_nv12Buffer, nv12Buffer); } - m_refCount += 1; -} -void CameraManager::Close() -{ - std::scoped_lock lock(m_mutex); - if (m_refCount == 0) - return; - m_refCount -= 1; - if (m_refCount != 0) - return; - CloseStream(); -} -std::optional CameraManager::FindCorrectFormat() -{ - const auto formatCount = Cap_getNumFormats(m_ctx, *m_device); - for (CapFormatID formatId = 0; formatId < formatCount; ++formatId) + void FillRGBBuffer(uint8* rgbBuffer) { - CapFormatInfo formatInfo; - if (Cap_getFormatInfo(m_ctx, *m_device, formatId, &formatInfo) != CAPRESULT_OK) - continue; - if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT) - return formatId; + std::scoped_lock lock(s_mutex); + std::ranges::copy(s_rgbBuffer, rgbBuffer); } - return std::nullopt; -} -void CameraManager::ResetBuffers() -{ - std::ranges::fill(m_rgbBuffer, 0); - std::ranges::fill_n(m_nv12Buffer.begin(), CAMERA_WIDTH * CAMERA_PITCH, 16); - std::ranges::fill_n(m_nv12Buffer.begin() + CAMERA_WIDTH * CAMERA_PITCH, (CAMERA_WIDTH / 2), 128); -} - -void CameraManager::FillNV12Buffer(uint8* nv12Buffer) const -{ - std::scoped_lock lock(m_mutex); - std::ranges::copy(m_nv12Buffer, nv12Buffer); -} - -void CameraManager::FillRGBBuffer(uint8* rgbBuffer) const -{ - std::scoped_lock lock(m_mutex); - std::ranges::copy(m_rgbBuffer, rgbBuffer); -} -void CameraManager::CaptureWorker() -{ - SetThreadName("CameraManager"); - while (m_running) + void SetDevice(uint32 deviceNo) { - while (m_capturing) + std::scoped_lock lock(s_mutex); + CloseStream(); + if (deviceNo == DEVICE_NONE) { - m_mutex.lock(); - if (m_stream && Cap_hasNewFrame(m_ctx, *m_stream) && - Cap_captureFrame(m_ctx, *m_stream, m_rgbBuffer.data(), m_rgbBuffer.size()) == CAPRESULT_OK) - Rgb2Nv12(m_rgbBuffer.data(), CAMERA_WIDTH, CAMERA_HEIGHT, m_nv12Buffer.data(), CAMERA_PITCH); - m_mutex.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + s_device = std::nullopt; + ResetBuffers(); + return; } - std::this_thread::sleep_for(std::chrono::seconds(1)); - std::this_thread::yield(); + s_device = deviceNo; + if (s_refCount != 0) + OpenStream(); } -} -void CameraManager::OpenStream() -{ - const auto formatId = FindCorrectFormat(); - if (!formatId) - return; - const auto stream = Cap_openStream(m_ctx, *m_device, *formatId); - if (stream == -1) - return; - m_capturing = true; - m_stream = stream; -} -void CameraManager::CloseStream() -{ - m_capturing = false; - if (m_stream) + void Open() { - Cap_closeStream(m_ctx, *m_stream); - m_stream = std::nullopt; + std::scoped_lock lock(s_mutex); + if (s_device && s_refCount == 0) + { + OpenStream(); + } + s_refCount += 1; } -} \ No newline at end of file + void Close() + { + std::scoped_lock lock(s_mutex); + if (s_refCount == 0) + return; + s_refCount -= 1; + if (s_refCount != 0) + return; + CloseStream(); + } + std::vector EnumerateDevices() + { + std::scoped_lock lock(s_mutex); + std::vector 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 \ No newline at end of file diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index e8e0c971..f372e86c 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -1,46 +1,24 @@ #pragma once -#include -#include -#include -#include -#include "util/helpers/Singleton.h" +#include -class CameraManager : public Singleton +namespace CameraManager { - CapContext m_ctx; - std::optional m_device; - std::optional m_stream; - std::vector m_rgbBuffer; - std::vector m_nv12Buffer; - int m_refCount; - std::thread m_captureThread; - std::atomic_bool m_capturing; - std::atomic_bool m_running; - mutable std::recursive_mutex m_mutex; - - public: - constexpr static uint32 DEVICE_NONE = std::numeric_limits::max(); struct DeviceInfo { std::string uniqueId; std::string name; }; - CameraManager(); - ~CameraManager(); + constexpr static uint32 DEVICE_NONE = std::numeric_limits::max(); + + void Init(); + void Deinit(); + void Open(); + void Close(); + + void FillNV12Buffer(uint8* nv12Buffer); + void FillRGBBuffer(uint8* rgbBuffer); void SetDevice(uint32 deviceNo); std::vector EnumerateDevices(); void SaveDevice(); - - void Open(); - void Close(); - void FillNV12Buffer(uint8* nv12Buffer) const; - void FillRGBBuffer(uint8* rgbBuffer) const; - - private: - std::optional FindCorrectFormat(); - void ResetBuffers(); - void CaptureWorker(); - void OpenStream(); - void CloseStream(); -}; +} // namespace CameraManager diff --git a/src/gui/CameraSettingsWindow.cpp b/src/gui/CameraSettingsWindow.cpp index e73d9211..4014513f 100644 --- a/src/gui/CameraSettingsWindow.cpp +++ b/src/gui/CameraSettingsWindow.cpp @@ -20,8 +20,7 @@ CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent) { auto* topSizer = new wxBoxSizer(wxHORIZONTAL); { - wxString choices[] = {_("None")}; - m_cameraChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, {300, -1}, 1, choices); + 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("⟳")); @@ -38,7 +37,8 @@ CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent) rootSizer->Add(m_imageWindow, wxEXPAND); } SetSizerAndFit(rootSizer); - CameraManager::instance().Open(); + 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); @@ -49,14 +49,14 @@ void CameraSettingsWindow::OnSelectCameraChoice(wxCommandEvent&) if (selection < 0) return; if (selection == 0) - CameraManager::instance().SetDevice(CameraManager::DEVICE_NONE); + CameraManager::SetDevice(CameraManager::DEVICE_NONE); else - CameraManager::instance().SetDevice(selection - 1); + CameraManager::SetDevice(selection - 1); } void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&) { wxArrayString choices = {_("None")}; - for (const auto& entry : CameraManager::instance().EnumerateDevices()) + for (const auto& entry : CameraManager::EnumerateDevices()) { choices.push_back(entry.name); } @@ -66,7 +66,7 @@ void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&) } void CameraSettingsWindow::UpdateImage(const wxTimerEvent&) { - CameraManager::instance().FillRGBBuffer(m_imageBuffer.data()); + CameraManager::FillRGBBuffer(m_imageBuffer.data()); wxNativePixelData data{m_imageBitmap}; if (!data) @@ -92,7 +92,7 @@ void CameraSettingsWindow::UpdateImage(const wxTimerEvent&) } void CameraSettingsWindow::OnClose(wxCloseEvent& event) { - CameraManager::instance().Close(); - CameraManager::instance().SaveDevice(); + CameraManager::Close(); + CameraManager::SaveDevice(); event.Skip(); } From 307af1297bfebb179508a28ef1577fd5afe70fae Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sat, 17 May 2025 21:47:31 +0100 Subject: [PATCH 26/37] Set init var --- src/camera/CameraManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 33698ff0..581e6d80 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -93,9 +93,11 @@ namespace CameraManager std::scoped_lock lock(s_mutex); if (s_initialized) return; + s_initialized = true; s_mutex.unlock(); s_running = true; s_captureThread = std::thread(&CaptureWorker); + s_ctx = Cap_createContext(); const auto uniqueId = GetConfig().camera_id.GetValue(); From 2a65ae60f6215baaf02ce338690e2e9e56a7029c Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sat, 17 May 2025 21:57:34 +0100 Subject: [PATCH 27/37] Update `openpnp-capture` (now statically linkable) --- CMakeLists.txt | 3 ++- dependencies/openpnp-capture | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 929259fd..5974cfe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,8 +222,9 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) +set(BUILD_SHARED_LIBS OFF) add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL SYSTEM) -set_property(TARGET openpnp-capture PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(BUILD_SHARED_LIBS "") find_package(ZArchive) if (NOT ZArchive_FOUND) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index 3daf77d3..f8759961 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit 3daf77d3d4013238af4c97f0e3ca46c6d3666d1b +Subproject commit f8759961c52228e4c28487ab94bbb7efbe714370 From c93ddb1973466a793f8078faf68d7dc7c9653d9c Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sat, 17 May 2025 22:08:56 +0100 Subject: [PATCH 28/37] Update `openpnp-capture` --- dependencies/openpnp-capture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index f8759961..eb8926c8 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit f8759961c52228e4c28487ab94bbb7efbe714370 +Subproject commit eb8926c87ecfb49a321adf183de2c8176cfaa374 From a1c123233bab78e810748e658d151fe3b0d9bcf5 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 00:28:21 +0100 Subject: [PATCH 29/37] Add `libjpeg-turbo` as a dependency on Linux because `openpnp-capture` needs it --- .github/workflows/build.yml | 4 ++-- BUILD.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7cbc202..88b2d1a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev + sudo apt install -y clang-15 cmake freeglut3-dev libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libturbojpeg0-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -96,7 +96,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev + sudo apt install -y appstream clang-15 cmake freeglut3-dev libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libturbojpeg0-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build - name: "Build AppImage" run: | diff --git a/BUILD.md b/BUILD.md index 31c26531..0a746a21 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required ### Dependencies #### For Arch and derivatives: -`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` +`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libjpeg-turbo libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` #### For Debian, Ubuntu and derivatives: -`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` +`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libturbojpeg0-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static` +`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libjpeg-turbo-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static` ### Build Cemu From 9a5b77c3db09fea3e3c3ba840aafb3149f1d40a4 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 03:45:52 +0100 Subject: [PATCH 30/37] Update openpnp-capture (function import/export changes) --- dependencies/openpnp-capture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index eb8926c8..79603f9f 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit eb8926c87ecfb49a321adf183de2c8176cfaa374 +Subproject commit 79603f9f0cafaf5692197b09c0aa2adf65d76e59 From ca292922a36f3c9721cfefc9da87723abe8053f6 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 04:01:44 +0100 Subject: [PATCH 31/37] Update openpnp-capture (msvc rt changes) --- dependencies/openpnp-capture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index 79603f9f..4ff7e5a2 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit 79603f9f0cafaf5692197b09c0aa2adf65d76e59 +Subproject commit 4ff7e5a2e67b39abad3a24f5a845ba5da858ca11 From 2d09952ef65c4caf2e310a2d9b0dad64faec7195 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 05:22:00 +0100 Subject: [PATCH 32/37] Update openpnp-capture (msvc rt changes for real) --- dependencies/openpnp-capture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/openpnp-capture b/dependencies/openpnp-capture index 4ff7e5a2..fd9d6fac 160000 --- a/dependencies/openpnp-capture +++ b/dependencies/openpnp-capture @@ -1 +1 @@ -Subproject commit 4ff7e5a2e67b39abad3a24f5a845ba5da858ca11 +Subproject commit fd9d6fac12d6258f07c788abfe543e4ba4323bf9 From d52800b7cd68252c5df4049d9cdb0bd085edc526 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 16:39:52 +0100 Subject: [PATCH 33/37] Solve some locking issues --- src/camera/CameraManager.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 581e6d80..1df42f10 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -90,15 +90,17 @@ namespace CameraManager void Init() { - std::scoped_lock lock(s_mutex); - if (s_initialized) - return; - s_initialized = true; - s_mutex.unlock(); - s_running = true; + { + std::scoped_lock lock(s_mutex); + if (s_initialized) + return; + s_initialized = true; + s_running = true; + } + s_ctx = Cap_createContext(); + s_captureThread = std::thread(&CaptureWorker); - s_ctx = Cap_createContext(); const auto uniqueId = GetConfig().camera_id.GetValue(); if (!uniqueId.empty()) From fd49f0e33492e5965671ecd1cfd2b7c8c07b5e27 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 May 2025 20:40:17 +0100 Subject: [PATCH 34/37] Make camera selection persistent and add logging --- src/Cemu/Logging/CemuLogging.h | 1 + src/camera/CameraManager.cpp | 56 ++++++++++++++++++++++++-------- src/camera/CameraManager.h | 1 + src/config/CemuConfig.cpp | 2 +- src/gui/CameraSettingsWindow.cpp | 11 +++---- src/gui/CameraSettingsWindow.h | 3 +- src/gui/MainWindow.cpp | 1 + 7 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index d729d364..c0debfb8 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -14,6 +14,7 @@ enum class LogType : sint32 UnsupportedAPI = 2, SoundAPI = 4, // any audio related API InputAPI = 5, // any input related API + CameraAPI = 27, Socket = 6, Save = 7, H264 = 9, diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 1df42f10..28fbf6cc 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -18,7 +18,6 @@ constexpr unsigned CAMERA_PITCH = 768; namespace CameraManager { std::mutex s_mutex; - bool s_initialized = false; CapContext s_ctx; std::optional s_device; std::optional s_stream; @@ -29,17 +28,38 @@ namespace CameraManager std::atomic_bool s_capturing = false; std::atomic_bool s_running = false; + std::string FourCC(uint32le value) + { + return { + static_cast((value >> 0) & 0xFF), + static_cast((value >> 8) & 0xFF), + static_cast((value >> 16) & 0xFF), + static_cast((value >> 24) & 0xFF)}; + } + + void CaptureLogFunction(uint32_t level, const char* string) + { + cemuLog_log(LogType::CameraAPI, "OpenPNPCapture: {}: {}", level, string); + } + std::optional FindCorrectFormat() { - const auto formatCount = Cap_getNumFormats(s_ctx, *s_device); + const auto device = *s_device; + cemuLog_log(LogType::CameraAPI, "Video capture device '{}' available formats:", Cap_getDeviceName(s_ctx, device)); + const auto formatCount = Cap_getNumFormats(s_ctx, device); for (int32_t formatId = 0; formatId < formatCount; ++formatId) { CapFormatInfo formatInfo; - if (Cap_getFormatInfo(s_ctx, *s_device, formatId, &formatInfo) != CAPRESULT_OK) + if (Cap_getFormatInfo(s_ctx, device, formatId, &formatInfo) != CAPRESULT_OK) continue; + cemuLog_log(LogType::CameraAPI, "{}: {} {}x{} @ {} fps, {} bpp", formatId, FourCC(formatInfo.fourcc), formatInfo.width, formatInfo.height, formatInfo.fps, formatInfo.bpp); if (formatInfo.width == CAMERA_WIDTH && formatInfo.height == CAMERA_HEIGHT) + { + cemuLog_log(LogType::CameraAPI, "Selected video format {}", formatId); return formatId; + } } + cemuLog_log(LogType::CameraAPI, "Failed to find suitable video format"); return std::nullopt; } @@ -92,16 +112,16 @@ namespace CameraManager { { std::scoped_lock lock(s_mutex); - if (s_initialized) + if (s_running) return; - s_initialized = true; s_running = true; + s_ctx = Cap_createContext(); + Cap_setLogLevel(4); + Cap_installCustomLogFunction(CaptureLogFunction); } - s_ctx = Cap_createContext(); s_captureThread = std::thread(&CaptureWorker); - const auto uniqueId = GetConfig().camera_id.GetValue(); if (!uniqueId.empty()) { @@ -121,8 +141,8 @@ namespace CameraManager { CloseStream(); Cap_releaseContext(s_ctx); + s_running = false; s_captureThread.join(); - s_initialized = false; } void FillNV12Buffer(uint8* nv12Buffer) { @@ -173,6 +193,7 @@ namespace CameraManager std::scoped_lock lock(s_mutex); std::vector infos; const auto deviceCount = Cap_getDeviceCount(s_ctx); + cemuLog_log(LogType::CameraAPI, "Available video capture devices:"); for (CapDeviceID deviceNo = 0; deviceNo < deviceCount; ++deviceNo) { const auto uniqueId = Cap_getDeviceUniqueID(s_ctx, deviceNo); @@ -181,19 +202,26 @@ namespace CameraManager info.uniqueId = uniqueId; if (name) - info.name = fmt::format("{}: {}", deviceNo + 1, name); + info.name = fmt::format("{}: {}", deviceNo, name); else - info.name = fmt::format("{}: Unknown", deviceNo + 1); + info.name = fmt::format("{}: Unknown", deviceNo); infos.push_back(info); + cemuLog_log(LogType::CameraAPI, "{}", info.name); } + if (infos.empty()) + cemuLog_log(LogType::CameraAPI, "No available video capture devices"); 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 = ""; + auto& config = GetConfig(); + const auto cameraId = s_device ? Cap_getDeviceUniqueID(s_ctx, *s_device) : ""; + config.camera_id = cameraId; + } + + std::optional GetCurrentDevice() + { + return s_device; } } // namespace CameraManager \ No newline at end of file diff --git a/src/camera/CameraManager.h b/src/camera/CameraManager.h index f372e86c..28e751a0 100644 --- a/src/camera/CameraManager.h +++ b/src/camera/CameraManager.h @@ -21,4 +21,5 @@ namespace CameraManager void SetDevice(uint32 deviceNo); std::vector EnumerateDevices(); void SaveDevice(); + std::optional GetCurrentDevice(); } // namespace CameraManager diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 95ef42b2..23256370 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -344,7 +344,7 @@ void CemuConfig::Load(XMLConfigParser& parser) dsu_client.port = dsuc.get_attribute("port", dsu_client.port); auto camera = parser.get("Camera"); - camera_id = camera.get_attribute("Id", ""); + camera_id = camera.get("Id", ""); // emulatedusbdevices auto usbdevices = parser.get("EmulatedUsbDevices"); diff --git a/src/gui/CameraSettingsWindow.cpp b/src/gui/CameraSettingsWindow.cpp index 4014513f..24cd96db 100644 --- a/src/gui/CameraSettingsWindow.cpp +++ b/src/gui/CameraSettingsWindow.cpp @@ -15,7 +15,8 @@ CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent) m_imageBitmap(CAMERA_WIDTH, CAMERA_HEIGHT, 24), m_imageBuffer(CAMERA_WIDTH * CAMERA_HEIGHT * 3) { - + CameraManager::Init(); + CameraManager::Open(); auto* rootSizer = new wxBoxSizer(wxVERTICAL); { auto* topSizer = new wxBoxSizer(wxHORIZONTAL); @@ -37,8 +38,6 @@ CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent) 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); @@ -61,8 +60,8 @@ void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&) choices.push_back(entry.name); } m_cameraChoice->Set(choices); - wxArrayString str; - + if (auto currentDevice = CameraManager::GetCurrentDevice()) + m_cameraChoice->SetSelection(*currentDevice + 1); } void CameraSettingsWindow::UpdateImage(const wxTimerEvent&) { @@ -95,4 +94,4 @@ void CameraSettingsWindow::OnClose(wxCloseEvent& event) CameraManager::Close(); CameraManager::SaveDevice(); event.Skip(); -} +} \ No newline at end of file diff --git a/src/gui/CameraSettingsWindow.h b/src/gui/CameraSettingsWindow.h index 86014d65..1a6dc6df 100644 --- a/src/gui/CameraSettingsWindow.h +++ b/src/gui/CameraSettingsWindow.h @@ -18,5 +18,4 @@ class CameraSettingsWindow : public wxDialog void OnRefreshPressed(wxCommandEvent&); void UpdateImage(const wxTimerEvent&); void OnClose(wxCloseEvent& event); -}; - +}; \ No newline at end of file diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 3bf0f6a4..92ac331a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2242,6 +2242,7 @@ void MainWindow::RecreateMenu() logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CameraAPI), _("Camera API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CameraAPI)); debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging")); debugLoggingMenu->AppendSeparator(); From 9ee33059afc0f94151acfbd716186866ccb3577c Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 19 May 2025 15:42:04 +0100 Subject: [PATCH 35/37] Make output black when no camera is selected --- src/camera/CameraManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/camera/CameraManager.cpp b/src/camera/CameraManager.cpp index 28fbf6cc..9f40e190 100644 --- a/src/camera/CameraManager.cpp +++ b/src/camera/CameraManager.cpp @@ -104,8 +104,9 @@ namespace CameraManager 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); + constexpr auto pixCount = CAMERA_HEIGHT * CAMERA_PITCH; + std::ranges::fill_n(s_nv12Buffer.begin(), pixCount, 16); + std::ranges::fill_n(s_nv12Buffer.begin() + pixCount, (pixCount / 2), 128); } void Init() From 45a893caf96a85c4a844bcd3bbcef6f44c5b60d2 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 20 May 2025 19:40:03 +0100 Subject: [PATCH 36/37] `camera.rpl` logs to `CameraAPI` logtype --- src/Cafe/OS/libs/camera/camera.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 8c8e22da..e19dc3ff 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -280,12 +280,12 @@ namespace camera void load() { reset(); - 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); + cafeExportRegister("camera", CAMGetMemReq, LogType::CameraAPI); + cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::CameraAPI); + cafeExportRegister("camera", CAMInit, LogType::CameraAPI); + cafeExportRegister("camera", CAMExit, LogType::CameraAPI); + cafeExportRegister("camera", CAMOpen, LogType::CameraAPI); + cafeExportRegister("camera", CAMClose, LogType::CameraAPI); + cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::CameraAPI); } } // namespace camera From 5bab8f06066f90baa8692095f9889ac650b492d1 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Jun 2025 16:08:18 +0100 Subject: [PATCH 37/37] Remove the merge conflict markers --- CMakeLists.txt | 5 +---- src/gui/MainWindow.cpp | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c0a6af56..fb22c0bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,20 +222,17 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) -<<<<<<< camera set(BUILD_SHARED_LIBS OFF) add_subdirectory("dependencies/openpnp-capture" EXCLUDE_FROM_ALL SYSTEM) set(BUILD_SHARED_LIBS "") -if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") -======= if (CMAKE_OSX_ARCHITECTURES) set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) else() set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) endif() + if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") ->>>>>>> main add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL) endif() diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index de5104f4..be6fbbe1 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -92,11 +92,8 @@ enum MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MAINFRAME_MENU_ID_OPTIONS_AUDIO, MAINFRAME_MENU_ID_OPTIONS_INPUT, -<<<<<<< camera MAINFRAME_MENU_ID_OPTIONS_CAMERA, -======= MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, ->>>>>>> main // options -> account MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, @@ -194,11 +191,8 @@ 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) -<<<<<<< camera EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_CAMERA, MainWindow::OnOptionsInput) -======= EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, MainWindow::OnOptionsInput) ->>>>>>> main // tools menu EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)