From 1c0421439ea6ad424cc23970b6eb150e23d14387 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Fri, 19 Jun 2026 10:47:08 +0100 Subject: [PATCH 1/5] libcamera: rpi: Always advertise the ScalerCrops control as available Ensure platform specific controls are advertised as still being available after calling configure(). Additionally, provide the correct defaults for the platform specific ScalerCrops control, even if there's only a single stream. This is more convenient for applications which no longer have to perform different actions depending on whether it's available. Signed-off-by: David Plowman --- src/ipa/rpi/common/ipa_base.cpp | 5 ++++ .../pipeline/rpi/common/pipeline_base.cpp | 27 +++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index f982e0bfd..14b3afe57 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -291,6 +291,11 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa } } + /* Platform specific controls should be available after configure() too. */ + auto platformCtrlsIt = platformControls.find(controller_.getTarget()); + if (platformCtrlsIt != platformControls.end()) + ctrlMap.merge(ControlInfoMap::Map(platformCtrlsIt->second)); + result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls); return platformConfigure(params, result); diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index 5a5acf6a1..8cd540d8d 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -580,17 +580,22 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config) Rectangle ispMinCrop = data->scaleIspCrop(Rectangle(cropParams.ispMinCropSize)); ctrlMap[&controls::ScalerCrop] = ControlInfo(ispMinCrop, data->sensorInfo_.analogCrop, data->scaleIspCrop(cropParams.ispCrop)); - if (data->cropParams_.size() == 2) { - /* - * The control map for rpi::ScalerCrops has the min value - * as the default crop for stream 0, max value as the default - * value for stream 1. - */ - ctrlMap[&controls::rpi::ScalerCrops] = - ControlInfo(data->scaleIspCrop(data->cropParams_.at(0).ispCrop), - data->scaleIspCrop(data->cropParams_.at(1).ispCrop), - ctrlMap[&controls::ScalerCrop].def()); - } + + /* Always advertise the ScalerCrops control, even if there's only one stream. */ + Rectangle streamTwoDefault{ 65535, 65535, 65535, 65535 }; + + if (data->cropParams_.size() == 2) + streamTwoDefault = data->scaleIspCrop(data->cropParams_.at(1).ispCrop); + + /* + * The control map for rpi::ScalerCrops has the min value + * as the default crop for stream 0, max value as the default + * value for stream 1 (where a second stream exists). + */ + ctrlMap[&controls::rpi::ScalerCrops] = + ControlInfo(data->scaleIspCrop(data->cropParams_.at(0).ispCrop), + streamTwoDefault, + ctrlMap[&controls::ScalerCrop].def()); } data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap()); From 389d932846e3c9531536e4b524b1e96f4f26f802 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Fri, 19 Jun 2026 11:21:16 +0100 Subject: [PATCH 2/5] pipeline: rpi: Reorganise ISP output pipeline indexing We sometimes need to know which stream (according to its index in the camera configuration) corresponds to which ISP output branch (0 or 1). Previously this was stored within the cropParams_ (for use by the ScalerCrops controls). Here we remove the ISP index to its own array, making it easier for other functions to refer to it in future. Additionally, when implementing cropping, we rename the "index" variable to "ispIndex" for future clarity. Signed-off-by: David Plowman --- .../pipeline/rpi/common/pipeline_base.cpp | 2 +- src/libcamera/pipeline/rpi/common/pipeline_base.h | 14 +++++++++----- src/libcamera/pipeline/rpi/pisp/pisp.cpp | 13 ++++++++----- src/libcamera/pipeline/rpi/vc4/vc4.cpp | 4 ++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index 8cd540d8d..2ca068887 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -1366,7 +1366,7 @@ void CameraData::applyScalerCrop(const ControlList &controls) if (ispCrop != cropParams_.at(i).ispCrop) { cropParams_.at(i).ispCrop = ispCrop; - platformSetIspCrop(cropParams_.at(i).ispIndex, ispCrop); + platformSetIspCrop(ispIndices_[i], ispCrop); } } } diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index 758155ee0..e47fc919a 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -83,7 +83,7 @@ class CameraData : public Camera::Private Rectangle scaleIspCrop(const Rectangle &ispCrop) const; void applyScalerCrop(const ControlList &controls); - virtual void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) = 0; + virtual void platformSetIspCrop(unsigned int ispIndex, const Rectangle &ispCrop) = 0; void cameraTimeout(); void frameStarted(uint32_t sequence); @@ -135,8 +135,8 @@ class CameraData : public Camera::Private IPACameraSensorInfo sensorInfo_; struct CropParams { - CropParams(Rectangle ispCrop_, Size ispMinCropSize_, unsigned int ispIndex_) - : ispCrop(ispCrop_), ispMinCropSize(ispMinCropSize_), ispIndex(ispIndex_) + CropParams(Rectangle ispCrop_, Size ispMinCropSize_) + : ispCrop(ispCrop_), ispMinCropSize(ispMinCropSize_) { } @@ -144,10 +144,14 @@ class CameraData : public Camera::Private Rectangle ispCrop; /* Minimum crop size in ISP output pixels */ Size ispMinCropSize; - /* Index of the ISP output channel for this crop */ - unsigned int ispIndex; }; + /* + * Vector recording the ISP index (output branch number) for the streams, ordered + * according to the stream's position in the CameraConfiguration. + */ + std::vector ispIndices_; + /* Mapping of CropParams keyed by the output stream order in CameraConfiguration */ std::map cropParams_; diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp index c9d89d58b..bf1dcb327 100644 --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -814,7 +814,7 @@ class PiSPCameraData final : public RPi::CameraData bool calculateCscConfiguration(const V4L2DeviceFormat &v4l2Format, pisp_be_ccm_config &csc); int configureBe(const std::optional &yuvColorSpace); - void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) override; + void platformSetIspCrop(unsigned int ispIndex, const Rectangle &ispCrop) override; void prepareCfe(); void prepareBe(uint32_t bufferId, bool stitchSwapBuffers); @@ -1505,7 +1505,9 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf s == &isp_[Isp::Output1]; }), streams_.end()); + ispIndices_.clear(); cropParams_.clear(); + for (unsigned int i = 0; i < outStreams.size(); i++) { StreamConfiguration *cfg = outStreams[i].cfg; unsigned int ispIndex; @@ -1524,6 +1526,7 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf beEnables |= PISP_BE_RGB_ENABLE_OUTPUT0; ispIndex = 0; } + ispIndices_.push_back(ispIndex); format = outStreams[i].format; bool needs32BitConversion = adjustDeviceFormat(format); @@ -1590,7 +1593,7 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf */ cropParams_.emplace(std::piecewise_construct, std::forward_as_tuple(outStreams[i].index), - std::forward_as_tuple(ispCrop, minCrop, ispIndex)); + std::forward_as_tuple(ispCrop, minCrop)); cfg->setStream(stream); stream->setFlags(flags); @@ -2138,7 +2141,7 @@ int PiSPCameraData::configureBe(const std::optional &yuvColorSpace) return 0; } -void PiSPCameraData::platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) +void PiSPCameraData::platformSetIspCrop(unsigned int ispIndex, const Rectangle &ispCrop) { pisp_be_crop_config beCrop = { static_cast(ispCrop.x), @@ -2147,8 +2150,8 @@ void PiSPCameraData::platformSetIspCrop(unsigned int index, const Rectangle &isp static_cast(ispCrop.height) }; - LOG(RPI, Debug) << "Output " << index << " " << ispCrop.toString(); - be_->SetCrop(index, beCrop); + LOG(RPI, Debug) << "Output " << ispIndex << " " << ispCrop.toString(); + be_->SetCrop(ispIndex, beCrop); } int PiSPCameraData::platformInitIpa(ipa::RPi::InitParams ¶ms) diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp index ac86a43ad..c776db776 100644 --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp @@ -121,7 +121,7 @@ class Vc4CameraData final : public RPi::CameraData Config config_; private: - void platformSetIspCrop([[maybe_unused]] unsigned int index, const Rectangle &ispCrop) override + void platformSetIspCrop([[maybe_unused]] unsigned int ispIndex, const Rectangle &ispCrop) override { Rectangle crop = ispCrop; isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); @@ -756,7 +756,7 @@ int Vc4CameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfi */ cropParams_.emplace(std::piecewise_construct, std::forward_as_tuple(0), - std::forward_as_tuple(ispCrop, testCrop.size(), 0)); + std::forward_as_tuple(ispCrop, testCrop.size())); return 0; } From 418a6a3d0e51ab4e946cb6e63b529c328e62e85c Mon Sep 17 00:00:00 2001 From: David Plowman Date: Fri, 19 Jun 2026 11:45:29 +0100 Subject: [PATCH 3/5] controls: rpi: Add the OutputWindows control This control allows the camera images to occupy only a smaller "window" within the overall output. It is principally intended for machine learning applications that might want to create letterboxed versions of the camera image directly in a larger square output. Signed-off-by: David Plowman --- src/libcamera/control_ids_rpi.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/libcamera/control_ids_rpi.yaml b/src/libcamera/control_ids_rpi.yaml index 9d0f30002..20113e208 100644 --- a/src/libcamera/control_ids_rpi.yaml +++ b/src/libcamera/control_ids_rpi.yaml @@ -59,6 +59,29 @@ controls: \sa ScalerCrop + - OutputWindows: + type: Rectangle + size: [n] + direction: inout + description: | + An array of rectangles, where each defines a region within the output + image for the stream corresponding to the position in this array. + + The camera image is resized and shifted to occupy only this + particular window within the overall output image, and is intended + for applications that want the camera image to be (for example) + "letterboxed" into a larger output (such as a number of machine + learning applications). + + There are platform specific limits on how small these windows can be, + and in particular format specific alignment constraints will apply to + the horizontal offset of the window. + + Unless changed, the output windows will occupy the entire image + buffers, in the usual manner. + + The OutputWindows control is supported only on the Pi5/PiSP platform. + - PispStatsOutput: type: uint8_t direction: out From 8cba62c5d1cc784761a2fb0cde075f09268cd5a5 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Fri, 19 Jun 2026 12:29:30 +0100 Subject: [PATCH 4/5] pipeline: rpi: pisp: Store more information about about buffers Add a small OutputInfo structure to store some extra useful information about the output buffers, such as the pixel alignment required for output format, and the minimum image width we can make before having to resort to software downscaling. This information will be used by future features. Signed-off-by: David Plowman --- src/libcamera/pipeline/rpi/pisp/pisp.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp index bf1dcb327..46d8d605a 100644 --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -51,6 +51,11 @@ enum class Cfe : unsigned int { Output0, Embedded, Stats, Config }; enum class Isp : unsigned int { Input, Output0, Output1, TdnInput, TdnOutput, StitchInput, StitchOutput, Config }; +struct OutputInfo { + unsigned int minWidth; + unsigned int alignment; +}; + /* Offset for all compressed buffers; mode for TDN and Stitch. */ constexpr unsigned int DefaultCompressionOffset = 2048; constexpr unsigned int DefaultCompressionMode = 1; @@ -691,12 +696,15 @@ unsigned int getFormatAlignment(const V4L2PixelFormat &fourcc) /* Calculate the amount of software downscale required (which is a power of 2). */ unsigned int calculateSwDownscale(const V4L2DeviceFormat &format, unsigned int largestWidth, - unsigned int platformMaxDownscale) + unsigned int platformMaxDownscale, OutputInfo *outputInfo = nullptr) { unsigned int formatAlignment = getFormatAlignment(format.fourcc); unsigned int maxDownscale = platformMaxDownscale * 16 / formatAlignment; unsigned int limitWidth = largestWidth / maxDownscale; + if (outputInfo) + *outputInfo = { limitWidth, formatAlignment }; + unsigned int hwWidth = format.size.width; unsigned int swDownscale = 1; for (; hwWidth < limitWidth; hwWidth *= 2, swDownscale *= 2); @@ -842,6 +850,8 @@ class PiSPCameraData final : public RPi::CameraData } std::string last_dump_file_; + + std::vector outputInfo_; }; class PipelineHandlerPiSP : public RPi::PipelineHandlerBase @@ -1538,8 +1548,10 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf PixelFormat pixFmt = format.fourcc.toPixelFormat(); /* If there's excessive downscaling we'll do some of it in software. */ + outputInfo_.emplace_back(OutputInfo{ 0, 0 }); unsigned int swDownscale = calculateSwDownscale(format, largestWidth, - be_->GetMaxDownscale()); + be_->GetMaxDownscale(), + &outputInfo_.back()); unsigned int hwWidth = format.size.width * swDownscale; format.size.width = hwWidth; From 98268583fa58d94ee515a044c0016afb2d29d19b Mon Sep 17 00:00:00 2001 From: David Plowman Date: Fri, 19 Jun 2026 13:40:21 +0100 Subject: [PATCH 5/5] pipeline: rpi: Implement OutputWindows control We add the OutputWindows control and apply it to the pipeline at the same time as the ScalerCrops (the applyScalerCrops function is renamed to applyScalerCropsAndWindows). The platform specific implementation for the Pi5/PiSP checks the validity (size and alignment) of the output windows using the extra OutputInfo that is now available. We only allow modification of the output window when no software downscaling is required, as this limits the amount of pipeline reconfiguration we might need to perform at runtime (though we could revisit this at a later date). Signed-off-by: David Plowman --- src/ipa/rpi/common/ipa_base.cpp | 8 ++- .../pipeline/rpi/common/pipeline_base.cpp | 34 +++++++++++- .../pipeline/rpi/common/pipeline_base.h | 6 ++- src/libcamera/pipeline/rpi/pisp/pisp.cpp | 53 ++++++++++++++++++- src/libcamera/pipeline/rpi/vc4/vc4.cpp | 8 ++- 5 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index 14b3afe57..7d5803181 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -118,7 +118,8 @@ const ControlInfoMap::Map ipaAfControls{ /* Platform specific controls */ const std::map platformControls { { "pisp", { - { &controls::rpi::ScalerCrops, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) } + { &controls::rpi::ScalerCrops, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, + { &controls::rpi::OutputWindows, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, } }, }; @@ -1321,6 +1322,11 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::rpi::OUTPUT_WINDOWS: { + /* The IPA does nothing with this, but avoid the warning. */ + break; + } + case controls::FRAME_DURATION_LIMITS: { auto frameDurations = ctrl.second.get>(); applyFrameDurations(frameDurations[0] * 1.0us, frameDurations[1] * 1.0us); diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index 2ca068887..cb6c9f236 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -659,7 +659,7 @@ int PipelineHandlerBase::start(Camera *camera, const ControlList *controls) /* Check if a ScalerCrop control was specified. */ if (controls) - data->applyScalerCrop(*controls); + data->applyScalerCropAndWindows(*controls); /* Start the IPA. */ ipa::RPi::StartResult result; @@ -1319,7 +1319,7 @@ Rectangle CameraData::scaleIspCrop(const Rectangle &ispCrop) const return nativeCrop; } -void CameraData::applyScalerCrop(const ControlList &controls) +void CameraData::applyScalerCropAndWindows(const ControlList &controls) { const auto &scalerCropRPi = controls.get>(controls::rpi::ScalerCrops); const auto &scalerCropCore = controls.get(controls::ScalerCrop); @@ -1369,6 +1369,31 @@ void CameraData::applyScalerCrop(const ControlList &controls) platformSetIspCrop(ispIndices_[i], ispCrop); } } + + const auto &outputWindows = controls.get>(controls::rpi::OutputWindows); + if (outputWindows) { + for (unsigned int i = 0; i < outputWindows->size() && i < outputWindows_.size(); i++) { + const Rectangle &outputWindow = outputWindows->data()[i]; + const StreamConfiguration &streamConfig = streams_[i]->configuration(); + + /* Do some basic checks here; platformSetOutputWindow will do the rest. */ + if (outputWindow.x + outputWindow.width > streamConfig.size.width) + LOG(RPI, Warning) << "Offset " << outputWindow.x << " plus width " + << outputWindow.width << " exceed maximum " + << streamConfig.size.width; + else if (outputWindow.y + outputWindow.height > streamConfig.size.height) + LOG(RPI, Warning) << "Offset " << outputWindow.y << " plus height " + << outputWindow.height << " exceed maximum " + << streamConfig.size.height; + else { + LOG(RPI, Debug) << "Ouptut window " << i << " set to " + << outputWindow.width << " x " << outputWindow.height; + /* platformSetOutputWindow will issue a warning if it fails. */ + if (platformSetOutputWindow(i, outputWindow) == 0) + outputWindows_[i] = outputWindow; + } + } + } } void CameraData::cameraTimeout() @@ -1535,6 +1560,11 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request crops.size())); } } + + if (outputWindows_.size()) + request->_d()->metadata().set(controls::rpi::OutputWindows, + Span(outputWindows_.data(), + outputWindows_.size())); } static bool isControlDelayed(unsigned int id) diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index e47fc919a..ad3b742ab 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -82,8 +82,9 @@ class CameraData : public Camera::Private void setSensorControls(ControlList &controls); Rectangle scaleIspCrop(const Rectangle &ispCrop) const; - void applyScalerCrop(const ControlList &controls); + void applyScalerCropAndWindows(const ControlList &controls); virtual void platformSetIspCrop(unsigned int ispIndex, const Rectangle &ispCrop) = 0; + virtual int platformSetOutputWindow(unsigned int streamIndex, const Rectangle &outputWindow) = 0; void cameraTimeout(); void frameStarted(uint32_t sequence); @@ -155,6 +156,9 @@ class CameraData : public Camera::Private /* Mapping of CropParams keyed by the output stream order in CameraConfiguration */ std::map cropParams_; + /* Vector of output windows, again ordered according to the CameraConfiguration. */ + std::vector outputWindows_; + unsigned int startupFrameCount_; unsigned int invalidFrameCount_; diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp index 46d8d605a..bdc609072 100644 --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -823,6 +823,7 @@ class PiSPCameraData final : public RPi::CameraData int configureBe(const std::optional &yuvColorSpace); void platformSetIspCrop(unsigned int ispIndex, const Rectangle &ispCrop) override; + int platformSetOutputWindow(unsigned int streamIndex, const Rectangle &outputWindow) override; void prepareCfe(); void prepareBe(uint32_t bufferId, bool stitchSwapBuffers); @@ -1517,6 +1518,7 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf ispIndices_.clear(); cropParams_.clear(); + outputWindows_.clear(); for (unsigned int i = 0; i < outStreams.size(); i++) { StreamConfiguration *cfg = outStreams[i].cfg; @@ -1581,6 +1583,8 @@ int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConf if (needs32BitConversion) flags |= StreamFlag::Needs32bitConv; + outputWindows_.push_back(Rectangle(outStreams[i].cfg->size)); + /* Set smallest selection the ISP will allow. */ Size minCrop{ 32, 32 }; @@ -2166,6 +2170,53 @@ void PiSPCameraData::platformSetIspCrop(unsigned int ispIndex, const Rectangle & be_->SetCrop(ispIndex, beCrop); } +int PiSPCameraData::platformSetOutputWindow(unsigned int streamIndex, const Rectangle &outputWindow) +{ + pisp_be_output_format_config config; + pisp_be_output_format_extra extra; + BackEnd::SmartResize resize; + const OutputInfo &outputInfo = outputInfo_[streamIndex]; + unsigned int ispIndex = ispIndices_[streamIndex]; + + be_->GetOutputFormat(ispIndex, config, extra); + + /* + * We insist on a minimum size that ensures no software downscaling is ever required, + * which limits the amount of reconfiguration one might have to do here. + */ + if (outputWindow.width < outputInfo.minWidth) { + LOG(RPI, Warning) << "Window width " << outputWindow.width + << " less than minimum value " << outputInfo.minWidth; + return -1; + } else if (outputWindow.height < 2) { + LOG(RPI, Warning) << "Window height " << outputWindow.height + << " less than minimum value 2"; + return -1; + } else if (outputWindow.x % outputInfo.alignment) { + LOG(RPI, Warning) << "Window x offset " << outputWindow.x + << " does not match pixel alignment " << outputInfo.alignment; + return -1; + } else if (outputWindow.y & 1) { + LOG(RPI, Warning) << "Window y offset " << outputWindow.y + << " must be even"; + return -1; + } + + resize.width = outputWindow.width; + resize.height = outputWindow.height; + be_->SetSmartResize(ispIndex, resize); + + config.image.width = outputWindow.width; + config.image.height = outputWindow.height; + + extra.offset_x = outputWindow.x; + extra.offset_y = outputWindow.y; + + be_->SetOutputFormat(ispIndex, config, extra); + + return 0; +} + int PiSPCameraData::platformInitIpa(ipa::RPi::InitParams ¶ms) { params.fe = fe_.fd(); @@ -2322,7 +2373,7 @@ void PiSPCameraData::tryRunPipeline() ASSERT(request->metadata().empty()); /* See if a new ScalerCrop value needs to be applied. */ - applyScalerCrop(request->controls()); + applyScalerCropAndWindows(request->controls()); fillRequestMetadata(job.sensorControls, request); diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp index c776db776..374be9298 100644 --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp @@ -126,6 +126,12 @@ class Vc4CameraData final : public RPi::CameraData Rectangle crop = ispCrop; isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); } + int platformSetOutputWindow([[maybe_unused]] unsigned int streamIndex, + [[maybe_unused]] const Rectangle &outputWindow) override + { + LOG(RPI, Warning) << "VC4 platform does not support setting output windows"; + return -1; + } int platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) override; int platformConfigureIpa(ipa::RPi::ConfigParams ¶ms) override; @@ -980,7 +986,7 @@ void Vc4CameraData::tryRunPipeline() ASSERT(request->metadata().empty()); /* See if a new ScalerCrop value needs to be applied. */ - applyScalerCrop(request->controls()); + applyScalerCropAndWindows(request->controls()); fillRequestMetadata(bayerFrame.controls, request);