[{"id":21684,"web_url":"https://patchwork.libcamera.org/comment/21684/","msgid":"<20211209085830.affr22qlko7sxxvm@uno.localdomain>","date":"2021-12-09T08:58:30","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hello Benjamin,\n\nOn Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> Controls and control info are translated between libcamera and V4L2\n> inside the simple pipeline. Request controls are applied when a request\n> is next to be completed.\n>\n> This also adds some additional draft controls needed for the PinePhone.\n\nSorry to get straight to this before looking at the patch content, but\nwe do enforce a code style as reported here\nhttps://libcamera.org/coding-style.html#coding-style-guidelines\n\nWe have a tool that might help you catching style issues at\nutils/checkstyle.py\n\nCare to reformat your patches to comply with the project code style ?\n\n>\n> Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> ---\n>  src/libcamera/control_ids.yaml             |  24 +++\n>  src/libcamera/controls.cpp                 |   6 -\n>  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n>  src/libcamera/pipeline/simple/controls.h   |  26 +++\n>  src/libcamera/pipeline/simple/meson.build  |   1 +\n>  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n>  6 files changed, 310 insertions(+), 7 deletions(-)\n>  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n>  create mode 100644 src/libcamera/pipeline/simple/controls.h\n>\n> diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> index 9d4638ae..2af230c3 100644\n> --- a/src/libcamera/control_ids.yaml\n> +++ b/src/libcamera/control_ids.yaml\n> @@ -406,6 +406,30 @@ controls:\n>              The camera will cancel any active or completed metering sequence.\n>              The AE algorithm is reset to its initial state.\n>\n> +  - AutoGain:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> +\n> +  - AfEnabled:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> +\n> +  - AfStart:\n> +      type: void\n\nWhy type void ? Isn't this a boolean ?\n\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> +\n> +  - AfStop:\n> +      type: void\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> +\n\nWe're in the process of reworking controls related to gain and focus,\nbut for the moment, as we comply with Android by having their controls\ndefined as draft, I'm not opposed to have these here.\n\nI'm worried once we have applications use them, we will never move to\nthe newly defined ones though unless we forcefully remove them in\nfuture...\n\n\n>    - AfTrigger:\n>        type: int32_t\n>        draft: true\n> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> index 0d8c0a5c..1f65fc73 100644\n> --- a/src/libcamera/controls.cpp\n> +++ b/src/libcamera/controls.cpp\n> @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> int id) const\n>  {\n>      const auto iter = controls_.find(id);\n>      if (iter == controls_.end()) {\n> -        LOG(Controls, Error)\n> -            << \"Control \" << utils::hex(id) << \" not found\";\n> -\n\nOuch, why remove the error message ?\n\n>          return nullptr;\n>      }\n>\n> @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> int id) const\n>  ControlValue *ControlList::find(unsigned int id)\n>  {\n>      if (validator_ && !validator_->validate(id)) {\n> -        LOG(Controls, Error)\n> -            << \"Control \" << utils::hex(id)\n> -            << \" is not valid for \" << validator_->name();\n>          return nullptr;\n>      }\n>\n> diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> b/src/libcamera/pipeline/simple/controls.cpp\n> new file mode 100644\n> index 00000000..32695749\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.cpp\n> @@ -0,0 +1,230 @@\n\nFile license ? You can copy the SPDX header from other files and\nattribute the copyright to you or any one you like\n\n> +#include \"controls.h\"\n> +\n> +#include <linux/v4l2-controls.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/control_ids.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(SimplePipeline)\n> +\n> +/*\n> + * These controls can be directly mapped between libcamera and V4L2 without\n> + * doing any conversion to the ControlValue.\n> + */\n> +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> +};\n\nIf them map is meant for internal use only you can declare it in an\nanonymous namespace\n\nnamsepace {\n        std::unordered_map<>...\n\n};\n\nnamespace libcamera {\n\n};\n\n> +\n> +/*\n> + * Convert from a libcamera control to a V4L2 control, optionally\n> also convert a\n> + * set of ControlValues.\n> + */\n\nWe use doxygen for documenting the code. Please see other files as an\nexample.\n\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values)\n\nBefore looking at the implementation, let's reason a bit on the\narchitecture.\n\nIs this control mapping valid for all platforms using the simple\npipeline handler ?\n\nIs the device on which controls have to be applied the same for all\nplatforms ?\n\nShould control handling be broken down to a platform specific\ncomponent to be selected at compile time ?\n\nAlso, I would like to see this implemented thorough a componenet with\na single interface towards the pipeline handler rather than a raw set\nof helper functions. We can indeed help designing that once we have\nthe previous question clarified.\n\nI'm not even sure this is the direction we want to got with the simple\npipeline handler (platform-specific backends), and I would like to\nhear Laurent's opinion on this, but I see a potential for doing what\nwe do with the android backend selection through the\n'android_platform' build option.\n\n        option('android_platform',\n                type : 'combo',\n                choices : ['cros', 'generic'],\n                value : 'generic',\n                description : 'Select the Android platform to compile for')\n\n> +{\n> +    // Convert controls\n> +    if (v4l2_control) {\n> +        auto it = controlsToV4L2.find(control);\n> +        if (it == controlsToV4L2.end())\n> +            return false;\n> +\n> +        *v4l2_control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (control) {\n> +    case controls::AE_ENABLE:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)(control_values[i].get<bool>() ?\n> V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> +        return true;\n> +    case controls::EXPOSURE_VALUE:\n> +    case controls::DIGITAL_GAIN:\n> +    case controls::ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)control_values[i].get<float>());\n> +        return true;\n> +    // Read only\n> +    case controls::AF_STATE:\n> +        return false;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] = control_values[i];\n> +        return true;\n> +    }\n> +\n> +}\n> +\n> +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> +\n> +/*\n> + * Convert from a V4L2 control to a libcamera control, optionally\n> also convert a\n> + * set of ControlValues.\n> + */\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values)\n> +{\n> +    // Initialize the inverse of controlsToV4L2\n> +    if (controlsFromV4L2.empty()) {\n> +        for (const auto &v : controlsToV4L2) {\n> +            controlsFromV4L2[v.second] = v.first;\n> +        }\n> +    }\n> +\n> +    // Convert control\n> +    if (control) {\n> +        auto it = controlsFromV4L2.find(v4l2_control);\n> +        if (it == controlsFromV4L2.end())\n> +            return false;\n> +\n> +        *control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (v4l2_control) {\n> +    case V4L2_CID_EXPOSURE_AUTO:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> +        return true;\n> +    case V4L2_CID_EXPOSURE:\n> +    case V4L2_CID_GAIN:\n> +    case V4L2_CID_ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue((float)v4l2_values[i].get<int32_t>());\n> +        return true;\n> +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> +        for (size_t i = 0; i < num_values; ++i) {\n> +            switch (v4l2_values[i].get<int32_t>()) {\n> +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateInactive);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> +                break;\n> +            default:\n> +                LOG(SimplePipeline, Error)\n> +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> +                /*TODO: Log Error*/\n> +                return false;\n> +            }\n> +        }\n> +        return true;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] = v4l2_values[i];\n> +        return true;\n> +    }\n> +}\n> +\n> +/*\n> + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> + * identifiers as well as all values.\n> + */\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> +{\n> +    ControlInfoMap::Map info_map;\n> +\n> +    for (const auto &pair : v4l2_info_map) {\n> +        unsigned int v4l2_control = pair.first->id();\n> +        const ControlInfo &v4l2_info = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue def;\n> +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_info.def(), &def, 1))\n> +            continue;\n> +\n> +        const ControlId *control_id = controls::controls.at(control);\n> +\n> +        // ControlInfo has either a list of values or a minimum and\n> +        // maximum. This includes controls that have no values or are\n> +        // booleans.\n> +        ControlInfo info;\n> +        if (v4l2_info.values().empty()) {\n> +            ControlValue min, max;\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.min(), &min, 1);\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.max(), &max, 1);\n> +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> +        } else {\n> +            std::vector<ControlValue> values;\n> +            values.resize(v4l2_info.values().size());\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> v4l2_info.values().data(), values.data(), values.size());\n> +            info = ControlInfo(std::move(values), std::move(def));\n> +        }\n> +        info_map.emplace(control_id, std::move(info));\n> +    }\n> +\n> +    return ControlInfoMap(std::move(info_map), controls::controls);\n> +}\n> +\n> +/*\n> + * Convert a control list from libcamera to V4L2.\n> + */\n> +ControlList simpleControlListToV4L2(const ControlList &controls)\n> +{\n> +    ControlList v4l2_controls;\n> +    for (const auto &pair : controls) {\n> +        unsigned int control = pair.first;\n> +        const ControlValue &value = pair.second;\n> +\n> +        unsigned int v4l2_control;\n> +        ControlValue v4l2_value;\n> +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> &v4l2_value, 1)) {\n> +            LOG(SimplePipeline, Warning)\n> +                << \"Control \" << utils::hex(control)\n> +                << \" does not have a V4L2 equivalent\";\n> +            continue;\n> +        }\n> +\n> +        v4l2_controls.set(v4l2_control, v4l2_value);\n> +    }\n> +    return v4l2_controls;\n> +}\n> +\n> +/*\n> + * Convert a control list from V4L2 to libcamera.\n> + */\n> +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> +{\n> +    ControlList controls;\n> +    for (const auto &pair : v4l2_controls) {\n> +        unsigned int v4l2_control = pair.first;\n> +        const ControlValue &v4l2_value = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue value;\n> +        if (simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_value, &value, 1))\n> +            controls.set(control, value);\n> +    }\n> +    return controls;\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/simple/controls.h\n> b/src/libcamera/pipeline/simple/controls.h\n> new file mode 100644\n> index 00000000..114c5fc2\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.h\n\nSame comment, license and copyright\n\n> @@ -0,0 +1,26 @@\n> +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n\n#pragma once\n\n> +\n> +#include <libcamera/controls.h>\n> +\n> +namespace libcamera {\n> +\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values);\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values);\n> +\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> +\n> +ControlList simpleControlListToV4L2(const ControlList &controls);\n> +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> +\n> +} /* namespace libcamera */\n> +\n> +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> diff --git a/src/libcamera/pipeline/simple/meson.build\n> b/src/libcamera/pipeline/simple/meson.build\n> index 9c99b32f..0c60d65a 100644\n> --- a/src/libcamera/pipeline/simple/meson.build\n> +++ b/src/libcamera/pipeline/simple/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libcamera_sources += files([\n> +    'controls.cpp',\n>      'converter.cpp',\n>      'simple.cpp',\n>  ])\n> diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> b/src/libcamera/pipeline/simple/simple.cpp\n> index a597e27f..b0d4a62a 100644\n> --- a/src/libcamera/pipeline/simple/simple.cpp\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -37,6 +37,7 @@\n>  #include \"libcamera/internal/v4l2_videodevice.h\"\n>\n>  #include \"converter.h\"\n> +#include \"controls.h\"\n>\n>  namespace libcamera {\n>\n> @@ -181,6 +182,7 @@ public:\n>      int setupLinks();\n>      int setupFormats(V4L2SubdeviceFormat *format,\n>               V4L2Subdevice::Whence whence);\n> +    int setRequestControls(Request *request);\n>      void bufferReady(FrameBuffer *buffer);\n>\n>      unsigned int streamIndex(const Stream *stream) const\n> @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n>              formats_[fmt] = &config;\n>      }\n>\n> -    properties_ = sensor_->properties();\n> +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n>\n>      return 0;\n>  }\n> @@ -624,6 +627,23 @@ int\n> SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n>      return 0;\n>  }\n>\n> +int SimpleCameraData::setRequestControls(Request *request)\n> +{\n> +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> +\n> +    // Apply controls only to one entity. If there's a subdevice use that.\n> +    V4L2Device *control_device = video_;\n> +    for (const SimpleCameraData::Entity &e : entities_) {\n> +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> +        if (subdev) {\n> +            control_device = subdev;\n> +        }\n> +    }\n> +\n> +    ControlList controls = simpleControlListToV4L2(request->controls());\n> +    return control_device->setControls(&controls);\n> +}\n> +\n>  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>  {\n>      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>          return;\n>      }\n>\n> +    // Set the controls for the next queued request\n> +    if (!queuedRequests_.empty())\n> +        setRequestControls(queuedRequests_.front());\n> +\n>      /*\n>       * Record the sensor's timestamp in the request metadata. The request\n>       * needs to be obtained from the user-facing buffer, as internal\n> @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> *camera, [[maybe_unused]] const ControlL\n>          return ret;\n>      }\n>\n> +    // Apply controls from first request\n> +    if (!data->queuedRequests_.empty())\n> +        data->setRequestControls(data->queuedRequests_.front());\n> +\n>      if (data->useConverter_) {\n>          ret = data->converter_->start();\n>          if (ret < 0) {\n> --\n> 2.25.1","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C1E55BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 08:57:40 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 25CBC60876;\n\tThu,  9 Dec 2021 09:57:40 +0100 (CET)","from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net\n\t[217.70.183.198])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4D10660224\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 09:57:38 +0100 (CET)","(Authenticated sender: jacopo@jmondi.org)\n\tby relay6-d.mail.gandi.net (Postfix) with ESMTPSA id BF39DC000B;\n\tThu,  9 Dec 2021 08:57:37 +0000 (UTC)"],"Date":"Thu, 9 Dec 2021 09:58:30 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Benjamin Schaaf <ben.schaaf@gmail.com>","Message-ID":"<20211209085830.affr22qlko7sxxvm@uno.localdomain>","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21706,"web_url":"https://patchwork.libcamera.org/comment/21706/","msgid":"<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","date":"2021-12-09T10:55:33","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":108,"url":"https://patchwork.libcamera.org/api/people/108/","name":"Benjamin Schaaf","email":"ben.schaaf@gmail.com"},"content":"Thanks for the review, I'll put the updated patch at the end.\n\nOn Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n>\n> Hello Benjamin,\n>\n> On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > Controls and control info are translated between libcamera and V4L2\n> > inside the simple pipeline. Request controls are applied when a request\n> > is next to be completed.\n> >\n> > This also adds some additional draft controls needed for the PinePhone.\n>\n> Sorry to get straight to this before looking at the patch content, but\n> we do enforce a code style as reported here\n> https://libcamera.org/coding-style.html#coding-style-guidelines\n>\n> We have a tool that might help you catching style issues at\n> utils/checkstyle.py\n>\n> Care to reformat your patches to comply with the project code style ?\n>\n> >\n> > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > ---\n> >  src/libcamera/control_ids.yaml             |  24 +++\n> >  src/libcamera/controls.cpp                 |   6 -\n> >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> >  6 files changed, 310 insertions(+), 7 deletions(-)\n> >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> >\n> > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > index 9d4638ae..2af230c3 100644\n> > --- a/src/libcamera/control_ids.yaml\n> > +++ b/src/libcamera/control_ids.yaml\n> > @@ -406,6 +406,30 @@ controls:\n> >              The camera will cancel any active or completed metering sequence.\n> >              The AE algorithm is reset to its initial state.\n> >\n> > +  - AutoGain:\n> > +      type: bool\n> > +      draft: true\n> > +      description: |\n> > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > +\n> > +  - AfEnabled:\n> > +      type: bool\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > +\n> > +  - AfStart:\n> > +      type: void\n>\n> Why type void ? Isn't this a boolean ?\n\nV4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\nperforms an action when the control is set. Thus type void. Same for\nV4L2_CID_AUTO_FOCUS_END.\n\nIt seems I forgot to include some required type conversion logic in\nv4l2_device, not sure how that got missed.\n\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > +\n> > +  - AfStop:\n> > +      type: void\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > +\n>\n> We're in the process of reworking controls related to gain and focus,\n> but for the moment, as we comply with Android by having their controls\n> defined as draft, I'm not opposed to have these here.\n>\n> I'm worried once we have applications use them, we will never move to\n> the newly defined ones though unless we forcefully remove them in\n> future...\n\nFWIW given that libcamera doesn't have versions/releases I don't\npersonally expect a stable API.\n\n> >    - AfTrigger:\n> >        type: int32_t\n> >        draft: true\n> > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > index 0d8c0a5c..1f65fc73 100644\n> > --- a/src/libcamera/controls.cpp\n> > +++ b/src/libcamera/controls.cpp\n> > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > int id) const\n> >  {\n> >      const auto iter = controls_.find(id);\n> >      if (iter == controls_.end()) {\n> > -        LOG(Controls, Error)\n> > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > -\n>\n> Ouch, why remove the error message ?\n\nMy bad, seems I was misinterpreting when that error could show up.\nI'll add it back in.\n\n> >          return nullptr;\n> >      }\n> >\n> > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > int id) const\n> >  ControlValue *ControlList::find(unsigned int id)\n> >  {\n> >      if (validator_ && !validator_->validate(id)) {\n> > -        LOG(Controls, Error)\n> > -            << \"Control \" << utils::hex(id)\n> > -            << \" is not valid for \" << validator_->name();\n> >          return nullptr;\n> >      }\n> >\n> > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > b/src/libcamera/pipeline/simple/controls.cpp\n> > new file mode 100644\n> > index 00000000..32695749\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > @@ -0,0 +1,230 @@\n>\n> File license ? You can copy the SPDX header from other files and\n> attribute the copyright to you or any one you like\n>\n> > +#include \"controls.h\"\n> > +\n> > +#include <linux/v4l2-controls.h>\n> > +\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include <libcamera/control_ids.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > +\n> > +/*\n> > + * These controls can be directly mapped between libcamera and V4L2 without\n> > + * doing any conversion to the ControlValue.\n> > + */\n> > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > +};\n>\n> If them map is meant for internal use only you can declare it in an\n> anonymous namespace\n>\n> namsepace {\n>         std::unordered_map<>...\n>\n> };\n>\n> namespace libcamera {\n>\n> };\n\nThat's the same as static though?\n\n> > +\n> > +/*\n> > + * Convert from a libcamera control to a V4L2 control, optionally\n> > also convert a\n> > + * set of ControlValues.\n> > + */\n>\n> We use doxygen for documenting the code. Please see other files as an\n> example.\n>\n> > +bool simpleControlToV4L2(unsigned int control,\n> > +             unsigned int *v4l2_control,\n> > +             const ControlValue *control_values,\n> > +             ControlValue *v4l2_values,\n> > +             size_t num_values)\n>\n> Before looking at the implementation, let's reason a bit on the\n> architecture.\n>\n> Is this control mapping valid for all platforms using the simple\n> pipeline handler ?\n>\n> Is the device on which controls have to be applied the same for all\n> platforms ?\n>\n> Should control handling be broken down to a platform specific\n> component to be selected at compile time ?\n\nI'm not sure what you mean by platform here? Do you mean Linux vs\nAndroid, x86 vs arm or PinePhone vs Librem 5?\n\nThe way I see it, the simple pipeline is just a simple abstraction on\nV4L2 and this is a simple conversion between V4L2 and libcamera\ncontrols.\n\n> Also, I would like to see this implemented thorough a componenet with\n> a single interface towards the pipeline handler rather than a raw set\n> of helper functions. We can indeed help designing that once we have\n> the previous question clarified.\n>\n> I'm not even sure this is the direction we want to got with the simple\n> pipeline handler (platform-specific backends), and I would like to\n> hear Laurent's opinion on this, but I see a potential for doing what\n> we do with the android backend selection through the\n> 'android_platform' build option.\n>\n>         option('android_platform',\n>                 type : 'combo',\n>                 choices : ['cros', 'generic'],\n>                 value : 'generic',\n>                 description : 'Select the Android platform to compile for')\n>\n> > +{\n> > +    // Convert controls\n> > +    if (v4l2_control) {\n> > +        auto it = controlsToV4L2.find(control);\n> > +        if (it == controlsToV4L2.end())\n> > +            return false;\n> > +\n> > +        *v4l2_control = it->second;\n> > +    }\n> > +\n> > +    // Convert values\n> > +    if (num_values == 0)\n> > +        return true;\n> > +\n> > +    switch (control) {\n> > +    case controls::AE_ENABLE:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] =\n> > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > +        return true;\n> > +    case controls::EXPOSURE_VALUE:\n> > +    case controls::DIGITAL_GAIN:\n> > +    case controls::ANALOGUE_GAIN:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] =\n> > ControlValue((int32_t)control_values[i].get<float>());\n> > +        return true;\n> > +    // Read only\n> > +    case controls::AF_STATE:\n> > +        return false;\n> > +    default:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] = control_values[i];\n> > +        return true;\n> > +    }\n> > +\n> > +}\n> > +\n> > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > +\n> > +/*\n> > + * Convert from a V4L2 control to a libcamera control, optionally\n> > also convert a\n> > + * set of ControlValues.\n> > + */\n> > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > +               unsigned int *control,\n> > +               const ControlValue *v4l2_values,\n> > +               ControlValue *control_values,\n> > +               size_t num_values)\n> > +{\n> > +    // Initialize the inverse of controlsToV4L2\n> > +    if (controlsFromV4L2.empty()) {\n> > +        for (const auto &v : controlsToV4L2) {\n> > +            controlsFromV4L2[v.second] = v.first;\n> > +        }\n> > +    }\n> > +\n> > +    // Convert control\n> > +    if (control) {\n> > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > +        if (it == controlsFromV4L2.end())\n> > +            return false;\n> > +\n> > +        *control = it->second;\n> > +    }\n> > +\n> > +    // Convert values\n> > +    if (num_values == 0)\n> > +        return true;\n> > +\n> > +    switch (v4l2_control) {\n> > +    case V4L2_CID_EXPOSURE_AUTO:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] =\n> > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > +        return true;\n> > +    case V4L2_CID_EXPOSURE:\n> > +    case V4L2_CID_GAIN:\n> > +    case V4L2_CID_ANALOGUE_GAIN:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] =\n> > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > +        return true;\n> > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > +        for (size_t i = 0; i < num_values; ++i) {\n> > +            switch (v4l2_values[i].get<int32_t>()) {\n> > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > +                break;\n> > +            default:\n> > +                LOG(SimplePipeline, Error)\n> > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > +                /*TODO: Log Error*/\n> > +                return false;\n> > +            }\n> > +        }\n> > +        return true;\n> > +    default:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] = v4l2_values[i];\n> > +        return true;\n> > +    }\n> > +}\n> > +\n> > +/*\n> > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > + * identifiers as well as all values.\n> > + */\n> > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > +{\n> > +    ControlInfoMap::Map info_map;\n> > +\n> > +    for (const auto &pair : v4l2_info_map) {\n> > +        unsigned int v4l2_control = pair.first->id();\n> > +        const ControlInfo &v4l2_info = pair.second;\n> > +\n> > +        unsigned int control;\n> > +        ControlValue def;\n> > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > &v4l2_info.def(), &def, 1))\n> > +            continue;\n> > +\n> > +        const ControlId *control_id = controls::controls.at(control);\n> > +\n> > +        // ControlInfo has either a list of values or a minimum and\n> > +        // maximum. This includes controls that have no values or are\n> > +        // booleans.\n> > +        ControlInfo info;\n> > +        if (v4l2_info.values().empty()) {\n> > +            ControlValue min, max;\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > &v4l2_info.min(), &min, 1);\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > &v4l2_info.max(), &max, 1);\n> > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > +        } else {\n> > +            std::vector<ControlValue> values;\n> > +            values.resize(v4l2_info.values().size());\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > v4l2_info.values().data(), values.data(), values.size());\n> > +            info = ControlInfo(std::move(values), std::move(def));\n> > +        }\n> > +        info_map.emplace(control_id, std::move(info));\n> > +    }\n> > +\n> > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > +}\n> > +\n> > +/*\n> > + * Convert a control list from libcamera to V4L2.\n> > + */\n> > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > +{\n> > +    ControlList v4l2_controls;\n> > +    for (const auto &pair : controls) {\n> > +        unsigned int control = pair.first;\n> > +        const ControlValue &value = pair.second;\n> > +\n> > +        unsigned int v4l2_control;\n> > +        ControlValue v4l2_value;\n> > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > &v4l2_value, 1)) {\n> > +            LOG(SimplePipeline, Warning)\n> > +                << \"Control \" << utils::hex(control)\n> > +                << \" does not have a V4L2 equivalent\";\n> > +            continue;\n> > +        }\n> > +\n> > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > +    }\n> > +    return v4l2_controls;\n> > +}\n> > +\n> > +/*\n> > + * Convert a control list from V4L2 to libcamera.\n> > + */\n> > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > +{\n> > +    ControlList controls;\n> > +    for (const auto &pair : v4l2_controls) {\n> > +        unsigned int v4l2_control = pair.first;\n> > +        const ControlValue &v4l2_value = pair.second;\n> > +\n> > +        unsigned int control;\n> > +        ControlValue value;\n> > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > &v4l2_value, &value, 1))\n> > +            controls.set(control, value);\n> > +    }\n> > +    return controls;\n> > +}\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > b/src/libcamera/pipeline/simple/controls.h\n> > new file mode 100644\n> > index 00000000..114c5fc2\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/simple/controls.h\n>\n> Same comment, license and copyright\n>\n> > @@ -0,0 +1,26 @@\n> > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n>\n> #pragma once\n\nAll the code I've seen uses #ifndef instead of #pragma once. I'd\nprefer to use #pragma once but it seems inconsistent with the rest of\nthe codebase?\n\n> > +\n> > +#include <libcamera/controls.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +bool simpleControlToV4L2(unsigned int control,\n> > +             unsigned int *v4l2_control,\n> > +             const ControlValue *control_values,\n> > +             ControlValue *v4l2_values,\n> > +             size_t num_values);\n> > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > +               unsigned int *control,\n> > +               const ControlValue *v4l2_values,\n> > +               ControlValue *control_values,\n> > +               size_t num_values);\n> > +\n> > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > +\n> > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > +\n> > +} /* namespace libcamera */\n> > +\n> > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > b/src/libcamera/pipeline/simple/meson.build\n> > index 9c99b32f..0c60d65a 100644\n> > --- a/src/libcamera/pipeline/simple/meson.build\n> > +++ b/src/libcamera/pipeline/simple/meson.build\n> > @@ -1,6 +1,7 @@\n> >  # SPDX-License-Identifier: CC0-1.0\n> >\n> >  libcamera_sources += files([\n> > +    'controls.cpp',\n> >      'converter.cpp',\n> >      'simple.cpp',\n> >  ])\n> > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > b/src/libcamera/pipeline/simple/simple.cpp\n> > index a597e27f..b0d4a62a 100644\n> > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > @@ -37,6 +37,7 @@\n> >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> >\n> >  #include \"converter.h\"\n> > +#include \"controls.h\"\n> >\n> >  namespace libcamera {\n> >\n> > @@ -181,6 +182,7 @@ public:\n> >      int setupLinks();\n> >      int setupFormats(V4L2SubdeviceFormat *format,\n> >               V4L2Subdevice::Whence whence);\n> > +    int setRequestControls(Request *request);\n> >      void bufferReady(FrameBuffer *buffer);\n> >\n> >      unsigned int streamIndex(const Stream *stream) const\n> > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> >              formats_[fmt] = &config;\n> >      }\n> >\n> > -    properties_ = sensor_->properties();\n> > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> >\n> >      return 0;\n> >  }\n> > @@ -624,6 +627,23 @@ int\n> > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> >      return 0;\n> >  }\n> >\n> > +int SimpleCameraData::setRequestControls(Request *request)\n> > +{\n> > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > +\n> > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > +    V4L2Device *control_device = video_;\n> > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > +        if (subdev) {\n> > +            control_device = subdev;\n> > +        }\n> > +    }\n> > +\n> > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > +    return control_device->setControls(&controls);\n> > +}\n> > +\n> >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> >  {\n> >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> >          return;\n> >      }\n> >\n> > +    // Set the controls for the next queued request\n> > +    if (!queuedRequests_.empty())\n> > +        setRequestControls(queuedRequests_.front());\n> > +\n> >      /*\n> >       * Record the sensor's timestamp in the request metadata. The request\n> >       * needs to be obtained from the user-facing buffer, as internal\n> > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > *camera, [[maybe_unused]] const ControlL\n> >          return ret;\n> >      }\n> >\n> > +    // Apply controls from first request\n> > +    if (!data->queuedRequests_.empty())\n> > +        data->setRequestControls(data->queuedRequests_.front());\n> > +\n> >      if (data->useConverter_) {\n> >          ret = data->converter_->start();\n> >          if (ret < 0) {\n> > --\n> > 2.25.1\n\n[PATCH] libcamera: pipeline: simple: Add support for controls\n\nControls and control info are translated between libcamera and V4L2\ninside the simple pipeline. Request controls are applied when a request\nis next to be completed.\n\nThis also adds some additional draft controls needed for the PinePhone.\n\nBug: https://bugs.libcamera.org/show_bug.cgi?id=98\nSigned-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n---\n src/libcamera/control_ids.yaml             |  24 ++\n src/libcamera/pipeline/simple/controls.cpp | 241 +++++++++++++++++++++\n src/libcamera/pipeline/simple/controls.h   |  33 +++\n src/libcamera/pipeline/simple/meson.build  |   1 +\n src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n src/libcamera/v4l2_device.cpp              |  12 +-\n 6 files changed, 339 insertions(+), 2 deletions(-)\n create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n create mode 100644 src/libcamera/pipeline/simple/controls.h\n\ndiff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\nindex 9d4638ae..2af230c3 100644\n--- a/src/libcamera/control_ids.yaml\n+++ b/src/libcamera/control_ids.yaml\n@@ -406,6 +406,30 @@ controls:\n             The camera will cancel any active or completed metering sequence.\n             The AE algorithm is reset to its initial state.\n\n+  - AutoGain:\n+      type: bool\n+      draft: true\n+      description: |\n+       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n+\n+  - AfEnabled:\n+      type: bool\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n+\n+  - AfStart:\n+      type: void\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n+\n+  - AfStop:\n+      type: void\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n+\n   - AfTrigger:\n       type: int32_t\n       draft: true\ndiff --git a/src/libcamera/pipeline/simple/controls.cpp\nb/src/libcamera/pipeline/simple/controls.cpp\nnew file mode 100644\nindex 00000000..f3922e45\n--- /dev/null\n+++ b/src/libcamera/pipeline/simple/controls.cpp\n@@ -0,0 +1,241 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Benjamin Schaaf\n+ *\n+ * controls.cpp - Simple pipeline control conversion\n+ */\n+\n+#include \"controls.h\"\n+\n+#include <linux/v4l2-controls.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/control_ids.h>\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(SimplePipeline)\n+\n+/*\n+ * These controls can be directly mapped between libcamera and V4L2 without\n+ * doing any conversion to the ControlValue.\n+ */\n+static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n+    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n+    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n+    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n+    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n+    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n+    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n+    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n+    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n+    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n+};\n+\n+/**\n+ * \\brief Convert from a libcamera control to a V4L2 control.\n+ *\n+ * Can optionally convert the libcamera control and/or a set of libcamera\n+ * control values to their V4L2 equivalents.\n+ */\n+bool simpleControlToV4L2(unsigned int control,\n+             unsigned int *v4l2_control,\n+             const ControlValue *control_values,\n+             ControlValue *v4l2_values,\n+             size_t num_values)\n+{\n+    // Convert controls\n+    if (v4l2_control) {\n+        auto it = controlsToV4L2.find(control);\n+        if (it == controlsToV4L2.end())\n+            return false;\n+\n+        *v4l2_control = it->second;\n+    }\n+\n+    // Convert values\n+    if (num_values == 0)\n+        return true;\n+\n+    switch (control) {\n+    case controls::AE_ENABLE:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] =\nControlValue((int32_t)(control_values[i].get<bool>() ?\nV4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n+        return true;\n+    case controls::EXPOSURE_VALUE:\n+    case controls::DIGITAL_GAIN:\n+    case controls::ANALOGUE_GAIN:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] =\nControlValue((int32_t)control_values[i].get<float>());\n+        return true;\n+    // Read only\n+    case controls::AF_STATE:\n+        return false;\n+    default:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] = control_values[i];\n+        return true;\n+    }\n+}\n+\n+static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n+\n+/**\n+ * \\brief Convert from a V4L2 control to a libcamera control.\n+ *\n+ * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n+ * to their libcamera equivalents.\n+ */\n+bool simpleControlFromV4L2(unsigned int v4l2_control,\n+               unsigned int *control,\n+               const ControlValue *v4l2_values,\n+               ControlValue *control_values,\n+               size_t num_values)\n+{\n+    // Initialize the inverse of controlsToV4L2\n+    if (controlsFromV4L2.empty()) {\n+        for (const auto &v : controlsToV4L2) {\n+            controlsFromV4L2[v.second] = v.first;\n+        }\n+    }\n+\n+    // Convert control\n+    if (control) {\n+        auto it = controlsFromV4L2.find(v4l2_control);\n+        if (it == controlsFromV4L2.end())\n+            return false;\n+\n+        *control = it->second;\n+    }\n+\n+    // Convert values\n+    if (num_values == 0)\n+        return true;\n+\n+    switch (v4l2_control) {\n+    case V4L2_CID_EXPOSURE_AUTO:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] =\nControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n+        return true;\n+    case V4L2_CID_EXPOSURE:\n+    case V4L2_CID_GAIN:\n+    case V4L2_CID_ANALOGUE_GAIN:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] =\nControlValue((float)v4l2_values[i].get<int32_t>());\n+        return true;\n+    case V4L2_CID_AUTO_FOCUS_STATUS:\n+        for (size_t i = 0; i < num_values; ++i) {\n+            switch (v4l2_values[i].get<int32_t>()) {\n+            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStateInactive);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStateActiveScan);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n+                break;\n+            default:\n+                LOG(SimplePipeline, Error)\n+                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n+                    << utils::hex(v4l2_values[i].get<int32_t>());\n+                /*TODO: Log Error*/\n+                return false;\n+            }\n+        }\n+        return true;\n+    default:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] = v4l2_values[i];\n+        return true;\n+    }\n+}\n+\n+/**\n+ * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n+ *\n+ * Converts both the control identifiers as well as all values.\n+ */\n+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n+{\n+    ControlInfoMap::Map info_map;\n+\n+    for (const auto &pair : v4l2_info_map) {\n+        unsigned int v4l2_control = pair.first->id();\n+        const ControlInfo &v4l2_info = pair.second;\n+\n+        unsigned int control;\n+        ControlValue def;\n+        if (!simpleControlFromV4L2(v4l2_control, &control,\n&v4l2_info.def(), &def, 1))\n+            continue;\n+\n+        const ControlId *control_id = controls::controls.at(control);\n+\n+        // ControlInfo has either a list of values or a minimum and\n+        // maximum. This includes controls that have no values or are\n+        // booleans.\n+        ControlInfo info;\n+        if (v4l2_info.values().empty()) {\n+            ControlValue min, max;\n+            simpleControlFromV4L2(v4l2_control, nullptr,\n&v4l2_info.min(), &min, 1);\n+            simpleControlFromV4L2(v4l2_control, nullptr,\n&v4l2_info.max(), &max, 1);\n+            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n+        } else {\n+            std::vector<ControlValue> values;\n+            values.resize(v4l2_info.values().size());\n+            simpleControlFromV4L2(v4l2_control, nullptr,\nv4l2_info.values().data(), values.data(), values.size());\n+            info = ControlInfo(std::move(values), std::move(def));\n+        }\n+        info_map.emplace(control_id, std::move(info));\n+    }\n+\n+    return ControlInfoMap(std::move(info_map), controls::controls);\n+}\n+\n+/**\n+ * \\brief Convert a control list from libcamera to V4L2.\n+ */\n+ControlList simpleControlListToV4L2(const ControlList &controls)\n+{\n+    ControlList v4l2_controls;\n+    for (const auto &pair : controls) {\n+        unsigned int control = pair.first;\n+        const ControlValue &value = pair.second;\n+\n+        unsigned int v4l2_control;\n+        ControlValue v4l2_value;\n+        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n&v4l2_value, 1)) {\n+            LOG(SimplePipeline, Warning)\n+                << \"Control \" << utils::hex(control)\n+                << \" does not have a V4L2 equivalent\";\n+            continue;\n+        }\n+\n+        v4l2_controls.set(v4l2_control, v4l2_value);\n+    }\n+    return v4l2_controls;\n+}\n+\n+/**\n+ * \\brief Convert a control list from V4L2 to libcamera.\n+ */\n+ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n+{\n+    ControlList controls;\n+    for (const auto &pair : v4l2_controls) {\n+        unsigned int v4l2_control = pair.first;\n+        const ControlValue &v4l2_value = pair.second;\n+\n+        unsigned int control;\n+        ControlValue value;\n+        if (simpleControlFromV4L2(v4l2_control, &control,\n&v4l2_value, &value, 1))\n+            controls.set(control, value);\n+    }\n+    return controls;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/simple/controls.h\nb/src/libcamera/pipeline/simple/controls.h\nnew file mode 100644\nindex 00000000..54b4a565\n--- /dev/null\n+++ b/src/libcamera/pipeline/simple/controls.h\n@@ -0,0 +1,33 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Benjamin Schaaf\n+ *\n+ * controls.h - Simple pipeline control conversion\n+ */\n+\n+#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n+#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+bool simpleControlToV4L2(unsigned int control,\n+             unsigned int *v4l2_control,\n+             const ControlValue *control_values,\n+             ControlValue *v4l2_values,\n+             size_t num_values);\n+bool simpleControlFromV4L2(unsigned int v4l2_control,\n+               unsigned int *control,\n+               const ControlValue *v4l2_values,\n+               ControlValue *control_values,\n+               size_t num_values);\n+\n+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n+\n+ControlList simpleControlListToV4L2(const ControlList &controls);\n+ControlList simpleControlListFromV4L2(const ControlList &controls);\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\ndiff --git a/src/libcamera/pipeline/simple/meson.build\nb/src/libcamera/pipeline/simple/meson.build\nindex 9c99b32f..0c60d65a 100644\n--- a/src/libcamera/pipeline/simple/meson.build\n+++ b/src/libcamera/pipeline/simple/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n\n libcamera_sources += files([\n+    'controls.cpp',\n     'converter.cpp',\n     'simple.cpp',\n ])\ndiff --git a/src/libcamera/pipeline/simple/simple.cpp\nb/src/libcamera/pipeline/simple/simple.cpp\nindex a597e27f..1717a1a7 100644\n--- a/src/libcamera/pipeline/simple/simple.cpp\n+++ b/src/libcamera/pipeline/simple/simple.cpp\n@@ -36,6 +36,7 @@\n #include \"libcamera/internal/v4l2_subdevice.h\"\n #include \"libcamera/internal/v4l2_videodevice.h\"\n\n+#include \"controls.h\"\n #include \"converter.h\"\n\n namespace libcamera {\n@@ -181,6 +182,7 @@ public:\n     int setupLinks();\n     int setupFormats(V4L2SubdeviceFormat *format,\n              V4L2Subdevice::Whence whence);\n+    int setRequestControls(Request *request);\n     void bufferReady(FrameBuffer *buffer);\n\n     unsigned int streamIndex(const Stream *stream) const\n@@ -519,7 +521,8 @@ int SimpleCameraData::init()\n             formats_[fmt] = &config;\n     }\n\n-    properties_ = sensor_->properties();\n+    properties_ = simpleControlListFromV4L2(sensor_->properties());\n+    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n\n     return 0;\n }\n@@ -624,6 +627,23 @@ int\nSimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n     return 0;\n }\n\n+int SimpleCameraData::setRequestControls(Request *request)\n+{\n+    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n+\n+    // Apply controls only to one entity. If there's a subdevice use that.\n+    V4L2Device *control_device = video_;\n+    for (const SimpleCameraData::Entity &e : entities_) {\n+        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n+        if (subdev) {\n+            control_device = subdev;\n+        }\n+    }\n+\n+    ControlList controls = simpleControlListToV4L2(request->controls());\n+    return control_device->setControls(&controls);\n+}\n+\n void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n {\n     SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n@@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n         return;\n     }\n\n+    // Set the controls for the next queued request\n+    if (!queuedRequests_.empty())\n+        setRequestControls(queuedRequests_.front());\n+\n     /*\n      * Record the sensor's timestamp in the request metadata. The request\n      * needs to be obtained from the user-facing buffer, as internal\n@@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n*camera, [[maybe_unused]] const ControlL\n         return ret;\n     }\n\n+    // Apply controls from first request\n+    if (!data->queuedRequests_.empty())\n+        data->setRequestControls(data->queuedRequests_.front());\n+\n     if (data->useConverter_) {\n         ret = data->converter_->start();\n         if (ret < 0) {\ndiff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\nindex 9c783c9c..2a15ae09 100644\n--- a/src/libcamera/v4l2_device.cpp\n+++ b/src/libcamera/v4l2_device.cpp\n@@ -296,6 +296,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n         /* Set the v4l2_ext_control value for the write operation. */\n         ControlValue &value = ctrl->second;\n         switch (iter->first->type()) {\n+        case ControlTypeBool:\n+            v4l2Ctrl.value64 = value.get<bool>();\n+            break;\n+\n+        case ControlTypeNone:\n+            break;\n+\n         case ControlTypeInteger64:\n             v4l2Ctrl.value64 = value.get<int64_t>();\n             break;\n@@ -479,7 +486,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n         return ControlTypeInteger64;\n\n     case V4L2_CTRL_TYPE_MENU:\n-    case V4L2_CTRL_TYPE_BUTTON:\n     case V4L2_CTRL_TYPE_BITMASK:\n     case V4L2_CTRL_TYPE_INTEGER_MENU:\n         /*\n@@ -488,6 +494,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n          */\n         return ControlTypeInteger32;\n\n+    case V4L2_CTRL_TYPE_BUTTON:\n     default:\n         return ControlTypeNone;\n     }\n@@ -530,6 +537,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\nv4l2_query_ext_ctrl &ctrl)\n                    static_cast<int64_t>(ctrl.maximum),\n                    static_cast<int64_t>(ctrl.default_value));\n\n+    case V4L2_CTRL_TYPE_BUTTON:\n+        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n+\n     case V4L2_CTRL_TYPE_INTEGER_MENU:\n     case V4L2_CTRL_TYPE_MENU:\n         return v4l2MenuControlInfo(ctrl);","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A2B03BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 10:55:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4FAEA60882;\n\tThu,  9 Dec 2021 11:55:48 +0100 (CET)","from mail-yb1-xb2f.google.com (mail-yb1-xb2f.google.com\n\t[IPv6:2607:f8b0:4864:20::b2f])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DE71D60224\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 11:55:46 +0100 (CET)","by mail-yb1-xb2f.google.com with SMTP id v203so12717780ybe.6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 09 Dec 2021 02:55:46 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"L5L9nL8y\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;\n\th=mime-version:references:in-reply-to:from:date:message-id:subject:to\n\t:cc; bh=OXqD0jdbhp6uiqLh3ma1DwN+cnjBtU4jpHU+/6sFt1Y=;\n\tb=L5L9nL8yaaRqLGPAyxKkSCdHXAQNGhyS6BdhNIfrTWTyFBiXKWAsYK9irBiRYjmqQF\n\tqmhVPNo3BMcVZq/zbkL+O4e001La4oQbXWTvYBxWSg9xL5voxSRx3pI50em5gQzHjZO2\n\tz+tia0d+Tp9dCScVXUtjSCPgtKl/3l8re+V3QusObbq5QvQW3RfTSiFUZqVRgZjWW1jC\n\tmg8cr4LCGYmUyorh2DPZFR7AVy/L+la9eJp3hYxkgzJ3gW1JJJzjTDBM/uLa8SrH1Zcr\n\teRLBRK3oC2a5yMQtTT991HuI/Fi1NC42Ilrg2DzBrQ93/Y82Rffo+pUPuwRQiVNO5u0w\n\tZsSg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=OXqD0jdbhp6uiqLh3ma1DwN+cnjBtU4jpHU+/6sFt1Y=;\n\tb=YyZg/VvsFB2iUSTM21GS337v3IP107FU8OWM/czmsVU/DtAoNJn9TaDqYYMifw8PS3\n\t89noQg9bPi0+FEKh/yfXf/vlvmvdvHb5AgrwOUZXmIzIs1/vJgaou5caMQbHozCMtVVq\n\tJHAt1G24PTSQXPDpFRFtSP0zBQvgZqMO3RlKY0bRsXbyG9yNTriDUhOcL7hGDLF1aybU\n\trOAlIGnmjC+tIdLc9agC96V4lue1d6rDBQprjhpBwHsAb4WHrS9IOQQ8kNH32FIIOl+i\n\tMjQ6mQycKRzbB7h+rs3/QbzkCoa5DQ41huGFI6+w5aGD63lRmIPqnKQN9LsGNOJJIlhL\n\tXrew==","X-Gm-Message-State":"AOAM530bdyH96Y9uY/Zf6FqKpQ/Ex6bciU1hQzsDilBpTtvj5hXAqWKM\n\tMxmcbLLcPYzZb16DlRyjr+s3FkLgkyRFZON2uJ4=","X-Google-Smtp-Source":"ABdhPJwth+Ry4PPKzplSsILYzxND5bV//FhM3MEYkahdvH1R2mC/aD6NV7dJpBDx6rJnOut10buKt8cgBjFZqSe5s2Q=","X-Received":"by 2002:a25:244c:: with SMTP id k73mr5497838ybk.12.1639047344713;\n\tThu, 09 Dec 2021 02:55:44 -0800 (PST)","MIME-Version":"1.0","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>","In-Reply-To":"<20211209085830.affr22qlko7sxxvm@uno.localdomain>","From":"Benjamin Schaaf <ben.schaaf@gmail.com>","Date":"Thu, 9 Dec 2021 21:55:33 +1100","Message-ID":"<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21709,"web_url":"https://patchwork.libcamera.org/comment/21709/","msgid":"<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>","date":"2021-12-09T11:34:12","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Benjamin\n\nOn Thu, Dec 09, 2021 at 09:55:33PM +1100, Benjamin Schaaf wrote:\n> Thanks for the review, I'll put the updated patch at the end.\n\nPlease don't :)\n\nAlways send a new patch for new versions!\n\n>\n> On Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n> >\n> > Hello Benjamin,\n> >\n> > On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > > Controls and control info are translated between libcamera and V4L2\n> > > inside the simple pipeline. Request controls are applied when a request\n> > > is next to be completed.\n> > >\n> > > This also adds some additional draft controls needed for the PinePhone.\n> >\n> > Sorry to get straight to this before looking at the patch content, but\n> > we do enforce a code style as reported here\n> > https://libcamera.org/coding-style.html#coding-style-guidelines\n> >\n> > We have a tool that might help you catching style issues at\n> > utils/checkstyle.py\n> >\n> > Care to reformat your patches to comply with the project code style ?\n> >\n> > >\n> > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > ---\n> > >  src/libcamera/control_ids.yaml             |  24 +++\n> > >  src/libcamera/controls.cpp                 |   6 -\n> > >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> > >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > >  6 files changed, 310 insertions(+), 7 deletions(-)\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > >\n> > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > index 9d4638ae..2af230c3 100644\n> > > --- a/src/libcamera/control_ids.yaml\n> > > +++ b/src/libcamera/control_ids.yaml\n> > > @@ -406,6 +406,30 @@ controls:\n> > >              The camera will cancel any active or completed metering sequence.\n> > >              The AE algorithm is reset to its initial state.\n> > >\n> > > +  - AutoGain:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > > +\n> > > +  - AfEnabled:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > > +\n> > > +  - AfStart:\n> > > +      type: void\n> >\n> > Why type void ? Isn't this a boolean ?\n>\n> V4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\n> performs an action when the control is set. Thus type void. Same for\n> V4L2_CID_AUTO_FOCUS_END.\n>\n> It seems I forgot to include some required type conversion logic in\n> v4l2_device, not sure how that got missed.\n\nSo you use void to indicate that we don't care about the value but the\ncontrol presence signify that, in example, the autofocus routine\nshould be started.\n\nWe don't have 'one shot' control so far (the assumption is that once a\ncontrol is set to a value, the value stays the same until it's not\nupdated), and this could be a valid usage of type: void indeed\n\n>\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > +\n> > > +  - AfStop:\n> > > +      type: void\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > +\n> >\n> > We're in the process of reworking controls related to gain and focus,\n> > but for the moment, as we comply with Android by having their controls\n> > defined as draft, I'm not opposed to have these here.\n> >\n> > I'm worried once we have applications use them, we will never move to\n> > the newly defined ones though unless we forcefully remove them in\n> > future...\n>\n> FWIW given that libcamera doesn't have versions/releases I don't\n> personally expect a stable API.\n>\n\nIn the long term, draft control will be replaced by standard controls.\nWhat I'm afraid of is that if applications start relying on draft\ncontrols it will be harder to remove them, as it will likely require a\ndifferent type of mapping. But I have no better suggestions to provide\natm\n\n> > >    - AfTrigger:\n> > >        type: int32_t\n> > >        draft: true\n> > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > > index 0d8c0a5c..1f65fc73 100644\n> > > --- a/src/libcamera/controls.cpp\n> > > +++ b/src/libcamera/controls.cpp\n> > > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > > int id) const\n> > >  {\n> > >      const auto iter = controls_.find(id);\n> > >      if (iter == controls_.end()) {\n> > > -        LOG(Controls, Error)\n> > > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > > -\n> >\n> > Ouch, why remove the error message ?\n>\n> My bad, seems I was misinterpreting when that error could show up.\n> I'll add it back in.\n>\n> > >          return nullptr;\n> > >      }\n> > >\n> > > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > > int id) const\n> > >  ControlValue *ControlList::find(unsigned int id)\n> > >  {\n> > >      if (validator_ && !validator_->validate(id)) {\n> > > -        LOG(Controls, Error)\n> > > -            << \"Control \" << utils::hex(id)\n> > > -            << \" is not valid for \" << validator_->name();\n> > >          return nullptr;\n> > >      }\n> > >\n> > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > new file mode 100644\n> > > index 00000000..32695749\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > @@ -0,0 +1,230 @@\n> >\n> > File license ? You can copy the SPDX header from other files and\n> > attribute the copyright to you or any one you like\n> >\n> > > +#include \"controls.h\"\n> > > +\n> > > +#include <linux/v4l2-controls.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/control_ids.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > +\n> > > +/*\n> > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > + * doing any conversion to the ControlValue.\n> > > + */\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > +};\n> >\n> > If them map is meant for internal use only you can declare it in an\n> > anonymous namespace\n> >\n> > namsepace {\n> >         std::unordered_map<>...\n> >\n> > };\n> >\n> > namespace libcamera {\n> >\n> > };\n>\n> That's the same as static though?\n>\n\nYes but we usually prefer anonymous namespaces... Not a big deal\nthough.\n\n> > > +\n> > > +/*\n> > > + * Convert from a libcamera control to a V4L2 control, optionally\n> > > also convert a\n> > > + * set of ControlValues.\n> > > + */\n> >\n> > We use doxygen for documenting the code. Please see other files as an\n> > example.\n> >\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values)\n> >\n> > Before looking at the implementation, let's reason a bit on the\n> > architecture.\n> >\n> > Is this control mapping valid for all platforms using the simple\n> > pipeline handler ?\n> >\n> > Is the device on which controls have to be applied the same for all\n> > platforms ?\n> >\n> > Should control handling be broken down to a platform specific\n> > component to be selected at compile time ?\n>\n> I'm not sure what you mean by platform here? Do you mean Linux vs\n> Android, x86 vs arm or PinePhone vs Librem 5?\n\nI mean the SoC.\n\nSimple is used (afaik) on imx6, imx8mq, allwinner, mediatek and\npossibly others.\n\nThe mapping between v4l2 controls and libcamera controls does\ngenerically apply to all of them ? Surely not the values the controls\ntransport, but if we assume the application knows what platform it\noperates on that's fine.\n\n>\n> The way I see it, the simple pipeline is just a simple abstraction on\n> V4L2 and this is a simple conversion between V4L2 and libcamera\n> controls.\n\nThis is a bit of a stretch.\n\nWhat's happening here is that you defined controls equal to the V4L2\nones, and have the app set the 'right' values for the sensor/platform\nin use (a 'gain' value does not have the same meaning between\ndifferent sensors, in example).\n\nIn 'regular' platforms with an ISP, an IPA etc the pipeline receives\nlibcamera::controls and with the help of IPA and statistics computes\nthe right v4l2 controls for the platform (the pipeline handler knows\nwhat platform it runs on, except for simple) and for the sensor\nthrough a set of CameraSensorHelpers that aid with the translation.\n\nNow we change the landscape a bit, and we assume the app knows what\nplatforms it runs on, something that defeats the purpose of libcamera\nusage, but I understand there aren't may way around that to support\nyour use case.\n\nAs a pipeline handler is charge of:\n\n1) Registering what control it supports to expose that to application\n2) Translate libcamera control to v4l2 controls (not in your case)\n3) Apply controls to the right device/subdevice at the right time (the\n  time when to set a control is not trivially calculated as most\n  controls have a delay and should be applied in advance)\n\nNow assuming point 2) is moved to the app in your case, point 1 and 3\ndo not apply generically to all platforms using the simple pipeline\nhandler. Some might support control the other does not support. Some\nmight want to set a control to the video devices while others will\napply the same control on the sensor subdev. It all very depends on\nthe driver architecture of the SoC.\n\nNow, even for simple boolean/button controls like the ones you're dealing with,\nfor which not much reasoning and translations are required, a platform\nspecific component seems to be needed to address 1) and 3).\n\nDoes it make sense to you ?\n\n>\n> > Also, I would like to see this implemented thorough a componenet with\n> > a single interface towards the pipeline handler rather than a raw set\n> > of helper functions. We can indeed help designing that once we have\n> > the previous question clarified.\n> >\n> > I'm not even sure this is the direction we want to got with the simple\n> > pipeline handler (platform-specific backends), and I would like to\n> > hear Laurent's opinion on this, but I see a potential for doing what\n> > we do with the android backend selection through the\n> > 'android_platform' build option.\n> >\n> >         option('android_platform',\n> >                 type : 'combo',\n> >                 choices : ['cros', 'generic'],\n> >                 value : 'generic',\n> >                 description : 'Select the Android platform to compile for')\n> >\n> > > +{\n> > > +    // Convert controls\n> > > +    if (v4l2_control) {\n> > > +        auto it = controlsToV4L2.find(control);\n> > > +        if (it == controlsToV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *v4l2_control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (control) {\n> > > +    case controls::AE_ENABLE:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > +        return true;\n> > > +    case controls::EXPOSURE_VALUE:\n> > > +    case controls::DIGITAL_GAIN:\n> > > +    case controls::ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)control_values[i].get<float>());\n> > > +        return true;\n> > > +    // Read only\n> > > +    case controls::AF_STATE:\n> > > +        return false;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] = control_values[i];\n> > > +        return true;\n> > > +    }\n> > > +\n> > > +}\n> > > +\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > +\n> > > +/*\n> > > + * Convert from a V4L2 control to a libcamera control, optionally\n> > > also convert a\n> > > + * set of ControlValues.\n> > > + */\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values)\n> > > +{\n> > > +    // Initialize the inverse of controlsToV4L2\n> > > +    if (controlsFromV4L2.empty()) {\n> > > +        for (const auto &v : controlsToV4L2) {\n> > > +            controlsFromV4L2[v.second] = v.first;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    // Convert control\n> > > +    if (control) {\n> > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > +        if (it == controlsFromV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (v4l2_control) {\n> > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > +        return true;\n> > > +    case V4L2_CID_EXPOSURE:\n> > > +    case V4L2_CID_GAIN:\n> > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > +        return true;\n> > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > +                break;\n> > > +            default:\n> > > +                LOG(SimplePipeline, Error)\n> > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > +                /*TODO: Log Error*/\n> > > +                return false;\n> > > +            }\n> > > +        }\n> > > +        return true;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] = v4l2_values[i];\n> > > +        return true;\n> > > +    }\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > > + * identifiers as well as all values.\n> > > + */\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > +{\n> > > +    ControlInfoMap::Map info_map;\n> > > +\n> > > +    for (const auto &pair : v4l2_info_map) {\n> > > +        unsigned int v4l2_control = pair.first->id();\n> > > +        const ControlInfo &v4l2_info = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue def;\n> > > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_info.def(), &def, 1))\n> > > +            continue;\n> > > +\n> > > +        const ControlId *control_id = controls::controls.at(control);\n> > > +\n> > > +        // ControlInfo has either a list of values or a minimum and\n> > > +        // maximum. This includes controls that have no values or are\n> > > +        // booleans.\n> > > +        ControlInfo info;\n> > > +        if (v4l2_info.values().empty()) {\n> > > +            ControlValue min, max;\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.min(), &min, 1);\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.max(), &max, 1);\n> > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > +        } else {\n> > > +            std::vector<ControlValue> values;\n> > > +            values.resize(v4l2_info.values().size());\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > v4l2_info.values().data(), values.data(), values.size());\n> > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > +        }\n> > > +        info_map.emplace(control_id, std::move(info));\n> > > +    }\n> > > +\n> > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a control list from libcamera to V4L2.\n> > > + */\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > +{\n> > > +    ControlList v4l2_controls;\n> > > +    for (const auto &pair : controls) {\n> > > +        unsigned int control = pair.first;\n> > > +        const ControlValue &value = pair.second;\n> > > +\n> > > +        unsigned int v4l2_control;\n> > > +        ControlValue v4l2_value;\n> > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > > &v4l2_value, 1)) {\n> > > +            LOG(SimplePipeline, Warning)\n> > > +                << \"Control \" << utils::hex(control)\n> > > +                << \" does not have a V4L2 equivalent\";\n> > > +            continue;\n> > > +        }\n> > > +\n> > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > +    }\n> > > +    return v4l2_controls;\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a control list from V4L2 to libcamera.\n> > > + */\n> > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > +{\n> > > +    ControlList controls;\n> > > +    for (const auto &pair : v4l2_controls) {\n> > > +        unsigned int v4l2_control = pair.first;\n> > > +        const ControlValue &v4l2_value = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue value;\n> > > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_value, &value, 1))\n> > > +            controls.set(control, value);\n> > > +    }\n> > > +    return controls;\n> > > +}\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > b/src/libcamera/pipeline/simple/controls.h\n> > > new file mode 100644\n> > > index 00000000..114c5fc2\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.h\n> >\n> > Same comment, license and copyright\n> >\n> > > @@ -0,0 +1,26 @@\n> > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> >\n> > #pragma once\n>\n> All the code I've seen uses #ifndef instead of #pragma once. I'd\n> prefer to use #pragma once but it seems inconsistent with the rest of\n> the codebase?\n>\n\nNot since\nhttps://patchwork.libcamera.org/project/libcamera/list/?series=2749&state=*\n\n> > > +\n> > > +#include <libcamera/controls.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values);\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values);\n> > > +\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > +\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > +\n> > > +} /* namespace libcamera */\n> > > +\n> > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > b/src/libcamera/pipeline/simple/meson.build\n> > > index 9c99b32f..0c60d65a 100644\n> > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > @@ -1,6 +1,7 @@\n> > >  # SPDX-License-Identifier: CC0-1.0\n> > >\n> > >  libcamera_sources += files([\n> > > +    'controls.cpp',\n> > >      'converter.cpp',\n> > >      'simple.cpp',\n> > >  ])\n> > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > index a597e27f..b0d4a62a 100644\n> > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > @@ -37,6 +37,7 @@\n> > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > >\n> > >  #include \"converter.h\"\n> > > +#include \"controls.h\"\n> > >\n> > >  namespace libcamera {\n> > >\n> > > @@ -181,6 +182,7 @@ public:\n> > >      int setupLinks();\n> > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > >               V4L2Subdevice::Whence whence);\n> > > +    int setRequestControls(Request *request);\n> > >      void bufferReady(FrameBuffer *buffer);\n> > >\n> > >      unsigned int streamIndex(const Stream *stream) const\n> > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > >              formats_[fmt] = &config;\n> > >      }\n> > >\n> > > -    properties_ = sensor_->properties();\n> > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > >\n> > >      return 0;\n> > >  }\n> > > @@ -624,6 +627,23 @@ int\n> > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > >      return 0;\n> > >  }\n> > >\n> > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > +{\n> > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > +\n> > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > +    V4L2Device *control_device = video_;\n> > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > +        if (subdev) {\n> > > +            control_device = subdev;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > +    return control_device->setControls(&controls);\n> > > +}\n> > > +\n> > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >  {\n> > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >          return;\n> > >      }\n> > >\n> > > +    // Set the controls for the next queued request\n> > > +    if (!queuedRequests_.empty())\n> > > +        setRequestControls(queuedRequests_.front());\n> > > +\n> > >      /*\n> > >       * Record the sensor's timestamp in the request metadata. The request\n> > >       * needs to be obtained from the user-facing buffer, as internal\n> > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > *camera, [[maybe_unused]] const ControlL\n> > >          return ret;\n> > >      }\n> > >\n> > > +    // Apply controls from first request\n> > > +    if (!data->queuedRequests_.empty())\n> > > +        data->setRequestControls(data->queuedRequests_.front());\n> > > +\n> > >      if (data->useConverter_) {\n> > >          ret = data->converter_->start();\n> > >          if (ret < 0) {\n> > > --\n> > > 2.25.1\n>\n> [PATCH] libcamera: pipeline: simple: Add support for controls\n>\n> Controls and control info are translated between libcamera and V4L2\n> inside the simple pipeline. Request controls are applied when a request\n> is next to be completed.\n>\n> This also adds some additional draft controls needed for the PinePhone.\n>\n> Bug: https://bugs.libcamera.org/show_bug.cgi?id=98\n> Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> ---\n>  src/libcamera/control_ids.yaml             |  24 ++\n>  src/libcamera/pipeline/simple/controls.cpp | 241 +++++++++++++++++++++\n>  src/libcamera/pipeline/simple/controls.h   |  33 +++\n>  src/libcamera/pipeline/simple/meson.build  |   1 +\n>  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n>  src/libcamera/v4l2_device.cpp              |  12 +-\n>  6 files changed, 339 insertions(+), 2 deletions(-)\n>  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n>  create mode 100644 src/libcamera/pipeline/simple/controls.h\n>\n> diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> index 9d4638ae..2af230c3 100644\n> --- a/src/libcamera/control_ids.yaml\n> +++ b/src/libcamera/control_ids.yaml\n> @@ -406,6 +406,30 @@ controls:\n>              The camera will cancel any active or completed metering sequence.\n>              The AE algorithm is reset to its initial state.\n>\n> +  - AutoGain:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> +\n> +  - AfEnabled:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> +\n> +  - AfStart:\n> +      type: void\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> +\n> +  - AfStop:\n> +      type: void\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> +\n>    - AfTrigger:\n>        type: int32_t\n>        draft: true\n> diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> b/src/libcamera/pipeline/simple/controls.cpp\n> new file mode 100644\n> index 00000000..f3922e45\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.cpp\n> @@ -0,0 +1,241 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Benjamin Schaaf\n> + *\n> + * controls.cpp - Simple pipeline control conversion\n> + */\n> +\n> +#include \"controls.h\"\n> +\n> +#include <linux/v4l2-controls.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/control_ids.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(SimplePipeline)\n> +\n> +/*\n> + * These controls can be directly mapped between libcamera and V4L2 without\n> + * doing any conversion to the ControlValue.\n> + */\n> +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> +};\n> +\n> +/**\n> + * \\brief Convert from a libcamera control to a V4L2 control.\n> + *\n> + * Can optionally convert the libcamera control and/or a set of libcamera\n> + * control values to their V4L2 equivalents.\n> + */\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values)\n> +{\n> +    // Convert controls\n> +    if (v4l2_control) {\n> +        auto it = controlsToV4L2.find(control);\n> +        if (it == controlsToV4L2.end())\n> +            return false;\n> +\n> +        *v4l2_control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (control) {\n> +    case controls::AE_ENABLE:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)(control_values[i].get<bool>() ?\n> V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> +        return true;\n> +    case controls::EXPOSURE_VALUE:\n> +    case controls::DIGITAL_GAIN:\n> +    case controls::ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)control_values[i].get<float>());\n> +        return true;\n> +    // Read only\n> +    case controls::AF_STATE:\n> +        return false;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] = control_values[i];\n> +        return true;\n> +    }\n> +}\n> +\n> +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> +\n> +/**\n> + * \\brief Convert from a V4L2 control to a libcamera control.\n> + *\n> + * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n> + * to their libcamera equivalents.\n> + */\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values)\n> +{\n> +    // Initialize the inverse of controlsToV4L2\n> +    if (controlsFromV4L2.empty()) {\n> +        for (const auto &v : controlsToV4L2) {\n> +            controlsFromV4L2[v.second] = v.first;\n> +        }\n> +    }\n> +\n> +    // Convert control\n> +    if (control) {\n> +        auto it = controlsFromV4L2.find(v4l2_control);\n> +        if (it == controlsFromV4L2.end())\n> +            return false;\n> +\n> +        *control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (v4l2_control) {\n> +    case V4L2_CID_EXPOSURE_AUTO:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> +        return true;\n> +    case V4L2_CID_EXPOSURE:\n> +    case V4L2_CID_GAIN:\n> +    case V4L2_CID_ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue((float)v4l2_values[i].get<int32_t>());\n> +        return true;\n> +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> +        for (size_t i = 0; i < num_values; ++i) {\n> +            switch (v4l2_values[i].get<int32_t>()) {\n> +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateInactive);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> +                break;\n> +            default:\n> +                LOG(SimplePipeline, Error)\n> +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> +                /*TODO: Log Error*/\n> +                return false;\n> +            }\n> +        }\n> +        return true;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] = v4l2_values[i];\n> +        return true;\n> +    }\n> +}\n> +\n> +/**\n> + * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n> + *\n> + * Converts both the control identifiers as well as all values.\n> + */\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> +{\n> +    ControlInfoMap::Map info_map;\n> +\n> +    for (const auto &pair : v4l2_info_map) {\n> +        unsigned int v4l2_control = pair.first->id();\n> +        const ControlInfo &v4l2_info = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue def;\n> +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_info.def(), &def, 1))\n> +            continue;\n> +\n> +        const ControlId *control_id = controls::controls.at(control);\n> +\n> +        // ControlInfo has either a list of values or a minimum and\n> +        // maximum. This includes controls that have no values or are\n> +        // booleans.\n> +        ControlInfo info;\n> +        if (v4l2_info.values().empty()) {\n> +            ControlValue min, max;\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.min(), &min, 1);\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.max(), &max, 1);\n> +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> +        } else {\n> +            std::vector<ControlValue> values;\n> +            values.resize(v4l2_info.values().size());\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> v4l2_info.values().data(), values.data(), values.size());\n> +            info = ControlInfo(std::move(values), std::move(def));\n> +        }\n> +        info_map.emplace(control_id, std::move(info));\n> +    }\n> +\n> +    return ControlInfoMap(std::move(info_map), controls::controls);\n> +}\n> +\n> +/**\n> + * \\brief Convert a control list from libcamera to V4L2.\n> + */\n> +ControlList simpleControlListToV4L2(const ControlList &controls)\n> +{\n> +    ControlList v4l2_controls;\n> +    for (const auto &pair : controls) {\n> +        unsigned int control = pair.first;\n> +        const ControlValue &value = pair.second;\n> +\n> +        unsigned int v4l2_control;\n> +        ControlValue v4l2_value;\n> +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> &v4l2_value, 1)) {\n> +            LOG(SimplePipeline, Warning)\n> +                << \"Control \" << utils::hex(control)\n> +                << \" does not have a V4L2 equivalent\";\n> +            continue;\n> +        }\n> +\n> +        v4l2_controls.set(v4l2_control, v4l2_value);\n> +    }\n> +    return v4l2_controls;\n> +}\n> +\n> +/**\n> + * \\brief Convert a control list from V4L2 to libcamera.\n> + */\n> +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> +{\n> +    ControlList controls;\n> +    for (const auto &pair : v4l2_controls) {\n> +        unsigned int v4l2_control = pair.first;\n> +        const ControlValue &v4l2_value = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue value;\n> +        if (simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_value, &value, 1))\n> +            controls.set(control, value);\n> +    }\n> +    return controls;\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/simple/controls.h\n> b/src/libcamera/pipeline/simple/controls.h\n> new file mode 100644\n> index 00000000..54b4a565\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.h\n> @@ -0,0 +1,33 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Benjamin Schaaf\n> + *\n> + * controls.h - Simple pipeline control conversion\n> + */\n> +\n> +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> +\n> +#include <libcamera/controls.h>\n> +\n> +namespace libcamera {\n> +\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values);\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values);\n> +\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> +\n> +ControlList simpleControlListToV4L2(const ControlList &controls);\n> +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> +\n> +} /* namespace libcamera */\n> +\n> +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> diff --git a/src/libcamera/pipeline/simple/meson.build\n> b/src/libcamera/pipeline/simple/meson.build\n> index 9c99b32f..0c60d65a 100644\n> --- a/src/libcamera/pipeline/simple/meson.build\n> +++ b/src/libcamera/pipeline/simple/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libcamera_sources += files([\n> +    'controls.cpp',\n>      'converter.cpp',\n>      'simple.cpp',\n>  ])\n> diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> b/src/libcamera/pipeline/simple/simple.cpp\n> index a597e27f..1717a1a7 100644\n> --- a/src/libcamera/pipeline/simple/simple.cpp\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -36,6 +36,7 @@\n>  #include \"libcamera/internal/v4l2_subdevice.h\"\n>  #include \"libcamera/internal/v4l2_videodevice.h\"\n>\n> +#include \"controls.h\"\n>  #include \"converter.h\"\n>\n>  namespace libcamera {\n> @@ -181,6 +182,7 @@ public:\n>      int setupLinks();\n>      int setupFormats(V4L2SubdeviceFormat *format,\n>               V4L2Subdevice::Whence whence);\n> +    int setRequestControls(Request *request);\n>      void bufferReady(FrameBuffer *buffer);\n>\n>      unsigned int streamIndex(const Stream *stream) const\n> @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n>              formats_[fmt] = &config;\n>      }\n>\n> -    properties_ = sensor_->properties();\n> +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n>\n>      return 0;\n>  }\n> @@ -624,6 +627,23 @@ int\n> SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n>      return 0;\n>  }\n>\n> +int SimpleCameraData::setRequestControls(Request *request)\n> +{\n> +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> +\n> +    // Apply controls only to one entity. If there's a subdevice use that.\n> +    V4L2Device *control_device = video_;\n> +    for (const SimpleCameraData::Entity &e : entities_) {\n> +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> +        if (subdev) {\n> +            control_device = subdev;\n> +        }\n> +    }\n> +\n> +    ControlList controls = simpleControlListToV4L2(request->controls());\n> +    return control_device->setControls(&controls);\n> +}\n> +\n>  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>  {\n>      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>          return;\n>      }\n>\n> +    // Set the controls for the next queued request\n> +    if (!queuedRequests_.empty())\n> +        setRequestControls(queuedRequests_.front());\n> +\n>      /*\n>       * Record the sensor's timestamp in the request metadata. The request\n>       * needs to be obtained from the user-facing buffer, as internal\n> @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> *camera, [[maybe_unused]] const ControlL\n>          return ret;\n>      }\n>\n> +    // Apply controls from first request\n> +    if (!data->queuedRequests_.empty())\n> +        data->setRequestControls(data->queuedRequests_.front());\n> +\n>      if (data->useConverter_) {\n>          ret = data->converter_->start();\n>          if (ret < 0) {\n> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\n> index 9c783c9c..2a15ae09 100644\n> --- a/src/libcamera/v4l2_device.cpp\n> +++ b/src/libcamera/v4l2_device.cpp\n> @@ -296,6 +296,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n>          /* Set the v4l2_ext_control value for the write operation. */\n>          ControlValue &value = ctrl->second;\n>          switch (iter->first->type()) {\n> +        case ControlTypeBool:\n> +            v4l2Ctrl.value64 = value.get<bool>();\n> +            break;\n> +\n> +        case ControlTypeNone:\n> +            break;\n> +\n>          case ControlTypeInteger64:\n>              v4l2Ctrl.value64 = value.get<int64_t>();\n>              break;\n> @@ -479,7 +486,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n>          return ControlTypeInteger64;\n>\n>      case V4L2_CTRL_TYPE_MENU:\n> -    case V4L2_CTRL_TYPE_BUTTON:\n>      case V4L2_CTRL_TYPE_BITMASK:\n>      case V4L2_CTRL_TYPE_INTEGER_MENU:\n>          /*\n> @@ -488,6 +494,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n>           */\n>          return ControlTypeInteger32;\n>\n> +    case V4L2_CTRL_TYPE_BUTTON:\n>      default:\n>          return ControlTypeNone;\n>      }\n> @@ -530,6 +537,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\n> v4l2_query_ext_ctrl &ctrl)\n>                     static_cast<int64_t>(ctrl.maximum),\n>                     static_cast<int64_t>(ctrl.default_value));\n>\n> +    case V4L2_CTRL_TYPE_BUTTON:\n> +        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n> +\n>      case V4L2_CTRL_TYPE_INTEGER_MENU:\n>      case V4L2_CTRL_TYPE_MENU:\n>          return v4l2MenuControlInfo(ctrl);\n> --\n> 2.25.1","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C0325BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 11:33:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EB04A60882;\n\tThu,  9 Dec 2021 12:33:22 +0100 (CET)","from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net\n\t[217.70.183.198])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 924FA607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 12:33:20 +0100 (CET)","(Authenticated sender: jacopo@jmondi.org)\n\tby relay6-d.mail.gandi.net (Postfix) with ESMTPSA id DD3CDC0019;\n\tThu,  9 Dec 2021 11:33:19 +0000 (UTC)"],"Date":"Thu, 9 Dec 2021 12:34:12 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Benjamin Schaaf <ben.schaaf@gmail.com>","Message-ID":"<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>\n\t<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21711,"web_url":"https://patchwork.libcamera.org/comment/21711/","msgid":"<CAJ+kdVEyEYaOzrrFMHbm5RMfCQ6sP745M824WfLQAD6kkWGeiA@mail.gmail.com>","date":"2021-12-09T11:57:43","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":108,"url":"https://patchwork.libcamera.org/api/people/108/","name":"Benjamin Schaaf","email":"ben.schaaf@gmail.com"},"content":"On Thu, Dec 9, 2021 at 10:33 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n>\n> Hi Benjamin\n>\n> On Thu, Dec 09, 2021 at 09:55:33PM +1100, Benjamin Schaaf wrote:\n> > Thanks for the review, I'll put the updated patch at the end.\n>\n> Please don't :)\n>\n> Always send a new patch for new versions!\n\nNo worries, I'll do that.\n\n> >\n> > On Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n> > >\n> > > Hello Benjamin,\n> > >\n> > > On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > > > Controls and control info are translated between libcamera and V4L2\n> > > > inside the simple pipeline. Request controls are applied when a request\n> > > > is next to be completed.\n> > > >\n> > > > This also adds some additional draft controls needed for the PinePhone.\n> > >\n> > > Sorry to get straight to this before looking at the patch content, but\n> > > we do enforce a code style as reported here\n> > > https://libcamera.org/coding-style.html#coding-style-guidelines\n> > >\n> > > We have a tool that might help you catching style issues at\n> > > utils/checkstyle.py\n> > >\n> > > Care to reformat your patches to comply with the project code style ?\n> > >\n> > > >\n> > > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > > ---\n> > > >  src/libcamera/control_ids.yaml             |  24 +++\n> > > >  src/libcamera/controls.cpp                 |   6 -\n> > > >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> > > >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> > > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > > >  6 files changed, 310 insertions(+), 7 deletions(-)\n> > > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > > >\n> > > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > > index 9d4638ae..2af230c3 100644\n> > > > --- a/src/libcamera/control_ids.yaml\n> > > > +++ b/src/libcamera/control_ids.yaml\n> > > > @@ -406,6 +406,30 @@ controls:\n> > > >              The camera will cancel any active or completed metering sequence.\n> > > >              The AE algorithm is reset to its initial state.\n> > > >\n> > > > +  - AutoGain:\n> > > > +      type: bool\n> > > > +      draft: true\n> > > > +      description: |\n> > > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > > > +\n> > > > +  - AfEnabled:\n> > > > +      type: bool\n> > > > +      draft: true\n> > > > +      description: |\n> > > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > > > +\n> > > > +  - AfStart:\n> > > > +      type: void\n> > >\n> > > Why type void ? Isn't this a boolean ?\n> >\n> > V4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\n> > performs an action when the control is set. Thus type void. Same for\n> > V4L2_CID_AUTO_FOCUS_END.\n> >\n> > It seems I forgot to include some required type conversion logic in\n> > v4l2_device, not sure how that got missed.\n>\n> So you use void to indicate that we don't care about the value but the\n> control presence signify that, in example, the autofocus routine\n> should be started.\n>\n> We don't have 'one shot' control so far (the assumption is that once a\n> control is set to a value, the value stays the same until it's not\n> updated), and this could be a valid usage of type: void indeed\n>\n> >\n> > > > +      draft: true\n> > > > +      description: |\n> > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > > +\n> > > > +  - AfStop:\n> > > > +      type: void\n> > > > +      draft: true\n> > > > +      description: |\n> > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > > +\n> > >\n> > > We're in the process of reworking controls related to gain and focus,\n> > > but for the moment, as we comply with Android by having their controls\n> > > defined as draft, I'm not opposed to have these here.\n> > >\n> > > I'm worried once we have applications use them, we will never move to\n> > > the newly defined ones though unless we forcefully remove them in\n> > > future...\n> >\n> > FWIW given that libcamera doesn't have versions/releases I don't\n> > personally expect a stable API.\n> >\n>\n> In the long term, draft control will be replaced by standard controls.\n> What I'm afraid of is that if applications start relying on draft\n> controls it will be harder to remove them, as it will likely require a\n> different type of mapping. But I have no better suggestions to provide\n> atm\n>\n> > > >    - AfTrigger:\n> > > >        type: int32_t\n> > > >        draft: true\n> > > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > > > index 0d8c0a5c..1f65fc73 100644\n> > > > --- a/src/libcamera/controls.cpp\n> > > > +++ b/src/libcamera/controls.cpp\n> > > > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > int id) const\n> > > >  {\n> > > >      const auto iter = controls_.find(id);\n> > > >      if (iter == controls_.end()) {\n> > > > -        LOG(Controls, Error)\n> > > > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > > > -\n> > >\n> > > Ouch, why remove the error message ?\n> >\n> > My bad, seems I was misinterpreting when that error could show up.\n> > I'll add it back in.\n> >\n> > > >          return nullptr;\n> > > >      }\n> > > >\n> > > > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > int id) const\n> > > >  ControlValue *ControlList::find(unsigned int id)\n> > > >  {\n> > > >      if (validator_ && !validator_->validate(id)) {\n> > > > -        LOG(Controls, Error)\n> > > > -            << \"Control \" << utils::hex(id)\n> > > > -            << \" is not valid for \" << validator_->name();\n> > > >          return nullptr;\n> > > >      }\n> > > >\n> > > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > > new file mode 100644\n> > > > index 00000000..32695749\n> > > > --- /dev/null\n> > > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > > @@ -0,0 +1,230 @@\n> > >\n> > > File license ? You can copy the SPDX header from other files and\n> > > attribute the copyright to you or any one you like\n> > >\n> > > > +#include \"controls.h\"\n> > > > +\n> > > > +#include <linux/v4l2-controls.h>\n> > > > +\n> > > > +#include <libcamera/base/log.h>\n> > > > +\n> > > > +#include <libcamera/control_ids.h>\n> > > > +\n> > > > +namespace libcamera {\n> > > > +\n> > > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > > +\n> > > > +/*\n> > > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > > + * doing any conversion to the ControlValue.\n> > > > + */\n> > > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > > +};\n> > >\n> > > If them map is meant for internal use only you can declare it in an\n> > > anonymous namespace\n> > >\n> > > namsepace {\n> > >         std::unordered_map<>...\n> > >\n> > > };\n> > >\n> > > namespace libcamera {\n> > >\n> > > };\n> >\n> > That's the same as static though?\n> >\n>\n> Yes but we usually prefer anonymous namespaces... Not a big deal\n> though.\n>\n> > > > +\n> > > > +/*\n> > > > + * Convert from a libcamera control to a V4L2 control, optionally\n> > > > also convert a\n> > > > + * set of ControlValues.\n> > > > + */\n> > >\n> > > We use doxygen for documenting the code. Please see other files as an\n> > > example.\n> > >\n> > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > +             unsigned int *v4l2_control,\n> > > > +             const ControlValue *control_values,\n> > > > +             ControlValue *v4l2_values,\n> > > > +             size_t num_values)\n> > >\n> > > Before looking at the implementation, let's reason a bit on the\n> > > architecture.\n> > >\n> > > Is this control mapping valid for all platforms using the simple\n> > > pipeline handler ?\n> > >\n> > > Is the device on which controls have to be applied the same for all\n> > > platforms ?\n> > >\n> > > Should control handling be broken down to a platform specific\n> > > component to be selected at compile time ?\n> >\n> > I'm not sure what you mean by platform here? Do you mean Linux vs\n> > Android, x86 vs arm or PinePhone vs Librem 5?\n>\n> I mean the SoC.\n>\n> Simple is used (afaik) on imx6, imx8mq, allwinner, mediatek and\n> possibly others.\n>\n> The mapping between v4l2 controls and libcamera controls does\n> generically apply to all of them ? Surely not the values the controls\n> transport, but if we assume the application knows what platform it\n> operates on that's fine.\n\nI don't see how they wouldn't. The V4L2 controls are standardized in\nthe same way the android ones are. FWIW the controls aren't SoC\nspecific, they're sensor + SoC +  device-tree specific.\n\n> >\n> > The way I see it, the simple pipeline is just a simple abstraction on\n> > V4L2 and this is a simple conversion between V4L2 and libcamera\n> > controls.\n>\n> This is a bit of a stretch.\n>\n> What's happening here is that you defined controls equal to the V4L2\n> ones, and have the app set the 'right' values for the sensor/platform\n> in use (a 'gain' value does not have the same meaning between\n> different sensors, in example).\n>\n> In 'regular' platforms with an ISP, an IPA etc the pipeline receives\n> libcamera::controls and with the help of IPA and statistics computes\n> the right v4l2 controls for the platform (the pipeline handler knows\n> what platform it runs on, except for simple) and for the sensor\n> through a set of CameraSensorHelpers that aid with the translation.\n>\n> Now we change the landscape a bit, and we assume the app knows what\n> platforms it runs on, something that defeats the purpose of libcamera\n> usage, but I understand there aren't may way around that to support\n> your use case.\n>\n> As a pipeline handler is charge of:\n>\n> 1) Registering what control it supports to expose that to application\n> 2) Translate libcamera control to v4l2 controls (not in your case)\n> 3) Apply controls to the right device/subdevice at the right time (the\n>   time when to set a control is not trivially calculated as most\n>   controls have a delay and should be applied in advance)\n>\n> Now assuming point 2) is moved to the app in your case, point 1 and 3\n> do not apply generically to all platforms using the simple pipeline\n> handler. Some might support control the other does not support. Some\n> might want to set a control to the video devices while others will\n> apply the same control on the sensor subdev. It all very depends on\n> the driver architecture of the SoC.\n>\n> Now, even for simple boolean/button controls like the ones you're dealing with,\n> for which not much reasoning and translations are required, a platform\n> specific component seems to be needed to address 1) and 3).\n>\n> Does it make sense to you ?\n\nIn terms of registering what controls are exposed that's done\ngenerically in SimpleCameraData::init right? As for when controls are\napplied the assumption being made in libcamera is that they can be\napplied before a frame no?\n\n> >\n> > > Also, I would like to see this implemented thorough a componenet with\n> > > a single interface towards the pipeline handler rather than a raw set\n> > > of helper functions. We can indeed help designing that once we have\n> > > the previous question clarified.\n> > >\n> > > I'm not even sure this is the direction we want to got with the simple\n> > > pipeline handler (platform-specific backends), and I would like to\n> > > hear Laurent's opinion on this, but I see a potential for doing what\n> > > we do with the android backend selection through the\n> > > 'android_platform' build option.\n> > >\n> > >         option('android_platform',\n> > >                 type : 'combo',\n> > >                 choices : ['cros', 'generic'],\n> > >                 value : 'generic',\n> > >                 description : 'Select the Android platform to compile for')\n> > >\n> > > > +{\n> > > > +    // Convert controls\n> > > > +    if (v4l2_control) {\n> > > > +        auto it = controlsToV4L2.find(control);\n> > > > +        if (it == controlsToV4L2.end())\n> > > > +            return false;\n> > > > +\n> > > > +        *v4l2_control = it->second;\n> > > > +    }\n> > > > +\n> > > > +    // Convert values\n> > > > +    if (num_values == 0)\n> > > > +        return true;\n> > > > +\n> > > > +    switch (control) {\n> > > > +    case controls::AE_ENABLE:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            v4l2_values[i] =\n> > > > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > > > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > > +        return true;\n> > > > +    case controls::EXPOSURE_VALUE:\n> > > > +    case controls::DIGITAL_GAIN:\n> > > > +    case controls::ANALOGUE_GAIN:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            v4l2_values[i] =\n> > > > ControlValue((int32_t)control_values[i].get<float>());\n> > > > +        return true;\n> > > > +    // Read only\n> > > > +    case controls::AF_STATE:\n> > > > +        return false;\n> > > > +    default:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            v4l2_values[i] = control_values[i];\n> > > > +        return true;\n> > > > +    }\n> > > > +\n> > > > +}\n> > > > +\n> > > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > > +\n> > > > +/*\n> > > > + * Convert from a V4L2 control to a libcamera control, optionally\n> > > > also convert a\n> > > > + * set of ControlValues.\n> > > > + */\n> > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > +               unsigned int *control,\n> > > > +               const ControlValue *v4l2_values,\n> > > > +               ControlValue *control_values,\n> > > > +               size_t num_values)\n> > > > +{\n> > > > +    // Initialize the inverse of controlsToV4L2\n> > > > +    if (controlsFromV4L2.empty()) {\n> > > > +        for (const auto &v : controlsToV4L2) {\n> > > > +            controlsFromV4L2[v.second] = v.first;\n> > > > +        }\n> > > > +    }\n> > > > +\n> > > > +    // Convert control\n> > > > +    if (control) {\n> > > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > > +        if (it == controlsFromV4L2.end())\n> > > > +            return false;\n> > > > +\n> > > > +        *control = it->second;\n> > > > +    }\n> > > > +\n> > > > +    // Convert values\n> > > > +    if (num_values == 0)\n> > > > +        return true;\n> > > > +\n> > > > +    switch (v4l2_control) {\n> > > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            control_values[i] =\n> > > > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > > +        return true;\n> > > > +    case V4L2_CID_EXPOSURE:\n> > > > +    case V4L2_CID_GAIN:\n> > > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            control_values[i] =\n> > > > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > > +        return true;\n> > > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > > +                control_values[i] =\n> > > > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > > +                break;\n> > > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > > +                control_values[i] =\n> > > > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > > +                break;\n> > > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > > +                control_values[i] =\n> > > > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > > +                break;\n> > > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > > +                control_values[i] =\n> > > > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > > +                break;\n> > > > +            default:\n> > > > +                LOG(SimplePipeline, Error)\n> > > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > > +                /*TODO: Log Error*/\n> > > > +                return false;\n> > > > +            }\n> > > > +        }\n> > > > +        return true;\n> > > > +    default:\n> > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > +            control_values[i] = v4l2_values[i];\n> > > > +        return true;\n> > > > +    }\n> > > > +}\n> > > > +\n> > > > +/*\n> > > > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > > > + * identifiers as well as all values.\n> > > > + */\n> > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > > +{\n> > > > +    ControlInfoMap::Map info_map;\n> > > > +\n> > > > +    for (const auto &pair : v4l2_info_map) {\n> > > > +        unsigned int v4l2_control = pair.first->id();\n> > > > +        const ControlInfo &v4l2_info = pair.second;\n> > > > +\n> > > > +        unsigned int control;\n> > > > +        ControlValue def;\n> > > > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > > > &v4l2_info.def(), &def, 1))\n> > > > +            continue;\n> > > > +\n> > > > +        const ControlId *control_id = controls::controls.at(control);\n> > > > +\n> > > > +        // ControlInfo has either a list of values or a minimum and\n> > > > +        // maximum. This includes controls that have no values or are\n> > > > +        // booleans.\n> > > > +        ControlInfo info;\n> > > > +        if (v4l2_info.values().empty()) {\n> > > > +            ControlValue min, max;\n> > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > &v4l2_info.min(), &min, 1);\n> > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > &v4l2_info.max(), &max, 1);\n> > > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > > +        } else {\n> > > > +            std::vector<ControlValue> values;\n> > > > +            values.resize(v4l2_info.values().size());\n> > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > v4l2_info.values().data(), values.data(), values.size());\n> > > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > > +        }\n> > > > +        info_map.emplace(control_id, std::move(info));\n> > > > +    }\n> > > > +\n> > > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > > +}\n> > > > +\n> > > > +/*\n> > > > + * Convert a control list from libcamera to V4L2.\n> > > > + */\n> > > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > > +{\n> > > > +    ControlList v4l2_controls;\n> > > > +    for (const auto &pair : controls) {\n> > > > +        unsigned int control = pair.first;\n> > > > +        const ControlValue &value = pair.second;\n> > > > +\n> > > > +        unsigned int v4l2_control;\n> > > > +        ControlValue v4l2_value;\n> > > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > > > &v4l2_value, 1)) {\n> > > > +            LOG(SimplePipeline, Warning)\n> > > > +                << \"Control \" << utils::hex(control)\n> > > > +                << \" does not have a V4L2 equivalent\";\n> > > > +            continue;\n> > > > +        }\n> > > > +\n> > > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > > +    }\n> > > > +    return v4l2_controls;\n> > > > +}\n> > > > +\n> > > > +/*\n> > > > + * Convert a control list from V4L2 to libcamera.\n> > > > + */\n> > > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > > +{\n> > > > +    ControlList controls;\n> > > > +    for (const auto &pair : v4l2_controls) {\n> > > > +        unsigned int v4l2_control = pair.first;\n> > > > +        const ControlValue &v4l2_value = pair.second;\n> > > > +\n> > > > +        unsigned int control;\n> > > > +        ControlValue value;\n> > > > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > > > &v4l2_value, &value, 1))\n> > > > +            controls.set(control, value);\n> > > > +    }\n> > > > +    return controls;\n> > > > +}\n> > > > +\n> > > > +} /* namespace libcamera */\n> > > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > > b/src/libcamera/pipeline/simple/controls.h\n> > > > new file mode 100644\n> > > > index 00000000..114c5fc2\n> > > > --- /dev/null\n> > > > +++ b/src/libcamera/pipeline/simple/controls.h\n> > >\n> > > Same comment, license and copyright\n> > >\n> > > > @@ -0,0 +1,26 @@\n> > > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > >\n> > > #pragma once\n> >\n> > All the code I've seen uses #ifndef instead of #pragma once. I'd\n> > prefer to use #pragma once but it seems inconsistent with the rest of\n> > the codebase?\n> >\n>\n> Not since\n> https://patchwork.libcamera.org/project/libcamera/list/?series=2749&state=*\n>\n> > > > +\n> > > > +#include <libcamera/controls.h>\n> > > > +\n> > > > +namespace libcamera {\n> > > > +\n> > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > +             unsigned int *v4l2_control,\n> > > > +             const ControlValue *control_values,\n> > > > +             ControlValue *v4l2_values,\n> > > > +             size_t num_values);\n> > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > +               unsigned int *control,\n> > > > +               const ControlValue *v4l2_values,\n> > > > +               ControlValue *control_values,\n> > > > +               size_t num_values);\n> > > > +\n> > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > > +\n> > > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > > +\n> > > > +} /* namespace libcamera */\n> > > > +\n> > > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > > b/src/libcamera/pipeline/simple/meson.build\n> > > > index 9c99b32f..0c60d65a 100644\n> > > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > > @@ -1,6 +1,7 @@\n> > > >  # SPDX-License-Identifier: CC0-1.0\n> > > >\n> > > >  libcamera_sources += files([\n> > > > +    'controls.cpp',\n> > > >      'converter.cpp',\n> > > >      'simple.cpp',\n> > > >  ])\n> > > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > > index a597e27f..b0d4a62a 100644\n> > > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > > @@ -37,6 +37,7 @@\n> > > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > > >\n> > > >  #include \"converter.h\"\n> > > > +#include \"controls.h\"\n> > > >\n> > > >  namespace libcamera {\n> > > >\n> > > > @@ -181,6 +182,7 @@ public:\n> > > >      int setupLinks();\n> > > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > > >               V4L2Subdevice::Whence whence);\n> > > > +    int setRequestControls(Request *request);\n> > > >      void bufferReady(FrameBuffer *buffer);\n> > > >\n> > > >      unsigned int streamIndex(const Stream *stream) const\n> > > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > > >              formats_[fmt] = &config;\n> > > >      }\n> > > >\n> > > > -    properties_ = sensor_->properties();\n> > > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > > >\n> > > >      return 0;\n> > > >  }\n> > > > @@ -624,6 +627,23 @@ int\n> > > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > > >      return 0;\n> > > >  }\n> > > >\n> > > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > > +{\n> > > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > +\n> > > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > > +    V4L2Device *control_device = video_;\n> > > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > > +        if (subdev) {\n> > > > +            control_device = subdev;\n> > > > +        }\n> > > > +    }\n> > > > +\n> > > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > > +    return control_device->setControls(&controls);\n> > > > +}\n> > > > +\n> > > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > >  {\n> > > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > >          return;\n> > > >      }\n> > > >\n> > > > +    // Set the controls for the next queued request\n> > > > +    if (!queuedRequests_.empty())\n> > > > +        setRequestControls(queuedRequests_.front());\n> > > > +\n> > > >      /*\n> > > >       * Record the sensor's timestamp in the request metadata. The request\n> > > >       * needs to be obtained from the user-facing buffer, as internal\n> > > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > > *camera, [[maybe_unused]] const ControlL\n> > > >          return ret;\n> > > >      }\n> > > >\n> > > > +    // Apply controls from first request\n> > > > +    if (!data->queuedRequests_.empty())\n> > > > +        data->setRequestControls(data->queuedRequests_.front());\n> > > > +\n> > > >      if (data->useConverter_) {\n> > > >          ret = data->converter_->start();\n> > > >          if (ret < 0) {\n> > > > --\n> > > > 2.25.1\n> >\n> > [PATCH] libcamera: pipeline: simple: Add support for controls\n> >\n> > Controls and control info are translated between libcamera and V4L2\n> > inside the simple pipeline. Request controls are applied when a request\n> > is next to be completed.\n> >\n> > This also adds some additional draft controls needed for the PinePhone.\n> >\n> > Bug: https://bugs.libcamera.org/show_bug.cgi?id=98\n> > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > ---\n> >  src/libcamera/control_ids.yaml             |  24 ++\n> >  src/libcamera/pipeline/simple/controls.cpp | 241 +++++++++++++++++++++\n> >  src/libcamera/pipeline/simple/controls.h   |  33 +++\n> >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> >  src/libcamera/v4l2_device.cpp              |  12 +-\n> >  6 files changed, 339 insertions(+), 2 deletions(-)\n> >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> >\n> > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > index 9d4638ae..2af230c3 100644\n> > --- a/src/libcamera/control_ids.yaml\n> > +++ b/src/libcamera/control_ids.yaml\n> > @@ -406,6 +406,30 @@ controls:\n> >              The camera will cancel any active or completed metering sequence.\n> >              The AE algorithm is reset to its initial state.\n> >\n> > +  - AutoGain:\n> > +      type: bool\n> > +      draft: true\n> > +      description: |\n> > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > +\n> > +  - AfEnabled:\n> > +      type: bool\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > +\n> > +  - AfStart:\n> > +      type: void\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > +\n> > +  - AfStop:\n> > +      type: void\n> > +      draft: true\n> > +      description: |\n> > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > +\n> >    - AfTrigger:\n> >        type: int32_t\n> >        draft: true\n> > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > b/src/libcamera/pipeline/simple/controls.cpp\n> > new file mode 100644\n> > index 00000000..f3922e45\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > @@ -0,0 +1,241 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Benjamin Schaaf\n> > + *\n> > + * controls.cpp - Simple pipeline control conversion\n> > + */\n> > +\n> > +#include \"controls.h\"\n> > +\n> > +#include <linux/v4l2-controls.h>\n> > +\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include <libcamera/control_ids.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > +\n> > +/*\n> > + * These controls can be directly mapped between libcamera and V4L2 without\n> > + * doing any conversion to the ControlValue.\n> > + */\n> > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > +};\n> > +\n> > +/**\n> > + * \\brief Convert from a libcamera control to a V4L2 control.\n> > + *\n> > + * Can optionally convert the libcamera control and/or a set of libcamera\n> > + * control values to their V4L2 equivalents.\n> > + */\n> > +bool simpleControlToV4L2(unsigned int control,\n> > +             unsigned int *v4l2_control,\n> > +             const ControlValue *control_values,\n> > +             ControlValue *v4l2_values,\n> > +             size_t num_values)\n> > +{\n> > +    // Convert controls\n> > +    if (v4l2_control) {\n> > +        auto it = controlsToV4L2.find(control);\n> > +        if (it == controlsToV4L2.end())\n> > +            return false;\n> > +\n> > +        *v4l2_control = it->second;\n> > +    }\n> > +\n> > +    // Convert values\n> > +    if (num_values == 0)\n> > +        return true;\n> > +\n> > +    switch (control) {\n> > +    case controls::AE_ENABLE:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] =\n> > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > +        return true;\n> > +    case controls::EXPOSURE_VALUE:\n> > +    case controls::DIGITAL_GAIN:\n> > +    case controls::ANALOGUE_GAIN:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] =\n> > ControlValue((int32_t)control_values[i].get<float>());\n> > +        return true;\n> > +    // Read only\n> > +    case controls::AF_STATE:\n> > +        return false;\n> > +    default:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            v4l2_values[i] = control_values[i];\n> > +        return true;\n> > +    }\n> > +}\n> > +\n> > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > +\n> > +/**\n> > + * \\brief Convert from a V4L2 control to a libcamera control.\n> > + *\n> > + * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n> > + * to their libcamera equivalents.\n> > + */\n> > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > +               unsigned int *control,\n> > +               const ControlValue *v4l2_values,\n> > +               ControlValue *control_values,\n> > +               size_t num_values)\n> > +{\n> > +    // Initialize the inverse of controlsToV4L2\n> > +    if (controlsFromV4L2.empty()) {\n> > +        for (const auto &v : controlsToV4L2) {\n> > +            controlsFromV4L2[v.second] = v.first;\n> > +        }\n> > +    }\n> > +\n> > +    // Convert control\n> > +    if (control) {\n> > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > +        if (it == controlsFromV4L2.end())\n> > +            return false;\n> > +\n> > +        *control = it->second;\n> > +    }\n> > +\n> > +    // Convert values\n> > +    if (num_values == 0)\n> > +        return true;\n> > +\n> > +    switch (v4l2_control) {\n> > +    case V4L2_CID_EXPOSURE_AUTO:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] =\n> > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > +        return true;\n> > +    case V4L2_CID_EXPOSURE:\n> > +    case V4L2_CID_GAIN:\n> > +    case V4L2_CID_ANALOGUE_GAIN:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] =\n> > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > +        return true;\n> > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > +        for (size_t i = 0; i < num_values; ++i) {\n> > +            switch (v4l2_values[i].get<int32_t>()) {\n> > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > +                break;\n> > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > +                control_values[i] =\n> > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > +                break;\n> > +            default:\n> > +                LOG(SimplePipeline, Error)\n> > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > +                /*TODO: Log Error*/\n> > +                return false;\n> > +            }\n> > +        }\n> > +        return true;\n> > +    default:\n> > +        for (size_t i = 0; i < num_values; ++i)\n> > +            control_values[i] = v4l2_values[i];\n> > +        return true;\n> > +    }\n> > +}\n> > +\n> > +/**\n> > + * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n> > + *\n> > + * Converts both the control identifiers as well as all values.\n> > + */\n> > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > +{\n> > +    ControlInfoMap::Map info_map;\n> > +\n> > +    for (const auto &pair : v4l2_info_map) {\n> > +        unsigned int v4l2_control = pair.first->id();\n> > +        const ControlInfo &v4l2_info = pair.second;\n> > +\n> > +        unsigned int control;\n> > +        ControlValue def;\n> > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > &v4l2_info.def(), &def, 1))\n> > +            continue;\n> > +\n> > +        const ControlId *control_id = controls::controls.at(control);\n> > +\n> > +        // ControlInfo has either a list of values or a minimum and\n> > +        // maximum. This includes controls that have no values or are\n> > +        // booleans.\n> > +        ControlInfo info;\n> > +        if (v4l2_info.values().empty()) {\n> > +            ControlValue min, max;\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > &v4l2_info.min(), &min, 1);\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > &v4l2_info.max(), &max, 1);\n> > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > +        } else {\n> > +            std::vector<ControlValue> values;\n> > +            values.resize(v4l2_info.values().size());\n> > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > v4l2_info.values().data(), values.data(), values.size());\n> > +            info = ControlInfo(std::move(values), std::move(def));\n> > +        }\n> > +        info_map.emplace(control_id, std::move(info));\n> > +    }\n> > +\n> > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Convert a control list from libcamera to V4L2.\n> > + */\n> > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > +{\n> > +    ControlList v4l2_controls;\n> > +    for (const auto &pair : controls) {\n> > +        unsigned int control = pair.first;\n> > +        const ControlValue &value = pair.second;\n> > +\n> > +        unsigned int v4l2_control;\n> > +        ControlValue v4l2_value;\n> > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > &v4l2_value, 1)) {\n> > +            LOG(SimplePipeline, Warning)\n> > +                << \"Control \" << utils::hex(control)\n> > +                << \" does not have a V4L2 equivalent\";\n> > +            continue;\n> > +        }\n> > +\n> > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > +    }\n> > +    return v4l2_controls;\n> > +}\n> > +\n> > +/**\n> > + * \\brief Convert a control list from V4L2 to libcamera.\n> > + */\n> > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > +{\n> > +    ControlList controls;\n> > +    for (const auto &pair : v4l2_controls) {\n> > +        unsigned int v4l2_control = pair.first;\n> > +        const ControlValue &v4l2_value = pair.second;\n> > +\n> > +        unsigned int control;\n> > +        ControlValue value;\n> > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > &v4l2_value, &value, 1))\n> > +            controls.set(control, value);\n> > +    }\n> > +    return controls;\n> > +}\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > b/src/libcamera/pipeline/simple/controls.h\n> > new file mode 100644\n> > index 00000000..54b4a565\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/simple/controls.h\n> > @@ -0,0 +1,33 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Benjamin Schaaf\n> > + *\n> > + * controls.h - Simple pipeline control conversion\n> > + */\n> > +\n> > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > +\n> > +#include <libcamera/controls.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +bool simpleControlToV4L2(unsigned int control,\n> > +             unsigned int *v4l2_control,\n> > +             const ControlValue *control_values,\n> > +             ControlValue *v4l2_values,\n> > +             size_t num_values);\n> > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > +               unsigned int *control,\n> > +               const ControlValue *v4l2_values,\n> > +               ControlValue *control_values,\n> > +               size_t num_values);\n> > +\n> > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > +\n> > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > +\n> > +} /* namespace libcamera */\n> > +\n> > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > b/src/libcamera/pipeline/simple/meson.build\n> > index 9c99b32f..0c60d65a 100644\n> > --- a/src/libcamera/pipeline/simple/meson.build\n> > +++ b/src/libcamera/pipeline/simple/meson.build\n> > @@ -1,6 +1,7 @@\n> >  # SPDX-License-Identifier: CC0-1.0\n> >\n> >  libcamera_sources += files([\n> > +    'controls.cpp',\n> >      'converter.cpp',\n> >      'simple.cpp',\n> >  ])\n> > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > b/src/libcamera/pipeline/simple/simple.cpp\n> > index a597e27f..1717a1a7 100644\n> > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > @@ -36,6 +36,7 @@\n> >  #include \"libcamera/internal/v4l2_subdevice.h\"\n> >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> >\n> > +#include \"controls.h\"\n> >  #include \"converter.h\"\n> >\n> >  namespace libcamera {\n> > @@ -181,6 +182,7 @@ public:\n> >      int setupLinks();\n> >      int setupFormats(V4L2SubdeviceFormat *format,\n> >               V4L2Subdevice::Whence whence);\n> > +    int setRequestControls(Request *request);\n> >      void bufferReady(FrameBuffer *buffer);\n> >\n> >      unsigned int streamIndex(const Stream *stream) const\n> > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> >              formats_[fmt] = &config;\n> >      }\n> >\n> > -    properties_ = sensor_->properties();\n> > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> >\n> >      return 0;\n> >  }\n> > @@ -624,6 +627,23 @@ int\n> > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> >      return 0;\n> >  }\n> >\n> > +int SimpleCameraData::setRequestControls(Request *request)\n> > +{\n> > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > +\n> > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > +    V4L2Device *control_device = video_;\n> > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > +        if (subdev) {\n> > +            control_device = subdev;\n> > +        }\n> > +    }\n> > +\n> > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > +    return control_device->setControls(&controls);\n> > +}\n> > +\n> >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> >  {\n> >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> >          return;\n> >      }\n> >\n> > +    // Set the controls for the next queued request\n> > +    if (!queuedRequests_.empty())\n> > +        setRequestControls(queuedRequests_.front());\n> > +\n> >      /*\n> >       * Record the sensor's timestamp in the request metadata. The request\n> >       * needs to be obtained from the user-facing buffer, as internal\n> > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > *camera, [[maybe_unused]] const ControlL\n> >          return ret;\n> >      }\n> >\n> > +    // Apply controls from first request\n> > +    if (!data->queuedRequests_.empty())\n> > +        data->setRequestControls(data->queuedRequests_.front());\n> > +\n> >      if (data->useConverter_) {\n> >          ret = data->converter_->start();\n> >          if (ret < 0) {\n> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\n> > index 9c783c9c..2a15ae09 100644\n> > --- a/src/libcamera/v4l2_device.cpp\n> > +++ b/src/libcamera/v4l2_device.cpp\n> > @@ -296,6 +296,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n> >          /* Set the v4l2_ext_control value for the write operation. */\n> >          ControlValue &value = ctrl->second;\n> >          switch (iter->first->type()) {\n> > +        case ControlTypeBool:\n> > +            v4l2Ctrl.value64 = value.get<bool>();\n> > +            break;\n> > +\n> > +        case ControlTypeNone:\n> > +            break;\n> > +\n> >          case ControlTypeInteger64:\n> >              v4l2Ctrl.value64 = value.get<int64_t>();\n> >              break;\n> > @@ -479,7 +486,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n> >          return ControlTypeInteger64;\n> >\n> >      case V4L2_CTRL_TYPE_MENU:\n> > -    case V4L2_CTRL_TYPE_BUTTON:\n> >      case V4L2_CTRL_TYPE_BITMASK:\n> >      case V4L2_CTRL_TYPE_INTEGER_MENU:\n> >          /*\n> > @@ -488,6 +494,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n> >           */\n> >          return ControlTypeInteger32;\n> >\n> > +    case V4L2_CTRL_TYPE_BUTTON:\n> >      default:\n> >          return ControlTypeNone;\n> >      }\n> > @@ -530,6 +537,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\n> > v4l2_query_ext_ctrl &ctrl)\n> >                     static_cast<int64_t>(ctrl.maximum),\n> >                     static_cast<int64_t>(ctrl.default_value));\n> >\n> > +    case V4L2_CTRL_TYPE_BUTTON:\n> > +        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n> > +\n> >      case V4L2_CTRL_TYPE_INTEGER_MENU:\n> >      case V4L2_CTRL_TYPE_MENU:\n> >          return v4l2MenuControlInfo(ctrl);\n> > --\n> > 2.25.1","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 22E45BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 11:58:00 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 622DC60882;\n\tThu,  9 Dec 2021 12:57:59 +0100 (CET)","from mail-yb1-xb35.google.com (mail-yb1-xb35.google.com\n\t[IPv6:2607:f8b0:4864:20::b35])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A6A9607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 12:57:57 +0100 (CET)","by mail-yb1-xb35.google.com with SMTP id j2so13033115ybg.9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 09 Dec 2021 03:57:57 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"CjahzaJA\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;\n\th=mime-version:references:in-reply-to:from:date:message-id:subject:to\n\t:cc; bh=u7of17Cyhgzunw6VTu+YyNwbSOZ0O+4+DfPxi+Gee8A=;\n\tb=CjahzaJA33NKiQ5VnILW91oFxy4vebI9FkjBuiPEQ9XNC69hdElPy/5UPDXYNI2FWM\n\tQfv8spmxtc1Vsx459x2NJ9ndFO1N1XhQG77MeOUagBqVsVDd8WHcw2lUZ/QL2+bhXr20\n\tIpwmdV+Z28sjW6zfzwdqQb2nssOsom1RWEG2eVlvBJ1ZtytVWqpbv6R0KZafgAWYSMap\n\tTVN6Hu/QyflJdOiRENEEgjrERsRRixT+EbX1KFWcdbG+AaOeyQC1HkpLzpoEBk+yXe+g\n\ts8mnj0VYvu5TpsFfNFNORu23PZ2OzuSb0U1NC5DyIg6/wG65FfQJc1R42LvYetRC7PHn\n\tcmJw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=u7of17Cyhgzunw6VTu+YyNwbSOZ0O+4+DfPxi+Gee8A=;\n\tb=tu3FUQGefRvp3mQy/h6YC3DuIpk7Riil3NYt2+xmpUxjVW/jTThSJkP2ROdsxa1qys\n\tE4dXG1gox9tHtV5Gj6p+Mn49Ht7BbYLjq7RJw4hHVebSo7tgCRIh0kAqDWkQH6i7y+y4\n\tx6j2dp9XiMrewnjSonYsOklrAdQC/8WsfpssgJpkNVCGXfbHG8TcGy/jsbgiN1c0T0kV\n\tIB8Z4WSmPpqqey3jgTkR54Te4qJs28zN7UxO9sEuA/xRnKU8chVXRZ4J0kCRYpBqSUmM\n\temdpqSAlh+CvQq9/yAGcHsaSdQ0fOFoTIGIsh5td59KUOhw3DMRW/MJTVWNlLPMRZPUG\n\ti3zg==","X-Gm-Message-State":"AOAM5303ZQR0Hr2Wz3uQG2QPxqnfwZZGrd5ZXoiMqI2pCoX8pvEtx+lO\n\tZlAPh5I9z4feTGGPHab0H7sP+puavnLDjx4iK+g=","X-Google-Smtp-Source":"ABdhPJz90buGTpI8/d+ClEk5wdrxjtWhEeD1eQXeK8G9ksYwG36xSEeK+IR8irkd5WsmYFk8qAmMnB9nAgIzP1YzUyo=","X-Received":"by 2002:a25:28c6:: with SMTP id\n\to189mr5421856ybo.462.1639051075202; \n\tThu, 09 Dec 2021 03:57:55 -0800 (PST)","MIME-Version":"1.0","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>\n\t<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>\n\t<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>","In-Reply-To":"<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>","From":"Benjamin Schaaf <ben.schaaf@gmail.com>","Date":"Thu, 9 Dec 2021 22:57:43 +1100","Message-ID":"<CAJ+kdVEyEYaOzrrFMHbm5RMfCQ6sP745M824WfLQAD6kkWGeiA@mail.gmail.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21712,"web_url":"https://patchwork.libcamera.org/comment/21712/","msgid":"<CAHW6GYLKOq98+DUGZ5Vhaasqqh5O7rAO2_ZEGP5Ur=VivmJymA@mail.gmail.com>","date":"2021-12-09T12:12:02","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Benjamin, everyone\n\nThanks for posting this patch. I think it's important to get autofocus\nworking, and indeed started a discussion on how it should look to\napplications and users some time ago (email to this mailing list on 21\nOctober).\n\nBack then I was concerned about creating an interface that would make\nsense to user applications and also Android, working typically at a\nslightly higher level than V4L2. I certainly see the appeal of a\nfairly direct translation between libcamera controls and V4L2 ones,\nbut I'm not sure it's the level at which a user, or Android, wants to\nwork. Having said that, maybe there could be a reason to have both?\n\nWhat I proposed then was the following controls:\n\n\"AF Mode\" - either \"auto\" or \"continuous\" (I didn't define an \"off\".\n\"auto\" is identical to \"off\" if you don't send any \"triggers\").\n\n\"AF Trigger\" - \"start\" or \"cancel\". Triggers (or cancels) an AF sweep\nif in \"auto\" mode. Ignored during CAF (continuous AF).\n\n\"AF Range\" - \"macro\", \"full\" or \"normal\". The part of the focus range\nto sweep - \"macro\" (just very close distances), \"full\" (everything),\nand \"normal\" (which is \"full\" minus \"macro\" - the reason being that\nyou spend a long time sweeping the macro end so it's often helpful to\nbe able to skip it).\n\n\"AF Speed\" - particularly with CAF, you may want the algorithm to move\nthe lens more slowly (during video capture, typically) or faster (when\nin preview mode before stills capture). Possibly this applies to\nregular AF sweeps too, where \"slower\" = \"more precise\" and \"faster\" =\n\"take that picture *now*!\".\n\nThough I didn't mention it in October, I'd also like to have:\n\n\"AF Metering\" - \"centre\" or \"multi-spot\". \"centre\" is pretty\nself-explanatory, you focus on the centre of the image. \"multi-spot\"\nmeans that you have multiple AF windows all across the image and you\nmonitor all of them when you sweep. At the end, you choose the one\nwith the closest focal distance. This has been quite common in many\ncamera systems.\n\n\"lens position\" - move the lens to this position. I'm a bit in two\nminds as to whether to use lens driver chip values directly, or more\nAndroid-like numbers such that 0 = infinity. At some point we'll have\nto support \"calibrated\" lenses where Android formally uses units of\ndioptres. Using driver chip values here pushes that calibration into\nthe Android layer. Not sure.\n\nThe min/max/default values for the \"lens position\" control could be:\nmin = infinity, max = the value corresponding to the closest focal\nposition, and default = hyperfocal. There's still a bit of a question\nas to where we get these numbers from as they depend on the module as\na whole (not just the lens driver chip).\n\nAnd there will need to be some metadata coming back:\n\n\"lens position\" - where the lens was for this frame\n\n\"AF state\" - state of the AF algorithm, maybe \"scanning\" (sweep in\nprogress), \"focused\" (sweep finished and succeeded), \"failed\" (sweep\nfailed) and \"reset\" (when the camera has just started, or a scan was\ncancelled, or the lens moved manually).\n\nSo the question in my mind is how all this co-exists with all those\nrather lower-level V4L2 controls. Can we implement the above API in\nterms of those V4L2 controls, do you think?\n\nAnyway, I'd love to hear people's thoughts on this matter. I am trying\nto get round to writing a slightly more formal proposal for all the\ncontrols that I have outlined, hopefully it won't be too much longer.\n\nThanks and best regards\n\nDavid\n\nOn Thu, 9 Dec 2021 at 10:55, Benjamin Schaaf <ben.schaaf@gmail.com> wrote:\n>\n> Thanks for the review, I'll put the updated patch at the end.\n>\n> On Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n> >\n> > Hello Benjamin,\n> >\n> > On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > > Controls and control info are translated between libcamera and V4L2\n> > > inside the simple pipeline. Request controls are applied when a request\n> > > is next to be completed.\n> > >\n> > > This also adds some additional draft controls needed for the PinePhone.\n> >\n> > Sorry to get straight to this before looking at the patch content, but\n> > we do enforce a code style as reported here\n> > https://libcamera.org/coding-style.html#coding-style-guidelines\n> >\n> > We have a tool that might help you catching style issues at\n> > utils/checkstyle.py\n> >\n> > Care to reformat your patches to comply with the project code style ?\n> >\n> > >\n> > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > ---\n> > >  src/libcamera/control_ids.yaml             |  24 +++\n> > >  src/libcamera/controls.cpp                 |   6 -\n> > >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> > >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > >  6 files changed, 310 insertions(+), 7 deletions(-)\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > >\n> > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > index 9d4638ae..2af230c3 100644\n> > > --- a/src/libcamera/control_ids.yaml\n> > > +++ b/src/libcamera/control_ids.yaml\n> > > @@ -406,6 +406,30 @@ controls:\n> > >              The camera will cancel any active or completed metering sequence.\n> > >              The AE algorithm is reset to its initial state.\n> > >\n> > > +  - AutoGain:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > > +\n> > > +  - AfEnabled:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > > +\n> > > +  - AfStart:\n> > > +      type: void\n> >\n> > Why type void ? Isn't this a boolean ?\n>\n> V4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\n> performs an action when the control is set. Thus type void. Same for\n> V4L2_CID_AUTO_FOCUS_END.\n>\n> It seems I forgot to include some required type conversion logic in\n> v4l2_device, not sure how that got missed.\n>\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > +\n> > > +  - AfStop:\n> > > +      type: void\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > +\n> >\n> > We're in the process of reworking controls related to gain and focus,\n> > but for the moment, as we comply with Android by having their controls\n> > defined as draft, I'm not opposed to have these here.\n> >\n> > I'm worried once we have applications use them, we will never move to\n> > the newly defined ones though unless we forcefully remove them in\n> > future...\n>\n> FWIW given that libcamera doesn't have versions/releases I don't\n> personally expect a stable API.\n>\n> > >    - AfTrigger:\n> > >        type: int32_t\n> > >        draft: true\n> > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > > index 0d8c0a5c..1f65fc73 100644\n> > > --- a/src/libcamera/controls.cpp\n> > > +++ b/src/libcamera/controls.cpp\n> > > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > > int id) const\n> > >  {\n> > >      const auto iter = controls_.find(id);\n> > >      if (iter == controls_.end()) {\n> > > -        LOG(Controls, Error)\n> > > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > > -\n> >\n> > Ouch, why remove the error message ?\n>\n> My bad, seems I was misinterpreting when that error could show up.\n> I'll add it back in.\n>\n> > >          return nullptr;\n> > >      }\n> > >\n> > > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > > int id) const\n> > >  ControlValue *ControlList::find(unsigned int id)\n> > >  {\n> > >      if (validator_ && !validator_->validate(id)) {\n> > > -        LOG(Controls, Error)\n> > > -            << \"Control \" << utils::hex(id)\n> > > -            << \" is not valid for \" << validator_->name();\n> > >          return nullptr;\n> > >      }\n> > >\n> > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > new file mode 100644\n> > > index 00000000..32695749\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > @@ -0,0 +1,230 @@\n> >\n> > File license ? You can copy the SPDX header from other files and\n> > attribute the copyright to you or any one you like\n> >\n> > > +#include \"controls.h\"\n> > > +\n> > > +#include <linux/v4l2-controls.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/control_ids.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > +\n> > > +/*\n> > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > + * doing any conversion to the ControlValue.\n> > > + */\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > +};\n> >\n> > If them map is meant for internal use only you can declare it in an\n> > anonymous namespace\n> >\n> > namsepace {\n> >         std::unordered_map<>...\n> >\n> > };\n> >\n> > namespace libcamera {\n> >\n> > };\n>\n> That's the same as static though?\n>\n> > > +\n> > > +/*\n> > > + * Convert from a libcamera control to a V4L2 control, optionally\n> > > also convert a\n> > > + * set of ControlValues.\n> > > + */\n> >\n> > We use doxygen for documenting the code. Please see other files as an\n> > example.\n> >\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values)\n> >\n> > Before looking at the implementation, let's reason a bit on the\n> > architecture.\n> >\n> > Is this control mapping valid for all platforms using the simple\n> > pipeline handler ?\n> >\n> > Is the device on which controls have to be applied the same for all\n> > platforms ?\n> >\n> > Should control handling be broken down to a platform specific\n> > component to be selected at compile time ?\n>\n> I'm not sure what you mean by platform here? Do you mean Linux vs\n> Android, x86 vs arm or PinePhone vs Librem 5?\n>\n> The way I see it, the simple pipeline is just a simple abstraction on\n> V4L2 and this is a simple conversion between V4L2 and libcamera\n> controls.\n>\n> > Also, I would like to see this implemented thorough a componenet with\n> > a single interface towards the pipeline handler rather than a raw set\n> > of helper functions. We can indeed help designing that once we have\n> > the previous question clarified.\n> >\n> > I'm not even sure this is the direction we want to got with the simple\n> > pipeline handler (platform-specific backends), and I would like to\n> > hear Laurent's opinion on this, but I see a potential for doing what\n> > we do with the android backend selection through the\n> > 'android_platform' build option.\n> >\n> >         option('android_platform',\n> >                 type : 'combo',\n> >                 choices : ['cros', 'generic'],\n> >                 value : 'generic',\n> >                 description : 'Select the Android platform to compile for')\n> >\n> > > +{\n> > > +    // Convert controls\n> > > +    if (v4l2_control) {\n> > > +        auto it = controlsToV4L2.find(control);\n> > > +        if (it == controlsToV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *v4l2_control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (control) {\n> > > +    case controls::AE_ENABLE:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > +        return true;\n> > > +    case controls::EXPOSURE_VALUE:\n> > > +    case controls::DIGITAL_GAIN:\n> > > +    case controls::ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)control_values[i].get<float>());\n> > > +        return true;\n> > > +    // Read only\n> > > +    case controls::AF_STATE:\n> > > +        return false;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] = control_values[i];\n> > > +        return true;\n> > > +    }\n> > > +\n> > > +}\n> > > +\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > +\n> > > +/*\n> > > + * Convert from a V4L2 control to a libcamera control, optionally\n> > > also convert a\n> > > + * set of ControlValues.\n> > > + */\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values)\n> > > +{\n> > > +    // Initialize the inverse of controlsToV4L2\n> > > +    if (controlsFromV4L2.empty()) {\n> > > +        for (const auto &v : controlsToV4L2) {\n> > > +            controlsFromV4L2[v.second] = v.first;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    // Convert control\n> > > +    if (control) {\n> > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > +        if (it == controlsFromV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (v4l2_control) {\n> > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > +        return true;\n> > > +    case V4L2_CID_EXPOSURE:\n> > > +    case V4L2_CID_GAIN:\n> > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > +        return true;\n> > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > +                break;\n> > > +            default:\n> > > +                LOG(SimplePipeline, Error)\n> > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > +                /*TODO: Log Error*/\n> > > +                return false;\n> > > +            }\n> > > +        }\n> > > +        return true;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] = v4l2_values[i];\n> > > +        return true;\n> > > +    }\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > > + * identifiers as well as all values.\n> > > + */\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > +{\n> > > +    ControlInfoMap::Map info_map;\n> > > +\n> > > +    for (const auto &pair : v4l2_info_map) {\n> > > +        unsigned int v4l2_control = pair.first->id();\n> > > +        const ControlInfo &v4l2_info = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue def;\n> > > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_info.def(), &def, 1))\n> > > +            continue;\n> > > +\n> > > +        const ControlId *control_id = controls::controls.at(control);\n> > > +\n> > > +        // ControlInfo has either a list of values or a minimum and\n> > > +        // maximum. This includes controls that have no values or are\n> > > +        // booleans.\n> > > +        ControlInfo info;\n> > > +        if (v4l2_info.values().empty()) {\n> > > +            ControlValue min, max;\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.min(), &min, 1);\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.max(), &max, 1);\n> > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > +        } else {\n> > > +            std::vector<ControlValue> values;\n> > > +            values.resize(v4l2_info.values().size());\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > v4l2_info.values().data(), values.data(), values.size());\n> > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > +        }\n> > > +        info_map.emplace(control_id, std::move(info));\n> > > +    }\n> > > +\n> > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a control list from libcamera to V4L2.\n> > > + */\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > +{\n> > > +    ControlList v4l2_controls;\n> > > +    for (const auto &pair : controls) {\n> > > +        unsigned int control = pair.first;\n> > > +        const ControlValue &value = pair.second;\n> > > +\n> > > +        unsigned int v4l2_control;\n> > > +        ControlValue v4l2_value;\n> > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > > &v4l2_value, 1)) {\n> > > +            LOG(SimplePipeline, Warning)\n> > > +                << \"Control \" << utils::hex(control)\n> > > +                << \" does not have a V4L2 equivalent\";\n> > > +            continue;\n> > > +        }\n> > > +\n> > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > +    }\n> > > +    return v4l2_controls;\n> > > +}\n> > > +\n> > > +/*\n> > > + * Convert a control list from V4L2 to libcamera.\n> > > + */\n> > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > +{\n> > > +    ControlList controls;\n> > > +    for (const auto &pair : v4l2_controls) {\n> > > +        unsigned int v4l2_control = pair.first;\n> > > +        const ControlValue &v4l2_value = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue value;\n> > > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_value, &value, 1))\n> > > +            controls.set(control, value);\n> > > +    }\n> > > +    return controls;\n> > > +}\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > b/src/libcamera/pipeline/simple/controls.h\n> > > new file mode 100644\n> > > index 00000000..114c5fc2\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.h\n> >\n> > Same comment, license and copyright\n> >\n> > > @@ -0,0 +1,26 @@\n> > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> >\n> > #pragma once\n>\n> All the code I've seen uses #ifndef instead of #pragma once. I'd\n> prefer to use #pragma once but it seems inconsistent with the rest of\n> the codebase?\n>\n> > > +\n> > > +#include <libcamera/controls.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values);\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values);\n> > > +\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > +\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > +\n> > > +} /* namespace libcamera */\n> > > +\n> > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > b/src/libcamera/pipeline/simple/meson.build\n> > > index 9c99b32f..0c60d65a 100644\n> > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > @@ -1,6 +1,7 @@\n> > >  # SPDX-License-Identifier: CC0-1.0\n> > >\n> > >  libcamera_sources += files([\n> > > +    'controls.cpp',\n> > >      'converter.cpp',\n> > >      'simple.cpp',\n> > >  ])\n> > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > index a597e27f..b0d4a62a 100644\n> > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > @@ -37,6 +37,7 @@\n> > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > >\n> > >  #include \"converter.h\"\n> > > +#include \"controls.h\"\n> > >\n> > >  namespace libcamera {\n> > >\n> > > @@ -181,6 +182,7 @@ public:\n> > >      int setupLinks();\n> > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > >               V4L2Subdevice::Whence whence);\n> > > +    int setRequestControls(Request *request);\n> > >      void bufferReady(FrameBuffer *buffer);\n> > >\n> > >      unsigned int streamIndex(const Stream *stream) const\n> > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > >              formats_[fmt] = &config;\n> > >      }\n> > >\n> > > -    properties_ = sensor_->properties();\n> > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > >\n> > >      return 0;\n> > >  }\n> > > @@ -624,6 +627,23 @@ int\n> > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > >      return 0;\n> > >  }\n> > >\n> > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > +{\n> > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > +\n> > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > +    V4L2Device *control_device = video_;\n> > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > +        if (subdev) {\n> > > +            control_device = subdev;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > +    return control_device->setControls(&controls);\n> > > +}\n> > > +\n> > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >  {\n> > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >          return;\n> > >      }\n> > >\n> > > +    // Set the controls for the next queued request\n> > > +    if (!queuedRequests_.empty())\n> > > +        setRequestControls(queuedRequests_.front());\n> > > +\n> > >      /*\n> > >       * Record the sensor's timestamp in the request metadata. The request\n> > >       * needs to be obtained from the user-facing buffer, as internal\n> > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > *camera, [[maybe_unused]] const ControlL\n> > >          return ret;\n> > >      }\n> > >\n> > > +    // Apply controls from first request\n> > > +    if (!data->queuedRequests_.empty())\n> > > +        data->setRequestControls(data->queuedRequests_.front());\n> > > +\n> > >      if (data->useConverter_) {\n> > >          ret = data->converter_->start();\n> > >          if (ret < 0) {\n> > > --\n> > > 2.25.1\n>\n> [PATCH] libcamera: pipeline: simple: Add support for controls\n>\n> Controls and control info are translated between libcamera and V4L2\n> inside the simple pipeline. Request controls are applied when a request\n> is next to be completed.\n>\n> This also adds some additional draft controls needed for the PinePhone.\n>\n> Bug: https://bugs.libcamera.org/show_bug.cgi?id=98\n> Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> ---\n>  src/libcamera/control_ids.yaml             |  24 ++\n>  src/libcamera/pipeline/simple/controls.cpp | 241 +++++++++++++++++++++\n>  src/libcamera/pipeline/simple/controls.h   |  33 +++\n>  src/libcamera/pipeline/simple/meson.build  |   1 +\n>  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n>  src/libcamera/v4l2_device.cpp              |  12 +-\n>  6 files changed, 339 insertions(+), 2 deletions(-)\n>  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n>  create mode 100644 src/libcamera/pipeline/simple/controls.h\n>\n> diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> index 9d4638ae..2af230c3 100644\n> --- a/src/libcamera/control_ids.yaml\n> +++ b/src/libcamera/control_ids.yaml\n> @@ -406,6 +406,30 @@ controls:\n>              The camera will cancel any active or completed metering sequence.\n>              The AE algorithm is reset to its initial state.\n>\n> +  - AutoGain:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> +\n> +  - AfEnabled:\n> +      type: bool\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> +\n> +  - AfStart:\n> +      type: void\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> +\n> +  - AfStop:\n> +      type: void\n> +      draft: true\n> +      description: |\n> +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> +\n>    - AfTrigger:\n>        type: int32_t\n>        draft: true\n> diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> b/src/libcamera/pipeline/simple/controls.cpp\n> new file mode 100644\n> index 00000000..f3922e45\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.cpp\n> @@ -0,0 +1,241 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Benjamin Schaaf\n> + *\n> + * controls.cpp - Simple pipeline control conversion\n> + */\n> +\n> +#include \"controls.h\"\n> +\n> +#include <linux/v4l2-controls.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/control_ids.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(SimplePipeline)\n> +\n> +/*\n> + * These controls can be directly mapped between libcamera and V4L2 without\n> + * doing any conversion to the ControlValue.\n> + */\n> +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> +};\n> +\n> +/**\n> + * \\brief Convert from a libcamera control to a V4L2 control.\n> + *\n> + * Can optionally convert the libcamera control and/or a set of libcamera\n> + * control values to their V4L2 equivalents.\n> + */\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values)\n> +{\n> +    // Convert controls\n> +    if (v4l2_control) {\n> +        auto it = controlsToV4L2.find(control);\n> +        if (it == controlsToV4L2.end())\n> +            return false;\n> +\n> +        *v4l2_control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (control) {\n> +    case controls::AE_ENABLE:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)(control_values[i].get<bool>() ?\n> V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> +        return true;\n> +    case controls::EXPOSURE_VALUE:\n> +    case controls::DIGITAL_GAIN:\n> +    case controls::ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] =\n> ControlValue((int32_t)control_values[i].get<float>());\n> +        return true;\n> +    // Read only\n> +    case controls::AF_STATE:\n> +        return false;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            v4l2_values[i] = control_values[i];\n> +        return true;\n> +    }\n> +}\n> +\n> +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> +\n> +/**\n> + * \\brief Convert from a V4L2 control to a libcamera control.\n> + *\n> + * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n> + * to their libcamera equivalents.\n> + */\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values)\n> +{\n> +    // Initialize the inverse of controlsToV4L2\n> +    if (controlsFromV4L2.empty()) {\n> +        for (const auto &v : controlsToV4L2) {\n> +            controlsFromV4L2[v.second] = v.first;\n> +        }\n> +    }\n> +\n> +    // Convert control\n> +    if (control) {\n> +        auto it = controlsFromV4L2.find(v4l2_control);\n> +        if (it == controlsFromV4L2.end())\n> +            return false;\n> +\n> +        *control = it->second;\n> +    }\n> +\n> +    // Convert values\n> +    if (num_values == 0)\n> +        return true;\n> +\n> +    switch (v4l2_control) {\n> +    case V4L2_CID_EXPOSURE_AUTO:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> +        return true;\n> +    case V4L2_CID_EXPOSURE:\n> +    case V4L2_CID_GAIN:\n> +    case V4L2_CID_ANALOGUE_GAIN:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] =\n> ControlValue((float)v4l2_values[i].get<int32_t>());\n> +        return true;\n> +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> +        for (size_t i = 0; i < num_values; ++i) {\n> +            switch (v4l2_values[i].get<int32_t>()) {\n> +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateInactive);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> +                break;\n> +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> +                control_values[i] =\n> ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> +                break;\n> +            default:\n> +                LOG(SimplePipeline, Error)\n> +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> +                /*TODO: Log Error*/\n> +                return false;\n> +            }\n> +        }\n> +        return true;\n> +    default:\n> +        for (size_t i = 0; i < num_values; ++i)\n> +            control_values[i] = v4l2_values[i];\n> +        return true;\n> +    }\n> +}\n> +\n> +/**\n> + * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n> + *\n> + * Converts both the control identifiers as well as all values.\n> + */\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> +{\n> +    ControlInfoMap::Map info_map;\n> +\n> +    for (const auto &pair : v4l2_info_map) {\n> +        unsigned int v4l2_control = pair.first->id();\n> +        const ControlInfo &v4l2_info = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue def;\n> +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_info.def(), &def, 1))\n> +            continue;\n> +\n> +        const ControlId *control_id = controls::controls.at(control);\n> +\n> +        // ControlInfo has either a list of values or a minimum and\n> +        // maximum. This includes controls that have no values or are\n> +        // booleans.\n> +        ControlInfo info;\n> +        if (v4l2_info.values().empty()) {\n> +            ControlValue min, max;\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.min(), &min, 1);\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> &v4l2_info.max(), &max, 1);\n> +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> +        } else {\n> +            std::vector<ControlValue> values;\n> +            values.resize(v4l2_info.values().size());\n> +            simpleControlFromV4L2(v4l2_control, nullptr,\n> v4l2_info.values().data(), values.data(), values.size());\n> +            info = ControlInfo(std::move(values), std::move(def));\n> +        }\n> +        info_map.emplace(control_id, std::move(info));\n> +    }\n> +\n> +    return ControlInfoMap(std::move(info_map), controls::controls);\n> +}\n> +\n> +/**\n> + * \\brief Convert a control list from libcamera to V4L2.\n> + */\n> +ControlList simpleControlListToV4L2(const ControlList &controls)\n> +{\n> +    ControlList v4l2_controls;\n> +    for (const auto &pair : controls) {\n> +        unsigned int control = pair.first;\n> +        const ControlValue &value = pair.second;\n> +\n> +        unsigned int v4l2_control;\n> +        ControlValue v4l2_value;\n> +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> &v4l2_value, 1)) {\n> +            LOG(SimplePipeline, Warning)\n> +                << \"Control \" << utils::hex(control)\n> +                << \" does not have a V4L2 equivalent\";\n> +            continue;\n> +        }\n> +\n> +        v4l2_controls.set(v4l2_control, v4l2_value);\n> +    }\n> +    return v4l2_controls;\n> +}\n> +\n> +/**\n> + * \\brief Convert a control list from V4L2 to libcamera.\n> + */\n> +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> +{\n> +    ControlList controls;\n> +    for (const auto &pair : v4l2_controls) {\n> +        unsigned int v4l2_control = pair.first;\n> +        const ControlValue &v4l2_value = pair.second;\n> +\n> +        unsigned int control;\n> +        ControlValue value;\n> +        if (simpleControlFromV4L2(v4l2_control, &control,\n> &v4l2_value, &value, 1))\n> +            controls.set(control, value);\n> +    }\n> +    return controls;\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/simple/controls.h\n> b/src/libcamera/pipeline/simple/controls.h\n> new file mode 100644\n> index 00000000..54b4a565\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/controls.h\n> @@ -0,0 +1,33 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Benjamin Schaaf\n> + *\n> + * controls.h - Simple pipeline control conversion\n> + */\n> +\n> +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> +\n> +#include <libcamera/controls.h>\n> +\n> +namespace libcamera {\n> +\n> +bool simpleControlToV4L2(unsigned int control,\n> +             unsigned int *v4l2_control,\n> +             const ControlValue *control_values,\n> +             ControlValue *v4l2_values,\n> +             size_t num_values);\n> +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> +               unsigned int *control,\n> +               const ControlValue *v4l2_values,\n> +               ControlValue *control_values,\n> +               size_t num_values);\n> +\n> +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> +\n> +ControlList simpleControlListToV4L2(const ControlList &controls);\n> +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> +\n> +} /* namespace libcamera */\n> +\n> +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> diff --git a/src/libcamera/pipeline/simple/meson.build\n> b/src/libcamera/pipeline/simple/meson.build\n> index 9c99b32f..0c60d65a 100644\n> --- a/src/libcamera/pipeline/simple/meson.build\n> +++ b/src/libcamera/pipeline/simple/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libcamera_sources += files([\n> +    'controls.cpp',\n>      'converter.cpp',\n>      'simple.cpp',\n>  ])\n> diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> b/src/libcamera/pipeline/simple/simple.cpp\n> index a597e27f..1717a1a7 100644\n> --- a/src/libcamera/pipeline/simple/simple.cpp\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -36,6 +36,7 @@\n>  #include \"libcamera/internal/v4l2_subdevice.h\"\n>  #include \"libcamera/internal/v4l2_videodevice.h\"\n>\n> +#include \"controls.h\"\n>  #include \"converter.h\"\n>\n>  namespace libcamera {\n> @@ -181,6 +182,7 @@ public:\n>      int setupLinks();\n>      int setupFormats(V4L2SubdeviceFormat *format,\n>               V4L2Subdevice::Whence whence);\n> +    int setRequestControls(Request *request);\n>      void bufferReady(FrameBuffer *buffer);\n>\n>      unsigned int streamIndex(const Stream *stream) const\n> @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n>              formats_[fmt] = &config;\n>      }\n>\n> -    properties_ = sensor_->properties();\n> +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n>\n>      return 0;\n>  }\n> @@ -624,6 +627,23 @@ int\n> SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n>      return 0;\n>  }\n>\n> +int SimpleCameraData::setRequestControls(Request *request)\n> +{\n> +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> +\n> +    // Apply controls only to one entity. If there's a subdevice use that.\n> +    V4L2Device *control_device = video_;\n> +    for (const SimpleCameraData::Entity &e : entities_) {\n> +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> +        if (subdev) {\n> +            control_device = subdev;\n> +        }\n> +    }\n> +\n> +    ControlList controls = simpleControlListToV4L2(request->controls());\n> +    return control_device->setControls(&controls);\n> +}\n> +\n>  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>  {\n>      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n>          return;\n>      }\n>\n> +    // Set the controls for the next queued request\n> +    if (!queuedRequests_.empty())\n> +        setRequestControls(queuedRequests_.front());\n> +\n>      /*\n>       * Record the sensor's timestamp in the request metadata. The request\n>       * needs to be obtained from the user-facing buffer, as internal\n> @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> *camera, [[maybe_unused]] const ControlL\n>          return ret;\n>      }\n>\n> +    // Apply controls from first request\n> +    if (!data->queuedRequests_.empty())\n> +        data->setRequestControls(data->queuedRequests_.front());\n> +\n>      if (data->useConverter_) {\n>          ret = data->converter_->start();\n>          if (ret < 0) {\n> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\n> index 9c783c9c..2a15ae09 100644\n> --- a/src/libcamera/v4l2_device.cpp\n> +++ b/src/libcamera/v4l2_device.cpp\n> @@ -296,6 +296,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n>          /* Set the v4l2_ext_control value for the write operation. */\n>          ControlValue &value = ctrl->second;\n>          switch (iter->first->type()) {\n> +        case ControlTypeBool:\n> +            v4l2Ctrl.value64 = value.get<bool>();\n> +            break;\n> +\n> +        case ControlTypeNone:\n> +            break;\n> +\n>          case ControlTypeInteger64:\n>              v4l2Ctrl.value64 = value.get<int64_t>();\n>              break;\n> @@ -479,7 +486,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n>          return ControlTypeInteger64;\n>\n>      case V4L2_CTRL_TYPE_MENU:\n> -    case V4L2_CTRL_TYPE_BUTTON:\n>      case V4L2_CTRL_TYPE_BITMASK:\n>      case V4L2_CTRL_TYPE_INTEGER_MENU:\n>          /*\n> @@ -488,6 +494,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n>           */\n>          return ControlTypeInteger32;\n>\n> +    case V4L2_CTRL_TYPE_BUTTON:\n>      default:\n>          return ControlTypeNone;\n>      }\n> @@ -530,6 +537,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\n> v4l2_query_ext_ctrl &ctrl)\n>                     static_cast<int64_t>(ctrl.maximum),\n>                     static_cast<int64_t>(ctrl.default_value));\n>\n> +    case V4L2_CTRL_TYPE_BUTTON:\n> +        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n> +\n>      case V4L2_CTRL_TYPE_INTEGER_MENU:\n>      case V4L2_CTRL_TYPE_MENU:\n>          return v4l2MenuControlInfo(ctrl);\n> --\n> 2.25.1","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id DD3B1BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 12:12:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1DB3C60889;\n\tThu,  9 Dec 2021 13:12:15 +0100 (CET)","from mail-wr1-x432.google.com (mail-wr1-x432.google.com\n\t[IPv6:2a00:1450:4864:20::432])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C0C78607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 13:12:13 +0100 (CET)","by mail-wr1-x432.google.com with SMTP id t9so9328486wrx.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 09 Dec 2021 04:12:13 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"L+OQmsre\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=mime-version:references:in-reply-to:from:date:message-id:subject:to\n\t:cc; bh=GZH9/qp5HrMGNxrbd1zrhnV5Miqx1aFjqatjlxrfmo4=;\n\tb=L+OQmsreDzpkkJ6ZWCl9UxFxaU1Vi1XWRCU0DzotGZu1e1R124Sr0GjGniLsysTreS\n\tTpfrIlM6KyTgzSeh/XY7QLeeNRKWl896Fzag8gAjTIVuhf4aiKVjDhRyrK59EMvZM0+v\n\toEfNfBjkEDKNirbE8SXN+s2wsLmfH2kOHp/ZqDAEmLqiPbI3n2QLT8n+9dYfANuA5zyT\n\tm0omRtaZ0pHFnV8MdwUgfe92DbdyPA4c8gFeaRe8bm7hFdnIzbEOv9uiO0+7TQW+PIxY\n\tR2ERgCoQa36547UiMlzxUz+m1Q5b0hpJJfBqAesqfs5Kfin1UKJ7sxb4k3diyDJC+5my\n\tqBaw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=GZH9/qp5HrMGNxrbd1zrhnV5Miqx1aFjqatjlxrfmo4=;\n\tb=mPW7pSdXRgHkI6LUmJYMe+r8xvO21/h0AseaBxJf7YYnQLSo4CHW47UceZZ+EZhGcj\n\tKmO4bkJ2bv0urPnKnE8h24DoVe1erbQiHpUZEii+OsmYbkY6FnROhG5+qNcDs9SB4wpT\n\t/b+WwqUSPXt+JKSVzP93DFBSJ+xoJOUt/zAMEfA5VGsqvPakG1ejiGooQVOeBRjEc+Ai\n\tCwF/zWmEdCNua4Y7T2e0lo238SPeKY6T8gcXO8CVHY6RTqSqNB7o+VD1VjS2/7qC3y3e\n\tbO/Iud1dke1NiXzVnNM5swtoV9N+ZkGVlPIs8/Sf/KWjSY8Hz3jQlP1jYGKNBHV2nX58\n\tCvIg==","X-Gm-Message-State":"AOAM5300GDotQ2FL8gzAHkH3GGe6ah79isoby+DXoWIjzw76EPvBKglZ\n\tWAEmxRSvQ+anuZtTqGw2YUXpSYqXZNjEIQZ6CqWJhQ==","X-Google-Smtp-Source":"ABdhPJzn4+TVMKhD+t7ESGYkMbyn8Mv9PellpBsoIE/O3TAS4dWTMHXZJ4T/B6WJvBm+96onu/yMZFJAhIvCdk/tBc4=","X-Received":"by 2002:adf:f44e:: with SMTP id f14mr6125609wrp.37.1639051932816;\n\tThu, 09 Dec 2021 04:12:12 -0800 (PST)","MIME-Version":"1.0","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>\n\t<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","In-Reply-To":"<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Thu, 9 Dec 2021 12:12:02 +0000","Message-ID":"<CAHW6GYLKOq98+DUGZ5Vhaasqqh5O7rAO2_ZEGP5Ur=VivmJymA@mail.gmail.com>","To":"Benjamin Schaaf <ben.schaaf@gmail.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera devel <libcamera-devel@lists.libcamera.org>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21713,"web_url":"https://patchwork.libcamera.org/comment/21713/","msgid":"<20211209123512.xmxsbd2wh4bli4dv@uno.localdomain>","date":"2021-12-09T12:35:12","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hello,\n\nOn Thu, Dec 09, 2021 at 10:57:43PM +1100, Benjamin Schaaf wrote:\n> On Thu, Dec 9, 2021 at 10:33 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n> >\n> > Hi Benjamin\n> >\n> > On Thu, Dec 09, 2021 at 09:55:33PM +1100, Benjamin Schaaf wrote:\n> > > Thanks for the review, I'll put the updated patch at the end.\n> >\n> > Please don't :)\n> >\n> > Always send a new patch for new versions!\n>\n> No worries, I'll do that.\n>\n> > >\n> > > On Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi <jacopo@jmondi.org> wrote:\n> > > >\n> > > > Hello Benjamin,\n> > > >\n> > > > On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > > > > Controls and control info are translated between libcamera and V4L2\n> > > > > inside the simple pipeline. Request controls are applied when a request\n> > > > > is next to be completed.\n> > > > >\n> > > > > This also adds some additional draft controls needed for the PinePhone.\n> > > >\n> > > > Sorry to get straight to this before looking at the patch content, but\n> > > > we do enforce a code style as reported here\n> > > > https://libcamera.org/coding-style.html#coding-style-guidelines\n> > > >\n> > > > We have a tool that might help you catching style issues at\n> > > > utils/checkstyle.py\n> > > >\n> > > > Care to reformat your patches to comply with the project code style ?\n> > > >\n> > > > >\n> > > > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > > > ---\n> > > > >  src/libcamera/control_ids.yaml             |  24 +++\n> > > > >  src/libcamera/controls.cpp                 |   6 -\n> > > > >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> > > > >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> > > > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > > > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > > > >  6 files changed, 310 insertions(+), 7 deletions(-)\n> > > > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > > > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > > > >\n> > > > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > > > index 9d4638ae..2af230c3 100644\n> > > > > --- a/src/libcamera/control_ids.yaml\n> > > > > +++ b/src/libcamera/control_ids.yaml\n> > > > > @@ -406,6 +406,30 @@ controls:\n> > > > >              The camera will cancel any active or completed metering sequence.\n> > > > >              The AE algorithm is reset to its initial state.\n> > > > >\n> > > > > +  - AutoGain:\n> > > > > +      type: bool\n> > > > > +      draft: true\n> > > > > +      description: |\n> > > > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > > > > +\n> > > > > +  - AfEnabled:\n> > > > > +      type: bool\n> > > > > +      draft: true\n> > > > > +      description: |\n> > > > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > > > > +\n> > > > > +  - AfStart:\n> > > > > +      type: void\n> > > >\n> > > > Why type void ? Isn't this a boolean ?\n> > >\n> > > V4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\n> > > performs an action when the control is set. Thus type void. Same for\n> > > V4L2_CID_AUTO_FOCUS_END.\n> > >\n> > > It seems I forgot to include some required type conversion logic in\n> > > v4l2_device, not sure how that got missed.\n> >\n> > So you use void to indicate that we don't care about the value but the\n> > control presence signify that, in example, the autofocus routine\n> > should be started.\n> >\n> > We don't have 'one shot' control so far (the assumption is that once a\n> > control is set to a value, the value stays the same until it's not\n> > updated), and this could be a valid usage of type: void indeed\n> >\n> > >\n> > > > > +      draft: true\n> > > > > +      description: |\n> > > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > > > +\n> > > > > +  - AfStop:\n> > > > > +      type: void\n> > > > > +      draft: true\n> > > > > +      description: |\n> > > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > > > +\n> > > >\n> > > > We're in the process of reworking controls related to gain and focus,\n> > > > but for the moment, as we comply with Android by having their controls\n> > > > defined as draft, I'm not opposed to have these here.\n> > > >\n> > > > I'm worried once we have applications use them, we will never move to\n> > > > the newly defined ones though unless we forcefully remove them in\n> > > > future...\n> > >\n> > > FWIW given that libcamera doesn't have versions/releases I don't\n> > > personally expect a stable API.\n> > >\n> >\n> > In the long term, draft control will be replaced by standard controls.\n> > What I'm afraid of is that if applications start relying on draft\n> > controls it will be harder to remove them, as it will likely require a\n> > different type of mapping. But I have no better suggestions to provide\n> > atm\n> >\n> > > > >    - AfTrigger:\n> > > > >        type: int32_t\n> > > > >        draft: true\n> > > > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > > > > index 0d8c0a5c..1f65fc73 100644\n> > > > > --- a/src/libcamera/controls.cpp\n> > > > > +++ b/src/libcamera/controls.cpp\n> > > > > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > > int id) const\n> > > > >  {\n> > > > >      const auto iter = controls_.find(id);\n> > > > >      if (iter == controls_.end()) {\n> > > > > -        LOG(Controls, Error)\n> > > > > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > > > > -\n> > > >\n> > > > Ouch, why remove the error message ?\n> > >\n> > > My bad, seems I was misinterpreting when that error could show up.\n> > > I'll add it back in.\n> > >\n> > > > >          return nullptr;\n> > > > >      }\n> > > > >\n> > > > > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > > int id) const\n> > > > >  ControlValue *ControlList::find(unsigned int id)\n> > > > >  {\n> > > > >      if (validator_ && !validator_->validate(id)) {\n> > > > > -        LOG(Controls, Error)\n> > > > > -            << \"Control \" << utils::hex(id)\n> > > > > -            << \" is not valid for \" << validator_->name();\n> > > > >          return nullptr;\n> > > > >      }\n> > > > >\n> > > > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > > > new file mode 100644\n> > > > > index 00000000..32695749\n> > > > > --- /dev/null\n> > > > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > > > @@ -0,0 +1,230 @@\n> > > >\n> > > > File license ? You can copy the SPDX header from other files and\n> > > > attribute the copyright to you or any one you like\n> > > >\n> > > > > +#include \"controls.h\"\n> > > > > +\n> > > > > +#include <linux/v4l2-controls.h>\n> > > > > +\n> > > > > +#include <libcamera/base/log.h>\n> > > > > +\n> > > > > +#include <libcamera/control_ids.h>\n> > > > > +\n> > > > > +namespace libcamera {\n> > > > > +\n> > > > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > > > +\n> > > > > +/*\n> > > > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > > > + * doing any conversion to the ControlValue.\n> > > > > + */\n> > > > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > > > +};\n> > > >\n> > > > If them map is meant for internal use only you can declare it in an\n> > > > anonymous namespace\n> > > >\n> > > > namsepace {\n> > > >         std::unordered_map<>...\n> > > >\n> > > > };\n> > > >\n> > > > namespace libcamera {\n> > > >\n> > > > };\n> > >\n> > > That's the same as static though?\n> > >\n> >\n> > Yes but we usually prefer anonymous namespaces... Not a big deal\n> > though.\n> >\n> > > > > +\n> > > > > +/*\n> > > > > + * Convert from a libcamera control to a V4L2 control, optionally\n> > > > > also convert a\n> > > > > + * set of ControlValues.\n> > > > > + */\n> > > >\n> > > > We use doxygen for documenting the code. Please see other files as an\n> > > > example.\n> > > >\n> > > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > > +             unsigned int *v4l2_control,\n> > > > > +             const ControlValue *control_values,\n> > > > > +             ControlValue *v4l2_values,\n> > > > > +             size_t num_values)\n> > > >\n> > > > Before looking at the implementation, let's reason a bit on the\n> > > > architecture.\n> > > >\n> > > > Is this control mapping valid for all platforms using the simple\n> > > > pipeline handler ?\n> > > >\n> > > > Is the device on which controls have to be applied the same for all\n> > > > platforms ?\n> > > >\n> > > > Should control handling be broken down to a platform specific\n> > > > component to be selected at compile time ?\n> > >\n> > > I'm not sure what you mean by platform here? Do you mean Linux vs\n> > > Android, x86 vs arm or PinePhone vs Librem 5?\n> >\n> > I mean the SoC.\n> >\n> > Simple is used (afaik) on imx6, imx8mq, allwinner, mediatek and\n> > possibly others.\n> >\n> > The mapping between v4l2 controls and libcamera controls does\n> > generically apply to all of them ? Surely not the values the controls\n> > transport, but if we assume the application knows what platform it\n> > operates on that's fine.\n>\n> I don't see how they wouldn't. The V4L2 controls are standardized in\n> the same way the android ones are. FWIW the controls aren't SoC\n> specific, they're sensor + SoC +  device-tree specific.\n>\n\nOh I wish they are. They're definition is, that's for sure, where to\napply them and how their values are interpreted much less so :)\n\nYou know, I think the foundamental issue here is that if for\nSoC-specific pipeline handlers we have some sort of control about how\nthe platform work and how the kernel infrastructure looks like, much\nless so can be assumed for simple.\n\nThe first example which comes to mind is two platforms that registers the\nsame control one on the sensor subdev the other on the video subdev.\nMaybe one requires only the sensor subdev to be operated, the other\ndemands the video device to be operated too to have the control set ?\nAm I overthinking this ?\n\nIn general, given that simple applies to many different platforms,\nwhere we have much less control on which kernel they run on, I'm a bit\nhesitant to assume anything generally applies and that's why I'm\npushing for a platform-specific backend. Maybe I'm over concerned.\nLet's see what Laurent and others think, maybe my argument is just\nmoot.\n\n> > >\n> > > The way I see it, the simple pipeline is just a simple abstraction on\n> > > V4L2 and this is a simple conversion between V4L2 and libcamera\n> > > controls.\n> >\n> > This is a bit of a stretch.\n> >\n> > What's happening here is that you defined controls equal to the V4L2\n> > ones, and have the app set the 'right' values for the sensor/platform\n> > in use (a 'gain' value does not have the same meaning between\n> > different sensors, in example).\n> >\n> > In 'regular' platforms with an ISP, an IPA etc the pipeline receives\n> > libcamera::controls and with the help of IPA and statistics computes\n> > the right v4l2 controls for the platform (the pipeline handler knows\n> > what platform it runs on, except for simple) and for the sensor\n> > through a set of CameraSensorHelpers that aid with the translation.\n> >\n> > Now we change the landscape a bit, and we assume the app knows what\n> > platforms it runs on, something that defeats the purpose of libcamera\n> > usage, but I understand there aren't may way around that to support\n> > your use case.\n> >\n> > As a pipeline handler is charge of:\n> >\n> > 1) Registering what control it supports to expose that to application\n> > 2) Translate libcamera control to v4l2 controls (not in your case)\n> > 3) Apply controls to the right device/subdevice at the right time (the\n> >   time when to set a control is not trivially calculated as most\n> >   controls have a delay and should be applied in advance)\n> >\n> > Now assuming point 2) is moved to the app in your case, point 1 and 3\n> > do not apply generically to all platforms using the simple pipeline\n> > handler. Some might support control the other does not support. Some\n> > might want to set a control to the video devices while others will\n> > apply the same control on the sensor subdev. It all very depends on\n> > the driver architecture of the SoC.\n> >\n> > Now, even for simple boolean/button controls like the ones you're dealing with,\n> > for which not much reasoning and translations are required, a platform\n> > specific component seems to be needed to address 1) and 3).\n> >\n> > Does it make sense to you ?\n>\n> In terms of registering what controls are exposed that's done\n> generically in SimpleCameraData::init right? As for when controls are\n\nOnly the ones available from the sensor in your implementation.\n\nI have no problems with that in general, I just wonder if this\nshouldn't be made specific to your platform. As an example\nlibcamera::controls relative to the CropRectangle might need to be\nregistered and those depends on the whole capture pipeline\nconfiguration, not just the sensor.\n\n> applied the assumption being made in libcamera is that they can be\n> applied before a frame no?\n\nAh well, applying them -after- a frame would not help much :)\n\nLong story short: as long as you don't care about per-frame control\nyou can apply a control more or less whenever. V4L2 has a weird way of\nhandling some controls (the way VBLANK and EXPOSURE inter-operates in\nexample requires you to prioritize updating the blankings to be able\nto apply the desired exposure) and some controls might take several\nframes to apply (VCM which has mechanical moving parts is the first example,\nbut also gains and exposure according to what I see in the delays\nregistered in example by RPi in src/ipa/raspberrypi/cam_helper_*).\nBut yes, assuming you don't care about precise controls handling,\napply a control as soon as you receive it might work to some extents.\n\nI was more concerned about where the control should be applied, video\ndevice, sensor subdevice, VCM subdev etc. This is platform dependent\nand again, simple works on many different platforms we're not in\ncontrol of and as soon as the list of supported contorls grows beyond\nthe ones you have defined here I'm afraid things might quickly go out\nof synch.\n\nAgain, maybe I'm over concerned, let's see what others think.\n\nThanks\n   j\n\n>\n> > >\n> > > > Also, I would like to see this implemented thorough a componenet with\n> > > > a single interface towards the pipeline handler rather than a raw set\n> > > > of helper functions. We can indeed help designing that once we have\n> > > > the previous question clarified.\n> > > >\n> > > > I'm not even sure this is the direction we want to got with the simple\n> > > > pipeline handler (platform-specific backends), and I would like to\n> > > > hear Laurent's opinion on this, but I see a potential for doing what\n> > > > we do with the android backend selection through the\n> > > > 'android_platform' build option.\n> > > >\n> > > >         option('android_platform',\n> > > >                 type : 'combo',\n> > > >                 choices : ['cros', 'generic'],\n> > > >                 value : 'generic',\n> > > >                 description : 'Select the Android platform to compile for')\n> > > >\n> > > > > +{\n> > > > > +    // Convert controls\n> > > > > +    if (v4l2_control) {\n> > > > > +        auto it = controlsToV4L2.find(control);\n> > > > > +        if (it == controlsToV4L2.end())\n> > > > > +            return false;\n> > > > > +\n> > > > > +        *v4l2_control = it->second;\n> > > > > +    }\n> > > > > +\n> > > > > +    // Convert values\n> > > > > +    if (num_values == 0)\n> > > > > +        return true;\n> > > > > +\n> > > > > +    switch (control) {\n> > > > > +    case controls::AE_ENABLE:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            v4l2_values[i] =\n> > > > > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > > > > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > > > +        return true;\n> > > > > +    case controls::EXPOSURE_VALUE:\n> > > > > +    case controls::DIGITAL_GAIN:\n> > > > > +    case controls::ANALOGUE_GAIN:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            v4l2_values[i] =\n> > > > > ControlValue((int32_t)control_values[i].get<float>());\n> > > > > +        return true;\n> > > > > +    // Read only\n> > > > > +    case controls::AF_STATE:\n> > > > > +        return false;\n> > > > > +    default:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            v4l2_values[i] = control_values[i];\n> > > > > +        return true;\n> > > > > +    }\n> > > > > +\n> > > > > +}\n> > > > > +\n> > > > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > > > +\n> > > > > +/*\n> > > > > + * Convert from a V4L2 control to a libcamera control, optionally\n> > > > > also convert a\n> > > > > + * set of ControlValues.\n> > > > > + */\n> > > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > > +               unsigned int *control,\n> > > > > +               const ControlValue *v4l2_values,\n> > > > > +               ControlValue *control_values,\n> > > > > +               size_t num_values)\n> > > > > +{\n> > > > > +    // Initialize the inverse of controlsToV4L2\n> > > > > +    if (controlsFromV4L2.empty()) {\n> > > > > +        for (const auto &v : controlsToV4L2) {\n> > > > > +            controlsFromV4L2[v.second] = v.first;\n> > > > > +        }\n> > > > > +    }\n> > > > > +\n> > > > > +    // Convert control\n> > > > > +    if (control) {\n> > > > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > > > +        if (it == controlsFromV4L2.end())\n> > > > > +            return false;\n> > > > > +\n> > > > > +        *control = it->second;\n> > > > > +    }\n> > > > > +\n> > > > > +    // Convert values\n> > > > > +    if (num_values == 0)\n> > > > > +        return true;\n> > > > > +\n> > > > > +    switch (v4l2_control) {\n> > > > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            control_values[i] =\n> > > > > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > > > +        return true;\n> > > > > +    case V4L2_CID_EXPOSURE:\n> > > > > +    case V4L2_CID_GAIN:\n> > > > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            control_values[i] =\n> > > > > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > > > +        return true;\n> > > > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > > > +                control_values[i] =\n> > > > > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > > > +                break;\n> > > > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > > > +                control_values[i] =\n> > > > > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > > > +                break;\n> > > > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > > > +                control_values[i] =\n> > > > > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > > > +                break;\n> > > > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > > > +                control_values[i] =\n> > > > > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > > > +                break;\n> > > > > +            default:\n> > > > > +                LOG(SimplePipeline, Error)\n> > > > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > > > +                /*TODO: Log Error*/\n> > > > > +                return false;\n> > > > > +            }\n> > > > > +        }\n> > > > > +        return true;\n> > > > > +    default:\n> > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > +            control_values[i] = v4l2_values[i];\n> > > > > +        return true;\n> > > > > +    }\n> > > > > +}\n> > > > > +\n> > > > > +/*\n> > > > > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > > > > + * identifiers as well as all values.\n> > > > > + */\n> > > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > > > +{\n> > > > > +    ControlInfoMap::Map info_map;\n> > > > > +\n> > > > > +    for (const auto &pair : v4l2_info_map) {\n> > > > > +        unsigned int v4l2_control = pair.first->id();\n> > > > > +        const ControlInfo &v4l2_info = pair.second;\n> > > > > +\n> > > > > +        unsigned int control;\n> > > > > +        ControlValue def;\n> > > > > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > > > > &v4l2_info.def(), &def, 1))\n> > > > > +            continue;\n> > > > > +\n> > > > > +        const ControlId *control_id = controls::controls.at(control);\n> > > > > +\n> > > > > +        // ControlInfo has either a list of values or a minimum and\n> > > > > +        // maximum. This includes controls that have no values or are\n> > > > > +        // booleans.\n> > > > > +        ControlInfo info;\n> > > > > +        if (v4l2_info.values().empty()) {\n> > > > > +            ControlValue min, max;\n> > > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > > &v4l2_info.min(), &min, 1);\n> > > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > > &v4l2_info.max(), &max, 1);\n> > > > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > > > +        } else {\n> > > > > +            std::vector<ControlValue> values;\n> > > > > +            values.resize(v4l2_info.values().size());\n> > > > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > > > v4l2_info.values().data(), values.data(), values.size());\n> > > > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > > > +        }\n> > > > > +        info_map.emplace(control_id, std::move(info));\n> > > > > +    }\n> > > > > +\n> > > > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > > > +}\n> > > > > +\n> > > > > +/*\n> > > > > + * Convert a control list from libcamera to V4L2.\n> > > > > + */\n> > > > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > > > +{\n> > > > > +    ControlList v4l2_controls;\n> > > > > +    for (const auto &pair : controls) {\n> > > > > +        unsigned int control = pair.first;\n> > > > > +        const ControlValue &value = pair.second;\n> > > > > +\n> > > > > +        unsigned int v4l2_control;\n> > > > > +        ControlValue v4l2_value;\n> > > > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > > > > &v4l2_value, 1)) {\n> > > > > +            LOG(SimplePipeline, Warning)\n> > > > > +                << \"Control \" << utils::hex(control)\n> > > > > +                << \" does not have a V4L2 equivalent\";\n> > > > > +            continue;\n> > > > > +        }\n> > > > > +\n> > > > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > > > +    }\n> > > > > +    return v4l2_controls;\n> > > > > +}\n> > > > > +\n> > > > > +/*\n> > > > > + * Convert a control list from V4L2 to libcamera.\n> > > > > + */\n> > > > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > > > +{\n> > > > > +    ControlList controls;\n> > > > > +    for (const auto &pair : v4l2_controls) {\n> > > > > +        unsigned int v4l2_control = pair.first;\n> > > > > +        const ControlValue &v4l2_value = pair.second;\n> > > > > +\n> > > > > +        unsigned int control;\n> > > > > +        ControlValue value;\n> > > > > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > > > > &v4l2_value, &value, 1))\n> > > > > +            controls.set(control, value);\n> > > > > +    }\n> > > > > +    return controls;\n> > > > > +}\n> > > > > +\n> > > > > +} /* namespace libcamera */\n> > > > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > > > b/src/libcamera/pipeline/simple/controls.h\n> > > > > new file mode 100644\n> > > > > index 00000000..114c5fc2\n> > > > > --- /dev/null\n> > > > > +++ b/src/libcamera/pipeline/simple/controls.h\n> > > >\n> > > > Same comment, license and copyright\n> > > >\n> > > > > @@ -0,0 +1,26 @@\n> > > > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > >\n> > > > #pragma once\n> > >\n> > > All the code I've seen uses #ifndef instead of #pragma once. I'd\n> > > prefer to use #pragma once but it seems inconsistent with the rest of\n> > > the codebase?\n> > >\n> >\n> > Not since\n> > https://patchwork.libcamera.org/project/libcamera/list/?series=2749&state=*\n> >\n> > > > > +\n> > > > > +#include <libcamera/controls.h>\n> > > > > +\n> > > > > +namespace libcamera {\n> > > > > +\n> > > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > > +             unsigned int *v4l2_control,\n> > > > > +             const ControlValue *control_values,\n> > > > > +             ControlValue *v4l2_values,\n> > > > > +             size_t num_values);\n> > > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > > +               unsigned int *control,\n> > > > > +               const ControlValue *v4l2_values,\n> > > > > +               ControlValue *control_values,\n> > > > > +               size_t num_values);\n> > > > > +\n> > > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > > > +\n> > > > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > > > +\n> > > > > +} /* namespace libcamera */\n> > > > > +\n> > > > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > > > b/src/libcamera/pipeline/simple/meson.build\n> > > > > index 9c99b32f..0c60d65a 100644\n> > > > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > > > @@ -1,6 +1,7 @@\n> > > > >  # SPDX-License-Identifier: CC0-1.0\n> > > > >\n> > > > >  libcamera_sources += files([\n> > > > > +    'controls.cpp',\n> > > > >      'converter.cpp',\n> > > > >      'simple.cpp',\n> > > > >  ])\n> > > > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > > > index a597e27f..b0d4a62a 100644\n> > > > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > > > @@ -37,6 +37,7 @@\n> > > > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > > > >\n> > > > >  #include \"converter.h\"\n> > > > > +#include \"controls.h\"\n> > > > >\n> > > > >  namespace libcamera {\n> > > > >\n> > > > > @@ -181,6 +182,7 @@ public:\n> > > > >      int setupLinks();\n> > > > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > > > >               V4L2Subdevice::Whence whence);\n> > > > > +    int setRequestControls(Request *request);\n> > > > >      void bufferReady(FrameBuffer *buffer);\n> > > > >\n> > > > >      unsigned int streamIndex(const Stream *stream) const\n> > > > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > > > >              formats_[fmt] = &config;\n> > > > >      }\n> > > > >\n> > > > > -    properties_ = sensor_->properties();\n> > > > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > > > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > > > >\n> > > > >      return 0;\n> > > > >  }\n> > > > > @@ -624,6 +627,23 @@ int\n> > > > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > > > >      return 0;\n> > > > >  }\n> > > > >\n> > > > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > > > +{\n> > > > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > > +\n> > > > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > > > +    V4L2Device *control_device = video_;\n> > > > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > > > +        if (subdev) {\n> > > > > +            control_device = subdev;\n> > > > > +        }\n> > > > > +    }\n> > > > > +\n> > > > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > > > +    return control_device->setControls(&controls);\n> > > > > +}\n> > > > > +\n> > > > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > > >  {\n> > > > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > > >          return;\n> > > > >      }\n> > > > >\n> > > > > +    // Set the controls for the next queued request\n> > > > > +    if (!queuedRequests_.empty())\n> > > > > +        setRequestControls(queuedRequests_.front());\n> > > > > +\n> > > > >      /*\n> > > > >       * Record the sensor's timestamp in the request metadata. The request\n> > > > >       * needs to be obtained from the user-facing buffer, as internal\n> > > > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > > > *camera, [[maybe_unused]] const ControlL\n> > > > >          return ret;\n> > > > >      }\n> > > > >\n> > > > > +    // Apply controls from first request\n> > > > > +    if (!data->queuedRequests_.empty())\n> > > > > +        data->setRequestControls(data->queuedRequests_.front());\n> > > > > +\n> > > > >      if (data->useConverter_) {\n> > > > >          ret = data->converter_->start();\n> > > > >          if (ret < 0) {\n> > > > > --\n> > > > > 2.25.1\n> > >\n> > > [PATCH] libcamera: pipeline: simple: Add support for controls\n> > >\n> > > Controls and control info are translated between libcamera and V4L2\n> > > inside the simple pipeline. Request controls are applied when a request\n> > > is next to be completed.\n> > >\n> > > This also adds some additional draft controls needed for the PinePhone.\n> > >\n> > > Bug: https://bugs.libcamera.org/show_bug.cgi?id=98\n> > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > ---\n> > >  src/libcamera/control_ids.yaml             |  24 ++\n> > >  src/libcamera/pipeline/simple/controls.cpp | 241 +++++++++++++++++++++\n> > >  src/libcamera/pipeline/simple/controls.h   |  33 +++\n> > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > >  src/libcamera/v4l2_device.cpp              |  12 +-\n> > >  6 files changed, 339 insertions(+), 2 deletions(-)\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > >\n> > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > index 9d4638ae..2af230c3 100644\n> > > --- a/src/libcamera/control_ids.yaml\n> > > +++ b/src/libcamera/control_ids.yaml\n> > > @@ -406,6 +406,30 @@ controls:\n> > >              The camera will cancel any active or completed metering sequence.\n> > >              The AE algorithm is reset to its initial state.\n> > >\n> > > +  - AutoGain:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n> > > +\n> > > +  - AfEnabled:\n> > > +      type: bool\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n> > > +\n> > > +  - AfStart:\n> > > +      type: void\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > +\n> > > +  - AfStop:\n> > > +      type: void\n> > > +      draft: true\n> > > +      description: |\n> > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > +\n> > >    - AfTrigger:\n> > >        type: int32_t\n> > >        draft: true\n> > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > new file mode 100644\n> > > index 00000000..f3922e45\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > @@ -0,0 +1,241 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Benjamin Schaaf\n> > > + *\n> > > + * controls.cpp - Simple pipeline control conversion\n> > > + */\n> > > +\n> > > +#include \"controls.h\"\n> > > +\n> > > +#include <linux/v4l2-controls.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/control_ids.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > +\n> > > +/*\n> > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > + * doing any conversion to the ControlValue.\n> > > + */\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > +};\n> > > +\n> > > +/**\n> > > + * \\brief Convert from a libcamera control to a V4L2 control.\n> > > + *\n> > > + * Can optionally convert the libcamera control and/or a set of libcamera\n> > > + * control values to their V4L2 equivalents.\n> > > + */\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values)\n> > > +{\n> > > +    // Convert controls\n> > > +    if (v4l2_control) {\n> > > +        auto it = controlsToV4L2.find(control);\n> > > +        if (it == controlsToV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *v4l2_control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (control) {\n> > > +    case controls::AE_ENABLE:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)(control_values[i].get<bool>() ?\n> > > V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > +        return true;\n> > > +    case controls::EXPOSURE_VALUE:\n> > > +    case controls::DIGITAL_GAIN:\n> > > +    case controls::ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] =\n> > > ControlValue((int32_t)control_values[i].get<float>());\n> > > +        return true;\n> > > +    // Read only\n> > > +    case controls::AF_STATE:\n> > > +        return false;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            v4l2_values[i] = control_values[i];\n> > > +        return true;\n> > > +    }\n> > > +}\n> > > +\n> > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > +\n> > > +/**\n> > > + * \\brief Convert from a V4L2 control to a libcamera control.\n> > > + *\n> > > + * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n> > > + * to their libcamera equivalents.\n> > > + */\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values)\n> > > +{\n> > > +    // Initialize the inverse of controlsToV4L2\n> > > +    if (controlsFromV4L2.empty()) {\n> > > +        for (const auto &v : controlsToV4L2) {\n> > > +            controlsFromV4L2[v.second] = v.first;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    // Convert control\n> > > +    if (control) {\n> > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > +        if (it == controlsFromV4L2.end())\n> > > +            return false;\n> > > +\n> > > +        *control = it->second;\n> > > +    }\n> > > +\n> > > +    // Convert values\n> > > +    if (num_values == 0)\n> > > +        return true;\n> > > +\n> > > +    switch (v4l2_control) {\n> > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > +        return true;\n> > > +    case V4L2_CID_EXPOSURE:\n> > > +    case V4L2_CID_GAIN:\n> > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] =\n> > > ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > +        return true;\n> > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > +                break;\n> > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > +                control_values[i] =\n> > > ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > +                break;\n> > > +            default:\n> > > +                LOG(SimplePipeline, Error)\n> > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > +                /*TODO: Log Error*/\n> > > +                return false;\n> > > +            }\n> > > +        }\n> > > +        return true;\n> > > +    default:\n> > > +        for (size_t i = 0; i < num_values; ++i)\n> > > +            control_values[i] = v4l2_values[i];\n> > > +        return true;\n> > > +    }\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n> > > + *\n> > > + * Converts both the control identifiers as well as all values.\n> > > + */\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > +{\n> > > +    ControlInfoMap::Map info_map;\n> > > +\n> > > +    for (const auto &pair : v4l2_info_map) {\n> > > +        unsigned int v4l2_control = pair.first->id();\n> > > +        const ControlInfo &v4l2_info = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue def;\n> > > +        if (!simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_info.def(), &def, 1))\n> > > +            continue;\n> > > +\n> > > +        const ControlId *control_id = controls::controls.at(control);\n> > > +\n> > > +        // ControlInfo has either a list of values or a minimum and\n> > > +        // maximum. This includes controls that have no values or are\n> > > +        // booleans.\n> > > +        ControlInfo info;\n> > > +        if (v4l2_info.values().empty()) {\n> > > +            ControlValue min, max;\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.min(), &min, 1);\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > &v4l2_info.max(), &max, 1);\n> > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > +        } else {\n> > > +            std::vector<ControlValue> values;\n> > > +            values.resize(v4l2_info.values().size());\n> > > +            simpleControlFromV4L2(v4l2_control, nullptr,\n> > > v4l2_info.values().data(), values.data(), values.size());\n> > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > +        }\n> > > +        info_map.emplace(control_id, std::move(info));\n> > > +    }\n> > > +\n> > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Convert a control list from libcamera to V4L2.\n> > > + */\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > +{\n> > > +    ControlList v4l2_controls;\n> > > +    for (const auto &pair : controls) {\n> > > +        unsigned int control = pair.first;\n> > > +        const ControlValue &value = pair.second;\n> > > +\n> > > +        unsigned int v4l2_control;\n> > > +        ControlValue v4l2_value;\n> > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n> > > &v4l2_value, 1)) {\n> > > +            LOG(SimplePipeline, Warning)\n> > > +                << \"Control \" << utils::hex(control)\n> > > +                << \" does not have a V4L2 equivalent\";\n> > > +            continue;\n> > > +        }\n> > > +\n> > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > +    }\n> > > +    return v4l2_controls;\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Convert a control list from V4L2 to libcamera.\n> > > + */\n> > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > +{\n> > > +    ControlList controls;\n> > > +    for (const auto &pair : v4l2_controls) {\n> > > +        unsigned int v4l2_control = pair.first;\n> > > +        const ControlValue &v4l2_value = pair.second;\n> > > +\n> > > +        unsigned int control;\n> > > +        ControlValue value;\n> > > +        if (simpleControlFromV4L2(v4l2_control, &control,\n> > > &v4l2_value, &value, 1))\n> > > +            controls.set(control, value);\n> > > +    }\n> > > +    return controls;\n> > > +}\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > b/src/libcamera/pipeline/simple/controls.h\n> > > new file mode 100644\n> > > index 00000000..54b4a565\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/simple/controls.h\n> > > @@ -0,0 +1,33 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Benjamin Schaaf\n> > > + *\n> > > + * controls.h - Simple pipeline control conversion\n> > > + */\n> > > +\n> > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > +\n> > > +#include <libcamera/controls.h>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +bool simpleControlToV4L2(unsigned int control,\n> > > +             unsigned int *v4l2_control,\n> > > +             const ControlValue *control_values,\n> > > +             ControlValue *v4l2_values,\n> > > +             size_t num_values);\n> > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > +               unsigned int *control,\n> > > +               const ControlValue *v4l2_values,\n> > > +               ControlValue *control_values,\n> > > +               size_t num_values);\n> > > +\n> > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > +\n> > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > +\n> > > +} /* namespace libcamera */\n> > > +\n> > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > b/src/libcamera/pipeline/simple/meson.build\n> > > index 9c99b32f..0c60d65a 100644\n> > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > @@ -1,6 +1,7 @@\n> > >  # SPDX-License-Identifier: CC0-1.0\n> > >\n> > >  libcamera_sources += files([\n> > > +    'controls.cpp',\n> > >      'converter.cpp',\n> > >      'simple.cpp',\n> > >  ])\n> > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > index a597e27f..1717a1a7 100644\n> > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > @@ -36,6 +36,7 @@\n> > >  #include \"libcamera/internal/v4l2_subdevice.h\"\n> > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > >\n> > > +#include \"controls.h\"\n> > >  #include \"converter.h\"\n> > >\n> > >  namespace libcamera {\n> > > @@ -181,6 +182,7 @@ public:\n> > >      int setupLinks();\n> > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > >               V4L2Subdevice::Whence whence);\n> > > +    int setRequestControls(Request *request);\n> > >      void bufferReady(FrameBuffer *buffer);\n> > >\n> > >      unsigned int streamIndex(const Stream *stream) const\n> > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > >              formats_[fmt] = &config;\n> > >      }\n> > >\n> > > -    properties_ = sensor_->properties();\n> > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n> > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > >\n> > >      return 0;\n> > >  }\n> > > @@ -624,6 +627,23 @@ int\n> > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > >      return 0;\n> > >  }\n> > >\n> > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > +{\n> > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > +\n> > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > +    V4L2Device *control_device = video_;\n> > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > +        if (subdev) {\n> > > +            control_device = subdev;\n> > > +        }\n> > > +    }\n> > > +\n> > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > +    return control_device->setControls(&controls);\n> > > +}\n> > > +\n> > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >  {\n> > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > >          return;\n> > >      }\n> > >\n> > > +    // Set the controls for the next queued request\n> > > +    if (!queuedRequests_.empty())\n> > > +        setRequestControls(queuedRequests_.front());\n> > > +\n> > >      /*\n> > >       * Record the sensor's timestamp in the request metadata. The request\n> > >       * needs to be obtained from the user-facing buffer, as internal\n> > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > *camera, [[maybe_unused]] const ControlL\n> > >          return ret;\n> > >      }\n> > >\n> > > +    // Apply controls from first request\n> > > +    if (!data->queuedRequests_.empty())\n> > > +        data->setRequestControls(data->queuedRequests_.front());\n> > > +\n> > >      if (data->useConverter_) {\n> > >          ret = data->converter_->start();\n> > >          if (ret < 0) {\n> > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\n> > > index 9c783c9c..2a15ae09 100644\n> > > --- a/src/libcamera/v4l2_device.cpp\n> > > +++ b/src/libcamera/v4l2_device.cpp\n> > > @@ -296,6 +296,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n> > >          /* Set the v4l2_ext_control value for the write operation. */\n> > >          ControlValue &value = ctrl->second;\n> > >          switch (iter->first->type()) {\n> > > +        case ControlTypeBool:\n> > > +            v4l2Ctrl.value64 = value.get<bool>();\n> > > +            break;\n> > > +\n> > > +        case ControlTypeNone:\n> > > +            break;\n> > > +\n> > >          case ControlTypeInteger64:\n> > >              v4l2Ctrl.value64 = value.get<int64_t>();\n> > >              break;\n> > > @@ -479,7 +486,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n> > >          return ControlTypeInteger64;\n> > >\n> > >      case V4L2_CTRL_TYPE_MENU:\n> > > -    case V4L2_CTRL_TYPE_BUTTON:\n> > >      case V4L2_CTRL_TYPE_BITMASK:\n> > >      case V4L2_CTRL_TYPE_INTEGER_MENU:\n> > >          /*\n> > > @@ -488,6 +494,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n> > >           */\n> > >          return ControlTypeInteger32;\n> > >\n> > > +    case V4L2_CTRL_TYPE_BUTTON:\n> > >      default:\n> > >          return ControlTypeNone;\n> > >      }\n> > > @@ -530,6 +537,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\n> > > v4l2_query_ext_ctrl &ctrl)\n> > >                     static_cast<int64_t>(ctrl.maximum),\n> > >                     static_cast<int64_t>(ctrl.default_value));\n> > >\n> > > +    case V4L2_CTRL_TYPE_BUTTON:\n> > > +        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n> > > +\n> > >      case V4L2_CTRL_TYPE_INTEGER_MENU:\n> > >      case V4L2_CTRL_TYPE_MENU:\n> > >          return v4l2MenuControlInfo(ctrl);\n> > > --\n> > > 2.25.1","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D1453BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 12:34:22 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0854D60882;\n\tThu,  9 Dec 2021 13:34:22 +0100 (CET)","from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net\n\t[217.70.183.198])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9D224607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 13:34:20 +0100 (CET)","(Authenticated sender: jacopo@jmondi.org)\n\tby relay6-d.mail.gandi.net (Postfix) with ESMTPSA id E63D9C000F;\n\tThu,  9 Dec 2021 12:34:19 +0000 (UTC)"],"Date":"Thu, 9 Dec 2021 13:35:12 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Benjamin Schaaf <ben.schaaf@gmail.com>","Message-ID":"<20211209123512.xmxsbd2wh4bli4dv@uno.localdomain>","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>\n\t<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>\n\t<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>\n\t<CAJ+kdVEyEYaOzrrFMHbm5RMfCQ6sP745M824WfLQAD6kkWGeiA@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAJ+kdVEyEYaOzrrFMHbm5RMfCQ6sP745M824WfLQAD6kkWGeiA@mail.gmail.com>","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21728,"web_url":"https://patchwork.libcamera.org/comment/21728/","msgid":"<YbKPP1qfbfir7BKB@pendragon.ideasonboard.com>","date":"2021-12-09T23:20:31","subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hello,\n\nOn Thu, Dec 09, 2021 at 01:35:12PM +0100, Jacopo Mondi wrote:\n> On Thu, Dec 09, 2021 at 10:57:43PM +1100, Benjamin Schaaf wrote:\n> > On Thu, Dec 9, 2021 at 10:33 PM Jacopo Mondi wrote:\n> > > On Thu, Dec 09, 2021 at 09:55:33PM +1100, Benjamin Schaaf wrote:\n> > > > Thanks for the review, I'll put the updated patch at the end.\n> > >\n> > > Please don't :)\n> > >\n> > > Always send a new patch for new versions!\n> >\n> > No worries, I'll do that.\n\nAnother small comment, when sending a new version, could you tag the\npatch(es) with the version number on the subject line ? There are plenty\nof examples on the list. The -v option to git format-patch can help\nthere.\n\n> > > > On Thu, Dec 9, 2021 at 7:57 PM Jacopo Mondi wrote:\n> > > > > On Wed, Dec 08, 2021 at 11:35:23PM +1100, Benjamin Schaaf wrote:\n> > > > > > Controls and control info are translated between libcamera and V4L2\n> > > > > > inside the simple pipeline. Request controls are applied when a request\n> > > > > > is next to be completed.\n> > > > > >\n> > > > > > This also adds some additional draft controls needed for the PinePhone.\n\nThis should likely be split in multiple patches:\n\n- V4L2_CTRL_TYPE_BUTTON support\n- New controls\n- Simple pipeline handler changes\n\n> > > > > Sorry to get straight to this before looking at the patch content, but\n> > > > > we do enforce a code style as reported here\n> > > > > https://libcamera.org/coding-style.html#coding-style-guidelines\n> > > > >\n> > > > > We have a tool that might help you catching style issues at\n> > > > > utils/checkstyle.py\n> > > > >\n> > > > > Care to reformat your patches to comply with the project code style ?\n\nRunning checkstyle.py can also be automated with a git commit hook, this\nis documented in coding-style.html too.\n\n> > > > > > Signed-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n> > > > > > ---\n> > > > > >  src/libcamera/control_ids.yaml             |  24 +++\n> > > > > >  src/libcamera/controls.cpp                 |   6 -\n> > > > > >  src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++\n> > > > > >  src/libcamera/pipeline/simple/controls.h   |  26 +++\n> > > > > >  src/libcamera/pipeline/simple/meson.build  |   1 +\n> > > > > >  src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n> > > > > >  6 files changed, 310 insertions(+), 7 deletions(-)\n> > > > > >  create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n> > > > > >  create mode 100644 src/libcamera/pipeline/simple/controls.h\n> > > > > >\n> > > > > > diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> > > > > > index 9d4638ae..2af230c3 100644\n> > > > > > --- a/src/libcamera/control_ids.yaml\n> > > > > > +++ b/src/libcamera/control_ids.yaml\n> > > > > > @@ -406,6 +406,30 @@ controls:\n> > > > > >              The camera will cancel any active or completed metering sequence.\n> > > > > >              The AE algorithm is reset to its initial state.\n> > > > > >\n> > > > > > +  - AutoGain:\n> > > > > > +      type: bool\n> > > > > > +      draft: true\n> > > > > > +      description: |\n> > > > > > +       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n\nControl of auto-exposure and auto-gain is under development. Patches are\navailable on the list, the latest version is\nhttps://lists.libcamera.org/pipermail/libcamera-devel/2021-October/025410.html\nif I'm not mistaken. Could you perhaps have a look and see if that would\ncover your use cases ?\n\n> > > > > > +\n> > > > > > +  - AfEnabled:\n> > > > > > +      type: bool\n> > > > > > +      draft: true\n> > > > > > +      description: |\n> > > > > > +       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n\nWe'll also work on this, but a draft control as a shortcut is OK to\nstart with.\n\n> > > > > > +  - AfStart:\n> > > > > > +      type: void\n> > > > >\n> > > > > Why type void ? Isn't this a boolean ?\n> > > >\n> > > > V4L2_CID_AUTO_FOCUS_START has type V4L2_CTRL_TYPE_BUTTON, which simply\n> > > > performs an action when the control is set. Thus type void. Same for\n> > > > V4L2_CID_AUTO_FOCUS_END.\n> > > >\n> > > > It seems I forgot to include some required type conversion logic in\n> > > > v4l2_device, not sure how that got missed.\n> > >\n> > > So you use void to indicate that we don't care about the value but the\n> > > control presence signify that, in example, the autofocus routine\n> > > should be started.\n> > >\n> > > We don't have 'one shot' control so far (the assumption is that once a\n> > > control is set to a value, the value stays the same until it's not\n> > > updated), and this could be a valid usage of type: void indeed\n\nInteresting topic. I'm tempted to propose splitting it out to allow\ndiscussing this without impacting the rest of the changes.\n\n> > > > > > +      draft: true\n> > > > > > +      description: |\n> > > > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n> > > > > > +\n> > > > > > +  - AfStop:\n> > > > > > +      type: void\n> > > > > > +      draft: true\n> > > > > > +      description: |\n> > > > > > +       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n> > > > > > +\n> > > > >\n> > > > > We're in the process of reworking controls related to gain and focus,\n> > > > > but for the moment, as we comply with Android by having their controls\n> > > > > defined as draft, I'm not opposed to have these here.\n> > > > >\n> > > > > I'm worried once we have applications use them, we will never move to\n> > > > > the newly defined ones though unless we forcefully remove them in\n> > > > > future...\n> > > >\n> > > > FWIW given that libcamera doesn't have versions/releases I don't\n> > > > personally expect a stable API.\n> > >\n> > > In the long term, draft control will be replaced by standard controls.\n> > > What I'm afraid of is that if applications start relying on draft\n> > > controls it will be harder to remove them, as it will likely require a\n> > > different type of mapping. But I have no better suggestions to provide\n> > > atm\n\nApplications should not rely on any part of the API being stable at the\nmoment :-)\n\nThis being said, don't those two controls duplicate AfTrigger ?\n\n> > > > > >    - AfTrigger:\n> > > > > >        type: int32_t\n> > > > > >        draft: true\n> > > > > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> > > > > > index 0d8c0a5c..1f65fc73 100644\n> > > > > > --- a/src/libcamera/controls.cpp\n> > > > > > +++ b/src/libcamera/controls.cpp\n> > > > > > @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > > > int id) const\n> > > > > >  {\n> > > > > >      const auto iter = controls_.find(id);\n> > > > > >      if (iter == controls_.end()) {\n> > > > > > -        LOG(Controls, Error)\n> > > > > > -            << \"Control \" << utils::hex(id) << \" not found\";\n> > > > > > -\n> > > > >\n> > > > > Ouch, why remove the error message ?\n> > > >\n> > > > My bad, seems I was misinterpreting when that error could show up.\n> > > > I'll add it back in.\n> > > >\n> > > > > >          return nullptr;\n> > > > > >      }\n> > > > > >\n> > > > > > @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned\n> > > > > > int id) const\n> > > > > >  ControlValue *ControlList::find(unsigned int id)\n> > > > > >  {\n> > > > > >      if (validator_ && !validator_->validate(id)) {\n> > > > > > -        LOG(Controls, Error)\n> > > > > > -            << \"Control \" << utils::hex(id)\n> > > > > > -            << \" is not valid for \" << validator_->name();\n> > > > > >          return nullptr;\n> > > > > >      }\n> > > > > >\n> > > > > > diff --git a/src/libcamera/pipeline/simple/controls.cpp\n> > > > > > b/src/libcamera/pipeline/simple/controls.cpp\n> > > > > > new file mode 100644\n> > > > > > index 00000000..32695749\n> > > > > > --- /dev/null\n> > > > > > +++ b/src/libcamera/pipeline/simple/controls.cpp\n> > > > > > @@ -0,0 +1,230 @@\n> > > > >\n> > > > > File license ? You can copy the SPDX header from other files and\n> > > > > attribute the copyright to you or any one you like\n> > > > >\n> > > > > > +#include \"controls.h\"\n> > > > > > +\n> > > > > > +#include <linux/v4l2-controls.h>\n> > > > > > +\n> > > > > > +#include <libcamera/base/log.h>\n> > > > > > +\n> > > > > > +#include <libcamera/control_ids.h>\n> > > > > > +\n> > > > > > +namespace libcamera {\n> > > > > > +\n> > > > > > +LOG_DECLARE_CATEGORY(SimplePipeline)\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * These controls can be directly mapped between libcamera and V4L2 without\n> > > > > > + * doing any conversion to the ControlValue.\n> > > > > > + */\n> > > > > > +static std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n> > > > > > +    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n> > > > > > +    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n> > > > > > +    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n> > > > > > +    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n> > > > > > +    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n> > > > > > +    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n> > > > > > +    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n> > > > > > +    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n> > > > > > +    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n> > > > > > +};\n> > > > >\n> > > > > If them map is meant for internal use only you can declare it in an\n> > > > > anonymous namespace\n> > > > >\n> > > > > namsepace {\n> > > > >         std::unordered_map<>...\n> > > > >\n> > > > > };\n> > > > >\n> > > > > namespace libcamera {\n> > > > >\n> > > > > };\n> > > >\n> > > > That's the same as static though?\n> > >\n> > > Yes but we usually prefer anonymous namespaces... Not a big deal\n> > > though.\n> > >\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * Convert from a libcamera control to a V4L2 control, optionally\n> > > > > > also convert a\n> > > > > > + * set of ControlValues.\n> > > > > > + */\n> > > > >\n> > > > > We use doxygen for documenting the code. Please see other files as an\n> > > > > example.\n> > > > >\n> > > > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > > > +             unsigned int *v4l2_control,\n> > > > > > +             const ControlValue *control_values,\n> > > > > > +             ControlValue *v4l2_values,\n> > > > > > +             size_t num_values)\n> > > > >\n> > > > > Before looking at the implementation, let's reason a bit on the\n> > > > > architecture.\n> > > > >\n> > > > > Is this control mapping valid for all platforms using the simple\n> > > > > pipeline handler ?\n> > > > >\n> > > > > Is the device on which controls have to be applied the same for all\n> > > > > platforms ?\n> > > > >\n> > > > > Should control handling be broken down to a platform specific\n> > > > > component to be selected at compile time ?\n> > > >\n> > > > I'm not sure what you mean by platform here? Do you mean Linux vs\n> > > > Android, x86 vs arm or PinePhone vs Librem 5?\n> > >\n> > > I mean the SoC.\n> > >\n> > > Simple is used (afaik) on imx6, imx8mq, allwinner, mediatek and\n> > > possibly others.\n> > >\n> > > The mapping between v4l2 controls and libcamera controls does\n> > > generically apply to all of them ? Surely not the values the controls\n> > > transport, but if we assume the application knows what platform it\n> > > operates on that's fine.\n> >\n> > I don't see how they wouldn't. The V4L2 controls are standardized in\n> > the same way the android ones are. FWIW the controls aren't SoC\n> > specific, they're sensor + SoC +  device-tree specific.\n> \n> Oh I wish they are. They're definition is, that's for sure, where to\n> apply them and how their values are interpreted much less so :)\n> \n> You know, I think the foundamental issue here is that if for\n> SoC-specific pipeline handlers we have some sort of control about how\n> the platform work and how the kernel infrastructure looks like, much\n> less so can be assumed for simple.\n> \n> The first example which comes to mind is two platforms that registers the\n> same control one on the sensor subdev the other on the video subdev.\n> Maybe one requires only the sensor subdev to be operated, the other\n> demands the video device to be operated too to have the control set ?\n> Am I overthinking this ?\n> \n> In general, given that simple applies to many different platforms,\n> where we have much less control on which kernel they run on, I'm a bit\n> hesitant to assume anything generally applies and that's why I'm\n> pushing for a platform-specific backend. Maybe I'm over concerned.\n> Let's see what Laurent and others think, maybe my argument is just\n> moot.\n\nWhen it comes to this patch at least, sensors seem to be only related to\nthe camera sensor. I don't see anything that will apply to the entities\non the SoC side. I would (at least for now) hardcode this fact, and\napply controls on the CameraSensor only, ignoring all other entities and\nvideo nodes.\n\n> > > >\n> > > > The way I see it, the simple pipeline is just a simple abstraction on\n> > > > V4L2 and this is a simple conversion between V4L2 and libcamera\n> > > > controls.\n> > >\n> > > This is a bit of a stretch.\n> > >\n> > > What's happening here is that you defined controls equal to the V4L2\n> > > ones, and have the app set the 'right' values for the sensor/platform\n> > > in use (a 'gain' value does not have the same meaning between\n> > > different sensors, in example).\n> > >\n> > > In 'regular' platforms with an ISP, an IPA etc the pipeline receives\n> > > libcamera::controls and with the help of IPA and statistics computes\n> > > the right v4l2 controls for the platform (the pipeline handler knows\n> > > what platform it runs on, except for simple) and for the sensor\n> > > through a set of CameraSensorHelpers that aid with the translation.\n> > >\n> > > Now we change the landscape a bit, and we assume the app knows what\n> > > platforms it runs on, something that defeats the purpose of libcamera\n> > > usage, but I understand there aren't may way around that to support\n> > > your use case.\n> > >\n> > > As a pipeline handler is charge of:\n> > >\n> > > 1) Registering what control it supports to expose that to application\n> > > 2) Translate libcamera control to v4l2 controls (not in your case)\n> > > 3) Apply controls to the right device/subdevice at the right time (the\n> > >   time when to set a control is not trivially calculated as most\n> > >   controls have a delay and should be applied in advance)\n> > >\n> > > Now assuming point 2) is moved to the app in your case, point 1 and 3\n> > > do not apply generically to all platforms using the simple pipeline\n> > > handler. Some might support control the other does not support. Some\n> > > might want to set a control to the video devices while others will\n> > > apply the same control on the sensor subdev. It all very depends on\n> > > the driver architecture of the SoC.\n> > >\n> > > Now, even for simple boolean/button controls like the ones you're dealing with,\n> > > for which not much reasoning and translations are required, a platform\n> > > specific component seems to be needed to address 1) and 3).\n> > >\n> > > Does it make sense to you ?\n> >\n> > In terms of registering what controls are exposed that's done\n> > generically in SimpleCameraData::init right? As for when controls are\n> \n> Only the ones available from the sensor in your implementation.\n> \n> I have no problems with that in general, I just wonder if this\n> shouldn't be made specific to your platform. As an example\n> libcamera::controls relative to the CropRectangle might need to be\n> registered and those depends on the whole capture pipeline\n> configuration, not just the sensor.\n> \n> > applied the assumption being made in libcamera is that they can be\n> > applied before a frame no?\n> \n> Ah well, applying them -after- a frame would not help much :)\n> \n> Long story short: as long as you don't care about per-frame control\n> you can apply a control more or less whenever. V4L2 has a weird way of\n> handling some controls (the way VBLANK and EXPOSURE inter-operates in\n> example requires you to prioritize updating the blankings to be able\n> to apply the desired exposure) and some controls might take several\n> frames to apply (VCM which has mechanical moving parts is the first example,\n> but also gains and exposure according to what I see in the delays\n> registered in example by RPi in src/ipa/raspberrypi/cam_helper_*).\n> But yes, assuming you don't care about precise controls handling,\n> apply a control as soon as you receive it might work to some extents.\n\nI think we could greatly improve the timings by applying controls in a\nV4L2Device::frameStart() handler and using DelayedControls. This doesn't\nhave to be implemented immediately, but that's the target we should\neventually reach.\n\nVBLANK is something I wouldn't expose through libcamera controls. We\nhave frame durations, manual exposure times and auto/manual exposure\ncontrol, VBLANK (and other timing parameters) should be computed from\nthose. That's usually the job of the IPA, but we could do so in the\npipeline handler too. Another option would be to create a generic IPA\nmodule, but I think that would be a bit overkill in this case as it\nwould do very little.\n\n> I was more concerned about where the control should be applied, video\n> device, sensor subdevice, VCM subdev etc. This is platform dependent\n> and again, simple works on many different platforms we're not in\n> control of and as soon as the list of supported contorls grows beyond\n> the ones you have defined here I'm afraid things might quickly go out\n> of synch.\n\nGenerally speaking I'd agree, but I think we can set some baseline\nrequirements that drivers need to comply with to be supported by the\nsimple pipeline handler. One of them could be that the camera sensor\ncontrols must be exposed by the camera sensor subdev. We already require\nan MC-centric driver, so this should already be the case.\n\nI'm thus a bit less concerned than Jacopo, but there's one concern I\nhave that he hasn't voiced. The code needs to translate the value of the\ncontrols between libcamera and V4L2. Unlike V4L2, libcamera standardizes\ncontrol units and values. There's an implementation of this in the UVC\npipeline handler, but we'll need something more complex here as the UVC\npipeline handler can rely on V4L2 controls being compliant with the UVC\nspecification, and we ca make no assumption.\n\nA very good example is the analog gain control, which is exposed as a\nnumber by V4L2 without any unit or model. You can see what we have to do\nto translate gains to control values in\nsrc/ipa/libipa/camera_sensor_helper.cpp, with sensor-specific data. We\ndiscussed in the past the option of adding V4L2 controls to expose the\ngain model parameters to userspace, and decided to hardcode them in\nlibcamera instead.\n\nOne way forward would be to move the CameraSensorHelper from libipa to\nlibcamera, and handle the translate in the CameraSensor class. IPA\nmodules would have to be updated to pass libcamera controls through the\nIPA API (that shouldn't be too hard). We would lose the ability on the\nIPA side to know how a value will be rounded to a V4L2 control, but that\ndoesn't seem to be an issue.\n\nThis would simplify the implementation in the simple pipeline handler,\nas it will be able to rely on the CameraSensor class implementing the\ntranslation of control values.\n\n> Again, maybe I'm over concerned, let's see what others think.\n> \n> > > > > Also, I would like to see this implemented thorough a componenet with\n> > > > > a single interface towards the pipeline handler rather than a raw set\n> > > > > of helper functions. We can indeed help designing that once we have\n> > > > > the previous question clarified.\n> > > > >\n> > > > > I'm not even sure this is the direction we want to got with the simple\n> > > > > pipeline handler (platform-specific backends), and I would like to\n> > > > > hear Laurent's opinion on this, but I see a potential for doing what\n> > > > > we do with the android backend selection through the\n> > > > > 'android_platform' build option.\n> > > > >\n> > > > >         option('android_platform',\n> > > > >                 type : 'combo',\n> > > > >                 choices : ['cros', 'generic'],\n> > > > >                 value : 'generic',\n> > > > >                 description : 'Select the Android platform to compile for')\n> > > > >\n> > > > > > +{\n> > > > > > +    // Convert controls\n> > > > > > +    if (v4l2_control) {\n> > > > > > +        auto it = controlsToV4L2.find(control);\n> > > > > > +        if (it == controlsToV4L2.end())\n> > > > > > +            return false;\n> > > > > > +\n> > > > > > +        *v4l2_control = it->second;\n> > > > > > +    }\n> > > > > > +\n> > > > > > +    // Convert values\n> > > > > > +    if (num_values == 0)\n> > > > > > +        return true;\n> > > > > > +\n> > > > > > +    switch (control) {\n> > > > > > +    case controls::AE_ENABLE:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            v4l2_values[i] = ControlValue((int32_t)(control_values[i].get<bool>() ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n> > > > > > +        return true;\n> > > > > > +    case controls::EXPOSURE_VALUE:\n> > > > > > +    case controls::DIGITAL_GAIN:\n> > > > > > +    case controls::ANALOGUE_GAIN:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            v4l2_values[i] = ControlValue((int32_t)control_values[i].get<float>());\n> > > > > > +        return true;\n> > > > > > +    // Read only\n> > > > > > +    case controls::AF_STATE:\n> > > > > > +        return false;\n> > > > > > +    default:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            v4l2_values[i] = control_values[i];\n> > > > > > +        return true;\n> > > > > > +    }\n> > > > > > +\n> > > > > > +}\n> > > > > > +\n> > > > > > +static std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * Convert from a V4L2 control to a libcamera control, optionally also convert a\n> > > > > > + * set of ControlValues.\n> > > > > > + */\n> > > > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > > > +               unsigned int *control,\n> > > > > > +               const ControlValue *v4l2_values,\n> > > > > > +               ControlValue *control_values,\n> > > > > > +               size_t num_values)\n> > > > > > +{\n> > > > > > +    // Initialize the inverse of controlsToV4L2\n> > > > > > +    if (controlsFromV4L2.empty()) {\n> > > > > > +        for (const auto &v : controlsToV4L2) {\n> > > > > > +            controlsFromV4L2[v.second] = v.first;\n> > > > > > +        }\n> > > > > > +    }\n> > > > > > +\n> > > > > > +    // Convert control\n> > > > > > +    if (control) {\n> > > > > > +        auto it = controlsFromV4L2.find(v4l2_control);\n> > > > > > +        if (it == controlsFromV4L2.end())\n> > > > > > +            return false;\n> > > > > > +\n> > > > > > +        *control = it->second;\n> > > > > > +    }\n> > > > > > +\n> > > > > > +    // Convert values\n> > > > > > +    if (num_values == 0)\n> > > > > > +        return true;\n> > > > > > +\n> > > > > > +    switch (v4l2_control) {\n> > > > > > +    case V4L2_CID_EXPOSURE_AUTO:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            control_values[i] = ControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n> > > > > > +        return true;\n> > > > > > +    case V4L2_CID_EXPOSURE:\n> > > > > > +    case V4L2_CID_GAIN:\n> > > > > > +    case V4L2_CID_ANALOGUE_GAIN:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            control_values[i] = ControlValue((float)v4l2_values[i].get<int32_t>());\n> > > > > > +        return true;\n> > > > > > +    case V4L2_CID_AUTO_FOCUS_STATUS:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i) {\n> > > > > > +            switch (v4l2_values[i].get<int32_t>()) {\n> > > > > > +            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n> > > > > > +                control_values[i] = ControlValue((int32_t)controls::draft::AfStateInactive);\n> > > > > > +                break;\n> > > > > > +            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n> > > > > > +                control_values[i] = ControlValue((int32_t)controls::draft::AfStateActiveScan);\n> > > > > > +                break;\n> > > > > > +            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n> > > > > > +                control_values[i] = ControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n> > > > > > +                break;\n> > > > > > +            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n> > > > > > +                control_values[i] = ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n> > > > > > +                break;\n> > > > > > +            default:\n> > > > > > +                LOG(SimplePipeline, Error)\n> > > > > > +                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n> > > > > > +                    << utils::hex(v4l2_values[i].get<int32_t>());\n> > > > > > +                /*TODO: Log Error*/\n> > > > > > +                return false;\n> > > > > > +            }\n> > > > > > +        }\n> > > > > > +        return true;\n> > > > > > +    default:\n> > > > > > +        for (size_t i = 0; i < num_values; ++i)\n> > > > > > +            control_values[i] = v4l2_values[i];\n> > > > > > +        return true;\n> > > > > > +    }\n> > > > > > +}\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * Convert a ControlInfoMap from V4L2 to libcamera. Converts both the control\n> > > > > > + * identifiers as well as all values.\n> > > > > > + */\n> > > > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n> > > > > > +{\n> > > > > > +    ControlInfoMap::Map info_map;\n> > > > > > +\n> > > > > > +    for (const auto &pair : v4l2_info_map) {\n> > > > > > +        unsigned int v4l2_control = pair.first->id();\n> > > > > > +        const ControlInfo &v4l2_info = pair.second;\n> > > > > > +\n> > > > > > +        unsigned int control;\n> > > > > > +        ControlValue def;\n> > > > > > +        if (!simpleControlFromV4L2(v4l2_control, &control, &v4l2_info.def(), &def, 1))\n> > > > > > +            continue;\n> > > > > > +\n> > > > > > +        const ControlId *control_id = controls::controls.at(control);\n> > > > > > +\n> > > > > > +        // ControlInfo has either a list of values or a minimum and\n> > > > > > +        // maximum. This includes controls that have no values or are\n> > > > > > +        // booleans.\n> > > > > > +        ControlInfo info;\n> > > > > > +        if (v4l2_info.values().empty()) {\n> > > > > > +            ControlValue min, max;\n> > > > > > +            simpleControlFromV4L2(v4l2_control, nullptr, &v4l2_info.min(), &min, 1);\n> > > > > > +            simpleControlFromV4L2(v4l2_control, nullptr, &v4l2_info.max(), &max, 1);\n> > > > > > +            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n> > > > > > +        } else {\n> > > > > > +            std::vector<ControlValue> values;\n> > > > > > +            values.resize(v4l2_info.values().size());\n> > > > > > +            simpleControlFromV4L2(v4l2_control, nullptr, v4l2_info.values().data(), values.data(), values.size());\n> > > > > > +            info = ControlInfo(std::move(values), std::move(def));\n> > > > > > +        }\n> > > > > > +        info_map.emplace(control_id, std::move(info));\n> > > > > > +    }\n> > > > > > +\n> > > > > > +    return ControlInfoMap(std::move(info_map), controls::controls);\n> > > > > > +}\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * Convert a control list from libcamera to V4L2.\n> > > > > > + */\n> > > > > > +ControlList simpleControlListToV4L2(const ControlList &controls)\n> > > > > > +{\n> > > > > > +    ControlList v4l2_controls;\n> > > > > > +    for (const auto &pair : controls) {\n> > > > > > +        unsigned int control = pair.first;\n> > > > > > +        const ControlValue &value = pair.second;\n> > > > > > +\n> > > > > > +        unsigned int v4l2_control;\n> > > > > > +        ControlValue v4l2_value;\n> > > > > > +        if (!simpleControlToV4L2(control, &v4l2_control, &value, &v4l2_value, 1)) {\n> > > > > > +            LOG(SimplePipeline, Warning)\n> > > > > > +                << \"Control \" << utils::hex(control)\n> > > > > > +                << \" does not have a V4L2 equivalent\";\n> > > > > > +            continue;\n> > > > > > +        }\n> > > > > > +\n> > > > > > +        v4l2_controls.set(v4l2_control, v4l2_value);\n> > > > > > +    }\n> > > > > > +    return v4l2_controls;\n> > > > > > +}\n> > > > > > +\n> > > > > > +/*\n> > > > > > + * Convert a control list from V4L2 to libcamera.\n> > > > > > + */\n> > > > > > +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n> > > > > > +{\n> > > > > > +    ControlList controls;\n> > > > > > +    for (const auto &pair : v4l2_controls) {\n> > > > > > +        unsigned int v4l2_control = pair.first;\n> > > > > > +        const ControlValue &v4l2_value = pair.second;\n> > > > > > +\n> > > > > > +        unsigned int control;\n> > > > > > +        ControlValue value;\n> > > > > > +        if (simpleControlFromV4L2(v4l2_control, &control, &v4l2_value, &value, 1))\n> > > > > > +            controls.set(control, value);\n> > > > > > +    }\n> > > > > > +    return controls;\n> > > > > > +}\n> > > > > > +\n> > > > > > +} /* namespace libcamera */\n> > > > > > diff --git a/src/libcamera/pipeline/simple/controls.h\n> > > > > > b/src/libcamera/pipeline/simple/controls.h\n> > > > > > new file mode 100644\n> > > > > > index 00000000..114c5fc2\n> > > > > > --- /dev/null\n> > > > > > +++ b/src/libcamera/pipeline/simple/controls.h\n> > > > >\n> > > > > Same comment, license and copyright\n> > > > >\n> > > > > > @@ -0,0 +1,26 @@\n> > > > > > +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > > > > +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__\n> > > > >\n> > > > > #pragma once\n> > > >\n> > > > All the code I've seen uses #ifndef instead of #pragma once. I'd\n> > > > prefer to use #pragma once but it seems inconsistent with the rest of\n> > > > the codebase?\n> > >\n> > > Not since\n> > > https://patchwork.libcamera.org/project/libcamera/list/?series=2749&state=*\n> > >\n> > > > > > +\n> > > > > > +#include <libcamera/controls.h>\n> > > > > > +\n> > > > > > +namespace libcamera {\n> > > > > > +\n> > > > > > +bool simpleControlToV4L2(unsigned int control,\n> > > > > > +             unsigned int *v4l2_control,\n> > > > > > +             const ControlValue *control_values,\n> > > > > > +             ControlValue *v4l2_values,\n> > > > > > +             size_t num_values);\n> > > > > > +bool simpleControlFromV4L2(unsigned int v4l2_control,\n> > > > > > +               unsigned int *control,\n> > > > > > +               const ControlValue *v4l2_values,\n> > > > > > +               ControlValue *control_values,\n> > > > > > +               size_t num_values);\n> > > > > > +\n> > > > > > +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n> > > > > > +\n> > > > > > +ControlList simpleControlListToV4L2(const ControlList &controls);\n> > > > > > +ControlList simpleControlListFromV4L2(const ControlList &controls);\n> > > > > > +\n> > > > > > +} /* namespace libcamera */\n> > > > > > +\n> > > > > > +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */\n> > > > > > diff --git a/src/libcamera/pipeline/simple/meson.build\n> > > > > > b/src/libcamera/pipeline/simple/meson.build\n> > > > > > index 9c99b32f..0c60d65a 100644\n> > > > > > --- a/src/libcamera/pipeline/simple/meson.build\n> > > > > > +++ b/src/libcamera/pipeline/simple/meson.build\n> > > > > > @@ -1,6 +1,7 @@\n> > > > > >  # SPDX-License-Identifier: CC0-1.0\n> > > > > >\n> > > > > >  libcamera_sources += files([\n> > > > > > +    'controls.cpp',\n> > > > > >      'converter.cpp',\n> > > > > >      'simple.cpp',\n> > > > > >  ])\n> > > > > > diff --git a/src/libcamera/pipeline/simple/simple.cpp\n> > > > > > b/src/libcamera/pipeline/simple/simple.cpp\n> > > > > > index a597e27f..b0d4a62a 100644\n> > > > > > --- a/src/libcamera/pipeline/simple/simple.cpp\n> > > > > > +++ b/src/libcamera/pipeline/simple/simple.cpp\n> > > > > > @@ -37,6 +37,7 @@\n> > > > > >  #include \"libcamera/internal/v4l2_videodevice.h\"\n> > > > > >\n> > > > > >  #include \"converter.h\"\n> > > > > > +#include \"controls.h\"\n\nWe sort headers alphabetically.\n\n> > > > > >\n> > > > > >  namespace libcamera {\n> > > > > >\n> > > > > > @@ -181,6 +182,7 @@ public:\n> > > > > >      int setupLinks();\n> > > > > >      int setupFormats(V4L2SubdeviceFormat *format,\n> > > > > >               V4L2Subdevice::Whence whence);\n> > > > > > +    int setRequestControls(Request *request);\n> > > > > >      void bufferReady(FrameBuffer *buffer);\n> > > > > >\n> > > > > >      unsigned int streamIndex(const Stream *stream) const\n> > > > > > @@ -519,7 +521,8 @@ int SimpleCameraData::init()\n> > > > > >              formats_[fmt] = &config;\n> > > > > >      }\n> > > > > >\n> > > > > > -    properties_ = sensor_->properties();\n> > > > > > +    properties_ = simpleControlListFromV4L2(sensor_->properties());\n\nI don't think this is right, CameraSensor::properties() returns a\nControlList of libcamera controls, not V4L2 controls.\n\n> > > > > > +    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n> > > > > >\n> > > > > >      return 0;\n> > > > > >  }\n> > > > > > @@ -624,6 +627,23 @@ int\n> > > > > > SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> > > > > >      return 0;\n> > > > > >  }\n> > > > > >\n> > > > > > +int SimpleCameraData::setRequestControls(Request *request)\n> > > > > > +{\n> > > > > > +    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > > > +\n> > > > > > +    // Apply controls only to one entity. If there's a subdevice use that.\n> > > > > > +    V4L2Device *control_device = video_;\n> > > > > > +    for (const SimpleCameraData::Entity &e : entities_) {\n> > > > > > +        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n> > > > > > +        if (subdev) {\n> > > > > > +            control_device = subdev;\n> > > > > > +        }\n> > > > > > +    }\n> > > > > > +\n> > > > > > +    ControlList controls = simpleControlListToV4L2(request->controls());\n> > > > > > +    return control_device->setControls(&controls);\n\nCould this be simplified by setting controls on the CameraSensor\nunconditionally ?\n\n> > > > > > +}\n> > > > > > +\n> > > > > >  void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > > > >  {\n> > > > > >      SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n> > > > > > @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n> > > > > >          return;\n> > > > > >      }\n> > > > > >\n> > > > > > +    // Set the controls for the next queued request\n> > > > > > +    if (!queuedRequests_.empty())\n> > > > > > +        setRequestControls(queuedRequests_.front());\n\nWe should ideally apply controls in a frame start handler (connected to\nthe V4L2Device::frameStart signal), but we can start with setting\ncontrols here. A \\todo comment would be good.\n\n> > > > > > +\n> > > > > >      /*\n> > > > > >       * Record the sensor's timestamp in the request metadata. The request\n> > > > > >       * needs to be obtained from the user-facing buffer, as internal\n> > > > > > @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n> > > > > > *camera, [[maybe_unused]] const ControlL\n> > > > > >          return ret;\n> > > > > >      }\n> > > > > >\n> > > > > > +    // Apply controls from first request\n> > > > > > +    if (!data->queuedRequests_.empty())\n> > > > > > +        data->setRequestControls(data->queuedRequests_.front());\n\nThis should also apply controls passed to the start() function.\n\n> > > > > > +\n> > > > > >      if (data->useConverter_) {\n> > > > > >          ret = data->converter_->start();\n> > > > > >          if (ret < 0) {","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D6D8DBDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 23:21:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2E8A36086A;\n\tFri, 10 Dec 2021 00:21:03 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 561AC60225\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 10 Dec 2021 00:21:01 +0100 (CET)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A3BDB90E;\n\tFri, 10 Dec 2021 00:21:00 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"mX7QxZVL\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1639092061;\n\tbh=obt57SKC0vNPRa+uFJmvXiw1oLQLzJZykJNTHbmz380=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=mX7QxZVL0kr26AZu7C+lsTnoMuvgEgnWc89mfFRwfkCsVPXdHpRx+UkNYVyCYJw/q\n\tzz8rqgNhTv7sajgLOd0pDaHirg0kthbEMsVuprcjC11Exe9IULugdK5KtIkubJzAZo\n\tfFqrRi0TYdqwDWfAkoJzZrzFWRaohIegxd9stH9M=","Date":"Fri, 10 Dec 2021 01:20:31 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<YbKPP1qfbfir7BKB@pendragon.ideasonboard.com>","References":"<CAJ+kdVGwe49mshX6=YTuUdU68ePTffdDwp+qL2FRm5ixrXDVsA@mail.gmail.com>\n\t<20211209085830.affr22qlko7sxxvm@uno.localdomain>\n\t<CAJ+kdVGmVHuF+bbTrFkzwADOXEVx--34VoJZpSCR-4yH8swzvg@mail.gmail.com>\n\t<20211209113412.rzc24vxbmvoaiw5m@uno.localdomain>\n\t<CAJ+kdVEyEYaOzrrFMHbm5RMfCQ6sP745M824WfLQAD6kkWGeiA@mail.gmail.com>\n\t<20211209123512.xmxsbd2wh4bli4dv@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20211209123512.xmxsbd2wh4bli4dv@uno.localdomain>","Subject":"Re: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add\n\tsupport for controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org,\n\tBenjamin Schaaf <ben.schaaf@gmail.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]