Initial commit

This commit is contained in:
Martijn Braam
2023-07-08 18:30:07 +02:00
commit afbc15a68a
15 changed files with 1122 additions and 0 deletions

25
CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.25)
project(libmegapixels C)
set(LIBRARY_VERSION_MAJOR 0)
set(LIBRARY_VERSION_STRING 0.1)
set(CMAKE_C_STANDARD 23)
set(CMAKE_C_VISIBILITY_PRESET hidden)
add_library(megapixels SHARED include/libmegapixels.h src/findconfig.c src/parse.c src/mode.c src/pipeline.c src/log.c src/util.c)
set_target_properties(megapixels PROPERTIES
VERSION ${LIBRARY_VERSION_STRING}
SOVERSION ${LIBRARY_VERSION_MAJOR}
PUBLIC_HEADER include/libmegapixels.h)
target_include_directories(megapixels PUBLIC include)
target_link_libraries(megapixels "config")
add_executable(findconfig util/findconfig.c)
target_include_directories(findconfig PUBLIC include)
target_link_libraries(findconfig PUBLIC megapixels)
add_executable(getframe util/getframe.c)
target_include_directories(getframe PUBLIC include)
target_link_libraries(getframe PUBLIC megapixels)

View File

@@ -0,0 +1,62 @@
Version = 1;
Make: "PINE64";
Model: "PinePhone";
Rear: {
SensorDriver: "ov5640";
BridgeDriver: "sun6i-csi";
FlashPath: "/sys/class/leds/white:flash";
IsoMin: 100;
IsoMax: 64000;
Modes: (
{
Width: 2592;
Height: 1944;
Rate: 15;
Format: "BGGR8";
Rotate: 270;
FocalLength: 3.33;
FNumber: 3.0;
Pipeline: (
{Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi", ToPad: 0},
{Type: "Mode", Entity: "ov5640", Width: 2592, Height: 1944, Format: "BGGR8"},
);
},
{
Width: 1280;
Height: 720;
Rate: 30;
Format: "BGGR8";
FocalLength: 3.33;
FNumber: 3.0;
Pipeline: (
{Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi", ToPad: 0},
);
}
);
};
Front: {
SensorDriver: "gc2145";
BridgeDriver: "sun6i-csi";
FlashDisplay: true;
Modes: (
{
Width: 1280;
Height: 960;
Rate: 60;
Format: "BGGR8";
Rotate: 90;
Mirror: true;
Pipeline: (
{Type: "Link", From: "gc2145", FromPad: 0, To: "sun6i-csi", ToPad: 0},
);
}
);
};

31
config/uvc.conf Normal file
View File

@@ -0,0 +1,31 @@
Version = 1;
Make: "UVC";
Model: "UVC";
Rear: {
SensorDriver: "ov5640";
BridgeDriver: "uvcvideo";
FlashPath: "/sys/class/leds/white:flash";
IsoMin: 100;
IsoMax: 64000;
Modes: (
{
Width: 2592;
Height: 1944;
Rate: 15;
Format: "BGGR8";
Rotate: 270;
FocalLength: 3.33;
FNumber: 3.0;
},
{
Width: 1280;
Height: 720;
Rate: 30;
Format: "BGGR8";
FocalLength: 3.33;
FNumber: 3.0;
}
);
};

86
include/libmegapixels.h Normal file
View File

@@ -0,0 +1,86 @@
#ifndef LIBMEGAPIXELS_HEADER
#define LIBMEGAPIXELS_HEADER
#include <stdint.h>
#define EXPORT __attribute__((__visibility__("default")))
EXPORT int
libmegapixels_find_config(char *configfile);
#define LIBMEGAPIXELS_CMD_LINK 1
#define LIBMEGAPIXELS_CMD_MODE 2
struct _lmp_cmd {
int type;
const char *entity_from;
const char *entity_to;
int pad_from;
int pad_to;
int width;
int height;
uint32_t mode;
uint32_t entity_from_id;
int pad_from_id;
uint32_t entity_to_id;
int pad_to_id;
};
typedef struct _lmp_cmd libmegapixels_cmd;
struct _lmp_mode {
int width;
int height;
int rate;
int format;
int rotation;
double focal_length;
double f_number;
uint32_t v4l_pixfmt;
uint32_t media_busfmt;
int num_cmds;
libmegapixels_cmd *cmds;
};
typedef struct _lmp_mode libmegapixels_mode;
struct _lmp_camera {
char *name;
char *sensor_name;
char *bridge_name;
char *media_path;
char *sensor_path;
char *video_path;
int media_fd;
int sensor_fd;
int video_fd;
int num_modes;
libmegapixels_mode *modes;
};
typedef struct _lmp_camera libmegapixels_camera;
struct _lmp_device_config {
char *path;
char *make;
char *model;
int count;
libmegapixels_camera **cameras;
};
typedef struct _lmp_device_config libmegapixels_devconfig;
EXPORT int
libmegapixels_load_file(libmegapixels_devconfig **config, const char *file);
EXPORT int
libmegapixels_open(libmegapixels_camera *camera);
EXPORT unsigned int
libmegapixels_select_mode(libmegapixels_camera *camera, libmegapixels_mode *mode);
#endif

59
src/findconfig.c Normal file
View File

@@ -0,0 +1,59 @@
#include <stdio.h>
#include <unistd.h>
#include <libmegapixels.h>
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
#ifndef DATADIR
#define DATADIR "/usr/share"
#endif
static int
find_device_by_model(char *conffile, char *model)
{
// Check config/%model.conf in the current working directory
sprintf(conffile, "config/%s.conf", model);
if (access(conffile, F_OK) != -1) {
return 1;
}
// Check user overridden /etc/megapixels/config/%model.conf
sprintf(conffile, "%s/megapixels/config/%s.conf", SYSCONFDIR, model);
if (access(conffile, F_OK) != -1) {
return 1;
}
// Check packaged /usr/share/megapixels/config/%model.conf
sprintf(conffile, "%s/megapixels/config/%s.conf", DATADIR, model);
if (access(conffile, F_OK) != -1) {
return 1;
}
return 0;
}
int
libmegapixels_find_config(char *configfile)
{
char model[512];
FILE *fp;
if (access("/proc/device-tree/compatible", F_OK) == -1) {
return 0;
}
fp = fopen("/proc/device-tree/compatible", "r");
char *modelptr = model;
while (1) {
int c = fgetc(fp);
if (c == EOF) {
return 0;
}
*(modelptr++) = (char) c;
if (c == 0) {
if (find_device_by_model(configfile, model)) {
return 1;
}
modelptr = model;
}
}
}

13
src/log.c Normal file
View File

@@ -0,0 +1,13 @@
#include <stdarg.h>
#include <stdio.h>
#include "log.h"
void
log_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "[libmegapixels] ");
vfprintf(stderr, fmt, args);
va_end(args);
}

