status: extend to provide all camera options and properties in JSON payload
- control: add `device_option_is_equal` - libcamera: provide human readable configurable options - v4l2: include camera control values - libcamera: store all applied controls - libcamera: use `device_option_is_equal`
This commit is contained in:
parent
8df8dcf2e5
commit
899f2c5e62
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -2,3 +2,6 @@
|
|||||||
path = third_party/libdatachannel
|
path = third_party/libdatachannel
|
||||||
url = https://github.com/paullouisageneau/libdatachannel.git
|
url = https://github.com/paullouisageneau/libdatachannel.git
|
||||||
ignore = dirty
|
ignore = dirty
|
||||||
|
[submodule "third_party/magic_enum"]
|
||||||
|
path = third_party/magic_enum
|
||||||
|
url = https://github.com/Neargye/magic_enum.git
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# Release #{GIT_VERSION}
|
# Release #{GIT_VERSION}
|
||||||
|
|
||||||
|
- http: extend `/status` with all options
|
||||||
|
- cmd: accept `--camera-options=AfMode=auto` and alike
|
||||||
|
- libcamera: expose all options with human readable settings
|
||||||
|
- v4l2: expose all options with human readable settings
|
||||||
|
|
||||||
## Variants
|
## Variants
|
||||||
|
|
||||||
Download correct version for your platform:
|
Download correct version for your platform:
|
||||||
|
@ -38,8 +38,8 @@ camera_options_t camera_options = {
|
|||||||
"video_bitrate=2000000" OPTION_VALUE_LIST_SEP
|
"video_bitrate=2000000" OPTION_VALUE_LIST_SEP
|
||||||
"repeat_sequence_header=5000000" OPTION_VALUE_LIST_SEP
|
"repeat_sequence_header=5000000" OPTION_VALUE_LIST_SEP
|
||||||
"h264_i_frame_period=30" OPTION_VALUE_LIST_SEP
|
"h264_i_frame_period=30" OPTION_VALUE_LIST_SEP
|
||||||
"h264_level=11" OPTION_VALUE_LIST_SEP
|
"h264_level=4" OPTION_VALUE_LIST_SEP
|
||||||
"h264_profile=4" OPTION_VALUE_LIST_SEP
|
"h264_profile=high" OPTION_VALUE_LIST_SEP
|
||||||
"h264_minimum_qp_value=16" OPTION_VALUE_LIST_SEP
|
"h264_minimum_qp_value=16" OPTION_VALUE_LIST_SEP
|
||||||
"h264_maximum_qp_value=32"
|
"h264_maximum_qp_value=32"
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,14 @@ extern "C" {
|
|||||||
|
|
||||||
#include "util/http/http.h"
|
#include "util/http/http.h"
|
||||||
#include "util/opts/fourcc.h"
|
#include "util/opts/fourcc.h"
|
||||||
|
#include "util/opts/control.h"
|
||||||
#include "device/buffer_list.h"
|
#include "device/buffer_list.h"
|
||||||
#include "device/buffer_lock.h"
|
#include "device/buffer_lock.h"
|
||||||
#include "device/camera/camera.h"
|
#include "device/camera/camera.h"
|
||||||
#include "output/rtsp/rtsp.h"
|
#include "output/rtsp/rtsp.h"
|
||||||
#include "output/webrtc/webrtc.h"
|
#include "output/webrtc/webrtc.h"
|
||||||
#include "output/output.h"
|
#include "output/output.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
extern camera_t *camera;
|
extern camera_t *camera;
|
||||||
extern http_server_options_t http_options;
|
extern http_server_options_t http_options;
|
||||||
@ -17,6 +19,7 @@ extern webrtc_options_t webrtc_options;
|
|||||||
};
|
};
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include "third_party/magic_enum/include/magic_enum.hpp"
|
||||||
|
|
||||||
static nlohmann::json serialize_buf_list(buffer_list_t *buf_list)
|
static nlohmann::json serialize_buf_list(buffer_list_t *buf_list)
|
||||||
{
|
{
|
||||||
@ -53,6 +56,48 @@ static nlohmann::json serialize_buf_lock(buffer_lock_t *buf_lock)
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *strip_prefix(const char *str, const char *prefix)
|
||||||
|
{
|
||||||
|
if (strstr(str, prefix) == str) {
|
||||||
|
return str + strlen(prefix);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string std_device_option_normalize(std::string key)
|
||||||
|
{
|
||||||
|
key.resize(device_option_normalize_name(key.data(), key.data()));
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int device_options_callback(device_option_t *option, void *opaque)
|
||||||
|
{
|
||||||
|
auto key = std_device_option_normalize(option->name);
|
||||||
|
nlohmann::json &device = *(nlohmann::json*)opaque;
|
||||||
|
nlohmann::json &node = option->flags.read_only ?
|
||||||
|
device["properties"][key] :
|
||||||
|
device["options"][key];
|
||||||
|
|
||||||
|
node["name"] = option->name;
|
||||||
|
node["type"] = strip_prefix(
|
||||||
|
std::string(magic_enum::enum_name(option->type)).c_str(),
|
||||||
|
"device_option_type_");
|
||||||
|
|
||||||
|
if (option->elems > 0)
|
||||||
|
node["elems"] = option->elems;
|
||||||
|
if (option->description[0])
|
||||||
|
node["description"] = option->description;
|
||||||
|
if (option->value[0])
|
||||||
|
node["value"] = option->value;
|
||||||
|
|
||||||
|
for (int i = 0; i < option->menu_items; i++) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", option->menu[i].id);
|
||||||
|
node["menu"][buf] = option->menu[i].name;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static nlohmann::json devices_status_json()
|
static nlohmann::json devices_status_json()
|
||||||
{
|
{
|
||||||
nlohmann::json devices;
|
nlohmann::json devices;
|
||||||
@ -70,6 +115,8 @@ static nlohmann::json devices_status_json()
|
|||||||
for (int j = 0; j < device->n_capture_list; j++) {
|
for (int j = 0; j < device->n_capture_list; j++) {
|
||||||
device_json["captures"][j] = serialize_buf_list(device->capture_lists[j]);
|
device_json["captures"][j] = serialize_buf_list(device->capture_lists[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device_dump_options2(device, device_options_callback, &device_json);
|
||||||
devices += device_json;
|
devices += device_json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +169,9 @@ extern "C" void camera_status_json(http_worker_t *worker, FILE *stream)
|
|||||||
{
|
{
|
||||||
nlohmann::json message;
|
nlohmann::json message;
|
||||||
|
|
||||||
|
message["version"] = GIT_VERSION;
|
||||||
|
message["revision"] = GIT_REVISION;
|
||||||
|
|
||||||
message["outputs"]["snapshot"] = serialize_buf_lock(&snapshot_lock);
|
message["outputs"]["snapshot"] = serialize_buf_lock(&snapshot_lock);
|
||||||
message["outputs"]["stream"] = serialize_buf_lock(&stream_lock);
|
message["outputs"]["stream"] = serialize_buf_lock(&stream_lock);
|
||||||
message["outputs"]["video"] = serialize_buf_lock(&video_lock);
|
message["outputs"]["video"] = serialize_buf_lock(&video_lock);
|
||||||
|
@ -194,6 +194,14 @@ void device_dump_options(device_t *dev, FILE *stream)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int device_dump_options2(device_t *dev, device_option_fn fn, void *opaque)
|
||||||
|
{
|
||||||
|
if (dev && dev->hw->device_dump_options) {
|
||||||
|
return dev->hw->device_dump_options2(dev, fn, opaque);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int device_set_fps(device_t *dev, int desired_fps)
|
int device_set_fps(device_t *dev, int desired_fps)
|
||||||
{
|
{
|
||||||
if (!dev)
|
if (!dev)
|
||||||
|
@ -7,14 +7,18 @@
|
|||||||
typedef struct buffer_s buffer_t;
|
typedef struct buffer_s buffer_t;
|
||||||
typedef struct buffer_list_s buffer_list_t;
|
typedef struct buffer_list_s buffer_list_t;
|
||||||
typedef struct buffer_format_s buffer_format_t;
|
typedef struct buffer_format_s buffer_format_t;
|
||||||
|
typedef struct device_option_s device_option_t;
|
||||||
typedef struct device_s device_t;
|
typedef struct device_s device_t;
|
||||||
struct pollfd;
|
struct pollfd;
|
||||||
|
|
||||||
|
typedef int device_option_fn(device_option_t *option, void *opaque);
|
||||||
|
|
||||||
typedef struct device_hw_s {
|
typedef struct device_hw_s {
|
||||||
int (*device_open)(device_t *dev);
|
int (*device_open)(device_t *dev);
|
||||||
void (*device_close)(device_t *dev);
|
void (*device_close)(device_t *dev);
|
||||||
int (*device_video_force_key)(device_t *dev);
|
int (*device_video_force_key)(device_t *dev);
|
||||||
void (*device_dump_options)(device_t *dev, FILE *stream);
|
void (*device_dump_options)(device_t *dev, FILE *stream);
|
||||||
|
int (*device_dump_options2)(device_t *dev, device_option_fn fn, void *opaque);
|
||||||
int (*device_set_fps)(device_t *dev, int desired_fps);
|
int (*device_set_fps)(device_t *dev, int desired_fps);
|
||||||
int (*device_set_rotation)(device_t *dev, bool vflip, bool hflip);
|
int (*device_set_rotation)(device_t *dev, bool vflip, bool hflip);
|
||||||
int (*device_set_option)(device_t *dev, const char *key, const char *value);
|
int (*device_set_option)(device_t *dev, const char *key, const char *value);
|
||||||
@ -55,6 +59,42 @@ typedef struct device_s {
|
|||||||
bool paused;
|
bool paused;
|
||||||
} device_t;
|
} device_t;
|
||||||
|
|
||||||
|
typedef enum device_option_type_s {
|
||||||
|
device_option_type_u8, // comp-type
|
||||||
|
device_option_type_u16, // comp-type
|
||||||
|
device_option_type_u32, // comp-type
|
||||||
|
device_option_type_bool,
|
||||||
|
device_option_type_integer,
|
||||||
|
device_option_type_integer64,
|
||||||
|
device_option_type_float,
|
||||||
|
device_option_type_string
|
||||||
|
} device_option_type_t;
|
||||||
|
|
||||||
|
#define MAX_DEVICE_OPTION_MENU 20
|
||||||
|
|
||||||
|
typedef struct device_option_menu_s {
|
||||||
|
int id;
|
||||||
|
char name[64];
|
||||||
|
} device_option_menu_t;
|
||||||
|
|
||||||
|
typedef struct device_option_s {
|
||||||
|
char name[64];
|
||||||
|
unsigned int control_id;
|
||||||
|
|
||||||
|
device_option_type_t type;
|
||||||
|
int elems;
|
||||||
|
struct {
|
||||||
|
bool read_only : 1;
|
||||||
|
bool invalid : 1;
|
||||||
|
} flags;
|
||||||
|
|
||||||
|
device_option_menu_t menu[MAX_DEVICE_OPTION_MENU];
|
||||||
|
int menu_items;
|
||||||
|
|
||||||
|
char value[256];
|
||||||
|
char description[256];
|
||||||
|
} device_option_t;
|
||||||
|
|
||||||
device_t *device_open(const char *name, const char *path, device_hw_t *hw);
|
device_t *device_open(const char *name, const char *path, device_hw_t *hw);
|
||||||
void device_close(device_t *dev);
|
void device_close(device_t *dev);
|
||||||
|
|
||||||
@ -68,6 +108,7 @@ int device_set_stream(device_t *dev, bool do_on);
|
|||||||
int device_video_force_key(device_t *dev);
|
int device_video_force_key(device_t *dev);
|
||||||
|
|
||||||
void device_dump_options(device_t *dev, FILE *stream);
|
void device_dump_options(device_t *dev, FILE *stream);
|
||||||
|
int device_dump_options2(device_t *dev, device_option_fn fn, void *opaque);
|
||||||
int device_set_fps(device_t *dev, int desired_fps);
|
int device_set_fps(device_t *dev, int desired_fps);
|
||||||
int device_set_rotation(device_t *dev, bool vflip, bool hflip);
|
int device_set_rotation(device_t *dev, bool vflip, bool hflip);
|
||||||
int device_set_option_string(device_t *dev, const char *option, const char *value);
|
int device_set_option_string(device_t *dev, const char *option, const char *value);
|
||||||
|
@ -85,7 +85,7 @@ int libcamera_buffer_enqueue(buffer_t *buf, const char *who)
|
|||||||
if (camera->queueRequest(buf->libcamera->request.get()) < 0) {
|
if (camera->queueRequest(buf->libcamera->request.get()) < 0) {
|
||||||
LOG_ERROR(buf, "Can't queue buffer.");
|
LOG_ERROR(buf, "Can't queue buffer.");
|
||||||
}
|
}
|
||||||
buf->buf_list->dev->libcamera->controls.clear();
|
libcamera_device_apply_controls(buf->buf_list->dev);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
@ -170,7 +170,7 @@ int libcamera_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on)
|
|||||||
LOG_ERROR(buf_list, "Failed to start camera.");
|
LOG_ERROR(buf_list, "Failed to start camera.");
|
||||||
}
|
}
|
||||||
|
|
||||||
buf_list->dev->libcamera->controls.clear();
|
libcamera_device_apply_controls(buf_list->dev);
|
||||||
} else {
|
} else {
|
||||||
buf_list->dev->libcamera->camera->requestCompleted.disconnect(
|
buf_list->dev->libcamera->camera->requestCompleted.disconnect(
|
||||||
buf_list->libcamera, &buffer_list_libcamera_t::libcamera_buffer_list_dequeued);
|
buf_list->libcamera, &buffer_list_libcamera_t::libcamera_buffer_list_dequeued);
|
||||||
|
@ -1,63 +1,6 @@
|
|||||||
#ifdef USE_LIBCAMERA
|
#ifdef USE_LIBCAMERA
|
||||||
#include "libcamera.hh"
|
#include "libcamera.hh"
|
||||||
|
|
||||||
std::string libcamera_device_option_normalize(std::string key)
|
|
||||||
{
|
|
||||||
key.resize(device_option_normalize_name(key.data(), key.data()));
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
libcamera::ControlInfoMap::Map libcamera_control_list(device_t *dev)
|
|
||||||
{
|
|
||||||
libcamera::ControlInfoMap::Map controls_map;
|
|
||||||
for (auto const &control : dev->libcamera->camera->controls()) {
|
|
||||||
controls_map[control.first] = control.second;
|
|
||||||
}
|
|
||||||
return controls_map;
|
|
||||||
}
|
|
||||||
|
|
||||||
void libcamera_device_dump_options(device_t *dev, FILE *stream)
|
|
||||||
{
|
|
||||||
auto &properties = dev->libcamera->camera->properties();
|
|
||||||
auto idMap = properties.idMap();
|
|
||||||
|
|
||||||
fprintf(stream, "%s Properties:\n", dev->name);
|
|
||||||
|
|
||||||
for (auto const &control : properties) {
|
|
||||||
if (!control.first)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto control_id = control.first;
|
|
||||||
auto control_value = control.second;
|
|
||||||
std::string control_id_name = "";
|
|
||||||
|
|
||||||
if (auto control_id_info = idMap ? idMap->at(control_id) : NULL) {
|
|
||||||
control_id_name = control_id_info->name();
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stream, "- property: %s (%08x, type=%d): %s\n",
|
|
||||||
control_id_name.c_str(), control_id, control_value.type(),
|
|
||||||
control_value.toString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stream, "\n");
|
|
||||||
fprintf(stream, "%s Options:\n", dev->name);
|
|
||||||
|
|
||||||
for (auto const &control : libcamera_control_list(dev)) {
|
|
||||||
if (!control.first)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto control_id = control.first;
|
|
||||||
auto control_key = libcamera_device_option_normalize(control_id->name());
|
|
||||||
auto control_info = control.second;
|
|
||||||
|
|
||||||
fprintf(stream, "- available option: %s (%08x, type=%d): %s\n",
|
|
||||||
control_id->name().c_str(), control_id->id(), control_id->type(),
|
|
||||||
control_info.toString().c_str());
|
|
||||||
}
|
|
||||||
fprintf(stream, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void libcamera_print_cameras(device_t *dev)
|
void libcamera_print_cameras(device_t *dev)
|
||||||
{
|
{
|
||||||
if (dev->libcamera->camera_manager->cameras().size()) {
|
if (dev->libcamera->camera_manager->cameras().size()) {
|
||||||
@ -153,99 +96,15 @@ int libcamera_device_set_rotation(device_t *dev, bool vflip, bool hflip)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int libcamera_device_set_option(device_t *dev, const char *keyp, const char *value)
|
void libcamera_device_apply_controls(device_t *dev)
|
||||||
{
|
{
|
||||||
auto key = libcamera_device_option_normalize(keyp);
|
auto &controls = dev->libcamera->controls;
|
||||||
|
auto &applied_controls = dev->libcamera->applied_controls;
|
||||||
|
|
||||||
for (auto const &control : libcamera_control_list(dev)) {
|
for (auto &control : controls) {
|
||||||
if (!control.first)
|
applied_controls.set(control.first, control.second);
|
||||||
continue;
|
|
||||||
|
|
||||||
auto control_id = control.first;
|
|
||||||
auto control_key = libcamera_device_option_normalize(control_id->name());
|
|
||||||
if (key != control_key)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
libcamera::ControlValue control_value;
|
|
||||||
|
|
||||||
switch (control_id->type()) {
|
|
||||||
case libcamera::ControlTypeNone:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeBool:
|
|
||||||
control_value.set<bool>(atoi(value));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeByte:
|
|
||||||
control_value.set<unsigned char>(atoi(value));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeInteger32:
|
|
||||||
control_value.set<int32_t>(atoi(value));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeInteger64:
|
|
||||||
control_value.set<int64_t>(atoi(value));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeFloat:
|
|
||||||
control_value.set<float>(atof(value));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeRectangle:
|
|
||||||
static const char *RECTANGLE_PATTERNS[] = {
|
|
||||||
"(%d,%d)/%ux%u",
|
|
||||||
"%d,%d,%u,%u",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; RECTANGLE_PATTERNS[i]; i++) {
|
|
||||||
libcamera::Rectangle rectangle;
|
|
||||||
|
|
||||||
if (4 == sscanf(value, RECTANGLE_PATTERNS[i],
|
|
||||||
&rectangle.x, &rectangle.y,
|
|
||||||
&rectangle.width, &rectangle.height)) {
|
|
||||||
control_value.set(rectangle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeSize:
|
|
||||||
static const char *SIZE_PATTERNS[] = {
|
|
||||||
"%ux%u",
|
|
||||||
"%u,%u",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; SIZE_PATTERNS[i]; i++) {
|
|
||||||
libcamera::Size size;
|
|
||||||
|
|
||||||
if (2 == sscanf(value, SIZE_PATTERNS[i], &size.width, &size.height)) {
|
|
||||||
control_value.set(size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case libcamera::ControlTypeString:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (control_value.isNone()) {
|
|
||||||
LOG_ERROR(dev, "The `%s` type `%d` is not supported.", control_key.c_str(), control_id->type());
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO(dev, "Configuring option %s (%08x, type=%d) = %s",
|
|
||||||
control_key.c_str(), control_id->id(), control_id->type(),
|
|
||||||
control_value.toString().c_str());
|
|
||||||
dev->libcamera->controls.set(control_id->id(), control_value);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
controls.clear();
|
||||||
|
|
||||||
error:
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
#endif // USE_LIBCAMERA
|
#endif // USE_LIBCAMERA
|
||||||
|
@ -5,6 +5,7 @@ device_hw_t libcamera_device_hw = {
|
|||||||
.device_open = libcamera_device_open,
|
.device_open = libcamera_device_open,
|
||||||
.device_close = libcamera_device_close,
|
.device_close = libcamera_device_close,
|
||||||
.device_dump_options = libcamera_device_dump_options,
|
.device_dump_options = libcamera_device_dump_options,
|
||||||
|
.device_dump_options2 = libcamera_device_dump_options2,
|
||||||
.device_set_fps = libcamera_device_set_fps,
|
.device_set_fps = libcamera_device_set_fps,
|
||||||
.device_set_rotation = libcamera_device_set_rotation,
|
.device_set_rotation = libcamera_device_set_rotation,
|
||||||
.device_set_option = libcamera_device_set_option,
|
.device_set_option = libcamera_device_set_option,
|
||||||
|
@ -42,6 +42,7 @@ typedef struct device_libcamera_s {
|
|||||||
std::shared_ptr<libcamera::CameraConfiguration> configuration;
|
std::shared_ptr<libcamera::CameraConfiguration> configuration;
|
||||||
std::shared_ptr<libcamera::FrameBufferAllocator> allocator;
|
std::shared_ptr<libcamera::FrameBufferAllocator> allocator;
|
||||||
libcamera::ControlList controls;
|
libcamera::ControlList controls;
|
||||||
|
libcamera::ControlList applied_controls;
|
||||||
bool vflip, hflip;
|
bool vflip, hflip;
|
||||||
} device_libcamera_t;
|
} device_libcamera_t;
|
||||||
|
|
||||||
@ -59,9 +60,11 @@ typedef struct buffer_libcamera_s {
|
|||||||
int libcamera_device_open(device_t *dev);
|
int libcamera_device_open(device_t *dev);
|
||||||
void libcamera_device_close(device_t *dev);
|
void libcamera_device_close(device_t *dev);
|
||||||
void libcamera_device_dump_options(device_t *dev, FILE *stream);
|
void libcamera_device_dump_options(device_t *dev, FILE *stream);
|
||||||
|
int libcamera_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque);
|
||||||
int libcamera_device_set_fps(device_t *dev, int desired_fps);
|
int libcamera_device_set_fps(device_t *dev, int desired_fps);
|
||||||
int libcamera_device_set_rotation(device_t *dev, bool vflip, bool hflip);
|
int libcamera_device_set_rotation(device_t *dev, bool vflip, bool hflip);
|
||||||
int libcamera_device_set_option(device_t *dev, const char *key, const char *value);
|
int libcamera_device_set_option(device_t *dev, const char *key, const char *value);
|
||||||
|
void libcamera_device_apply_controls(device_t *dev);
|
||||||
|
|
||||||
int libcamera_buffer_open(buffer_t *buf);
|
int libcamera_buffer_open(buffer_t *buf);
|
||||||
void libcamera_buffer_close(buffer_t *buf);
|
void libcamera_buffer_close(buffer_t *buf);
|
||||||
|
446
device/libcamera/options.cc
Normal file
446
device/libcamera/options.cc
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
#ifdef USE_LIBCAMERA
|
||||||
|
#include "libcamera.hh"
|
||||||
|
#include "third_party/magic_enum/include/magic_enum.hpp"
|
||||||
|
|
||||||
|
static std::string libcamera_device_option_normalize(std::string key)
|
||||||
|
{
|
||||||
|
key.resize(device_option_normalize_name(key.data(), key.data()));
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static std::map<unsigned, std::string> control_enum_values(const char *prefix)
|
||||||
|
{
|
||||||
|
std::map<unsigned, std::string> ret;
|
||||||
|
|
||||||
|
for (auto e : magic_enum::enum_entries<T>()) {
|
||||||
|
auto value = std::string(e.second);
|
||||||
|
if (prefix && value.find(prefix) == 0) {
|
||||||
|
value = value.substr(strlen(prefix));
|
||||||
|
}
|
||||||
|
ret[e.first] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct libcamera_control_id_t
|
||||||
|
{
|
||||||
|
const libcamera::ControlId *control_id;
|
||||||
|
std::map<unsigned, std::string> enum_values;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define LIBCAMERA_CONTROL_RAW(Name, Strip) \
|
||||||
|
{ Name.id(), { .control_id = &Name, .enum_values = control_enum_values<Name##Enum>(Strip), } }
|
||||||
|
|
||||||
|
#define LIBCAMERA_CONTROL(Name, Strip) \
|
||||||
|
LIBCAMERA_CONTROL_RAW(libcamera::controls::Name, Strip ? Strip : #Name)
|
||||||
|
|
||||||
|
#define LIBCAMERA_DRAFT_CONTROL(Name) \
|
||||||
|
LIBCAMERA_CONTROL_RAW(libcamera::controls::draft::Name, #Name)
|
||||||
|
|
||||||
|
static std::map<unsigned, libcamera_control_id_t> libcamera_control_ids =
|
||||||
|
{
|
||||||
|
LIBCAMERA_CONTROL(AeMeteringMode, "Metering"),
|
||||||
|
LIBCAMERA_CONTROL(AeConstraintMode, "Constraint"),
|
||||||
|
LIBCAMERA_CONTROL(AeExposureMode, "Exposure"),
|
||||||
|
LIBCAMERA_CONTROL(AwbMode, "Awb"),
|
||||||
|
LIBCAMERA_CONTROL(AfMode, "AfMode"),
|
||||||
|
LIBCAMERA_CONTROL(AfRange, "AfRange"),
|
||||||
|
LIBCAMERA_CONTROL(AfSpeed, "AfSpeed"),
|
||||||
|
LIBCAMERA_CONTROL(AfTrigger, "AfTrigger"),
|
||||||
|
LIBCAMERA_CONTROL(AfState, "AfState"),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(AePrecaptureTrigger),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(NoiseReductionMode),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(ColorCorrectionAberrationMode),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(AeState),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(AwbState),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(LensShadingMapMode),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(SceneFlicker),
|
||||||
|
LIBCAMERA_DRAFT_CONTROL(TestPatternMode)
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto control_type_values = control_enum_values<libcamera::ControlType>("ControlType");
|
||||||
|
|
||||||
|
static const std::map<unsigned, std::string> *libcamera_find_control_ids(unsigned control_id)
|
||||||
|
{
|
||||||
|
auto iter = libcamera_control_ids.find(control_id);
|
||||||
|
if (iter == libcamera_control_ids.end())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return &iter->second.enum_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long long libcamera_find_control_id_named_value(unsigned control_id, const char *name)
|
||||||
|
{
|
||||||
|
auto named_values = libcamera_find_control_ids(control_id);
|
||||||
|
if (named_values) {
|
||||||
|
for (const auto & named_value : *named_values) {
|
||||||
|
if (device_option_is_equal(named_value.second.c_str(), name))
|
||||||
|
return named_value.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoll(name, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static libcamera::ControlInfoMap::Map libcamera_control_list(device_t *dev)
|
||||||
|
{
|
||||||
|
libcamera::ControlInfoMap::Map controls_map;
|
||||||
|
for (auto const &control : dev->libcamera->camera->controls()) {
|
||||||
|
controls_map[control.first] = control.second;
|
||||||
|
}
|
||||||
|
return controls_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void libcamera_device_dump_options(device_t *dev, FILE *stream)
|
||||||
|
{
|
||||||
|
auto &properties = dev->libcamera->camera->properties();
|
||||||
|
auto idMap = properties.idMap();
|
||||||
|
|
||||||
|
fprintf(stream, "%s Properties:\n", dev->name);
|
||||||
|
|
||||||
|
for (auto const &control : properties) {
|
||||||
|
if (!control.first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto control_id = control.first;
|
||||||
|
auto control_value = control.second;
|
||||||
|
std::string control_id_name = "";
|
||||||
|
|
||||||
|
if (auto control_id_info = idMap ? idMap->at(control_id) : NULL) {
|
||||||
|
control_id_name = control_id_info->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stream, "- property: %s (%08x, type=%s) = %s\n",
|
||||||
|
control_id_name.c_str(), control_id,
|
||||||
|
control_type_values[control_value.type()].c_str(),
|
||||||
|
control_value.toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stream, "\n");
|
||||||
|
fprintf(stream, "%s Options:\n", dev->name);
|
||||||
|
|
||||||
|
for (auto const &control : libcamera_control_list(dev)) {
|
||||||
|
if (!control.first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto control_id = control.first;
|
||||||
|
auto control_key = libcamera_device_option_normalize(control_id->name());
|
||||||
|
auto control_info = control.second;
|
||||||
|
|
||||||
|
fprintf(stream, "- available option: %s (%08x, type=%s): %s\n",
|
||||||
|
control_id->name().c_str(), control_id->id(),
|
||||||
|
control_type_values[control_id->type()].c_str(),
|
||||||
|
control_info.toString().c_str());
|
||||||
|
|
||||||
|
auto named_values = libcamera_find_control_ids(control_id->id());
|
||||||
|
|
||||||
|
if (named_values != NULL) {
|
||||||
|
for (const auto & named_value : *named_values) {
|
||||||
|
fprintf(stream, "\t\t%d: %s\n", named_value.first, named_value.second.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < control_info.values().size(); i++) {
|
||||||
|
fprintf(stream, "\t\t%s\n", control_info.values()[i].toString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stream, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int libcamera_device_dump_control_option(device_option_fn fn, void *opaque, const libcamera::ControlId &control_id, const libcamera::ControlInfo *control_info, const libcamera::ControlValue *control_value, bool read_only)
|
||||||
|
{
|
||||||
|
device_option_t opt = {
|
||||||
|
.control_id = control_id.id()
|
||||||
|
};
|
||||||
|
opt.flags.read_only = read_only;
|
||||||
|
strcpy(opt.name, control_id.name().c_str());
|
||||||
|
|
||||||
|
if (control_info) {
|
||||||
|
strcpy(opt.description, control_info->toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control_value) {
|
||||||
|
if (!control_value->isNone()) {
|
||||||
|
strcpy(opt.value, control_value->toString().c_str());
|
||||||
|
} else {
|
||||||
|
control_value = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (control_id.type()) {
|
||||||
|
case libcamera::ControlTypeNone:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeBool:
|
||||||
|
opt.type = device_option_type_bool;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeByte:
|
||||||
|
opt.type = device_option_type_u8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeInteger32:
|
||||||
|
opt.type = device_option_type_integer;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeInteger64:
|
||||||
|
opt.type = device_option_type_integer64;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeFloat:
|
||||||
|
opt.type = device_option_type_float;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeRectangle:
|
||||||
|
opt.type = device_option_type_float;
|
||||||
|
opt.elems = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeSize:
|
||||||
|
opt.type = device_option_type_float;
|
||||||
|
opt.elems = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeString:
|
||||||
|
opt.type = device_option_type_string;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto named_values = libcamera_find_control_ids(control_id.id());
|
||||||
|
|
||||||
|
if (named_values != NULL) {
|
||||||
|
for (const auto & named_value : *named_values) {
|
||||||
|
if (opt.menu_items >= MAX_DEVICE_OPTION_MENU) {
|
||||||
|
opt.flags.invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_option_menu_t *opt_menu = &opt.menu[opt.menu_items++];
|
||||||
|
opt_menu->id = named_value.first;
|
||||||
|
strcpy(opt_menu->name, named_value.second.c_str());
|
||||||
|
if (control_value && atoi(control_value->toString().c_str()) == opt_menu->id) {
|
||||||
|
strcpy(opt.value, opt_menu->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (control_info) {
|
||||||
|
for (size_t i = 0; i < control_info->values().size(); i++) {
|
||||||
|
if (opt.menu_items >= MAX_DEVICE_OPTION_MENU) {
|
||||||
|
opt.flags.invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_option_menu_t *opt_menu = &opt.menu[opt.menu_items++];
|
||||||
|
opt_menu->id = atoi(control_info->values()[i].toString().c_str());
|
||||||
|
strcpy(opt_menu->name, control_info->values()[i].toString().c_str());
|
||||||
|
if (control_value && atoi(control_value->toString().c_str()) == opt_menu->id) {
|
||||||
|
strcpy(opt.value, opt_menu->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (control_value && control_value->numElements() > 0) {
|
||||||
|
opt.elems = control_value->numElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.type) {
|
||||||
|
int ret = fn(&opt, opaque);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int libcamera_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque)
|
||||||
|
{
|
||||||
|
auto &properties = dev->libcamera->camera->properties();
|
||||||
|
auto idMap = properties.idMap();
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
for (auto const &control : properties) {
|
||||||
|
if (!control.first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto control_id = control.first;
|
||||||
|
auto control_value = control.second;
|
||||||
|
std::string control_id_name = "";
|
||||||
|
|
||||||
|
if (auto control_id_info = idMap ? idMap->at(control_id) : NULL) {
|
||||||
|
int ret = libcamera_device_dump_control_option(fn, opaque, *control_id_info, NULL, &control_value, true);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const &control : libcamera_control_list(dev)) {
|
||||||
|
if (!control.first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto control_id = control.first;
|
||||||
|
auto control_info = control.second;
|
||||||
|
|
||||||
|
auto control_value = dev->libcamera->applied_controls.contains(control_id->id())
|
||||||
|
? &dev->libcamera->applied_controls.get(control_id->id())
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
int ret = libcamera_device_dump_control_option(fn, opaque,
|
||||||
|
*control_id, &control_info, control_value, false);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static libcamera::Rectangle libcamera_parse_rectangle(const char *value)
|
||||||
|
{
|
||||||
|
static const char *RECTANGLE_PATTERNS[] =
|
||||||
|
{
|
||||||
|
"(%d,%d)/%ux%u",
|
||||||
|
"%d,%d,%u,%u",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; RECTANGLE_PATTERNS[i]; i++) {
|
||||||
|
libcamera::Rectangle rectangle;
|
||||||
|
|
||||||
|
if (4 == sscanf(value, RECTANGLE_PATTERNS[i],
|
||||||
|
&rectangle.x, &rectangle.y,
|
||||||
|
&rectangle.width, &rectangle.height)) {
|
||||||
|
return rectangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libcamera::Rectangle();
|
||||||
|
}
|
||||||
|
|
||||||
|
static libcamera::Size libcamera_parse_size(const char *value)
|
||||||
|
{
|
||||||
|
static const char *SIZE_PATTERNS[] =
|
||||||
|
{
|
||||||
|
"%ux%u",
|
||||||
|
"%u,%u",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; SIZE_PATTERNS[i]; i++) {
|
||||||
|
libcamera::Size size;
|
||||||
|
|
||||||
|
if (2 == sscanf(value, SIZE_PATTERNS[i], &size.width, &size.height)) {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libcamera::Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename F>
|
||||||
|
static bool libcamera_parse_control_value(libcamera::ControlValue &control_value, const char *value, const F &fn)
|
||||||
|
{
|
||||||
|
std::vector<T> parsed;
|
||||||
|
|
||||||
|
while (value && *value) {
|
||||||
|
std::string current_value;
|
||||||
|
|
||||||
|
if (const char *next = strchr(value, ',')) {
|
||||||
|
current_value.assign(value, next);
|
||||||
|
value = &next[1];
|
||||||
|
} else {
|
||||||
|
current_value.assign(value);
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_value.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
parsed.push_back(fn(current_value.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.size() > 1) {
|
||||||
|
control_value.set<libcamera::Span<T> >(parsed);
|
||||||
|
} else {
|
||||||
|
control_value.set<T>(parsed[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int libcamera_device_set_option(device_t *dev, const char *keyp, const char *value)
|
||||||
|
{
|
||||||
|
for (auto const &control : libcamera_control_list(dev)) {
|
||||||
|
if (!control.first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto control_id = control.first;
|
||||||
|
auto control_key = control_id->name();
|
||||||
|
if (!device_option_is_equal(keyp, control_key.c_str()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
libcamera::ControlValue control_value;
|
||||||
|
|
||||||
|
switch (control_id->type()) {
|
||||||
|
case libcamera::ControlTypeNone:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeBool:
|
||||||
|
control_value.set<bool>(atoi(value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeByte:
|
||||||
|
libcamera_parse_control_value<unsigned char>(control_value, value,
|
||||||
|
[control_id](const char *value) { return libcamera_find_control_id_named_value(control_id->id(), value); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeInteger32:
|
||||||
|
libcamera_parse_control_value<int32_t>(control_value, value,
|
||||||
|
[control_id](const char *value) { return libcamera_find_control_id_named_value(control_id->id(), value); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeInteger64:
|
||||||
|
libcamera_parse_control_value<int64_t>(control_value, value,
|
||||||
|
[control_id](const char *value) { return libcamera_find_control_id_named_value(control_id->id(), value); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeFloat:
|
||||||
|
libcamera_parse_control_value<float>(control_value, value, atof);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeRectangle:
|
||||||
|
libcamera_parse_control_value<libcamera::Rectangle>(
|
||||||
|
control_value, value, libcamera_parse_rectangle);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeSize:
|
||||||
|
libcamera_parse_control_value<libcamera::Size>(
|
||||||
|
control_value, value, libcamera_parse_size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case libcamera::ControlTypeString:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control_value.isNone()) {
|
||||||
|
LOG_ERROR(dev, "The `%s` type `%d` is not supported.", control_key.c_str(), control_id->type());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(dev, "Configuring option '%s' (%08x, type=%d) = %s",
|
||||||
|
control_key.c_str(), control_id->id(), control_id->type(),
|
||||||
|
control_value.toString().c_str());
|
||||||
|
dev->libcamera->controls.set(control_id->id(), control_value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif // USE_LIBCAMERA
|
@ -13,7 +13,7 @@ int v4l2_device_set_option_by_id(device_t *dev, const char *name, uint32_t id, i
|
|||||||
|
|
||||||
ctl.id = id;
|
ctl.id = id;
|
||||||
ctl.value = value;
|
ctl.value = value;
|
||||||
LOG_DEBUG(dev, "Configuring option %s (%08x) = %d", name, id, value);
|
LOG_DEBUG(dev, "Configuring option '%s' (%08x) = %d", name, id, value);
|
||||||
ERR_IOCTL(dev, dev->v4l2->subdev_fd >= 0 ? dev->v4l2->subdev_fd : dev->v4l2->dev_fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name);
|
ERR_IOCTL(dev, dev->v4l2->subdev_fd >= 0 ? dev->v4l2->subdev_fd : dev->v4l2->dev_fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name);
|
||||||
return 0;
|
return 0;
|
||||||
error:
|
error:
|
||||||
@ -29,8 +29,6 @@ static int v4l2_device_query_control_iter_id(device_t *dev, int fd, uint32_t *id
|
|||||||
}
|
}
|
||||||
*id = qctrl.id;
|
*id = qctrl.id;
|
||||||
|
|
||||||
device_option_normalize_name(qctrl.name, qctrl.name);
|
|
||||||
|
|
||||||
if (qctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
|
if (qctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
|
||||||
LOG_VERBOSE(dev, "The '%s' is disabled", qctrl.name);
|
LOG_VERBOSE(dev, "The '%s' is disabled", qctrl.name);
|
||||||
return 0;
|
return 0;
|
||||||
@ -39,7 +37,7 @@ static int v4l2_device_query_control_iter_id(device_t *dev, int fd, uint32_t *id
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_VERBOSE(dev, "Available control: %s (%08x, type=%d)",
|
LOG_VERBOSE(dev, "Available control: '%s' (%08x, type=%d)",
|
||||||
qctrl.name, qctrl.id, qctrl.type);
|
qctrl.name, qctrl.id, qctrl.type);
|
||||||
|
|
||||||
dev->v4l2->controls = reallocarray(dev->v4l2->controls, dev->v4l2->ncontrols+1, sizeof(device_v4l2_control_t));
|
dev->v4l2->controls = reallocarray(dev->v4l2->controls, dev->v4l2->ncontrols+1, sizeof(device_v4l2_control_t));
|
||||||
@ -71,10 +69,8 @@ int v4l2_device_set_option(device_t *dev, const char *key, const char *value)
|
|||||||
device_v4l2_control_t *control = NULL;
|
device_v4l2_control_t *control = NULL;
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
|
|
||||||
device_option_normalize_name(keyp, keyp);
|
|
||||||
|
|
||||||
for (int i = 0; i < dev->v4l2->ncontrols; i++) {
|
for (int i = 0; i < dev->v4l2->ncontrols; i++) {
|
||||||
if (strcmp(dev->v4l2->controls[i].control.name, keyp) == 0) {
|
if (device_option_is_equal(dev->v4l2->controls[i].control.name, keyp)) {
|
||||||
control = &dev->v4l2->controls[i];
|
control = &dev->v4l2->controls[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -87,14 +83,40 @@ int v4l2_device_set_option(device_t *dev, const char *key, const char *value)
|
|||||||
|
|
||||||
switch(control->control.type) {
|
switch(control->control.type) {
|
||||||
case V4L2_CTRL_TYPE_INTEGER:
|
case V4L2_CTRL_TYPE_INTEGER:
|
||||||
|
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||||
case V4L2_CTRL_TYPE_BOOLEAN:
|
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||||
|
{
|
||||||
|
struct v4l2_control ctl = {
|
||||||
|
.id = control->control.id,
|
||||||
|
.value = atoi(value)
|
||||||
|
};
|
||||||
|
LOG_INFO(dev, "Configuring option '%s' (%08x) = %d", control->control.name, ctl.id, ctl.value);
|
||||||
|
ERR_IOCTL(dev, control->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", control->control.name);
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case V4L2_CTRL_TYPE_MENU:
|
case V4L2_CTRL_TYPE_MENU:
|
||||||
{
|
{
|
||||||
struct v4l2_control ctl = {
|
struct v4l2_control ctl = {
|
||||||
.id = control->control.id,
|
.id = control->control.id,
|
||||||
.value = atoi(value)
|
.value = atoi(value)
|
||||||
};
|
};
|
||||||
LOG_INFO(dev, "Configuring option %s (%08x) = %d", control->control.name, ctl.id, ctl.value);
|
|
||||||
|
for (int j = control->control.minimum; j <= control->control.maximum; j++) {
|
||||||
|
struct v4l2_querymenu menu = {
|
||||||
|
.id = control->control.id,
|
||||||
|
.index = j
|
||||||
|
};
|
||||||
|
|
||||||
|
if (0 == ioctl(control->fd, VIDIOC_QUERYMENU, &menu)) {
|
||||||
|
if (device_option_is_equal(valuep, (const char*)menu.name)) {
|
||||||
|
ctl.value = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO(dev, "Configuring option '%s' (%08x) = %d", control->control.name, ctl.id, ctl.value);
|
||||||
ERR_IOCTL(dev, control->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", control->control.name);
|
ERR_IOCTL(dev, control->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", control->control.name);
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
@ -138,7 +160,7 @@ int v4l2_device_set_option(device_t *dev, const char *key, const char *value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO(dev, "Configuring option %s (%08x) = [%d tokens, expected %d]",
|
LOG_INFO(dev, "Configuring option '%s' (%08x) = [%d tokens, expected %d]",
|
||||||
control->control.name, ctl.id, tokens, control->control.elems);
|
control->control.name, ctl.id, tokens, control->control.elems);
|
||||||
ERR_IOCTL(dev, control->fd, VIDIOC_S_EXT_CTRLS, &ctrls, "Can't set option %s", control->control.name);
|
ERR_IOCTL(dev, control->fd, VIDIOC_S_EXT_CTRLS, &ctrls, "Can't set option %s", control->control.name);
|
||||||
ret = 1;
|
ret = 1;
|
||||||
@ -163,8 +185,11 @@ void v4l2_device_dump_options(device_t *dev, FILE *stream)
|
|||||||
for (int i = 0; i < dev->v4l2->ncontrols; i++) {
|
for (int i = 0; i < dev->v4l2->ncontrols; i++) {
|
||||||
device_v4l2_control_t *control = &dev->v4l2->controls[i];
|
device_v4l2_control_t *control = &dev->v4l2->controls[i];
|
||||||
|
|
||||||
|
char name[512];
|
||||||
|
device_option_normalize_name(control->control.name, name);
|
||||||
|
|
||||||
fprintf(stream, "- available option: %s (%08x, type=%d): ",
|
fprintf(stream, "- available option: %s (%08x, type=%d): ",
|
||||||
control->control.name, control->control.id, control->control.type);
|
name, control->control.id, control->control.type);
|
||||||
|
|
||||||
switch(control->control.type) {
|
switch(control->control.type) {
|
||||||
case V4L2_CTRL_TYPE_U8:
|
case V4L2_CTRL_TYPE_U8:
|
||||||
@ -210,3 +235,163 @@ void v4l2_device_dump_options(device_t *dev, FILE *stream)
|
|||||||
}
|
}
|
||||||
fprintf(stream, "\n");
|
fprintf(stream, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int v4l2_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < dev->v4l2->ncontrols; i++) {
|
||||||
|
device_v4l2_control_t *control = &dev->v4l2->controls[i];
|
||||||
|
|
||||||
|
device_option_t opt = {
|
||||||
|
.control_id = control->control.id,
|
||||||
|
.elems = control->control.elems,
|
||||||
|
};
|
||||||
|
strcpy(opt.name, control->control.name);
|
||||||
|
|
||||||
|
char buf[8192];
|
||||||
|
|
||||||
|
struct v4l2_ext_control ext_control = {
|
||||||
|
.id = control->control.id,
|
||||||
|
.size = sizeof(buf),
|
||||||
|
.ptr = buf,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct v4l2_ext_controls ext_controls = {
|
||||||
|
.ctrl_class = 0,
|
||||||
|
.which = V4L2_CTRL_WHICH_CUR_VAL,
|
||||||
|
.count = 1,
|
||||||
|
.controls = &ext_control
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ext_control_valid = ioctl(control->fd, VIDIOC_G_EXT_CTRLS, &ext_controls) == 0;
|
||||||
|
|
||||||
|
switch(control->control.type) {
|
||||||
|
case V4L2_CTRL_TYPE_U8:
|
||||||
|
opt.type = device_option_type_u8;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%02x..%02x]",
|
||||||
|
(__u8)control->control.minimum & 0xFF, (__u8)control->control.maximum & 0xFF);
|
||||||
|
if (ext_control_valid) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < ext_control.size; i++) {
|
||||||
|
if (n + 3 >= sizeof(opt.value)) break;
|
||||||
|
if (i) opt.value[n++] = ' ';
|
||||||
|
n += sprintf(opt.value + n, "%02x", ext_control.p_u8[i]&0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_U16:
|
||||||
|
opt.type = device_option_type_u16;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%04x..%04x]",
|
||||||
|
(__u16)control->control.minimum & 0xFFFF, (__u16)control->control.maximum & 0xFFFF);
|
||||||
|
if (ext_control_valid) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < ext_control.size; i++) {
|
||||||
|
if (n + 5 >= sizeof(opt.value)) break;
|
||||||
|
if (i) opt.value[n++] = ' ';
|
||||||
|
n += sprintf(opt.value + n, "%04x", ext_control.p_u16[i]&0xFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_U32:
|
||||||
|
opt.type = device_option_type_u32;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%lld..%lld]",
|
||||||
|
control->control.minimum, control->control.maximum);
|
||||||
|
if (ext_control_valid) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < ext_control.size; i++) {
|
||||||
|
if (n + 9 >= sizeof(opt.value)) break;
|
||||||
|
if (i) opt.value[n++] = ' ';
|
||||||
|
n += sprintf(opt.value + n, "%08x", ext_control.p_u32[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||||
|
opt.type = device_option_type_bool;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%lld..%lld]",
|
||||||
|
control->control.minimum, control->control.maximum);
|
||||||
|
if (ext_control_valid)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%d", ext_control.value ? 1 : 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_INTEGER:
|
||||||
|
opt.type = device_option_type_integer;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%lld..%lld]",
|
||||||
|
control->control.minimum, control->control.maximum);
|
||||||
|
if (ext_control_valid)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%d", ext_control.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_INTEGER64:
|
||||||
|
opt.type = device_option_type_integer64;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%lld..%lld]",
|
||||||
|
control->control.minimum, control->control.maximum);
|
||||||
|
if (ext_control_valid)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%lld", ext_control.value64);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_BUTTON:
|
||||||
|
opt.type = device_option_type_bool;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "button");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_MENU:
|
||||||
|
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||||
|
opt.type = device_option_type_integer;
|
||||||
|
snprintf(opt.description, sizeof(opt.description), "[%lld..%lld]",
|
||||||
|
control->control.minimum, control->control.maximum);
|
||||||
|
|
||||||
|
if (ext_control_valid)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%d", ext_control.value);
|
||||||
|
|
||||||
|
for (int j = control->control.minimum; j <= control->control.maximum; j++) {
|
||||||
|
struct v4l2_querymenu menu = {
|
||||||
|
.id = control->control.id,
|
||||||
|
.index = j
|
||||||
|
};
|
||||||
|
|
||||||
|
if (0 != ioctl(control->fd, VIDIOC_QUERYMENU, &menu))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (opt.menu_items >= MAX_DEVICE_OPTION_MENU) {
|
||||||
|
opt.flags.invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_option_menu_t *opt_menu = &opt.menu[opt.menu_items++];
|
||||||
|
opt_menu->id = j;
|
||||||
|
|
||||||
|
if (control->control.type == V4L2_CTRL_TYPE_MENU) {
|
||||||
|
snprintf(opt_menu->name, sizeof(opt_menu->name), "%s", menu.name);
|
||||||
|
if (ext_control_valid && opt_menu->id == ext_control.value)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%s", menu.name);
|
||||||
|
} else {
|
||||||
|
snprintf(opt_menu->name, sizeof(opt_menu->name), "%lld", menu.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_CTRL_TYPE_STRING:
|
||||||
|
opt.type = device_option_type_string;
|
||||||
|
if (ext_control_valid)
|
||||||
|
snprintf(opt.value, sizeof(opt.value), "%s", ext_control.string);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
opt.flags.invalid = true; // unsupported
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.type) {
|
||||||
|
int ret = fn(&opt, opaque);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ device_hw_t v4l2_device_hw = {
|
|||||||
.device_close = v4l2_device_close,
|
.device_close = v4l2_device_close,
|
||||||
.device_video_force_key = v4l2_device_video_force_key,
|
.device_video_force_key = v4l2_device_video_force_key,
|
||||||
.device_dump_options = v4l2_device_dump_options,
|
.device_dump_options = v4l2_device_dump_options,
|
||||||
|
.device_dump_options2 = v4l2_device_dump_options2,
|
||||||
.device_set_fps = v4l2_device_set_fps,
|
.device_set_fps = v4l2_device_set_fps,
|
||||||
.device_set_option = v4l2_device_set_option,
|
.device_set_option = v4l2_device_set_option,
|
||||||
|
|
||||||
|
@ -9,9 +9,12 @@
|
|||||||
|
|
||||||
typedef struct buffer_s buffer_t;
|
typedef struct buffer_s buffer_t;
|
||||||
typedef struct buffer_list_s buffer_list_t;
|
typedef struct buffer_list_s buffer_list_t;
|
||||||
|
typedef struct device_option_s device_option_t;
|
||||||
typedef struct device_s device_t;
|
typedef struct device_s device_t;
|
||||||
struct pollfd;
|
struct pollfd;
|
||||||
|
|
||||||
|
typedef int device_option_fn(device_option_t *option, void *opaque);
|
||||||
|
|
||||||
typedef struct device_v4l2_control_s {
|
typedef struct device_v4l2_control_s {
|
||||||
int fd;
|
int fd;
|
||||||
struct v4l2_query_ext_ctrl control;
|
struct v4l2_query_ext_ctrl control;
|
||||||
@ -38,6 +41,7 @@ int v4l2_device_open(device_t *dev);
|
|||||||
void v4l2_device_close(device_t *dev);
|
void v4l2_device_close(device_t *dev);
|
||||||
int v4l2_device_video_force_key(device_t *dev);
|
int v4l2_device_video_force_key(device_t *dev);
|
||||||
void v4l2_device_dump_options(device_t *dev, FILE *stream);
|
void v4l2_device_dump_options(device_t *dev, FILE *stream);
|
||||||
|
int v4l2_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque);
|
||||||
int v4l2_device_set_fps(device_t *dev, int desired_fps);
|
int v4l2_device_set_fps(device_t *dev, int desired_fps);
|
||||||
int v4l2_device_set_option(device_t *dev, const char *key, const char *value);
|
int v4l2_device_set_option(device_t *dev, const char *key, const char *value);
|
||||||
|
|
||||||
|
1
third_party/magic_enum
vendored
Submodule
1
third_party/magic_enum
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 48054f64abcfc83f58d9aa2efbbd21604f93aca4
|
@ -4,17 +4,23 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static bool isalnum_dot(char c)
|
||||||
|
{
|
||||||
|
return isalnum(c) || c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
int device_option_normalize_name(const char *in, char *outp)
|
int device_option_normalize_name(const char *in, char *outp)
|
||||||
{
|
{
|
||||||
// The output is always shorter, so `outp=in`
|
// The output is always shorter, so `outp=in`
|
||||||
// colour_correction_matrix => colourcorrectionmatrix
|
// colour_correction_matrix => colourcorrectionmatrix
|
||||||
// Colour Correction Matrix => colourcorrectionmatrix
|
// Colour Correction Matrix => colourcorrectionmatrix
|
||||||
// ColourCorrectionMatrix => colourcorrectionmatrix
|
// ColourCorrectionMatrix => colourcorrectionmatrix
|
||||||
|
// Colour.Correction.Matrix => colour.correction.matrix
|
||||||
|
|
||||||
char *out = outp;
|
char *out = outp;
|
||||||
|
|
||||||
while (*in) {
|
while (*in) {
|
||||||
if (isalnum(*in)) {
|
if (isalnum_dot(*in)) {
|
||||||
*out++ = tolower(*in++);
|
*out++ = tolower(*in++);
|
||||||
} else {
|
} else {
|
||||||
in++;
|
in++;
|
||||||
@ -22,5 +28,26 @@ int device_option_normalize_name(const char *in, char *outp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
*out++ = 0;
|
*out++ = 0;
|
||||||
return out - outp;
|
return out - outp - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_option_is_equal(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
// Similar to device_option_normalize_name
|
||||||
|
// but ignore case sensitiveness
|
||||||
|
|
||||||
|
while (*a || *b) {
|
||||||
|
while (*a && !isalnum_dot(*a))
|
||||||
|
a++;
|
||||||
|
while (*b && !isalnum_dot(*b))
|
||||||
|
b++;
|
||||||
|
|
||||||
|
if (tolower(*a) != tolower(*b))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
int device_option_normalize_name(const char *in, char *out);
|
int device_option_normalize_name(const char *in, char *out);
|
||||||
|
bool device_option_is_equal(const char *a, const char *b);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user