diff --git a/BUILD.md b/BUILD.md index 6e1a2be9..276a5263 100644 --- a/BUILD.md +++ b/BUILD.md @@ -242,11 +242,12 @@ Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON - |--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------| | ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | | | CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | | +| ENABLE_CAMERA | | Enable camera support | ON | | | ENABLE_CUBEB | | Enable cubeb audio backend | ON | | | ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | | | ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | | | ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | | -| ENABLE_SDL | | Enable SDLController controller API | ON | | +| ENABLE_SDL | | Enable SDLController controller API/SDLCamera implementation | ON | | | ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | | | ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | | | ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required | diff --git a/CMakeLists.txt b/CMakeLists.txt index 38f41a3d..9ef32ace 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ endif() option(ENABLE_HIDAPI "Build with HIDAPI" ON) option(ENABLE_SDL "Enables the SDLController backend" ON) option(ENABLE_LIBUSB "Build with libusb support" ON) +option(ENABLE_CAMERA "Build with support for camera access" ON) if (ENABLE_LIBUSB) add_compile_definitions(HAS_LIBUSB) diff --git a/src/gui/wxgui/CameraSettingsWindow.cpp b/src/gui/wxgui/CameraSettingsWindow.cpp index a362dabc..33b33622 100644 --- a/src/gui/wxgui/CameraSettingsWindow.cpp +++ b/src/gui/wxgui/CameraSettingsWindow.cpp @@ -7,6 +7,12 @@ #include #include +struct wxCameraDeviceUniqueId : public wxClientData +{ + std::string id; + explicit wxCameraDeviceUniqueId(std::string id) : id(std::move(id)) {} +}; + CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Camera settings"), wxDefaultPosition), m_imageBitmap(CameraManager::CAMERA_WIDTH, CameraManager::CAMERA_HEIGHT, 24), @@ -55,21 +61,35 @@ void CameraSettingsWindow::OnSelectCameraChoice(wxCommandEvent&) if (selection == 0) CameraManager::ResetDevice(); else - CameraManager::SetDevice(selection - 1); + { + auto id = static_cast(m_cameraComboBox->GetClientData(selection))->id; + CameraManager::SetDevice(id); + } } void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&) { wxArrayString choices = {_("None")}; - for (const auto& entry : CameraManager::EnumerateDevices()) + const auto devices = CameraManager::EnumerateDevices(); + const auto currentDevice = CameraManager::GetCurrentDevice(); + auto currentIndex = 0; + for (auto i = 0; i < devices.size(); ++i) { - choices.push_back(fmt::format("{} ({})", entry.name, entry.uniqueId)); + auto& device = devices[i]; + choices.push_back(fmt::format("{} ({})", device.name, device.uniqueId)); + if (currentDevice && device.uniqueId == *currentDevice) + currentIndex = i + 1; } + m_cameraComboBox->Set(choices); - if (auto currentDevice = CameraManager::GetCurrentDevice()) - m_cameraComboBox->SetSelection(static_cast(*currentDevice) + 1); - else - m_cameraComboBox->SetSelection(0); + m_cameraComboBox->SetSelection(currentIndex); + // Client data can only be set when there are entries + for (auto i = 0; i < devices.size(); ++i) + { + auto& device = devices[i]; + const auto choiceIndex = i + 1; + m_cameraComboBox->SetClientData(choiceIndex, new wxCameraDeviceUniqueId{device.uniqueId}); + } } void CameraSettingsWindow::UpdateImage(const wxTimerEvent&) diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index fad146a2..6670069b 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -19,10 +19,7 @@ add_library(CemuInput api/Keyboard/KeyboardControllerProvider.cpp api/Keyboard/KeyboardController.cpp api/Keyboard/KeyboardController.h - camera/CameraManager.cpp camera/CameraManager.h - camera/Rgb2Nv12.cpp - camera/Rgb2Nv12.h emulated/ProController.cpp emulated/EmulatedController.h emulated/EmulatedController.cpp @@ -102,12 +99,21 @@ if(ENABLE_SDL) target_link_libraries(CemuInput PRIVATE SDL3::SDL3) endif() +if (NOT ENABLE_CAMERA) + target_sources(CemuInput PRIVATE + camera/DummyCameraManager.cpp + ) +elseif (ENABLE_SDL) + target_sources(CemuInput PRIVATE + camera/SDLCameraManager.cpp + ) +endif () + target_include_directories(CemuInput PUBLIC "../") target_link_libraries(CemuInput PRIVATE CemuCommon CemuGui - openpnp-capture ) if (ENABLE_BLUEZ) diff --git a/src/input/camera/CameraManager.cpp b/src/input/camera/CameraManager.cpp deleted file mode 100644 index ffe30530..00000000 --- a/src/input/camera/CameraManager.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "CameraManager.h" - -#include "config/CemuConfig.h" -#include "util/helpers/helpers.h" -#include "Rgb2Nv12.h" - -#include -#include -#include -#include - -#include - -namespace CameraManager -{ - static std::mutex s_mutex; - static std::mutex s_bufferMutex; - static CapContext s_ctx; - static std::optional s_device; - static std::optional s_stream; - static uint8_t* s_rgbBufferOut; - static uint8_t* s_nv12BufferOut; - static int s_refCount = 0; - static std::thread s_captureThread; - static std::atomic_bool s_capturing = false; - static std::atomic_bool s_running = false; - - static 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) - }; - } - - static void CaptureLogFunction(uint32_t level, const char* string) - { - cemuLog_log(LogType::InputAPI, "openpnp-capture: {}: {}", level, string); - } - - static std::optional FindCorrectFormat() - { - const auto device = *s_device; - cemuLog_log(LogType::InputAPI, "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, device, formatId, &formatInfo) != CAPRESULT_OK) - continue; - cemuLog_log(LogType::InputAPI, "{}: {} {}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::InputAPI, "Selected video format {}", formatId); - return formatId; - } - } - cemuLog_log(LogType::InputAPI, "Failed to find suitable video format"); - return std::nullopt; - } - - static void CaptureWorkerFunc() - { - SetThreadName("CameraManager"); - auto rgbBuffer = new uint8[CAMERA_RGB_BUFFER_SIZE]; - auto nv12Buffer = new uint8[CAMERA_NV12_BUFFER_SIZE]; - while (s_running) - { - while (s_capturing) - { - if (s_mutex.try_lock()) - { - if (s_stream && Cap_hasNewFrame(s_ctx, *s_stream) && - Cap_captureFrame(s_ctx, *s_stream, rgbBuffer, CAMERA_RGB_BUFFER_SIZE) == CAPRESULT_OK) - { - Rgb2Nv12(rgbBuffer, CAMERA_WIDTH, CAMERA_HEIGHT, nv12Buffer, CAMERA_PITCH); - std::scoped_lock lock(s_bufferMutex); - std::swap(s_rgbBufferOut, rgbBuffer); - std::swap(s_nv12BufferOut, nv12Buffer); - } - 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(); - } - delete[] rgbBuffer; - delete[] nv12Buffer; - } - - static 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; - } - - static void CloseStream() - { - s_capturing = false; - if (s_stream) - { - Cap_closeStream(s_ctx, *s_stream); - s_stream = std::nullopt; - } - } - - static void ResetBuffers() - { - std::scoped_lock lock(s_bufferMutex); - std::fill_n(s_rgbBufferOut, CAMERA_RGB_BUFFER_SIZE, 0); - constexpr static auto PIXEL_COUNT = CAMERA_HEIGHT * CAMERA_PITCH; - std::ranges::fill_n(s_nv12BufferOut, PIXEL_COUNT, 16); - std::ranges::fill_n(s_nv12BufferOut + PIXEL_COUNT, (PIXEL_COUNT / 2), 128); - } - - static std::vector InternalEnumerateDevices() - { - std::vector infos; - const auto deviceCount = Cap_getDeviceCount(s_ctx); - cemuLog_log(LogType::InputAPI, "Available video capture devices:"); - 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; - info.name = name ? name : ""; - infos.push_back(info); - cemuLog_log(LogType::InputAPI, "{}", info.name); - } - return infos; - } - - static void Init() - { - s_running = true; - s_ctx = Cap_createContext(); - Cap_setLogLevel(4); - Cap_installCustomLogFunction(CaptureLogFunction); - s_rgbBufferOut = new uint8[CAMERA_RGB_BUFFER_SIZE]; - s_nv12BufferOut = new uint8[CAMERA_NV12_BUFFER_SIZE]; - - s_captureThread = std::thread(&CaptureWorkerFunc); - - const auto deviceName = GetConfig().camera_id.GetValue(); - if (!deviceName.empty()) - { - const auto devices = InternalEnumerateDevices(); - for (CapDeviceID deviceId = 0; deviceId < devices.size(); ++deviceId) - { - if (devices[deviceId].name == deviceName) - { - s_device = deviceId; - return; - } - } - } - ResetBuffers(); - } - - static void Deinit() - { - CloseStream(); - Cap_releaseContext(s_ctx); - s_running = false; - s_captureThread.join(); - delete[] s_rgbBufferOut; - delete[] s_nv12BufferOut; - } - - void FillNV12Buffer(std::span nv12Buffer) - { - std::scoped_lock lock(s_bufferMutex); - std::ranges::copy_n(s_nv12BufferOut, CAMERA_NV12_BUFFER_SIZE, nv12Buffer.data()); - } - - void FillRGBBuffer(std::span rgbBuffer) - { - std::scoped_lock lock(s_bufferMutex); - std::ranges::copy_n(s_rgbBufferOut, CAMERA_RGB_BUFFER_SIZE, rgbBuffer.data()); - } - - void SetDevice(uint32 deviceNo) - { - std::scoped_lock lock(s_mutex); - CloseStream(); - s_device = deviceNo; - if (s_refCount != 0) - OpenStream(); - } - - void ResetDevice() - { - std::scoped_lock lock(s_mutex); - CloseStream(); - s_device = std::nullopt; - ResetBuffers(); - } - - void Open() - { - std::scoped_lock lock(s_mutex); - if (!s_running) - { - Init(); - } - if (s_device && s_refCount == 0) - { - OpenStream(); - } - s_refCount += 1; - } - - void Close() - { - std::scoped_lock lock(s_mutex); - if (s_refCount == 0) - return; - s_refCount -= 1; - if (s_refCount != 0) - return; - CloseStream(); - Deinit(); - } - - std::vector EnumerateDevices() - { - std::scoped_lock lock(s_mutex); - return InternalEnumerateDevices(); - } - - void SaveDevice() - { - std::scoped_lock lock(s_mutex); - const std::string cameraId = s_device ? Cap_getDeviceName(s_ctx, *s_device) : ""; - GetConfig().camera_id.SetValue(cameraId); - GetConfigHandle().Save(); - } - - std::optional GetCurrentDevice() - { - return s_device; - } -} // namespace CameraManager diff --git a/src/input/camera/CameraManager.h b/src/input/camera/CameraManager.h index 54eaaede..98d2db98 100644 --- a/src/input/camera/CameraManager.h +++ b/src/input/camera/CameraManager.h @@ -21,9 +21,9 @@ namespace CameraManager void FillNV12Buffer(std::span nv12Buffer); void FillRGBBuffer(std::span rgbBuffer); - void SetDevice(uint32 deviceNo); + void SetDevice(std::string_view deviceId); void ResetDevice(); std::vector EnumerateDevices(); void SaveDevice(); - std::optional GetCurrentDevice(); + std::optional GetCurrentDevice(); } // namespace CameraManager diff --git a/src/input/camera/DummyCameraManager.cpp b/src/input/camera/DummyCameraManager.cpp new file mode 100644 index 00000000..41d1017b --- /dev/null +++ b/src/input/camera/DummyCameraManager.cpp @@ -0,0 +1,46 @@ +#include "CameraManager.h" + +namespace CameraManager +{ + void FillNV12Buffer(std::span nv12Buffer) + { + constexpr static auto PIXEL_COUNT = CAMERA_HEIGHT * CAMERA_PITCH; + std::ranges::fill_n(nv12Buffer.data(), PIXEL_COUNT, 16); + std::ranges::fill_n(nv12Buffer.data() + PIXEL_COUNT, (PIXEL_COUNT / 2), 128); + } + + void FillRGBBuffer(std::span rgbBuffer) + { + std::ranges::fill(rgbBuffer, 0); + } + + void SetDevice(std::string_view deviceId) + { + } + + void ResetDevice() + { + } + + void Open() + { + } + + void Close() + { + } + + std::vector EnumerateDevices() + { + return {}; + } + + void SaveDevice() + { + } + + std::optional GetCurrentDevice() + { + return std::nullopt; + } +} // namespace CameraManager diff --git a/src/input/camera/Rgb2Nv12.cpp b/src/input/camera/Rgb2Nv12.cpp deleted file mode 100644 index 7b599309..00000000 --- a/src/input/camera/Rgb2Nv12.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Based on https://github.com/cohenrotem/Rgb2NV12 -#include "Rgb2Nv12.h" - -constexpr static glm::mat3x3 COEFFICIENT_MATRIX = -{ - +0.257f, -0.148f, 0.439f, - +0.504f, -0.291f, -0.368f, - +0.098f, +0.439f, -0.071f}; - -constexpr static glm::mat4x3 OFFSET_MATRIX = { - 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 = COEFFICIENT_MATRIX * rgbMatrix + OFFSET_MATRIX; - - topLineY[x + 0] = result[0].s; - topLineY[x + 1] = result[1].s; - bottomLineY[x + 0] = result[2].s; - bottomLineY[x + 1] = result[3].s; - - uv[x + 0] = (result[0].t + result[1].t + result[2].t + result[3].t) * 0.25f; - uv[x + 1] = (result[0].p + result[1].p + result[2].p + result[3].p) * 0.25f; - } -} - -void Rgb2Nv12(const uint8* rgbImage, - unsigned imageWidth, - unsigned imageHeight, - uint8* outNv12Image, - unsigned nv12Pitch) -{ - cemu_assert_debug(!((imageWidth | imageHeight) & 1)); - unsigned char* UV = outNv12Image + nv12Pitch * imageHeight; - - for (auto row = 0u; row < imageHeight; row += 2) - { - Rgb2Nv12TwoRows(&rgbImage[row * imageWidth * 3], - &rgbImage[(row + 1) * imageWidth * 3], - imageWidth, - &outNv12Image[row * nv12Pitch], - &outNv12Image[(row + 1) * nv12Pitch], - &UV[(row / 2) * nv12Pitch]); - } -} \ No newline at end of file diff --git a/src/input/camera/Rgb2Nv12.h b/src/input/camera/Rgb2Nv12.h deleted file mode 100644 index d0e01d01..00000000 --- a/src/input/camera/Rgb2Nv12.h +++ /dev/null @@ -1,7 +0,0 @@ -#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/input/camera/SDLCameraManager.cpp b/src/input/camera/SDLCameraManager.cpp new file mode 100644 index 00000000..7eec11f4 --- /dev/null +++ b/src/input/camera/SDLCameraManager.cpp @@ -0,0 +1,278 @@ +#include "CameraManager.h" + +#include "config/CemuConfig.h" +#include "util/helpers/helpers.h" + +#include +#include +#include +#include + +#include +#include + +namespace CameraManager +{ + namespace + { + struct InternalDeviceInfo + { + SDL_CameraID id; + std::string name; + }; + std::mutex s_deviceMutex; + std::mutex s_bufferMutex; + std::optional s_deviceId; + SDL_Camera* s_camera; + std::unique_ptr s_rgbBufferOut; + std::unique_ptr s_nv12BufferOut; + int s_refCount = 0; + std::thread s_captureThread; + std::atomic_bool s_capturing = false; + std::atomic_bool s_running = false; + } + + static void CaptureWorkerFunc() + { + SetThreadName("CameraManager"); + auto nv12Buffer = std::unique_ptr(new uint8[CAMERA_NV12_BUFFER_SIZE]); + while (s_running) + { + while (s_capturing) + { + if (auto deviceLock = std::unique_lock(s_deviceMutex, std::try_to_lock); deviceLock && s_camera) + { + if (auto frame = SDL_AcquireCameraFrame(s_camera, nullptr)) + { + if (frame->format != SDL_PIXELFORMAT_NV12) + return; + if (SDL_MUSTLOCK(frame)) + SDL_LockSurface(frame); + const auto byteRowCount = (frame->h * 3) >> 1; + for (auto row = 0; row < byteRowCount; ++row) + { + const auto lineIn = static_cast(frame->pixels) + frame->pitch * row; + const auto lineOut = nv12Buffer.get() + CAMERA_PITCH * row; + std::memcpy(lineOut, lineIn, frame->w); + } + if (SDL_MUSTLOCK(frame)) + SDL_UnlockSurface(frame); + std::scoped_lock lock(s_bufferMutex); + std::swap(s_nv12BufferOut, nv12Buffer); + SDL_ReleaseCameraFrame(s_camera, frame); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::this_thread::yield(); + } + } + + static void OpenStream() + { + SDL_CameraSpec cameraSpec; + cameraSpec.format = SDL_PIXELFORMAT_NV12; + cameraSpec.colorspace = SDL_COLORSPACE_BT601_LIMITED; + cameraSpec.framerate_numerator = 30; + cameraSpec.framerate_denominator = 1; + cameraSpec.width = CAMERA_WIDTH; + cameraSpec.height = CAMERA_HEIGHT; + const auto camera = SDL_OpenCamera(*s_deviceId, &cameraSpec); + if (camera == nullptr) + return; + + if (SDL_GetCameraFormat(camera, &cameraSpec) && cameraSpec.format != SDL_PIXELFORMAT_NV12) + { + cemuLog_log(LogType::Force, "Camera output format is NV12"); + SDL_CloseCamera(camera); + return; + } + s_capturing = true; + s_camera = camera; + } + + static void CloseStream() + { + s_capturing = false; + if (s_camera) + { + SDL_CloseCamera(s_camera); + s_camera = nullptr; + } + } + + static void ResetBuffers() + { + std::scoped_lock lock(s_bufferMutex); + std::fill_n(s_rgbBufferOut.get(), CAMERA_RGB_BUFFER_SIZE, 0); + constexpr static auto PIXEL_COUNT = CAMERA_HEIGHT * CAMERA_PITCH; + std::ranges::fill_n(s_nv12BufferOut.get(), PIXEL_COUNT, 16); + std::ranges::fill_n(s_nv12BufferOut.get() + PIXEL_COUNT, (PIXEL_COUNT / 2), 128); + } + + static std::vector InternalEnumerateDevices() + { + std::vector infos; + int deviceCount = 0; + auto devices = SDL_GetCameras(&deviceCount); + if (devices == nullptr) + { + cemuLog_log(LogType::Force, "{}", SDL_GetError()); + return {}; + } + cemuLog_log(LogType::InputAPI, "Available video capture devices:"); + for (auto cameraId : std::span(devices, deviceCount)) + { + const auto name = SDL_GetCameraName(cameraId); + infos.emplace_back(cameraId, name ? name : ""); + } + SDL_free(devices); + return infos; + } + + static void Init() + { + SDL_InitSubSystem(SDL_INIT_CAMERA); + s_running = true; + s_rgbBufferOut.reset(new uint8[CAMERA_RGB_BUFFER_SIZE]); + s_nv12BufferOut.reset(new uint8[CAMERA_NV12_BUFFER_SIZE]); + + s_captureThread = std::thread(&CaptureWorkerFunc); + + const auto deviceName = GetConfig().camera_id.GetValue(); + if (!deviceName.empty()) + { + for (auto device : InternalEnumerateDevices()) + { + if (device.name == deviceName) + { + s_deviceId = device.id; + } + } + } + ResetBuffers(); + } + + static void Deinit() + { + CloseStream(); + s_running = false; + s_captureThread.join(); + s_nv12BufferOut.reset(); + s_rgbBufferOut.reset(); + SDL_QuitSubSystem(SDL_INIT_CAMERA); + } + + void FillNV12Buffer(std::span nv12Buffer) + { + std::scoped_lock lock(s_bufferMutex); + std::ranges::copy_n(s_nv12BufferOut.get(), CAMERA_NV12_BUFFER_SIZE, nv12Buffer.data()); + } + + static uint8 ClampU8(int v) + { + v = (v > 255) ? 255 : v; + v = (v < 0) ? 0 : v; + return v; + } + + void FillRGBBuffer(std::span rgbBuffer) + { + std::scoped_lock lock(s_bufferMutex); + const auto* yPtr = s_nv12BufferOut.get(); + const auto* uvPtr = s_nv12BufferOut.get() + CAMERA_PITCH * CAMERA_HEIGHT; + auto* rgbPtr = rgbBuffer.data(); + for (auto row = 0; row < CAMERA_HEIGHT; ++row) + { + const auto yRow = yPtr + row * CAMERA_PITCH; + const auto uvRow = uvPtr + (row >> 1) * CAMERA_PITCH; + const auto rgbRow = rgbPtr + row * CAMERA_WIDTH * 3; + for (auto col = 0; col < CAMERA_WIDTH; ++col) + { + const auto _y = 19 * (yRow[col] - 16); + const auto uvCol = col >> 1; + const auto u = uvRow[(uvCol << 1) + 0]; + const auto v = uvRow[(uvCol << 1) + 1]; + rgbRow[col * 3 + 0] = ClampU8((_y + 26 * (v - 128)) >> 4); + rgbRow[col * 3 + 1] = ClampU8((_y - 13 * (v - 128) - 6 * (u - 128)) >> 4); + rgbRow[col * 3 + 2] = ClampU8((_y + 32 * (u - 128)) >> 4);; + } + } + } + + void SetDevice(std::string_view deviceId) + { + std::scoped_lock lock(s_deviceMutex); + CloseStream(); + SDL_CameraID id; + auto [it, ec] = std::from_chars(deviceId.begin(), deviceId.end(), id); + if (ec != std::errc{}) + return; + s_deviceId = id; + if (s_refCount != 0) + OpenStream(); + } + + void ResetDevice() + { + std::scoped_lock lock(s_deviceMutex); + CloseStream(); + s_deviceId = std::nullopt; + ResetBuffers(); + } + + + void Open() + { + std::scoped_lock lock(s_deviceMutex); + if (!s_running) + { + Init(); + } + if (s_deviceId && s_refCount == 0) + { + OpenStream(); + } + s_refCount += 1; + } + + void Close() + { + std::scoped_lock lock(s_deviceMutex); + if (s_refCount == 0) + return; + s_refCount -= 1; + if (s_refCount != 0) + return; + CloseStream(); + Deinit(); + } + + std::vector EnumerateDevices() + { + std::scoped_lock lock(s_deviceMutex); + std::vector deviceInfos; + for (const auto& [id, name] : InternalEnumerateDevices()) + { + deviceInfos.emplace_back(fmt::to_string(id), name); + } + return deviceInfos; + } + + void SaveDevice() + { + std::scoped_lock lock(s_deviceMutex); + const std::string cameraId = s_deviceId ? SDL_GetCameraName(*s_deviceId) : ""; + GetConfig().camera_id.SetValue(cameraId); + GetConfigHandle().Save(); + } + + std::optional GetCurrentDevice() + { + if (s_deviceId) + return std::to_string(*s_deviceId); + return std::nullopt; + } +} // namespace CameraManager