4
src/log.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
void
log_error(const char *fmt, ...);

91
src/mode.c Normal file
View File

@@ -0,0 +1,91 @@
#include <stdint.h>
#include <linux/v4l2-subdev.h>
#include <strings.h>
#include "mode.h"
struct libmegapixels_modename {
char *name;
uint32_t v4l_pixel_format;
uint32_t media_bus_format;
};
static struct libmegapixels_modename mode_lut[] = {
{
.name = "unsupported",
.v4l_pixel_format = 0,
.media_bus_format = 0,
},
{
.name = "BGGR8",
.v4l_pixel_format = V4L2_PIX_FMT_SBGGR8,
.media_bus_format = MEDIA_BUS_FMT_SBGGR8_1X8,
},
{
.name = "GBRG8",
.v4l_pixel_format = V4L2_PIX_FMT_SGBRG8,
.media_bus_format = MEDIA_BUS_FMT_SGBRG8_1X8,
},
{
.name = "GRBG8",
.v4l_pixel_format = V4L2_PIX_FMT_SGRBG8,
.media_bus_format = MEDIA_BUS_FMT_SGRBG8_1X8,
},
{
.name = "RGGB8",
.v4l_pixel_format = V4L2_PIX_FMT_SRGGB8,
.media_bus_format = MEDIA_BUS_FMT_SRGGB8_1X8,
},
{
.name = "BGGR10P",
.v4l_pixel_format = V4L2_PIX_FMT_SBGGR10P,
.media_bus_format = MEDIA_BUS_FMT_SBGGR10_1X10,
},
{
.name = "GBRG10P",
.v4l_pixel_format = V4L2_PIX_FMT_SGBRG10P,
.media_bus_format = MEDIA_BUS_FMT_SGBRG10_1X10,
},
{
.name = "GRBG10P",
.v4l_pixel_format = V4L2_PIX_FMT_SGRBG10P,
.media_bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
},
{
.name = "RGGB10P",
.v4l_pixel_format = V4L2_PIX_FMT_SRGGB10P,
.media_bus_format = MEDIA_BUS_FMT_SRGGB10_1X10,
},
{
.name = "UYVY",
.v4l_pixel_format = V4L2_PIX_FMT_UYVY,
.media_bus_format = MEDIA_BUS_FMT_UYVY8_2X8,
},
{
.name = "YUYV",
.v4l_pixel_format = V4L2_PIX_FMT_YUYV,
.media_bus_format = MEDIA_BUS_FMT_YUYV8_2X8,
},
};
uint32_t
format_name_to_v4l_pixfmt(const char *name)
{
for (int i = 0; i < sizeof(mode_lut); i++) {
if (strcasecmp(mode_lut[i].name, name) == 0) {
return mode_lut[i].v4l_pixel_format;
}
}
return 0;
}
uint32_t
format_name_to_media_busfmt(const char *name)
{
for (int i = 0; i < sizeof(mode_lut); i++) {
if (strcasecmp(mode_lut[i].name, name) == 0) {
return mode_lut[i].media_bus_format;
}
}
return 0;
}

7
src/mode.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
uint32_t
format_name_to_v4l_pixfmt(const char *name);
uint32_t
format_name_to_media_busfmt(const char *name);

334
src/parse.c Normal file
View File

@@ -0,0 +1,334 @@
#include <stdio.h>
#include <libconfig.h>
#include <malloc.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/media.h>
#include <stdint.h>
#include <limits.h>
#include "libmegapixels.h"
#include "mode.h"
#include "util.h"
char *
find_path_for_devnode(struct media_v2_intf_devnode devnode)
{
char uevent_path[PATH_MAX];
snprintf(uevent_path, PATH_MAX, "/sys/dev/char/%d:%d/uevent", devnode.major, devnode.minor);
FILE *fp = fopen(uevent_path, "r");
if (!fp) {
return NULL;
}
char line[PATH_MAX];
char path[PATH_MAX];
while (fgets(line, PATH_MAX, fp)) {
if (strncmp(line, "DEVNAME=", 8) == 0) {
// Drop newline
unsigned long length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
snprintf(path, length, "/dev/%s", line + 8);
return strdup(path);
}
}
}
int
find_media_node(libmegapixels_camera *camera, const char *media_name, const char *sensor_name)
{
struct dirent *dir;
DIR *d = opendir("/dev");
while ((dir = readdir(d)) != NULL) {
if (strncmp(dir->d_name, "media", 5) == 0) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/dev/%s", dir->d_name);
int media_fd = open(path, O_RDWR);
if (media_fd == -1) {
fprintf(stderr, "Could not open %s\n", path);
continue;
}
struct media_device_info mdi;
if (xioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdi) == -1) {
fprintf(stderr, "Could not MDI\n");
close(media_fd);
}
if (strcmp(mdi.driver, media_name) != 0 && strcmp(mdi.model, media_name) != 0) {
close(media_fd);
continue;
}
// This media device matches on model or driver, scan the entities for the sensor
struct media_v2_topology topology = {0};
if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1 ||
topology.num_entities == 0) {
close(media_fd);
continue;
}
struct media_v2_entity *entities = calloc(topology.num_entities, sizeof(struct media_v2_entity));
struct media_v2_interface *interfaces = calloc(topology.num_interfaces, sizeof(struct media_v2_interface));
struct media_v2_pad *pads = calloc(topology.num_pads, sizeof(struct media_v2_pad));
struct media_v2_link *links = calloc(topology.num_links, sizeof(struct media_v2_link));
topology.ptr_entities = (uint64_t) entities;
topology.ptr_interfaces = (uint64_t) interfaces;
topology.ptr_pads = (uint64_t) pads;
topology.ptr_links = (uint64_t) links;
if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1) {
close(media_fd);
continue;
}
// Find the sensor
unsigned long len = strlen(sensor_name);
int found = 0;
for (int i = 0; i < topology.num_entities; i++) {
if (strncmp(entities[i].name, sensor_name, len) == 0) {
found++;
for (int j = 0; j < topology.num_links; j++) {
if (links[j].sink_id != entities[i].id) {
continue;
}
for (int k = 0; k < topology.num_interfaces; k++) {
if (interfaces[k].id != links[j].source_id) {
continue;
}
camera->sensor_path = find_path_for_devnode(interfaces[k].devnode);
break;
}
}
break;
}
}
// Find the bridge
for (int i = 0; i < topology.num_entities; i++) {
if (entities[i].function == MEDIA_ENT_F_IO_V4L) {
found++;
for (int j = 0; j < topology.num_links; j++) {
if (links[j].sink_id != entities[i].id) {
continue;
}
for (int k = 0; k < topology.num_interfaces; k++) {
if (interfaces[k].id != links[j].source_id) {
continue;
}
camera->video_path = find_path_for_devnode(interfaces[k].devnode);
break;
}
}
// TODO: find /dev path for this entity
break;
}
}
close(media_fd);
if (found == 2) {
camera->media_path = strdup(path);
return 1;
}
}
}
closedir(d);
return -1;
}
int
load_camera(libmegapixels_devconfig *config, config_t *cfg, const char *name)
{
const char *sensor_driver, *bridge_driver;
config_setting_t *root = config_lookup(cfg, name);
if (!config_setting_lookup_string(root, "SensorDriver", &sensor_driver)) {
return -1;
}
if (!config_setting_lookup_string(root, "BridgeDriver", &bridge_driver)) {
return -1;
}
libmegapixels_camera *camera;
camera = malloc(sizeof(libmegapixels_camera));
int res = find_media_node(camera, bridge_driver, sensor_driver);
if (res < 0) {
free(camera);
return -1;
}
camera->name = strdup(name);
camera->sensor_name = strdup(sensor_driver);
camera->bridge_name = strdup(bridge_driver);
config->cameras = realloc(config->cameras, (config->count + 1) * sizeof(config->cameras));
if (config->cameras == NULL) {
return -1;
}
config->cameras[config->count++] = camera;
config_setting_t *modes = config_setting_lookup(root, "Modes");
config_setting_t *mode;
int num_modes = config_setting_length(modes);
camera->modes = malloc(num_modes * sizeof(libmegapixels_mode));
camera->num_modes = num_modes;
int n = 0;
while (1) {
mode = config_setting_get_elem(modes, n);
if (mode == NULL) {
break;
}
if (!config_setting_lookup_int(mode, "Width", &(camera->modes[n].width))) {
return -1;
}
if (!config_setting_lookup_int(mode, "Height", &(camera->modes[n].height))) {
return -1;
}
if (!config_setting_lookup_int(mode, "Rate", &(camera->modes[n].rate))) {
return -1;
}
const char *fmt;
config_setting_lookup_string(mode, "Format", &fmt);
camera->modes[n].v4l_pixfmt = format_name_to_v4l_pixfmt(fmt);
camera->modes[n].media_busfmt = format_name_to_media_busfmt(fmt);
if (!config_setting_lookup_int(mode, "Rotate", &(camera->modes[n].rotation))) {
camera->modes[n].rotation = 0;
}
if (!config_setting_lookup_float(mode, "FocalLength", &(camera->modes[n].focal_length))) {
camera->modes[n].focal_length = 0.0f;
}
if (!config_setting_lookup_float(mode, "FNumber", &(camera->modes[n].f_number))) {
camera->modes[n].f_number = 0.0f;
}
config_setting_t *cmds = config_setting_lookup(mode, "Pipeline");
config_setting_t *cmd;
if (cmds == NULL) {
fprintf(stderr, "Missing pipeline\n");
n++;
continue;
}
int num_cmds = config_setting_length(cmds);
camera->modes[n].cmds = malloc(num_cmds * sizeof(libmegapixels_cmd));
camera->modes[n].num_cmds = num_cmds;
int m = 0;
while (1) {
cmd = config_setting_get_elem(cmds, m);
if (cmd == NULL) {
break;
}
libmegapixels_cmd *cur = &(camera->modes[n].cmds[m]);
const char *type;
if (!config_setting_lookup_string(cmd, "Type", &type)) {
break;
}
if (strcmp(type, "Link") == 0) {
camera->modes[n].cmds[m].type = LIBMEGAPIXELS_CMD_LINK;
if (!config_setting_lookup_string(cmd, "From", &cur->entity_from)) {
fprintf(stderr, "Missing From\n");
break;
}
if (!config_setting_lookup_string(cmd, "To", &cur->entity_to)) {
fprintf(stderr, "Missing To\n");
break;
}
if (!config_setting_lookup_int(cmd, "FromPad", &cur->pad_from)) {
cur->pad_from = 0;
}
if (!config_setting_lookup_int(cmd, "ToPad", &cur->pad_to)) {
cur->pad_to = 0;
}
} else if (strcmp(type, "Mode") == 0) {
camera->modes[n].cmds[m].type = LIBMEGAPIXELS_CMD_MODE;
if (!config_setting_lookup_string(cmd, "Entity", &cur->entity_from)) {
fprintf(stderr, "Missing entity\n");
break;
}
if (!config_setting_lookup_int(cmd, "Width", &cur->width)) {
cur->width = camera->modes[n].width;
}
if (!config_setting_lookup_int(cmd, "Height", &cur->height)) {
cur->height = camera->modes[n].height;
}
}
m++;
}
n++;
}
return 0;
}
int
libmegapixels_load_file(libmegapixels_devconfig **config, const char *file)
{
config_t cfg;
config_setting_t *setting, *member;
*config = malloc(sizeof(libmegapixels_devconfig));
(*config)->path = strdup(file);
(*config)->count = 0;
(*config)->cameras = malloc(sizeof((*config)->cameras));
config_init(&cfg);
if (!config_read_file(&cfg, file)) {
fprintf(stderr, "Could not read %s\n", file);
config_destroy(&cfg);
return 0;
}
int version = 0;
if (config_lookup_int(&cfg, "Version", &version)) {
if (version != 1) {
fprintf(stderr, "Invalid version %d\n", version);
return 0;
}
} else {
fprintf(stderr, "Missing version\n");
return 0;
}
if (!config_lookup_string(&cfg, "Make", (const char **) &(*config)->make)) {
(*config)->make = strdup("Megapixels");
}
if (!config_lookup_string(&cfg, "Model", (const char **) &(*config)->model)) {
(*config)->model = strdup("Camera");
}
setting = config_root_setting(&cfg);
int n = 0;
while (1) {
member = config_setting_get_elem(setting, n++);
if (member == NULL) {
break;
}
if (strcmp(member->name, "Version") == 0) {
continue;
}
if (strcmp(member->name, "Make") == 0) {
continue;
}
if (strcmp(member->name, "Model") == 0) {
continue;
}
load_camera(*config, &cfg, member->name);
}
return 1;
}

