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)
|
# 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,3 +1,4 @@
|
|||||||
add_subdirectory(xcbXorg)
|
add_subdirectory(xcbXorg)
|
||||||
add_subdirectory(livoxProto1)
|
add_subdirectory(livoxProto1)
|
||||||
|
add_subdirectory(lcameraDev)
|
||||||
add_subdirectory(attachmentSupport)
|
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
|
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 camera’s `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 SMO’s perspective; channel differences come from
|
the same physical device from SMO’s 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 session’s 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`.
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user