Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/libcamera/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class Camera final : public Object, public std::enable_shared_from_this<Camera>,

std::unique_ptr<Request> createRequest(uint64_t cookie = 0);
int queueRequest(Request *request);
int queueControls(ControlList &&controls);

int start(const ControlList *controls = nullptr);
int stop();
Expand Down
7 changes: 7 additions & 0 deletions include/libcamera/internal/pipeline_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class PipelineHandler : public std::enable_shared_from_this<PipelineHandler>,

void registerRequest(Request *request);
void queueRequest(Request *request);
int queueControls(Camera *camera, ControlList controls);

bool completeBuffer(Request *request, FrameBuffer *buffer);
void completeRequest(Request *request);
Expand All @@ -76,6 +77,12 @@ class PipelineHandler : public std::enable_shared_from_this<PipelineHandler>,
unsigned int useCount() const { return useCount_; }

virtual int queueRequestDevice(Camera *camera, Request *request) = 0;

virtual int queueControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)
{
return -EOPNOTSUPP;
}

virtual void stopDevice(Camera *camera) = 0;

virtual bool acquireDevice(Camera *camera);
Expand Down
18 changes: 10 additions & 8 deletions src/ipa/rpi/common/ipa_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,9 @@ void IpaBase::prepareIsp(const PrepareParams &params)
fillDeviceStatus(params.sensorControls, ipaContext);
fillSyncParams(params, ipaContext);

if (!params.requestControls.empty())
rpiMetadata.set("ipa.request_controls", true);

if (params.buffers.embedded) {
/*
* Pipeline handler has supplied us with an embedded data buffer,
Expand All @@ -460,7 +463,7 @@ void IpaBase::prepareIsp(const PrepareParams &params)
*/
AgcStatus agcStatus;
bool hdrChange = false;
RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext];
RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext % rpiMetadata_.size()];
if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) {
rpiMetadata.set("agc.delayed_status", agcStatus);
hdrChange = agcStatus.hdr.mode != hdrStatus_.mode;
Expand All @@ -473,9 +476,13 @@ void IpaBase::prepareIsp(const PrepareParams &params)
*/
helper_->prepare(embeddedBuffer, rpiMetadata);

bool delayedRequestControls = false;
delayedMetadata.get<bool>("ipa.request_controls", delayedRequestControls);

/* Allow a 10% margin on the comparison below. */
Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;
if (lastRunTimestamp_ && frameCount_ > invalidCount_ &&
if (!delayedRequestControls && params.requestControls.empty() &&
lastRunTimestamp_ && frameCount_ > invalidCount_ &&
delta < controllerMinFrameDuration * 0.9 && !hdrChange) {
/*
* Ensure we merge the previous frame's metadata with the current
Expand Down Expand Up @@ -558,7 +565,7 @@ void IpaBase::processStats(const ProcessParams &params)
ControlList ctrls(sensorCtrls_);
applyAGC(&agcStatus, ctrls, offset);
rpiMetadata.set("agc.status", agcStatus);
setDelayedControls.emit(ctrls, ipaContext);
setDelayedControls.emit(ctrls, params.ipaContext);
setCameraTimeoutValue();
}

Expand Down Expand Up @@ -975,8 +982,6 @@ void IpaBase::applyControls(const ControlList &controls)

/* The control provides units of microseconds. */
agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us);

libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());
break;
}

Expand All @@ -1000,9 +1005,6 @@ void IpaBase::applyControls(const ControlList &controls)
break;

agc->setFixedGain(0, ctrl.second.get<float>());

libcameraMetadata_.set(controls::AnalogueGain,
ctrl.second.get<float>());
break;
}

Expand Down
51 changes: 51 additions & 0 deletions src/libcamera/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,57 @@ int Camera::queueRequest(Request *request)
return 0;
}

/**
* \brief Queue controls to be applied as soon as possible
* \param[in] controls The list of controls to queue
*
* This function tries to ensure that the controls in \a controls are applied
* to the camera as soon as possible. If there are still pending controls waiting
* to be applied (because of previous calls to Camera::queueControls), then
* these controls will be applied as soon as possible on a frame after those.
*
* The exact guarantees are camera dependent, but it is guaranteed that the
* controls will be applied no later than with the next \ref Request"request"
* that the application \ref Camera::queueRequest() "queues" (after any requests
* have been *used up" for sending previously queued controls).
*
* \context This function is \threadsafe. It may only be called when the camera
* is in the Running state as defined in \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The camera is not running
*/
int Camera::queueControls(ControlList &&controls)
{
Private *const d = _d();

/*
* Like requests, controls can't be queued if the camera is not running.
* Controls can be applied immediately when the camera starts using the
* Camera::Start method.
*/

int ret = d->isAccessAllowed(Private::CameraRunning);
if (ret < 0)
return ret;

/*
* We want to be able to queue empty control lists, as this gives a way of
* forcing another frame with the same controls as last time, before queueing
* another control list that might change them again.
*/

patchControlList(controls);

/*
* \todo Or `ConnectionTypeBlocking` to get the return value?
*/
d->pipe_->invokeMethod(&PipelineHandler::queueControls, ConnectionTypeQueued, this, std::move(controls));

return 0;
}

