lcameraDev: Add session mgr lib for libcamera device binding
This commit is contained in:
@@ -60,7 +60,7 @@ set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "ubuntu")
|
||||
# Build dependencies (from builddeps file)
|
||||
# These are needed to build the package from source
|
||||
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.
|
||||
# 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.
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "")
|
||||
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
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Proprietary")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering")
|
||||
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_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.
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
add_subdirectory(xcbXorg)
|
||||
add_subdirectory(livoxProto1)
|
||||
add_subdirectory(lcameraDev)
|
||||
add_subdirectory(attachmentSupport)
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -34,8 +34,10 @@ lcameraBuff (stimBuffApi) SMO-facing producers, channel fan-out, intrins
|
||||
|
||||
Channel splitting, colourspace conversion, threshold masks, and stencils belong
|
||||
in a separate shared raster library (`rasterStimulus`, future) and in
|
||||
`lcameraBuff`; `lcameraDev` stops at “here is a stable, acquired libcamera
|
||||
`Camera` you can configure and stream from.”
|
||||
`lcameraBuff`; `lcameraDev` stops at selector resolution, `CameraManager`
|
||||
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)
|
||||
|
||||
@@ -120,7 +122,8 @@ Possible future params:
|
||||
|
||||
* `fps-hz=30`
|
||||
* `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
|
||||
|
||||
@@ -180,7 +183,7 @@ tokens).
|
||||
|
||||
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`,
|
||||
`location`, `systemDevices`).
|
||||
3. Parse `deviceSelector` into criterion clauses; apply **all** clauses (AND).
|
||||
@@ -197,9 +200,9 @@ each camera’s `id()`, `model`, and `location` so operators can copy a stable
|
||||
## Shared session and refcounting
|
||||
|
||||
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
|
||||
are **views** over one capture session, all under the same `dev-identifier`
|
||||
(e.g. `cam0`).
|
||||
always designates the whole physical camera. Multiple DAP lines for H, S, and V
|
||||
share one **device session** (acquired `libcamera::Camera`), all under the same
|
||||
`dev-identifier` (e.g. `cam0`).
|
||||
|
||||
```text
|
||||
dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (refcounted)
|
||||
@@ -213,11 +216,10 @@ dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (re
|
||||
Rules:
|
||||
|
||||
* 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
|
||||
session handle.
|
||||
* Last `release`: `release()` the libcamera camera, stop streaming, tear down
|
||||
buffers.
|
||||
* Last `release`: `release()` the libcamera camera and erase the session entry.
|
||||
* Different `deviceSelector` strings that resolve to the same libcamera ID share
|
||||
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
|
||||
@@ -226,84 +228,89 @@ Rules:
|
||||
the same physical device from SMO’s perspective; channel differences come from
|
||||
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 session’s acquired camera handle to set up
|
||||
capture on attach.
|
||||
|
||||
## dlopen API
|
||||
|
||||
Exported from `liblcameraDev.so` using `extern "C"` symbols (mirroring
|
||||
`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
|
||||
|
||||
```c
|
||||
/* Start CameraManager; idempotent per process. */
|
||||
typedef void lcameraDev_mainFn(void);
|
||||
typedef void lcameraDev_mainFn(
|
||||
const std::shared_ptr<sscl::ComponentThread>& componentThread);
|
||||
|
||||
/* Stop manager, release all devices; called on SMO shutdown. */
|
||||
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
|
||||
|
||||
```c
|
||||
```cpp
|
||||
struct LcameraDevGetOrCreateResult
|
||||
{
|
||||
bool success;
|
||||
/* Opaque session handle; valid until matching release call. */
|
||||
void* deviceSession;
|
||||
/* Resolved libcamera camera ID (stable session key). */
|
||||
const char* resolvedCameraId;
|
||||
std::shared_ptr<CameraSession> deviceSession;
|
||||
CameraIdentityRecord resolvedIdentity;
|
||||
};
|
||||
|
||||
typedef LcameraDevGetOrCreateResult lcameraDev_getOrCreateDeviceFn(
|
||||
const char* deviceSelector);
|
||||
typedef sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
|
||||
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
|
||||
detaching; it must not call libcamera APIs directly.
|
||||
Failures throw `std::exception`. `lcameraBuff` holds the returned
|
||||
`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)
|
||||
|
||||
```c
|
||||
```cpp
|
||||
struct LcameraDevCameraInfo
|
||||
{
|
||||
const char* id;
|
||||
const char* model; /* empty string if unavailable */
|
||||
const char* location; /* "front", "back", "external", or "" */
|
||||
std::string id;
|
||||
std::string model;
|
||||
std::string location;
|
||||
};
|
||||
|
||||
struct LcameraDevEnumerateResult
|
||||
{
|
||||
size_t count;
|
||||
LcameraDevCameraInfo* cameras; /* caller frees via lcameraDev_freeEnumerateResult */
|
||||
};
|
||||
|
||||
typedef LcameraDevEnumerateResult lcameraDev_enumerateCamerasFn(void);
|
||||
typedef void lcameraDev_freeEnumerateResultFn(LcameraDevEnumerateResult* result);
|
||||
typedef sscl::co::ViralNonPostingInvoker<std::vector<LcameraDevCameraInfo>>
|
||||
lcameraDev_enumerateCamerasCReqFn(void);
|
||||
```
|
||||
|
||||
### Frame access (boundary with lcameraBuff)
|
||||
### Manual verification tools
|
||||
|
||||
Exact frame-delivery API is TBD and will likely use a callback or a “dequeue
|
||||
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.
|
||||
When built with `-DENABLE_LIB_lcameraDev=ON`:
|
||||
|
||||
Principle: **one negotiated stream format per session** (prefer native YUV such
|
||||
as NV12/YUYV). `lcameraBuff` + `rasterStimulus` derive H/S/V greyscale planes
|
||||
from that single stream.
|
||||
* `lcameraDev_list_cameras` — runs `enumerateCamerasCReq` on a minimal probe
|
||||
`ComponentThread`.
|
||||
* `lcameraDev_probe <deviceSelector>` — `getOrCreateDeviceCReq`, then
|
||||
`releaseDeviceCReq` (selector and session attach/detach only).
|
||||
|
||||
## Module layout (planned)
|
||||
## Module layout
|
||||
|
||||
```text
|
||||
commonLibs/lcameraDev/
|
||||
CMakeLists.txt
|
||||
lcameraDev.h Public C API + C++ internal headers
|
||||
lcameraDev.cpp dlopen exports, CameraManager singleton
|
||||
cameraSession.cpp Refcounted session, stream negotiation
|
||||
selectorResolve.cpp deviceSelector parsing and matching
|
||||
cameraEnumerate.cpp Discovery / identity records
|
||||
lcameraDev.h / lcameraDev.cpp Public C API and dlopen exports
|
||||
cameraManagerState.h / .cpp CameraManager singleton, session map
|
||||
cameraSession.h / .cpp Refcounted acquired-camera session
|
||||
cameraIdentity.h / .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
|
||||
@@ -313,8 +320,8 @@ Build links against `libcamera` (pkg-config). Does **not** link Salmanoff
|
||||
|
||||
| Component | Responsibility |
|
||||
|---|---|
|
||||
| `lcameraDev` | libcamera lifecycle, selector resolution, shared capture session |
|
||||
| `lcameraBuff` | `StimBuffApiDesc`, `StimulusProducer`, per-channel ring buffers, intrins |
|
||||
| `lcameraDev` | libcamera lifecycle, selector resolution, refcounted acquired camera session |
|
||||
| `lcameraBuff` | Stream setup, frames, `StimBuffApiDesc`, channel fan-out, intrins |
|
||||
| `rasterStimulus` (future) | YUV↔HSV, plane extraction, threshold masks, stencil geometry |
|
||||
| `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
|
||||
|
||||
1. **Stream format defaults** — prefer first supported YUV format, or allow
|
||||
`lcameraBuff(pixfmt=...)` override?
|
||||
2. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
|
||||
1. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
|
||||
and drop the session, or attempt re-enumeration by stored `lcamera-id:`?
|
||||
3. **Thread model** — libcamera callbacks vs polling in the Body thread
|
||||
production daemon; align with `StimulusProducer::productionCDaemon` timing
|
||||
(`CONFIG_STIMBUFF_FRAME_PERIOD_MS`).
|
||||
4. **IPA packaging** — document per-platform `apt install` requirements in the
|
||||
2. **IPA packaging** — document per-platform `apt install` requirements in the
|
||||
main README when `lcameraBuff` lands (RPi needs `libcamera-ipa`).
|
||||
|
||||
Stream format, frame timing, and libcamera callback threading are owned by
|
||||
`lcameraBuff`, not `lcameraDev`.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
/* Common Libraries */
|
||||
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED
|
||||
#cmakedefine CONFIG_LIB_LIVOXPROTO1_ENABLED
|
||||
#cmakedefine CONFIG_LIB_LCAMERADEV_ENABLED
|
||||
#cmakedefine CONFIG_LIB_ALSA_ENABLED
|
||||
|
||||
/* Stim Buff APIs */
|
||||
|
||||
Reference in New Issue
Block a user