lcameraDev: add resolve-only deviceSelector API and deduplicate resolve paths.

Export lcameraDev_resolveDeviceSelectorCReq for attach-identity consumers,
factor live-camera snapshot helpers, and share resolveDeviceSelectorAgainstRecords
with get-or-create session acquisition.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 11:02:11 -04:00
parent 7a47f2bd49
commit 7af684039d
9 changed files with 229 additions and 34 deletions
+62 -22
View File
@@ -2,7 +2,6 @@
#include <cameraIdentity.h>
#include <cameraManagerState.h>
#include <selectorParse.h>
#include <selectorResolve.h>
#include <algorithm>
#include <stdexcept>
@@ -49,6 +48,40 @@ std::shared_ptr<libcamera::Camera> findCameraById(
return nullptr;
}
struct LiveCameraSnapshot
{
std::vector<std::shared_ptr<libcamera::Camera>> cameras;
std::vector<CameraIdentityRecord> identityRecords;
};
LiveCameraSnapshot snapshotLiveCameras(CameraManagerResources& resources)
{
LiveCameraSnapshot snapshot;
snapshot.cameras = resources.cameraManager->cameras();
snapshot.identityRecords = buildIdentityRecords(snapshot.cameras);
return snapshot;
}
CameraIdentityRecord resolveDeviceSelectorForLiveCameras(
const std::string& deviceSelector,
CameraManagerResources& resources)
{
const LiveCameraSnapshot snapshot = snapshotLiveCameras(resources);
return resolveDeviceSelectorAgainstRecords(
deviceSelector, snapshot.identityRecords);
}
void requireNonEmptyDeviceSelector(
const std::string& deviceSelector,
const char *apiName)
{
if (deviceSelector.empty())
{
throw std::runtime_error(
std::string(apiName) + ": deviceSelector is empty");
}
}
} // namespace
LcameraDevState& getLcameraDevState()
@@ -110,29 +143,38 @@ void lcameraDevExit()
state.isInitialized = false;
}
sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
getOrCreateDeviceSessionCReq(const std::string& deviceSelector)
sscl::co::ViralNonPostingInvoker<CameraIdentityRecord>
resolveDeviceSelectorCReq(const std::string& deviceSelector)
{
if (deviceSelector.empty())
{
throw std::runtime_error(
"lcameraDev_getOrCreateDeviceCReq: deviceSelector is empty");
}
requireNonEmptyDeviceSelector(
deviceSelector, "lcameraDev_resolveDeviceSelectorCReq");
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);
co_return resolveDeviceSelectorForLiveCameras(
deviceSelector, state.managerState.rsrc);
}
sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
getOrCreateDeviceSessionCReq(const std::string& deviceSelector)
{
requireNonEmptyDeviceSelector(
deviceSelector, "lcameraDev_getOrCreateDeviceCReq");
LcameraDevState& state = getLcameraDevState();
sscl::co::CoQutex::ReleaseHandle managerGuard =
co_await state.managerState.lock.getAcquireInvocationAndSuspensionPolicy();
const LiveCameraSnapshot snapshot =
snapshotLiveCameras(state.managerState.rsrc);
const CameraIdentityRecord resolvedRecord =
resolveSelectorAgainstRecords(criteria, identityRecords);
resolveDeviceSelectorAgainstRecords(
deviceSelector, snapshot.identityRecords);
const std::string& resolvedCameraId = resolvedRecord.id;
auto sessionIt =
@@ -153,7 +195,7 @@ getOrCreateDeviceSessionCReq(const std::string& deviceSelector)
}
std::shared_ptr<libcamera::Camera> camera =
findCameraById(cameras, resolvedCameraId);
findCameraById(snapshot.cameras, resolvedCameraId);
if (!camera)
{
throw std::runtime_error(
@@ -228,15 +270,13 @@ enumerateCamerasCReq()
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);
const LiveCameraSnapshot snapshot =
snapshotLiveCameras(state.managerState.rsrc);
std::vector<LcameraDevCameraInfo> cameraInfos;
cameraInfos.reserve(identityRecords.size());
cameraInfos.reserve(snapshot.identityRecords.size());
for (const CameraIdentityRecord& record : identityRecords)
for (const CameraIdentityRecord& record : snapshot.identityRecords)
{
cameraInfos.push_back(LcameraDevCameraInfo{
record.id,
@@ -40,6 +40,9 @@ void lcameraDevExit();
std::vector<std::shared_ptr<libcamera::Camera>> listLibcameraCameras();
sscl::co::ViralNonPostingInvoker<CameraIdentityRecord>
resolveDeviceSelectorCReq(const std::string& deviceSelector);
sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
getOrCreateDeviceSessionCReq(const std::string& deviceSelector);
+13
View File
@@ -30,6 +30,19 @@ lcameraDev_getOrCreateDeviceCReq(const std::string& deviceSelector)
co_return co_await lcamera_dev::getOrCreateDeviceSessionCReq(deviceSelector);
}
sscl::co::ViralNonPostingInvoker<lcamera_dev::CameraIdentityRecord>
lcameraDev_resolveDeviceSelectorCReq(const std::string& deviceSelector)
{
lcamera_dev::LcameraDevState& state = lcamera_dev::getLcameraDevState();
if (!state.isInitialized)
{
throw std::runtime_error(
"lcameraDev_resolveDeviceSelectorCReq: call lcameraDev_main first");
}
co_return co_await lcamera_dev::resolveDeviceSelectorCReq(deviceSelector);
}
sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReq(
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession)
+4
View File
@@ -40,6 +40,9 @@ typedef void lcameraDev_exitFn(void);
typedef sscl::co::ViralNonPostingInvoker<lcamera_dev::LcameraDevGetOrCreateResult>
lcameraDev_getOrCreateDeviceCReqFn(const std::string& deviceSelector);
typedef sscl::co::ViralNonPostingInvoker<lcamera_dev::CameraIdentityRecord>
lcameraDev_resolveDeviceSelectorCReqFn(const std::string& deviceSelector);
typedef sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReqFn(
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession);
@@ -55,6 +58,7 @@ typedef sscl::co::ViralNonPostingInvoker<lcamera_dev::LcameraDevConfiguredCamera
lcameraDev_mainFn lcameraDev_main;
lcameraDev_exitFn lcameraDev_exit;
lcameraDev_getOrCreateDeviceCReqFn lcameraDev_getOrCreateDeviceCReq;
lcameraDev_resolveDeviceSelectorCReqFn lcameraDev_resolveDeviceSelectorCReq;
lcameraDev_releaseDeviceCReqFn lcameraDev_releaseDeviceCReq;
lcameraDev_enumerateCamerasCReqFn lcameraDev_enumerateCamerasCReq;
lcameraDev_configureSessionModeCReqFn lcameraDev_configureSessionModeCReq;
@@ -173,4 +173,13 @@ CameraIdentityRecord resolveSelectorAgainstRecords(
return *matches.front();
}
CameraIdentityRecord resolveDeviceSelectorAgainstRecords(
const std::string& deviceSelector,
const std::vector<CameraIdentityRecord>& records)
{
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector(deviceSelector);
return resolveSelectorAgainstRecords(criteria, records);
}
} // namespace lcamera_dev
+4
View File
@@ -12,6 +12,10 @@ CameraIdentityRecord resolveSelectorAgainstRecords(
const std::vector<SelectorCriterion>& criteria,
const std::vector<CameraIdentityRecord>& records);
CameraIdentityRecord resolveDeviceSelectorAgainstRecords(
const std::string& deviceSelector,
const std::vector<CameraIdentityRecord>& records);
std::string formatCameraListForDiagnostics(
const std::vector<CameraIdentityRecord>& records);
@@ -71,6 +71,16 @@ sscl::co::NonViralNonPostingInvoker releaseCInd(
co_return;
}
sscl::co::NonViralNonPostingInvoker resolveSelectorCInd(
std::exception_ptr &, std::function<void()>,
const char *deviceSelector,
lcamera_dev::CameraIdentityRecord& resolvedIdentity)
{
resolvedIdentity =
co_await lcameraDev_resolveDeviceSelectorCReq(deviceSelector);
co_return;
}
void runLcameraDevMainAndNurseryTask(
const std::function<void(
const std::shared_ptr<sscl::ComponentThread>&)>& work)
@@ -189,5 +199,62 @@ TEST_F(LcameraDevHilTest, GetOrCreateByBakedSelector)
});
}
TEST_F(LcameraDevHilTest, ResolveDeviceSelectorMatchesGetOrCreateIdentity)
{
runLcameraDevMainAndNurseryTask(
[this](const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
for (const test_fixtures::BakedCameraProfile *profile :
requiredProfiles)
{
lcamera_dev::CameraIdentityRecord resolvedIdentity;
lcamera_dev::LcameraDevGetOrCreateResult createResult;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &resolvedIdentity](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return resolveSelectorCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
resolvedIdentity);
});
EXPECT_EQ(resolvedIdentity.id, profile->libcameraId)
<< profile->profileTag;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return getOrCreateCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
createResult);
});
EXPECT_EQ(
createResult.resolvedIdentity.id,
resolvedIdentity.id)
<< profile->profileTag;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[&createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return releaseCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession);
});
}
});
}
} // namespace
} // namespace lcamera_dev
@@ -45,6 +45,22 @@ TEST(FormatCameraListForDiagnosticsTest, ListsIndexedCameras)
EXPECT_NE(text.find("location=front"), std::string::npos);
}
TEST(ResolveDeviceSelectorAgainstRecordsTest, BakedUsbHdmiCameraMatchesExampleSelector)
{
const std::vector<CameraIdentityRecord> records =
tests::bakedProfilesAsRecords(dellLaptopMachineTag);
const CameraIdentityRecord resolved =
resolveDeviceSelectorAgainstRecords(
"model-substr:HDMI;location:EXTERNAL",
records);
EXPECT_EQ(
resolved.id,
"\\_SB_.PCI0.XHC_.RHUB.HS01-1:1.0-32e4:9415");
EXPECT_EQ(resolved.locationLabel, "external");
}
TEST(ResolveSelectorAgainstRecordsTest, BakedUsbHdmiCameraMatchesExampleSelector)
{
const std::vector<CameraIdentityRecord> records =