From patchwork Wed Dec 8 12:35:23 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Schaaf X-Patchwork-Id: 15078 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 712F2BDB13 for ; Wed, 8 Dec 2021 12:35:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AB2C460822; Wed, 8 Dec 2021 13:35:38 +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="qHGv2Juy"; dkim-atps=neutral Received: from mail-yb1-xb2a.google.com (mail-yb1-xb2a.google.com [IPv6:2607:f8b0:4864:20::b2a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6226560225 for ; Wed, 8 Dec 2021 13:35:37 +0100 (CET) Received: by mail-yb1-xb2a.google.com with SMTP id y68so5698713ybe.1 for ; Wed, 08 Dec 2021 04:35:37 -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=a1aJq3pkhIbDTHBvNy9eMXicBodX1KPs2MqBUQOXTe4=; b=qHGv2JuyyhYdHunchhG6Qqm8qE9pV8AsjPWyR09T4w5vOUxeS8UgXObBVCG/g0uk8j x8GnuTnkUxgHxnRPmycrOXV//edESQjG+CyNth6+efMBDpac1ShaiDE5W5qdDFza3p1N 4gHbMWIOdVB8HzTR4fwYQDbbFcWD4+TbSUFlgElNntYYKwRlRP+c8KmXK+mbjKLgPsvS 7lsxVWSIcAm4ay182FfnTBYBOf/oK62CHI744eLvJD7Ov+/oQgjfjdnJ+iLqQ9MvmMOc Xn6nNoVJ6X3B178x/jHojT3TCn6a17y9+tR7t9d+2bzPdg5nsYgii8EiLTZjRyLyVnl0 i1BA== 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=a1aJq3pkhIbDTHBvNy9eMXicBodX1KPs2MqBUQOXTe4=; b=eIiGH0/HSkFvnNkcxgLJ6uDQv3WRYWi5Rhm6be7pn0wvac0hvuRQn/gCW3wNN6Kh8M K+/hdt6aXHoP2IeW5yOWEW1j0zO8ppo62zDrikwHEDetD1IUqKxTQmZU4yzvx5ReEgLq u2Ykte7QL8lRCh4tCcR8Ysu/Rh94FyPYGFW2booBdHBVMT8OZW/mFHCiSmeVh0S/XKAx 66cR7yiL3FsnBF0KQxAZmOSaOERS1Nl/GMhRc/ZuNb0zTbEe3LGYwKHHPCrJYkchMvsJ 7X60ocfejv3p1q7RsztGXklzqeQNHUvGT2iGXa3KcFmGZcJQ1uGqSBiY/0Pqb8BSUl1s nKcQ== X-Gm-Message-State: AOAM533KJFebUvzlF1MlasFRq6Ztc5z9YqdOymU3MejL52n9ciyZ61cX 2+Tmay3/5p/koa8bAaaOHW+Fl6q23xEAMl1sNDt6p4MflRg= X-Google-Smtp-Source: ABdhPJzVEgkl+kf5DcTSyfiIeKj+e9U2GTmzGgkBNLZ0zZhi9VSn1+0DhhLgvMcDxodQTiQZ1t6BgI0gJ0J86o1WYys= X-Received: by 2002:a25:9bc9:: with SMTP id w9mr58032128ybo.398.1638966935031; Wed, 08 Dec 2021 04:35:35 -0800 (PST) MIME-Version: 1.0 From: Benjamin Schaaf Date: Wed, 8 Dec 2021 23:35:23 +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. Signed-off-by: Benjamin Schaaf Signed-off-by: Benjamin Schaaf --- src/libcamera/control_ids.yaml | 24 +++ src/libcamera/controls.cpp | 6 - src/libcamera/pipeline/simple/controls.cpp | 230 +++++++++++++++++++++ src/libcamera/pipeline/simple/controls.h | 26 +++ src/libcamera/pipeline/simple/meson.build | 1 + src/libcamera/pipeline/simple/simple.cpp | 30 ++- 6 files changed, 310 insertions(+), 7 deletions(-) create mode 100644 src/libcamera/pipeline/simple/controls.cpp create mode 100644 src/libcamera/pipeline/simple/controls.h 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/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/controls.cpp b/src/libcamera/controls.cpp index 0d8c0a5c..1f65fc73 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -1052,9 +1052,6 @@ const ControlValue *ControlList::find(unsigned int id) const { const auto iter = controls_.find(id); if (iter == controls_.end()) { - LOG(Controls, Error) - << "Control " << utils::hex(id) << " not found"; - return nullptr; } @@ -1064,9 +1061,6 @@ const ControlValue *ControlList::find(unsigned int id) const ControlValue *ControlList::find(unsigned int id) { if (validator_ && !validator_->validate(id)) { - LOG(Controls, Error) - << "Control " << utils::hex(id) - << " is not valid for " << validator_->name(); return nullptr; } diff --git a/src/libcamera/pipeline/simple/controls.cpp b/src/libcamera/pipeline/simple/controls.cpp new file mode 100644 index 00000000..32695749 --- /dev/null +++ b/src/libcamera/pipeline/simple/controls.cpp @@ -0,0 +1,230 @@ +#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. + */ +static 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 }, +}; + +/* + * Convert from a libcamera control to a V4L2 control, optionally also convert a + * set of ControlValues. + */ +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; + } + +} + +static std::unordered_map controlsFromV4L2; + +/* + * Convert from a V4L2 control to a libcamera control, optionally also convert a + * set of ControlValues. + */ +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; + } +} + +/* + * 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); +} + +/* + * 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; +} + +/* + * 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..114c5fc2 --- /dev/null +++ b/src/libcamera/pipeline/simple/controls.h @@ -0,0 +1,26 @@ +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ +#define __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ + +#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 */ + +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONTROLS_H__ */ 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..b0d4a62a 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -37,6 +37,7 @@ #include "libcamera/internal/v4l2_videodevice.h" #include "converter.h" +#include "controls.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) {