diff --git a/commonLibs/lcameraDev/cameraModeRequest.cpp b/commonLibs/lcameraDev/cameraModeRequest.cpp index d6da1c4..80ac2ff 100644 --- a/commonLibs/lcameraDev/cameraModeRequest.cpp +++ b/commonLibs/lcameraDev/cameraModeRequest.cpp @@ -4,14 +4,6 @@ namespace lcamera_dev { -namespace { - -constexpr const char *incompleteOptionalPlanarMessage = - "lcameraDev: fullPlanarIsOptional/opt-planar is not honored yet " - "(non-planar producer deinterleaving is not implemented)"; - -} // namespace - void validateCameraModeRequest(const LcameraDevCameraModeRequest& request) { if (request.width == 0 || request.height == 0) @@ -28,15 +20,6 @@ void validateCameraModeRequest(const LcameraDevCameraModeRequest& request) } } -void rejectFullPlanarOptionalAtConfigureApi( - const LcameraDevCameraModeRequest& request) -{ - if (request.fullPlanarIsOptional) - { - throw std::runtime_error(incompleteOptionalPlanarMessage); - } -} - bool cameraModeRequestsEqual( const LcameraDevCameraModeRequest& left, const LcameraDevCameraModeRequest& right) diff --git a/commonLibs/lcameraDev/cameraModeRequest.h b/commonLibs/lcameraDev/cameraModeRequest.h index 2a9a143..f3c2244 100644 --- a/commonLibs/lcameraDev/cameraModeRequest.h +++ b/commonLibs/lcameraDev/cameraModeRequest.h @@ -17,8 +17,8 @@ struct LcameraDevCameraModeRequest LcameraDevColourSpace colourSpace = LcameraDevColourSpace::Yuv; /** EXPLANATION: * When false, configure must select a fully planar YUV pixel format. - * When true, relaxed non-planar formats are not honored at the configure - * API yet — producer-side deinterleaving is not implemented (Stage 2). + * When true, configure may accept semi-planar or packed YUV; lcameraBuff + * deinterleaves components in later capture stages. */ bool fullPlanarIsOptional = false; }; @@ -34,11 +34,6 @@ struct LcameraDevConfiguredCameraMode void validateCameraModeRequest(const LcameraDevCameraModeRequest& request); -/** Rejects fullPlanarIsOptional at the configure API until non-planar fallback - * paths exist in lcameraBuff. */ -void rejectFullPlanarOptionalAtConfigureApi( - const LcameraDevCameraModeRequest& request); - bool cameraModeRequestsEqual( const LcameraDevCameraModeRequest& left, const LcameraDevCameraModeRequest& right); diff --git a/commonLibs/lcameraDev/cameraSession.cpp b/commonLibs/lcameraDev/cameraSession.cpp index e535bdd..0bcb373 100644 --- a/commonLibs/lcameraDev/cameraSession.cpp +++ b/commonLibs/lcameraDev/cameraSession.cpp @@ -43,7 +43,6 @@ CameraSession::configureSessionModeCReq( } validateCameraModeRequest(request); - rejectFullPlanarOptionalAtConfigureApi(request); if (s.rsrc.configuredMode.has_value() && cameraModeRequestsEqual(s.rsrc.configuredRequest, request)) diff --git a/commonLibs/lcameraDev/sessionModeConfigure.cpp b/commonLibs/lcameraDev/sessionModeConfigure.cpp index 0f71796..1e52c3f 100644 --- a/commonLibs/lcameraDev/sessionModeConfigure.cpp +++ b/commonLibs/lcameraDev/sessionModeConfigure.cpp @@ -112,7 +112,9 @@ LcameraDevConfiguredCameraMode configureLibcameraSessionMode( const std::vector pixelFormatCandidates = streamConfig.formats().pixelformats(); const std::optional selectedPixelFormat = - selectYuvCaptureFormat(pixelFormatCandidates, false); + selectYuvCaptureFormat( + pixelFormatCandidates, + request.fullPlanarIsOptional); if (!selectedPixelFormat.has_value()) { diff --git a/commonLibs/lcameraDev/tests/cameraModeRequest_tests.cpp b/commonLibs/lcameraDev/tests/cameraModeRequest_tests.cpp index ebd89f5..34e482f 100644 --- a/commonLibs/lcameraDev/tests/cameraModeRequest_tests.cpp +++ b/commonLibs/lcameraDev/tests/cameraModeRequest_tests.cpp @@ -52,23 +52,19 @@ TEST(CameraModeRequestTest, UnsupportedColourSpaceThrows) std::runtime_error); } -TEST(CameraModeRequestTest, FullPlanarOptionalRejectedAtConfigureApi) +TEST(CameraModeRequestTest, CameraModeRequestsEqualComparesOptionalPlanarFlag) { - LcameraDevCameraModeRequest request; - request.width = 640; - request.height = 480; - request.fullPlanarIsOptional = true; + LcameraDevCameraModeRequest left; + left.width = 640; + left.height = 480; + left.colourSpace = LcameraDevColourSpace::Yuv; + left.fullPlanarIsOptional = false; - try { - rejectFullPlanarOptionalAtConfigureApi(request); - FAIL() << "Expected runtime_error"; - } - catch (const std::runtime_error& exception) - { - sscl::tests::expectExceptionMessageContains( - exception, - "not honored yet"); - } + LcameraDevCameraModeRequest right = left; + EXPECT_TRUE(cameraModeRequestsEqual(left, right)); + + right.fullPlanarIsOptional = true; + EXPECT_FALSE(cameraModeRequestsEqual(left, right)); } TEST(CameraModeRequestTest, CameraModeRequestsEqualComparesAllFields) diff --git a/commonLibs/lcameraDev/tests/lcameraDev_configure_hil_tests.cpp b/commonLibs/lcameraDev/tests/lcameraDev_configure_hil_tests.cpp index 479db0e..794cfa1 100644 --- a/commonLibs/lcameraDev/tests/lcameraDev_configure_hil_tests.cpp +++ b/commonLibs/lcameraDev/tests/lcameraDev_configure_hil_tests.cpp @@ -537,5 +537,99 @@ TEST_F(LcameraDevConfigureHilTest, ConflictingReconfigureThrows) } } +void configureProfileWithOptPlanarExpectingYuyv( + const test_fixtures::BakedCameraProfile *profile, + const LcameraDevCameraModeRequest& request, + const std::shared_ptr& 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; + LcameraDevCameraModeRequest optPlanarRequest = request; + optPlanarRequest.fullPlanarIsOptional = true; + + runNonViralNurseryRethrowingOnComponentThread( + componentThread, + [&createResult, &optPlanarRequest, &configuredMode]( + sscl::co::NonViralTaskNursery::Slot::Lease& lease) + { + return configureCInd( + lease.getExceptionStorage(), + lease.getCallerLambda(), + createResult.deviceSession, + optPlanarRequest, + configuredMode); + }); + + EXPECT_FALSE(configuredMode.isFullyPlanar) << profile->profileTag; + EXPECT_EQ(configuredMode.planeCount, 1u) << profile->profileTag; + EXPECT_EQ(configuredMode.pixelFormatName, "YUYV") << profile->profileTag; + EXPECT_GE(configuredMode.width, 1u) << profile->profileTag; + EXPECT_GE(configuredMode.height, 1u) << profile->profileTag; + + sscl::tests::runNonViralNurseryOnComponentThread( + componentThread, + [&createResult](sscl::co::NonViralTaskNursery::Slot::Lease& lease) + { + return releaseCInd( + lease.getExceptionStorage(), + lease.getCallerLambda(), + createResult.deviceSession); + }); +} + +TEST_F(LcameraDevConfigureHilTest, ConfigureUsbHdmiYuvWithOptPlanarSelectsYuyv) +{ + 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& componentThread) + { + configureProfileWithOptPlanarExpectingYuyv( + profile, + configureRequest, + componentThread); + }); +} + +TEST_F(LcameraDevConfigureHilTest, ConfigureIntegratedWebcamYuvWithOptPlanarSelectsYuyv) +{ + const test_fixtures::BakedCameraProfile *profile = + findProfile("integrated_webcam"); + if (!profile) { + GTEST_SKIP() << "integrated_webcam profile not available"; + } + + runLcameraDevMainAndNurseryTask( + [this, profile]( + const std::shared_ptr& componentThread) + { + configureProfileWithOptPlanarExpectingYuyv( + profile, + configureRequest, + componentThread); + }); +} + } // namespace } // namespace lcamera_dev diff --git a/commonLibs/lcameraDev/tests/planarYuvFormatPolicy_tests.cpp b/commonLibs/lcameraDev/tests/planarYuvFormatPolicy_tests.cpp index edbd02c..c15f6ad 100644 --- a/commonLibs/lcameraDev/tests/planarYuvFormatPolicy_tests.cpp +++ b/commonLibs/lcameraDev/tests/planarYuvFormatPolicy_tests.cpp @@ -7,6 +7,7 @@ namespace lcamera_dev { namespace { +using libcamera::formats::MJPEG; using libcamera::formats::NV12; using libcamera::formats::YUYV; using libcamera::formats::YUV420; @@ -67,5 +68,18 @@ TEST(PlanarYuvFormatPolicyTest, IsFullyPlanarYuvRecognizesYuv420) EXPECT_FALSE(isFullyPlanarYuv(NV12)); } +TEST(PlanarYuvFormatPolicyTest, FullyPlanarOptionalPicksYuyvOverMjpeg) +{ + const std::vector candidates = {MJPEG, YUYV}; + + const std::optional selected = + selectYuvCaptureFormat(candidates, true); + + EXPECT_TRUE(selected.has_value()); + EXPECT_EQ(*selected, YUYV); + EXPECT_FALSE(isFullyPlanarYuv(*selected)); + EXPECT_EQ(yuvCapturePlaneCount(*selected), 1u); +} + } // namespace } // namespace lcamera_dev diff --git a/tests/fixtures/bakedCameraProfiles.h b/tests/fixtures/bakedCameraProfiles.h index c600af9..4750331 100644 --- a/tests/fixtures/bakedCameraProfiles.h +++ b/tests/fixtures/bakedCameraProfiles.h @@ -31,7 +31,8 @@ struct BakedCameraProfile // Configure HIL note (2026-06-10): both cameras expose MJPEG and packed YUYV // via libcamera VideoRecording/Viewfinder — no fully planar YUV420/NV12 at // 640x480. configureSessionModeCReq correctly throws when fullPlanarIsOptional -// is false; success-path HIL tests skip on this machine. +// is false; with opt-planar / fullPlanarIsOptional=true configure succeeds +// selecting YUYV (Yuv422). lcameraBuff Stage 2 attach HIL uses v-res=480p. inline constexpr BakedCameraProfile bakedCameraProfiles[] = { { "dell-laptop",