[libcamera-devel] libcamera: pipeline: simple: Add support for controls
diff mbox series

Message ID CAJ+kdVH_0cE_Mj-P-P5-+rYeQKHJM2TTSN2wT904GFi6yN=4qQ@mail.gmail.com
State New
Headers show
Series
  • [libcamera-devel] libcamera: pipeline: simple: Add support for controls
Related show

Commit Message

Benjamin Schaaf Dec. 9, 2021, 11:59 a.m. UTC
Controls and control info are translated between libcamera and V4L2
inside the simple pipeline. Request controls are applied when a request
is next to be completed.

This also adds some additional draft controls needed for the PinePhone.

Bug: https://bugs.libcamera.org/show_bug.cgi?id=98
Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>
---
 src/libcamera/control_ids.yaml             |  24 ++
 src/libcamera/pipeline/simple/controls.cpp | 242 +++++++++++++++++++++
 src/libcamera/pipeline/simple/controls.h   |  30 +++
 src/libcamera/pipeline/simple/meson.build  |   1 +
 src/libcamera/pipeline/simple/simple.cpp   |  30 ++-
 src/libcamera/v4l2_device.cpp              |  12 +-
 6 files changed, 337 insertions(+), 2 deletions(-)
 create mode 100644 src/libcamera/pipeline/simple/controls.cpp
 create mode 100644 src/libcamera/pipeline/simple/controls.h

         return v4l2MenuControlInfo(ctrl);

Patch
diff mbox series

diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml
index 9d4638ae..2af230c3 100644
--- a/src/libcamera/control_ids.yaml
+++ b/src/libcamera/control_ids.yaml
@@ -406,6 +406,30 @@  controls:
             The camera will cancel any active or completed metering sequence.
             The AE algorithm is reset to its initial state.

+  - AutoGain:
+      type: bool
+      draft: true
+      description: |
+       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.
+
+  - AfEnabled:
+      type: bool
+      draft: true
+      description: |
+       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.
+
+  - AfStart:
+      type: void
+      draft: true
+      description: |
+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.
+
+  - AfStop:
+      type: void
+      draft: true
+      description: |
+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.
+
   - AfTrigger:
       type: int32_t
       draft: true
