[libcamera-devel] [RFC PATCH 3/5] libcamera: pipeline: Add readControls(), writeControl() interfaces

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Jun 11 14:13:30 CEST 2019


Hi Kieran,

On Fri, Jun 07, 2019 at 10:35:15PM +0100, Kieran Bingham wrote:
> Hi Laurent,
> 
> Thank you for your comments, they are certainly helpful for sorting out
> some of the design issues.
> 
> On 07/06/2019 17:19, Laurent Pinchart wrote:
> > On Thu, Jun 06, 2019 at 09:56:52PM +0100, Kieran Bingham wrote:
> >> Pipeline handlers must implement functions to handle controls as part of
> >> their interface.
> >>
> >> Extend the pipeline handler interface to declare this requirement and
> >> implement basic control functionality in the currently supported
> >> PipelineHandlers
> > 
> > I don't think this should be exposed through the PipelineHandler API, it
> > should be handled inside each pipeline handler instead. You're calling
> > the readControls() and writeControls() methods of the pipeline handler
> > internally only in this patch, so it's effectively internal already.
> 
> I had visions of the 'call' to read/write the controls being in a common
> point, but as the PipelineHandlers determine when the request completes,
> and the internal timings - I think you're right. There is no need to
> enforce these functions on the handler API.
> 
> It's just up to a pipeline handler to decide how to deal with controls.
> 
> >> ---
> >>  src/libcamera/include/pipeline_handler.h |   3 +
> >>  src/libcamera/pipeline/ipu3/ipu3.cpp     |  19 ++++
> >>  src/libcamera/pipeline/raspberrypi.cpp   | 108 ++++++++++++++++++-
> >>  src/libcamera/pipeline/rkisp1/rkisp1.cpp |  19 ++++
> >>  src/libcamera/pipeline/uvcvideo.cpp      | 127 ++++++++++++++++++++++-
> >>  src/libcamera/pipeline/vimc.cpp          |  19 ++++
> >>  6 files changed, 293 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/src/libcamera/include/pipeline_handler.h b/src/libcamera/include/pipeline_handler.h
> >> index 7da6df1ab2a0..4e306d964bff 100644
> >> --- a/src/libcamera/include/pipeline_handler.h
> >> +++ b/src/libcamera/include/pipeline_handler.h
> >> @@ -72,6 +72,9 @@ public:
> >>  	virtual int start(Camera *camera) = 0;
> >>  	virtual void stop(Camera *camera);
> >>  
> >> +	virtual int readControls(Camera *camera, Request *request) = 0;
> >> +	virtual int writeControls(Camera *camera, Request *request) = 0;
> >> +
> >>  	virtual int queueRequest(Camera *camera, Request *request);
> >>  
> >>  	bool completeBuffer(Camera *camera, Request *request, Buffer *buffer);
> > 
> > [snip]
> > 
> >> diff --git a/src/libcamera/pipeline/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi.cpp
> >> index d6749eaae759..337554501c82 100644
> >> --- a/src/libcamera/pipeline/raspberrypi.cpp
> >> +++ b/src/libcamera/pipeline/raspberrypi.cpp
> >> @@ -15,6 +15,7 @@
> >>  #include "media_device.h"
> >>  #include "pipeline_handler.h"
> >>  #include "utils.h"
> >> +#include "v4l2_controls.h"
> >>  #include "v4l2_device.h"
> >>  
> >>  namespace libcamera {
> >> @@ -77,6 +78,9 @@ public:
> >>  	int start(Camera *camera) override;
> >>  	void stop(Camera *camera) override;
> >>  
> >> +	int writeControls(Camera *camera, Request *request);
> >> +	int readControls(Camera *camera, Request *request);
> >> +
> >>  	int queueRequest(Camera *camera, Request *request) override;
> >>  
> >>  	bool match(DeviceEnumerator *enumerator) override;
> >> @@ -293,6 +297,94 @@ void PipelineHandlerRPi::stop(Camera *camera)
> >>  	PipelineHandler::stop(camera);
> >>  }
> >>  
> >> +int PipelineHandlerRPi::writeControls(Camera *camera, Request *request)
> >> +{
> >> +	RPiCameraData *data = cameraData(camera);
> >> +
> >> +	std::vector<V4L2Control *> controls;
> >> +
> >> +	for (Control c : request->controls()) {
> >> +		if (c.value().isWrite()) {
> > 
> > Would it make sense to split the read and write sets at the Request
> > level ?
> 
> I'd love to be able to do a python generator type syntax here, and
> 'yield' to cheaply iterate the controls handling ones that are appropriate.
> 
> This might be possible by writing our own iterators, but I'm not sure
> how elegant that would be.
> 
> If you are imagining that every request defines every control, how would
> you anticipate then splitting up the distinction at the request level?
> With multiple sets?

Yes, a set of controls, and a set of metadata (I'm more and more
thinking that what we called read controls so far should be renamed
metadata).

> What values would you expect to be present in the reqeust when it is
> created with preset controls?
> 
>  - All values at default?
>  - All values at current / cached values?
>  - Not set at all... perhaps in the request in an 'unset' state.?
>  - Some other option?

I don't expect requests to be created with "preset controls". Requests
should be created empty, and populate it by applications. This could be
done manually, or through a preset provided by libcamera. I would expect
applications to create multiple instances of lists of controls, populate
them with the help of preset and manual settings, and then use them at
runtime to populate requests.

> >> +			switch (c.id()) {
> >> +			case ManualGain:
> >> +				break;
> >> +			case ManualExposure:
> >> +				break;
> >> +			default:
> >> +				LOG(RPI, Error)
> >> +					<< "Unhandled write control";
> >> +				break;
> >> +			}
> >> +		}
> >> +	}
> >> +
> >> +	/* Perhaps setControls could accept an empty vector/list as success? */
> >> +	if (!controls.empty())
> >> +		return data->unicam_->setControls(controls);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +/* This is becoming horrible. How can we improve this
> >> + * - Iterate reqeust controls to find controls to read
> >> + * - Add those to a new list
> >> + * - Query the device for that list to get controls
> >> + * - iterate returned list
> >> + * 	- For each control, search for corresponding control in request
> >> + * 	- Set control value to return value.
> >> + * 	- Return list.
> >> + * 	- Check for any values that were not updated?
> >> + */
> >> +int PipelineHandlerRPi::readControls(Camera *camera, Request *request)
> >> +{
> >> +	RPiCameraData *data = cameraData(camera);
> >> +	std::vector<unsigned int> controlIDs;
> >> +	std::vector<V4L2Control *> controls;
> >> +
> >> +	for (Control c : request->controls()) {
> >> +		if (c.value().isRead()) {
> >> +			LOG(RPI, Error) << "Read Control: " << c;
> >> +
> >> +			switch (c.id()) {
> >> +			case ManualGain:
> >> +				controlIDs.push_back(V4L2_CID_ANALOGUE_GAIN);
> >> +				break;
> >> +			case ManualExposure:
> >> +				controlIDs.push_back(V4L2_CID_EXPOSURE);
> >> +				break;
> >> +			default:
> >> +				LOG(RPI, Error)
> >> +					<< "Unhandled write control";
> >> +				break;
> >> +			}
> >> +		}
> >> +	}
> >> +
> >> +	/* Perhaps setControls could accept an empty vector/list as success? */
> >> +	if (controlIDs.empty())
> >> +		return 0;
> >> +
> >> +	controls = data->unicam_->getControls(controlIDs);
> >> +	if (controls.empty())
> >> +		return -ENODATA;
> >> +
> >> +	for (V4L2Control *ctrl : controls) {
> >> +		switch(ctrl->id()) {
> >> +		case V4L2_CID_ANALOGUE_GAIN:
> >> +			//Control gain(ManualGain); //= request->controls().find(ManualGain);
> >> +			auto it = request->controls().find(ManualGain);
> >> +			Control gain = *it;
> >> +
> >> +			//V4L2IntControl *c = static_cast<V4L2IntControl *>(ctrl);
> >> +			gain.value().set(0x88);
> >> +
> >> +			break;
> >> +		}
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >>  int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request)
> >>  {
> >>  	RPiCameraData *data = cameraData(camera);
> >> @@ -304,7 +396,15 @@ int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request)
> >>  		return -ENOENT;
> >>  	}
> >>  
> >> -	int ret = data->isp_.capture->queueBuffer(buffer);
> >> +	/*
> >> +	 * Handle 'set' controls first
> >> +	 * Todo: Validate ordering and timing.
> > 
> > Handling timing will be interesting, as V4L2 controls are synchronous,
> > while buffers are not. I expect we'll need to evaluate when the buffer
> > will be captured, and set a timer accordingly to set controls (taking
> > into account the latencies of the capture pipeline).
> 
> Indeed - making sure we set the value for that buffer is going to be
> interesting. It might be some 'guess work' :-( - but we should know how
> many buffers are queued ahead of us, and we can store the completion
> time of each previously completed buffer so we should be able to
> estimate when things happen in some form, and calculate our expected
> 'latency' for any given request when it is queued.
> 
> Hrm ... that sounds like it might actually be quite fun (but probably
> also painful somehow) to implement ...

Fun and painful, yes :-) I hope we'll be able to factor some of that
code to helpers at a later point.

> >> +	 */
> >> +	int ret = writeControls(camera, request);
> >> +	if (ret < 0)
> >> +		return ret;
> >> +
> >> +	ret = data->isp_.capture->queueBuffer(buffer);
> >>  	if (ret < 0)
> >>  		return ret;
> >>  
> >> @@ -394,6 +494,12 @@ void RPiCameraData::ispCaptureReady(Buffer *buffer)
> >>  	Request *request = queuedRequests_.front();
> >>  
> >>  	pipe_->completeBuffer(camera_, request, buffer);
> >> +
> >> +	int ret = pipe_->readControls(camera_, request);
> >> +	if (ret < 0)
> >> +		LOG(RPI, Error)
> >> +			<< "Failed to read controls. No way to pass error";
> > 
> > Should this be reflected in the request status ?
> 
> Aha, yes of course. That is indeed how the status could be returned!
> 
> Can we end up in a situation were we have multiple error status' to
> return in a request? Perhaps we might have to store a vector of Error
> status'?

Ideally we should have no error :-) When an error occurs I expect it
will be fatal for the application, so I don't think we need to store
multiple error sources.

