lcameraDev: Add session mgr lib for libcamera device binding

This commit is contained in:
2026-06-13 12:02:04 -04:00
parent cc7f4fcd9b
commit 46f767f232
21 changed files with 1363 additions and 64 deletions
+5 -3
View File
@@ -60,7 +60,7 @@ set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "ubuntu")
# Build dependencies (from builddeps file) # Build dependencies (from builddeps file)
# These are needed to build the package from source # These are needed to build the package from source
set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS
"build-essential, cmake (>= 3.16), libboost-all-dev, flex, bison, ocl-icd-opencl-dev, liburing-dev") "build-essential, cmake (>= 3.16), libboost-all-dev, flex, bison, ocl-icd-opencl-dev, liburing-dev, libcamera-dev")
# Runtime dependencies. # Runtime dependencies.
# Let dpkg-shlibdeps derive the actual ELF dependencies from the built # Let dpkg-shlibdeps derive the actual ELF dependencies from the built
@@ -69,14 +69,16 @@ set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS
# tree the generated binaries are not currently linked against Boost DSOs. # tree the generated binaries are not currently linked against Boost DSOs.
set(CPACK_DEBIAN_PACKAGE_DEPENDS "") set(CPACK_DEBIAN_PACKAGE_DEPENDS "")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6") set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk") set(CPACK_DEBIAN_PACKAGE_SUGGESTS
"livox-sdk, libcamera0.2")
# RPM package specific settings # RPM package specific settings
set(CPACK_RPM_PACKAGE_LICENSE "Proprietary") set(CPACK_RPM_PACKAGE_LICENSE "Proprietary")
set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering") set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering")
set(CPACK_RPM_PACKAGE_URL "https://github.com/salmanoff/salmanoff") set(CPACK_RPM_PACKAGE_URL "https://github.com/salmanoff/salmanoff")
set(CPACK_RPM_PACKAGE_REQUIRES "boost-system >= 1.72.0, boost-log >= 1.72.0, glibc, libstdc++, ocl-icd, liburing") set(CPACK_RPM_PACKAGE_REQUIRES "boost-system >= 1.72.0, boost-log >= 1.72.0, glibc, libstdc++, ocl-icd, liburing")
set(CPACK_RPM_PACKAGE_SUGGESTS "xcb, libX11, livox-sdk") set(CPACK_RPM_PACKAGE_SUGGESTS
"xcb, libX11, livox-sdk, libcamera")
# Package file naming using Debian's architecture naming when available. # Package file naming using Debian's architecture naming when available.
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
+1
View File
@@ -1,3 +1,4 @@
add_subdirectory(xcbXorg) add_subdirectory(xcbXorg)
add_subdirectory(livoxProto1) add_subdirectory(livoxProto1)
add_subdirectory(lcameraDev)
add_subdirectory(attachmentSupport) add_subdirectory(attachmentSupport)
+93
View File
@@ -0,0 +1,93 @@
option(ENABLE_LIB_lcameraDev "Enable libcamera device provider backend lib" OFF)
if(ENABLE_LIB_lcameraDev)
pkg_check_modules(LIBCAMERA libcamera)
if(NOT LIBCAMERA_FOUND)
message(FATAL_ERROR
"libcamera not found. Install libcamera-dev (and runtime libcamera0.2 "
"+ libcamera-ipa), then reconfigure with -DENABLE_LIB_lcameraDev=ON.")
endif()
add_compile_definitions(CONFIG_LIB_LCAMERADEV_ENABLED)
add_library(lcameraDev SHARED
lcameraDev.cpp
cameraIdentity.cpp
selectorParse.cpp
selectorResolve.cpp
cameraManagerState.cpp
cameraSession.cpp
)
set_target_properties(lcameraDev PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
target_compile_definitions(lcameraDev PRIVATE CONFIG_LIB_LCAMERADEV_ENABLED)
target_include_directories(lcameraDev PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
${LIBCAMERA_INCLUDE_DIRS}
)
target_link_libraries(lcameraDev PUBLIC
Boost::system
Boost::log
spinscale
${LIBCAMERA_LIBRARIES}
)
target_link_directories(lcameraDev PUBLIC
${LIBCAMERA_LIBRARY_DIRS}
)
add_custom_command(TARGET lcameraDev POST_BUILD
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:lcameraDev>"
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
COMMENT "Verifying Boost dynamic dependencies for lcameraDev"
)
install(TARGETS lcameraDev
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_SKIP
)
option(ENABLE_LCAMERADEV_TOOLS "Build lcameraDev probe/list tools" ON)
if(ENABLE_LCAMERADEV_TOOLS)
add_executable(lcameraDev_list_cameras
tools/lcameraDevListCameras.cpp
tools/probeRunner.cpp
)
target_include_directories(lcameraDev_list_cameras PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tools
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(lcameraDev_list_cameras PRIVATE
lcameraDev
spinscale
Boost::system
Boost::log
)
add_executable(lcameraDev_probe
tools/lcameraDevProbe.cpp
tools/probeRunner.cpp
)
target_include_directories(lcameraDev_probe PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tools
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(lcameraDev_probe PRIVATE
lcameraDev
spinscale
Boost::system
Boost::log
)
endif()
endif()
+66
View File
@@ -0,0 +1,66 @@
#include <cameraIdentity.h>
#include <libcamera/property_ids.h>
#include <algorithm>
#include <cctype>
#include <optional>
namespace lcamera_dev {
std::string locationPropertyToLabel(int32_t locationValue)
{
using namespace libcamera::properties;
if (locationValue == CameraLocationFront) {
return "front";
}
if (locationValue == CameraLocationBack) {
return "back";
}
if (locationValue == CameraLocationExternal) {
return "external";
}
return "";
}
CameraIdentityRecord buildIdentityRecord(
const std::shared_ptr<libcamera::Camera>& camera)
{
CameraIdentityRecord record;
record.id = camera->id();
const libcamera::ControlList& props = camera->properties();
const std::optional<std::string> model =
props.get(libcamera::properties::Model);
if (model) {
record.model = *model;
}
const std::optional<int> location =
props.get(libcamera::properties::Location);
if (location)
{
record.locationLabel = locationPropertyToLabel(*location);
}
return record;
}
std::vector<CameraIdentityRecord> buildIdentityRecords(
const std::vector<std::shared_ptr<libcamera::Camera>>& cameras)
{
std::vector<CameraIdentityRecord> records;
records.reserve(cameras.size());
for (const auto& camera : cameras)
{
if (!camera) { continue; }
records.push_back(buildIdentityRecord(camera));
}
return records;
}
} // namespace lcamera_dev
+28
View File
@@ -0,0 +1,28 @@
#ifndef LCAMERA_DEV_CAMERA_IDENTITY_H
#define LCAMERA_DEV_CAMERA_IDENTITY_H
#include <libcamera/camera.h>
#include <optional>
#include <string>
#include <vector>
namespace lcamera_dev {
struct CameraIdentityRecord
{
std::string id;
std::string model;
std::string locationLabel;
};
CameraIdentityRecord buildIdentityRecord(
const std::shared_ptr<libcamera::Camera>& camera);
std::vector<CameraIdentityRecord> buildIdentityRecords(
const std::vector<std::shared_ptr<libcamera::Camera>>& cameras);
std::string locationPropertyToLabel(int32_t locationValue);
} // namespace lcamera_dev
#endif // LCAMERA_DEV_CAMERA_IDENTITY_H
@@ -0,0 +1,251 @@
#include <boostAsioLinkageFix.h>
#include <cameraIdentity.h>
#include <cameraManagerState.h>
#include <selectorParse.h>
#include <selectorResolve.h>
#include <algorithm>
#include <stdexcept>
namespace lcamera_dev {
namespace {
static LcameraDevState lcameraDevState;
void startCameraManager(CameraManagerResources& resources)
{
resources.cameraManager = std::make_unique<libcamera::CameraManager>();
if (resources.cameraManager->start())
{
resources.cameraManager.reset();
throw std::runtime_error(
"lcameraDev: failed to start libcamera CameraManager");
}
}
void stopCameraManager(CameraManagerResources& resources)
{
if (!resources.cameraManager) {
return;
}
resources.cameraManager->stop();
resources.cameraManager.reset();
}
std::shared_ptr<libcamera::Camera> findCameraById(
const std::vector<std::shared_ptr<libcamera::Camera>>& cameras,
const std::string& cameraId)
{
for (const std::shared_ptr<libcamera::Camera>& camera : cameras)
{
if (camera && camera->id() == cameraId) {
return camera;
}
}
return nullptr;
}
} // namespace
LcameraDevState& getLcameraDevState()
{
return lcameraDevState;
}
std::vector<std::shared_ptr<libcamera::Camera>> listLibcameraCameras()
{
LcameraDevState& state = getLcameraDevState();
if (!state.isInitialized || !state.managerState.rsrc.cameraManager) {
return {};
}
return state.managerState.rsrc.cameraManager->cameras();
}
void lcameraDevMain(
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
LcameraDevState& state = getLcameraDevState();
if (state.isInitialized) {
return;
}
if (!componentThread)
{
throw std::runtime_error(
"lcameraDev_main: componentThread must be non-null");
}
startCameraManager(state.managerState.rsrc);
state.componentThread = componentThread;
state.isInitialized = true;
}
void lcameraDevExit()
{
LcameraDevState& state = getLcameraDevState();
if (!state.isInitialized) {
return;
}
CameraManagerResources& resources = state.managerState.rsrc;
for (auto& entry : resources.sessionsByCameraId)
{
std::shared_ptr<CameraSession> session = entry.second;
if (!session || !session->s.rsrc.camera) {
continue;
}
session->s.rsrc.camera->release();
}
resources.sessionsByCameraId.clear();
stopCameraManager(resources);
state.componentThread.reset();
state.isInitialized = false;
}
sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
getOrCreateDeviceSessionCReq(const std::string& deviceSelector)
{
if (deviceSelector.empty())
{
throw std::runtime_error(
"lcameraDev_getOrCreateDeviceCReq: deviceSelector is empty");
}
LcameraDevState& state = getLcameraDevState();
sscl::co::CoQutex::ReleaseHandle managerGuard =
co_await state.managerState.lock.getAcquireInvocationAndSuspensionPolicy();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector(deviceSelector);
const std::vector<std::shared_ptr<libcamera::Camera>> cameras =
state.managerState.rsrc.cameraManager->cameras();
const std::vector<CameraIdentityRecord> identityRecords =
buildIdentityRecords(cameras);
const CameraIdentityRecord resolvedRecord =
resolveSelectorAgainstRecords(criteria, identityRecords);
const std::string& resolvedCameraId = resolvedRecord.id;
auto sessionIt =
state.managerState.rsrc.sessionsByCameraId.find(resolvedCameraId);
if (sessionIt != state.managerState.rsrc.sessionsByCameraId.end())
{
std::shared_ptr<CameraSession> session = sessionIt->second;
sscl::co::CoQutex::ReleaseHandle sessionGuard =
co_await session->s.lock.getAcquireInvocationAndSuspensionPolicy();
session->incrementRefcount();
LcameraDevGetOrCreateResult result;
result.deviceSession = session;
result.resolvedIdentity = session->getIdentityRecord();
co_return result;
}
std::shared_ptr<libcamera::Camera> camera =
findCameraById(cameras, resolvedCameraId);
if (!camera)
{
throw std::runtime_error(
"lcameraDev: resolved camera is no longer available: "
+ resolvedCameraId);
}
if (camera->acquire())
{
throw std::runtime_error(
"lcameraDev: failed to acquire camera: " + resolvedCameraId);
}
std::shared_ptr<CameraSession> session =
std::make_shared<CameraSession>(resolvedRecord, camera);
session->incrementRefcount();
state.managerState.rsrc.sessionsByCameraId.emplace(
resolvedCameraId, session);
LcameraDevGetOrCreateResult result;
result.deviceSession = session;
result.resolvedIdentity = session->getIdentityRecord();
co_return result;
}
sscl::co::ViralNonPostingInvoker<void>
releaseDeviceSessionCReq(
const std::shared_ptr<CameraSession>& deviceSession)
{
if (!deviceSession) { co_return; }
LcameraDevState& state = getLcameraDevState();
sscl::co::CoQutex::ReleaseHandle managerGuard =
co_await state.managerState.lock.getAcquireInvocationAndSuspensionPolicy();
const auto sessionIt = std::find_if(
state.managerState.rsrc.sessionsByCameraId.begin(),
state.managerState.rsrc.sessionsByCameraId.end(),
[&deviceSession](const auto& entry) {
return entry.second == deviceSession;
});
if (sessionIt == state.managerState.rsrc.sessionsByCameraId.end()) {
co_return;
}
bool shouldDestroy = false;
{
sscl::co::CoQutex::ReleaseHandle sessionGuard =
co_await deviceSession->s.lock.getAcquireInvocationAndSuspensionPolicy();
shouldDestroy = deviceSession->decrementRefcount();
}
if (!shouldDestroy) {
co_return;
}
if (deviceSession->s.rsrc.camera) {
deviceSession->s.rsrc.camera->release();
}
state.managerState.rsrc.sessionsByCameraId.erase(sessionIt);
co_return;
}
sscl::co::ViralNonPostingInvoker<std::vector<LcameraDevCameraInfo>>
enumerateCamerasCReq()
{
LcameraDevState& state = getLcameraDevState();
sscl::co::CoQutex::ReleaseHandle managerGuard =
co_await state.managerState.lock.getAcquireInvocationAndSuspensionPolicy();
const std::vector<std::shared_ptr<libcamera::Camera>> cameras =
state.managerState.rsrc.cameraManager->cameras();
const std::vector<CameraIdentityRecord> identityRecords =
buildIdentityRecords(cameras);
std::vector<LcameraDevCameraInfo> cameraInfos;
cameraInfos.reserve(identityRecords.size());
for (const CameraIdentityRecord& record : identityRecords)
{
cameraInfos.push_back(LcameraDevCameraInfo{
record.id,
record.model,
record.locationLabel
});
}
co_return cameraInfos;
}
} // namespace lcamera_dev
@@ -0,0 +1,55 @@
#ifndef LCAMERA_DEV_CAMERA_MANAGER_STATE_H
#define LCAMERA_DEV_CAMERA_MANAGER_STATE_H
#include <lcameraDev.h>
#include <cameraSession.h>
#include <libcamera/camera_manager.h>
#include <map>
#include <memory>
#include <spinscale/co/coQutex.h>
#include <spinscale/componentThread.h>
#include <spinscale/sharedResourceGroup.h>
#include <string>
#include <vector>
namespace lcamera_dev {
struct CameraManagerResources
{
std::unique_ptr<libcamera::CameraManager> cameraManager;
std::map<std::string, std::shared_ptr<CameraSession>> sessionsByCameraId;
};
struct LcameraDevState
{
LcameraDevState()
: managerState("lcameraDev::CameraManager")
{}
bool isInitialized = false;
std::shared_ptr<sscl::ComponentThread> componentThread;
sscl::SharedResourceGroup<sscl::co::CoQutex, CameraManagerResources>
managerState;
};
LcameraDevState& getLcameraDevState();
void lcameraDevMain(
const std::shared_ptr<sscl::ComponentThread>& componentThread);
void lcameraDevExit();
std::vector<std::shared_ptr<libcamera::Camera>> listLibcameraCameras();
sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
getOrCreateDeviceSessionCReq(const std::string& deviceSelector);
sscl::co::ViralNonPostingInvoker<void>
releaseDeviceSessionCReq(
const std::shared_ptr<CameraSession>& deviceSession);
sscl::co::ViralNonPostingInvoker<std::vector<LcameraDevCameraInfo>>
enumerateCamerasCReq();
} // namespace lcamera_dev
#endif // LCAMERA_DEV_CAMERA_MANAGER_STATE_H
+29
View File
@@ -0,0 +1,29 @@
#include <cameraSession.h>
#include <stdexcept>
namespace lcamera_dev {
CameraSession::CameraSession(
const CameraIdentityRecord& identity,
const std::shared_ptr<libcamera::Camera>& camera)
: s("lcameraDev::CameraSession", CameraSessionResources{identity, camera})
{}
void CameraSession::incrementRefcount()
{
++s.rsrc.refcount;
}
bool CameraSession::decrementRefcount()
{
if (s.rsrc.refcount <= 0)
{
throw std::logic_error(
"lcameraDev: releaseDeviceCReq refcount underflow");
}
--s.rsrc.refcount;
return s.rsrc.refcount == 0;
}
} // namespace lcamera_dev
+46
View File
@@ -0,0 +1,46 @@
#ifndef LCAMERA_DEV_CAMERA_SESSION_H
#define LCAMERA_DEV_CAMERA_SESSION_H
#include <cameraIdentity.h>
#include <libcamera/camera.h>
#include <memory>
#include <spinscale/co/coQutex.h>
#include <spinscale/sharedResourceGroup.h>
namespace lcamera_dev {
struct CameraSessionResources
{
CameraSessionResources(
const CameraIdentityRecord& identity,
const std::shared_ptr<libcamera::Camera>& camera)
: identity(identity), camera(camera)
{}
int refcount = 0;
CameraIdentityRecord identity;
std::shared_ptr<libcamera::Camera> camera;
};
class CameraSession
{
public:
CameraSession(
const CameraIdentityRecord& identity,
const std::shared_ptr<libcamera::Camera>& camera);
const CameraIdentityRecord& getIdentityRecord() const
{ return s.rsrc.identity; }
const std::shared_ptr<libcamera::Camera>& getCamera() const
{ return s.rsrc.camera; }
void incrementRefcount();
bool decrementRefcount();
sscl::SharedResourceGroup<sscl::co::CoQutex, CameraSessionResources> s;
};
} // namespace lcamera_dev
#endif // LCAMERA_DEV_CAMERA_SESSION_H
+61
View File
@@ -0,0 +1,61 @@
#include <boostAsioLinkageFix.h>
#include <cameraManagerState.h>
#include <lcameraDev.h>
#include <stdexcept>
extern "C" {
void lcameraDev_main(
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcamera_dev::lcameraDevMain(componentThread);
}
void lcameraDev_exit(void)
{
lcamera_dev::lcameraDevExit();
}
sscl::co::ViralNonPostingInvoker<lcamera_dev::LcameraDevGetOrCreateResult>
lcameraDev_getOrCreateDeviceCReq(const std::string& deviceSelector)
{
lcamera_dev::LcameraDevState& state = lcamera_dev::getLcameraDevState();
if (!state.isInitialized)
{
throw std::runtime_error(
"lcameraDev_getOrCreateDeviceCReq: call lcameraDev_main first");
}
co_return co_await lcamera_dev::getOrCreateDeviceSessionCReq(deviceSelector);
}
sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReq(
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession)
{
lcamera_dev::LcameraDevState& state = lcamera_dev::getLcameraDevState();
if (!state.isInitialized)
{
throw std::runtime_error(
"lcameraDev_releaseDeviceCReq: call lcameraDev_main first");
}
co_await lcamera_dev::releaseDeviceSessionCReq(deviceSession);
co_return;
}
sscl::co::ViralNonPostingInvoker<std::vector<lcamera_dev::LcameraDevCameraInfo>>
lcameraDev_enumerateCamerasCReq(void)
{
lcamera_dev::LcameraDevState& state = lcamera_dev::getLcameraDevState();
if (!state.isInitialized)
{
throw std::runtime_error(
"lcameraDev_enumerateCamerasCReq: call lcameraDev_main first");
}
co_return co_await lcamera_dev::enumerateCamerasCReq();
}
} // extern "C"
+59
View File
@@ -0,0 +1,59 @@
#ifndef LCAMERA_DEV_H
#define LCAMERA_DEV_H
#include <cameraIdentity.h>
#include <memory>
#include <spinscale/co/invokers.h>
#include <spinscale/componentThread.h>
#include <string>
#include <vector>
namespace lcamera_dev {
class CameraSession;
struct LcameraDevGetOrCreateResult
{
std::shared_ptr<CameraSession> deviceSession;
CameraIdentityRecord resolvedIdentity;
};
struct LcameraDevCameraInfo
{
std::string id;
std::string model;
std::string location;
};
} // namespace lcamera_dev
#ifdef __cplusplus
extern "C" {
#endif
typedef void lcameraDev_mainFn(
const std::shared_ptr<sscl::ComponentThread>& componentThread);
typedef void lcameraDev_exitFn(void);
typedef sscl::co::ViralNonPostingInvoker<lcamera_dev::LcameraDevGetOrCreateResult>
lcameraDev_getOrCreateDeviceCReqFn(const std::string& deviceSelector);
typedef sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReqFn(
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession);
typedef sscl::co::ViralNonPostingInvoker<std::vector<lcamera_dev::LcameraDevCameraInfo>>
lcameraDev_enumerateCamerasCReqFn(void);
lcameraDev_mainFn lcameraDev_main;
lcameraDev_exitFn lcameraDev_exit;
lcameraDev_getOrCreateDeviceCReqFn lcameraDev_getOrCreateDeviceCReq;
lcameraDev_releaseDeviceCReqFn lcameraDev_releaseDeviceCReq;
lcameraDev_enumerateCamerasCReqFn lcameraDev_enumerateCamerasCReq;
#ifdef __cplusplus
}
#endif
#endif // LCAMERA_DEV_H
+127
View File
@@ -0,0 +1,127 @@
#include <selectorParse.h>
#include <stdexcept>
#include <cctype>
namespace lcamera_dev {
namespace {
bool startsWith(const std::string& text, const std::string& prefix)
{
return text.size() >= prefix.size()
&& text.compare(0, prefix.size(), prefix) == 0;
}
SelectorCriterionKind parseCriterionKind(const std::string& prefixToken)
{
if (prefixToken == "lcamera-id") {
return SelectorCriterionKind::LibcameraId;
}
if (prefixToken == "index") {
return SelectorCriterionKind::Index;
}
if (prefixToken == "model-substr") {
return SelectorCriterionKind::ModelSubstr;
}
if (prefixToken == "model") {
return SelectorCriterionKind::Model;
}
if (prefixToken == "location") {
return SelectorCriterionKind::Location;
}
throw std::runtime_error(
"Unknown deviceSelector prefix: " + prefixToken);
}
SelectorCriterion parseCriterionClause(const std::string& clause)
{
const std::string trimmedClause = trimWhitespace(clause);
if (trimmedClause.empty()) {
throw std::runtime_error("Empty deviceSelector clause");
}
const size_t colonPos = trimmedClause.find(':');
if (colonPos == std::string::npos)
{
return SelectorCriterion{
SelectorCriterionKind::LibcameraId,
trimmedClause
};
}
const std::string prefixToken = trimWhitespace(
trimmedClause.substr(0, colonPos));
const std::string value = trimWhitespace(
trimmedClause.substr(colonPos + 1));
if (value.empty())
{
throw std::runtime_error(
"deviceSelector clause has empty value for prefix "
+ prefixToken);
}
return SelectorCriterion{
parseCriterionKind(prefixToken),
value
};
}
} // namespace
std::string trimWhitespace(const std::string& text)
{
size_t start = 0;
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start]))) {
++start;
}
size_t end = text.size();
while (end > start
&& std::isspace(static_cast<unsigned char>(text[end - 1])))
{
--end;
}
return text.substr(start, end - start);
}
std::vector<SelectorCriterion> parseDeviceSelector(
const std::string& deviceSelector)
{
const std::string trimmedSelector = trimWhitespace(deviceSelector);
if (trimmedSelector.empty()) {
throw std::runtime_error("deviceSelector is empty");
}
std::vector<SelectorCriterion> criteria;
std::string currentClause;
currentClause.reserve(trimmedSelector.size());
for (size_t i = 0; i < trimmedSelector.size(); ++i)
{
const char ch = trimmedSelector[i];
if (ch == '\\' && i + 1 < trimmedSelector.size()
&& trimmedSelector[i + 1] == ';')
{
currentClause.push_back(';');
++i;
continue;
}
if (ch == ';')
{
criteria.push_back(parseCriterionClause(currentClause));
currentClause.clear();
continue;
}
currentClause.push_back(ch);
}
criteria.push_back(parseCriterionClause(currentClause));
return criteria;
}
} // namespace lcamera_dev
+31
View File
@@ -0,0 +1,31 @@
#ifndef LCAMERA_DEV_SELECTOR_PARSE_H
#define LCAMERA_DEV_SELECTOR_PARSE_H
#include <string>
#include <vector>
namespace lcamera_dev {
enum class SelectorCriterionKind
{
LibcameraId,
Index,
Model,
ModelSubstr,
Location,
};
struct SelectorCriterion
{
SelectorCriterionKind kind = SelectorCriterionKind::LibcameraId;
std::string value;
};
std::vector<SelectorCriterion> parseDeviceSelector(
const std::string& deviceSelector);
std::string trimWhitespace(const std::string& text);
} // namespace lcamera_dev
#endif // LCAMERA_DEV_SELECTOR_PARSE_H
+171
View File
@@ -0,0 +1,171 @@
#include <selectorResolve.h>
#include <algorithm>
#include <cctype>
#include <optional>
#include <sstream>
#include <stdexcept>
namespace lcamera_dev {
namespace {
std::string toLowerAscii(const std::string& text)
{
std::string lowered = text;
for (char& ch : lowered) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
return lowered;
}
bool recordMatchesCriterion(
const CameraIdentityRecord& record, const SelectorCriterion& criterion)
{
switch (criterion.kind)
{
case SelectorCriterionKind::LibcameraId:
return record.id == criterion.value;
case SelectorCriterionKind::Index:
return false;
case SelectorCriterionKind::Model:
return record.model == criterion.value;
case SelectorCriterionKind::ModelSubstr:
return record.model.find(criterion.value) != std::string::npos;
case SelectorCriterionKind::Location:
return toLowerAscii(record.locationLabel)
== toLowerAscii(criterion.value);
}
return false;
}
int parseIndexCriterion(const SelectorCriterion& criterion)
{
try {
return std::stoi(criterion.value);
}
catch (const std::exception&) {
throw std::runtime_error("Invalid index: value in deviceSelector");
}
}
} // namespace
std::string formatCameraListForDiagnostics(
const std::vector<CameraIdentityRecord>& records)
{
std::ostringstream result;
result << "Known cameras:\n";
for (size_t i = 0; i < records.size(); ++i)
{
const CameraIdentityRecord& record = records[i];
result << " [" << i << "] id=" << record.id;
if (!record.model.empty()) {
result << " model=" << record.model;
}
if (!record.locationLabel.empty()) {
result << " location=" << record.locationLabel;
}
result << '\n';
}
return result.str();
}
CameraIdentityRecord resolveSelectorAgainstRecords(
const std::vector<SelectorCriterion>& criteria,
const std::vector<CameraIdentityRecord>& records)
{
std::optional<int> indexCriterion;
for (const SelectorCriterion& criterion : criteria)
{
if (criterion.kind != SelectorCriterionKind::Index) {
continue;
}
const int index = parseIndexCriterion(criterion);
if (index < 0
|| static_cast<size_t>(index) >= records.size())
{
throw std::runtime_error("index: selector out of range");
}
indexCriterion = index;
}
std::vector<const CameraIdentityRecord*> matches;
matches.reserve(records.size());
for (const CameraIdentityRecord& record : records)
{
bool matchesAll = true;
for (const SelectorCriterion& criterion : criteria)
{
if (criterion.kind == SelectorCriterionKind::Index) {
continue;
}
if (!recordMatchesCriterion(record, criterion))
{
matchesAll = false;
break;
}
}
if (!matchesAll) {
continue;
}
matches.push_back(&record);
}
if (indexCriterion.has_value())
{
const CameraIdentityRecord& indexedRecord =
records.at(static_cast<size_t>(*indexCriterion));
auto it = std::find_if(
matches.begin(), matches.end(),
[&indexedRecord](const CameraIdentityRecord* candidate) {
return candidate->id == indexedRecord.id;
});
if (it == matches.end())
{
throw std::runtime_error(
"index: criterion conflicts with other selector clauses");
}
if (matches.size() > 1)
{
throw std::runtime_error(
"Ambiguous deviceSelector: multiple cameras match\n"
+ formatCameraListForDiagnostics(records));
}
return indexedRecord;
}
if (matches.empty())
{
throw std::runtime_error(
"No camera matches deviceSelector\n"
+ formatCameraListForDiagnostics(records));
}
if (matches.size() > 1)
{
throw std::runtime_error(
"Ambiguous deviceSelector: multiple cameras match\n"
+ formatCameraListForDiagnostics(records));
}
return *matches.front();
}
} // namespace lcamera_dev
+20
View File
@@ -0,0 +1,20 @@
#ifndef LCAMERA_DEV_SELECTOR_RESOLVE_H
#define LCAMERA_DEV_SELECTOR_RESOLVE_H
#include <cameraIdentity.h>
#include <selectorParse.h>
#include <string>
#include <vector>
namespace lcamera_dev {
CameraIdentityRecord resolveSelectorAgainstRecords(
const std::vector<SelectorCriterion>& criteria,
const std::vector<CameraIdentityRecord>& records);
std::string formatCameraListForDiagnostics(
const std::vector<CameraIdentityRecord>& records);
} // namespace lcamera_dev
#endif // LCAMERA_DEV_SELECTOR_RESOLVE_H
@@ -0,0 +1,71 @@
#include <boostAsioLinkageFix.h>
#include <lcameraDev.h>
#include <probeRunner.h>
#include <iostream>
#include <spinscale/co/nonViralTaskNursery.h>
namespace {
sscl::co::NonViralNonPostingInvoker listCamerasCInd(
std::exception_ptr& exceptionStorage,
std::function<void()> callerLambda)
{
(void)exceptionStorage;
(void)callerLambda;
const std::vector<lcamera_dev::LcameraDevCameraInfo> cameras =
co_await lcameraDev_enumerateCamerasCReq();
std::cout << "lcameraDev: found " << cameras.size() << " camera(s)\n";
for (size_t i = 0; i < cameras.size(); ++i)
{
const lcamera_dev::LcameraDevCameraInfo& info = cameras[i];
std::cout << " [" << i << "] id=" << info.id;
if (!info.model.empty()) {
std::cout << " model=" << info.model;
}
if (!info.location.empty()) {
std::cout << " location=" << info.location;
}
std::cout << '\n';
}
co_return;
}
void runListCameras(
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcameraDev_main(componentThread);
sscl::co::NonViralTaskNursery nursery;
nursery.openAdmission();
nursery.launch(
[](sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return listCamerasCInd(
lease.getExceptionStorage(),
lease.getCallerLambda());
});
nursery.closeAdmission();
nursery.syncAwaitAllSettlements(componentThread->getIoContext());
lcameraDev_exit();
}
} // namespace
int main()
{
try {
lcamera_dev_probe::runOnComponentThread(runListCameras);
return 0;
}
catch (const std::exception& exc) {
std::cerr << "lcameraDev_list_cameras: " << exc.what() << '\n';
return 1;
}
}
@@ -0,0 +1,78 @@
#include <boostAsioLinkageFix.h>
#include <lcameraDev.h>
#include <probeRunner.h>
#include <iostream>
#include <spinscale/co/nonViralTaskNursery.h>
#include <string>
namespace {
sscl::co::NonViralNonPostingInvoker probeSelectorCInd(
std::exception_ptr& exceptionStorage,
std::function<void()> callerLambda,
const std::string& deviceSelector)
{
(void)exceptionStorage;
(void)callerLambda;
const lcamera_dev::LcameraDevGetOrCreateResult createResult =
co_await lcameraDev_getOrCreateDeviceCReq(deviceSelector);
std::cout << "lcameraDev_probe: opened session for camera id="
<< createResult.resolvedIdentity.id << '\n';
co_await lcameraDev_releaseDeviceCReq(createResult.deviceSession);
std::cout << "lcameraDev_probe: released session\n";
co_return;
}
void runProbe(
const std::shared_ptr<sscl::ComponentThread>& componentThread,
const std::string& deviceSelector)
{
lcameraDev_main(componentThread);
sscl::co::NonViralTaskNursery nursery;
nursery.openAdmission();
nursery.launch(
[deviceSelector](sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return probeSelectorCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
deviceSelector);
});
nursery.closeAdmission();
nursery.syncAwaitAllSettlements(componentThread->getIoContext());
lcameraDev_exit();
}
} // namespace
int main(int argc, char* argv[])
{
if (argc < 2)
{
std::cerr << "Usage: lcameraDev_probe <deviceSelector>\n";
return 1;
}
try {
const std::string deviceSelector = argv[1];
lcamera_dev_probe::runOnComponentThread(
[&](const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
runProbe(componentThread, deviceSelector);
});
return 0;
}
catch (const std::exception& exc) {
std::cerr << "lcameraDev_probe: " << exc.what() << '\n';
return 1;
}
}
@@ -0,0 +1,88 @@
#include <boostAsioLinkageFix.h>
#include <probeRunner.h>
#include <spinscale/component.h>
#include <future>
#include <iostream>
namespace lcamera_dev_probe {
namespace {
constexpr sscl::ThreadId PROBE_PUPPETEER_THREAD_ID = 1;
class DummyPuppeteerComponent
: public sscl::pptr::PuppeteerComponent
{
public:
explicit DummyPuppeteerComponent(
const std::shared_ptr<sscl::PuppeteerThread>& componentThread)
: sscl::pptr::PuppeteerComponent(componentThread)
{}
void handleLoopExceptionHook() override
{
std::cerr << "lcameraDev probe: puppeteer loop exception\n";
}
};
void probePuppeteerMain(
const sscl::PuppeteerThread::EntryFnArguments& args,
const std::function<void(
const std::shared_ptr<sscl::ComponentThread>&)>& work,
std::promise<std::exception_ptr>& donePromise)
{
sscl::PuppeteerThread& thr = args.usableBeforeJolt;
thr.initializeTls();
sscl::ComponentThread::setPuppeteerThreadId(PROBE_PUPPETEER_THREAD_ID);
std::shared_ptr<sscl::PuppeteerThread> thrPtr =
std::static_pointer_cast<sscl::PuppeteerThread>(thr.shared_from_this());
sscl::ComponentThread::setPuppeteerThread(thrPtr);
try {
work(thrPtr);
donePromise.set_value(nullptr);
}
catch (...) {
donePromise.set_value(std::current_exception());
}
thr.getIoContext().stop();
}
} // namespace
void runOnComponentThread(
const std::function<void(
const std::shared_ptr<sscl::ComponentThread>&)>& work)
{
std::promise<std::exception_ptr> donePromise;
std::future<std::exception_ptr> doneFuture = donePromise.get_future();
DummyPuppeteerComponent dummyComponent{
std::shared_ptr<sscl::PuppeteerThread>()};
std::shared_ptr<sscl::PuppeteerThread> probeThread =
std::make_shared<sscl::PuppeteerThread>(
PROBE_PUPPETEER_THREAD_ID,
"lcameraDev-probe",
[&work, &donePromise](
const sscl::PuppeteerThread::EntryFnArguments& args)
{
probePuppeteerMain(args, work, donePromise);
},
dummyComponent,
nullptr);
dummyComponent.thread = probeThread;
probeThread->thread.join();
std::exception_ptr probeException = doneFuture.get();
if (probeException) {
std::rethrow_exception(probeException);
}
}
} // namespace lcamera_dev_probe
+16
View File
@@ -0,0 +1,16 @@
#ifndef LCAMERA_DEV_PROBE_RUNNER_H
#define LCAMERA_DEV_PROBE_RUNNER_H
#include <functional>
#include <memory>
#include <spinscale/componentThread.h>
namespace lcamera_dev_probe {
void runOnComponentThread(
const std::function<void(
const std::shared_ptr<sscl::ComponentThread>&)>& work);
} // namespace lcamera_dev_probe
#endif // LCAMERA_DEV_PROBE_RUNNER_H
+66 -61
View File
@@ -34,8 +34,10 @@ lcameraBuff (stimBuffApi) SMO-facing producers, channel fan-out, intrins
Channel splitting, colourspace conversion, threshold masks, and stencils belong Channel splitting, colourspace conversion, threshold masks, and stencils belong
in a separate shared raster library (`rasterStimulus`, future) and in in a separate shared raster library (`rasterStimulus`, future) and in
`lcameraBuff`; `lcameraDev` stops at “here is a stable, acquired libcamera `lcameraBuff`; `lcameraDev` stops at selector resolution, `CameraManager`
`Camera` you can configure and stream from.” lifecycle, and a refcounted, **acquired** `libcamera::Camera` handle per
resolved device. Stream negotiation, pixel-format selection, frame buffers,
and capture timing belong in `lcameraBuff` (and supporting libraries), not here.
## Why libcamera (for now) ## Why libcamera (for now)
@@ -120,7 +122,8 @@ Possible future params:
* `fps-hz=30` * `fps-hz=30`
* `width=640|height=480` * `width=640|height=480`
* `pixfmt=NV12` — negotiation hint passed down to `lcameraDev` * `pixfmt=NV12` — negotiation hint for `lcameraBuff` stream setup (not
`lcameraDev`)
## Device selector format ## Device selector format
@@ -180,7 +183,7 @@ tokens).
Resolution algorithm (summary): Resolution algorithm (summary):
1. Start `CameraManager` (once per process, inside `lcameraDev`). 1. Start `CameraManager` in `lcameraDev_main` (once per process).
2. Enumerate cameras; build an identity record per camera (`id`, `model`, 2. Enumerate cameras; build an identity record per camera (`id`, `model`,
`location`, `systemDevices`). `location`, `systemDevices`).
3. Parse `deviceSelector` into criterion clauses; apply **all** clauses (AND). 3. Parse `deviceSelector` into criterion clauses; apply **all** clauses (AND).
@@ -197,9 +200,9 @@ each cameras `id()`, `model`, and `location` so operators can copy a stable
## Shared session and refcounting ## Shared session and refcounting
Unlike X11 windows, a camera has no “sub-objects” inside its feed — the selector Unlike X11 windows, a camera has no “sub-objects” inside its feed — the selector
always designates the whole camera stream. Multiple DAP lines for H, S, and V always designates the whole physical camera. Multiple DAP lines for H, S, and V
are **views** over one capture session, all under the same `dev-identifier` share one **device session** (acquired `libcamera::Camera`), all under the same
(e.g. `cam0`). `dev-identifier` (e.g. `cam0`).
```text ```text
dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (refcounted) dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (refcounted)
@@ -213,11 +216,10 @@ dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (re
Rules: Rules:
* First `getOrCreate` for a resolved camera ID: enumerate, `acquire()` the * First `getOrCreate` for a resolved camera ID: enumerate, `acquire()` the
`libcamera::Camera`, negotiate and start the shared stream. `libcamera::Camera`, create the session entry.
* Subsequent `getOrCreate` for the same ID: increment refcount; return the same * Subsequent `getOrCreate` for the same ID: increment refcount; return the same
session handle. session handle.
* Last `release`: `release()` the libcamera camera, stop streaming, tear down * Last `release`: `release()` the libcamera camera and erase the session entry.
buffers.
* Different `deviceSelector` strings that resolve to the same libcamera ID share * Different `deviceSelector` strings that resolve to the same libcamera ID share
one session (even if the selector text differs, e.g. one line uses one session (even if the selector text differs, e.g. one line uses
`lcamera-id:...` and another uses a compound selector that resolves to the `lcamera-id:...` and another uses a compound selector that resolves to the
@@ -226,84 +228,89 @@ Rules:
the same physical device from SMOs perspective; channel differences come from the same physical device from SMOs perspective; channel differences come from
the qualeIface name on each DAP line. the qualeIface name on each DAP line.
## dlopen API (planned) Streaming, frame delivery, and colourspace work are **out of scope** for
`lcameraDev`; `lcameraBuff` uses the sessions acquired camera handle to set up
capture on attach.
## dlopen API
Exported from `liblcameraDev.so` using `extern "C"` symbols (mirroring Exported from `liblcameraDev.so` using `extern "C"` symbols (mirroring
`livoxProto1`). `lcameraBuff` loads the library with `dlopen` + `dlsym` and `livoxProto1`). `lcameraBuff` loads the library with `dlopen` + `dlsym` and
calls through function pointers. calls through function pointers. Hot-path operations are **`*CReq` coroutine
invokers** (`sscl::co::ViralNonPostingInvoker`); `main` / `exit` remain
synchronous.
Header: `commonLibs/lcameraDev/lcameraDev.h`.
### Lifecycle ### Lifecycle
```c ```c
/* Start CameraManager; idempotent per process. */ typedef void lcameraDev_mainFn(
typedef void lcameraDev_mainFn(void); const std::shared_ptr<sscl::ComponentThread>& componentThread);
/* Stop manager, release all devices; called on SMO shutdown. */
typedef void lcameraDev_exitFn(void); typedef void lcameraDev_exitFn(void);
``` ```
`lcameraDev_main` records the Body `ComponentThread`, starts libcamera's
`CameraManager` (idempotent), and must succeed before any `*CReq` runs.
### Device acquisition ### Device acquisition
```c ```cpp
struct LcameraDevGetOrCreateResult struct LcameraDevGetOrCreateResult
{ {
bool success; std::shared_ptr<CameraSession> deviceSession;
/* Opaque session handle; valid until matching release call. */ CameraIdentityRecord resolvedIdentity;
void* deviceSession;
/* Resolved libcamera camera ID (stable session key). */
const char* resolvedCameraId;
}; };
typedef LcameraDevGetOrCreateResult lcameraDev_getOrCreateDeviceFn( typedef sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
const char* deviceSelector); lcameraDev_getOrCreateDeviceCReqFn(const std::string& deviceSelector);
typedef void lcameraDev_releaseDeviceFn(void* deviceSession); typedef sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReqFn(
const std::shared_ptr<CameraSession>& deviceSession);
``` ```
`deviceSession` is opaque to callers. `lcameraBuff` passes it back when Failures throw `std::exception`. `lcameraBuff` holds the returned
detaching; it must not call libcamera APIs directly. `shared_ptr<CameraSession>` and passes it back to `releaseDeviceCReq`. The
session wraps the acquired `libcamera::Camera`; higher layers configure and
stream from that handle — `lcameraDev` does not expose frame or stream APIs.
### Enumeration (discovery) ### Enumeration (discovery)
```c ```cpp
struct LcameraDevCameraInfo struct LcameraDevCameraInfo
{ {
const char* id; std::string id;
const char* model; /* empty string if unavailable */ std::string model;
const char* location; /* "front", "back", "external", or "" */ std::string location;
}; };
struct LcameraDevEnumerateResult typedef sscl::co::ViralNonPostingInvoker<std::vector<LcameraDevCameraInfo>>
{ lcameraDev_enumerateCamerasCReqFn(void);
size_t count;
LcameraDevCameraInfo* cameras; /* caller frees via lcameraDev_freeEnumerateResult */
};
typedef LcameraDevEnumerateResult lcameraDev_enumerateCamerasFn(void);
typedef void lcameraDev_freeEnumerateResultFn(LcameraDevEnumerateResult* result);
``` ```
### Frame access (boundary with lcameraBuff) ### Manual verification tools
Exact frame-delivery API is TBD and will likely use a callback or a “dequeue When built with `-DENABLE_LIB_lcameraDev=ON`:
latest frame buffer” method on the session handle. `lcameraDev` owns libcamera
`FrameBuffer` allocation and `Request` requeue; `lcameraBuff` copies or maps the
plane it needs for the active qualeIface channel.
Principle: **one negotiated stream format per session** (prefer native YUV such * `lcameraDev_list_cameras` — runs `enumerateCamerasCReq` on a minimal probe
as NV12/YUYV). `lcameraBuff` + `rasterStimulus` derive H/S/V greyscale planes `ComponentThread`.
from that single stream. * `lcameraDev_probe <deviceSelector>``getOrCreateDeviceCReq`, then
`releaseDeviceCReq` (selector and session attach/detach only).
## Module layout (planned) ## Module layout
```text ```text
commonLibs/lcameraDev/ commonLibs/lcameraDev/
CMakeLists.txt CMakeLists.txt
lcameraDev.h Public C API + C++ internal headers lcameraDev.h / lcameraDev.cpp Public C API and dlopen exports
lcameraDev.cpp dlopen exports, CameraManager singleton cameraManagerState.h / .cpp CameraManager singleton, session map
cameraSession.cpp Refcounted session, stream negotiation cameraSession.h / .cpp Refcounted acquired-camera session
selectorResolve.cpp deviceSelector parsing and matching cameraIdentity.h / .cpp Discovery identity records
cameraEnumerate.cpp Discovery / identity records selectorParse.h / .cpp Compound selector parsing
selectorResolve.h / .cpp AND-match resolution
tools/ lcameraDev_list_cameras, lcameraDev_probe
``` ```
Build links against `libcamera` (pkg-config). Does **not** link Salmanoff Build links against `libcamera` (pkg-config). Does **not** link Salmanoff
@@ -313,8 +320,8 @@ Build links against `libcamera` (pkg-config). Does **not** link Salmanoff
| Component | Responsibility | | Component | Responsibility |
|---|---| |---|---|
| `lcameraDev` | libcamera lifecycle, selector resolution, shared capture session | | `lcameraDev` | libcamera lifecycle, selector resolution, refcounted acquired camera session |
| `lcameraBuff` | `StimBuffApiDesc`, `StimulusProducer`, per-channel ring buffers, intrins | | `lcameraBuff` | Stream setup, frames, `StimBuffApiDesc`, channel fan-out, intrins |
| `rasterStimulus` (future) | YUV↔HSV, plane extraction, threshold masks, stencil geometry | | `rasterStimulus` (future) | YUV↔HSV, plane extraction, threshold masks, stencil geometry |
| `xcbWindow` / `waylandWindow` | Separate capture path; reuse `rasterStimulus` only | | `xcbWindow` / `waylandWindow` | Separate capture path; reuse `rasterStimulus` only |
@@ -324,12 +331,10 @@ If libcamera IDs prove insufficient in practice, selector policies can gain
## Open questions ## Open questions
1. **Stream format defaults** — prefer first supported YUV format, or allow 1. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
`lcameraBuff(pixfmt=...)` override?
2. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
and drop the session, or attempt re-enumeration by stored `lcamera-id:`? and drop the session, or attempt re-enumeration by stored `lcamera-id:`?
3. **Thread model** — libcamera callbacks vs polling in the Body thread 2. **IPA packaging** — document per-platform `apt install` requirements in the
production daemon; align with `StimulusProducer::productionCDaemon` timing
(`CONFIG_STIMBUFF_FRAME_PERIOD_MS`).
4. **IPA packaging** — document per-platform `apt install` requirements in the
main README when `lcameraBuff` lands (RPi needs `libcamera-ipa`). main README when `lcameraBuff` lands (RPi needs `libcamera-ipa`).
Stream format, frame timing, and libcamera callback threading are owned by
`lcameraBuff`, not `lcameraDev`.
+1
View File
@@ -30,6 +30,7 @@
/* Common Libraries */ /* Common Libraries */
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED #cmakedefine CONFIG_LIB_XCBXORG_ENABLED
#cmakedefine CONFIG_LIB_LIVOXPROTO1_ENABLED #cmakedefine CONFIG_LIB_LIVOXPROTO1_ENABLED
#cmakedefine CONFIG_LIB_LCAMERADEV_ENABLED
#cmakedefine CONFIG_LIB_ALSA_ENABLED #cmakedefine CONFIG_LIB_ALSA_ENABLED
/* Stim Buff APIs */ /* Stim Buff APIs */