diff --git a/src/libcamera/pipeline/simple/controls.cpp
b/src/libcamera/pipeline/simple/controls.cpp
new file mode 100644
index 00000000..2d3cfc62
--- /dev/null
+++ b/src/libcamera/pipeline/simple/controls.cpp
@@ -0,0 +1,242 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Benjamin Schaaf
+ *
+ * controls.cpp - Simple pipeline control conversion
+ */
+
+#include "controls.h"
+
+#include <linux/v4l2-controls.h>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(SimplePipeline)
+
+/*
+ * These controls can be directly mapped between libcamera and V4L2 without
+ * doing any conversion to the ControlValue.
+ */
+namespace {
+std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {
+    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },
+    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },
+    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },
+    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },
+    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },
+    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },
+    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },
+    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },
+    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },
+};
+std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;
+}
+
+/**
+ * \brief Convert from a libcamera control to a V4L2 control.
+ *
+ * Can optionally convert the libcamera control and/or a set of libcamera
+ * control values to their V4L2 equivalents.
+ */
+bool simpleControlToV4L2(unsigned int control,
+             unsigned int *v4l2_control,
+             const ControlValue *control_values,
+             ControlValue *v4l2_values,
+             size_t num_values)
+{
+    // Convert controls
+    if (v4l2_control) {
+        auto it = controlsToV4L2.find(control);
+        if (it == controlsToV4L2.end())
+            return false;
+
+        *v4l2_control = it->second;
+    }
+
+    // Convert values
+    if (num_values == 0)
+        return true;
+
+    switch (control) {
+    case controls::AE_ENABLE:
+        for (size_t i = 0; i < num_values; ++i)
+            v4l2_values[i] =
ControlValue((int32_t)(control_values[i].get<bool>() ?
V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));
+        return true;
+    case controls::EXPOSURE_VALUE:
+    case controls::DIGITAL_GAIN:
+    case controls::ANALOGUE_GAIN:
+        for (size_t i = 0; i < num_values; ++i)
+            v4l2_values[i] =
ControlValue((int32_t)control_values[i].get<float>());
+        return true;
+    // Read only
+    case controls::AF_STATE:
+        return false;
+    default:
+        for (size_t i = 0; i < num_values; ++i)
+            v4l2_values[i] = control_values[i];
+        return true;
+    }
+}
+
+/**
+ * \brief Convert from a V4L2 control to a libcamera control.
+ *
+ * Can optionally convert the V4L2 control and/or a set of V4L2 control values
+ * to their libcamera equivalents.
+ */
+bool simpleControlFromV4L2(unsigned int v4l2_control,
+               unsigned int *control,
+               const ControlValue *v4l2_values,
+               ControlValue *control_values,
+               size_t num_values)
+{
+    // Initialize the inverse of controlsToV4L2
+    if (controlsFromV4L2.empty()) {
+        for (const auto &v : controlsToV4L2) {
+            controlsFromV4L2[v.second] = v.first;
+        }
+    }
+
+    // Convert control
+    if (control) {
+        auto it = controlsFromV4L2.find(v4l2_control);
+        if (it == controlsFromV4L2.end())
+            return false;
+
+        *control = it->second;
+    }
+
+    // Convert values
+    if (num_values == 0)
+        return true;
+
+    switch (v4l2_control) {
+    case V4L2_CID_EXPOSURE_AUTO:
+        for (size_t i = 0; i < num_values; ++i)
+            control_values[i] =
ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);
+        return true;
+    case V4L2_CID_EXPOSURE:
+    case V4L2_CID_GAIN:
+    case V4L2_CID_ANALOGUE_GAIN:
+        for (size_t i = 0; i < num_values; ++i)
+            control_values[i] =
ControlValue((float)v4l2_values[i].get<int32_t>());
+        return true;
+    case V4L2_CID_AUTO_FOCUS_STATUS:
+        for (size_t i = 0; i < num_values; ++i) {
+            switch (v4l2_values[i].get<int32_t>()) {
+            case V4L2_AUTO_FOCUS_STATUS_IDLE:
+                control_values[i] =
ControlValue((int32_t)controls::draft::AfStateInactive);
+                break;
+            case V4L2_AUTO_FOCUS_STATUS_BUSY:
+                control_values[i] =
ControlValue((int32_t)controls::draft::AfStateActiveScan);
+                break;
+            case V4L2_AUTO_FOCUS_STATUS_REACHED:
+                control_values[i] =
ControlValue((int32_t)controls::draft::AfStatePassiveFocused);
+                break;
+            case V4L2_AUTO_FOCUS_STATUS_FAILED:
+                control_values[i] =
ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);
+                break;
+            default:
+                LOG(SimplePipeline, Error)
+                    << "AUTO_FOCUS_STATUS has invalid value: "
+                    << utils::hex(v4l2_values[i].get<int32_t>());
+                /*TODO: Log Error*/
+                return false;
+            }
+        }
+        return true;
+    default:
+        for (size_t i = 0; i < num_values; ++i)
+            control_values[i] = v4l2_values[i];
+        return true;
+    }
+}
+
+/**
+ * \brief Convert a ControlInfoMap from V4L2 to libcamera.
+ *
+ * Converts both the control identifiers as well as all values.
+ */
+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)
+{
+    ControlInfoMap::Map info_map;
+
+    for (const auto &pair : v4l2_info_map) {
+        unsigned int v4l2_control = pair.first->id();
+        const ControlInfo &v4l2_info = pair.second;
+
+        unsigned int control;
+        ControlValue def;
+        if (!simpleControlFromV4L2(v4l2_control, &control,
&v4l2_info.def(), &def, 1))
+            continue;
+
+        const ControlId *control_id = controls::controls.at(control);
+
+        // ControlInfo has either a list of values or a minimum and
+        // maximum. This includes controls that have no values or are
+        // booleans.
+        ControlInfo info;
+        if (v4l2_info.values().empty()) {
+            ControlValue min, max;
+            simpleControlFromV4L2(v4l2_control, nullptr,
&v4l2_info.min(), &min, 1);
+            simpleControlFromV4L2(v4l2_control, nullptr,
&v4l2_info.max(), &max, 1);
+            info = ControlInfo(std::move(min), std::move(max), std::move(def));
+        } else {
+            std::vector<ControlValue> values;
+            values.resize(v4l2_info.values().size());
+            simpleControlFromV4L2(v4l2_control, nullptr,
v4l2_info.values().data(), values.data(), values.size());
+            info = ControlInfo(std::move(values), std::move(def));
+        }
+        info_map.emplace(control_id, std::move(info));
+    }
+
+    return ControlInfoMap(std::move(info_map), controls::controls);
+}
+
+/**
+ * \brief Convert a control list from libcamera to V4L2.
+ */
+ControlList simpleControlListToV4L2(const ControlList &controls)
+{
+    ControlList v4l2_controls;
+    for (const auto &pair : controls) {
+        unsigned int control = pair.first;
+        const ControlValue &value = pair.second;
+
+        unsigned int v4l2_control;
+        ControlValue v4l2_value;
+        if (!simpleControlToV4L2(control, &v4l2_control, &value,
&v4l2_value, 1)) {
+            LOG(SimplePipeline, Warning)
+                << "Control " << utils::hex(control)
+                << " does not have a V4L2 equivalent";
+            continue;
+        }
+
+        v4l2_controls.set(v4l2_control, v4l2_value);
+    }
+    return v4l2_controls;
+}
+
+/**
+ * \brief Convert a control list from V4L2 to libcamera.
+ */
+ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)
+{
+    ControlList controls;
+    for (const auto &pair : v4l2_controls) {
+        unsigned int v4l2_control = pair.first;
+        const ControlValue &v4l2_value = pair.second;
+
+        unsigned int control;
+        ControlValue value;
+        if (simpleControlFromV4L2(v4l2_control, &control,
&v4l2_value, &value, 1))
+            controls.set(control, value);
+    }
+    return controls;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/controls.h
b/src/libcamera/pipeline/simple/controls.h
new file mode 100644
index 00000000..cdbcaed1
--- /dev/null
+++ b/src/libcamera/pipeline/simple/controls.h
@@ -0,0 +1,30 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Benjamin Schaaf
+ *
+ * controls.h - Simple pipeline control conversion
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+bool simpleControlToV4L2(unsigned int control,
+             unsigned int *v4l2_control,
+             const ControlValue *control_values,
+             ControlValue *v4l2_values,
+             size_t num_values);
+bool simpleControlFromV4L2(unsigned int v4l2_control,
+               unsigned int *control,
+               const ControlValue *v4l2_values,
+               ControlValue *control_values,
+               size_t num_values);
+
+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);
+
+ControlList simpleControlListToV4L2(const ControlList &controls);
+ControlList simpleControlListFromV4L2(const ControlList &controls);
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/meson.build
b/src/libcamera/pipeline/simple/meson.build
index 9c99b32f..0c60d65a 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,6 +1,7 @@ 
 # SPDX-License-Identifier: CC0-1.0

 libcamera_sources += files([
+    'controls.cpp',
     'converter.cpp',
     'simple.cpp',
 ])
