Switch from openpnp-capture to SDL_Camera and make camera support build optional

Had to change because `openpnp-capture` converts all output to RGB (performance problem) doesn't re-enumerate after initialization (usability problem).
This commit is contained in:
capitalistspz
2026-07-21 12:20:58 +01:00
parent d26010ca6b
commit 529e7d5cbb
10 changed files with 366 additions and 342 deletions

View File

@@ -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 |

View File

@@ -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)

View File

@@ -7,6 +7,12 @@
#include <wx/dcbuffer.h>
#include <wx/rawbmp.h>
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<wxCameraDeviceUniqueId*>(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<int>(*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&)

View File

@@ -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)

View File

@@ -1,256 +0,0 @@
#include "CameraManager.h"
#include "config/CemuConfig.h"
#include "util/helpers/helpers.h"
#include "Rgb2Nv12.h"
#include <algorithm>
#include <mutex>
#include <optional>
#include <thread>
#include <openpnp-capture.h>
namespace CameraManager
{
static std::mutex s_mutex;
static std::mutex s_bufferMutex;
static CapContext s_ctx;
static std::optional<CapDeviceID> s_device;
static std::optional<CapStream> 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<char>((value >> 0) & 0xFF),
static_cast<char>((value >> 8) & 0xFF),
static_cast<char>((value >> 16) & 0xFF),
static_cast<char>((value >> 24) & 0xFF)
};
}
static void CaptureLogFunction(uint32_t level, const char* string)
{
cemuLog_log(LogType::InputAPI, "openpnp-capture: {}: {}", level, string);
}
static std::optional<CapFormatID> 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<DeviceInfo> InternalEnumerateDevices()
{
std::vector<DeviceInfo> 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<uint8, CAMERA_NV12_BUFFER_SIZE> nv12Buffer)
{
std::scoped_lock lock(s_bufferMutex);
std::ranges::copy_n(s_nv12BufferOut, CAMERA_NV12_BUFFER_SIZE, nv12Buffer.data());
}
void FillRGBBuffer(std::span<uint8, CAMERA_RGB_BUFFER_SIZE> 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<DeviceInfo> 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<uint32> GetCurrentDevice()
{
return s_device;
}
} // namespace CameraManager

View File

@@ -21,9 +21,9 @@ namespace CameraManager
void FillNV12Buffer(std::span<uint8, CAMERA_NV12_BUFFER_SIZE> nv12Buffer);
void FillRGBBuffer(std::span<uint8, CAMERA_RGB_BUFFER_SIZE> rgbBuffer);
void SetDevice(uint32 deviceNo);
void SetDevice(std::string_view deviceId);
void ResetDevice();
std::vector<DeviceInfo> EnumerateDevices();
void SaveDevice();
std::optional<uint32> GetCurrentDevice();
std::optional<std::string> GetCurrentDevice();
} // namespace CameraManager

View File

@@ -0,0 +1,46 @@
#include "CameraManager.h"
namespace CameraManager
{
void FillNV12Buffer(std::span<uint8, CAMERA_NV12_BUFFER_SIZE> 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<uint8, CAMERA_RGB_BUFFER_SIZE> rgbBuffer)
{
std::ranges::fill(rgbBuffer, 0);
}
void SetDevice(std::string_view deviceId)
{
}
void ResetDevice()
{
}
void Open()
{
}
void Close()
{
}
std::vector<DeviceInfo> EnumerateDevices()
{
return {};
}
void SaveDevice()
{
}
std::optional<std::string> GetCurrentDevice()
{
return std::nullopt;
}
} // namespace CameraManager

View File

@@ -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<const glm::u8vec3*>(topLine);
auto* botIn = reinterpret_cast<const glm::u8vec3*>(bottomLine);
for (auto x = 0u; x < imageWidth; x += 2)
{
const glm::mat4x3 rgbMatrix{
topIn[x],
topIn[x + 1],
botIn[x],
botIn[x + 1],
};
const auto result = 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]);
}
}

View File

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

View File

@@ -0,0 +1,278 @@
#include "CameraManager.h"
#include "config/CemuConfig.h"
#include "util/helpers/helpers.h"
#include <algorithm>
#include <mutex>
#include <optional>
#include <thread>
#include <SDL3/SDL_camera.h>
#include <SDL3/SDL_init.h>
namespace CameraManager
{
namespace
{
struct InternalDeviceInfo
{
SDL_CameraID id;
std::string name;
};
std::mutex s_deviceMutex;
std::mutex s_bufferMutex;
std::optional<SDL_CameraID> s_deviceId;
SDL_Camera* s_camera;
std::unique_ptr<uint8[]> s_rgbBufferOut;
std::unique_ptr<uint8[]> 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<uint8[]>(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<const uint8*>(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<InternalDeviceInfo> InternalEnumerateDevices()
{
std::vector<InternalDeviceInfo> 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<uint8, CAMERA_NV12_BUFFER_SIZE> 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<uint8, CAMERA_RGB_BUFFER_SIZE> 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<DeviceInfo> EnumerateDevices()
{
std::scoped_lock lock(s_deviceMutex);
std::vector<DeviceInfo> 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<std::string> GetCurrentDevice()
{
if (s_deviceId)
return std::to_string(*s_deviceId);
return std::nullopt;
}
} // namespace CameraManager