LCamDev: implement configureSessionModeCReq

We can, theoretically, now change the v4l camera's mode.
This commit is contained in:
2026-06-13 20:56:33 -04:00
parent 25d7b9c013
commit 3e85b920fb
20 changed files with 1926 additions and 11 deletions
+39 -1
View File
@@ -2,6 +2,9 @@ add_executable(lcameraDev_unit_tests
selectorParse_tests.cpp
selectorResolve_tests.cpp
cameraIdentity_tests.cpp
cameraModeRequest_tests.cpp
planarYuvFormatPolicy_tests.cpp
sessionModeConfigure_state_tests.cpp
)
target_include_directories(lcameraDev_unit_tests PRIVATE
@@ -9,18 +12,26 @@ target_include_directories(lcameraDev_unit_tests PRIVATE
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev/tests
${CMAKE_SOURCE_DIR}/tests/fixtures
${CMAKE_SOURCE_DIR}/libspinscale/tests
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
${LIBCAMERA_INCLUDE_DIRS}
)
target_link_libraries(lcameraDev_unit_tests
gtest_main
lcameraDev
spinscale
spinscale_test_support
${Boost_LIBRARIES}
${LIBCAMERA_LIBRARIES}
)
add_dependencies(lcameraDev_unit_tests gtest_main)
target_link_directories(lcameraDev_unit_tests PRIVATE
${LIBCAMERA_LIBRARY_DIRS}
)
add_dependencies(lcameraDev_unit_tests gtest_main spinscale_test_support)
add_test(NAME lcameraDev_unit_tests COMMAND lcameraDev_unit_tests)
@@ -50,3 +61,30 @@ add_dependencies(lcameraDev_hil_tests gtest_main spinscale_test_support)
add_test(NAME lcameraDev_hil_tests COMMAND lcameraDev_hil_tests)
set_tests_properties(lcameraDev_hil_tests PROPERTIES LABELS "HIL")
add_executable(lcameraDev_configure_hil_tests
lcameraDev_configure_hil_tests.cpp
)
target_include_directories(lcameraDev_configure_hil_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev/tests
${CMAKE_SOURCE_DIR}/tests/fixtures
${CMAKE_SOURCE_DIR}/libspinscale/tests
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(lcameraDev_configure_hil_tests
gtest_main
lcameraDev
spinscale
spinscale_test_support
${Boost_LIBRARIES}
)
add_dependencies(lcameraDev_configure_hil_tests gtest_main spinscale_test_support)
add_test(NAME lcameraDev_configure_hil_tests COMMAND lcameraDev_configure_hil_tests)
set_tests_properties(lcameraDev_configure_hil_tests PROPERTIES LABELS "HIL")
@@ -0,0 +1,90 @@
#include <cameraModeRequest.h>
#include <gtest/gtest.h>
#include <support/exceptionAssertions.h>
namespace lcamera_dev {
namespace {
TEST(CameraModeRequestTest, DefaultFullPlanarIsOptionalIsFalse)
{
LcameraDevCameraModeRequest request;
EXPECT_FALSE(request.fullPlanarIsOptional);
}
TEST(CameraModeRequestTest, ZeroWidthThrows)
{
LcameraDevCameraModeRequest request;
request.width = 0;
request.height = 480;
EXPECT_THROW(
validateCameraModeRequest(request),
std::runtime_error);
}
TEST(CameraModeRequestTest, ZeroHeightThrows)
{
LcameraDevCameraModeRequest request;
request.width = 640;
request.height = 0;
EXPECT_THROW(
validateCameraModeRequest(request),
std::runtime_error);
}
TEST(CameraModeRequestTest, UnsupportedColourSpaceThrows)
{
LcameraDevCameraModeRequest request;
request.width = 640;
request.height = 480;
static_assert(
static_cast<int>(LcameraDevColourSpace::Yuv) == 0,
"test assumes Yuv is first enumerator");
const LcameraDevColourSpace unsupportedColourSpace =
static_cast<LcameraDevColourSpace>(1);
request.colourSpace = unsupportedColourSpace;
EXPECT_THROW(
validateCameraModeRequest(request),
std::runtime_error);
}
TEST(CameraModeRequestTest, FullPlanarOptionalRejectedAtConfigureApi)
{
LcameraDevCameraModeRequest request;
request.width = 640;
request.height = 480;
request.fullPlanarIsOptional = true;
try {
rejectFullPlanarOptionalAtConfigureApi(request);
FAIL() << "Expected runtime_error";
}
catch (const std::runtime_error& exception)
{
sscl::tests::expectExceptionMessageContains(
exception,
"not honored yet");
}
}
TEST(CameraModeRequestTest, CameraModeRequestsEqualComparesAllFields)
{
LcameraDevCameraModeRequest left;
left.width = 640;
left.height = 480;
left.colourSpace = LcameraDevColourSpace::Yuv;
left.fullPlanarIsOptional = false;
LcameraDevCameraModeRequest right = left;
EXPECT_TRUE(cameraModeRequestsEqual(left, right));
right.height = 720;
EXPECT_FALSE(cameraModeRequestsEqual(left, right));
}
} // namespace
} // namespace lcamera_dev
@@ -0,0 +1,541 @@
#include <boostAsioLinkageFix.h>
#include <catalogHelpers.h>
#include <cameraSession.h>
#include <lcameraDev.h>
#include <gtest/gtest.h>
#include <spinscale/co/nonViralTaskNursery.h>
#include <support/bakedDeviceCatalog.h>
#include <support/exceptionAssertions.h>
#include <support/probeComponentThread.h>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace lcamera_dev {
namespace {
constexpr const char *hilEnvVar = "LCAMERADEV_HIL";
constexpr const char *machineEnvVar = "LCAMERADEV_MACHINE";
constexpr const char *configureWidthEnvVar = "LCAMERADEV_CONFIGURE_W";
constexpr const char *configureHeightEnvVar = "LCAMERADEV_CONFIGURE_H";
constexpr const char *defaultMachineTag = "dell-laptop";
constexpr unsigned defaultConfigureWidth = 640;
constexpr unsigned defaultConfigureHeight = 480;
bool hilTestsEnabled()
{
const char *value = std::getenv(hilEnvVar);
return value != nullptr && std::string(value) == "1";
}
std::string machineTagFromEnvironment()
{
const char *value = std::getenv(machineEnvVar);
if (value != nullptr && std::string(value).size() > 0) {
return value;
}
return defaultMachineTag;
}
unsigned unsignedFromEnvironmentOrDefault(
const char *envVar,
unsigned defaultValue)
{
const char *value = std::getenv(envVar);
if (value == nullptr || std::string(value).empty()) {
return defaultValue;
}
return static_cast<unsigned>(std::stoul(value));
}
LcameraDevCameraModeRequest configureRequestFromEnvironment()
{
LcameraDevCameraModeRequest request;
request.width = unsignedFromEnvironmentOrDefault(
configureWidthEnvVar,
defaultConfigureWidth);
request.height = unsignedFromEnvironmentOrDefault(
configureHeightEnvVar,
defaultConfigureHeight);
request.colourSpace = LcameraDevColourSpace::Yuv;
request.fullPlanarIsOptional = false;
return request;
}
sscl::co::NonViralNonPostingInvoker getOrCreateCInd(
std::exception_ptr& exceptionStorage,
std::function<void()> callerLambda,
const char *deviceSelector,
lcamera_dev::LcameraDevGetOrCreateResult& createResult)
{
(void)exceptionStorage;
(void)callerLambda;
createResult = co_await lcameraDev_getOrCreateDeviceCReq(deviceSelector);
co_return;
}
sscl::co::NonViralNonPostingInvoker configureCInd(
std::exception_ptr& exceptionStorage,
std::function<void()> callerLambda,
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession,
const LcameraDevCameraModeRequest& request,
LcameraDevConfiguredCameraMode& configuredMode)
{
(void)callerLambda;
configuredMode =
co_await lcameraDev_configureSessionModeCReq(deviceSession, request);
if (exceptionStorage) {
std::rethrow_exception(exceptionStorage);
}
co_return;
}
sscl::co::NonViralNonPostingInvoker releaseCInd(
std::exception_ptr& exceptionStorage,
std::function<void()> callerLambda,
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession)
{
(void)exceptionStorage;
(void)callerLambda;
co_await lcameraDev_releaseDeviceCReq(deviceSession);
co_return;
}
void runLcameraDevMainAndNurseryTask(
const std::function<void(
const std::shared_ptr<sscl::ComponentThread>&)>& work)
{
sscl::tests::ProbeComponentThreadHarness harness("lcameraDev-configure-hil");
harness.runSync(
[&work](const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcameraDev_main(componentThread);
work(componentThread);
lcameraDev_exit();
});
}
void runNonViralNurseryRethrowingOnComponentThread(
const std::shared_ptr<sscl::ComponentThread>& componentThread,
const std::function<sscl::co::NonViralNonPostingInvoker(
sscl::co::NonViralTaskNursery::Slot::Lease&)>& invokerFactory)
{
std::exception_ptr slotException;
sscl::co::NonViralTaskNursery nursery;
nursery.openAdmission();
nursery.launch(
invokerFactory,
[&slotException](std::exception_ptr& exceptionPtr)
{
slotException = exceptionPtr;
});
nursery.closeAdmission();
nursery.syncAwaitAllSettlements(componentThread->getIoContext());
if (slotException) {
std::rethrow_exception(slotException);
}
}
bool configureSessionOrExpectPlanarFailure(
const std::shared_ptr<sscl::ComponentThread>& componentThread,
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession,
const LcameraDevCameraModeRequest& request,
LcameraDevConfiguredCameraMode& configuredMode,
const char *profileTag)
{
try {
runNonViralNurseryRethrowingOnComponentThread(
componentThread,
[&deviceSession, &request, &configuredMode](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return configureCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
deviceSession,
request,
configuredMode);
});
EXPECT_TRUE(configuredMode.isFullyPlanar) << profileTag;
EXPECT_GE(configuredMode.width, 1u) << profileTag;
EXPECT_GE(configuredMode.height, 1u) << profileTag;
return true;
}
catch (const std::exception& exception)
{
sscl::tests::expectExceptionMessageContains(
exception,
"planar");
return false;
}
}
void configureProfileExpectingPlanarOrExplicitFailure(
const test_fixtures::BakedCameraProfile *profile,
const LcameraDevCameraModeRequest& request,
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcamera_dev::LcameraDevGetOrCreateResult createResult;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return getOrCreateCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
createResult);
});
EXPECT_TRUE(createResult.deviceSession != nullptr)
<< profile->profileTag;
LcameraDevConfiguredCameraMode configuredMode;
configureSessionOrExpectPlanarFailure(
componentThread,
createResult.deviceSession,
request,
configuredMode,
profile->profileTag);
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[&createResult](sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return releaseCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession);
});
}
class LcameraDevConfigureHilTest : public ::testing::Test
{
protected:
void SetUp() override
{
if (!hilTestsEnabled()) {
GTEST_SKIP() << "Set " << hilEnvVar << "=1 to run hardware tests";
}
machineTag = machineTagFromEnvironment();
requiredProfiles =
sscl::tests::requiredProfilesForMachine(machineTag.c_str());
configureRequest = configureRequestFromEnvironment();
if (requiredProfiles.empty()) {
GTEST_SKIP() << "No baked profiles for machine tag "
<< machineTag;
}
}
const test_fixtures::BakedCameraProfile *findProfile(
const char *profileTag) const
{
for (const test_fixtures::BakedCameraProfile *profile :
requiredProfiles)
{
if (std::string(profile->profileTag) == profileTag) {
return profile;
}
}
return nullptr;
}
std::string machineTag;
std::vector<const test_fixtures::BakedCameraProfile *> requiredProfiles;
LcameraDevCameraModeRequest configureRequest;
};
TEST_F(LcameraDevConfigureHilTest, ConfigureUsbHdmiYuvRequiresPlanar)
{
const test_fixtures::BakedCameraProfile *profile =
findProfile("usb_hdmi_camera");
if (!profile) {
GTEST_SKIP() << "usb_hdmi_camera profile not available";
}
runLcameraDevMainAndNurseryTask(
[this, profile](
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
configureProfileExpectingPlanarOrExplicitFailure(
profile,
configureRequest,
componentThread);
});
}
TEST_F(LcameraDevConfigureHilTest, ConfigureIntegratedWebcamYuv)
{
const test_fixtures::BakedCameraProfile *profile =
findProfile("integrated_webcam");
if (!profile) {
GTEST_SKIP() << "integrated_webcam profile not available";
}
runLcameraDevMainAndNurseryTask(
[this, profile](
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
configureProfileExpectingPlanarOrExplicitFailure(
profile,
configureRequest,
componentThread);
});
}
TEST_F(LcameraDevConfigureHilTest, ConfiguredModeMatchesRequestDimensions)
{
const test_fixtures::BakedCameraProfile *profile =
findProfile("integrated_webcam");
if (!profile) {
GTEST_SKIP() << "integrated_webcam profile not available";
}
bool skipForMissingPlanarYuv = false;
runLcameraDevMainAndNurseryTask(
[this, profile, &skipForMissingPlanarYuv](
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcamera_dev::LcameraDevGetOrCreateResult createResult;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return getOrCreateCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
createResult);
});
LcameraDevConfiguredCameraMode configuredMode;
const bool configureSucceeded =
configureSessionOrExpectPlanarFailure(
componentThread,
createResult.deviceSession,
configureRequest,
configuredMode,
profile->profileTag);
if (!configureSucceeded) {
skipForMissingPlanarYuv = true;
}
else
{
EXPECT_GE(configuredMode.width, 1u);
EXPECT_GE(configuredMode.height, 1u);
EXPECT_LE(
configuredMode.width,
configureRequest.width);
EXPECT_LE(
configuredMode.height,
configureRequest.height);
}
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[&createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return releaseCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession);
});
});
if (skipForMissingPlanarYuv) {
GTEST_SKIP() << "Camera lacks fully planar YUV at requested resolution";
}
}
TEST_F(LcameraDevConfigureHilTest, IdenticalReconfigureIsNoOp)
{
const test_fixtures::BakedCameraProfile *profile =
findProfile("integrated_webcam");
if (!profile) {
GTEST_SKIP() << "integrated_webcam profile not available";
}
bool skipForMissingPlanarYuv = false;
runLcameraDevMainAndNurseryTask(
[this, profile, &skipForMissingPlanarYuv](
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcamera_dev::LcameraDevGetOrCreateResult createResult;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return getOrCreateCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
createResult);
});
LcameraDevConfiguredCameraMode firstMode;
LcameraDevConfiguredCameraMode secondMode;
const bool firstConfigureSucceeded =
configureSessionOrExpectPlanarFailure(
componentThread,
createResult.deviceSession,
configureRequest,
firstMode,
profile->profileTag);
if (!firstConfigureSucceeded) {
skipForMissingPlanarYuv = true;
}
else
{
const int configureCallCountAfterFirst =
createResult.deviceSession
->getLibcameraConfigureCallCount();
runNonViralNurseryRethrowingOnComponentThread(
componentThread,
[&createResult, &secondMode, this](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return configureCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession,
configureRequest,
secondMode);
});
EXPECT_EQ(
createResult.deviceSession
->getLibcameraConfigureCallCount(),
configureCallCountAfterFirst);
EXPECT_EQ(secondMode.pixelFormatName, firstMode.pixelFormatName);
EXPECT_EQ(secondMode.width, firstMode.width);
EXPECT_EQ(secondMode.height, firstMode.height);
}
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[&createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return releaseCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession);
});
});
if (skipForMissingPlanarYuv) {
GTEST_SKIP() << "Camera lacks fully planar YUV at requested resolution";
}
}
TEST_F(LcameraDevConfigureHilTest, ConflictingReconfigureThrows)
{
const test_fixtures::BakedCameraProfile *profile =
findProfile("integrated_webcam");
if (!profile) {
GTEST_SKIP() << "integrated_webcam profile not available";
}
bool skipForMissingPlanarYuv = false;
runLcameraDevMainAndNurseryTask(
[this, profile, &skipForMissingPlanarYuv](
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
lcamera_dev::LcameraDevGetOrCreateResult createResult;
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[profile, &createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return getOrCreateCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
profile->exampleSelector,
createResult);
});
LcameraDevConfiguredCameraMode configuredMode;
const bool configureSucceeded =
configureSessionOrExpectPlanarFailure(
componentThread,
createResult.deviceSession,
configureRequest,
configuredMode,
profile->profileTag);
if (!configureSucceeded) {
skipForMissingPlanarYuv = true;
}
else
{
LcameraDevCameraModeRequest conflictingRequest =
configureRequest;
conflictingRequest.width = configureRequest.width + 64;
EXPECT_THROW(
runNonViralNurseryRethrowingOnComponentThread(
componentThread,
[&createResult, &conflictingRequest](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
LcameraDevConfiguredCameraMode ignoredMode;
return configureCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession,
conflictingRequest,
ignoredMode);
}),
std::runtime_error);
}
sscl::tests::runNonViralNurseryOnComponentThread(
componentThread,
[&createResult](
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
{
return releaseCInd(
lease.getExceptionStorage(),
lease.getCallerLambda(),
createResult.deviceSession);
});
});
if (skipForMissingPlanarYuv) {
GTEST_SKIP() << "Camera lacks fully planar YUV at requested resolution";
}
}
} // namespace
} // namespace lcamera_dev
@@ -0,0 +1,71 @@
#include <planarYuvFormatPolicy.h>
#include <gtest/gtest.h>
#include <libcamera/formats.h>
#include <support/exceptionAssertions.h>
#include <vector>
namespace lcamera_dev {
namespace {
using libcamera::formats::NV12;
using libcamera::formats::YUYV;
using libcamera::formats::YUV420;
TEST(PlanarYuvFormatPolicyTest, FullyPlanarRequiredPicksYuv420OverNv12)
{
const std::vector<libcamera::PixelFormat> candidates = {YUV420, NV12};
const std::optional<libcamera::PixelFormat> selected =
selectYuvCaptureFormat(candidates, false);
EXPECT_TRUE(selected.has_value());
EXPECT_EQ(*selected, YUV420);
EXPECT_TRUE(isFullyPlanarYuv(*selected));
}
TEST(PlanarYuvFormatPolicyTest, FullyPlanarRequiredThrowsWhenOnlyNonPlanar)
{
const std::vector<libcamera::PixelFormat> candidates = {NV12, YUYV};
try {
selectYuvCaptureFormat(candidates, false);
FAIL() << "Expected runtime_error";
}
catch (const std::runtime_error& exception)
{
sscl::tests::expectExceptionMessageContains(
exception,
"planar");
}
}
TEST(PlanarYuvFormatPolicyTest, FullyPlanarOptionalPicksNv12)
{
const std::vector<libcamera::PixelFormat> candidates = {NV12};
const std::optional<libcamera::PixelFormat> selected =
selectYuvCaptureFormat(candidates, true);
EXPECT_TRUE(selected.has_value());
EXPECT_EQ(*selected, NV12);
EXPECT_FALSE(isFullyPlanarYuv(*selected));
EXPECT_EQ(yuvCapturePlaneCount(*selected), 2u);
}
TEST(PlanarYuvFormatPolicyTest, EmptyCandidateListThrows)
{
const std::vector<libcamera::PixelFormat> candidates;
EXPECT_THROW(
selectYuvCaptureFormat(candidates, false),
std::runtime_error);
}
TEST(PlanarYuvFormatPolicyTest, IsFullyPlanarYuvRecognizesYuv420)
{
EXPECT_TRUE(isFullyPlanarYuv(YUV420));
EXPECT_FALSE(isFullyPlanarYuv(NV12));
}
} // namespace
} // namespace lcamera_dev
@@ -0,0 +1,138 @@
#include <cameraModeRequest.h>
#include <cameraSession.h>
#include <gtest/gtest.h>
#include <sessionModeConfigure.h>
#include <memory>
namespace lcamera_dev {
namespace {
LcameraDevConfiguredCameraMode syntheticResolvedMode(
unsigned width,
unsigned height,
const char *pixelFormatName)
{
LcameraDevConfiguredCameraMode mode;
mode.width = width;
mode.height = height;
mode.colourSpace = LcameraDevColourSpace::Yuv;
mode.pixelFormatName = pixelFormatName;
mode.isFullyPlanar = true;
mode.planeCount = 3;
return mode;
}
LcameraDevCameraModeRequest makeRequest(unsigned width, unsigned height)
{
LcameraDevCameraModeRequest request;
request.width = width;
request.height = height;
request.colourSpace = LcameraDevColourSpace::Yuv;
request.fullPlanarIsOptional = false;
return request;
}
TEST(SessionModeConfigureStateTest, FirstConfigureMarksSessionConfigured)
{
CameraSessionResources resources(
CameraIdentityRecord{},
std::shared_ptr<libcamera::Camera>());
const LcameraDevCameraModeRequest request = makeRequest(640, 480);
const LcameraDevConfiguredCameraMode resolvedMode =
syntheticResolvedMode(640, 480, "YU12");
const ConfigureSessionModeStatus status =
applyModeRequestToSessionState(
resources,
request,
resolvedMode,
nullptr);
EXPECT_EQ(status, ConfigureSessionModeStatus::Configured);
EXPECT_TRUE(resources.configuredMode.has_value());
EXPECT_EQ(resources.configuredMode->width, 640u);
EXPECT_EQ(resources.configuredMode->pixelFormatName, "YU12");
EXPECT_EQ(resources.libcameraConfigureCallCount, 1);
}
TEST(SessionModeConfigureStateTest, IdenticalReconfigureIsNoOp)
{
CameraSessionResources resources(
CameraIdentityRecord{},
std::shared_ptr<libcamera::Camera>());
const LcameraDevCameraModeRequest request = makeRequest(640, 480);
const LcameraDevConfiguredCameraMode resolvedMode =
syntheticResolvedMode(640, 480, "YU12");
applyModeRequestToSessionState(
resources,
request,
resolvedMode,
nullptr);
const ConfigureSessionModeStatus status =
applyModeRequestToSessionState(
resources,
request,
resolvedMode,
nullptr);
EXPECT_EQ(status, ConfigureSessionModeStatus::NoOpAlreadyConfigured);
EXPECT_EQ(resources.libcameraConfigureCallCount, 1);
EXPECT_EQ(resources.configuredMode->pixelFormatName, "YU12");
}
TEST(SessionModeConfigureStateTest, ConflictingReconfigureThrows)
{
CameraSessionResources resources(
CameraIdentityRecord{},
std::shared_ptr<libcamera::Camera>());
const LcameraDevCameraModeRequest firstRequest = makeRequest(640, 480);
const LcameraDevConfiguredCameraMode firstMode =
syntheticResolvedMode(640, 480, "YU12");
applyModeRequestToSessionState(
resources,
firstRequest,
firstMode,
nullptr);
const LcameraDevCameraModeRequest conflictingRequest = makeRequest(1280, 720);
EXPECT_THROW(
applyModeRequestToSessionState(
resources,
conflictingRequest,
syntheticResolvedMode(1280, 720, "YU12"),
nullptr),
std::runtime_error);
}
TEST(SessionModeConfigureStateTest, GetConfiguredModeReturnsStoredValues)
{
CameraSessionResources resources(
CameraIdentityRecord{},
std::shared_ptr<libcamera::Camera>());
const LcameraDevCameraModeRequest request = makeRequest(800, 600);
const LcameraDevConfiguredCameraMode resolvedMode =
syntheticResolvedMode(800, 600, "YU12");
applyModeRequestToSessionState(
resources,
request,
resolvedMode,
nullptr);
EXPECT_EQ(resources.configuredMode->width, 800u);
EXPECT_EQ(resources.configuredMode->height, 600u);
EXPECT_EQ(resources.configuredMode->colourSpace, LcameraDevColourSpace::Yuv);
EXPECT_TRUE(resources.configuredMode->isFullyPlanar);
EXPECT_EQ(resources.configuredMode->planeCount, 3u);
}
} // namespace
} // namespace lcamera_dev