<div dir="ltr"><div dir="ltr">Hi David,<div><br></div><div>Thank you for your feedback.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 22 Oct 2021 at 14:09, David Plowman <<a href="mailto:david.plowman@raspberrypi.com" target="_blank">david.plowman@raspberrypi.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi Naush<br>
<br>
Thanks for this patch!<br>
<br>
On Fri, 22 Oct 2021 at 12:55, Naushir Patuck <<a href="mailto:naush@raspberrypi.com" target="_blank">naush@raspberrypi.com</a>> wrote:<br>
><br>
> Switch the pipeline handler to use the new Unicam media controller based driver.<br>
> With this change, we directly talk to the sensor device driver to set controls<br>
> and set/get formats in the pipeline handler.<br>
><br>
> This change requires the accompanying Raspberry Pi linux kernel change at<br>
> <a href="https://github.com/raspberrypi/linux/pull/4645" rel="noreferrer" target="_blank">https://github.com/raspberrypi/linux/pull/4645</a>. If this kernel change is not<br>
> present, the pipeline handler will fail to run with an error message informing<br>
> the user to update the kernel build.<br>
><br>
> Signed-off-by: Naushir Patuck <<a href="mailto:naush@raspberrypi.com" target="_blank">naush@raspberrypi.com</a>><br>
> ---<br>
>  .../pipeline/raspberrypi/raspberrypi.cpp      | 113 +++++++++++-------<br>
>  1 file changed, 67 insertions(+), 46 deletions(-)<br>
><br>
> diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp<br>
> index 1634ca98f481..730f1575095c 100644<br>
> --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp<br>
> +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp<br>
> @@ -48,6 +48,19 @@ LOG_DEFINE_CATEGORY(RPI)<br>
><br>
>  namespace {<br>
><br>
> +/* Map of mbus codes to supported sizes reported by the sensor. */<br>
> +using SensorFormats = std::map<unsigned int, std::vector<Size>>;<br>
> +<br>
> +SensorFormats populateSensorFormats(std::unique_ptr<CameraSensor> &sensor)<br>
> +{<br>
> +       SensorFormats formats;<br>
> +<br>
> +       for (auto const mbusCode : sensor->mbusCodes())<br>
> +               formats.emplace(mbusCode, sensor->sizes(mbusCode));<br>
> +<br>
> +       return formats;<br>
> +}<br>
> +<br>
>  bool isRaw(PixelFormat &pixFmt)<br>
>  {<br>
>         /*<br>
> @@ -74,8 +87,7 @@ double scoreFormat(double desired, double actual)<br>
>         return score;<br>
>  }<br>
><br>
> -V4L2DeviceFormat findBestMode(V4L2VideoDevice::Formats &formatsMap,<br>
> -                             const Size &req)<br>
> +V4L2DeviceFormat findBestMode(const SensorFormats &formatsMap, const Size &req)<br>
<br>
I wonder whether this function should return something more like an<br>
mbus code and a size. The packing is not really relevant to what comes<br>
out of the sensor and should, I suspect, actually be determined by any<br>
raw stream that has been requested by the application. This all starts<br>
to impinge a bit on the sensor-mode-hint discussions I've been having.<br></blockquote><div><br></div><div>Yes, you are right!   And for convenience, a V4L2SubdeviceFormat</div><div>is a struct that has these two fields.  I will return that struct from</div><div>findBestMode to make things simpler.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
One thing I wonder at this point is how an application could express a<br>
wish for packed or unpacked raw streams. I think generateConfiguration<br>
should probably fill in whatever it wants, probably "packed" as that's<br>
more efficient. Then the application would have to be able to change<br>
this to "unpacked" - which we could make less annoying by making the<br>
BayerFormat publicly available.<br></blockquote><div><br></div><div>That makes sense.  For this change I will adjust the "packing" param to</div><div>be packed for all cases, except if the application requested an unpacked raw</div><div>stream pixelformat.</div><div><br></div><div>We can consider opening the BayerFormat class to the public later.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
>  {<br>
>         double bestScore = std::numeric_limits<double>::max(), score;<br>
>         V4L2DeviceFormat bestMode;<br>
> @@ -88,18 +100,17 @@ V4L2DeviceFormat findBestMode(V4L2VideoDevice::Formats &formatsMap,<br>
><br>
>         /* Calculate the closest/best mode from the user requested size. */<br>
>         for (const auto &iter : formatsMap) {<br>
> -               V4L2PixelFormat v4l2Format = iter.first;<br>
> +               const unsigned int mbus_code = iter.first;<br>
> +               const V4L2PixelFormat v4l2Format = BayerFormat::fromMbusCode(mbus_code).toV4L2PixelFormat();<br>
>                 const PixelFormatInfo &info = PixelFormatInfo::info(v4l2Format);<br>
><br>
> -               for (const SizeRange &sz : iter.second) {<br>
> -                       double modeWidth = sz.contains(req) ? req.width : sz.max.width;<br>
> -                       double modeHeight = sz.contains(req) ? req.height : sz.max.height;<br>
> +               for (const Size &sz : iter.second) {<br>
>                         double reqAr = static_cast<double>(req.width) / req.height;<br>
> -                       double modeAr = modeWidth / modeHeight;<br>
> +                       double modeAr = sz.width / sz.height;<br>
><br>
>                         /* Score the dimensions for closeness. */<br>
> -                       score = scoreFormat(req.width, modeWidth);<br>
> -                       score += scoreFormat(req.height, modeHeight);<br>
> +                       score = scoreFormat(req.width, sz.width);<br>
> +                       score += scoreFormat(req.height, sz.height);<br>
>                         score += PENALTY_AR * scoreFormat(reqAr, modeAr);<br>
><br>
>                         /* Add any penalties... this is not an exact science! */<br>
> @@ -116,10 +127,10 @@ V4L2DeviceFormat findBestMode(V4L2VideoDevice::Formats &formatsMap,<br>
>                         if (score <= bestScore) {<br>
>                                 bestScore = score;<br>
>                                 bestMode.fourcc = v4l2Format;<br>
> -                               bestMode.size = Size(modeWidth, modeHeight);<br>
> +                               bestMode.size = sz;<br>
>                         }<br>
><br>
> -                       LOG(RPI, Info) << "Mode: " << modeWidth << "x" << modeHeight<br>
> +                       LOG(RPI, Info) << "Mode: " << sz.width << "x" << sz.height<br>
>                                        << " fmt " << v4l2Format.toString()<br>
>                                        << " Score: " << score<br>
>                                        << " (best " << bestScore << ")";<br>
> @@ -170,6 +181,7 @@ public:<br>
>         std::unique_ptr<ipa::RPi::IPAProxyRPi> ipa_;<br>
><br>
>         std::unique_ptr<CameraSensor> sensor_;<br>
> +       SensorFormats sensorFormats_;<br>
>         /* Array of Unicam and ISP device streams and associated buffers/streams. */<br>
>         RPi::Device<Unicam, 2> unicam_;<br>
>         RPi::Device<Isp, 4> isp_;<br>
> @@ -352,8 +364,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()<br>
>                          * Calculate the best sensor mode we can use based on<br>
>                          * the user request.<br>
>                          */<br>
> -                       V4L2VideoDevice::Formats fmts = data_->unicam_[Unicam::Image].dev()->formats();<br>
> -                       V4L2DeviceFormat sensorFormat = findBestMode(fmts, cfg.size);<br>
> +                       V4L2DeviceFormat sensorFormat = findBestMode(data_->sensorFormats_, cfg.size);<br>
>                         int ret = data_->unicam_[Unicam::Image].dev()->tryFormat(&sensorFormat);<br>
>                         if (ret)<br>
>                                 return Invalid;<br>
> @@ -487,8 +498,7 @@ CameraConfiguration *PipelineHandlerRPi::generateConfiguration(Camera *camera,<br>
>                 switch (role) {<br>
>                 case StreamRole::Raw:<br>
>                         size = data->sensor_->resolution();<br>
> -                       fmts = data->unicam_[Unicam::Image].dev()->formats();<br>
> -                       sensorFormat = findBestMode(fmts, size);<br>
> +                       sensorFormat = findBestMode(data->sensorFormats_, size);<br>
>                         pixelFormat = sensorFormat.fourcc.toPixelFormat();<br>
>                         ASSERT(pixelFormat.isValid());<br>
>                         bufferCount = 2;<br>
> @@ -599,32 +609,32 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)<br>
>         }<br>
><br>
>         /* First calculate the best sensor mode we can use based on the user request. */<br>
> -       V4L2VideoDevice::Formats fmts = data->unicam_[Unicam::Image].dev()->formats();<br>
> -       V4L2DeviceFormat sensorFormat = findBestMode(fmts, rawStream ? sensorSize : maxSize);<br>
> +       V4L2DeviceFormat unicamFormat = findBestMode(data->sensorFormats_, rawStream ? sensorSize : maxSize);<br>
> +<br>
> +       unsigned int mbus_code = BayerFormat::fromV4L2PixelFormat(unicamFormat.fourcc).toMbusCode();<br>
> +       V4L2SubdeviceFormat sensorFormat { .mbus_code = mbus_code, .size = unicamFormat.size };<br>
> +<br>
> +       ret = data->sensor_->setFormat(&sensorFormat);<br>
> +       if (ret)<br>
> +               return ret;<br>
<br>
As discussed above, the unicamFormat probably wants to take account of<br>
the packing (or not) in any raw stream (if present).<br></blockquote><div><br></div><div>Ack.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
><br>
>         /*<br>
>          * Unicam image output format. The ISP input format gets set at start,<br>
>          * just in case we have swapped bayer orders due to flips.<br>
>          */<br>
> -       ret = data->unicam_[Unicam::Image].dev()->setFormat(&sensorFormat);<br>
> +       ret = data->unicam_[Unicam::Image].dev()->setFormat(&unicamFormat);<br>
>         if (ret)<br>
>                 return ret;<br>
><br>
> -       /*<br>
> -        * The control ranges associated with the sensor may need updating<br>
> -        * after a format change.<br>
> -        * \todo Use the CameraSensor::setFormat API instead.<br>
> -        */<br>
> -       data->sensor_->updateControlInfo();<br>
<br>
Hmm, nice, I wonder if we could delete updateControlInfo entirely?<br></blockquote><div><br></div><div>There does not seem to be any other users of V4L2Device::updateControlInfo()</div><div>so I could remove this.  I recall this was mainly for our use, so that should be fine.</div><div><br></div><div>Regards,</div><div>Naush</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
> -<br>
>         LOG(RPI, Info) << "Sensor: " << camera->id()<br>
> -                      << " - Selected mode: " << sensorFormat.toString();<br>
> +                      << " - Selected sensor mode: " << sensorFormat.toString()<br>
> +                      << " - Selected unicam mode: " << unicamFormat.toString();<br>
><br>
>         /*<br>
>          * This format may be reset on start() if the bayer order has changed<br>
>          * because of flips in the sensor.<br>
>          */<br>
> -       ret = data->isp_[Isp::Input].dev()->setFormat(&sensorFormat);<br>
> +       ret = data->isp_[Isp::Input].dev()->setFormat(&unicamFormat);<br>
>         if (ret)<br>
>                 return ret;<br>
><br>
> @@ -746,8 +756,8 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)<br>
>         data->ispMinCropSize_ = testCrop.size();<br>
><br>
>         /* Adjust aspect ratio by providing crops on the input image. */<br>
> -       Size size = sensorFormat.size.boundedToAspectRatio(maxSize);<br>
> -       Rectangle crop = size.centeredTo(Rectangle(sensorFormat.size).center());<br>
> +       Size size = unicamFormat.size.boundedToAspectRatio(maxSize);<br>
> +       Rectangle crop = size.centeredTo(Rectangle(unicamFormat.size).center());<br>
>         data->ispCrop_ = crop;<br>
><br>
>         data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop);<br>
> @@ -761,10 +771,11 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)<br>
>          * supports it.<br>
>          */<br>
>         if (data->sensorMetadata_) {<br>
> -               format = {};<br>
> -               format.fourcc = V4L2PixelFormat(V4L2_META_FMT_SENSOR_DATA);<br>
> +               V4L2SubdeviceFormat embeddedFormat;<br>
><br>
> -               LOG(RPI, Debug) << "Setting embedded data format.";<br>
> +               data->sensor_->device()->getFormat(1, &embeddedFormat);<br>
> +               format.fourcc = V4L2PixelFormat(V4L2_META_FMT_SENSOR_DATA);<br>
> +               format.planes[0].size = embeddedFormat.size.width * embeddedFormat.size.height;<br>
>                 ret = data->unicam_[Unicam::Embedded].dev()->setFormat(&format);<br>
>                 if (ret) {<br>
>                         LOG(RPI, Error) << "Failed to set format on Unicam embedded: "<br>
> @@ -847,9 +858,14 @@ int PipelineHandlerRPi::start(Camera *camera, const ControlList *controls)<br>
>          * IPA configure may have changed the sensor flips - hence the bayer<br>
>          * order. Get the sensor format and set the ISP input now.<br>
>          */<br>
> -       V4L2DeviceFormat sensorFormat;<br>
> -       data->unicam_[Unicam::Image].dev()->getFormat(&sensorFormat);<br>
> -       ret = data->isp_[Isp::Input].dev()->setFormat(&sensorFormat);<br>
> +       V4L2SubdeviceFormat sensorFormat;<br>
> +       data->sensor_->device()->getFormat(0, &sensorFormat);<br>
> +<br>
> +       V4L2DeviceFormat ispFormat;<br>
> +       ispFormat.fourcc = BayerFormat::fromMbusCode(sensorFormat.mbus_code).toV4L2PixelFormat();<br>
> +       ispFormat.size = sensorFormat.size;<br>
> +<br>
> +       ret = data->isp_[Isp::Input].dev()->setFormat(&ispFormat);<br>
>         if (ret) {<br>
>                 stop(camera);<br>
>                 return ret;<br>
> @@ -1004,6 +1020,8 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)<br>
>         if (data->sensor_->init())<br>
>                 return false;<br>
><br>
> +       data->sensorFormats_ = populateSensorFormats(data->sensor_);<br>
> +<br>
>         ipa::RPi::SensorConfig sensorConfig;<br>
>         if (data->loadIPA(&sensorConfig)) {<br>
>                 LOG(RPI, Error) << "Failed to load a suitable IPA library";<br>
> @@ -1030,6 +1048,11 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)<br>
>                         return false;<br>
>         }<br>
><br>
> +       if (!(data->unicam_[Unicam::Image].dev()->caps().device_caps() & V4L2_CAP_IO_MC)) {<br>
> +               LOG(RPI, Error) << "Unicam driver did not advertise V4L2_CAP_IO_MC, please update your kernel!";<br>
> +               return false;<br>
> +       }<br>
> +<br>
>         /*<br>
>          * Setup our delayed control writer with the sensor default<br>
>          * gain and exposure delays. Mark VBLANK for priority write.<br>
> @@ -1039,7 +1062,7 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)<br>
>                 { V4L2_CID_EXPOSURE, { sensorConfig.exposureDelay, false } },<br>
>                 { V4L2_CID_VBLANK, { sensorConfig.vblankDelay, true } }<br>
>         };<br>
> -       data->delayedCtrls_ = std::make_unique<DelayedControls>(data->unicam_[Unicam::Image].dev(), params);<br>
> +       data->delayedCtrls_ = std::make_unique<DelayedControls>(data->sensor_->device(), params);<br>
>         data->sensorMetadata_ = sensorConfig.sensorMetadata;<br>
><br>
>         /* Register the controls that the Raspberry Pi IPA can handle. */<br>
> @@ -1066,15 +1089,14 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)<br>
>          * As part of answering the final question, we reset the camera to<br>
>          * no transform at all.<br>
>          */<br>
> -<br>
> -       V4L2VideoDevice *dev = data->unicam_[Unicam::Image].dev();<br>
> -       const struct v4l2_query_ext_ctrl *hflipCtrl = dev->controlInfo(V4L2_CID_HFLIP);<br>
> +       const V4L2Subdevice *sensor = data->sensor_->device();<br>
> +       const struct v4l2_query_ext_ctrl *hflipCtrl = sensor->controlInfo(V4L2_CID_HFLIP);<br>
>         if (hflipCtrl) {<br>
>                 /* We assume it will support vflips too... */<br>
>                 data->supportsFlips_ = true;<br>
>                 data->flipsAlterBayerOrder_ = hflipCtrl->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT;<br>
><br>
> -               ControlList ctrls(dev->controls());<br>
> +               ControlList ctrls(data->sensor_->controls());<br>
>                 ctrls.set(V4L2_CID_HFLIP, 0);<br>
>                 ctrls.set(V4L2_CID_VFLIP, 0);<br>
>                 data->setSensorControls(ctrls);<br>
> @@ -1082,9 +1104,8 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)<br>
><br>
>         /* Look for a valid Bayer format. */<br>
>         BayerFormat bayerFormat;<br>
> -       for (const auto &iter : dev->formats()) {<br>
> -               V4L2PixelFormat v4l2Format = iter.first;<br>
> -               bayerFormat = BayerFormat::fromV4L2PixelFormat(v4l2Format);<br>
> +       for (const auto &iter : data->sensorFormats_) {<br>
> +               bayerFormat = BayerFormat::fromMbusCode(iter.first);<br>
>                 if (bayerFormat.isValid())<br>
>                         break;<br>
>         }<br>
> @@ -1271,7 +1292,7 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config)<br>
>                 }<br>
>         }<br>
><br>
> -       entityControls.emplace(0, unicam_[Unicam::Image].dev()->controls());<br>
> +       entityControls.emplace(0, sensor_->controls());<br>
>         entityControls.emplace(1, isp_[Isp::Input].dev()->controls());<br>
><br>
>         /* Always send the user transform to the IPA. */<br>
> @@ -1406,10 +1427,10 @@ void RPiCameraData::setSensorControls(ControlList &controls)<br>
>                 ControlList vblank_ctrl;<br>
><br>
>                 vblank_ctrl.set(V4L2_CID_VBLANK, controls.get(V4L2_CID_VBLANK));<br>
> -               unicam_[Unicam::Image].dev()->setControls(&vblank_ctrl);<br>
> +               sensor_->setControls(&vblank_ctrl);<br>
>         }<br>
><br>
> -       unicam_[Unicam::Image].dev()->setControls(&controls);<br>
> +       sensor_->setControls(&controls);<br>
>  }<br>
><br>
>  void RPiCameraData::unicamBufferDequeue(FrameBuffer *buffer)<br>
> --<br>
> 2.25.1<br>
><br>
<br>
Thanks!<br>
<br>
David<br>
</blockquote></div></div>