lcameraDev: Add session mgr lib for libcamera device binding
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user