/**
* \brief Start capture from camera
* \param[in] controls Controls to be applied before starting the Camera
Expand Down
17 changes: 17 additions & 0 deletions src/libcamera/control_ids_rpi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,21 @@ controls:
This control returns performance metrics for the CNN processing stage.
Two values are returned in this span, the runtime of the CNN/DNN stage
and the DSP stage in milliseconds.

- ControlListSequence:
type: int64_t
direction: out
description: |
This is the control list id number that is synchonised with this
request, meaning that this request is the first one where the images
have had the identified list of controls applied.

Every time a control list is queued using Camera::queueControls, it
is assigned a new sequence number, starting at 1 on the first such
after the camera is started, and increasing by one on every subsequent
call.

This is the number that is then reported back as the
ControlListSequence. When no controls have been sent with
Camera::queueControls, the ControlListSequence reports the value zero.
...
62 changes: 36 additions & 26 deletions src/libcamera/pipeline/rpi/common/delayed_controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ namespace RPi {
*
* Some sensor controls take effect with a delay as the sensor needs time to
* adjust, for example exposure and analog gain. This is a helper class to deal
* with such controls and the intended users are pipeline handlers.
* with such controls.
*
* The idea is to extend the concept of the buffer depth of a pipeline the
* application needs to maintain to also cover controls. Just as with buffer
* depth if the application keeps the number of requests queued above the
* control depth the controls are guaranteed to take effect for the correct
* request. The control depth is determined by the control with the greatest
* delay.
* The idea is to maintain a queue of controls that have been submitted.
* Whenever a new frame starts, we can "peak back" into this queue to send
* controls at the correct number of frames in advance to account for each
* control's delay.
*
* The overall delay for controls becomes the maximum delay of any of the
* controls.
*/

/**
Expand Down Expand Up @@ -119,7 +120,6 @@ DelayedControls::DelayedControls(V4L2Device *device,
*/
void DelayedControls::reset(unsigned int cookie)
{
queueCount_ = 1;
writeCount_ = 0;
cookies_[0] = cookie;

Expand All @@ -146,18 +146,25 @@ void DelayedControls::reset(unsigned int cookie)
* \brief Push a set of controls on the queue
* \param[in] controls List of controls to add to the device queue
*
* Push a set of controls to the control queue. This increases the control queue
* depth by one.
* Push a set of controls to the control queue. The next call to applyControls
* will advance the slot in the queue where the next call to push will write
* controls.
*
* \returns true if \a controls are accepted, or false otherwise
*/
bool DelayedControls::push(const ControlList &controls, const unsigned int cookie)
{
/* Copy state from previous frame. */
for (auto &ctrl : values_) {
Info &info = ctrl.second[queueCount_];
info = values_[ctrl.first][queueCount_ - 1];
info.updated = false;
if (writeCount_ > 0) {
for (auto &ctrl : values_) {
Info &info = ctrl.second[writeCount_];
info = values_[ctrl.first][writeCount_ - 1];
info.updated = false;
}
} else {
/* Although it works, we don't expect this. */
LOG(RPiDelayedControls, Warning)
<< "push called before applyControls";
}

/* Update with new controls. */
Expand All @@ -175,18 +182,17 @@ bool DelayedControls::push(const ControlList &controls, const unsigned int cooki
if (controlParams_.find(id) == controlParams_.end())
return false;

Info &info = values_[id][queueCount_];
Info &info = values_[id][writeCount_];

info = Info(control.second);

LOG(RPiDelayedControls, Debug)
<< "Queuing " << id->name()
<< " to " << info.toString()
<< " at index " << queueCount_;
<< " at index " << writeCount_;
}

cookies_[queueCount_] = cookie;
queueCount_++;
cookies_[writeCount_] = cookie;

return true;
}
Expand All @@ -200,9 +206,9 @@ bool DelayedControls::push(const ControlList &controls, const unsigned int cooki
* the callers responsibility to not read too old sequence numbers that have been
* pushed out of the history.
*
* Historic values are evicted by pushing new values onto the queue using
* push(). The max history from the current sequence number that yields valid
* values are thus 16 minus number of controls pushed.
* Historic values are evicted by new frames arriving and applyControls
* advancing the head of the queue in the ring buffer. The valid history in
* the queue consists of 16 entries from this head of the queue.
*
* \return The controls at \a sequence number
*/
Expand Down Expand Up @@ -234,6 +240,10 @@ std::pair<ControlList, unsigned int> DelayedControls::get(uint32_t sequence)
* number. Any user of these helpers is responsible to inform the helper about
* the start of any frame. This can be connected with ease to the start of a
* exposure (SOE) V4L2 event.
*
* Controls will be sent to the device, each staggered by the appropriate
* number of frames for that control, so that they are applied at the same
* time.
*/
void DelayedControls::applyControls(uint32_t sequence)
{
Expand Down Expand Up @@ -277,12 +287,12 @@ void DelayedControls::applyControls(uint32_t sequence)
}
}

writeCount_ = sequence + 1;

while (writeCount_ > queueCount_) {
while (writeCount_ < sequence + 1) {
writeCount_++;
LOG(RPiDelayedControls, Debug)
<< "Queue is empty, auto queue no-op.";
push({}, cookies_[queueCount_ - 1]);
<< "Pushing noop with for index " << writeCount_
<< " with cookie " << cookies_[writeCount_ - 1];
push({}, cookies_[writeCount_ - 1]);
}

device_->setControls(&out);
Expand Down
1 change: 0 additions & 1 deletion src/libcamera/pipeline/rpi/common/delayed_controls.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class DelayedControls
std::unordered_map<const ControlId *, ControlParams> controlParams_;
unsigned int maxDelay_;

uint32_t queueCount_;
uint32_t writeCount_;
std::unordered_map<const ControlId *, RingBuffer<Info>> values_;
RingBuffer<unsigned int> cookies_;
Expand Down
Loading