@@ -431,6 +431,15 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)
rpiMetadata.clear();
fillDeviceStatus(params.sensorControls, ipaContext);
+ /*
+ * When there are controls, it's important that we don't skip running the
+ * IPAs, as that can mess with synchronisation. Crucially though, we need
+ * to know whether there were controls when this comes back as the
+ * _delayed_ metadata, hence why we flag this in the metadata itself.
+ */
+ if (!params.requestControls.empty())
+ rpiMetadata.set("ipa.request_controls", true);
+
if (params.buffers.embedded) {
/*
* Pipeline handler has supplied us with an embedded data buffer,
@@ -451,7 +460,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)
*/
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;
@@ -464,9 +473,13 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)
*/
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
@@ -535,7 +548,7 @@ void IpaBase::processStats(const ProcessParams ¶ms)
ControlList ctrls(sensorCtrls_);
applyAGC(&agcStatus, ctrls);
rpiMetadata.set("agc.status", agcStatus);
- setDelayedControls.emit(ctrls, ipaContext);
+ setDelayedControls.emit(ctrls, params.ipaContext);
setCameraTimeoutValue();
}
@@ -951,8 +964,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;
}
@@ -976,9 +987,6 @@ void IpaBase::applyControls(const ControlList &controls)
break;
agc->setFixedGain(0, ctrl.second.get<float>());
-
- libcameraMetadata_.set(controls::AnalogueGain,
- ctrl.second.get<float>());
break;
}
@@ -1528,4 +1528,50 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request
}
}
+static bool isControlDelayed(unsigned int id)
+{
+ return id == controls::ExposureTime ||
+ id == controls::AnalogueGain ||
+ id == controls::FrameDurationLimits ||
+ id == controls::AeEnable ||
+ id == controls::ExposureTimeMode ||
+ id == controls::AnalogueGainMode;
+}
+
+void CameraData::handleControlLists(uint32_t delayContext)
+{
+ /*
+ * THe delayContext is the sequence number after it's gone through the various
+ * pipeline delays, so that's what gets reported as the "ControlListSequence"
+ * in the metadata, being the sequence number of the request whose ControlList
+ * has just been applied.
+ */
+ Request *request = requestQueue_.front();
+ request->_d()->metadata().set(controls::rpi::ControlListSequence, delayContext);
+
+ /*
+ * Controls that take effect immediately (typically ISP controls) have to be
+ * delayed so as to synchronise with those controls that do get delayed. So we
+ * must remove them from the current request, and push them onto a queue so
+ * that they can be used later.
+ */
+ ControlList controls = std::move(request->controls());
+ request->controls().clear();
+ immediateControls_.push({ request->sequence(), {} });
+ for (const auto &ctrl : controls) {
+ if (isControlDelayed(ctrl.first))
+ request->controls().set(ctrl.first, ctrl.second);
+ else
+ immediateControls_.back().controls.set(ctrl.first, ctrl.second);
+ }
+
+ /* "Immediate" controls that have become due are now merged back into this request. */
+ while (!immediateControls_.empty() &&
+ immediateControls_.front().controlListId <= delayContext) {
+ request->controls().merge(immediateControls_.front().controls,
+ ControlList::MergePolicy::OverwriteExisting);
+ immediateControls_.pop();
+ }
+}
+
} /* namespace libcamera */
@@ -180,10 +180,18 @@ public:
ClockRecovery wallClockRecovery_;
+ struct ImmediateControlsEntry {
+ uint64_t controlListId;
+ ControlList controls;
+ };
+ std::queue<ImmediateControlsEntry> immediateControls_;
+
protected:
void fillRequestMetadata(const ControlList &bufferControls,
Request *request);
+ void handleControlLists(uint32_t delayContext);
+
virtual void tryRunPipeline() = 0;
private:
@@ -2322,6 +2322,9 @@ void PiSPCameraData::tryRunPipeline()
fillRequestMetadata(job.sensorControls, request);
+ /* This sorts out synchronisation with ControlLists in earlier requests. */
+ handleControlLists(job.delayContext);
+
/* Set our state to say the pipeline is active. */
state_ = State::Busy;
@@ -939,6 +939,9 @@ void Vc4CameraData::tryRunPipeline()
fillRequestMetadata(bayerFrame.controls, request);
+ /* This sorts out synchronisation with ControlLists in earlier requests. */
+ handleControlLists(bayerFrame.delayContext);
+
/* Set our state to say the pipeline is active. */
state_ = State::Busy;