From patchwork Thu Dec 9 11:59:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Schaaf X-Patchwork-Id: 15099 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 17A20BDB13 for ; Thu, 9 Dec 2021 11:59:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C48976086A; Thu, 9 Dec 2021 12:59:18 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="O8lqURTN"; dkim-atps=neutral Received: from mail-yb1-xb32.google.com (mail-yb1-xb32.google.com [IPv6:2607:f8b0:4864:20::b32]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AADCB607DE for ; Thu, 9 Dec 2021 12:59:16 +0100 (CET) Received: by mail-yb1-xb32.google.com with SMTP id 131so13087821ybc.7 for ; Thu, 09 Dec 2021 03:59:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=mime-version:from:date:message-id:subject:to; bh=wDA4qvP4DTeS/Vf2iPE3WC/kUaCc6Dp4WRLtPeQr8iM=; b=O8lqURTNhWmLWqSSoIxd1nvllfzbBBY4mibzZ0SXeeA/9z5jaqnaa5Oa46ksH+33oP uysUAwQgBnxXbcJxKQM6TwFmGsI3zejR+TB8q9NnLFTJljNrFubkge99QbnSwjVR3ysd ldW2AuJk3QTNW4JhLUFokCNzNW90G2VhNgvFE6nqdXR22wiHacXAbDTGvhMK6wxagDAv xcGEMi0eTcY69Cu2EM2MYTOBApaDugJHS98Uzyff3wtdYfrYPHP1ALGIolkPsqzlmMl9 hhJtAqy3O1snXEnBZEYLecL6/f3BOojTnGDnzVsabZbGx7rCfeWmLG2iuo44UzvcyGNx 5yjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=wDA4qvP4DTeS/Vf2iPE3WC/kUaCc6Dp4WRLtPeQr8iM=; b=zVBDfRsCGFXXkmCbn0l4T8YLX3ydPEKuIpWgIjBrOxuWWyJUDVFKUIXXZinQv7Q1re nLLhv1Zc+3U803CNq1wM7EHOiU+JAU9wL+YWgiAvPjWj2r0aH9Syihd4PvR3Q9ham5Jr qi2HVc7OofJiQe8DZxz7C4liL5pQ8hicyKDVGqgs6W0vkJeu5NBZ3X1bQHDnQvU6y6AU VGAN2mkJdQ//1YxeDUdng+DPW/3WikJUJuDDPPIGtpiYwRXVI7tCi9Tp7XOJLhAtZF8l xfGz5DK5+n2FH+rDKJ07G9/AbIWAfYrK6RT64iF5lTIiC0U6ZajKmDyzt4wC7pzKE4l6 8R1A== X-Gm-Message-State: AOAM533I6U8SGujjyWN0/8f/KQrdyYMq3Jrrt98QRZ2iIfk0WAda9P0S A0EnmuzGyh+JO0Gr1K1sDMUYQps6ox2pBByb0Zq62q5zaKqvIg== X-Google-Smtp-Source: ABdhPJyUPtM+LJL2Y4gqMwXTQumBu4TUq5KabAC6J8z8fBzgM/ODDKrR0sBv2jLKXs77iCjgCrTTw6Kr7R6/2OkFyIg= X-Received: by 2002:a25:9bc9:: with SMTP id w9mr5665777ybo.398.1639051155168; Thu, 09 Dec 2021 03:59:15 -0800 (PST) MIME-Version: 1.0 From: Benjamin Schaaf Date: Thu, 9 Dec 2021 22:59:04 +1100 Message-ID: To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH] libcamera: pipeline: simple: Add support for controls X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Controls and control info are translated between libcamera and V4L2 inside the simple pipeline. Request controls are applied when a request is next to be completed. This also adds some additional draft controls needed for the PinePhone. Bug: https://bugs.libcamera.org/show_bug.cgi?id=98 Signed-off-by: Benjamin Schaaf --- src/libcamera/control_ids.yaml | 24 ++ src/libcamera/pipeline/simple/controls.cpp | 242 +++++++++++++++++++++ src/libcamera/pipeline/simple/controls.h | 30 +++ src/libcamera/pipeline/simple/meson.build | 1 + src/libcamera/pipeline/simple/simple.cpp | 30 ++- src/libcamera/v4l2_device.cpp | 12 +- 6 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 src/libcamera/pipeline/simple/controls.cpp create mode 100644 src/libcamera/pipeline/simple/controls.h return v4l2MenuControlInfo(ctrl); diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml index 9d4638ae..2af230c3 100644 --- a/src/libcamera/control_ids.yaml +++ b/src/libcamera/control_ids.yaml @@ -406,6 +406,30 @@ controls: The camera will cancel any active or completed metering sequence. The AE algorithm is reset to its initial state. + - AutoGain: + type: bool + draft: true + description: | + Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN. + + - AfEnabled: + type: bool + draft: true + description: | + Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO. + + - AfStart: + type: void + draft: true + description: | + Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START. + + - AfStop: + type: void + draft: true + description: | + Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END. + - AfTrigger: type: int32_t draft: true diff --git a/src/libcamera/pipeline/simple/controls.cpp b/src/libcamera/pipeline/simple/controls.cpp new file mode 100644 index 00000000..2d3cfc62 --- /dev/null +++ b/src/libcamera/pipeline/simple/controls.cpp @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Benjamin Schaaf + * + * controls.cpp - Simple pipeline control conversion + */ + +#include "controls.h" + +#include + +#include + +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(SimplePipeline) + +/* + * These controls can be directly mapped between libcamera and V4L2 without + * doing any conversion to the ControlValue. + */ +namespace { +std::unordered_map controlsToV4L2 = { + { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN }, + { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO }, + { controls::AF_START, V4L2_CID_AUTO_FOCUS_START }, + { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP }, + { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO }, + { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE }, + { controls::DIGITAL_GAIN, V4L2_CID_GAIN }, + { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN }, + { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS }, +}; +std::unordered_map controlsFromV4L2; +} + +/** + * \brief Convert from a libcamera control to a V4L2 control. + * + * Can optionally convert the libcamera control and/or a set of libcamera + * control values to their V4L2 equivalents. + */ +bool simpleControlToV4L2(unsigned int control, + unsigned int *v4l2_control, + const ControlValue *control_values, + ControlValue *v4l2_values, + size_t num_values) +{ + // Convert controls + if (v4l2_control) { + auto it = controlsToV4L2.find(control); + if (it == controlsToV4L2.end()) + return false; + + *v4l2_control = it->second; + } + + // Convert values + if (num_values == 0) + return true; + + switch (control) { + case controls::AE_ENABLE: + for (size_t i = 0; i < num_values; ++i) + v4l2_values[i] = ControlValue((int32_t)(control_values[i].get() ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL)); + return true; + case controls::EXPOSURE_VALUE: + case controls::DIGITAL_GAIN: + case controls::ANALOGUE_GAIN: + for (size_t i = 0; i < num_values; ++i) + v4l2_values[i] = ControlValue((int32_t)control_values[i].get()); + return true; + // Read only + case controls::AF_STATE: + return false; + default: + for (size_t i = 0; i < num_values; ++i) + v4l2_values[i] = control_values[i]; + return true; + } +} + +/** + * \brief Convert from a V4L2 control to a libcamera control. + * + * Can optionally convert the V4L2 control and/or a set of V4L2 control values + * to their libcamera equivalents. + */ +bool simpleControlFromV4L2(unsigned int v4l2_control, + unsigned int *control, + const ControlValue *v4l2_values, + ControlValue *control_values, + size_t num_values) +{ + // Initialize the inverse of controlsToV4L2 + if (controlsFromV4L2.empty()) { + for (const auto &v : controlsToV4L2) { + controlsFromV4L2[v.second] = v.first; + } + } + + // Convert control + if (control) { + auto it = controlsFromV4L2.find(v4l2_control); + if (it == controlsFromV4L2.end()) + return false; + + *control = it->second; + } + + // Convert values + if (num_values == 0) + return true; + + switch (v4l2_control) { + case V4L2_CID_EXPOSURE_AUTO: + for (size_t i = 0; i < num_values; ++i) + control_values[i] = ControlValue(v4l2_values[i].get() == V4L2_EXPOSURE_AUTO); + return true; + case V4L2_CID_EXPOSURE: + case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: + for (size_t i = 0; i < num_values; ++i) + control_values[i] = ControlValue((float)v4l2_values[i].get()); + return true; + case V4L2_CID_AUTO_FOCUS_STATUS: + for (size_t i = 0; i < num_values; ++i) { + switch (v4l2_values[i].get()) { + case V4L2_AUTO_FOCUS_STATUS_IDLE: + control_values[i] = ControlValue((int32_t)controls::draft::AfStateInactive); + break; + case V4L2_AUTO_FOCUS_STATUS_BUSY: + control_values[i] = ControlValue((int32_t)controls::draft::AfStateActiveScan); + break; + case V4L2_AUTO_FOCUS_STATUS_REACHED: + control_values[i] = ControlValue((int32_t)controls::draft::AfStatePassiveFocused); + break; + case V4L2_AUTO_FOCUS_STATUS_FAILED: + control_values[i] = ControlValue((int32_t)controls::draft::AfStatePassiveUnfocused); + break; + default: + LOG(SimplePipeline, Error) + << "AUTO_FOCUS_STATUS has invalid value: " + << utils::hex(v4l2_values[i].get()); + /*TODO: Log Error*/ + return false; + } + } + return true; + default: + for (size_t i = 0; i < num_values; ++i) + control_values[i] = v4l2_values[i]; + return true; + } +} + +/** + * \brief Convert a ControlInfoMap from V4L2 to libcamera. + * + * Converts both the control identifiers as well as all values. + */ +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map) +{ + ControlInfoMap::Map info_map; + + for (const auto &pair : v4l2_info_map) { + unsigned int v4l2_control = pair.first->id(); + const ControlInfo &v4l2_info = pair.second; + + unsigned int control; + ControlValue def; + if (!simpleControlFromV4L2(v4l2_control, &control, &v4l2_info.def(), &def, 1)) + continue; + + const ControlId *control_id = controls::controls.at(control); + + // ControlInfo has either a list of values or a minimum and + // maximum. This includes controls that have no values or are + // booleans. + ControlInfo info; + if (v4l2_info.values().empty()) { + ControlValue min, max; + simpleControlFromV4L2(v4l2_control, nullptr, &v4l2_info.min(), &min, 1); + simpleControlFromV4L2(v4l2_control, nullptr, &v4l2_info.max(), &max, 1); + info = ControlInfo(std::move(min), std::move(max), std::move(def)); + } else { + std::vector values; + values.resize(v4l2_info.values().size()); + simpleControlFromV4L2(v4l2_control, nullptr, v4l2_info.values().data(), values.data(), values.size()); + info = ControlInfo(std::move(values), std::move(def)); + } + info_map.emplace(control_id, std::move(info)); + } + + return ControlInfoMap(std::move(info_map), controls::controls); +} + +/** + * \brief Convert a control list from libcamera to V4L2. + */ +ControlList simpleControlListToV4L2(const ControlList &controls) +{ + ControlList v4l2_controls; + for (const auto &pair : controls) { + unsigned int control = pair.first; + const ControlValue &value = pair.second; + + unsigned int v4l2_control; + ControlValue v4l2_value; + if (!simpleControlToV4L2(control, &v4l2_control, &value, &v4l2_value, 1)) { + LOG(SimplePipeline, Warning) + << "Control " << utils::hex(control) + << " does not have a V4L2 equivalent"; + continue; + } + + v4l2_controls.set(v4l2_control, v4l2_value); + } + return v4l2_controls; +} + +/** + * \brief Convert a control list from V4L2 to libcamera. + */ +ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls) +{ + ControlList controls; + for (const auto &pair : v4l2_controls) { + unsigned int v4l2_control = pair.first; + const ControlValue &v4l2_value = pair.second; + + unsigned int control; + ControlValue value; + if (simpleControlFromV4L2(v4l2_control, &control, &v4l2_value, &value, 1)) + controls.set(control, value); + } + return controls; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/simple/controls.h b/src/libcamera/pipeline/simple/controls.h new file mode 100644 index 00000000..cdbcaed1 --- /dev/null +++ b/src/libcamera/pipeline/simple/controls.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Benjamin Schaaf + * + * controls.h - Simple pipeline control conversion + */ + +#pragma once + +#include + +namespace libcamera { + +bool simpleControlToV4L2(unsigned int control, + unsigned int *v4l2_control, + const ControlValue *control_values, + ControlValue *v4l2_values, + size_t num_values); +bool simpleControlFromV4L2(unsigned int v4l2_control, + unsigned int *control, + const ControlValue *v4l2_values, + ControlValue *control_values, + size_t num_values); + +ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info); + +ControlList simpleControlListToV4L2(const ControlList &controls); +ControlList simpleControlListFromV4L2(const ControlList &controls); + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build index 9c99b32f..0c60d65a 100644 --- a/src/libcamera/pipeline/simple/meson.build +++ b/src/libcamera/pipeline/simple/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_sources += files([ + 'controls.cpp', 'converter.cpp', 'simple.cpp', ]) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index a597e27f..1717a1a7 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -36,6 +36,7 @@ #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" +#include "controls.h" #include "converter.h" namespace libcamera { @@ -181,6 +182,7 @@ public: int setupLinks(); int setupFormats(V4L2SubdeviceFormat *format, V4L2Subdevice::Whence whence); + int setRequestControls(Request *request); void bufferReady(FrameBuffer *buffer); unsigned int streamIndex(const Stream *stream) const @@ -519,7 +521,8 @@ int SimpleCameraData::init() formats_[fmt] = &config; } - properties_ = sensor_->properties(); + properties_ = simpleControlListFromV4L2(sensor_->properties()); + controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls()); return 0; } @@ -624,6 +627,23 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, return 0; } +int SimpleCameraData::setRequestControls(Request *request) +{ + SimplePipelineHandler *pipe = SimpleCameraData::pipe(); + + // Apply controls only to one entity. If there's a subdevice use that. + V4L2Device *control_device = video_; + for (const SimpleCameraData::Entity &e : entities_) { + V4L2Subdevice *subdev = pipe->subdev(e.entity); + if (subdev) { + control_device = subdev; + } + } + + ControlList controls = simpleControlListToV4L2(request->controls()); + return control_device->setControls(&controls); +} + void SimpleCameraData::bufferReady(FrameBuffer *buffer) { SimplePipelineHandler *pipe = SimpleCameraData::pipe(); @@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) return; } + // Set the controls for the next queued request + if (!queuedRequests_.empty()) + setRequestControls(queuedRequests_.front()); + /* * Record the sensor's timestamp in the request metadata. The request * needs to be obtained from the user-facing buffer, as internal @@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL return ret; } + // Apply controls from first request + if (!data->queuedRequests_.empty()) + data->setRequestControls(data->queuedRequests_.front()); + if (data->useConverter_) { ret = data->converter_->start(); if (ret < 0) { diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 39f36009..7a3b56a2 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -293,6 +293,13 @@ int V4L2Device::setControls(ControlList *ctrls) /* Set the v4l2_ext_control value for the write operation. */ ControlValue &value = ctrl->second; switch (iter->first->type()) { + case ControlTypeBool: + v4l2Ctrl.value64 = value.get(); + break; + + case ControlTypeNone: + break; + case ControlTypeInteger64: v4l2Ctrl.value64 = value.get(); break; @@ -476,7 +483,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType) return ControlTypeInteger64; case V4L2_CTRL_TYPE_MENU: - case V4L2_CTRL_TYPE_BUTTON: case V4L2_CTRL_TYPE_BITMASK: case V4L2_CTRL_TYPE_INTEGER_MENU: /* @@ -485,6 +491,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType) */ return ControlTypeInteger32; + case V4L2_CTRL_TYPE_BUTTON: default: return ControlTypeNone; } @@ -527,6 +534,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl) static_cast(ctrl.maximum), static_cast(ctrl.default_value)); + case V4L2_CTRL_TYPE_BUTTON: + return ControlInfo(ControlValue(), ControlValue(), ControlValue()); + case V4L2_CTRL_TYPE_INTEGER_MENU: case V4L2_CTRL_TYPE_MENU: