Add common, media_ctrl and v4l2_ctrl modules with CLI drivers and docs
Modules (src/modules/): - common/error: App_Error struct with structured detail union, error codes, app_error_print(); designed for future upgrade to preprocessor-generated location codes - media_ctrl: media device enumeration, topology query (entities/pads/links), link enable/disable via Media Controller API (/dev/media*) - v4l2_ctrl: control enumeration (with menu item fetching), get/set via V4L2 ext controls API, device discovery (/dev/video*) All modules use -std=c11 -D_GNU_SOURCE, build artifacts go to build/ only. Kernel-version-dependent constants guarded with #ifdef + #warning. CLI drivers (dev/cli/): - media_ctrl_cli: list, info, topology, set-link subcommands - v4l2_ctrl_cli: list, controls, get, set subcommands Docs (docs/cli/): - media_ctrl_cli.md and v4l2_ctrl_cli.md with usage, examples, and context within the video routing system Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
17
src/modules/common/Makefile
Normal file
17
src/modules/common/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
ROOT := $(abspath ../../..)
|
||||
CC = gcc
|
||||
CFLAGS = -std=c11 -Wall -Wextra -D_GNU_SOURCE -I$(ROOT)/include
|
||||
BUILD = $(ROOT)/build/common
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BUILD)/error.o
|
||||
|
||||
$(BUILD)/error.o: error.c $(ROOT)/include/error.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(BUILD)/error.o
|
||||
29
src/modules/common/error.c
Normal file
29
src/modules/common/error.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
void app_error_print(struct App_Error *e) {
|
||||
if (APP_IS_OK(*e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s:%d: ", e->file, e->line);
|
||||
|
||||
switch (e->code) {
|
||||
case ERR_NONE:
|
||||
break;
|
||||
case ERR_SYSCALL:
|
||||
fprintf(stderr, "syscall error: %s\n", strerror(e->detail.syscall.err_no));
|
||||
break;
|
||||
case ERR_INVALID:
|
||||
fprintf(stderr, "invalid argument\n");
|
||||
break;
|
||||
case ERR_NOT_FOUND:
|
||||
fprintf(stderr, "not found\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown error (code %d)\n", (int)e->code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
17
src/modules/media_ctrl/Makefile
Normal file
17
src/modules/media_ctrl/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
ROOT := $(abspath ../../..)
|
||||
CC = gcc
|
||||
CFLAGS = -std=c11 -Wall -Wextra -D_GNU_SOURCE -I$(ROOT)/include
|
||||
BUILD = $(ROOT)/build/media_ctrl
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BUILD)/media_ctrl.o
|
||||
|
||||
$(BUILD)/media_ctrl.o: media_ctrl.c $(ROOT)/include/media_ctrl.h $(ROOT)/include/error.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(BUILD)/media_ctrl.o
|
||||
283
src/modules/media_ctrl/media_ctrl.c
Normal file
283
src/modules/media_ctrl/media_ctrl.c
Normal file
@@ -0,0 +1,283 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <glob.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/media.h>
|
||||
|
||||
/*
|
||||
* Kernel compatibility checks.
|
||||
* The media entity type constants were renamed between kernel versions.
|
||||
* Older kernels use MEDIA_ENT_T_* under MEDIA_ENT_TYPE_MASK.
|
||||
* Newer kernels use MEDIA_ENT_F_* (function-based naming).
|
||||
* We support the older API here; warn clearly if the expected symbols are absent.
|
||||
*/
|
||||
#ifndef MEDIA_ENT_ID_FLAG_NEXT
|
||||
# warning "MEDIA_ENT_ID_FLAG_NEXT not defined — entity enumeration will not work"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_ENT_TYPE_MASK
|
||||
# warning "MEDIA_ENT_TYPE_MASK not defined — entity type classification will not work"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_ENT_T_DEVNODE
|
||||
# warning "MEDIA_ENT_T_DEVNODE not defined — devnode entity type will not be recognised"
|
||||
# define MEDIA_ENT_T_DEVNODE 0x00000002
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_ENT_T_V4L2_SUBDEV
|
||||
# warning "MEDIA_ENT_T_V4L2_SUBDEV not defined — subdev entity type will not be recognised"
|
||||
# define MEDIA_ENT_T_V4L2_SUBDEV 0x00020000
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_LNK_FL_ENABLED
|
||||
# warning "MEDIA_LNK_FL_ENABLED not defined"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_LNK_FL_IMMUTABLE
|
||||
# warning "MEDIA_LNK_FL_IMMUTABLE not defined"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_PAD_FL_SOURCE
|
||||
# warning "MEDIA_PAD_FL_SOURCE not defined"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_PAD_FL_SINK
|
||||
# warning "MEDIA_PAD_FL_SINK not defined"
|
||||
#endif
|
||||
|
||||
#include "error.h"
|
||||
#include "media_ctrl.h"
|
||||
|
||||
struct Media_Ctrl {
|
||||
int fd;
|
||||
char device_path[256];
|
||||
};
|
||||
|
||||
struct App_Error media_ctrl_find_devices(
|
||||
void (*callback)(const char *device_path, void *userdata),
|
||||
void *userdata)
|
||||
{
|
||||
glob_t g;
|
||||
|
||||
int r = glob("/dev/media*", 0, NULL, &g);
|
||||
if (r == GLOB_NOMATCH) {
|
||||
globfree(&g);
|
||||
return APP_OK;
|
||||
}
|
||||
if (r != 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < g.gl_pathc; i++) {
|
||||
callback(g.gl_pathv[i], userdata);
|
||||
}
|
||||
|
||||
globfree(&g);
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error media_ctrl_open(const char *device_path, struct Media_Ctrl **out) {
|
||||
int fd = open(device_path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
struct Media_Ctrl *ctrl = malloc(sizeof(struct Media_Ctrl));
|
||||
if (!ctrl) {
|
||||
close(fd);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
ctrl->fd = fd;
|
||||
strncpy(ctrl->device_path, device_path, sizeof(ctrl->device_path) - 1);
|
||||
ctrl->device_path[sizeof(ctrl->device_path) - 1] = '\0';
|
||||
|
||||
*out = ctrl;
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
void media_ctrl_close(struct Media_Ctrl *ctrl) {
|
||||
if (!ctrl) {
|
||||
return;
|
||||
}
|
||||
close(ctrl->fd);
|
||||
free(ctrl);
|
||||
}
|
||||
|
||||
struct App_Error media_ctrl_get_info(
|
||||
struct Media_Ctrl *ctrl,
|
||||
struct Media_Device_Info *out)
|
||||
{
|
||||
struct media_device_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
||||
if (ioctl(ctrl->fd, MEDIA_IOC_DEVICE_INFO, &info) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
memcpy(out->driver, info.driver, sizeof(out->driver));
|
||||
memcpy(out->model, info.model, sizeof(out->model));
|
||||
memcpy(out->serial, info.serial, sizeof(out->serial));
|
||||
memcpy(out->bus_info, info.bus_info, sizeof(out->bus_info));
|
||||
out->media_version = info.media_version;
|
||||
out->hw_revision = info.hw_revision;
|
||||
out->driver_version = info.driver_version;
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error media_ctrl_enum_entities(
|
||||
struct Media_Ctrl *ctrl,
|
||||
void (*callback)(const struct Media_Entity *entity, void *userdata),
|
||||
void *userdata)
|
||||
{
|
||||
struct media_entity_desc desc;
|
||||
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.id = MEDIA_ENT_ID_FLAG_NEXT;
|
||||
|
||||
while (ioctl(ctrl->fd, MEDIA_IOC_ENUM_ENTITIES, &desc) == 0) {
|
||||
struct Media_Entity entity;
|
||||
memset(&entity, 0, sizeof(entity));
|
||||
|
||||
entity.id = desc.id;
|
||||
entity.type = desc.type;
|
||||
entity.flags = desc.flags;
|
||||
entity.pad_count = desc.pads;
|
||||
entity.link_count = desc.links;
|
||||
strncpy(entity.name, desc.name, sizeof(entity.name) - 1);
|
||||
|
||||
/* Extract device node info when available */
|
||||
if (desc.type == MEDIA_ENT_T_DEVNODE ||
|
||||
(desc.type & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_T_DEVNODE)
|
||||
{
|
||||
entity.dev_major = desc.v4l.major;
|
||||
entity.dev_minor = desc.v4l.minor;
|
||||
} else {
|
||||
entity.dev_major = 0;
|
||||
entity.dev_minor = 0;
|
||||
}
|
||||
|
||||
callback(&entity, userdata);
|
||||
|
||||
desc.id |= MEDIA_ENT_ID_FLAG_NEXT;
|
||||
}
|
||||
|
||||
if (errno != EINVAL) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error media_ctrl_enum_entity_pads_and_links(
|
||||
struct Media_Ctrl *ctrl,
|
||||
const struct Media_Entity *entity,
|
||||
void (*pad_callback)(const struct Media_Pad *pad, void *userdata),
|
||||
void (*link_callback)(const struct Media_Link *link, void *userdata),
|
||||
void *userdata)
|
||||
{
|
||||
struct media_links_enum links_enum;
|
||||
struct media_pad_desc *pads = NULL;
|
||||
struct media_link_desc *links = NULL;
|
||||
struct App_Error err = APP_OK;
|
||||
|
||||
memset(&links_enum, 0, sizeof(links_enum));
|
||||
links_enum.entity = entity->id;
|
||||
|
||||
if (entity->pad_count > 0) {
|
||||
pads = calloc(entity->pad_count, sizeof(struct media_pad_desc));
|
||||
if (!pads) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
if (entity->link_count > 0) {
|
||||
links = calloc(entity->link_count, sizeof(struct media_link_desc));
|
||||
if (!links) {
|
||||
free(pads);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
links_enum.pads = pads;
|
||||
links_enum.links = links;
|
||||
|
||||
if (ioctl(ctrl->fd, MEDIA_IOC_ENUM_LINKS, &links_enum) < 0) {
|
||||
err = APP_SYSCALL_ERROR();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pad_callback) {
|
||||
for (uint16_t i = 0; i < entity->pad_count; i++) {
|
||||
struct Media_Pad pad;
|
||||
pad.entity_id = pads[i].entity;
|
||||
pad.index = pads[i].index;
|
||||
pad.flags = pads[i].flags;
|
||||
pad_callback(&pad, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
if (link_callback) {
|
||||
for (uint16_t i = 0; i < entity->link_count; i++) {
|
||||
struct Media_Link link;
|
||||
link.source.entity_id = links[i].source.entity;
|
||||
link.source.index = links[i].source.index;
|
||||
link.source.flags = links[i].source.flags;
|
||||
link.sink.entity_id = links[i].sink.entity;
|
||||
link.sink.index = links[i].sink.index;
|
||||
link.sink.flags = links[i].sink.flags;
|
||||
link.flags = links[i].flags;
|
||||
link_callback(&link, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free(pads);
|
||||
free(links);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct App_Error media_ctrl_set_link(
|
||||
struct Media_Ctrl *ctrl,
|
||||
uint32_t source_entity_id, uint16_t source_pad_index,
|
||||
uint32_t sink_entity_id, uint16_t sink_pad_index,
|
||||
int enabled)
|
||||
{
|
||||
struct media_link_desc desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
|
||||
desc.source.entity = source_entity_id;
|
||||
desc.source.index = source_pad_index;
|
||||
desc.sink.entity = sink_entity_id;
|
||||
desc.sink.index = sink_pad_index;
|
||||
desc.flags = enabled ? MEDIA_LNK_FL_ENABLED : 0;
|
||||
|
||||
if (ioctl(ctrl->fd, MEDIA_IOC_SETUP_LINK, &desc) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
const char *media_entity_type_name(uint32_t type) {
|
||||
switch (type & MEDIA_ENT_TYPE_MASK) {
|
||||
case MEDIA_ENT_T_DEVNODE: return "devnode";
|
||||
case MEDIA_ENT_T_V4L2_SUBDEV: return "v4l2-subdev";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *media_pad_flag_name(uint32_t flags) {
|
||||
if (flags & MEDIA_PAD_FL_SOURCE) {
|
||||
return "source";
|
||||
}
|
||||
if (flags & MEDIA_PAD_FL_SINK) {
|
||||
return "sink";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
17
src/modules/v4l2_ctrl/Makefile
Normal file
17
src/modules/v4l2_ctrl/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
ROOT := $(abspath ../../..)
|
||||
CC = gcc
|
||||
CFLAGS = -std=c11 -Wall -Wextra -D_GNU_SOURCE -I$(ROOT)/include
|
||||
BUILD = $(ROOT)/build/v4l2_ctrl
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BUILD)/v4l2_ctrl.o
|
||||
|
||||
$(BUILD)/v4l2_ctrl.o: v4l2_ctrl.c $(ROOT)/include/v4l2_ctrl.h $(ROOT)/include/error.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(BUILD)/v4l2_ctrl.o
|
||||
292
src/modules/v4l2_ctrl/v4l2_ctrl.c
Normal file
292
src/modules/v4l2_ctrl/v4l2_ctrl.c
Normal file
@@ -0,0 +1,292 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <glob.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
/* Kernel compatibility checks. */
|
||||
#ifndef VIDIOC_QUERY_EXT_CTRL
|
||||
# warning "VIDIOC_QUERY_EXT_CTRL not defined — requires kernel >= 3.6; control enumeration will not work"
|
||||
#endif
|
||||
|
||||
#ifndef V4L2_CTRL_FLAG_NEXT_CTRL
|
||||
# warning "V4L2_CTRL_FLAG_NEXT_CTRL not defined"
|
||||
#endif
|
||||
|
||||
#ifndef V4L2_CTRL_FLAG_NEXT_COMPOUND
|
||||
# warning "V4L2_CTRL_FLAG_NEXT_COMPOUND not defined"
|
||||
# define V4L2_CTRL_FLAG_NEXT_COMPOUND 0
|
||||
#endif
|
||||
|
||||
#ifndef V4L2_CTRL_WHICH_CUR_VAL
|
||||
# warning "V4L2_CTRL_WHICH_CUR_VAL not defined"
|
||||
# define V4L2_CTRL_WHICH_CUR_VAL 0
|
||||
#endif
|
||||
|
||||
#include "error.h"
|
||||
#include "v4l2_ctrl.h"
|
||||
|
||||
struct V4l2_Ctrl_Handle {
|
||||
int fd;
|
||||
char device_path[256];
|
||||
};
|
||||
|
||||
struct App_Error v4l2_ctrl_find_devices(
|
||||
void (*callback)(const char *device_path, void *userdata),
|
||||
void *userdata)
|
||||
{
|
||||
glob_t g;
|
||||
|
||||
int r = glob("/dev/video*", 0, NULL, &g);
|
||||
if (r == GLOB_NOMATCH) {
|
||||
globfree(&g);
|
||||
return APP_OK;
|
||||
}
|
||||
if (r != 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < g.gl_pathc; i++) {
|
||||
callback(g.gl_pathv[i], userdata);
|
||||
}
|
||||
|
||||
globfree(&g);
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error v4l2_ctrl_open(const char *device_path, struct V4l2_Ctrl_Handle **out) {
|
||||
int fd = open(device_path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
struct V4l2_Ctrl_Handle *handle = malloc(sizeof(struct V4l2_Ctrl_Handle));
|
||||
if (!handle) {
|
||||
close(fd);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
handle->fd = fd;
|
||||
strncpy(handle->device_path, device_path, sizeof(handle->device_path) - 1);
|
||||
handle->device_path[sizeof(handle->device_path) - 1] = '\0';
|
||||
|
||||
*out = handle;
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
void v4l2_ctrl_close(struct V4l2_Ctrl_Handle *handle) {
|
||||
if (!handle) {
|
||||
return;
|
||||
}
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static V4l2_Ctrl_Type map_ctrl_type(uint32_t kernel_type) {
|
||||
switch (kernel_type) {
|
||||
case V4L2_CTRL_TYPE_INTEGER: return CTRL_TYPE_INTEGER;
|
||||
case V4L2_CTRL_TYPE_BOOLEAN: return CTRL_TYPE_BOOLEAN;
|
||||
case V4L2_CTRL_TYPE_MENU: return CTRL_TYPE_MENU;
|
||||
case V4L2_CTRL_TYPE_BUTTON: return CTRL_TYPE_BUTTON;
|
||||
case V4L2_CTRL_TYPE_INTEGER64: return CTRL_TYPE_INTEGER64;
|
||||
case V4L2_CTRL_TYPE_CTRL_CLASS: return CTRL_TYPE_CTRL_CLASS;
|
||||
case V4L2_CTRL_TYPE_STRING: return CTRL_TYPE_STRING;
|
||||
case V4L2_CTRL_TYPE_BITMASK: return CTRL_TYPE_BITMASK;
|
||||
case V4L2_CTRL_TYPE_INTEGER_MENU: return CTRL_TYPE_INTEGER_MENU;
|
||||
default: return CTRL_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch menu items for a menu or integer-menu control.
|
||||
* Returns a malloc'd array and writes the count to count_out.
|
||||
* Caller must free the returned array.
|
||||
*/
|
||||
static struct V4l2_Menu_Item *fetch_menu_items(
|
||||
int fd,
|
||||
const struct v4l2_query_ext_ctrl *qc,
|
||||
uint32_t *count_out)
|
||||
{
|
||||
uint32_t n = 0;
|
||||
|
||||
/* Count valid items first */
|
||||
for (int64_t i = qc->minimum; i <= qc->maximum; i += qc->step > 0 ? qc->step : 1) {
|
||||
struct v4l2_querymenu qm;
|
||||
memset(&qm, 0, sizeof(qm));
|
||||
qm.id = qc->id;
|
||||
qm.index = (uint32_t)i;
|
||||
if (ioctl(fd, VIDIOC_QUERYMENU, &qm) == 0) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
*count_out = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct V4l2_Menu_Item *items = calloc(n, sizeof(struct V4l2_Menu_Item));
|
||||
if (!items) {
|
||||
*count_out = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t idx = 0;
|
||||
for (int64_t i = qc->minimum; i <= qc->maximum; i += qc->step > 0 ? qc->step : 1) {
|
||||
struct v4l2_querymenu qm;
|
||||
memset(&qm, 0, sizeof(qm));
|
||||
qm.id = qc->id;
|
||||
qm.index = (uint32_t)i;
|
||||
if (ioctl(fd, VIDIOC_QUERYMENU, &qm) == 0) {
|
||||
items[idx].index = qm.index;
|
||||
if (qc->type == V4L2_CTRL_TYPE_MENU) {
|
||||
strncpy(items[idx].name, (char *)qm.name, sizeof(items[idx].name) - 1);
|
||||
items[idx].value = (int64_t)qm.index;
|
||||
} else {
|
||||
items[idx].value = qm.value;
|
||||
snprintf(items[idx].name, sizeof(items[idx].name), "%lld", (long long)qm.value);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
*count_out = idx;
|
||||
return items;
|
||||
}
|
||||
|
||||
struct App_Error v4l2_ctrl_enumerate(
|
||||
struct V4l2_Ctrl_Handle *handle,
|
||||
void (*callback)(
|
||||
const struct V4l2_Ctrl_Desc *desc,
|
||||
uint32_t menu_count,
|
||||
const struct V4l2_Menu_Item *menu_items,
|
||||
void *userdata),
|
||||
void *userdata)
|
||||
{
|
||||
struct v4l2_query_ext_ctrl qc;
|
||||
memset(&qc, 0, sizeof(qc));
|
||||
qc.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
|
||||
|
||||
while (ioctl(handle->fd, VIDIOC_QUERY_EXT_CTRL, &qc) == 0) {
|
||||
/* Skip control class headers */
|
||||
if (qc.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
|
||||
qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct V4l2_Ctrl_Desc desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
|
||||
desc.id = qc.id;
|
||||
desc.type = map_ctrl_type(qc.type);
|
||||
desc.min = (int32_t)qc.minimum;
|
||||
desc.max = (int32_t)qc.maximum;
|
||||
desc.step = (int32_t)qc.step;
|
||||
desc.default_value = (int32_t)qc.default_value;
|
||||
desc.flags = qc.flags;
|
||||
strncpy(desc.name, qc.name, sizeof(desc.name) - 1);
|
||||
|
||||
/* Fetch current value */
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
memset(&ctrls, 0, sizeof(ctrls));
|
||||
ctrl.id = qc.id;
|
||||
ctrls.controls = &ctrl;
|
||||
ctrls.count = 1;
|
||||
ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
|
||||
|
||||
if (ioctl(handle->fd, VIDIOC_G_EXT_CTRLS, &ctrls) == 0) {
|
||||
desc.current_value = ctrl.value;
|
||||
} else {
|
||||
desc.current_value = desc.default_value;
|
||||
}
|
||||
|
||||
struct V4l2_Menu_Item *menu_items = NULL;
|
||||
uint32_t menu_count = 0;
|
||||
|
||||
if (qc.type == V4L2_CTRL_TYPE_MENU ||
|
||||
qc.type == V4L2_CTRL_TYPE_INTEGER_MENU)
|
||||
{
|
||||
menu_items = fetch_menu_items(handle->fd, &qc, &menu_count);
|
||||
}
|
||||
|
||||
callback(&desc, menu_count, menu_items, userdata);
|
||||
|
||||
free(menu_items);
|
||||
|
||||
qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
|
||||
}
|
||||
|
||||
if (errno != EINVAL) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error v4l2_ctrl_get(
|
||||
struct V4l2_Ctrl_Handle *handle,
|
||||
uint32_t id,
|
||||
int32_t *value_out)
|
||||
{
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
memset(&ctrls, 0, sizeof(ctrls));
|
||||
|
||||
ctrl.id = id;
|
||||
ctrls.controls = &ctrl;
|
||||
ctrls.count = 1;
|
||||
ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
|
||||
|
||||
if (ioctl(handle->fd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
*value_out = ctrl.value;
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error v4l2_ctrl_set(
|
||||
struct V4l2_Ctrl_Handle *handle,
|
||||
uint32_t id,
|
||||
int32_t value)
|
||||
{
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
memset(&ctrls, 0, sizeof(ctrls));
|
||||
|
||||
ctrl.id = id;
|
||||
ctrl.value = value;
|
||||
ctrls.controls = &ctrl;
|
||||
ctrls.count = 1;
|
||||
ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
|
||||
|
||||
if (ioctl(handle->fd, VIDIOC_S_EXT_CTRLS, &ctrls) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
const char *v4l2_ctrl_type_name(V4l2_Ctrl_Type type) {
|
||||
switch (type) {
|
||||
case CTRL_TYPE_INTEGER: return "int";
|
||||
case CTRL_TYPE_BOOLEAN: return "bool";
|
||||
case CTRL_TYPE_MENU: return "menu";
|
||||
case CTRL_TYPE_BUTTON: return "button";
|
||||
case CTRL_TYPE_INTEGER64: return "int64";
|
||||
case CTRL_TYPE_CTRL_CLASS: return "class";
|
||||
case CTRL_TYPE_STRING: return "string";
|
||||
case CTRL_TYPE_BITMASK: return "bitmask";
|
||||
case CTRL_TYPE_INTEGER_MENU: return "int-menu";
|
||||
case CTRL_TYPE_UNKNOWN: /* fallthrough */
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user