diff --git a/src/libcamera/pipeline/simple/simple.cpp
b/src/libcamera/pipeline/simple/simple.cpp
index a597e27f..1717a1a7 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -36,6 +36,7 @@ 
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"

+#include "controls.h"
 #include "converter.h"

 namespace libcamera {
@@ -181,6 +182,7 @@  public:
     int setupLinks();
     int setupFormats(V4L2SubdeviceFormat *format,
              V4L2Subdevice::Whence whence);
+    int setRequestControls(Request *request);
     void bufferReady(FrameBuffer *buffer);

     unsigned int streamIndex(const Stream *stream) const
@@ -519,7 +521,8 @@  int SimpleCameraData::init()
             formats_[fmt] = &config;
     }

-    properties_ = sensor_->properties();
+    properties_ = simpleControlListFromV4L2(sensor_->properties());
+    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());

     return 0;
 }
@@ -624,6 +627,23 @@  int
SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
     return 0;
 }

+int SimpleCameraData::setRequestControls(Request *request)
+{
+    SimplePipelineHandler *pipe = SimpleCameraData::pipe();
+
+    // Apply controls only to one entity. If there's a subdevice use that.
+    V4L2Device *control_device = video_;
+    for (const SimpleCameraData::Entity &e : entities_) {
+        V4L2Subdevice *subdev = pipe->subdev(e.entity);
+        if (subdev) {
+            control_device = subdev;
+        }
+    }
+
+    ControlList controls = simpleControlListToV4L2(request->controls());
+    return control_device->setControls(&controls);
+}
+
 void SimpleCameraData::bufferReady(FrameBuffer *buffer)
 {
     SimplePipelineHandler *pipe = SimpleCameraData::pipe();
@@ -666,6 +686,10 @@  void SimpleCameraData::bufferReady(FrameBuffer *buffer)
         return;
     }