201
src/pipeline.c Normal file
View File

@@ -0,0 +1,201 @@
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "libmegapixels.h"
#include "log.h"
#include "util.h"
int
setup_link(libmegapixels_camera *camera, uint32_t source_entity_id, uint32_t sink_entity_id, int enabled)
{
struct media_link_desc link = {};
link.flags = (enabled > 0) ? MEDIA_LNK_FL_ENABLED : 0;
link.source.entity = source_entity_id;
link.source.index = 0;
link.sink.entity = sink_entity_id;
link.sink.index = 0;
if (xioctl(camera->media_fd, MEDIA_IOC_SETUP_LINK, &link) == -1) {
log_error("Could not setup link: %s\n", strerror(errno));
return -1;
}
return 0;
}
int
load_entity_ids(libmegapixels_camera *camera)
{
// This media device matches on model or driver, scan the entities for the sensor
struct media_v2_topology topology = {0};
if (xioctl(camera->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1 ||
topology.num_entities == 0) {
close(camera->media_fd);
return -1;
}
struct media_v2_entity *entities = calloc(topology.num_entities, sizeof(struct media_v2_entity));
struct media_v2_interface *interfaces = calloc(topology.num_interfaces, sizeof(struct media_v2_interface));
struct media_v2_pad *pads = calloc(topology.num_pads, sizeof(struct media_v2_pad));
struct media_v2_link *links = calloc(topology.num_links, sizeof(struct media_v2_link));
topology.ptr_entities = (uint64_t) entities;
topology.ptr_interfaces = (uint64_t) interfaces;
topology.ptr_pads = (uint64_t) pads;
topology.ptr_links = (uint64_t) links;
if (xioctl(camera->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1) {
close(camera->media_fd);
return -1;
}
for (int i = 0; i < camera->num_modes; i++) {
libmegapixels_mode *mode = &camera->modes[i];
for (int j = 0; j < mode->num_cmds; j++) {
libmegapixels_cmd *cmd = &mode->cmds[j];
if (cmd->type == LIBMEGAPIXELS_CMD_LINK) {
int found_from = 0;
int found_to = 0;
for (int k = 0; k < topology.num_entities; k++) {
if (strncmp(entities[k].name, cmd->entity_from, strlen(cmd->entity_from)) == 0) {
cmd->entity_from_id = entities[k].id;
found_from++;
}
if (strncmp(entities[k].name, cmd->entity_to, strlen(cmd->entity_to)) == 0) {
cmd->entity_to_id = entities[k].id;
found_to++;
}
}
if (found_from != 1) {
log_error("Could not find entity '%s'\n", cmd->entity_from);
return -1;
}
if (found_to != 1) {
log_error("Could not find entity '%s'\n", cmd->entity_to);
return -1;
}
}
}
}
for (int i = 0; i < topology.num_links; i++) {
log_error("Link %d flags", i);
if (links[i].flags & MEDIA_LNK_FL_ENABLED) {
fprintf(stderr, " [enabled]");
}
if (links[i].flags & MEDIA_LNK_FL_IMMUTABLE) {
fprintf(stderr, " [immutable]");
}
if (links[i].flags & MEDIA_LNK_FL_DYNAMIC) {
fprintf(stderr, " [dynamic]");
}
if (links[i].flags & MEDIA_LNK_FL_INTERFACE_LINK) {
fprintf(stderr, " [interface-link]");
}
fprintf(stderr, "\n");
if (links[i].flags & MEDIA_LNK_FL_ENABLED && !(links[i].flags & MEDIA_LNK_FL_IMMUTABLE)) {
uint32_t source_entity, sink_entity;
for (int j = 0; j < topology.num_pads; j++) {
if (pads[j].id == links[i].source_id) {
source_entity = pads[j].entity_id;
} else if (pads[j].id == links[i].sink_id) {
sink_entity = pads[j].entity_id;
}
}
setup_link(camera, source_entity, sink_entity, 0);
}
}
return 0;
}
int
libmegapixels_open(libmegapixels_camera *camera)
{
if (camera->media_fd != 0) {
log_error("Camera already opened\n");
return -1;
}
if (camera->sensor_fd != 0) {
log_error("Sensor already opened\n");
return -1;
}
if (camera->video_fd != 0) {
log_error("Bridge already opened\n");
return -1;
}
camera->media_fd = open(camera->media_path, O_RDWR);
if (camera->media_fd < 0) {
log_error("Could not open %s: %s\n", camera->media_path, strerror(errno));
return -1;
}
camera->sensor_fd = open(camera->sensor_path, O_RDWR);
if (camera->sensor_fd < 0) {
log_error("Could not open %s: %s\n", camera->sensor_path, strerror(errno));
return -1;
}
camera->video_fd = open(camera->video_path, O_RDWR);
if (camera->video_fd < 0) {
log_error("Could not open %s: %s\n", camera->video_path, strerror(errno));
return -1;
}
int ret = load_entity_ids(camera);
if (ret < 0) {
return ret;
}
return 0;
}
unsigned int
libmegapixels_select_mode(libmegapixels_camera *camera, libmegapixels_mode *mode)
{
for (int i = 0; i < mode->num_cmds; i++) {
libmegapixels_cmd *cmd = &mode->cmds[i];
struct v4l2_subdev_format subdev_fmt = {};
fprintf(stderr, "Do %d\n", cmd->type);
switch (cmd->type) {
case LIBMEGAPIXELS_CMD_LINK:
if (setup_link(camera, cmd->entity_from_id, cmd->entity_to_id, 1) != 0) {
log_error("Could not link %d -> %d [%s -> %s] \n", cmd->entity_from_id, cmd->entity_to_id,
cmd->entity_from,
cmd->entity_to);
}
break;
case LIBMEGAPIXELS_CMD_MODE:
subdev_fmt.pad = cmd->pad_to;
subdev_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
subdev_fmt.format.width = cmd->width;
subdev_fmt.format.height = cmd->height;
subdev_fmt.format.code = mode->media_busfmt;
subdev_fmt.format.field = V4L2_FIELD_ANY;
if (xioctl(camera->sensor_fd, VIDIOC_SUBDEV_S_FMT, &subdev_fmt) == -1) {
log_error("Could not set mode on sensor: %s\n", strerror(errno));
}
break;
}
}
struct v4l2_format format = {0};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = mode->width;
format.fmt.pix.height = mode->height;
format.fmt.pix.pixelformat = mode->v4l_pixfmt;
format.fmt.pix.field = V4L2_FIELD_ANY;
if (xioctl(camera->video_fd, VIDIOC_S_FMT, &format) == -1) {
log_error("Could not set mode on bridge: %s\n", strerror(errno));
return 0;
}
return format.fmt.pix.sizeimage;
}

13
src/util.c Normal file
View File

@@ -0,0 +1,13 @@
#include <sys/ioctl.h>
#include <errno.h>
#include "util.h"
int
xioctl(int fd, int request, void *arg)
{
int r;
do {
r = ioctl(fd, request, arg);
} while (r == -1 && errno == EINTR);
return r;
}

4
src/util.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
int
xioctl(int fd, int request, void *arg);

40
util/findconfig.c Normal file
View File

@@ -0,0 +1,40 @@
#include <libmegapixels.h>
#include <stdio.h>
#include <limits.h>
int
main()
{
char configpath[PATH_MAX];
int ret = libmegapixels_find_config(configpath);
libmegapixels_devconfig *config = {0};
if (ret) {
printf("Using config: %s\n", configpath);
libmegapixels_load_file(&config, configpath);
} else {
printf("No config found\n");
libmegapixels_load_file(&config, "config/uvc.conf");
}
printf("Device: %s %s\n", config->make, config->model);
printf("Found %d cameras\n", config->count);
for (int i = 0; i < config->count; i++) {
printf("\n----[ Camera %s (%d) ]----\n", config->cameras[i]->name, i);
printf("Media : %s (%s)\n", config->cameras[i]->bridge_name, config->cameras[i]->media_path);
printf("Sensor: %s (%s)\n", config->cameras[i]->sensor_name, config->cameras[i]->sensor_path);
printf("Bridge: %s\n", config->cameras[i]->video_path);
printf("Modes : ");
for (int j = 0; j < config->cameras[i]->num_modes; j++) {
if (j > 0) {
printf(" ");
}
libmegapixels_mode *mode = &config->cameras[i]->modes[j];
printf("%dx%d@%d\n", mode->width, mode->height, mode->rate);
}
}
return 0;
}

152
util/getframe.c Normal file
View File

@@ -0,0 +1,152 @@
#include <libmegapixels.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
struct buffer {
void *start;
size_t length;
};
struct buffer *buffers;
int
xioctl(int fd, int request, void *arg)
{
int r;
do {
r = ioctl(fd, request, arg);
} while (r == -1 && errno == EINTR);
return r;
}
int
main()
{
char configpath[PATH_MAX];
int ret = libmegapixels_find_config(configpath);
libmegapixels_devconfig *config = {0};
if (ret) {
printf("Using config: %s\n", configpath);
libmegapixels_load_file(&config, configpath);
} else {
fprintf(stderr, "No config found for this device\n");
return 1;
}
if (config->count == 0) {
fprintf(stderr, "No valid camera configuration\n");
return 1;
}
libmegapixels_camera *camera = config->cameras[0];
if (libmegapixels_open(camera) != 0) {
fprintf(stderr, "Could not open default camera\n");
return 1;
}
libmegapixels_mode *mode = &camera->modes[0];
unsigned int frame_size = libmegapixels_select_mode(camera, mode);
if (frame_size == 0) {
fprintf(stderr, "Could not select mode\n");
return 1;
}
// Do the reqular V4L2 stuff to get a frame
struct v4l2_capability cap;
if (ioctl(camera->video_fd, VIDIOC_QUERYCAP, &cap) != 0) {
fprintf(stderr, "VIDIOC_QUERYCAP failed\n");
return 1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "Device does not support streaming i/o\n");
return 1;
}
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) {
fprintf(stderr, "VIDIOC_REQBUFS failed\n");
return 1;
}
buffers = calloc(req.count, sizeof(*buffers));
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(camera->video_fd, VIDIOC_QUERYBUF, &buf) == -1) {
fprintf(stderr, "VIDIOC_QUERYBUF failed\n");
return 1;
}
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camera->video_fd, buf.m.offset);
if (buffers[i].start == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
return 1;
}
}
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer qbuf = {0};
qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
qbuf.memory = V4L2_MEMORY_MMAP;
qbuf.index = i;
if (xioctl(camera->video_fd, VIDIOC_QBUF, &qbuf) == -1) {
fprintf(stderr, "VIDIOC_QBUF failed\n");
return 1;
}
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(camera->video_fd, VIDIOC_STREAMON, &type) == -1) {
fprintf(stderr, "VIDIOC_STREAMON failed\n");
return 1;
}
int count = 5;
while (count-- > 0) {
while (1) {
fd_set(fds);
FD_ZERO(&fds);
FD_SET(camera->video_fd, &fds);
int sr = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
if (sr == -1) {
if (errno == EINTR) {
count++;
continue;
}
fprintf(stderr, "select() failed: %s\n", strerror(errno));
return 1;
}
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(camera->video_fd, VIDIOC_DQBUF, &buf) == -1) {
fprintf(stderr, "VIDIOC_DQBUF failed\n");
return 1;
}
fprintf(stderr, "GOT FRAME!\n");
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
fprintf(stderr, "VIDIOC_DQBUF failed\n");
return 1;
}
break;
}
}
return 0;
}