> >> +
> >>  	pipe_->completeRequest(camera_, request);
> >>  }
> >>  
> > 
> > [snip]
> > 
> >> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp
> >> index e2d02c0dafb8..216fbe237827 100644
> >> --- a/src/libcamera/pipeline/uvcvideo.cpp
> >> +++ b/src/libcamera/pipeline/uvcvideo.cpp
> >> @@ -14,6 +14,7 @@
> >>  #include "media_device.h"
> >>  #include "pipeline_handler.h"
> >>  #include "utils.h"
> >> +#include "v4l2_controls.h"
> >>  #include "v4l2_device.h"
> >>  
> >>  namespace libcamera {
> >> @@ -73,6 +74,9 @@ public:
> >>  	int start(Camera *camera) override;
> >>  	void stop(Camera *camera) override;
> >>  
> >> +	int readControls(Camera *camera, Request *request) override;
> >> +	int writeControls(Camera *camera, Request *request) override;
> >> +
> >>  	int queueRequest(Camera *camera, Request *request) override;
> >>  
> >>  	bool match(DeviceEnumerator *enumerator) override;
> >> @@ -252,6 +256,117 @@ void PipelineHandlerUVC::stop(Camera *camera)
> >>  	PipelineHandler::stop(camera);
> >>  }
> >>  
> >> +int PipelineHandlerUVC::writeControls(Camera *camera, Request *request)
> >> +{
> >> +	UVCCameraData *data = cameraData(camera);
> >> +
> >> +	std::vector<V4L2Control *> controls;
> >> +
> >> +	for (Control c : request->controls()) {
> >> +		if (c.value().isWrite()) {
> >> +			switch (c.id()) {
> >> +			case ManualBrightness:
> >> +				controls.emplace_back(new V4L2IntControl(V4L2_CID_BRIGHTNESS, c.value().getInt()));
> >> +				break;
> >> +
> >> +			case IpaAwbEnable:
> >> +			case ManualGain:
> >> +			case ManualExposure:
> >> +				break;
> >> +			default:
> >> +				LOG(UVC, Error)
> >> +					<< "Unhandled write control";
> >> +				break;
> >> +			}
> >> +		}
> >> +	}
> >> +
> >> +	/* Perhaps setControls could accept an empty vector/list as success? */
> >> +	if (!controls.empty()) {
> >> +		int ret = data->video_->setControls(controls);
> >> +
> >> +		/* It would be nice to avoid the need to manually delete
> >> +		 * the controls */
> > 
> > Yes, this seems like a no-go to me.
> 
> I think we can adapt the V4L2Control interface to accept a set or map as
> well, and potentially even (where relevant) put in a reference to the
> ControlValue supplied to prevent excess copying of what will essentially
> be the same object type to update.
> 
> >> +		for (V4L2Control *c : controls)
> >> +			delete c;
> >> +
> >> +		return ret;
> >> +	}
> >> +
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +int PipelineHandlerUVC::readControls(Camera *camera, Request *request)
> >> +{
> >> +	UVCCameraData *data = cameraData(camera);
> >> +	std::vector<unsigned int> controlIDs;
> >> +	std::vector<V4L2Control *> controls;
> >> +
> >> +	for (Control c : request->controls()) {
> >> +		if (c.value().isRead()) {
> >> +			LOG(UVC, Error) << "Read Control: " << c;
> >> +
> >> +			switch (c.id()) {
> >> +			case ManualBrightness:
> >> +				controlIDs.push_back(V4L2_CID_BRIGHTNESS);
> >> +				break;
> >> +			case ManualGain:
> >> +				controlIDs.push_back(V4L2_CID_ANALOGUE_GAIN);
> >> +				break;
> >> +			case ManualExposure:
> >> +				controlIDs.push_back(V4L2_CID_EXPOSURE);
> >> +				break;
> >> +			default:
> >> +				LOG(UVC, Error)
> >> +					<< "Unhandled write control";
> >> +				break;
> >> +			}
> >> +		}
> >> +	}
> >> +
> >> +	/* Perhaps setControls could accept an empty vector/list as success? */
> >> +	if (controlIDs.empty())
> >> +		return 0;
> >> +
> >> +	controls = data->video_->getControls(controlIDs);
> >> +	if (controls.empty())
> >> +		return -ENODATA;
> >> +
> >> +	for (V4L2Control *ctrl : controls) {
> >> +		switch (ctrl->id()) {
> >> +		case V4L2_CID_BRIGHTNESS: {
> >> +			Control bright = *request->controls().find(ManualBrightness);
> >> +			/* RFC:
> >> +			 * If the iterator doesn't find the control ^
> >> +			 * then it causes a segfault, so this is really nasty...
> >> +			 * and where just a map might be better - (ornot?) as it
> >> +			 * will create the object at that key.
> >> +			 * Now , that *shouldn't* happen (we should only look
> >> +			 * for controls that were given to us in this
> >> +			 * function ... but
> > 
> > 			auto iter = request->controls().find(ManualBrightness);
> > 			if (iter == request->controls().end())
> > 				...
> 
> It shouldn't ever occur, as we would only look for a value we have
> requested... but that's what made me interested in the difference for
> using a map, as it would then create the ControlValue for use.
> 
> But then also if we are always going to have all controls in the
> request, then failing to find a control would become an assertion failure.

I don't think all requests will contain all controls, I expect them to
be incremental, without having to store the full state.

> >> +			 */
> >> +			V4L2IntControl *vc = static_cast<V4L2IntControl *>(ctrl);
> >> +			bright.value().set(vc->value());
> >> +
> >> +			LOG(UVC, Debug) << "Setting Brightness: " << bright;
> >> +
> >> +			break;
> >> +		}
> >> +		case V4L2_CID_ANALOGUE_GAIN: {
> >> +			Control gain = *request->controls().find(ManualGain);
> >> +			V4L2IntControl *vc = static_cast<V4L2IntControl *>(ctrl);
> >> +			gain.value().set(vc->value());
> >> +			break;
> >> +		}
> >> +		default:
> >> +			LOG(UVC, Warning) << "Unhandled V4L2 Control ID";
> >> +		}
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >>  int PipelineHandlerUVC::queueRequest(Camera *camera, Request *request)
> >>  {
> >>  	UVCCameraData *data = cameraData(camera);
> >> @@ -263,7 +378,11 @@ int PipelineHandlerUVC::queueRequest(Camera *camera, Request *request)
> >>  		return -ENOENT;
> >>  	}
> >>  
> >> -	int ret = data->video_->queueBuffer(buffer);
> >> +	int ret = writeControls(camera, request);
> >> +	if (ret < 0)
> >> +		return ret;
> >> +
> >> +	ret = data->video_->queueBuffer(buffer);
> >>  	if (ret < 0)
> >>  		return ret;
> >>  
> >> @@ -317,6 +436,12 @@ void UVCCameraData::bufferReady(Buffer *buffer)
> >>  	Request *request = queuedRequests_.front();
> >>  
> >>  	pipe_->completeBuffer(camera_, request, buffer);
> >> +
> >> +	int ret = pipe_->readControls(camera_, request);
> >> +	if (ret < 0)
> >> +		LOG(UVC, Error)
> >> +			<< "Failed to read controls. No way to pass error";
> >> +
> >>  	pipe_->completeRequest(camera_, request);
> >>  }
> >>  
> > 
> > [snip]

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list