+    // Set the controls for the next queued request
+    if (!queuedRequests_.empty())
+        setRequestControls(queuedRequests_.front());
+
     /*
      * Record the sensor's timestamp in the request metadata. The request
      * needs to be obtained from the user-facing buffer, as internal
@@ -1033,6 +1057,10 @@  int SimplePipelineHandler::start(Camera
*camera, [[maybe_unused]] const ControlL
         return ret;
     }

+    // Apply controls from first request
+    if (!data->queuedRequests_.empty())
+        data->setRequestControls(data->queuedRequests_.front());
+
     if (data->useConverter_) {
         ret = data->converter_->start();
         if (ret < 0) {
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 39f36009..7a3b56a2 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -293,6 +293,13 @@  int V4L2Device::setControls(ControlList *ctrls)
         /* Set the v4l2_ext_control value for the write operation. */
         ControlValue &value = ctrl->second;
         switch (iter->first->type()) {
+        case ControlTypeBool:
+            v4l2Ctrl.value64 = value.get<bool>();
+            break;
+
+        case ControlTypeNone:
+            break;
+
         case ControlTypeInteger64:
             v4l2Ctrl.value64 = value.get<int64_t>();
             break;
@@ -476,7 +483,6 @@  ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)
         return ControlTypeInteger64;

     case V4L2_CTRL_TYPE_MENU:
-    case V4L2_CTRL_TYPE_BUTTON:
     case V4L2_CTRL_TYPE_BITMASK:
     case V4L2_CTRL_TYPE_INTEGER_MENU:
         /*
@@ -485,6 +491,7 @@  ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)
          */
         return ControlTypeInteger32;

+    case V4L2_CTRL_TYPE_BUTTON:
     default:
         return ControlTypeNone;
     }
@@ -527,6 +534,9 @@  ControlInfo V4L2Device::v4l2ControlInfo(const
v4l2_query_ext_ctrl &ctrl)
                    static_cast<int64_t>(ctrl.maximum),
                    static_cast<int64_t>(ctrl.default_value));

+    case V4L2_CTRL_TYPE_BUTTON:
+        return ControlInfo(ControlValue(), ControlValue(), ControlValue());
+
     case V4L2_CTRL_TYPE_INTEGER_MENU:
     case V4L2_CTRL_TYPE_MENU: